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
pull/2137/head
Dr. David A. Kunz 3 years ago committed by GitHub
parent 450f348925
commit b04c425c63
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -37,6 +37,7 @@ hidden = false
| `scroll-lines` | Number of lines to scroll per scroll wheel step. | `3` | | `scroll-lines` | Number of lines to scroll per scroll wheel step. | `3` |
| `shell` | Shell to use when running external commands. | Unix: `["sh", "-c"]`<br/>Windows: `["cmd", "/C"]` | | `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` | | `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` | | `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` | | `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` | | `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` |

@ -466,7 +466,7 @@ impl EditorView {
// avoid lots of small allocations by reusing a text buffer for each line // avoid lots of small allocations by reusing a text buffer for each line
let mut text = String::with_capacity(8); 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); let gutter = constructor(editor, doc, view, theme, is_focused, *width);
text.reserve(*width); // ensure there's enough space for the gutter text.reserve(*width); // ensure there's enough space for the gutter
for (i, line) in (view.offset.row..(last_line + 1)).enumerate() { for (i, line) in (view.offset.row..(last_line + 1)).enumerate() {

@ -117,6 +117,8 @@ pub struct Config {
pub shell: Vec<String>, pub shell: Vec<String>,
/// Line number mode. /// Line number mode.
pub line_number: LineNumber, pub line_number: LineNumber,
/// Gutters. Default ["diagnostics", "line-numbers"]
pub gutters: Vec<GutterType>,
/// Middle click paste support. Defaults to true. /// Middle click paste support. Defaults to true.
pub middle_click_paste: bool, pub middle_click_paste: bool,
/// Automatic insertion of pairs to parentheses, brackets, /// 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<Self, Self::Err> {
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 { impl Default for Config {
fn default() -> Self { fn default() -> Self {
Self { Self {
@ -250,6 +273,7 @@ impl Default for Config {
vec!["sh".to_owned(), "-c".to_owned()] vec!["sh".to_owned(), "-c".to_owned()]
}, },
line_number: LineNumber::Absolute, line_number: LineNumber::Absolute,
gutters: vec![GutterType::Diagnostics, GutterType::LineNumbers],
middle_click_paste: true, middle_click_paste: true,
auto_pairs: AutoPairConfig::default(), auto_pairs: AutoPairConfig::default(),
auto_completion: true, auto_completion: true,
@ -579,7 +603,7 @@ impl Editor {
return; return;
} }
Action::HorizontalSplit | Action::VerticalSplit => { Action::HorizontalSplit | Action::VerticalSplit => {
let view = View::new(id); let view = View::new(id, self.config().gutters.clone());
let view_id = self.tree.split( let view_id = self.tree.split(
view, view,
match action { match action {
@ -701,7 +725,7 @@ impl Editor {
.map(|(&doc_id, _)| doc_id) .map(|(&doc_id, _)| doc_id)
.next() .next()
.unwrap_or_else(|| self.new_document(Document::default())); .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 view_id = self.tree.insert(view);
let doc = self.documents.get_mut(&doc_id).unwrap(); let doc = self.documents.get_mut(&doc_id).unwrap();
doc.selections.insert(view_id, Selection::point(0)); doc.selections.insert(view_id, Selection::point(0));

@ -39,7 +39,7 @@ pub fn diagnostic<'doc>(
}) })
} }
pub fn line_number<'doc>( pub fn line_numbers<'doc>(
editor: &'doc Editor, editor: &'doc Editor,
doc: &'doc Document, doc: &'doc Document,
view: &View, view: &View,

@ -568,6 +568,7 @@ impl<'a> Iterator for Traverse<'a> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use crate::editor::GutterType;
use crate::DocumentId; use crate::DocumentId;
#[test] #[test]
@ -578,22 +579,34 @@ mod test {
width: 180, width: 180,
height: 80, 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); view.area = Rect::new(0, 0, 180, 80);
tree.insert(view); tree.insert(view);
let l0 = tree.focus; 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); tree.split(view, Layout::Vertical);
let r0 = tree.focus; let r0 = tree.focus;
tree.focus = l0; 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); tree.split(view, Layout::Horizontal);
let l1 = tree.focus; let l1 = tree.focus;
tree.focus = l0; 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); tree.split(view, Layout::Vertical);
let l2 = tree.focus; let l2 = tree.focus;

@ -11,6 +11,8 @@ use helix_core::{
visual_coords_at_pos, Position, RopeSlice, Selection, visual_coords_at_pos, Position, RopeSlice, Selection,
}; };
use std::fmt;
type Jump = (DocumentId, Selection); type Jump = (DocumentId, Selection);
#[derive(Debug, Clone)] #[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 struct View {
pub id: ViewId, pub id: ViewId,
pub doc: DocumentId,
pub offset: Position, pub offset: Position,
pub area: Rect, pub area: Rect,
pub doc: DocumentId,
pub jumps: JumpList, pub jumps: JumpList,
/// the last accessed file before the current one /// the last accessed file before the current one
pub last_accessed_doc: Option<DocumentId>, pub last_accessed_doc: Option<DocumentId>,
@ -85,10 +81,29 @@ pub struct View {
pub last_modified_docs: [Option<DocumentId>; 2], pub last_modified_docs: [Option<DocumentId>; 2],
/// used to store previous selections of tree-sitter objecs /// used to store previous selections of tree-sitter objecs
pub object_selections: Vec<Selection>, pub object_selections: Vec<Selection>,
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 { impl View {
pub fn new(doc: DocumentId) -> Self { pub fn new(doc: DocumentId, gutter_types: Vec<crate::editor::GutterType>) -> 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 { Self {
id: ViewId::default(), id: ViewId::default(),
doc, doc,
@ -98,17 +113,14 @@ impl View {
last_accessed_doc: None, last_accessed_doc: None,
last_modified_docs: [None, None], last_modified_docs: [None, None],
object_selections: Vec::new(), object_selections: Vec::new(),
gutters,
} }
} }
pub fn gutters(&self) -> &[(Gutter, usize)] {
GUTTERS
}
pub fn inner_area(&self) -> Rect { pub fn inner_area(&self) -> Rect {
// TODO: cache this // TODO: cache this
let offset = self let offset = self
.gutters() .gutters
.iter() .iter()
.map(|(_, width)| *width as u16) .map(|(_, width)| *width as u16)
.sum::<u16>() .sum::<u16>()
@ -324,11 +336,16 @@ mod tests {
use super::*; use super::*;
use helix_core::Rope; use helix_core::Rope;
const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
const OFFSET_WITHOUT_LINE_NUMBERS: u16 = 2; // 1 diagnostic + 1 gutter
// const OFFSET: u16 = GUTTERS.iter().map(|(_, width)| *width as u16).sum(); // const OFFSET: u16 = GUTTERS.iter().map(|(_, width)| *width as u16).sum();
use crate::editor::GutterType;
#[test] #[test]
fn test_text_pos_at_screen_coords() { 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); view.area = Rect::new(40, 40, 40, 40);
let rope = Rope::from_str("abc\n\tdef"); let rope = Rope::from_str("abc\n\tdef");
let text = rope.slice(..); let text = rope.slice(..);
@ -372,9 +389,36 @@ mod tests {
assert_eq!(view.text_pos_at_screen_coords(&text, 41, 80, 4), Some(8)); 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] #[test]
fn test_text_pos_at_screen_coords_cjk() { 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); view.area = Rect::new(40, 40, 40, 40);
let rope = Rope::from_str("Hi! こんにちは皆さん"); let rope = Rope::from_str("Hi! こんにちは皆さん");
let text = rope.slice(..); let text = rope.slice(..);
@ -411,7 +455,10 @@ mod tests {
#[test] #[test]
fn test_text_pos_at_screen_coords_graphemes() { 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); view.area = Rect::new(40, 40, 40, 40);
let rope = Rope::from_str("Hèl̀l̀ò world!"); let rope = Rope::from_str("Hèl̀l̀ò world!");
let text = rope.slice(..); let text = rope.slice(..);

Loading…
Cancel
Save