From 60dde3a40928e010e518b225b948c413f995a383 Mon Sep 17 00:00:00 2001 From: Stephen Broadley Date: Fri, 2 Aug 2024 18:53:23 +0100 Subject: [PATCH 01/19] added 'nullspace' option --- helix-term/src/ui/editor.rs | 56 ++++++++++++++++++++++++++++++++++--- helix-view/src/editor.rs | 19 +++++++++++++ helix-view/src/view.rs | 20 ++++++++++++- 3 files changed, 90 insertions(+), 5 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index f7541fe25..61a8f392f 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -93,7 +93,51 @@ impl EditorView { let theme = &editor.theme; let config = editor.config(); - let view_offset = doc.view_offset(view.id); + if config.nullspace.enable { + let gutter_width = view.gutter_offset(doc); + let text_width = config.text_width as u16; + + let view_width = text_width + gutter_width; + + if view_width < view.area.width { + let null_width = (area.width - view_width) / 2; + + let null_l = area.with_width(null_width).clip_bottom(1); + let null_r = Rect::new( + area.x + view_width + null_width, + area.y, + area.width - view_width - null_width, + area.height, + ) + .clip_bottom(1); + + let null_style = theme + .try_get("ui.null") + .or_else(|| Some(theme.get("ui.linenr"))) + .unwrap(); + + fn fill_area(s: &mut Surface, r: Rect, c: char) { + 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); + } + } + } + + // We currently on use the first char in the 'pattern' + // but in future I would like to use the whole string + // to render nicer patterns. + let c = config.nullspace.pattern.chars().nth(0).unwrap(); + + fill_area(surface, null_l, c); + fill_area(surface, null_r, c); + + surface.set_style(null_l, null_style); + surface.set_style(null_r, null_style); + } + } let text_annotations = view.text_annotations(doc, Some(theme)); let mut decorations = DecorationManager::default(); @@ -165,13 +209,16 @@ impl EditorView { } } - let gutter_overflow = view.gutter_offset(doc) == 0; - if !gutter_overflow { + let gutter_width = view.gutter_offset(doc); + if gutter_width > 0 { + let mut gutter_area = inner.with_width(gutter_width); + gutter_area.x -= gutter_width; + Self::render_gutter( editor, doc, view, - view.area, + gutter_area, theme, is_focused & self.terminal_focused, &mut decorations, @@ -201,6 +248,7 @@ impl EditorView { inline_diagnostic_config, config.end_of_line_diagnostics, )); + render_document( surface, inner, diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 1708b3b4e..5054e79d5 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -315,6 +315,8 @@ pub struct Config { /// Column numbers at which to draw the rulers. Defaults to `[]`, meaning no rulers. pub rulers: Vec, #[serde(default)] + pub nullspace: NullspaceConfig, + #[serde(default)] pub whitespace: WhitespaceConfig, /// Persistently display open buffers along the top pub bufferline: BufferLine, @@ -698,6 +700,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: true, + pattern: String::from("╲"), + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(default)] pub struct WhitespaceConfig { @@ -960,6 +978,7 @@ impl Default for Config { lsp: LspConfig::default(), terminal: get_terminal_provider(), rulers: Vec::new(), + nullspace: NullspaceConfig::default(), whitespace: WhitespaceConfig::default(), bufferline: BufferLine::default(), indent_guides: IndentGuidesConfig::default(), diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index a229f01ea..2c3130c22 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -191,7 +191,25 @@ impl View { } pub fn inner_area(&self, doc: &Document) -> Rect { - self.area.clip_left(self.gutter_offset(doc)).clip_bottom(1) // -1 for statusline + let config = doc.config.load(); + let gutter_width = self.gutter_offset(doc); + + if config.nullspace.enable { + let text_width = config.text_width as u16; + let view_width = gutter_width + text_width; + + if self.area.width > view_width { + let null_width = (self.area.width - view_width) / 2; + + return self + .area + .clip_left(gutter_width + null_width) + .clip_bottom(1) + .with_width(text_width.min(self.area.width)); + } + } + + self.area.clip_left(gutter_width).clip_bottom(1) // -1 for statusline } pub fn inner_height(&self) -> usize { From 35657560ddadd927c0b9702815030278bbba41e7 Mon Sep 17 00:00:00 2001 From: Stephen Broadley Date: Fri, 2 Aug 2024 19:15:05 +0100 Subject: [PATCH 02/19] corrected merge --- helix-term/src/ui/editor.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 61a8f392f..4710f833b 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -164,6 +164,8 @@ impl EditorView { decorations.add_decoration(line_decoration); } + let view_offset = doc.view_offset(view.id); + let syntax_highlights = Self::doc_syntax_highlights(doc, view_offset.anchor, inner.height, theme); From 4e6cd3fd06d59872d09a8c0fbc46cc1bc8c2998f Mon Sep 17 00:00:00 2001 From: Stephen Broadley Date: Fri, 2 Aug 2024 20:13:49 +0100 Subject: [PATCH 03/19] added 'shift_left' and 'shift_right' to Rect --- helix-term/src/ui/editor.rs | 3 +-- helix-view/src/editor.rs | 2 +- helix-view/src/graphics.rs | 16 ++++++++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 4710f833b..2d48c1fb6 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -213,8 +213,7 @@ impl EditorView { let gutter_width = view.gutter_offset(doc); if gutter_width > 0 { - let mut gutter_area = inner.with_width(gutter_width); - gutter_area.x -= gutter_width; + let gutter_area = inner.with_width(gutter_width).shift_left(gutter_width); Self::render_gutter( editor, diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 5054e79d5..a8ede13d6 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -710,7 +710,7 @@ pub struct NullspaceConfig { impl Default for NullspaceConfig { fn default() -> Self { Self { - enable: true, + enable: false, pattern: String::from("╲"), } } diff --git a/helix-view/src/graphics.rs b/helix-view/src/graphics.rs index a26823b97..a04394a0a 100644 --- a/helix-view/src/graphics.rs +++ b/helix-view/src/graphics.rs @@ -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. // This does _not_ change the `y` coordinate. pub fn clip_bottom(self, height: u16) -> Rect { From faa03c4e850469a03f8eeb1d6131bb292affbf63 Mon Sep 17 00:00:00 2001 From: Stephen Broadley Date: Fri, 2 Aug 2024 20:38:24 +0100 Subject: [PATCH 04/19] updated style name for 'nullspace' to 'ui.nullspace' --- helix-term/src/ui/editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 2d48c1fb6..f57d9f2eb 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -112,7 +112,7 @@ impl EditorView { .clip_bottom(1); let null_style = theme - .try_get("ui.null") + .try_get("ui.nullspace") .or_else(|| Some(theme.get("ui.linenr"))) .unwrap(); From eb614fc4a4351df65d65400b0431e8e37f270bce Mon Sep 17 00:00:00 2001 From: Stephen Broadley Date: Sun, 4 Aug 2024 20:46:21 +0100 Subject: [PATCH 05/19] add 'split_***' utility API to Rect --- helix-view/src/graphics.rs | 68 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/helix-view/src/graphics.rs b/helix-view/src/graphics.rs index a04394a0a..c5b6f9baf 100644 --- a/helix-view/src/graphics.rs +++ b/helix-view/src/graphics.rs @@ -254,6 +254,74 @@ impl Rect { && self.y < other.y + other.height && self.y + self.height > other.y } + + pub fn split_left(self, width: u16) -> (Rect, Rect) { + ( + Rect { width, ..self }, + Rect { + x: self.x + width, + width: self.width - width, + ..self + }, + ) + } + + pub fn split_right(self, width: u16) -> (Rect, Rect) { + ( + Rect { + width: self.width - width, + ..self + }, + Rect { + x: self.right() - width, + width, + ..self + }, + ) + } + + pub fn split_top(self, height: u16) -> (Rect, Rect) { + ( + Rect { height, ..self }, + Rect { + y: self.y + height, + height: self.height - height, + ..self + }, + ) + } + + pub fn split_bottom(self, height: u16) -> (Rect, Rect) { + ( + 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 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)] From cf531cb8545a4938c1e4f98ab530ee1337321172 Mon Sep 17 00:00:00 2001 From: Stephen Broadley Date: Sun, 4 Aug 2024 20:47:00 +0100 Subject: [PATCH 06/19] simplified code for 'nullspace' calculation in EditorView render --- helix-term/src/ui/editor.rs | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index f57d9f2eb..4eb25935c 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -94,22 +94,8 @@ impl EditorView { let config = editor.config(); if config.nullspace.enable { - let gutter_width = view.gutter_offset(doc); - let text_width = config.text_width as u16; - - let view_width = text_width + gutter_width; - - if view_width < view.area.width { - let null_width = (area.width - view_width) / 2; - - let null_l = area.with_width(null_width).clip_bottom(1); - let null_r = Rect::new( - area.x + view_width + null_width, - area.y, - area.width - view_width - null_width, - area.height, - ) - .clip_bottom(1); + if inner.width < view.area.width { + let (null_l, _, null_r) = area.clip_bottom(1).split_centre_vertical(inner.width); let null_style = theme .try_get("ui.nullspace") From a243e2e1e644da2e887b09f873e51051d39b0422 Mon Sep 17 00:00:00 2001 From: Stephen Broadley Date: Fri, 2 Aug 2024 18:53:23 +0100 Subject: [PATCH 07/19] added 'nullspace' option --- helix-term/src/ui/editor.rs | 56 ++++++++++++++++++++++++++++++++++--- helix-view/src/editor.rs | 19 +++++++++++++ helix-view/src/view.rs | 20 ++++++++++++- 3 files changed, 90 insertions(+), 5 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index f7541fe25..61a8f392f 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -93,7 +93,51 @@ impl EditorView { let theme = &editor.theme; let config = editor.config(); - let view_offset = doc.view_offset(view.id); + if config.nullspace.enable { + let gutter_width = view.gutter_offset(doc); + let text_width = config.text_width as u16; + + let view_width = text_width + gutter_width; + + if view_width < view.area.width { + let null_width = (area.width - view_width) / 2; + + let null_l = area.with_width(null_width).clip_bottom(1); + let null_r = Rect::new( + area.x + view_width + null_width, + area.y, + area.width - view_width - null_width, + area.height, + ) + .clip_bottom(1); + + let null_style = theme + .try_get("ui.null") + .or_else(|| Some(theme.get("ui.linenr"))) + .unwrap(); + + fn fill_area(s: &mut Surface, r: Rect, c: char) { + 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); + } + } + } + + // We currently on use the first char in the 'pattern' + // but in future I would like to use the whole string + // to render nicer patterns. + let c = config.nullspace.pattern.chars().nth(0).unwrap(); + + fill_area(surface, null_l, c); + fill_area(surface, null_r, c); + + surface.set_style(null_l, null_style); + surface.set_style(null_r, null_style); + } + } let text_annotations = view.text_annotations(doc, Some(theme)); let mut decorations = DecorationManager::default(); @@ -165,13 +209,16 @@ impl EditorView { } } - let gutter_overflow = view.gutter_offset(doc) == 0; - if !gutter_overflow { + let gutter_width = view.gutter_offset(doc); + if gutter_width > 0 { + let mut gutter_area = inner.with_width(gutter_width); + gutter_area.x -= gutter_width; + Self::render_gutter( editor, doc, view, - view.area, + gutter_area, theme, is_focused & self.terminal_focused, &mut decorations, @@ -201,6 +248,7 @@ impl EditorView { inline_diagnostic_config, config.end_of_line_diagnostics, )); + render_document( surface, inner, diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 1708b3b4e..5054e79d5 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -315,6 +315,8 @@ pub struct Config { /// Column numbers at which to draw the rulers. Defaults to `[]`, meaning no rulers. pub rulers: Vec, #[serde(default)] + pub nullspace: NullspaceConfig, + #[serde(default)] pub whitespace: WhitespaceConfig, /// Persistently display open buffers along the top pub bufferline: BufferLine, @@ -698,6 +700,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: true, + pattern: String::from("╲"), + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(default)] pub struct WhitespaceConfig { @@ -960,6 +978,7 @@ impl Default for Config { lsp: LspConfig::default(), terminal: get_terminal_provider(), rulers: Vec::new(), + nullspace: NullspaceConfig::default(), whitespace: WhitespaceConfig::default(), bufferline: BufferLine::default(), indent_guides: IndentGuidesConfig::default(), diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index a229f01ea..2c3130c22 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -191,7 +191,25 @@ impl View { } pub fn inner_area(&self, doc: &Document) -> Rect { - self.area.clip_left(self.gutter_offset(doc)).clip_bottom(1) // -1 for statusline + let config = doc.config.load(); + let gutter_width = self.gutter_offset(doc); + + if config.nullspace.enable { + let text_width = config.text_width as u16; + let view_width = gutter_width + text_width; + + if self.area.width > view_width { + let null_width = (self.area.width - view_width) / 2; + + return self + .area + .clip_left(gutter_width + null_width) + .clip_bottom(1) + .with_width(text_width.min(self.area.width)); + } + } + + self.area.clip_left(gutter_width).clip_bottom(1) // -1 for statusline } pub fn inner_height(&self) -> usize { From 7c8e6792f790ee26ced48b08db1b7b3d2864a02b Mon Sep 17 00:00:00 2001 From: Stephen Broadley Date: Fri, 2 Aug 2024 19:15:05 +0100 Subject: [PATCH 08/19] corrected merge --- helix-term/src/ui/editor.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 61a8f392f..4710f833b 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -164,6 +164,8 @@ impl EditorView { decorations.add_decoration(line_decoration); } + let view_offset = doc.view_offset(view.id); + let syntax_highlights = Self::doc_syntax_highlights(doc, view_offset.anchor, inner.height, theme); From 940b1e6357ed956547d4bcbdb3435d42bad1138e Mon Sep 17 00:00:00 2001 From: Stephen Broadley Date: Fri, 2 Aug 2024 20:13:49 +0100 Subject: [PATCH 09/19] added 'shift_left' and 'shift_right' to Rect --- helix-term/src/ui/editor.rs | 3 +-- helix-view/src/editor.rs | 2 +- helix-view/src/graphics.rs | 16 ++++++++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 4710f833b..2d48c1fb6 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -213,8 +213,7 @@ impl EditorView { let gutter_width = view.gutter_offset(doc); if gutter_width > 0 { - let mut gutter_area = inner.with_width(gutter_width); - gutter_area.x -= gutter_width; + let gutter_area = inner.with_width(gutter_width).shift_left(gutter_width); Self::render_gutter( editor, diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 5054e79d5..a8ede13d6 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -710,7 +710,7 @@ pub struct NullspaceConfig { impl Default for NullspaceConfig { fn default() -> Self { Self { - enable: true, + enable: false, pattern: String::from("╲"), } } diff --git a/helix-view/src/graphics.rs b/helix-view/src/graphics.rs index a26823b97..a04394a0a 100644 --- a/helix-view/src/graphics.rs +++ b/helix-view/src/graphics.rs @@ -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. // This does _not_ change the `y` coordinate. pub fn clip_bottom(self, height: u16) -> Rect { From 0902fb4dc9f1e109dbe6c886908bf5f9a7183ff4 Mon Sep 17 00:00:00 2001 From: Stephen Broadley Date: Fri, 2 Aug 2024 20:38:24 +0100 Subject: [PATCH 10/19] updated style name for 'nullspace' to 'ui.nullspace' --- helix-term/src/ui/editor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 2d48c1fb6..f57d9f2eb 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -112,7 +112,7 @@ impl EditorView { .clip_bottom(1); let null_style = theme - .try_get("ui.null") + .try_get("ui.nullspace") .or_else(|| Some(theme.get("ui.linenr"))) .unwrap(); From 6ffd2f8cbe075f37b73604f4e1ef941c8cb4cc5c Mon Sep 17 00:00:00 2001 From: Stephen Broadley Date: Sun, 4 Aug 2024 20:46:21 +0100 Subject: [PATCH 11/19] add 'split_***' utility API to Rect --- helix-view/src/graphics.rs | 68 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/helix-view/src/graphics.rs b/helix-view/src/graphics.rs index a04394a0a..c5b6f9baf 100644 --- a/helix-view/src/graphics.rs +++ b/helix-view/src/graphics.rs @@ -254,6 +254,74 @@ impl Rect { && self.y < other.y + other.height && self.y + self.height > other.y } + + pub fn split_left(self, width: u16) -> (Rect, Rect) { + ( + Rect { width, ..self }, + Rect { + x: self.x + width, + width: self.width - width, + ..self + }, + ) + } + + pub fn split_right(self, width: u16) -> (Rect, Rect) { + ( + Rect { + width: self.width - width, + ..self + }, + Rect { + x: self.right() - width, + width, + ..self + }, + ) + } + + pub fn split_top(self, height: u16) -> (Rect, Rect) { + ( + Rect { height, ..self }, + Rect { + y: self.y + height, + height: self.height - height, + ..self + }, + ) + } + + pub fn split_bottom(self, height: u16) -> (Rect, Rect) { + ( + 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 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)] From fb6d0d8f7b2784de7b34566f87b1294ecd358b28 Mon Sep 17 00:00:00 2001 From: Stephen Broadley Date: Sun, 4 Aug 2024 20:47:00 +0100 Subject: [PATCH 12/19] simplified code for 'nullspace' calculation in EditorView render --- helix-term/src/ui/editor.rs | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index f57d9f2eb..4eb25935c 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -94,22 +94,8 @@ impl EditorView { let config = editor.config(); if config.nullspace.enable { - let gutter_width = view.gutter_offset(doc); - let text_width = config.text_width as u16; - - let view_width = text_width + gutter_width; - - if view_width < view.area.width { - let null_width = (area.width - view_width) / 2; - - let null_l = area.with_width(null_width).clip_bottom(1); - let null_r = Rect::new( - area.x + view_width + null_width, - area.y, - area.width - view_width - null_width, - area.height, - ) - .clip_bottom(1); + if inner.width < view.area.width { + let (null_l, _, null_r) = area.clip_bottom(1).split_centre_vertical(inner.width); let null_style = theme .try_get("ui.nullspace") From 4a2ad21247becb6333e9e85b292e72a6fcbdc9a4 Mon Sep 17 00:00:00 2001 From: Stephen Broadley Date: Sun, 4 Aug 2024 21:35:45 +0100 Subject: [PATCH 13/19] ensure we don't form invalid Rects with 'split_***' API --- helix-view/src/graphics.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/helix-view/src/graphics.rs b/helix-view/src/graphics.rs index c5b6f9baf..bd9bd21a3 100644 --- a/helix-view/src/graphics.rs +++ b/helix-view/src/graphics.rs @@ -256,6 +256,7 @@ impl Rect { } pub fn split_left(self, width: u16) -> (Rect, Rect) { + let width = width.min(self.width); ( Rect { width, ..self }, Rect { @@ -267,6 +268,7 @@ impl Rect { } pub fn split_right(self, width: u16) -> (Rect, Rect) { + let width = width.min(self.width); ( Rect { width: self.width - width, @@ -281,6 +283,7 @@ impl Rect { } pub fn split_top(self, height: u16) -> (Rect, Rect) { + let height = height.min(self.height); ( Rect { height, ..self }, Rect { @@ -292,6 +295,7 @@ impl Rect { } pub fn split_bottom(self, height: u16) -> (Rect, Rect) { + let height = height.min(self.height); ( Rect { height: self.height - height, @@ -306,6 +310,7 @@ impl Rect { } 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; ( From 2fed0f87e60dfbd6d431b5b753d271fee7be97fd Mon Sep 17 00:00:00 2001 From: Stephen Broadley Date: Sun, 4 Aug 2024 22:02:18 +0100 Subject: [PATCH 14/19] corrected inner_area calculation --- helix-view/src/view.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index 2c3130c22..1415e62b4 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -205,7 +205,7 @@ impl View { .area .clip_left(gutter_width + null_width) .clip_bottom(1) - .with_width(text_width.min(self.area.width)); + .with_width(text_width.min(self.area.width - gutter_width)); } } From 786148a1e10cb76a414b7dfcd8b595c56cd0fb84 Mon Sep 17 00:00:00 2001 From: Stephen Broadley Date: Sun, 4 Aug 2024 22:02:45 +0100 Subject: [PATCH 15/19] corrected 'split_centre_vertical' argument --- helix-term/src/ui/editor.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 4eb25935c..484ccf43c 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -95,8 +95,6 @@ impl EditorView { if config.nullspace.enable { if inner.width < view.area.width { - let (null_l, _, null_r) = area.clip_bottom(1).split_centre_vertical(inner.width); - let null_style = theme .try_get("ui.nullspace") .or_else(|| Some(theme.get("ui.linenr"))) @@ -112,6 +110,9 @@ impl EditorView { } } + let view_width = inner.width + view.gutter_offset(doc); + let (null_l, _, null_r) = area.clip_bottom(1).split_centre_vertical(view_width); + // We currently on use the first char in the 'pattern' // but in future I would like to use the whole string // to render nicer patterns. From 6962ca6830fff83823a88b305a27a854f51faecf Mon Sep 17 00:00:00 2001 From: Stephen Broadley Date: Sun, 10 Nov 2024 08:56:30 +0000 Subject: [PATCH 16/19] added nullspace diagnostic rendering --- helix-term/src/ui/editor.rs | 46 ++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 484ccf43c..593be6c1d 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -773,8 +773,24 @@ impl EditorView { let info = theme.get("info"); let hint = theme.get("hint"); + let width = 50.min(viewport.width); + + // place it in the nullspace if we have room, and on the same + // line as the cursor to make it easier to read + // + let gutter_size = view.gutter_offset(doc); + let left_nullspace = viewport.left() - view.area.left() - gutter_size; + let use_nullspace = left_nullspace > width; + + let background_style = if use_nullspace { + theme + .try_get("ui.nullspace") + .unwrap_or(theme.get("ui.background")) + } else { + theme.get("ui.background") + }; + let mut lines = Vec::new(); - let background_style = theme.get("ui.background"); for diagnostic in diagnostics { let style = Style::reset() .patch(background_style) @@ -800,12 +816,26 @@ impl EditorView { let paragraph = Paragraph::new(&text) .alignment(Alignment::Right) .wrap(Wrap { trim: true }); - let width = 100.min(viewport.width); - let height = 15.min(viewport.height); - paragraph.render( - Rect::new(viewport.right() - width, viewport.y + 1, width, height), - surface, - ); + let height = 15.min(viewport.height).min(text.lines.len() as u16); + + // decide where this diagnostic is gonna get rendered + // + let mut diag_x = viewport.right() - width; + let mut diag_y = viewport.top() + 1; + + if use_nullspace { + if let Some(pos) = view.screen_coords_at_pos(doc, doc.text().slice(..), cursor) { + diag_x = viewport.left() - width - gutter_size - 2; + diag_y = pos.row as u16 + viewport.top(); + + // correct for OOB + if diag_y + height > viewport.bottom() { + diag_y -= (diag_y + height) - viewport.bottom(); + } + } + } + + paragraph.render(Rect::new(diag_x, diag_y, width, height), surface); } /// Apply the highlighting on the lines where a cursor is active @@ -828,7 +858,7 @@ impl EditorView { let primary_style = theme.get("ui.cursorline.primary"); let secondary_style = theme.get("ui.cursorline.secondary"); - let viewport = view.area; + let viewport = view.inner_area(doc); move |renderer: &mut TextRenderer, pos: LinePos| { let area = Rect::new(viewport.x, pos.visual_line, viewport.width, 1); From 775660ce1f3876939cb7617bebe3902db6a3078a Mon Sep 17 00:00:00 2001 From: Stephen Broadley Date: Mon, 11 Nov 2024 18:54:12 +0000 Subject: [PATCH 17/19] added nullspace alignment calculation --- helix-term/src/ui/editor.rs | 129 +++++++++++++++++++----------------- helix-view/src/tree.rs | 1 + helix-view/src/view.rs | 88 +++++++++++++++++++----- 3 files changed, 142 insertions(+), 76 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 593be6c1d..585689323 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -88,19 +88,19 @@ impl EditorView { surface: &mut Surface, is_focused: bool, ) { - let inner = view.inner_area(doc); + let areas = view.get_view_areas(doc); + + // TODO: use the 'areas' struct for these! + let inner = areas.text; let area = view.area; let theme = &editor.theme; let config = editor.config(); if config.nullspace.enable { - if inner.width < view.area.width { - let null_style = theme - .try_get("ui.nullspace") - .or_else(|| Some(theme.get("ui.linenr"))) - .unwrap(); - - fn fill_area(s: &mut Surface, r: Rect, c: char) { + // 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(); @@ -109,20 +109,21 @@ impl EditorView { } } } + } - let view_width = inner.width + view.gutter_offset(doc); - let (null_l, _, null_r) = area.clip_bottom(1).split_centre_vertical(view_width); + 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' - // but in future I would like to use the whole string - // to render nicer patterns. + // We currently on use the first + // char in the 'pattern'. + // let c = config.nullspace.pattern.chars().nth(0).unwrap(); - fill_area(surface, null_l, c); - fill_area(surface, null_r, c); - - surface.set_style(null_l, null_style); - surface.set_style(null_r, null_style); + fill_area(surface, areas.left, c, style); + fill_area(surface, areas.right, c, style); } } @@ -253,7 +254,7 @@ impl EditorView { // if we're not at the edge of the screen, draw a right border if viewport.right() != view.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() { surface[(x, y)] .set_symbol(tui::symbols::line::VERTICAL) @@ -265,7 +266,7 @@ impl EditorView { if config.inline_diagnostics.disabled() && 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 @@ -745,13 +746,7 @@ impl EditorView { } } - pub fn render_diagnostics( - doc: &Document, - view: &View, - viewport: Rect, - surface: &mut Surface, - theme: &Theme, - ) { + pub fn render_diagnostics(doc: &Document, view: &View, surface: &mut Surface, theme: &Theme) { use helix_core::diagnostic::Severity; use tui::{ layout::Alignment, @@ -773,27 +768,56 @@ impl EditorView { let info = theme.get("info"); let hint = theme.get("hint"); - let width = 50.min(viewport.width); + let areas = view.get_view_areas(doc); + let width = 50.min(areas.text.width); - // place it in the nullspace if we have room, and on the same - // line as the cursor to make it easier to read + let height = 15.min(areas.text.height); // .min(text.lines.len() as u16); + + // decide where this diagnostic is gonna get rendered // - let gutter_size = view.gutter_offset(doc); - let left_nullspace = viewport.left() - view.area.left() - gutter_size; - let use_nullspace = left_nullspace > width; + 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"); - let background_style = if use_nullspace { - theme - .try_get("ui.nullspace") - .unwrap_or(theme.get("ui.background")) - } else { - 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(); for diagnostic in diagnostics { let style = Style::reset() - .patch(background_style) + .patch(background) .patch(match diagnostic.severity { Some(Severity::Error) => error, Some(Severity::Warning) | None => warning, @@ -813,29 +837,12 @@ impl EditorView { } let text = Text::from(lines); + let paragraph = Paragraph::new(&text) - .alignment(Alignment::Right) + .alignment(align) .wrap(Wrap { trim: true }); - let height = 15.min(viewport.height).min(text.lines.len() as u16); - // decide where this diagnostic is gonna get rendered - // - let mut diag_x = viewport.right() - width; - let mut diag_y = viewport.top() + 1; - - if use_nullspace { - if let Some(pos) = view.screen_coords_at_pos(doc, doc.text().slice(..), cursor) { - diag_x = viewport.left() - width - gutter_size - 2; - diag_y = pos.row as u16 + viewport.top(); - - // correct for OOB - if diag_y + height > viewport.bottom() { - diag_y -= (diag_y + height) - viewport.bottom(); - } - } - } - - paragraph.render(Rect::new(diag_x, diag_y, width, height), surface); + paragraph.render(Rect::new(x, y, width, height), surface); } /// Apply the highlighting on the lines where a cursor is active diff --git a/helix-view/src/tree.rs b/helix-view/src/tree.rs index be8bd4e5b..7acf58654 100644 --- a/helix-view/src/tree.rs +++ b/helix-view/src/tree.rs @@ -373,6 +373,7 @@ impl Tree { match &mut node.content { Content::View(view) => { // debug!!("setting view area {:?}", area); + view.container_area = self.area; view.area = area; } // TODO: call f() Content::Container(container) => { diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index 1415e62b4..5e78d1df8 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -25,6 +25,20 @@ use std::{ const JUMP_LIST_CAPACITY: usize = 30; +#[derive(Debug, Clone, Default)] +pub struct ViewAreas { + pub left: Rect, + 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); #[derive(Debug, Clone)] @@ -129,6 +143,7 @@ pub struct ViewPosition { pub struct View { pub id: ViewId, pub area: Rect, + pub container_area: Rect, pub doc: DocumentId, pub jumps: JumpList, // documents accessed from this view from the oldest one to last viewed one @@ -173,6 +188,7 @@ impl View { id: ViewId::default(), doc, 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 docs_access_history: Vec::new(), last_modified_docs: [None, None], @@ -190,26 +206,68 @@ impl View { self.docs_access_history.push(id); } - pub fn inner_area(&self, doc: &Document) -> Rect { + pub fn get_view_areas(&self, doc: &Document) -> ViewAreas { let config = doc.config.load(); - let gutter_width = self.gutter_offset(doc); - if config.nullspace.enable { - let text_width = config.text_width as u16; - let view_width = gutter_width + text_width; + let gutter = self.gutter_offset(doc); + let area = self.area.clip_bottom(1); // -1 for status line - if self.area.width > view_width { - let null_width = (self.area.width - view_width) / 2; - - return self - .area - .clip_left(gutter_width + null_width) - .clip_bottom(1) - .with_width(text_width.min(self.area.width - gutter_width)); - } + // 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() + }; } - self.area.clip_left(gutter_width).clip_bottom(1) // -1 for statusline + // 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 { + self.get_view_areas(doc).text } pub fn inner_height(&self) -> usize { From 8cf9e6c2b99dd4f0f1707e808f9178286dac4597 Mon Sep 17 00:00:00 2001 From: Stephen Broadley Date: Mon, 11 Nov 2024 19:03:30 +0000 Subject: [PATCH 18/19] added comments to ViewAreas --- helix-view/src/view.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index 5e78d1df8..398b2b455 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -27,7 +27,9 @@ 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, From 64b662228f0c0c928b8e9ac0825bb8280e59ed52 Mon Sep 17 00:00:00 2001 From: Stephen Broadley Date: Mon, 11 Nov 2024 19:04:10 +0000 Subject: [PATCH 19/19] renamed get_view_areas -> get_areas --- helix-term/src/ui/editor.rs | 4 ++-- helix-view/src/view.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 585689323..8aa60af14 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -88,7 +88,7 @@ impl EditorView { surface: &mut Surface, is_focused: bool, ) { - let areas = view.get_view_areas(doc); + let areas = view.get_areas(doc); // TODO: use the 'areas' struct for these! let inner = areas.text; @@ -768,7 +768,7 @@ impl EditorView { let info = theme.get("info"); let hint = theme.get("hint"); - let areas = view.get_view_areas(doc); + 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); diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index 398b2b455..66f3263de 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -208,7 +208,7 @@ impl View { self.docs_access_history.push(id); } - pub fn get_view_areas(&self, doc: &Document) -> ViewAreas { + pub fn get_areas(&self, doc: &Document) -> ViewAreas { let config = doc.config.load(); let gutter = self.gutter_offset(doc); @@ -269,7 +269,7 @@ impl View { /// Returns the 'inner area' (the text renderable area) of the view. /// pub fn inner_area(&self, doc: &Document) -> Rect { - self.get_view_areas(doc).text + self.get_areas(doc).text } pub fn inner_height(&self) -> usize {