From b04c425c63d03f8320eb47ab6e38e4bb79763248 Mon Sep 17 00:00:00 2001 From: "Dr. David A. Kunz" Date: Sat, 16 Apr 2022 03:41:25 +0200 Subject: [PATCH] Make gutters configurable (#1967) * config option line numbers none * view tests * added tests * doc * comment * Make gutters configurable * docu * docu * rm none docu * order * order * precedence * simpler * rm todo * fixed clippy * order * double quotes * only allow diagnostics and line-numbers * tests * docu * format * rm short variant and more docu * performance improvements * typo * rename --- book/src/configuration.md | 1 + helix-term/src/ui/editor.rs | 2 +- helix-view/src/editor.rs | 28 ++++++++++++- helix-view/src/gutter.rs | 2 +- helix-view/src/tree.rs | 21 ++++++++-- helix-view/src/view.rs | 81 +++++++++++++++++++++++++++++-------- 6 files changed, 110 insertions(+), 25 deletions(-) diff --git a/book/src/configuration.md b/book/src/configuration.md index dae46176b..3ec2bedb9 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -37,6 +37,7 @@ hidden = false | `scroll-lines` | Number of lines to scroll per scroll wheel step. | `3` | | `shell` | Shell to use when running external commands. | Unix: `["sh", "-c"]`
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` | +| `gutters` | Gutters to display: Available are `diagnostics` and `line-numbers`, note that `diagnostics` also includes other features like breakpoints | `["diagnostics", "line-numbers"]` | | `auto-completion` | Enable automatic pop up of auto-completion. | `true` | | `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant. | `400` | | `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` | diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 564605de4..459a8c876 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -466,7 +466,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 76fc67138..dd805c000 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -117,6 +117,8 @@ pub struct Config { pub shell: Vec, /// Line number mode. pub line_number: LineNumber, + /// Gutters. Default ["diagnostics", "line-numbers"] + pub gutters: Vec, /// Middle click paste support. Defaults to true. pub middle_click_paste: bool, /// Automatic insertion of pairs to parentheses, brackets, @@ -238,6 +240,27 @@ impl std::str::FromStr for LineNumber { } } +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum GutterType { + /// Show diagnostics and other features like breakpoints + Diagnostics, + /// Show line numbers + LineNumbers, +} + +impl std::str::FromStr for GutterType { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "diagnostics" => Ok(Self::Diagnostics), + "line-numbers" => Ok(Self::LineNumbers), + _ => anyhow::bail!("Gutter type can only be `diagnostics` or `line-numbers`."), + } + } +} + impl Default for Config { fn default() -> Self { Self { @@ -250,6 +273,7 @@ impl Default for Config { vec!["sh".to_owned(), "-c".to_owned()] }, line_number: LineNumber::Absolute, + gutters: vec![GutterType::Diagnostics, GutterType::LineNumbers], middle_click_paste: true, auto_pairs: AutoPairConfig::default(), auto_completion: true, @@ -579,7 +603,7 @@ impl Editor { return; } Action::HorizontalSplit | Action::VerticalSplit => { - let view = View::new(id); + let view = View::new(id, self.config().gutters.clone()); let view_id = self.tree.split( view, match action { @@ -701,7 +725,7 @@ impl Editor { .map(|(&doc_id, _)| doc_id) .next() .unwrap_or_else(|| self.new_document(Document::default())); - let view = View::new(doc_id); + let view = View::new(doc_id, self.config().gutters.clone()); let view_id = self.tree.insert(view); let doc = self.documents.get_mut(&doc_id).unwrap(); doc.selections.insert(view_id, Selection::point(0)); diff --git a/helix-view/src/gutter.rs b/helix-view/src/gutter.rs index 7327ed1a2..06ce1b2e7 100644 --- a/helix-view/src/gutter.rs +++ b/helix-view/src/gutter.rs @@ -39,7 +39,7 @@ pub fn diagnostic<'doc>( }) } -pub fn line_number<'doc>( +pub fn line_numbers<'doc>( editor: &'doc Editor, doc: &'doc Document, view: &View, diff --git a/helix-view/src/tree.rs b/helix-view/src/tree.rs index 99cbe0f90..b068f4c7e 100644 --- a/helix-view/src/tree.rs +++ b/helix-view/src/tree.rs @@ -568,6 +568,7 @@ impl<'a> Iterator for Traverse<'a> { #[cfg(test)] mod test { use super::*; + use crate::editor::GutterType; use crate::DocumentId; #[test] @@ -578,22 +579,34 @@ mod test { width: 180, height: 80, }); - let mut view = View::new(DocumentId::default()); + let mut view = View::new( + DocumentId::default(), + vec![GutterType::Diagnostics, GutterType::LineNumbers], + ); view.area = Rect::new(0, 0, 180, 80); tree.insert(view); let l0 = tree.focus; - let view = View::new(DocumentId::default()); + let view = View::new( + DocumentId::default(), + vec![GutterType::Diagnostics, GutterType::LineNumbers], + ); tree.split(view, Layout::Vertical); let r0 = tree.focus; tree.focus = l0; - let view = View::new(DocumentId::default()); + let view = View::new( + DocumentId::default(), + vec![GutterType::Diagnostics, GutterType::LineNumbers], + ); tree.split(view, Layout::Horizontal); let l1 = tree.focus; tree.focus = l0; - let view = View::new(DocumentId::default()); + let view = View::new( + DocumentId::default(), + vec![GutterType::Diagnostics, GutterType::LineNumbers], + ); tree.split(view, Layout::Vertical); let l2 = tree.focus; diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index c6ae0c56e..7cf88c2e1 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -11,6 +11,8 @@ use helix_core::{ visual_coords_at_pos, Position, RopeSlice, Selection, }; +use std::fmt; + type Jump = (DocumentId, Selection); #[derive(Debug, Clone)] @@ -64,17 +66,11 @@ impl JumpList { } } -const GUTTERS: &[(Gutter, usize)] = &[ - (gutter::diagnostics_or_breakpoints, 1), - (gutter::line_number, 5), -]; - -#[derive(Debug)] pub struct View { pub id: ViewId, - pub doc: DocumentId, pub offset: Position, pub area: Rect, + pub doc: DocumentId, pub jumps: JumpList, /// the last accessed file before the current one pub last_accessed_doc: Option, @@ -85,10 +81,29 @@ pub struct View { pub last_modified_docs: [Option; 2], /// used to store previous selections of tree-sitter objecs pub object_selections: Vec, + pub gutters: Vec<(Gutter, usize)>, +} + +impl fmt::Debug for View { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("View") + .field("id", &self.id) + .field("area", &self.area) + .field("doc", &self.doc) + .finish() + } } impl View { - pub fn new(doc: DocumentId) -> Self { + pub fn new(doc: DocumentId, gutter_types: Vec) -> Self { + let mut gutters: Vec<(Gutter, usize)> = vec![]; + 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)), + } + } Self { id: ViewId::default(), doc, @@ -98,17 +113,14 @@ impl View { last_accessed_doc: None, last_modified_docs: [None, None], object_selections: Vec::new(), + gutters, } } - pub fn gutters(&self) -> &[(Gutter, usize)] { - GUTTERS - } - pub fn inner_area(&self) -> Rect { // TODO: cache this let offset = self - .gutters() + .gutters .iter() .map(|(_, width)| *width as u16) .sum::() @@ -324,11 +336,16 @@ mod tests { use super::*; use helix_core::Rope; const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter - // const OFFSET: u16 = GUTTERS.iter().map(|(_, width)| *width as u16).sum(); + const OFFSET_WITHOUT_LINE_NUMBERS: u16 = 2; // 1 diagnostic + 1 gutter + // const OFFSET: u16 = GUTTERS.iter().map(|(_, width)| *width as u16).sum(); + use crate::editor::GutterType; #[test] fn test_text_pos_at_screen_coords() { - let mut view = View::new(DocumentId::default()); + let mut view = View::new( + DocumentId::default(), + vec![GutterType::Diagnostics, GutterType::LineNumbers], + ); view.area = Rect::new(40, 40, 40, 40); let rope = Rope::from_str("abc\n\tdef"); let text = rope.slice(..); @@ -372,9 +389,36 @@ mod tests { assert_eq!(view.text_pos_at_screen_coords(&text, 41, 80, 4), Some(8)); } + #[test] + fn test_text_pos_at_screen_coords_without_line_numbers_gutter() { + let mut view = View::new(DocumentId::default(), vec![GutterType::Diagnostics]); + view.area = Rect::new(40, 40, 40, 40); + let rope = Rope::from_str("abc\n\tdef"); + let text = rope.slice(..); + assert_eq!( + view.text_pos_at_screen_coords(&text, 41, 40 + OFFSET_WITHOUT_LINE_NUMBERS + 1, 4), + Some(4) + ); + } + + #[test] + fn test_text_pos_at_screen_coords_without_any_gutters() { + let mut view = View::new(DocumentId::default(), vec![]); + view.area = Rect::new(40, 40, 40, 40); + let rope = Rope::from_str("abc\n\tdef"); + let text = rope.slice(..); + assert_eq!( + view.text_pos_at_screen_coords(&text, 41, 40 + 1, 4), + Some(4) + ); + } + #[test] fn test_text_pos_at_screen_coords_cjk() { - let mut view = View::new(DocumentId::default()); + let mut view = View::new( + DocumentId::default(), + vec![GutterType::Diagnostics, GutterType::LineNumbers], + ); view.area = Rect::new(40, 40, 40, 40); let rope = Rope::from_str("Hi! こんにちは皆さん"); let text = rope.slice(..); @@ -411,7 +455,10 @@ mod tests { #[test] fn test_text_pos_at_screen_coords_graphemes() { - let mut view = View::new(DocumentId::default()); + let mut view = View::new( + DocumentId::default(), + vec![GutterType::Diagnostics, GutterType::LineNumbers], + ); view.area = Rect::new(40, 40, 40, 40); let rope = Rope::from_str("Hèl̀l̀ò world!"); let text = rope.slice(..);