@@ -4,7 +4,10 @@ use crate::{
4 job::{self, Callback},
4 job::{self, Callback},
6 keymap::{KeymapResult, Keymaps},
6 keymap::{KeymapResult, Keymaps},
8+ document::{render_document, LinePos, TextRenderer, TranslatedPosition},
7− ui::{Completion, ProgressSpinners},
9+ Completion, ProgressSpinners,
10use helix_core::{
13use helix_core::{
@@ -13,8 +16,9 @@ use helix_core::{
14 movement::Direction,
17 movement::Direction,
15 syntax::{self, HighlightEvent},
18 syntax::{self, HighlightEvent},
19+ text_annotations::TextAnnotations,
16 unicode::width::UnicodeWidthStr,
20 unicode::width::UnicodeWidthStr,
17− visual_coords_at_pos, LineEnding, Position, Range, Selection, Transaction,
21+ visual_offset_from_block, Position, Range, Selection, Transaction,
19use helix_view::{
23use helix_view::{
20 document::{Mode, SCRATCH_BUFFER_NAME},
24 document::{Mode, SCRATCH_BUFFER_NAME},
@@ -24,12 +28,12 @@ use helix_view::{
24 keyboard::{KeyCode, KeyModifiers},
28 keyboard::{KeyCode, KeyModifiers},
25 Document, Editor, Theme, View,
29 Document, Editor, Theme, View,
27−use std::{borrow::Cow, cmp::min, num::NonZeroUsize, path::PathBuf};
31+use std::{num::NonZeroUsize, path::PathBuf, rc::Rc};
29use tui::buffer::Buffer as Surface;
33use tui::buffer::Buffer as Surface;
31−use super::lsp::SignatureHelp;
32use super::statusline;
35use super::statusline;
36+use super::{document::LineDecoration, lsp::SignatureHelp};
34pub struct EditorView {
38pub struct EditorView {
35 pub keymaps: Keymaps,
39 pub keymaps: Keymaps,
@@ -83,6 +87,10 @@ impl EditorView {
83 let theme = &editor.theme;
87 let theme = &editor.theme;
84 let config = editor.config();
88 let config = editor.config();
90+ let text_annotations = view.text_annotations(doc, Some(theme));
91+ let mut line_decorations: Vec<Box<dyn LineDecoration>> = Vec::new();
92+ let mut translated_positions: Vec<TranslatedPosition> = Vec::new();
86 // DAP: Highlight current stack frame position
94 // DAP: Highlight current stack frame position
87 let stack_frame = editor.debugger.as_ref().and_then(|debugger| {
95 let stack_frame = editor.debugger.as_ref().and_then(|debugger| {
88 if let (Some(frame), Some(thread_id)) = (debugger.active_frame, debugger.thread_id) {
96 if let (Some(frame), Some(thread_id)) = (debugger.active_frame, debugger.thread_id) {
@@ -103,28 +111,40 @@ impl EditorView {
103 == doc.path()
111 == doc.path()
105 let line = frame.line - 1; // convert to 0-indexing
113 let line = frame.line - 1; // convert to 0-indexing
106− if line >= view.offset.row && line < view.offset.row + area.height as usize {
114+ let style = theme.get("ui.highlight");
115+ let line_decoration = move |renderer: &mut TextRenderer, pos: LinePos| {
116+ if pos.doc_line != line {
107− surface.set_style(
120+ .surface
108− Rect::new(
121+ .set_style(Rect::new(area.x, pos.visual_line, area.width, 1), style);
110− area.y + (line - view.offset.row) as u16,
123+
111− area.width,
124+ line_decorations.push(Box::new(line_decoration));
114− theme.get("ui.highlight"),
120 if is_focused && config.cursorline {
128 if is_focused && config.cursorline {
121− Self::highlight_cursorline(doc, view, surface, theme);
129+ line_decorations.push(Self::cursorline_decorator(doc, view, theme))
123 if is_focused && config.cursorcolumn {
132 if is_focused && config.cursorcolumn {
124− Self::highlight_cursorcolumn(doc, view, surface, theme);
133+ Self::highlight_cursorcolumn(doc, view, surface, theme, inner, &text_annotations);
136+ let mut highlights =
137+ Self::doc_syntax_highlights(doc, view.offset.anchor, inner.height, theme);
138+ let overlay_highlights = Self::overlay_syntax_highlights(
144+ if !overlay_highlights.is_empty() {
145+ highlights = Box::new(syntax::merge(highlights, overlay_highlights));
127− let mut highlights = Self::doc_syntax_highlights(doc, view.offset, inner.height, theme);
128 for diagnostic in Self::doc_diagnostics_highlights(doc, theme) {
148 for diagnostic in Self::doc_diagnostics_highlights(doc, theme) {
129 // Most of the `diagnostic` Vecs are empty most of the time. Skipping
149 // Most of the `diagnostic` Vecs are empty most of the time. Skipping
130 // a merge for any empty Vec saves a significant amount of work.
150 // a merge for any empty Vec saves a significant amount of work.
@@ -133,8 +153,9 @@ impl EditorView {
134 highlights = Box::new(syntax::merge(highlights, diagnostic));
154 highlights = Box::new(syntax::merge(highlights, diagnostic));
136 let highlights: Box<dyn Iterator<Item = HighlightEvent>> = if is_focused {
157 let highlights: Box<dyn Iterator<Item = HighlightEvent>> = if is_focused {
137− Box::new(syntax::merge(
158+ let highlights = syntax::merge(
138 highlights,
159 highlights,
139 Self::doc_selection_highlights(
160 Self::doc_selection_highlights(
140 editor.mode(),
161 editor.mode(),
@@ -143,19 +164,52 @@ impl EditorView {
144 &config.cursor_shape,
165 &config.cursor_shape,
168+ let focused_view_elements = Self::highlight_focused_view_elements(view, doc, theme);
169+ if focused_view_elements.is_empty() {
170+ Box::new(highlights)
172+ Box::new(syntax::merge(highlights, focused_view_elements))
148 Box::new(highlights)
175 Box::new(highlights)
151− Self::render_text_highlights(doc, view.offset, inner, surface, theme, highlights, &config);
178+ Self::render_gutter(
152− Self::render_gutter(editor, doc, view, view.area, surface, theme, is_focused);
179+ editor,
153− Self::render_rulers(editor, doc, view, inner, surface, theme);
180+ doc,
185+ &mut line_decorations,
155 if is_focused {
188 if is_focused {
156− Self::render_focused_view_elements(view, doc, inner, theme, surface);
189+ let cursor = doc
192+ .cursor(doc.text().slice(..));
193+ // set the cursor_cache to out of view in case the position is not found
194+ editor.cursor_cache.set(Some(None));
195+ let update_cursor_cache =
196+ |_: &mut TextRenderer, pos| editor.cursor_cache.set(Some(Some(pos)));
197+ translated_positions.push((cursor, Box::new(update_cursor_cache)));
208+ &mut line_decorations,
209+ &mut *translated_positions,
211+ Self::render_rulers(editor, doc, view, inner, surface, theme);
159 // if we're not at the edge of the screen, draw a right border
213 // if we're not at the edge of the screen, draw a right border
160 if viewport.right() != view.area.right() {
214 if viewport.right() != view.area.right() {
161 let x = area.right();
215 let x = area.right();
@@ -203,31 +257,53 @@ impl EditorView {
204 // View might be horizontally scrolled, convert from absolute distance
258 // View might be horizontally scrolled, convert from absolute distance
205 // from the 1st column to relative distance from left of viewport
259 // from the 1st column to relative distance from left of viewport
206− .filter_map(|ruler| ruler.checked_sub(1 + view.offset.col as u16))
260+ .filter_map(|ruler| ruler.checked_sub(1 + view.offset.horizontal_offset as u16))
207 .filter(|ruler| ruler < &viewport.width)
261 .filter(|ruler| ruler < &viewport.width)
208 .map(|ruler| viewport.clip_left(ruler).with_width(1))
262 .map(|ruler| viewport.clip_left(ruler).with_width(1))
209 .for_each(|area| surface.set_style(area, ruler_theme))
263 .for_each(|area| surface.set_style(area, ruler_theme))
266+ pub fn overlay_syntax_highlights(
270+ text_annotations: &TextAnnotations,
271+ ) -> Vec<(usize, std::ops::Range<usize>)> {
272+ let text = doc.text().slice(..);
273+ let row = text.char_to_line(anchor.min(text.len_chars()));
276+ // Calculate viewport byte ranges:
277+ // Saturating subs to make it inclusive zero indexing.
278+ let last_line = text.len_lines().saturating_sub(1);
279+ let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line);
280+ let start = text.line_to_byte(row.min(last_line));
281+ let end = text.line_to_byte(last_visible_line + 1);
286+ text_annotations.collect_overlay_highlights(range)
212 /// Get syntax highlights for a document in a view represented by the first line
289 /// Get syntax highlights for a document in a view represented by the first line
213 /// and column (`offset`) and the last line. This is done instead of using a view
290 /// and column (`offset`) and the last line. This is done instead of using a view
214 /// directly to enable rendering syntax highlighted docs anywhere (eg. picker preview)
291 /// directly to enable rendering syntax highlighted docs anywhere (eg. picker preview)
215 pub fn doc_syntax_highlights<'doc>(
292 pub fn doc_syntax_highlights<'doc>(
216 doc: &'doc Document,
293 doc: &'doc Document,
217− offset: Position,
294+ anchor: usize,
218 height: u16,
295 height: u16,
219 _theme: &Theme,
296 _theme: &Theme,
220 ) -> Box<dyn Iterator<Item = HighlightEvent> + 'doc> {
297 ) -> Box<dyn Iterator<Item = HighlightEvent> + 'doc> {
221 let text = doc.text().slice(..);
298 let text = doc.text().slice(..);
299+ let row = text.char_to_line(anchor.min(text.len_chars()));
223 let range = {
301 let range = {
224 // Calculate viewport byte ranges:
302 // Calculate viewport byte ranges:
225 // Saturating subs to make it inclusive zero indexing.
303 // Saturating subs to make it inclusive zero indexing.
226− let last_line = doc.text().len_lines().saturating_sub(1);
304+ let last_line = text.len_lines().saturating_sub(1);
227− let last_visible_line = (offset.row + height as usize)
305+ let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line);
230− let start = text.line_to_byte(offset.row.min(last_line));
306+ let start = text.line_to_byte(row.min(last_line));
231 let end = text.line_to_byte(last_visible_line + 1);
307 let end = text.line_to_byte(last_visible_line + 1);
233 start..end
309 start..end
@@ -272,11 +348,11 @@ impl EditorView {
272 use helix_core::diagnostic::Severity;
348 use helix_core::diagnostic::Severity;
273 let get_scope_of = |scope| {
349 let get_scope_of = |scope| {
275− .find_scope_index(scope)
351+ .find_scope_index_exact(scope)
276 // get one of the themes below as fallback values
352 // get one of the themes below as fallback values
277− .or_else(|| theme.find_scope_index("diagnostic"))
353+ .or_else(|| theme.find_scope_index_exact("diagnostic"))
278− .or_else(|| theme.find_scope_index("ui.cursor"))
354+ .or_else(|| theme.find_scope_index_exact("ui.cursor"))
279− .or_else(|| theme.find_scope_index("ui.selection"))
355+ .or_else(|| theme.find_scope_index_exact("ui.selection"))
281 "at least one of the following scopes must be defined in the theme: `diagnostic`, `ui.cursor`, or `ui.selection`",
357 "at least one of the following scopes must be defined in the theme: `diagnostic`, `ui.cursor`, or `ui.selection`",
@@ -339,29 +415,29 @@ impl EditorView {
339 let cursor_is_block = cursorkind == CursorKind::Block;
415 let cursor_is_block = cursorkind == CursorKind::Block;
341 let selection_scope = theme
417 let selection_scope = theme
342− .find_scope_index("ui.selection")
418+ .find_scope_index_exact("ui.selection")
343 .expect("could not find `ui.selection` scope in the theme!");
419 .expect("could not find `ui.selection` scope in the theme!");
344 let primary_selection_scope = theme
420 let primary_selection_scope = theme
345− .find_scope_index("ui.selection.primary")
421+ .find_scope_index_exact("ui.selection.primary")
346 .unwrap_or(selection_scope);
422 .unwrap_or(selection_scope);
347 let base_cursor_scope = theme
423 let base_cursor_scope = theme
348− .find_scope_index("ui.cursor")
424+ .find_scope_index_exact("ui.cursor")
349 .unwrap_or(selection_scope);
425 .unwrap_or(selection_scope);
350 let base_primary_cursor_scope = theme
426 let base_primary_cursor_scope = theme
351 .find_scope_index("ui.cursor.primary")
427 .find_scope_index("ui.cursor.primary")
352 .unwrap_or(base_cursor_scope);
428 .unwrap_or(base_cursor_scope);
354 let cursor_scope = match mode {
430 let cursor_scope = match mode {
355− Mode::Insert => theme.find_scope_index("ui.cursor.insert"),
431+ Mode::Insert => theme.find_scope_index_exact("ui.cursor.insert"),
356− Mode::Select => theme.find_scope_index("ui.cursor.select"),
432+ Mode::Select => theme.find_scope_index_exact("ui.cursor.select"),
357− Mode::Normal => theme.find_scope_index("ui.cursor.normal"),
433+ Mode::Normal => theme.find_scope_index_exact("ui.cursor.normal"),
359 .unwrap_or(base_cursor_scope);
435 .unwrap_or(base_cursor_scope);
361 let primary_cursor_scope = match mode {
437 let primary_cursor_scope = match mode {
362− Mode::Insert => theme.find_scope_index("ui.cursor.primary.insert"),
438+ Mode::Insert => theme.find_scope_index_exact("ui.cursor.primary.insert"),
363− Mode::Select => theme.find_scope_index("ui.cursor.primary.select"),
439+ Mode::Select => theme.find_scope_index_exact("ui.cursor.primary.select"),
364− Mode::Normal => theme.find_scope_index("ui.cursor.primary.normal"),
440+ Mode::Normal => theme.find_scope_index_exact("ui.cursor.primary.normal"),
366 .unwrap_or(base_primary_cursor_scope);
442 .unwrap_or(base_primary_cursor_scope);
@@ -424,248 +500,26 @@ impl EditorView {
427− pub fn render_text_highlights<H: Iterator<Item = HighlightEvent>>(
431− surface: &mut Surface,
434− config: &helix_view::editor::Config,
436− let whitespace = &config.whitespace;
437− use helix_view::editor::WhitespaceRenderValue;
439− // It's slightly more efficient to produce a full RopeSlice from the Rope, then slice that a bunch
440− // of times than it is to always call Rope::slice/get_slice (it will internally always hit RSEnum::Light).
441− let text = doc.text().slice(..);
443− let characters = &whitespace.characters;
445− let mut spans = Vec::new();
446− let mut visual_x = 0usize;
447− let mut line = 0u16;
448− let tab_width = doc.tab_width();
449− let tab = if whitespace.render.tab() == WhitespaceRenderValue::All {
450− std::iter::once(characters.tab)
451− .chain(std::iter::repeat(characters.tabpad).take(tab_width - 1))
454− " ".repeat(tab_width)
456− let space = characters.space.to_string();
457− let nbsp = characters.nbsp.to_string();
458− let newline = if whitespace.render.newline() == WhitespaceRenderValue::All {
459− characters.newline.to_string()
463− let indent_guide_char = config.indent_guides.character.to_string();
465− let text_style = theme.get("ui.text");
466− let whitespace_style = theme.get("ui.virtual.whitespace");
468− let mut is_in_indent_area = true;
469− let mut last_line_indent_level = 0;
471− // use whitespace style as fallback for indent-guide
472− let indent_guide_style = text_style.patch(
474− .try_get("ui.virtual.indent-guide")
475− .unwrap_or_else(|| theme.get("ui.virtual.whitespace")),
478− let draw_indent_guides = |indent_level, line, surface: &mut Surface| {
479− if !config.indent_guides.render {
483− let starting_indent =
484− (offset.col / tab_width) + config.indent_guides.skip_levels as usize;
486− // Don't draw indent guides outside of view
487− let end_indent = min(
489− // Add tab_width - 1 to round up, since the first visible
490− // indent might be a bit after offset.col
491− offset.col + viewport.width as usize + (tab_width - 1),
494− for i in starting_indent..end_indent {
495− let x = (viewport.x as usize + (i * tab_width) - offset.col) as u16;
496− let y = viewport.y + line;
497− debug_assert!(surface.in_bounds(x, y));
498− surface.set_string(x, y, &indent_guide_char, indent_guide_style);
502− 'outer: for event in highlights {
504− HighlightEvent::HighlightStart(span) => {
507− HighlightEvent::HighlightEnd => {
510− HighlightEvent::Source { start, end } => {
511− let is_trailing_cursor = text.len_chars() < end;
513− // `unwrap_or_else` part is for off-the-end indices of
514− // the rope, to allow cursor highlighting at the end
516− let text = text.get_slice(start..end).unwrap_or_else(|| " ".into());
519− .fold(text_style, |acc, span| acc.patch(theme.highlight(span.0)));
521− let space = if whitespace.render.space() == WhitespaceRenderValue::All
522− && !is_trailing_cursor
529− let nbsp = if whitespace.render.nbsp() == WhitespaceRenderValue::All
530− && text.len_chars() < end
537− use helix_core::graphemes::{grapheme_width, RopeGraphemes};
539− for grapheme in RopeGraphemes::new(text) {
540− let out_of_bounds = offset.col > visual_x
541− || visual_x >= viewport.width as usize + offset.col;
543− if LineEnding::from_rope_slice(&grapheme).is_some() {
545− // we still want to render an empty cell with the style
547− (viewport.x as usize + visual_x - offset.col) as u16,
550− style.patch(whitespace_style),
554− draw_indent_guides(last_line_indent_level, line, surface);
558− is_in_indent_area = true;
560− // TODO: with proper iter this shouldn't be necessary
561− if line >= viewport.height {
565− let grapheme = Cow::from(grapheme);
568− let (display_grapheme, width) = if grapheme == "\t" {
569− is_whitespace = true;
570− // make sure we display tab as appropriate amount of spaces
571− let visual_tab_width = tab_width - (visual_x % tab_width);
572− let grapheme_tab_width =
573− helix_core::str_utils::char_to_byte_idx(&tab, visual_tab_width);
575− (&tab[..grapheme_tab_width], visual_tab_width)
576− } else if grapheme == " " {
577− is_whitespace = true;
579− } else if grapheme == "\u{00A0}" {
580− is_whitespace = true;
583− is_whitespace = false;
584− // Cow will prevent allocations if span contained in a single slice
585− // which should really be the majority case
586− let width = grapheme_width(&grapheme);
587− (grapheme.as_ref(), width)
590− let cut_off_start = offset.col.saturating_sub(visual_x);
593− // if we're offscreen just keep going until we hit a new line
595− (viewport.x as usize + visual_x - offset.col) as u16,
599− style.patch(whitespace_style)
604− } else if cut_off_start != 0 && cut_off_start < width {
605− // partially on screen
606− let rect = Rect::new(
609− (width - cut_off_start) as u16,
615− style.patch(whitespace_style)
622− if is_in_indent_area && !(grapheme == " " || grapheme == "\t") {
623− draw_indent_guides(visual_x, line, surface);
624− is_in_indent_area = false;
625− last_line_indent_level = visual_x;
628− visual_x = visual_x.saturating_add(width);
636 /// Render brace match, etc (meant for the focused view only)
503 /// Render brace match, etc (meant for the focused view only)
637− pub fn render_focused_view_elements(
504+ pub fn highlight_focused_view_elements(
638 view: &View,
505 view: &View,
639 doc: &Document,
506 doc: &Document,
641 theme: &Theme,
507 theme: &Theme,
642− surface: &mut Surface,
508+ ) -> Vec<(usize, std::ops::Range<usize>)> {
644 // Highlight matching braces
509 // Highlight matching braces
645 if let Some(syntax) = doc.syntax() {
510 if let Some(syntax) = doc.syntax() {
646 let text = doc.text().slice(..);
511 let text = doc.text().slice(..);
647 use helix_core::match_brackets;
512 use helix_core::match_brackets;
648 let pos = doc.selection(view.id).primary().cursor(text);
513 let pos = doc.selection(view.id).primary().cursor(text);
650− let pos = match_brackets::find_matching_bracket(syntax, doc.text(), pos)
515+ if let Some(pos) = match_brackets::find_matching_bracket(syntax, doc.text(), pos) {
651− .and_then(|pos| view.screen_coords_at_pos(doc, text, pos));
653− if let Some(pos) = pos {
654 // ensure col is on screen
516 // ensure col is on screen
655− if (pos.col as u16) < viewport.width + view.offset.col as u16
656− && pos.col >= view.offset.col
658− let style = theme.try_get("ui.cursor.match").unwrap_or_else(|| {
517+ if let Some(highlight) = theme.find_scope_index_exact("ui.cursor.match") {
659− Style::default()
518+ return vec![(highlight, pos..pos + 1)];
660− .add_modifier(Modifier::REVERSED)
661− .add_modifier(Modifier::DIM)
664− surface[(viewport.x + pos.col as u16, viewport.y + pos.row as u16)]
671 /// Render bufferline at the top
525 /// Render bufferline at the top
@@ -721,22 +575,17 @@ impl EditorView {
724− pub fn render_gutter(
578+ pub fn render_gutter<'d>(
725− editor: &Editor,
579+ editor: &'d Editor,
726− doc: &Document,
580+ doc: &'d Document,
727 view: &View,
581 view: &View,
728 viewport: Rect,
582 viewport: Rect,
729− surface: &mut Surface,
730 theme: &Theme,
583 theme: &Theme,
731 is_focused: bool,
584 is_focused: bool,
585+ line_decorations: &mut Vec<Box<(dyn LineDecoration + 'd)>>,
733 let text = doc.text().slice(..);
587 let text = doc.text().slice(..);
734− let last_line = view.last_line(doc);
736− // it's used inside an iterator so the collect isn't needless:
737− // https://github.com/rust-lang/rust-clippy/issues/6164
738− #[allow(clippy::needless_collect)]
739− let cursors: Vec<_> = doc
588+ let cursors: Rc<[_]> = doc
740 .selection(view.id)
589 .selection(view.id)
742 .map(|range| range.cursor_line(text))
591 .map(|range| range.cursor_line(text))
@@ -746,29 +595,36 @@ impl EditorView {
747 let gutter_style = theme.get("ui.gutter");
596 let gutter_style = theme.get("ui.gutter");
748 let gutter_selected_style = theme.get("ui.gutter.selected");
597 let gutter_selected_style = theme.get("ui.gutter.selected");
749−
598+ let gutter_style_virtual = theme.get("ui.gutter.virtual");
750− // avoid lots of small allocations by reusing a text buffer for each line
599+ let gutter_selected_style_virtual = theme.get("ui.gutter.selected.virtual");
751− let mut text = String::with_capacity(8);
753 for gutter_type in view.gutters() {
601 for gutter_type in view.gutters() {
754 let mut gutter = gutter_type.style(editor, doc, view, theme, is_focused);
602 let mut gutter = gutter_type.style(editor, doc, view, theme, is_focused);
755 let width = gutter_type.width(view, doc);
603 let width = gutter_type.width(view, doc);
756− text.reserve(width); // ensure there's enough space for the gutter
604+ // avoid lots of small allocations by reusing a text buffer for each line
757− for (i, line) in (view.offset.row..(last_line + 1)).enumerate() {
605+ let mut text = String::with_capacity(width);
606+ let cursors = cursors.clone();
607+ let gutter_decoration = move |renderer: &mut TextRenderer, pos: LinePos| {
608+ // TODO handle softwrap in gutters
758− let selected = cursors.contains(&line);
609+ let selected = cursors.contains(&pos.doc_line);
759 let x = viewport.x + offset;
610 let x = viewport.x + offset;
760− let y = viewport.y + i as u16;
611+ let y = viewport.y + pos.visual_line;
762− let gutter_style = if selected {
613+ let gutter_style = match (selected, pos.first_visual_line) {
614+ (false, true) => gutter_style,
763− gutter_selected_style
615+ (true, true) => gutter_selected_style,
764− } else {
616+ (false, false) => gutter_style_virtual,
765− gutter_style
617+ (true, false) => gutter_selected_style_virtual,
768− if let Some(style) = gutter(line, selected, &mut text) {
620+ if let Some(style) =
621+ gutter(pos.doc_line, selected, pos.first_visual_line, &mut text)
769− surface.set_stringn(x, y, &text, width, gutter_style.patch(style));
625+ .set_stringn(x, y, &text, width, gutter_style.patch(style));
771− surface.set_style(
627+ renderer.surface.set_style(
@@ -779,7 +635,8 @@ impl EditorView {
781 text.clear();
637 text.clear();
639+ line_decorations.push(Box::new(gutter_decoration));
784 offset += width as u16;
641 offset += width as u16;
@@ -840,10 +697,13 @@ impl EditorView {
842 /// Apply the highlighting on the lines where a cursor is active
699 /// Apply the highlighting on the lines where a cursor is active
843− pub fn highlight_cursorline(doc: &Document, view: &View, surface: &mut Surface, theme: &Theme) {
700+ pub fn cursorline_decorator(
704+ ) -> Box<dyn LineDecoration> {
844 let text = doc.text().slice(..);
705 let text = doc.text().slice(..);
845− let last_line = view.last_line(doc);
706+ // TODO only highlight the visual line that contains the cursor instead of the full visual line
847 let primary_line = doc.selection(view.id).primary().cursor_line(text);
707 let primary_line = doc.selection(view.id).primary().cursor_line(text);
849 // The secondary_lines do contain the primary_line, it doesn't matter
709 // The secondary_lines do contain the primary_line, it doesn't matter
@@ -860,20 +720,23 @@ impl EditorView {
861 let primary_style = theme.get("ui.cursorline.primary");
721 let primary_style = theme.get("ui.cursorline.primary");
862 let secondary_style = theme.get("ui.cursorline.secondary");
722 let secondary_style = theme.get("ui.cursorline.secondary");
723+ let viewport = view.area;
864− for line in view.offset.row..(last_line + 1) {
725+ let line_decoration = move |renderer: &mut TextRenderer, pos: LinePos| {
865 let area = Rect::new(
726 let area = Rect::new(
866− view.area.x,
727+ viewport.x,
867− view.area.y + (line - view.offset.row) as u16,
728+ viewport.y + pos.visual_line as u16,
868− view.area.width,
729+ viewport.width,
871− if primary_line == line {
732+ if primary_line == pos.doc_line {
872− surface.set_style(area, primary_style);
733+ renderer.surface.set_style(area, primary_style);
873− } else if secondary_lines.binary_search(&line).is_ok() {
734+ } else if secondary_lines.binary_search(&pos.doc_line).is_ok() {
874− surface.set_style(area, secondary_style);
735+ renderer.surface.set_style(area, secondary_style);
739+ Box::new(line_decoration)
879 /// Apply the highlighting on the columns where a cursor is active
742 /// Apply the highlighting on the columns where a cursor is active
@@ -882,6 +745,8 @@ impl EditorView {
882 view: &View,
745 view: &View,
883 surface: &mut Surface,
746 surface: &mut Surface,
884 theme: &Theme,
747 theme: &Theme,
749+ text_annotations: &TextAnnotations,
886 let text = doc.text().slice(..);
751 let text = doc.text().slice(..);
@@ -897,19 +762,23 @@ impl EditorView {
897 .unwrap_or_else(|| theme.get("ui.cursorline.secondary"));
762 .unwrap_or_else(|| theme.get("ui.cursorline.secondary"));
899 let inner_area = view.inner_area(doc);
764 let inner_area = view.inner_area(doc);
900− let offset = view.offset.col;
902 let selection = doc.selection(view.id);
766 let selection = doc.selection(view.id);
903 let primary = selection.primary();
767 let primary = selection.primary();
768+ let text_format = doc.text_format(viewport.width, None);
904 for range in selection.iter() {
769 for range in selection.iter() {
905 let is_primary = primary == *range;
770 let is_primary = primary == *range;
771+ let cursor = range.cursor(text);
773+ let Position { col, .. } =
774+ visual_offset_from_block(text, cursor, cursor, &text_format, text_annotations).0;
907− let Position { row: _, col } =
908− visual_coords_at_pos(text, range.cursor(text), doc.tab_width());
909 // if the cursor is horizontally in the view
776 // if the cursor is horizontally in the view
777+ if col >= view.offset.horizontal_offset
910− if col >= offset && inner_area.width > (col - offset) as u16 {
778+ && inner_area.width > (col - view.offset.horizontal_offset) as u16
911 let area = Rect::new(
780 let area = Rect::new(
912− inner_area.x + (col - offset) as u16,
781+ inner_area.x + (col - view.offset.horizontal_offset) as u16,
913 view.area.y,
782 view.area.y,
915 view.area.height,
784 view.area.height,
@@ -1149,7 +1018,7 @@ impl EditorView {
1150 let pos_and_view = |editor: &Editor, row, column| {
1019 let pos_and_view = |editor: &Editor, row, column| {
1151 editor.tree.views().find_map(|(view, _focus)| {
1020 editor.tree.views().find_map(|(view, _focus)| {
1152− view.pos_at_screen_coords(&editor.documents[&view.doc], row, column)
1021+ view.pos_at_screen_coords(&editor.documents[&view.doc], row, column, true)
1153 .map(|pos| (pos, view.id))
1022 .map(|pos| (pos, view.id))
@@ -1191,8 +1060,10 @@ impl EditorView {
1191 None => return EventResult::Ignored(None),
1060 None => return EventResult::Ignored(None),
1194− let line = coords.row + view.offset.row;
1063+ if let Some(char_idx) =
1195− if line < doc.text().len_lines() {
1064+ view.pos_at_visual_coords(doc, coords.row as u16, coords.col as u16, true)
1066+ let line = doc.text().char_to_line(char_idx);
1196 commands::dap_toggle_breakpoint_impl(cxt, path, line);
1067 commands::dap_toggle_breakpoint_impl(cxt, path, line);
1197 return EventResult::Consumed(None);
1068 return EventResult::Consumed(None);
@@ -1204,7 +1075,7 @@ impl EditorView {
1204 MouseEventKind::Drag(MouseButton::Left) => {
1075 MouseEventKind::Drag(MouseButton::Left) => {
1205 let (view, doc) = current!(cxt.editor);
1076 let (view, doc) = current!(cxt.editor);
1207− let pos = match view.pos_at_screen_coords(doc, row, column) {
1078+ let pos = match view.pos_at_screen_coords(doc, row, column, true) {
1208 Some(pos) => pos,
1079 Some(pos) => pos,
1209 None => return EventResult::Ignored(None),
1080 None => return EventResult::Ignored(None),
@@ -1268,8 +1139,9 @@ impl EditorView {
1268 cxt.editor.focus(view_id);
1139 cxt.editor.focus(view_id);
1270 let (view, doc) = current!(cxt.editor);
1141 let (view, doc) = current!(cxt.editor);
1271− let line = coords.row + view.offset.row;
1142+ if let Some(pos) =
1272− if let Ok(pos) = doc.text().try_line_to_char(line) {
1143+ view.pos_at_visual_coords(doc, coords.row as u16, coords.col as u16, true)
1273 doc.set_selection(view_id, Selection::point(pos));
1145 doc.set_selection(view_id, Selection::point(pos));
1274 if modifiers == KeyModifiers::ALT {
1146 if modifiers == KeyModifiers::ALT {
1275 commands::MappableCommand::dap_edit_log.execute(cxt);
1147 commands::MappableCommand::dap_edit_log.execute(cxt);