From e8214fb1e6009d44a4b7094c6e5297a19f18f549 Mon Sep 17 00:00:00 2001
From: Ivan Tham <pickfire@riseup.net>
Date: Mon, 18 Jul 2022 09:13:47 +0800
Subject: [PATCH] Make gutters padding optional (#2996)

If all gutters are removed, there are still an extra one padding, would be nice
to remove that to save some space.
---
 book/src/configuration.md   |  2 +-
 helix-term/src/ui/editor.rs |  2 +-
 helix-view/src/editor.rs    |  8 ++++-
 helix-view/src/gutter.rs    | 11 +++++++
 helix-view/src/view.rs      | 63 +++++++++++++++++++++++++++----------
 5 files changed, 66 insertions(+), 20 deletions(-)

diff --git a/book/src/configuration.md b/book/src/configuration.md
index 4c849f26..b8b939e7 100644
--- a/book/src/configuration.md
+++ b/book/src/configuration.md
@@ -38,7 +38,7 @@ hidden = false
 | `shell` | Shell to use when running external commands. | Unix: `["sh", "-c"]`<br/>Windows: `["cmd", "/C"]` |
 | `line-number` | Line number display: `absolute` simply shows each line's number, while `relative` shows the distance from the current line. When unfocused or in insert mode, `relative` will still show absolute line numbers. | `absolute` |
 | `cursorline` | Highlight all lines with a cursor. | `false` |
-| `gutters` | Gutters to display: Available are `diagnostics` and `line-numbers`, note that `diagnostics` also includes other features like breakpoints | `["diagnostics", "line-numbers"]` |
+| `gutters` | Gutters to display: Available are `diagnostics` and `line-numbers` and `padding`, note that `diagnostics` also includes other features like breakpoints | `["diagnostics", "line-numbers", "padding"]` |
 | `auto-completion` | Enable automatic pop up of auto-completion. | `true` |
 | `auto-format` | Enable automatic formatting on save. | `true` |
 | `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. | `400` |
diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs
index 9b8bf8eb..911ee0f0 100644
--- a/helix-term/src/ui/editor.rs
+++ b/helix-term/src/ui/editor.rs
@@ -615,7 +615,7 @@ impl EditorView {
         // avoid lots of small allocations by reusing a text buffer for each line
         let mut text = String::with_capacity(8);
 
-        for (constructor, width) in &view.gutters {
+        for (constructor, width) in view.gutters() {
             let gutter = constructor(editor, doc, view, theme, is_focused, *width);
             text.reserve(*width); // ensure there's enough space for the gutter
             for (i, line) in (view.offset.row..(last_line + 1)).enumerate() {
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index 51c0eee0..babb5c43 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -314,6 +314,8 @@ pub enum GutterType {
     Diagnostics,
     /// Show line numbers
     LineNumbers,
+    /// Show one blank space
+    Padding,
 }
 
 impl std::str::FromStr for GutterType {
@@ -450,7 +452,11 @@ impl Default for Config {
             },
             line_number: LineNumber::Absolute,
             cursorline: false,
-            gutters: vec![GutterType::Diagnostics, GutterType::LineNumbers],
+            gutters: vec![
+                GutterType::Diagnostics,
+                GutterType::LineNumbers,
+                GutterType::Padding,
+            ],
             middle_click_paste: true,
             auto_pairs: AutoPairConfig::default(),
             auto_completion: true,
diff --git a/helix-view/src/gutter.rs b/helix-view/src/gutter.rs
index 05fec758..8f7c3062 100644
--- a/helix-view/src/gutter.rs
+++ b/helix-view/src/gutter.rs
@@ -102,6 +102,17 @@ pub fn line_numbers<'doc>(
     })
 }
 
+pub fn padding<'doc>(
+    _editor: &'doc Editor,
+    _doc: &'doc Document,
+    _view: &View,
+    _theme: &Theme,
+    _is_focused: bool,
+    _width: usize,
+) -> GutterFn<'doc> {
+    Box::new(|_line: usize, _selected: bool, _out: &mut String| None)
+}
+
 #[inline(always)]
 const fn abs_diff(a: usize, b: usize) -> usize {
     if a > b {
diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs
index bfae12a4..8bf3611f 100644
--- a/helix-view/src/view.rs
+++ b/helix-view/src/view.rs
@@ -75,7 +75,11 @@ pub struct View {
     pub last_modified_docs: [Option<DocumentId>; 2],
     /// used to store previous selections of tree-sitter objects
     pub object_selections: Vec<Selection>,
-    pub gutters: Vec<(Gutter, usize)>,
+    /// Gutter (constructor) and width of gutter, used to calculate
+    /// `gutter_offset`
+    gutters: Vec<(Gutter, usize)>,
+    /// cached total width of gutter
+    gutter_offset: u16,
 }
 
 impl fmt::Debug for View {
@@ -91,12 +95,23 @@ impl fmt::Debug for View {
 impl View {
     pub fn new(doc: DocumentId, gutter_types: Vec<crate::editor::GutterType>) -> Self {
         let mut gutters: Vec<(Gutter, usize)> = vec![];
+        let mut gutter_offset = 0;
         use crate::editor::GutterType;
         for gutter_type in &gutter_types {
-            match gutter_type {
-                GutterType::Diagnostics => gutters.push((gutter::diagnostics_or_breakpoints, 1)),
-                GutterType::LineNumbers => gutters.push((gutter::line_numbers, 5)),
-            }
+            let width = match gutter_type {
+                GutterType::Diagnostics => 1,
+                GutterType::LineNumbers => 5,
+                GutterType::Padding => 1,
+            };
+            gutter_offset += width;
+            gutters.push((
+                match gutter_type {
+                    GutterType::Diagnostics => gutter::diagnostics_or_breakpoints,
+                    GutterType::LineNumbers => gutter::line_numbers,
+                    GutterType::Padding => gutter::padding,
+                },
+                width as usize,
+            ));
         }
         Self {
             id: ViewId::default(),
@@ -108,6 +123,7 @@ impl View {
             last_modified_docs: [None, None],
             object_selections: Vec::new(),
             gutters,
+            gutter_offset,
         }
     }
 
@@ -119,14 +135,12 @@ impl View {
     }
 
     pub fn inner_area(&self) -> Rect {
-        // TODO: cache this
-        let offset = self
-            .gutters
-            .iter()
-            .map(|(_, width)| *width as u16)
-            .sum::<u16>()
-            + 1; // +1 for some space between gutters and line
-        self.area.clip_left(offset).clip_bottom(1) // -1 for statusline
+        // TODO add abilty to not use cached offset for runtime configurable gutter
+        self.area.clip_left(self.gutter_offset).clip_bottom(1) // -1 for statusline
+    }
+
+    pub fn gutters(&self) -> &[(Gutter, usize)] {
+        &self.gutters
     }
 
     //
@@ -327,7 +341,11 @@ mod tests {
     fn test_text_pos_at_screen_coords() {
         let mut view = View::new(
             DocumentId::default(),
-            vec![GutterType::Diagnostics, GutterType::LineNumbers],
+            vec![
+                GutterType::Diagnostics,
+                GutterType::LineNumbers,
+                GutterType::Padding,
+            ],
         );
         view.area = Rect::new(40, 40, 40, 40);
         let rope = Rope::from_str("abc\n\tdef");
@@ -374,7 +392,10 @@ mod tests {
 
     #[test]
     fn test_text_pos_at_screen_coords_without_line_numbers_gutter() {
-        let mut view = View::new(DocumentId::default(), vec![GutterType::Diagnostics]);
+        let mut view = View::new(
+            DocumentId::default(),
+            vec![GutterType::Diagnostics, GutterType::Padding],
+        );
         view.area = Rect::new(40, 40, 40, 40);
         let rope = Rope::from_str("abc\n\tdef");
         let text = rope.slice(..);
@@ -400,7 +421,11 @@ mod tests {
     fn test_text_pos_at_screen_coords_cjk() {
         let mut view = View::new(
             DocumentId::default(),
-            vec![GutterType::Diagnostics, GutterType::LineNumbers],
+            vec![
+                GutterType::Diagnostics,
+                GutterType::LineNumbers,
+                GutterType::Padding,
+            ],
         );
         view.area = Rect::new(40, 40, 40, 40);
         let rope = Rope::from_str("Hi! こんにちは皆さん");
@@ -440,7 +465,11 @@ mod tests {
     fn test_text_pos_at_screen_coords_graphemes() {
         let mut view = View::new(
             DocumentId::default(),
-            vec![GutterType::Diagnostics, GutterType::LineNumbers],
+            vec![
+                GutterType::Diagnostics,
+                GutterType::LineNumbers,
+                GutterType::Padding,
+            ],
         );
         view.area = Rect::new(40, 40, 40, 40);
         let rope = Rope::from_str("Hèl̀l̀ò world!");