mirror of https://github.com/helix-editor/helix
Merge 6c6ddc9e3a
into cb1ecc9128
commit
de41398281
|
@ -83,12 +83,44 @@ impl EditorView {
|
||||||
surface: &mut Surface,
|
surface: &mut Surface,
|
||||||
is_focused: bool,
|
is_focused: bool,
|
||||||
) {
|
) {
|
||||||
let inner = view.inner_area(doc);
|
let areas = view.get_areas(doc);
|
||||||
|
|
||||||
|
// TODO: use the 'areas' struct for these!
|
||||||
|
let inner = areas.text;
|
||||||
let area = view.area;
|
let area = view.area;
|
||||||
let theme = &editor.theme;
|
let theme = &editor.theme;
|
||||||
let config = editor.config();
|
let config = editor.config();
|
||||||
|
|
||||||
let view_offset = doc.view_offset(view.id);
|
if config.nullspace.enable {
|
||||||
|
// helper:
|
||||||
|
fn fill_area(s: &mut Surface, r: Rect, c: char, style: Style) {
|
||||||
|
if r.width > 0 && r.height > 0 {
|
||||||
|
s.set_style(r, style);
|
||||||
|
for y in r.top()..r.bottom() {
|
||||||
|
for x in r.left()..r.right() {
|
||||||
|
let cell = s.get_mut(x, y).unwrap();
|
||||||
|
cell.symbol.clear();
|
||||||
|
cell.symbol.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if areas.left.width > 0 || areas.right.width > 0 {
|
||||||
|
let style = theme
|
||||||
|
.try_get("ui.nullspace")
|
||||||
|
.or_else(|| Some(theme.get("ui.linenr")))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// We currently on use the first
|
||||||
|
// char in the 'pattern'.
|
||||||
|
//
|
||||||
|
let c = config.nullspace.pattern.chars().nth(0).unwrap();
|
||||||
|
|
||||||
|
fill_area(surface, areas.left, c, style);
|
||||||
|
fill_area(surface, areas.right, c, style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let text_annotations = view.text_annotations(doc, Some(theme));
|
let text_annotations = view.text_annotations(doc, Some(theme));
|
||||||
let mut decorations = DecorationManager::default();
|
let mut decorations = DecorationManager::default();
|
||||||
|
@ -115,6 +147,8 @@ impl EditorView {
|
||||||
decorations.add_decoration(line_decoration);
|
decorations.add_decoration(line_decoration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let view_offset = doc.view_offset(view.id);
|
||||||
|
|
||||||
let syntax_highlights =
|
let syntax_highlights =
|
||||||
Self::doc_syntax_highlights(doc, view_offset.anchor, inner.height, theme);
|
Self::doc_syntax_highlights(doc, view_offset.anchor, inner.height, theme);
|
||||||
|
|
||||||
|
@ -163,13 +197,15 @@ impl EditorView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let gutter_overflow = view.gutter_offset(doc) == 0;
|
let gutter_width = view.gutter_offset(doc);
|
||||||
if !gutter_overflow {
|
if gutter_width > 0 {
|
||||||
|
let gutter_area = inner.with_width(gutter_width).shift_left(gutter_width);
|
||||||
|
|
||||||
Self::render_gutter(
|
Self::render_gutter(
|
||||||
editor,
|
editor,
|
||||||
doc,
|
doc,
|
||||||
view,
|
view,
|
||||||
view.area,
|
gutter_area,
|
||||||
theme,
|
theme,
|
||||||
is_focused & self.terminal_focused,
|
is_focused & self.terminal_focused,
|
||||||
&mut decorations,
|
&mut decorations,
|
||||||
|
@ -201,6 +237,7 @@ impl EditorView {
|
||||||
inline_diagnostic_config,
|
inline_diagnostic_config,
|
||||||
config.end_of_line_diagnostics,
|
config.end_of_line_diagnostics,
|
||||||
));
|
));
|
||||||
|
|
||||||
render_document(
|
render_document(
|
||||||
surface,
|
surface,
|
||||||
inner,
|
inner,
|
||||||
|
@ -216,7 +253,7 @@ impl EditorView {
|
||||||
// if we're not at the edge of the screen, draw a right border
|
// if we're not at the edge of the screen, draw a right border
|
||||||
if viewport.right() != view.area.right() {
|
if viewport.right() != view.area.right() {
|
||||||
let x = area.right();
|
let x = area.right();
|
||||||
let border_style = theme.get("ui.window");
|
let border_style = theme.try_get("ui.divide").unwrap_or(theme.get("ui.window"));
|
||||||
for y in area.top()..area.bottom() {
|
for y in area.top()..area.bottom() {
|
||||||
surface[(x, y)]
|
surface[(x, y)]
|
||||||
.set_symbol(tui::symbols::line::VERTICAL)
|
.set_symbol(tui::symbols::line::VERTICAL)
|
||||||
|
@ -228,7 +265,7 @@ impl EditorView {
|
||||||
if config.inline_diagnostics.disabled()
|
if config.inline_diagnostics.disabled()
|
||||||
&& config.end_of_line_diagnostics == DiagnosticFilter::Disable
|
&& config.end_of_line_diagnostics == DiagnosticFilter::Disable
|
||||||
{
|
{
|
||||||
Self::render_diagnostics(doc, view, inner, surface, theme);
|
Self::render_diagnostics(doc, view, surface, theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
let statusline_area = view
|
let statusline_area = view
|
||||||
|
@ -726,13 +763,7 @@ impl EditorView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_diagnostics(
|
pub fn render_diagnostics(doc: &Document, view: &View, surface: &mut Surface, theme: &Theme) {
|
||||||
doc: &Document,
|
|
||||||
view: &View,
|
|
||||||
viewport: Rect,
|
|
||||||
surface: &mut Surface,
|
|
||||||
theme: &Theme,
|
|
||||||
) {
|
|
||||||
use helix_core::diagnostic::Severity;
|
use helix_core::diagnostic::Severity;
|
||||||
use tui::{
|
use tui::{
|
||||||
layout::Alignment,
|
layout::Alignment,
|
||||||
|
@ -754,11 +785,56 @@ impl EditorView {
|
||||||
let info = theme.get("info");
|
let info = theme.get("info");
|
||||||
let hint = theme.get("hint");
|
let hint = theme.get("hint");
|
||||||
|
|
||||||
|
let areas = view.get_areas(doc);
|
||||||
|
let width = 50.min(areas.text.width);
|
||||||
|
|
||||||
|
let height = 15.min(areas.text.height); // .min(text.lines.len() as u16);
|
||||||
|
|
||||||
|
// decide where this diagnostic is gonna get rendered
|
||||||
|
//
|
||||||
|
let mut x = areas.text.right() - width;
|
||||||
|
let mut y = areas.text.top() + 1;
|
||||||
|
let mut align = Alignment::Right;
|
||||||
|
let mut background = theme.get("ui.background");
|
||||||
|
|
||||||
|
// do we have space in the nullspace to render the diagnostic?
|
||||||
|
//
|
||||||
|
const PADDING: u16 = 2;
|
||||||
|
let width_pad = width + PADDING;
|
||||||
|
|
||||||
|
if areas.left.width >= width_pad || areas.right.width >= width_pad {
|
||||||
|
if let Some(pos) = view.screen_coords_at_pos(doc, doc.text().slice(..), cursor) {
|
||||||
|
background = theme
|
||||||
|
.try_get("ui.nullspace")
|
||||||
|
.unwrap_or(theme.get("ui.background"));
|
||||||
|
|
||||||
|
// decide if we are placing the diagnositcs in the
|
||||||
|
// left or right nullspace?
|
||||||
|
//
|
||||||
|
y = pos.row as u16 + areas.text.top();
|
||||||
|
|
||||||
|
x = if areas.left.width >= width_pad {
|
||||||
|
align = Alignment::Right;
|
||||||
|
areas.left.right() - PADDING - width
|
||||||
|
} else {
|
||||||
|
align = Alignment::Left;
|
||||||
|
areas.right.left() + PADDING
|
||||||
|
};
|
||||||
|
|
||||||
|
// correct for overflow in y position
|
||||||
|
//
|
||||||
|
if y + height > areas.text.bottom() {
|
||||||
|
y -= (y + height) - areas.text.bottom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// build diagnostic lines
|
||||||
|
//
|
||||||
let mut lines = Vec::new();
|
let mut lines = Vec::new();
|
||||||
let background_style = theme.get("ui.background");
|
|
||||||
for diagnostic in diagnostics {
|
for diagnostic in diagnostics {
|
||||||
let style = Style::reset()
|
let style = Style::reset()
|
||||||
.patch(background_style)
|
.patch(background)
|
||||||
.patch(match diagnostic.severity {
|
.patch(match diagnostic.severity {
|
||||||
Some(Severity::Error) => error,
|
Some(Severity::Error) => error,
|
||||||
Some(Severity::Warning) | None => warning,
|
Some(Severity::Warning) | None => warning,
|
||||||
|
@ -778,15 +854,12 @@ impl EditorView {
|
||||||
}
|
}
|
||||||
|
|
||||||
let text = Text::from(lines);
|
let text = Text::from(lines);
|
||||||
|
|
||||||
let paragraph = Paragraph::new(&text)
|
let paragraph = Paragraph::new(&text)
|
||||||
.alignment(Alignment::Right)
|
.alignment(align)
|
||||||
.wrap(Wrap { trim: true });
|
.wrap(Wrap { trim: true });
|
||||||
let width = 100.min(viewport.width);
|
|
||||||
let height = 15.min(viewport.height);
|
paragraph.render(Rect::new(x, y, width, height), surface);
|
||||||
paragraph.render(
|
|
||||||
Rect::new(viewport.right() - width, viewport.y + 1, width, height),
|
|
||||||
surface,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply the highlighting on the lines where a cursor is active
|
/// Apply the highlighting on the lines where a cursor is active
|
||||||
|
@ -809,7 +882,7 @@ impl EditorView {
|
||||||
|
|
||||||
let primary_style = theme.get("ui.cursorline.primary");
|
let primary_style = theme.get("ui.cursorline.primary");
|
||||||
let secondary_style = theme.get("ui.cursorline.secondary");
|
let secondary_style = theme.get("ui.cursorline.secondary");
|
||||||
let viewport = view.area;
|
let viewport = view.inner_area(doc);
|
||||||
|
|
||||||
move |renderer: &mut TextRenderer, pos: LinePos| {
|
move |renderer: &mut TextRenderer, pos: LinePos| {
|
||||||
let area = Rect::new(viewport.x, pos.visual_line, viewport.width, 1);
|
let area = Rect::new(viewport.x, pos.visual_line, viewport.width, 1);
|
||||||
|
|
|
@ -329,6 +329,8 @@ pub struct Config {
|
||||||
/// Column numbers at which to draw the rulers. Defaults to `[]`, meaning no rulers.
|
/// Column numbers at which to draw the rulers. Defaults to `[]`, meaning no rulers.
|
||||||
pub rulers: Vec<u16>,
|
pub rulers: Vec<u16>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub nullspace: NullspaceConfig,
|
||||||
|
#[serde(default)]
|
||||||
pub whitespace: WhitespaceConfig,
|
pub whitespace: WhitespaceConfig,
|
||||||
/// Persistently display open buffers along the top
|
/// Persistently display open buffers along the top
|
||||||
pub bufferline: BufferLine,
|
pub bufferline: BufferLine,
|
||||||
|
@ -733,6 +735,22 @@ impl std::str::FromStr for GutterType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct NullspaceConfig {
|
||||||
|
pub enable: bool,
|
||||||
|
pub pattern: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NullspaceConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
enable: false,
|
||||||
|
pattern: String::from("╲"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct WhitespaceConfig {
|
pub struct WhitespaceConfig {
|
||||||
|
@ -997,6 +1015,7 @@ impl Default for Config {
|
||||||
lsp: LspConfig::default(),
|
lsp: LspConfig::default(),
|
||||||
terminal: get_terminal_provider(),
|
terminal: get_terminal_provider(),
|
||||||
rulers: Vec::new(),
|
rulers: Vec::new(),
|
||||||
|
nullspace: NullspaceConfig::default(),
|
||||||
whitespace: WhitespaceConfig::default(),
|
whitespace: WhitespaceConfig::default(),
|
||||||
bufferline: BufferLine::default(),
|
bufferline: BufferLine::default(),
|
||||||
indent_guides: IndentGuidesConfig::default(),
|
indent_guides: IndentGuidesConfig::default(),
|
||||||
|
|
|
@ -153,6 +153,22 @@ impl Rect {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a new Rect shifted left.
|
||||||
|
pub fn shift_left(self, shift: u16) -> Rect {
|
||||||
|
Rect {
|
||||||
|
x: self.x - shift,
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a new Rect shifted left.
|
||||||
|
pub fn shift_right(self, shift: u16) -> Rect {
|
||||||
|
Rect {
|
||||||
|
x: self.x + shift,
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Returns a new Rect with height reduced from the bottom.
|
// Returns a new Rect with height reduced from the bottom.
|
||||||
// This does _not_ change the `y` coordinate.
|
// This does _not_ change the `y` coordinate.
|
||||||
pub fn clip_bottom(self, height: u16) -> Rect {
|
pub fn clip_bottom(self, height: u16) -> Rect {
|
||||||
|
@ -238,6 +254,79 @@ impl Rect {
|
||||||
&& self.y < other.y + other.height
|
&& self.y < other.y + other.height
|
||||||
&& self.y + self.height > other.y
|
&& self.y + self.height > other.y
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn split_left(self, width: u16) -> (Rect, Rect) {
|
||||||
|
let width = width.min(self.width);
|
||||||
|
(
|
||||||
|
Rect { width, ..self },
|
||||||
|
Rect {
|
||||||
|
x: self.x + width,
|
||||||
|
width: self.width - width,
|
||||||
|
..self
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split_right(self, width: u16) -> (Rect, Rect) {
|
||||||
|
let width = width.min(self.width);
|
||||||
|
(
|
||||||
|
Rect {
|
||||||
|
width: self.width - width,
|
||||||
|
..self
|
||||||
|
},
|
||||||
|
Rect {
|
||||||
|
x: self.right() - width,
|
||||||
|
width,
|
||||||
|
..self
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split_top(self, height: u16) -> (Rect, Rect) {
|
||||||
|
let height = height.min(self.height);
|
||||||
|
(
|
||||||
|
Rect { height, ..self },
|
||||||
|
Rect {
|
||||||
|
y: self.y + height,
|
||||||
|
height: self.height - height,
|
||||||
|
..self
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split_bottom(self, height: u16) -> (Rect, Rect) {
|
||||||
|
let height = height.min(self.height);
|
||||||
|
(
|
||||||
|
Rect {
|
||||||
|
height: self.height - height,
|
||||||
|
..self
|
||||||
|
},
|
||||||
|
Rect {
|
||||||
|
y: self.bottom() - height,
|
||||||
|
height,
|
||||||
|
..self
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split_centre_vertical(self, width: u16) -> (Rect, Rect, Rect) {
|
||||||
|
let width = width.min(self.width);
|
||||||
|
let l = (self.width - width) / 2;
|
||||||
|
let r = self.width - width - l;
|
||||||
|
(
|
||||||
|
Rect { width: l, ..self },
|
||||||
|
Rect {
|
||||||
|
width,
|
||||||
|
x: self.x + l,
|
||||||
|
..self
|
||||||
|
},
|
||||||
|
Rect {
|
||||||
|
width: r,
|
||||||
|
x: self.right() - r,
|
||||||
|
..self
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
|
|
@ -373,6 +373,7 @@ impl Tree {
|
||||||
match &mut node.content {
|
match &mut node.content {
|
||||||
Content::View(view) => {
|
Content::View(view) => {
|
||||||
// debug!!("setting view area {:?}", area);
|
// debug!!("setting view area {:?}", area);
|
||||||
|
view.container_area = self.area;
|
||||||
view.area = area;
|
view.area = area;
|
||||||
} // TODO: call f()
|
} // TODO: call f()
|
||||||
Content::Container(container) => {
|
Content::Container(container) => {
|
||||||
|
|
|
@ -25,6 +25,22 @@ use std::{
|
||||||
|
|
||||||
const JUMP_LIST_CAPACITY: usize = 30;
|
const JUMP_LIST_CAPACITY: usize = 30;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct ViewAreas {
|
||||||
|
/// left-hand nullspace area
|
||||||
|
pub left: Rect,
|
||||||
|
/// right-hand nullspace area
|
||||||
|
pub right: Rect,
|
||||||
|
pub gutter: Rect,
|
||||||
|
pub text: Rect,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewAreas {
|
||||||
|
pub fn has_nullspace(&self) -> bool {
|
||||||
|
self.left.width > 0 || self.right.width > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Jump = (DocumentId, Selection);
|
type Jump = (DocumentId, Selection);
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -129,6 +145,7 @@ pub struct ViewPosition {
|
||||||
pub struct View {
|
pub struct View {
|
||||||
pub id: ViewId,
|
pub id: ViewId,
|
||||||
pub area: Rect,
|
pub area: Rect,
|
||||||
|
pub container_area: Rect,
|
||||||
pub doc: DocumentId,
|
pub doc: DocumentId,
|
||||||
pub jumps: JumpList,
|
pub jumps: JumpList,
|
||||||
// documents accessed from this view from the oldest one to last viewed one
|
// documents accessed from this view from the oldest one to last viewed one
|
||||||
|
@ -173,6 +190,7 @@ impl View {
|
||||||
id: ViewId::default(),
|
id: ViewId::default(),
|
||||||
doc,
|
doc,
|
||||||
area: Rect::default(), // will get calculated upon inserting into tree
|
area: Rect::default(), // will get calculated upon inserting into tree
|
||||||
|
container_area: Rect::default(), // will get calculated upon inserting into tree
|
||||||
jumps: JumpList::new((doc, Selection::point(0))), // TODO: use actual sel
|
jumps: JumpList::new((doc, Selection::point(0))), // TODO: use actual sel
|
||||||
docs_access_history: Vec::new(),
|
docs_access_history: Vec::new(),
|
||||||
last_modified_docs: [None, None],
|
last_modified_docs: [None, None],
|
||||||
|
@ -190,8 +208,68 @@ impl View {
|
||||||
self.docs_access_history.push(id);
|
self.docs_access_history.push(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_areas(&self, doc: &Document) -> ViewAreas {
|
||||||
|
let config = doc.config.load();
|
||||||
|
|
||||||
|
let gutter = self.gutter_offset(doc);
|
||||||
|
let area = self.area.clip_bottom(1); // -1 for status line
|
||||||
|
|
||||||
|
// if we are not using nullspace, then we use the whole
|
||||||
|
// area (without nullspace)
|
||||||
|
//
|
||||||
|
if config.nullspace.enable == false {
|
||||||
|
let (gutter, text) = self.area.split_left(gutter);
|
||||||
|
return ViewAreas {
|
||||||
|
gutter,
|
||||||
|
text,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// midpoint (with tolerances)
|
||||||
|
//
|
||||||
|
let mid_lo = (self.container_area.width / 2) - 2;
|
||||||
|
let mid_hi = (self.container_area.width / 2) + 2;
|
||||||
|
|
||||||
|
let width = config.text_width as u16 + gutter;
|
||||||
|
|
||||||
|
if area.right() < mid_hi {
|
||||||
|
// align: right
|
||||||
|
let (left, text) = area.split_right(width);
|
||||||
|
let (gutter, text) = text.split_left(gutter);
|
||||||
|
ViewAreas {
|
||||||
|
left,
|
||||||
|
right: Rect::default(),
|
||||||
|
gutter,
|
||||||
|
text,
|
||||||
|
}
|
||||||
|
} else if area.left() > mid_lo {
|
||||||
|
// align: left
|
||||||
|
let (text, right) = area.split_left(width);
|
||||||
|
let (gutter, text) = text.split_left(gutter);
|
||||||
|
ViewAreas {
|
||||||
|
left: Rect::default(),
|
||||||
|
right,
|
||||||
|
gutter,
|
||||||
|
text,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// align: center
|
||||||
|
let (left, text, right) = area.split_centre_vertical(width);
|
||||||
|
let (gutter, text) = text.split_left(gutter);
|
||||||
|
ViewAreas {
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
gutter,
|
||||||
|
text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the 'inner area' (the text renderable area) of the view.
|
||||||
|
///
|
||||||
pub fn inner_area(&self, doc: &Document) -> Rect {
|
pub fn inner_area(&self, doc: &Document) -> Rect {
|
||||||
self.area.clip_left(self.gutter_offset(doc)).clip_bottom(1) // -1 for statusline
|
self.get_areas(doc).text
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inner_height(&self) -> usize {
|
pub fn inner_height(&self) -> usize {
|
||||||
|
|
Loading…
Reference in New Issue