diff options
-rw-r--r-- | helix-term/src/ui/editor.rs | 97 | ||||
-rw-r--r-- | helix-view/src/gutter.rs | 95 | ||||
-rw-r--r-- | helix-view/src/lib.rs | 1 | ||||
-rw-r--r-- | helix-view/src/view.rs | 24 |
4 files changed, 117 insertions, 100 deletions
diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 82fb8fbf..83be816f 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -17,7 +17,6 @@ use helix_core::{ }; use helix_view::{ document::{Mode, SCRATCH_BUFFER_NAME}, - editor::{Config, LineNumber}, graphics::{CursorKind, Modifier, Rect, Style}, info::Info, input::KeyEvent, @@ -421,97 +420,12 @@ impl EditorView { .map(|range| range.cursor_line(text)) .collect(); - use std::fmt::Write; - - fn diagnostic<'doc>( - doc: &'doc Document, - _view: &View, - theme: &Theme, - _config: &Config, - _is_focused: bool, - _width: usize, - ) -> GutterFn<'doc> { - let warning = theme.get("warning"); - let error = theme.get("error"); - let info = theme.get("info"); - let hint = theme.get("hint"); - let diagnostics = doc.diagnostics(); - - Box::new(move |line: usize, _selected: bool, out: &mut String| { - use helix_core::diagnostic::Severity; - if let Some(diagnostic) = diagnostics.iter().find(|d| d.line == line) { - write!(out, "●").unwrap(); - return Some(match diagnostic.severity { - Some(Severity::Error) => error, - Some(Severity::Warning) | None => warning, - Some(Severity::Info) => info, - Some(Severity::Hint) => hint, - }); - } - None - }) - } - - fn line_number<'doc>( - doc: &'doc Document, - view: &View, - theme: &Theme, - config: &Config, - is_focused: bool, - width: usize, - ) -> GutterFn<'doc> { - let text = doc.text().slice(..); - let last_line = 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 linenr = theme.get("ui.linenr"); - let linenr_select: Style = theme.try_get("ui.linenr.selected").unwrap_or(linenr); - - let current_line = doc - .text() - .char_to_line(doc.selection(view.id).primary().cursor(text)); - - let config = config.line_number; - - Box::new(move |line: usize, selected: bool, out: &mut String| { - if line == last_line && !draw_last { - write!(out, "{:>1$}", '~', width).unwrap(); - Some(linenr) - } else { - let line = match config { - LineNumber::Absolute => line + 1, - LineNumber::Relative => { - if current_line == line { - line + 1 - } else { - abs_diff(current_line, line) - } - } - }; - let style = if selected && is_focused { - linenr_select - } else { - linenr - }; - write!(out, "{:>1$}", line, width).unwrap(); - Some(style) - } - }) - } - - type GutterFn<'doc> = Box<dyn Fn(usize, bool, &mut String) -> Option<Style> + 'doc>; - type Gutter = - for<'doc> fn(&'doc Document, &View, &Theme, &Config, bool, usize) -> GutterFn<'doc>; - let gutters: &[(Gutter, usize)] = &[(diagnostic, 1), (line_number, 5)]; - let mut offset = 0; // avoid lots of small allocations by reusing a text buffer for each line let mut text = String::with_capacity(8); - for (constructor, width) in gutters { + for (constructor, width) in view.gutters() { let gutter = constructor(doc, view, theme, config, 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() { @@ -1214,12 +1128,3 @@ fn canonicalize_key(key: &mut KeyEvent) { key.modifiers.remove(KeyModifiers::SHIFT) } } - -#[inline] -const fn abs_diff(a: usize, b: usize) -> usize { - if a > b { - a - b - } else { - b - a - } -} diff --git a/helix-view/src/gutter.rs b/helix-view/src/gutter.rs new file mode 100644 index 00000000..86773c1d --- /dev/null +++ b/helix-view/src/gutter.rs @@ -0,0 +1,95 @@ +use std::fmt::Write; + +use crate::{editor::Config, graphics::Style, Document, Theme, View}; + +pub type GutterFn<'doc> = Box<dyn Fn(usize, bool, &mut String) -> Option<Style> + 'doc>; +pub type Gutter = + for<'doc> fn(&'doc Document, &View, &Theme, &Config, bool, usize) -> GutterFn<'doc>; + +pub fn diagnostic<'doc>( + doc: &'doc Document, + _view: &View, + theme: &Theme, + _config: &Config, + _is_focused: bool, + _width: usize, +) -> GutterFn<'doc> { + let warning = theme.get("warning"); + let error = theme.get("error"); + let info = theme.get("info"); + let hint = theme.get("hint"); + let diagnostics = doc.diagnostics(); + + Box::new(move |line: usize, _selected: bool, out: &mut String| { + use helix_core::diagnostic::Severity; + if let Some(diagnostic) = diagnostics.iter().find(|d| d.line == line) { + write!(out, "●").unwrap(); + return Some(match diagnostic.severity { + Some(Severity::Error) => error, + Some(Severity::Warning) | None => warning, + Some(Severity::Info) => info, + Some(Severity::Hint) => hint, + }); + } + None + }) +} + +pub fn line_number<'doc>( + doc: &'doc Document, + view: &View, + theme: &Theme, + config: &Config, + is_focused: bool, + width: usize, +) -> GutterFn<'doc> { + let text = doc.text().slice(..); + let last_line = 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 linenr = theme.get("ui.linenr"); + let linenr_select: Style = theme.try_get("ui.linenr.selected").unwrap_or(linenr); + + let current_line = doc + .text() + .char_to_line(doc.selection(view.id).primary().cursor(text)); + + let config = config.line_number; + + Box::new(move |line: usize, selected: bool, out: &mut String| { + if line == last_line && !draw_last { + write!(out, "{:>1$}", '~', width).unwrap(); + Some(linenr) + } else { + use crate::editor::LineNumber; + let line = match config { + LineNumber::Absolute => line + 1, + LineNumber::Relative => { + if current_line == line { + line + 1 + } else { + abs_diff(current_line, line) + } + } + }; + let style = if selected && is_focused { + linenr_select + } else { + linenr + }; + write!(out, "{:>1$}", line, width).unwrap(); + Some(style) + } + }) +} + +#[inline(always)] +const fn abs_diff(a: usize, b: usize) -> usize { + if a > b { + a - b + } else { + b - a + } +} diff --git a/helix-view/src/lib.rs b/helix-view/src/lib.rs index 4d19ee2e..a56c914d 100644 --- a/helix-view/src/lib.rs +++ b/helix-view/src/lib.rs @@ -5,6 +5,7 @@ pub mod clipboard; pub mod document; pub mod editor; pub mod graphics; +pub mod gutter; pub mod info; pub mod input; pub mod keyboard; diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index a77f1562..217a4940 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -1,6 +1,10 @@ use std::borrow::Cow; -use crate::{graphics::Rect, Document, DocumentId, ViewId}; +use crate::{ + graphics::Rect, + gutter::{self, Gutter}, + Document, DocumentId, ViewId, +}; use helix_core::{ graphemes::{grapheme_width, RopeGraphemes}, line_ending::line_end_char_index, @@ -60,6 +64,8 @@ impl JumpList { } } +const GUTTERS: &[(Gutter, usize)] = &[(gutter::diagnostic, 1), (gutter::line_number, 5)]; + #[derive(Debug)] pub struct View { pub id: ViewId, @@ -83,10 +89,19 @@ impl View { } } + pub fn gutters(&self) -> &[(Gutter, usize)] { + GUTTERS + } + pub fn inner_area(&self) -> Rect { - // TODO: not ideal - const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter - self.area.clip_left(OFFSET).clip_bottom(1) // -1 for statusline + // 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 } // @@ -276,6 +291,7 @@ 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(); #[test] fn test_text_pos_at_screen_coords() { |