From 7ed9e9cf2567ee5e23cd8694ffccb4b38602c02a Mon Sep 17 00:00:00 2001 From: Doug Kelkhoff <18220321+dgkf@users.noreply.github.com> Date: Tue, 8 Nov 2022 07:19:59 -0500 Subject: [PATCH] Dynamically resize line number gutter width (#3469) * dynamically resize line number gutter width * removing digits lower-bound, permitting spacer * removing max line num char limit; adding notes; qualified successors; notes * updating tests to use new line number width when testing views * linenr width based on document line count * using min width of 2 so line numbers relative is useful * lint rolling; removing unnecessary type parameter lifetime * merge change resolution * reformat code * rename row_styler to style; add int_log resource * adding spacer to gutters default; updating book config entry * adding view.inner_height(), swap for loop for iterator * reverting change of current! to view! now that doc is not needed --- book/src/configuration.md | 2 +- helix-term/src/commands.rs | 16 ++-- helix-term/src/ui/editor.rs | 17 +++-- helix-view/src/editor.rs | 9 ++- helix-view/src/gutter.rs | 57 ++++++++++++-- helix-view/src/lib.rs | 2 +- helix-view/src/tree.rs | 4 +- helix-view/src/view.rs | 145 ++++++++++++++++-------------------- 8 files changed, 143 insertions(+), 109 deletions(-) diff --git a/book/src/configuration.md b/book/src/configuration.md index 9f3fe2fa95bd..20abeba5a216 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -46,7 +46,7 @@ on unix operating systems. | `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` | | `cursorcolumn` | Highlight all columns with a cursor. | `false` | -| `gutters` | Gutters to display: Available are `diagnostics` and `line-numbers` and `spacer`, note that `diagnostics` also includes other features like breakpoints, 1-width padding will be inserted if gutters is non-empty | `["diagnostics", "line-numbers"]` | +| `gutters` | Gutters to display: Available are `diagnostics` and `line-numbers` and `spacer`, note that `diagnostics` also includes other features like breakpoints, 1-width padding will be inserted if gutters is non-empty | `["diagnostics", "spacer", "line-numbers"]` | | `auto-completion` | Enable automatic pop up of auto-completion. | `true` | | `auto-format` | Enable automatic formatting on save. | `true` | | `auto-save` | Enable automatic saving on focus moving away from Helix. Requires [focus event support](https://github.com/helix-editor/helix/wiki/Terminal-Support) from your terminal. | `false` | diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 31498a7b51a4..4e3c321cb8fd 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -870,7 +870,7 @@ fn goto_window(cx: &mut Context, align: Align) { let config = cx.editor.config(); let (view, doc) = current!(cx.editor); - let height = view.inner_area().height as usize; + let height = view.inner_height(); // respect user given count if any // - 1 so we have at least one gap in the middle. @@ -1372,9 +1372,9 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) { return; } - let height = view.inner_area().height; + let height = view.inner_height(); - let scrolloff = config.scrolloff.min(height as usize / 2); + let scrolloff = config.scrolloff.min(height / 2); view.offset.row = match direction { Forward => view.offset.row + offset, @@ -1412,25 +1412,25 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) { fn page_up(cx: &mut Context) { let view = view!(cx.editor); - let offset = view.inner_area().height as usize; + let offset = view.inner_height(); scroll(cx, offset, Direction::Backward); } fn page_down(cx: &mut Context) { let view = view!(cx.editor); - let offset = view.inner_area().height as usize; + let offset = view.inner_height(); scroll(cx, offset, Direction::Forward); } fn half_page_up(cx: &mut Context) { let view = view!(cx.editor); - let offset = view.inner_area().height as usize / 2; + let offset = view.inner_height() / 2; scroll(cx, offset, Direction::Backward); } fn half_page_down(cx: &mut Context) { let view = view!(cx.editor); - let offset = view.inner_area().height as usize / 2; + let offset = view.inner_height() / 2; scroll(cx, offset, Direction::Forward); } @@ -4342,7 +4342,7 @@ fn align_view_middle(cx: &mut Context) { view.offset.col = pos .col - .saturating_sub((view.inner_area().width as usize) / 2); + .saturating_sub((view.inner_area(doc).width as usize) / 2); } fn scroll_up(cx: &mut Context) { diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 2cd2ad056b53..f2a588e338f2 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -79,7 +79,7 @@ impl EditorView { surface: &mut Surface, is_focused: bool, ) { - let inner = view.inner_area(); + let inner = view.inner_area(doc); let area = view.area; let theme = &editor.theme; @@ -736,9 +736,10 @@ 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() { - let gutter = constructor(editor, doc, view, theme, is_focused, *width); - text.reserve(*width); // ensure there's enough space for the gutter + for gutter_type in view.gutters() { + let gutter = gutter_type.style(editor, doc, view, theme, is_focused); + let width = gutter_type.width(view, doc); + text.reserve(width); // ensure there's enough space for the gutter for (i, line) in (view.offset.row..(last_line + 1)).enumerate() { let selected = cursors.contains(&line); let x = viewport.x + offset; @@ -751,13 +752,13 @@ impl EditorView { }; if let Some(style) = gutter(line, selected, &mut text) { - surface.set_stringn(x, y, &text, *width, gutter_style.patch(style)); + surface.set_stringn(x, y, &text, width, gutter_style.patch(style)); } else { surface.set_style( Rect { x, y, - width: *width as u16, + width: width as u16, height: 1, }, gutter_style, @@ -766,7 +767,7 @@ impl EditorView { text.clear(); } - offset += *width as u16; + offset += width as u16; } } @@ -882,7 +883,7 @@ impl EditorView { .or_else(|| theme.try_get_exact("ui.cursorcolumn")) .unwrap_or_else(|| theme.get("ui.cursorline.secondary")); - let inner_area = view.inner_area(); + let inner_area = view.inner_area(doc); let offset = view.offset.col; let selection = doc.selection(view.id); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index bcd8dedb8c81..db97cbb1472b 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -456,6 +456,7 @@ impl std::str::FromStr for GutterType { fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { "diagnostics" => Ok(Self::Diagnostics), + "spacer" => Ok(Self::Spacer), "line-numbers" => Ok(Self::LineNumbers), _ => anyhow::bail!("Gutter type can only be `diagnostics` or `line-numbers`."), } @@ -589,7 +590,11 @@ impl Default for Config { line_number: LineNumber::Absolute, cursorline: false, cursorcolumn: false, - gutters: vec![GutterType::Diagnostics, GutterType::LineNumbers], + gutters: vec![ + GutterType::Diagnostics, + GutterType::Spacer, + GutterType::LineNumbers, + ], middle_click_paste: true, auto_pairs: AutoPairConfig::default(), auto_completion: true, @@ -1308,7 +1313,7 @@ impl Editor { .primary() .cursor(doc.text().slice(..)); if let Some(mut pos) = view.screen_coords_at_pos(doc, doc.text().slice(..), cursor) { - let inner = view.inner_area(); + let inner = view.inner_area(doc); pos.col += inner.x as usize; pos.row += inner.y as usize; let cursorkind = config.cursor_shape.from_mode(self.mode); diff --git a/helix-view/src/gutter.rs b/helix-view/src/gutter.rs index 2c207d2768d4..61a1779186c3 100644 --- a/helix-view/src/gutter.rs +++ b/helix-view/src/gutter.rs @@ -1,21 +1,54 @@ use std::fmt::Write; use crate::{ + editor::GutterType, graphics::{Color, Style, UnderlineStyle}, Document, Editor, Theme, View, }; +fn count_digits(n: usize) -> usize { + // NOTE: if int_log gets standardized in stdlib, can use checked_log10 + // (https://github.com/rust-lang/rust/issues/70887#issue) + std::iter::successors(Some(n), |&n| (n >= 10).then(|| n / 10)).count() +} + pub type GutterFn<'doc> = Box Option