on master help sign in
Extract gutters into helix-view
225e8ccBlaž Hrastnik
helix-term/src/ui/editor.rs+1−96
@@ -17,7 +17,6 @@ use helix_core::{
17};
17};
18use helix_view::{
18use helix_view::{
19 document::{Mode, SCRATCH_BUFFER_NAME},
19 document::{Mode, SCRATCH_BUFFER_NAME},
20− editor::{Config, LineNumber},
21 graphics::{CursorKind, Modifier, Rect, Style},
20 graphics::{CursorKind, Modifier, Rect, Style},
22 info::Info,
21 info::Info,
23 input::KeyEvent,
22 input::KeyEvent,
@@ -421,97 +420,12 @@ impl EditorView {
421 .map(|range| range.cursor_line(text))
420 .map(|range| range.cursor_line(text))
422 .collect();
421 .collect();
423
422
424− use std::fmt::Write;
425−
426− fn diagnostic<'doc>(
427− doc: &'doc Document,
428− _view: &View,
429− theme: &Theme,
430− _config: &Config,
431− _is_focused: bool,
432− _width: usize,
433− ) -> GutterFn<'doc> {
434− let warning = theme.get("warning");
435− let error = theme.get("error");
436− let info = theme.get("info");
437− let hint = theme.get("hint");
438− let diagnostics = doc.diagnostics();
439−
440− Box::new(move |line: usize, _selected: bool, out: &mut String| {
441− use helix_core::diagnostic::Severity;
442− if let Some(diagnostic) = diagnostics.iter().find(|d| d.line == line) {
443− write!(out, "●").unwrap();
444− return Some(match diagnostic.severity {
445− Some(Severity::Error) => error,
446− Some(Severity::Warning) | None => warning,
447− Some(Severity::Info) => info,
448− Some(Severity::Hint) => hint,
449− });
450− }
451− None
452− })
453− }
454−
455− fn line_number<'doc>(
456− doc: &'doc Document,
457− view: &View,
458− theme: &Theme,
459− config: &Config,
460− is_focused: bool,
461− width: usize,
462− ) -> GutterFn<'doc> {
463− let text = doc.text().slice(..);
464− let last_line = view.last_line(doc);
465− // Whether to draw the line number for the last line of the
466− // document or not. We only draw it if it's not an empty line.
467− let draw_last = text.line_to_byte(last_line) < text.len_bytes();
468−
469− let linenr = theme.get("ui.linenr");
470− let linenr_select: Style = theme.try_get("ui.linenr.selected").unwrap_or(linenr);
471−
472− let current_line = doc
473− .text()
474− .char_to_line(doc.selection(view.id).primary().cursor(text));
475−
476− let config = config.line_number;
477−
478− Box::new(move |line: usize, selected: bool, out: &mut String| {
479− if line == last_line && !draw_last {
480− write!(out, "{:>1$}", '~', width).unwrap();
481− Some(linenr)
482− } else {
483− let line = match config {
484− LineNumber::Absolute => line + 1,
485− LineNumber::Relative => {
486− if current_line == line {
487− line + 1
488− } else {
489− abs_diff(current_line, line)
490− }
491− }
492− };
493− let style = if selected && is_focused {
494− linenr_select
495− } else {
496− linenr
497− };
498− write!(out, "{:>1$}", line, width).unwrap();
499− Some(style)
500− }
501− })
502− }
503−
504− type GutterFn<'doc> = Box<dyn Fn(usize, bool, &mut String) -> Option<Style> + 'doc>;
505− type Gutter =
506− for<'doc> fn(&'doc Document, &View, &Theme, &Config, bool, usize) -> GutterFn<'doc>;
507− let gutters: &[(Gutter, usize)] = &[(diagnostic, 1), (line_number, 5)];
508−
509 let mut offset = 0;
423 let mut offset = 0;
510
424
511 // avoid lots of small allocations by reusing a text buffer for each line
425 // avoid lots of small allocations by reusing a text buffer for each line
512 let mut text = String::with_capacity(8);
426 let mut text = String::with_capacity(8);
513
427
514− for (constructor, width) in gutters {
428+ for (constructor, width) in view.gutters() {
515 let gutter = constructor(doc, view, theme, config, is_focused, *width);
429 let gutter = constructor(doc, view, theme, config, is_focused, *width);
516 text.reserve(*width); // ensure there's enough space for the gutter
430 text.reserve(*width); // ensure there's enough space for the gutter
517 for (i, line) in (view.offset.row..(last_line + 1)).enumerate() {
431 for (i, line) in (view.offset.row..(last_line + 1)).enumerate() {
@@ -1214,12 +1128,3 @@ fn canonicalize_key(key: &mut KeyEvent) {
1214 key.modifiers.remove(KeyModifiers::SHIFT)
1128 key.modifiers.remove(KeyModifiers::SHIFT)
1215 }
1129 }
1216}
1130}
1217−
1218−#[inline]
1219−const fn abs_diff(a: usize, b: usize) -> usize {
1220− if a > b {
1221− a - b
1222− } else {
1223− b - a
1224− }
1225−}
helix-view/src/gutter.rsnew file+95
@@ -0,0 +1,95 @@
1+use std::fmt::Write;
2+
3+use crate::{editor::Config, graphics::Style, Document, Theme, View};
4+
5+pub type GutterFn<'doc> = Box<dyn Fn(usize, bool, &mut String) -> Option<Style> + 'doc>;
6+pub type Gutter =
7+ for<'doc> fn(&'doc Document, &View, &Theme, &Config, bool, usize) -> GutterFn<'doc>;
8+
9+pub fn diagnostic<'doc>(
10+ doc: &'doc Document,
11+ _view: &View,
12+ theme: &Theme,
13+ _config: &Config,
14+ _is_focused: bool,
15+ _width: usize,
16+) -> GutterFn<'doc> {
17+ let warning = theme.get("warning");
18+ let error = theme.get("error");
19+ let info = theme.get("info");
20+ let hint = theme.get("hint");
21+ let diagnostics = doc.diagnostics();
22+
23+ Box::new(move |line: usize, _selected: bool, out: &mut String| {
24+ use helix_core::diagnostic::Severity;
25+ if let Some(diagnostic) = diagnostics.iter().find(|d| d.line == line) {
26+ write!(out, "●").unwrap();
27+ return Some(match diagnostic.severity {
28+ Some(Severity::Error) => error,
29+ Some(Severity::Warning) | None => warning,
30+ Some(Severity::Info) => info,
31+ Some(Severity::Hint) => hint,
32+ });
33+ }
34+ None
35+ })
36+}
37+
38+pub fn line_number<'doc>(
39+ doc: &'doc Document,
40+ view: &View,
41+ theme: &Theme,
42+ config: &Config,
43+ is_focused: bool,
44+ width: usize,
45+) -> GutterFn<'doc> {
46+ let text = doc.text().slice(..);
47+ let last_line = view.last_line(doc);
48+ // Whether to draw the line number for the last line of the
49+ // document or not. We only draw it if it's not an empty line.
50+ let draw_last = text.line_to_byte(last_line) < text.len_bytes();
51+
52+ let linenr = theme.get("ui.linenr");
53+ let linenr_select: Style = theme.try_get("ui.linenr.selected").unwrap_or(linenr);
54+
55+ let current_line = doc
56+ .text()
57+ .char_to_line(doc.selection(view.id).primary().cursor(text));
58+
59+ let config = config.line_number;
60+
61+ Box::new(move |line: usize, selected: bool, out: &mut String| {
62+ if line == last_line && !draw_last {
63+ write!(out, "{:>1$}", '~', width).unwrap();
64+ Some(linenr)
65+ } else {
66+ use crate::editor::LineNumber;
67+ let line = match config {
68+ LineNumber::Absolute => line + 1,
69+ LineNumber::Relative => {
70+ if current_line == line {
71+ line + 1
72+ } else {
73+ abs_diff(current_line, line)
74+ }
75+ }
76+ };
77+ let style = if selected && is_focused {
78+ linenr_select
79+ } else {
80+ linenr
81+ };
82+ write!(out, "{:>1$}", line, width).unwrap();
83+ Some(style)
84+ }
85+ })
86+}
87+
88+#[inline(always)]
89+const fn abs_diff(a: usize, b: usize) -> usize {
90+ if a > b {
91+ a - b
92+ } else {
93+ b - a
94+ }
95+}
helix-view/src/lib.rs+1
@@ -5,6 +5,7 @@ pub mod clipboard;
5pub mod document;
5pub mod document;
6pub mod editor;
6pub mod editor;
7pub mod graphics;
7pub mod graphics;
8+pub mod gutter;
8pub mod info;
9pub mod info;
9pub mod input;
10pub mod input;
10pub mod keyboard;
11pub mod keyboard;
helix-view/src/view.rs+20−4
@@ -1,6 +1,10 @@
1use std::borrow::Cow;
1use std::borrow::Cow;
2
2
3+use crate::{
4+ graphics::Rect,
5+ gutter::{self, Gutter},
3−use crate::{graphics::Rect, Document, DocumentId, ViewId};
6+ Document, DocumentId, ViewId,
7+};
4use helix_core::{
8use helix_core::{
5 graphemes::{grapheme_width, RopeGraphemes},
9 graphemes::{grapheme_width, RopeGraphemes},
6 line_ending::line_end_char_index,
10 line_ending::line_end_char_index,
@@ -60,6 +64,8 @@ impl JumpList {
60 }
64 }
61}
65}
62
66
67+const GUTTERS: &[(Gutter, usize)] = &[(gutter::diagnostic, 1), (gutter::line_number, 5)];
68+
63#[derive(Debug)]
69#[derive(Debug)]
64pub struct View {
70pub struct View {
65 pub id: ViewId,
71 pub id: ViewId,
@@ -83,10 +89,19 @@ impl View {
83 }
89 }
84 }
90 }
85
91
92+ pub fn gutters(&self) -> &[(Gutter, usize)] {
93+ GUTTERS
94+ }
95+
86 pub fn inner_area(&self) -> Rect {
96 pub fn inner_area(&self) -> Rect {
87− // TODO: not ideal
97+ // TODO: cache this
88− const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
98+ let offset = self
99+ .gutters()
100+ .iter()
101+ .map(|(_, width)| *width as u16)
102+ .sum::<u16>()
103+ + 1; // +1 for some space between gutters and line
89− self.area.clip_left(OFFSET).clip_bottom(1) // -1 for statusline
104+ self.area.clip_left(offset).clip_bottom(1) // -1 for statusline
90 }
105 }
91
106
92 //
107 //
@@ -276,6 +291,7 @@ mod tests {
276 use super::*;
291 use super::*;
277 use helix_core::Rope;
292 use helix_core::Rope;
278 const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
293 const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
294+ // const OFFSET: u16 = GUTTERS.iter().map(|(_, width)| *width as u16).sum();
279
295
280 #[test]
296 #[test]
281 fn test_text_pos_at_screen_coords() {
297 fn test_text_pos_at_screen_coords() {