From 2b58ff4d7cc09bc48bcdd79096110eeb578b509f Mon Sep 17 00:00:00 2001
From: Doug Kelkhoff <18220321+dgkf@users.noreply.github.com>
Date: Sat, 21 Jan 2023 15:20:59 -0500
Subject: [PATCH] Add configuration for min width of line-numbers gutter
 (#4724)

---
 book/src/configuration.md |  52 +++++++++++++++++++
 helix-view/src/editor.rs  | 104 +++++++++++++++++++++++++++++++++----
 helix-view/src/gutter.rs  | 105 ++++++++++++++++++++++++++++++++++----
 helix-view/src/tree.rs    |  42 ++++-----------
 helix-view/src/view.rs    |  99 ++++++++++++++++++++---------------
 5 files changed, 310 insertions(+), 92 deletions(-)

diff --git a/book/src/configuration.md b/book/src/configuration.md
index f89ef5ae..ab229f77 100644
--- a/book/src/configuration.md
+++ b/book/src/configuration.md
@@ -256,3 +256,55 @@ render = true
 character = "╎" # Some characters that work well: "▏", "┆", "┊", "⸽"
 skip-levels = 1
 ```
+
+### `[editor.gutters]` Section
+
+For simplicity, `editor.gutters` accepts an array of gutter types, which will
+use default settings for all gutter components.
+
+```toml
+[editor]
+gutters = ["diff", "diagnostics", "line-numbers", "spacer"]
+```
+
+To customize the behavior of gutters, the `[editor.gutters]` section must
+be used. This section contains top level settings, as well as settings for
+specific gutter components as sub-sections.
+
+| Key      | Description                    | Default                                                       |
+| ---      | ---                            | ---                                                           |
+| `layout` | A vector of gutters to display | `["diagnostics", "spacer", "line-numbers", "spacer", "diff"]` |
+
+Example:
+
+```toml
+[editor.gutters]
+layout = ["diff", "diagnostics", "line-numbers", "spacer"]
+```
+
+#### `[editor.gutters.line-numbers]` Section
+
+Options for the line number gutter
+
+| Key         | Description                             | Default |
+| ---         | ---                                     | ---     |
+| `min-width` | The minimum number of characters to use | `3`     |
+
+Example:
+
+```toml
+[editor.gutters.line-numbers]
+min-width = 1
+```
+
+#### `[editor.gutters.diagnotics]` Section
+
+Currently unused
+
+#### `[editor.gutters.diff]` Section
+
+Currently unused
+
+#### `[editor.gutters.spacer]` Section
+
+Currently unused
\ No newline at end of file
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index 3ee0325d..9af8e4c3 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -71,6 +71,96 @@ where
     )
 }
 
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
+pub struct GutterConfig {
+    /// Gutter Layout
+    pub layout: Vec<GutterType>,
+    /// Options specific to the "line-numbers" gutter
+    pub line_numbers: GutterLineNumbersConfig,
+}
+
+impl Default for GutterConfig {
+    fn default() -> Self {
+        Self {
+            layout: vec![
+                GutterType::Diagnostics,
+                GutterType::Spacer,
+                GutterType::LineNumbers,
+                GutterType::Spacer,
+                GutterType::Diff,
+            ],
+            line_numbers: GutterLineNumbersConfig::default(),
+        }
+    }
+}
+
+impl From<Vec<GutterType>> for GutterConfig {
+    fn from(x: Vec<GutterType>) -> Self {
+        GutterConfig {
+            layout: x,
+            ..Default::default()
+        }
+    }
+}
+
+fn deserialize_gutter_seq_or_struct<'de, D>(deserializer: D) -> Result<GutterConfig, D::Error>
+where
+    D: Deserializer<'de>,
+{
+    struct GutterVisitor;
+
+    impl<'de> serde::de::Visitor<'de> for GutterVisitor {
+        type Value = GutterConfig;
+
+        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+            write!(
+                formatter,
+                "an array of gutter names or a detailed gutter configuration"
+            )
+        }
+
+        fn visit_seq<S>(self, mut seq: S) -> Result<Self::Value, S::Error>
+        where
+            S: serde::de::SeqAccess<'de>,
+        {
+            let mut gutters = Vec::new();
+            while let Some(gutter) = seq.next_element::<&str>()? {
+                gutters.push(
+                    gutter
+                        .parse::<GutterType>()
+                        .map_err(serde::de::Error::custom)?,
+                )
+            }
+
+            Ok(gutters.into())
+        }
+
+        fn visit_map<M>(self, map: M) -> Result<Self::Value, M::Error>
+        where
+            M: serde::de::MapAccess<'de>,
+        {
+            let deserializer = serde::de::value::MapAccessDeserializer::new(map);
+            Deserialize::deserialize(deserializer)
+        }
+    }
+
+    deserializer.deserialize_any(GutterVisitor)
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
+pub struct GutterLineNumbersConfig {
+    /// Minimum number of characters to use for line number gutter. Defaults to 3.
+    pub min_width: usize,
+}
+
+impl Default for GutterLineNumbersConfig {
+    fn default() -> Self {
+        Self { min_width: 3 }
+    }
+}
+
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 #[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
 pub struct FilePickerConfig {
@@ -132,8 +222,8 @@ pub struct Config {
     pub cursorline: bool,
     /// Highlight the columns cursors are currently on. Defaults to false.
     pub cursorcolumn: bool,
-    /// Gutters. Default ["diagnostics", "line-numbers"]
-    pub gutters: Vec<GutterType>,
+    #[serde(deserialize_with = "deserialize_gutter_seq_or_struct")]
+    pub gutters: GutterConfig,
     /// Middle click paste support. Defaults to true.
     pub middle_click_paste: bool,
     /// Automatic insertion of pairs to parentheses, brackets,
@@ -606,13 +696,7 @@ impl Default for Config {
             line_number: LineNumber::Absolute,
             cursorline: false,
             cursorcolumn: false,
-            gutters: vec![
-                GutterType::Diagnostics,
-                GutterType::Spacer,
-                GutterType::LineNumbers,
-                GutterType::Spacer,
-                GutterType::Diff,
-            ],
+            gutters: GutterConfig::default(),
             middle_click_paste: true,
             auto_pairs: AutoPairConfig::default(),
             auto_completion: true,
@@ -844,6 +928,7 @@ impl Editor {
         let config = self.config();
         self.auto_pairs = (&config.auto_pairs).into();
         self.reset_idle_timer();
+        self._refresh();
     }
 
     pub fn clear_idle_timer(&mut self) {
@@ -984,6 +1069,7 @@ impl Editor {
         for (view, _) in self.tree.views_mut() {
             let doc = doc_mut!(self, &view.doc);
             view.sync_changes(doc);
+            view.gutters = config.gutters.clone();
             view.ensure_cursor_in_view(doc, config.scrolloff)
         }
     }
diff --git a/helix-view/src/gutter.rs b/helix-view/src/gutter.rs
index 377518fb..c1b5e2b1 100644
--- a/helix-view/src/gutter.rs
+++ b/helix-view/src/gutter.rs
@@ -35,10 +35,10 @@ impl GutterType {
         }
     }
 
-    pub fn width(self, _view: &View, doc: &Document) -> usize {
+    pub fn width(self, view: &View, doc: &Document) -> usize {
         match self {
             GutterType::Diagnostics => 1,
-            GutterType::LineNumbers => line_numbers_width(_view, doc),
+            GutterType::LineNumbers => line_numbers_width(view, doc),
             GutterType::Spacer => 1,
             GutterType::Diff => 1,
         }
@@ -140,12 +140,13 @@ pub fn line_numbers<'doc>(
     is_focused: bool,
 ) -> GutterFn<'doc> {
     let text = doc.text().slice(..);
-    let last_line = view.last_line(doc);
-    let width = GutterType::LineNumbers.width(view, doc);
+    let width = line_numbers_width(view, doc);
+
+    let last_line_in_view = view.last_line(doc);
 
     // Whether to draw the line number for the last line of the
     // document or not.  We only draw it if it's not an empty line.
-    let draw_last = text.line_to_byte(last_line) < text.len_bytes();
+    let draw_last = text.line_to_byte(last_line_in_view) < text.len_bytes();
 
     let linenr = theme.get("ui.linenr");
     let linenr_select = theme.get("ui.linenr.selected");
@@ -158,7 +159,7 @@ pub fn line_numbers<'doc>(
     let mode = editor.mode;
 
     Box::new(move |line: usize, selected: bool, out: &mut String| {
-        if line == last_line && !draw_last {
+        if line == last_line_in_view && !draw_last {
             write!(out, "{:>1$}", '~', width).unwrap();
             Some(linenr)
         } else {
@@ -187,14 +188,19 @@ pub fn line_numbers<'doc>(
     })
 }
 
-pub fn line_numbers_width(_view: &View, doc: &Document) -> usize {
+/// The width of a "line-numbers" gutter
+///
+/// The width of the gutter depends on the number of lines in the document,
+/// whether there is content on the last line (the `~` line), and the
+/// `editor.gutters.line-numbers.min-width` settings.
+fn line_numbers_width(view: &View, doc: &Document) -> usize {
     let text = doc.text();
     let last_line = text.len_lines().saturating_sub(1);
     let draw_last = text.line_to_byte(last_line) < text.len_bytes();
     let last_drawn = if draw_last { last_line + 1 } else { last_line };
-
-    // set a lower bound to 2-chars to minimize ambiguous relative line numbers
-    std::cmp::max(count_digits(last_drawn), 2)
+    let digits = count_digits(last_drawn);
+    let n_min = view.gutters.line_numbers.min_width;
+    digits.max(n_min)
 }
 
 pub fn padding<'doc>(
@@ -282,3 +288,82 @@ pub fn diagnostics_or_breakpoints<'doc>(
         breakpoints(line, selected, out).or_else(|| diagnostics(line, selected, out))
     })
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::document::Document;
+    use crate::editor::{GutterConfig, GutterLineNumbersConfig};
+    use crate::graphics::Rect;
+    use crate::DocumentId;
+    use helix_core::Rope;
+
+    #[test]
+    fn test_default_gutter_widths() {
+        let mut view = View::new(DocumentId::default(), GutterConfig::default());
+        view.area = Rect::new(40, 40, 40, 40);
+
+        let rope = Rope::from_str("abc\n\tdef");
+        let doc = Document::from(rope, None);
+
+        assert_eq!(view.gutters.layout.len(), 5);
+        assert_eq!(view.gutters.layout[0].width(&view, &doc), 1);
+        assert_eq!(view.gutters.layout[1].width(&view, &doc), 1);
+        assert_eq!(view.gutters.layout[2].width(&view, &doc), 3);
+        assert_eq!(view.gutters.layout[3].width(&view, &doc), 1);
+        assert_eq!(view.gutters.layout[4].width(&view, &doc), 1);
+    }
+
+    #[test]
+    fn test_configured_gutter_widths() {
+        let gutters = GutterConfig {
+            layout: vec![GutterType::Diagnostics],
+            ..Default::default()
+        };
+
+        let mut view = View::new(DocumentId::default(), gutters);
+        view.area = Rect::new(40, 40, 40, 40);
+
+        let rope = Rope::from_str("abc\n\tdef");
+        let doc = Document::from(rope, None);
+
+        assert_eq!(view.gutters.layout.len(), 1);
+        assert_eq!(view.gutters.layout[0].width(&view, &doc), 1);
+
+        let gutters = GutterConfig {
+            layout: vec![GutterType::Diagnostics, GutterType::LineNumbers],
+            line_numbers: GutterLineNumbersConfig { min_width: 10 },
+        };
+
+        let mut view = View::new(DocumentId::default(), gutters);
+        view.area = Rect::new(40, 40, 40, 40);
+
+        let rope = Rope::from_str("abc\n\tdef");
+        let doc = Document::from(rope, None);
+
+        assert_eq!(view.gutters.layout.len(), 2);
+        assert_eq!(view.gutters.layout[0].width(&view, &doc), 1);
+        assert_eq!(view.gutters.layout[1].width(&view, &doc), 10);
+    }
+
+    #[test]
+    fn test_line_numbers_gutter_width_resizes() {
+        let gutters = GutterConfig {
+            layout: vec![GutterType::Diagnostics, GutterType::LineNumbers],
+            line_numbers: GutterLineNumbersConfig { min_width: 1 },
+        };
+
+        let mut view = View::new(DocumentId::default(), gutters);
+        view.area = Rect::new(40, 40, 40, 40);
+
+        let rope = Rope::from_str("a\nb");
+        let doc_short = Document::from(rope, None);
+
+        let rope = Rope::from_str("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np");
+        let doc_long = Document::from(rope, None);
+
+        assert_eq!(view.gutters.layout.len(), 2);
+        assert_eq!(view.gutters.layout[1].width(&view, &doc_short), 1);
+        assert_eq!(view.gutters.layout[1].width(&view, &doc_long), 2);
+    }
+}
diff --git a/helix-view/src/tree.rs b/helix-view/src/tree.rs
index 469e913d..5ec2773d 100644
--- a/helix-view/src/tree.rs
+++ b/helix-view/src/tree.rs
@@ -701,7 +701,7 @@ impl<'a> DoubleEndedIterator for Traverse<'a> {
 #[cfg(test)]
 mod test {
     use super::*;
-    use crate::editor::GutterType;
+    use crate::editor::GutterConfig;
     use crate::DocumentId;
 
     #[test]
@@ -712,34 +712,22 @@ mod test {
             width: 180,
             height: 80,
         });
-        let mut view = View::new(
-            DocumentId::default(),
-            vec![GutterType::Diagnostics, GutterType::LineNumbers],
-        );
+        let mut view = View::new(DocumentId::default(), GutterConfig::default());
         view.area = Rect::new(0, 0, 180, 80);
         tree.insert(view);
 
         let l0 = tree.focus;
-        let view = View::new(
-            DocumentId::default(),
-            vec![GutterType::Diagnostics, GutterType::LineNumbers],
-        );
+        let view = View::new(DocumentId::default(), GutterConfig::default());
         tree.split(view, Layout::Vertical);
         let r0 = tree.focus;
 
         tree.focus = l0;
-        let view = View::new(
-            DocumentId::default(),
-            vec![GutterType::Diagnostics, GutterType::LineNumbers],
-        );
+        let view = View::new(DocumentId::default(), GutterConfig::default());
         tree.split(view, Layout::Horizontal);
         let l1 = tree.focus;
 
         tree.focus = l0;
-        let view = View::new(
-            DocumentId::default(),
-            vec![GutterType::Diagnostics, GutterType::LineNumbers],
-        );
+        let view = View::new(DocumentId::default(), GutterConfig::default());
         tree.split(view, Layout::Vertical);
         let l2 = tree.focus;
 
@@ -781,40 +769,28 @@ mod test {
         });
 
         let doc_l0 = DocumentId::default();
-        let mut view = View::new(
-            doc_l0,
-            vec![GutterType::Diagnostics, GutterType::LineNumbers],
-        );
+        let mut view = View::new(doc_l0, GutterConfig::default());
         view.area = Rect::new(0, 0, 180, 80);
         tree.insert(view);
 
         let l0 = tree.focus;
 
         let doc_r0 = DocumentId::default();
-        let view = View::new(
-            doc_r0,
-            vec![GutterType::Diagnostics, GutterType::LineNumbers],
-        );
+        let view = View::new(doc_r0, GutterConfig::default());
         tree.split(view, Layout::Vertical);
         let r0 = tree.focus;
 
         tree.focus = l0;
 
         let doc_l1 = DocumentId::default();
-        let view = View::new(
-            doc_l1,
-            vec![GutterType::Diagnostics, GutterType::LineNumbers],
-        );
+        let view = View::new(doc_l1, GutterConfig::default());
         tree.split(view, Layout::Horizontal);
         let l1 = tree.focus;
 
         tree.focus = l0;
 
         let doc_l2 = DocumentId::default();
-        let view = View::new(
-            doc_l2,
-            vec![GutterType::Diagnostics, GutterType::LineNumbers],
-        );
+        let view = View::new(doc_l2, GutterConfig::default());
         tree.split(view, Layout::Vertical);
         let l2 = tree.focus;
 
diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs
index 23fb85c9..abcf9a16 100644
--- a/helix-view/src/view.rs
+++ b/helix-view/src/view.rs
@@ -1,4 +1,10 @@
-use crate::{align_view, editor::GutterType, graphics::Rect, Align, Document, DocumentId, ViewId};
+use crate::{
+    align_view,
+    editor::{GutterConfig, GutterType},
+    graphics::Rect,
+    Align, Document, DocumentId, ViewId,
+};
+
 use helix_core::{
     pos_at_visual_coords, visual_coords_at_pos, Position, RopeSlice, Selection, Transaction,
 };
@@ -103,8 +109,8 @@ pub struct View {
     pub last_modified_docs: [Option<DocumentId>; 2],
     /// used to store previous selections of tree-sitter objects
     pub object_selections: Vec<Selection>,
-    /// GutterTypes used to fetch Gutter (constructor) and width for rendering
-    gutters: Vec<GutterType>,
+    /// all gutter-related configuration settings, used primarily for gutter rendering
+    pub gutters: GutterConfig,
     /// A mapping between documents and the last history revision the view was updated at.
     /// Changes between documents and views are synced lazily when switching windows. This
     /// mapping keeps track of the last applied history revision so that only new changes
@@ -123,7 +129,7 @@ impl fmt::Debug for View {
 }
 
 impl View {
-    pub fn new(doc: DocumentId, gutter_types: Vec<crate::editor::GutterType>) -> Self {
+    pub fn new(doc: DocumentId, gutters: GutterConfig) -> Self {
         Self {
             id: ViewId::default(),
             doc,
@@ -133,7 +139,7 @@ impl View {
             docs_access_history: Vec::new(),
             last_modified_docs: [None, None],
             object_selections: Vec::new(),
-            gutters: gutter_types,
+            gutters,
             doc_revisions: HashMap::new(),
         }
     }
@@ -154,11 +160,12 @@ impl View {
     }
 
     pub fn gutters(&self) -> &[GutterType] {
-        &self.gutters
+        &self.gutters.layout
     }
 
     pub fn gutter_offset(&self, doc: &Document) -> u16 {
         self.gutters
+            .layout
             .iter()
             .map(|gutter| gutter.width(self, doc) as u16)
             .sum()
@@ -414,18 +421,19 @@ impl View {
 mod tests {
     use super::*;
     use helix_core::Rope;
-    const OFFSET: u16 = 3; // 1 diagnostic + 2 linenr (< 100 lines)
-    const OFFSET_WITHOUT_LINE_NUMBERS: u16 = 1; // 1 diagnostic
-                                                // const OFFSET: u16 = GUTTERS.iter().map(|(_, width)| *width as u16).sum();
+
+    // 1 diagnostic + 1 spacer + 3 linenr (< 1000 lines) + 1 spacer + 1 diff
+    const DEFAULT_GUTTER_OFFSET: u16 = 7;
+
+    // 1 diagnostics + 1 spacer + 1 gutter
+    const DEFAULT_GUTTER_OFFSET_ONLY_DIAGNOSTICS: u16 = 3;
+
     use crate::document::Document;
-    use crate::editor::GutterType;
+    use crate::editor::{GutterConfig, GutterLineNumbersConfig, GutterType};
 
     #[test]
     fn test_text_pos_at_screen_coords() {
-        let mut view = View::new(
-            DocumentId::default(),
-            vec![GutterType::Diagnostics, GutterType::LineNumbers],
-        );
+        let mut view = View::new(DocumentId::default(), GutterConfig::default());
         view.area = Rect::new(40, 40, 40, 40);
         let rope = Rope::from_str("abc\n\tdef");
         let doc = Document::from(rope, None);
@@ -445,24 +453,24 @@ mod tests {
         assert_eq!(view.text_pos_at_screen_coords(&doc, 78, 41, 4), None);
 
         assert_eq!(
-            view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 3, 4),
+            view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 3, 4),
             Some(3)
         );
 
         assert_eq!(view.text_pos_at_screen_coords(&doc, 40, 80, 4), Some(3));
 
         assert_eq!(
-            view.text_pos_at_screen_coords(&doc, 41, 40 + OFFSET + 1, 4),
+            view.text_pos_at_screen_coords(&doc, 41, 40 + DEFAULT_GUTTER_OFFSET + 1, 4),
             Some(4)
         );
 
         assert_eq!(
-            view.text_pos_at_screen_coords(&doc, 41, 40 + OFFSET + 4, 4),
+            view.text_pos_at_screen_coords(&doc, 41, 40 + DEFAULT_GUTTER_OFFSET + 4, 4),
             Some(5)
         );
 
         assert_eq!(
-            view.text_pos_at_screen_coords(&doc, 41, 40 + OFFSET + 7, 4),
+            view.text_pos_at_screen_coords(&doc, 41, 40 + DEFAULT_GUTTER_OFFSET + 7, 4),
             Some(8)
         );
 
@@ -471,19 +479,36 @@ 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(),
+            GutterConfig {
+                layout: vec![GutterType::Diagnostics],
+                line_numbers: GutterLineNumbersConfig::default(),
+            },
+        );
         view.area = Rect::new(40, 40, 40, 40);
         let rope = Rope::from_str("abc\n\tdef");
         let doc = Document::from(rope, None);
         assert_eq!(
-            view.text_pos_at_screen_coords(&doc, 41, 40 + OFFSET_WITHOUT_LINE_NUMBERS + 1, 4),
+            view.text_pos_at_screen_coords(
+                &doc,
+                41,
+                40 + DEFAULT_GUTTER_OFFSET_ONLY_DIAGNOSTICS + 1,
+                4
+            ),
             Some(4)
         );
     }
 
     #[test]
     fn test_text_pos_at_screen_coords_without_any_gutters() {
-        let mut view = View::new(DocumentId::default(), vec![]);
+        let mut view = View::new(
+            DocumentId::default(),
+            GutterConfig {
+                layout: vec![],
+                line_numbers: GutterLineNumbersConfig::default(),
+            },
+        );
         view.area = Rect::new(40, 40, 40, 40);
         let rope = Rope::from_str("abc\n\tdef");
         let doc = Document::from(rope, None);
@@ -492,76 +517,70 @@ mod tests {
 
     #[test]
     fn test_text_pos_at_screen_coords_cjk() {
-        let mut view = View::new(
-            DocumentId::default(),
-            vec![GutterType::Diagnostics, GutterType::LineNumbers],
-        );
+        let mut view = View::new(DocumentId::default(), GutterConfig::default());
         view.area = Rect::new(40, 40, 40, 40);
         let rope = Rope::from_str("Hi! こんにちは皆さん");
         let doc = Document::from(rope, None);
 
         assert_eq!(
-            view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET, 4),
+            view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET, 4),
             Some(0)
         );
 
         assert_eq!(
-            view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 4, 4),
+            view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 4, 4),
             Some(4)
         );
         assert_eq!(
-            view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 5, 4),
+            view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 5, 4),
             Some(4)
         );
 
         assert_eq!(
-            view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 6, 4),
+            view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 6, 4),
             Some(5)
         );
 
         assert_eq!(
-            view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 7, 4),
+            view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 7, 4),
             Some(5)
         );
 
         assert_eq!(
-            view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 8, 4),
+            view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 8, 4),
             Some(6)
         );
     }
 
     #[test]
     fn test_text_pos_at_screen_coords_graphemes() {
-        let mut view = View::new(
-            DocumentId::default(),
-            vec![GutterType::Diagnostics, GutterType::LineNumbers],
-        );
+        let mut view = View::new(DocumentId::default(), GutterConfig::default());
         view.area = Rect::new(40, 40, 40, 40);
         let rope = Rope::from_str("Hèl̀l̀ò world!");
         let doc = Document::from(rope, None);
 
         assert_eq!(
-            view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET, 4),
+            view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET, 4),
             Some(0)
         );
 
         assert_eq!(
-            view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 1, 4),
+            view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 1, 4),
             Some(1)
         );
 
         assert_eq!(
-            view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 2, 4),
+            view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 2, 4),
             Some(3)
         );
 
         assert_eq!(
-            view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 3, 4),
+            view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 3, 4),
             Some(5)
         );
 
         assert_eq!(
-            view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 4, 4),
+            view.text_pos_at_screen_coords(&doc, 40, 40 + DEFAULT_GUTTER_OFFSET + 4, 4),
             Some(7)
         );
     }