on master help sign in
Extract view.inner_area(), simplify render_focused_view_elements
557fd86Blaž Hrastnik
helix-term/src/commands.rs+12−14
@@ -110,7 +110,7 @@ fn align_view(doc: &Document, view: &mut View, align: Align) {
110 .cursor(doc.text().slice(..));
110 .cursor(doc.text().slice(..));
111 let line = doc.text().char_to_line(pos);
111 let line = doc.text().char_to_line(pos);
112
112
113− let height = view.area.height.saturating_sub(1) as usize; // -1 for statusline
113+ let height = view.inner_area().height as usize;
114
114
115 let relative = match align {
115 let relative = match align {
116 Align::Center => height / 2,
116 Align::Center => height / 2,
@@ -455,7 +455,7 @@ fn goto_first_nonwhitespace(cx: &mut Context) {
455fn goto_window(cx: &mut Context, align: Align) {
455fn goto_window(cx: &mut Context, align: Align) {
456 let (view, doc) = current!(cx.editor);
456 let (view, doc) = current!(cx.editor);
457
457
458− let height = view.area.height.saturating_sub(1) as usize; // -1 for statusline
458+ let height = view.inner_area().height as usize;
459
459
460 // - 1 so we have at least one gap in the middle.
460 // - 1 so we have at least one gap in the middle.
461 // a height of 6 with padding of 3 on each side will keep shifting the view back and forth
461 // a height of 6 with padding of 3 on each side will keep shifting the view back and forth
@@ -898,11 +898,9 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
898 return;
898 return;
899 }
899 }
900
900
901− let scrolloff = cx
901+ let height = view.inner_area().height;
902− .editor
902+
903− .config
904− .scrolloff
905− .min(view.area.height as usize / 2);
903+ let scrolloff = cx.editor.config.scrolloff.min(height as usize / 2);
906
904
907 view.offset.row = match direction {
905 view.offset.row = match direction {
908 Forward => view.offset.row + offset,
906 Forward => view.offset.row + offset,
@@ -928,25 +926,25 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
928
926
929fn page_up(cx: &mut Context) {
927fn page_up(cx: &mut Context) {
930 let view = view!(cx.editor);
928 let view = view!(cx.editor);
931− let offset = view.area.height as usize;
929+ let offset = view.inner_area().height as usize;
932 scroll(cx, offset, Direction::Backward);
930 scroll(cx, offset, Direction::Backward);
933}
931}
934
932
935fn page_down(cx: &mut Context) {
933fn page_down(cx: &mut Context) {
936 let view = view!(cx.editor);
934 let view = view!(cx.editor);
937− let offset = view.area.height as usize;
935+ let offset = view.inner_area().height as usize;
938 scroll(cx, offset, Direction::Forward);
936 scroll(cx, offset, Direction::Forward);
939}
937}
940
938
941fn half_page_up(cx: &mut Context) {
939fn half_page_up(cx: &mut Context) {
942 let view = view!(cx.editor);
940 let view = view!(cx.editor);
943− let offset = view.area.height as usize / 2;
941+ let offset = view.inner_area().height as usize / 2;
944 scroll(cx, offset, Direction::Backward);
942 scroll(cx, offset, Direction::Backward);
945}
943}
946
944
947fn half_page_down(cx: &mut Context) {
945fn half_page_down(cx: &mut Context) {
948 let view = view!(cx.editor);
946 let view = view!(cx.editor);
949− let offset = view.area.height as usize / 2;
947+ let offset = view.inner_area().height as usize / 2;
950 scroll(cx, offset, Direction::Forward);
948 scroll(cx, offset, Direction::Forward);
951}
949}
952
950
@@ -4113,9 +4111,9 @@ fn align_view_middle(cx: &mut Context) {
4113 .cursor(doc.text().slice(..));
4111 .cursor(doc.text().slice(..));
4114 let pos = coords_at_pos(doc.text().slice(..), pos);
4112 let pos = coords_at_pos(doc.text().slice(..), pos);
4115
4113
4116− view.offset.col = pos.col.saturating_sub(
4114+ view.offset.col = pos
4117− ((view.area.width as usize).saturating_sub(crate::ui::editor::GUTTER_OFFSET as usize)) / 2,
4115+ .col
4118− );
4116+ .saturating_sub((view.inner_area().width as usize) / 2);
4119}
4117}
4120
4118
4121fn scroll_up(cx: &mut Context) {
4119fn scroll_up(cx: &mut Context) {
helix-term/src/ui/completion.rs+1−1
@@ -314,7 +314,7 @@ impl Component for Completion {
314 let half = area.height / 2;
314 let half = area.height / 2;
315 let height = 15.min(half);
315 let height = 15.min(half);
316 // we want to make sure the cursor is visible (not hidden behind the documentation)
316 // we want to make sure the cursor is visible (not hidden behind the documentation)
317− let y = if cursor_pos + view.area.y
317+ let y = if cursor_pos + area.y
318 >= (cx.editor.tree.area().height - height - 2/* statusline + commandline */)
318 >= (cx.editor.tree.area().height - height - 2/* statusline + commandline */)
319 {
319 {
320 0
320 0
helix-term/src/ui/editor.rs+60−100
@@ -38,8 +38,6 @@ pub struct EditorView {
38 autoinfo: Option<Info>,
38 autoinfo: Option<Info>,
39}
39}
40
40
41−pub const GUTTER_OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
42−
43impl Default for EditorView {
41impl Default for EditorView {
44 fn default() -> Self {
42 fn default() -> Self {
45 Self::new(Keymaps::default())
43 Self::new(Keymaps::default())
@@ -74,15 +72,10 @@ impl EditorView {
74 loader: &syntax::Loader,
72 loader: &syntax::Loader,
75 config: &helix_view::editor::Config,
73 config: &helix_view::editor::Config,
76 ) {
74 ) {
77− let area = Rect::new(
75+ let inner = view.inner_area();
78− view.area.x + GUTTER_OFFSET,
76+ let area = view.area;
79− view.area.y,
77+
80− view.area.width - GUTTER_OFFSET,
81− view.area.height.saturating_sub(1),
82− ); // - 1 for statusline
83− let height = view.area.height.saturating_sub(1); // - 1 for statusline
84−
85− let highlights = Self::doc_syntax_highlights(doc, view.offset, height, theme, loader);
78+ let highlights = Self::doc_syntax_highlights(doc, view.offset, inner.height, theme, loader);
86 let highlights = syntax::merge(highlights, Self::doc_diagnostics_highlights(doc, theme));
79 let highlights = syntax::merge(highlights, Self::doc_diagnostics_highlights(doc, theme));
87 let highlights: Box<dyn Iterator<Item = HighlightEvent>> = if is_focused {
80 let highlights: Box<dyn Iterator<Item = HighlightEvent>> = if is_focused {
88 Box::new(syntax::merge(
81 Box::new(syntax::merge(
@@ -93,11 +86,11 @@ impl EditorView {
93 Box::new(highlights)
86 Box::new(highlights)
94 };
87 };
95
88
96− Self::render_text_highlights(doc, view.offset, area, surface, theme, highlights);
89+ Self::render_text_highlights(doc, view.offset, inner, surface, theme, highlights);
97− Self::render_gutter(doc, view, area, surface, theme, config);
90+ Self::render_gutter(doc, view, view.area, surface, theme, is_focused, config);
98
91
99 if is_focused {
92 if is_focused {
100− Self::render_focused_view_elements(view, doc, area, theme, surface);
93+ Self::render_focused_view_elements(view, doc, inner, theme, surface);
101 }
94 }
102
95
103 // if we're not at the edge of the screen, draw a right border
96 // if we're not at the edge of the screen, draw a right border
@@ -113,7 +106,7 @@ impl EditorView {
113 }
106 }
114 }
107 }
115
108
116− self.render_diagnostics(doc, view, area, surface, theme);
109+ self.render_diagnostics(doc, view, inner, surface, theme);
117
110
118 let area = Rect::new(
111 let area = Rect::new(
119 view.area.x,
112 view.area.x,
@@ -369,7 +362,7 @@ impl EditorView {
369 }
362 }
370 }
363 }
371
364
372− /// Render brace match, selected line numbers, etc (meant for the focused view only)
365+ /// Render brace match, etc (meant for the focused view only)
373 pub fn render_focused_view_elements(
366 pub fn render_focused_view_elements(
374 view: &View,
367 view: &View,
375 doc: &Document,
368 doc: &Document,
@@ -377,77 +370,29 @@ impl EditorView {
377 theme: &Theme,
370 theme: &Theme,
378 surface: &mut Surface,
371 surface: &mut Surface,
379 ) {
372 ) {
373+ // Highlight matching braces
374+ if let Some(syntax) = doc.syntax() {
380− let text = doc.text().slice(..);
375+ let text = doc.text().slice(..);
376+ use helix_core::match_brackets;
381− let selection = doc.selection(view.id);
377+ let pos = doc.selection(view.id).primary().cursor(text);
382− let last_line = view.last_line(doc);
378+
383− let screen = {
379+ let pos = match_brackets::find(syntax, doc.text(), pos)
384− let start = text.line_to_char(view.offset.row);
380+ .and_then(|pos| view.screen_coords_at_pos(doc, text, pos));
385− let end = text.line_to_char(last_line + 1) + 1; // +1 for cursor at end of text.
381+
386− Range::new(start, end)
382+ if let Some(pos) = pos {
387− };
383+ // ensure col is on screen
388−
384+ if (pos.col as u16) < viewport.width + view.offset.col as u16
389− // render selected linenr(s)
385+ && pos.col >= view.offset.col
390− let linenr_select: Style = theme
386+ {
391− .try_get("ui.linenr.selected")
387+ let style = theme.try_get("ui.cursor.match").unwrap_or_else(|| {
392− .unwrap_or_else(|| theme.get("ui.linenr"));
388+ Style::default()
393−
389+ .add_modifier(Modifier::REVERSED)
394− // Whether to draw the line number for the last line of the
390+ .add_modifier(Modifier::DIM)
395− // document or not. We only draw it if it's not an empty line.
396− let draw_last = text.line_to_byte(last_line) < text.len_bytes();
397−
398− for selection in selection.iter().filter(|range| range.overlaps(&screen)) {
399− let head = view.screen_coords_at_pos(
400− doc,
401− text,
402− if selection.head > selection.anchor {
403− selection.head - 1
404− } else {
405− selection.head
406− },
407− );
408− if let Some(head) = head {
409− // Highlight line number for selected lines.
410− let line_number = view.offset.row + head.row;
411− let line_number_text = if line_number == last_line && !draw_last {
412− " ~".into()
413− } else {
414− format!("{:>5}", line_number + 1)
415− };
416− surface.set_stringn(
417− viewport.x - GUTTER_OFFSET + 1,
418− viewport.y + head.row as u16,
419− line_number_text,
420− 5,
421− linenr_select,
422− );
391+ });
423
392
424− // Highlight matching braces
425− // TODO: set cursor position for IME
426− if let Some(syntax) = doc.syntax() {
427− use helix_core::match_brackets;
428− let pos = doc
429− .selection(view.id)
430− .primary()
431− .cursor(doc.text().slice(..));
432− let pos = match_brackets::find(syntax, doc.text(), pos)
433− .and_then(|pos| view.screen_coords_at_pos(doc, text, pos));
434−
435− if let Some(pos) = pos {
436− // ensure col is on screen
437− if (pos.col as u16) < viewport.width + view.offset.col as u16
438− && pos.col >= view.offset.col
439− {
440− let style = theme.try_get("ui.cursor.match").unwrap_or_else(|| {
441− Style::default()
442− .add_modifier(Modifier::REVERSED)
443− .add_modifier(Modifier::DIM)
444− });
445−
446− surface
393+ surface
447− .get_mut(viewport.x + pos.col as u16, viewport.y + pos.row as u16)
394+ .get_mut(viewport.x + pos.col as u16, viewport.y + pos.row as u16)
448− .set_style(style);
395+ .set_style(style);
449− }
450− }
451 }
396 }
452 }
397 }
453 }
398 }
@@ -460,12 +405,15 @@ impl EditorView {
460 viewport: Rect,
405 viewport: Rect,
461 surface: &mut Surface,
406 surface: &mut Surface,
462 theme: &Theme,
407 theme: &Theme,
408+ is_focused: bool,
463 config: &helix_view::editor::Config,
409 config: &helix_view::editor::Config,
464 ) {
410 ) {
465 let text = doc.text().slice(..);
411 let text = doc.text().slice(..);
466 let last_line = view.last_line(doc);
412 let last_line = view.last_line(doc);
467
413
468 let linenr = theme.get("ui.linenr");
414 let linenr = theme.get("ui.linenr");
415+ let linenr_select: Style = theme.try_get("ui.linenr.selected").unwrap_or(linenr);
416+
469 let warning = theme.get("warning");
417 let warning = theme.get("warning");
470 let error = theme.get("error");
418 let error = theme.get("error");
471 let info = theme.get("info");
419 let info = theme.get("info");
@@ -478,11 +426,21 @@ impl EditorView {
478 let current_line = doc
426 let current_line = doc
479 .text()
427 .text()
480 .char_to_line(doc.selection(view.id).primary().anchor);
428 .char_to_line(doc.selection(view.id).primary().anchor);
429+
430+ // it's used inside an iterator so the collect isn't needless:
431+ // https://github.com/rust-lang/rust-clippy/issues/6164
432+ #[allow(clippy::clippy::needless_collect)]
433+ let cursors: Vec<_> = doc
434+ .selection(view.id)
435+ .iter()
436+ .map(|range| range.cursor_line(text))
437+ .collect();
438+
481 for (i, line) in (view.offset.row..(last_line + 1)).enumerate() {
439 for (i, line) in (view.offset.row..(last_line + 1)).enumerate() {
482 use helix_core::diagnostic::Severity;
440 use helix_core::diagnostic::Severity;
483 if let Some(diagnostic) = doc.diagnostics().iter().find(|d| d.line == line) {
441 if let Some(diagnostic) = doc.diagnostics().iter().find(|d| d.line == line) {
484 surface.set_stringn(
442 surface.set_stringn(
485− viewport.x - GUTTER_OFFSET,
443+ viewport.x,
486 viewport.y + i as u16,
444 viewport.y + i as u16,
487 "●",
445 "●",
488 1,
446 1,
@@ -495,25 +453,27 @@ impl EditorView {
495 );
453 );
496 }
454 }
497
455
498− // Line numbers having selections are rendered
456+ let selected = cursors.contains(&line);
499− // differently, further below.
457+
500− let line_number_text = if line == last_line && !draw_last {
458+ let text = if line == last_line && !draw_last {
501 " ~".into()
459 " ~".into()
502 } else {
460 } else {
503− match config.line_number {
461+ let line = match config.line_number {
504− LineNumber::Absolute => format!("{:>5}", line + 1),
462+ LineNumber::Absolute => line + 1,
505− LineNumber::Relative => {
463+ LineNumber::Relative => abs_diff(current_line, line),
506− let relative_line = abs_diff(current_line, line);
464+ };
507− format!("{:>5}", relative_line)
465+ format!("{:>5}", line)
508− }
509− }
510 };
466 };
511 surface.set_stringn(
467 surface.set_stringn(
512− viewport.x + 1 - GUTTER_OFFSET,
468+ viewport.x + 1,
513 viewport.y + i as u16,
469 viewport.y + i as u16,
514− line_number_text,
470+ text,
515 5,
471 5,
472+ if selected && is_focused {
473+ linenr_select
474+ } else {
516− linenr,
475+ linenr
476+ },
517 );
477 );
518 }
478 }
519 }
479 }
helix-view/src/editor.rs+4−4
@@ -194,7 +194,7 @@ impl Editor {
194 .primary()
194 .primary()
195 .cursor(doc.text().slice(..));
195 .cursor(doc.text().slice(..));
196 let line = doc.text().char_to_line(pos);
196 let line = doc.text().char_to_line(pos);
197− view.offset.row = line.saturating_sub(view.area.height as usize / 2);
197+ view.offset.row = line.saturating_sub(view.inner_area().height as usize / 2);
198
198
199 return;
199 return;
200 }
200 }
@@ -344,7 +344,6 @@ impl Editor {
344 // }
344 // }
345
345
346 pub fn cursor(&self) -> (Option<Position>, CursorKind) {
346 pub fn cursor(&self) -> (Option<Position>, CursorKind) {
347− const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
348 let view = view!(self);
347 let view = view!(self);
349 let doc = &self.documents[view.doc];
348 let doc = &self.documents[view.doc];
350 let cursor = doc
349 let cursor = doc
@@ -352,8 +351,9 @@ impl Editor {
352 .primary()
351 .primary()
353 .cursor(doc.text().slice(..));
352 .cursor(doc.text().slice(..));
354 if let Some(mut pos) = view.screen_coords_at_pos(doc, doc.text().slice(..), cursor) {
353 if let Some(mut pos) = view.screen_coords_at_pos(doc, doc.text().slice(..), cursor) {
354+ let inner = view.inner_area();
355− pos.col += view.area.x as usize + OFFSET as usize;
355+ pos.col += inner.x as usize;
356− pos.row += view.area.y as usize;
356+ pos.row += inner.y as usize;
357 (Some(pos), CursorKind::Hidden)
357 (Some(pos), CursorKind::Hidden)
358 } else {
358 } else {
359 (None, CursorKind::Hidden)
359 (None, CursorKind::Hidden)
helix-view/src/view.rs+23−18
@@ -80,25 +80,32 @@ impl View {
80 }
80 }
81 }
81 }
82
82
83+ pub fn inner_area(&self) -> Rect {
84+ // TODO: not ideal
85+ const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
86+ Rect::new(
87+ self.area.x + OFFSET,
88+ self.area.y,
89+ self.area.width - OFFSET,
90+ self.area.height.saturating_sub(1), // -1 for statusline
91+ )
92+ }
93+
83 pub fn ensure_cursor_in_view(&mut self, doc: &Document, scrolloff: usize) {
94 pub fn ensure_cursor_in_view(&mut self, doc: &Document, scrolloff: usize) {
84 let cursor = doc
95 let cursor = doc
85 .selection(self.id)
96 .selection(self.id)
86 .primary()
97 .primary()
87 .cursor(doc.text().slice(..));
98 .cursor(doc.text().slice(..));
88− let pos = coords_at_pos(doc.text().slice(..), cursor);
99+ let Position { col, row: line } = coords_at_pos(doc.text().slice(..), cursor);
89− let line = pos.row;
100+ let inner_area = self.inner_area();
90− let col = pos.col;
91− let height = self.area.height.saturating_sub(1); // - 1 for statusline
92− let last_line = (self.offset.row + height as usize).saturating_sub(1);
101+ let last_line = (self.offset.row + inner_area.height as usize).saturating_sub(1);
93
102
94 // - 1 so we have at least one gap in the middle.
103 // - 1 so we have at least one gap in the middle.
95 // a height of 6 with padding of 3 on each side will keep shifting the view back and forth
104 // a height of 6 with padding of 3 on each side will keep shifting the view back and forth
96 // as we type
105 // as we type
97− let scrolloff = scrolloff.min(height.saturating_sub(1) as usize / 2);
106+ let scrolloff = scrolloff.min(inner_area.height.saturating_sub(1) as usize / 2);
98
107
99− // TODO: not ideal
100− const OFFSET: usize = 7; // 1 diagnostic + 5 linenr + 1 gutter
101− let last_col = (self.offset.col + self.area.width as usize).saturating_sub(OFFSET + 1);
108+ let last_col = self.offset.col + inner_area.width.saturating_sub(1) as usize;
102
109
103 if line > last_line.saturating_sub(scrolloff) {
110 if line > last_line.saturating_sub(scrolloff) {
104 // scroll down
111 // scroll down
@@ -120,7 +127,7 @@ impl View {
120 /// Calculates the last visible line on screen
127 /// Calculates the last visible line on screen
121 #[inline]
128 #[inline]
122 pub fn last_line(&self, doc: &Document) -> usize {
129 pub fn last_line(&self, doc: &Document) -> usize {
123− let height = self.area.height.saturating_sub(1); // - 1 for statusline
130+ let height = self.inner_area().height;
124 std::cmp::min(
131 std::cmp::min(
125 // Saturating subs to make it inclusive zero indexing.
132 // Saturating subs to make it inclusive zero indexing.
126 (self.offset.row + height as usize).saturating_sub(1),
133 (self.offset.row + height as usize).saturating_sub(1),
@@ -172,19 +179,17 @@ impl View {
172 column: u16,
179 column: u16,
173 tab_width: usize,
180 tab_width: usize,
174 ) -> Option<usize> {
181 ) -> Option<usize> {
175− // TODO: not ideal
182+ let inner = self.inner_area();
176− const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
177−
178− // 2 for status
183+ // 1 for status
179− if row < self.area.top() || row > self.area.bottom().saturating_sub(2) {
184+ if row < inner.top() || row >= inner.bottom() {
180 return None;
185 return None;
181 }
186 }
182
187
183− if column < self.area.left() + OFFSET || column > self.area.right() {
188+ if column < inner.left() || column > inner.right() {
184 return None;
189 return None;
185 }
190 }
186
191
187− let line_number = (row - self.area.y) as usize + self.offset.row;
192+ let line_number = (row - inner.y) as usize + self.offset.row;
188
193
189 if line_number > text.len_lines() - 1 {
194 if line_number > text.len_lines() - 1 {
190 return Some(text.len_chars());
195 return Some(text.len_chars());
@@ -194,7 +199,7 @@ impl View {
194
199
195 let current_line = text.line(line_number);
200 let current_line = text.line(line_number);
196
201
197− let target = (column - OFFSET - self.area.x) as usize + self.offset.col;
202+ let target = (column - inner.x) as usize + self.offset.col;
198 let mut selected = 0;
203 let mut selected = 0;
199
204
200 for grapheme in RopeGraphemes::new(current_line) {
205 for grapheme in RopeGraphemes::new(current_line) {