Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/src/ui/editor.rs')
| -rw-r--r-- | helix-term/src/ui/editor.rs | 548 |
1 files changed, 246 insertions, 302 deletions
diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index b25af107..a071bfaa 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -1,14 +1,11 @@ use crate::{ - commands::{self, OnKeyCallback, OnKeyCallbackKind}, + commands::{self, OnKeyCallback}, compositor::{Component, Context, Event, EventResult}, events::{OnModeSwitch, PostCommand}, - handlers::completion::CompletionItem, key, keymap::{KeymapResult, Keymaps}, ui::{ - document::{render_document, LinePos, TextRenderer}, - statusline, - text_decorations::{self, Decoration, DecorationManager, InlineDiagnostics}, + document::{render_document, LinePos, TextRenderer, TranslatedPosition}, Completion, ProgressSpinners, }, }; @@ -17,27 +14,29 @@ use helix_core::{ diagnostic::NumberOrString, graphemes::{next_grapheme_boundary, prev_grapheme_boundary}, movement::Direction, - syntax::{self, OverlayHighlights}, + syntax::{self, HighlightEvent}, text_annotations::TextAnnotations, unicode::width::UnicodeWidthStr, visual_offset_from_block, Change, Position, Range, Selection, Transaction, }; use helix_view::{ - annotations::diagnostics::DiagnosticFilter, - document::{Mode, SCRATCH_BUFFER_NAME}, + document::{Mode, SavePoint, SCRATCH_BUFFER_NAME}, editor::{CompleteAction, CursorShapeConfig}, graphics::{Color, CursorKind, Modifier, Rect, Style}, input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind}, keyboard::{KeyCode, KeyModifiers}, Document, Editor, Theme, View, }; -use std::{mem::take, num::NonZeroUsize, ops, path::PathBuf, rc::Rc}; +use std::{mem::take, num::NonZeroUsize, path::PathBuf, rc::Rc, sync::Arc}; use tui::{buffer::Buffer as Surface, text::Span}; +use super::document::LineDecoration; +use super::{completion::CompletionItem, statusline}; + pub struct EditorView { pub keymaps: Keymaps, - on_next_key: Option<(OnKeyCallback, OnKeyCallbackKind)>, + on_next_key: Option<OnKeyCallback>, pseudo_pending: Vec<KeyEvent>, pub(crate) last_insert: (commands::MappableCommand, Vec<InsertEvent>), pub(crate) completion: Option<Completion>, @@ -57,6 +56,12 @@ pub enum InsertEvent { RequestCompletion, } +impl Default for EditorView { + fn default() -> Self { + Self::new(Keymaps::default()) + } +} + impl EditorView { pub fn new(keymaps: Keymaps) -> Self { Self { @@ -87,15 +92,13 @@ impl EditorView { let area = view.area; let theme = &editor.theme; let config = editor.config(); - let loader = editor.syn_loader.load(); - - let view_offset = doc.view_offset(view.id); let text_annotations = view.text_annotations(doc, Some(theme)); - let mut decorations = DecorationManager::default(); + let mut line_decorations: Vec<Box<dyn LineDecoration>> = Vec::new(); + let mut translated_positions: Vec<TranslatedPosition> = Vec::new(); if is_focused && config.cursorline { - decorations.add_decoration(Self::cursorline(doc, view, theme)); + line_decorations.push(Self::cursorline_decorator(doc, view, theme)) } if is_focused && config.cursorcolumn { @@ -110,51 +113,57 @@ impl EditorView { if pos.doc_line != dap_line { return; } - renderer.set_style(Rect::new(inner.x, pos.visual_line, inner.width, 1), style); + renderer.surface.set_style( + Rect::new(inner.x, inner.y + pos.visual_line, inner.width, 1), + style, + ); }; - decorations.add_decoration(line_decoration); + line_decorations.push(Box::new(line_decoration)); } - let syntax_highlighter = - Self::doc_syntax_highlighter(doc, view_offset.anchor, inner.height, &loader); - let mut overlays = Vec::new(); + let syntax_highlights = + Self::doc_syntax_highlights(doc, view.offset.anchor, inner.height, theme); - overlays.push(Self::overlay_syntax_highlights( + let mut overlay_highlights = + Self::empty_highlight_iter(doc, view.offset.anchor, inner.height); + let overlay_syntax_highlights = Self::overlay_syntax_highlights( doc, - view_offset.anchor, + view.offset.anchor, inner.height, &text_annotations, - )); + ); + if !overlay_syntax_highlights.is_empty() { + overlay_highlights = + Box::new(syntax::merge(overlay_highlights, overlay_syntax_highlights)); + } - if doc - .language_config() - .and_then(|config| config.rainbow_brackets) - .unwrap_or(config.rainbow_brackets) - { - if let Some(overlay) = - Self::doc_rainbow_highlights(doc, view_offset.anchor, inner.height, theme, &loader) - { - overlays.push(overlay); + for diagnostic in Self::doc_diagnostics_highlights(doc, theme) { + // Most of the `diagnostic` Vecs are empty most of the time. Skipping + // a merge for any empty Vec saves a significant amount of work. + if diagnostic.is_empty() { + continue; } + overlay_highlights = Box::new(syntax::merge(overlay_highlights, diagnostic)); } - Self::doc_diagnostics_highlights_into(doc, theme, &mut overlays); - if is_focused { - if let Some(tabstops) = Self::tabstop_highlights(doc, theme) { - overlays.push(tabstops); - } - overlays.push(Self::doc_selection_highlights( - editor.mode(), - doc, - view, - theme, - &config.cursor_shape, - self.terminal_focused, - )); - if let Some(overlay) = Self::highlight_focused_view_elements(view, doc, theme) { - overlays.push(overlay); + let highlights = syntax::merge( + overlay_highlights, + Self::doc_selection_highlights( + editor.mode(), + doc, + view, + theme, + &config.cursor_shape, + self.terminal_focused, + ), + ); + let focused_view_elements = Self::highlight_focused_view_elements(view, doc, theme); + if focused_view_elements.is_empty() { + overlay_highlights = Box::new(highlights) + } else { + overlay_highlights = Box::new(syntax::merge(highlights, focused_view_elements)) } } @@ -167,46 +176,35 @@ impl EditorView { view.area, theme, is_focused & self.terminal_focused, - &mut decorations, + &mut line_decorations, ); } - Self::render_rulers(editor, doc, view, inner, surface, theme); - - let primary_cursor = doc - .selection(view.id) - .primary() - .cursor(doc.text().slice(..)); if is_focused { - decorations.add_decoration(text_decorations::Cursor { - cache: &editor.cursor_cache, - primary_cursor, - }); + let cursor = doc + .selection(view.id) + .primary() + .cursor(doc.text().slice(..)); + // set the cursor_cache to out of view in case the position is not found + editor.cursor_cache.set(Some(None)); + let update_cursor_cache = + |_: &mut TextRenderer, pos| editor.cursor_cache.set(Some(Some(pos))); + translated_positions.push((cursor, Box::new(update_cursor_cache))); } - let width = view.inner_width(doc); - let config = doc.config.load(); - let enable_cursor_line = view - .diagnostics_handler - .show_cursorline_diagnostics(doc, view.id); - let inline_diagnostic_config = config.inline_diagnostics.prepare(width, enable_cursor_line); - decorations.add_decoration(InlineDiagnostics::new( - doc, - theme, - primary_cursor, - inline_diagnostic_config, - config.end_of_line_diagnostics, - )); + render_document( surface, inner, doc, - view_offset, + view.offset, &text_annotations, - syntax_highlighter, - overlays, + syntax_highlights, + overlay_highlights, theme, - decorations, + &mut line_decorations, + &mut translated_positions, ); + Self::render_rulers(editor, doc, view, inner, surface, theme); // if we're not at the edge of the screen, draw a right border if viewport.right() != view.area.right() { @@ -220,11 +218,7 @@ impl EditorView { } } - if config.inline_diagnostics.disabled() - && config.end_of_line_diagnostics == DiagnosticFilter::Disable - { - Self::render_diagnostics(doc, view, inner, surface, theme); - } + Self::render_diagnostics(doc, view, inner, surface, theme); let statusline_area = view .area @@ -255,13 +249,11 @@ impl EditorView { .and_then(|config| config.rulers.as_ref()) .unwrap_or(editor_rulers); - let view_offset = doc.view_offset(view.id); - rulers .iter() // View might be horizontally scrolled, convert from absolute distance // from the 1st column to relative distance from left of viewport - .filter_map(|ruler| ruler.checked_sub(1 + view_offset.horizontal_offset as u16)) + .filter_map(|ruler| ruler.checked_sub(1 + view.offset.horizontal_offset as u16)) .filter(|ruler| ruler < &viewport.width) .map(|ruler| viewport.clip_left(ruler).with_width(1)) .for_each(|area| surface.set_style(area, ruler_theme)) @@ -282,23 +274,57 @@ impl EditorView { start..end } - /// Get the syntax highlighter for a document in a view represented by the first line + pub fn empty_highlight_iter( + doc: &Document, + anchor: usize, + height: u16, + ) -> Box<dyn Iterator<Item = HighlightEvent>> { + let text = doc.text().slice(..); + let row = text.char_to_line(anchor.min(text.len_chars())); + + // Calculate viewport byte ranges: + // Saturating subs to make it inclusive zero indexing. + let range = Self::viewport_byte_range(text, row, height); + Box::new( + [HighlightEvent::Source { + start: text.byte_to_char(range.start), + end: text.byte_to_char(range.end), + }] + .into_iter(), + ) + } + + /// Get syntax highlights for a document in a view represented by the first line /// and column (`offset`) and the last line. This is done instead of using a view /// directly to enable rendering syntax highlighted docs anywhere (eg. picker preview) - pub fn doc_syntax_highlighter<'editor>( - doc: &'editor Document, + pub fn doc_syntax_highlights<'doc>( + doc: &'doc Document, anchor: usize, height: u16, - loader: &'editor syntax::Loader, - ) -> Option<syntax::Highlighter<'editor>> { - let syntax = doc.syntax()?; + _theme: &Theme, + ) -> Box<dyn Iterator<Item = HighlightEvent> + 'doc> { let text = doc.text().slice(..); let row = text.char_to_line(anchor.min(text.len_chars())); + let range = Self::viewport_byte_range(text, row, height); - let range = range.start as u32..range.end as u32; - let highlighter = syntax.highlighter(text, loader, range); - Some(highlighter) + match doc.syntax() { + Some(syntax) => { + let iter = syntax + // TODO: range doesn't actually restrict source, just highlight range + .highlight_iter(text.slice(..), Some(range), None) + .map(|event| event.unwrap()); + + Box::new(iter) + } + None => Box::new( + [HighlightEvent::Source { + start: range.start, + end: range.end, + }] + .into_iter(), + ), + } } pub fn overlay_syntax_highlights( @@ -306,7 +332,7 @@ impl EditorView { anchor: usize, height: u16, text_annotations: &TextAnnotations, - ) -> OverlayHighlights { + ) -> Vec<(usize, std::ops::Range<usize>)> { let text = doc.text().slice(..); let row = text.char_to_line(anchor.min(text.len_chars())); @@ -316,51 +342,36 @@ impl EditorView { text_annotations.collect_overlay_highlights(range) } - pub fn doc_rainbow_highlights( - doc: &Document, - anchor: usize, - height: u16, - theme: &Theme, - loader: &syntax::Loader, - ) -> Option<OverlayHighlights> { - let syntax = doc.syntax()?; - let text = doc.text().slice(..); - let row = text.char_to_line(anchor.min(text.len_chars())); - let visible_range = Self::viewport_byte_range(text, row, height); - let start = syntax::child_for_byte_range( - &syntax.tree().root_node(), - visible_range.start as u32..visible_range.end as u32, - ) - .map_or(visible_range.start as u32, |node| node.start_byte()); - let range = start..visible_range.end as u32; - - Some(syntax.rainbow_highlights(text, theme.rainbow_length(), loader, range)) - } - /// Get highlight spans for document diagnostics - pub fn doc_diagnostics_highlights_into( + pub fn doc_diagnostics_highlights( doc: &Document, theme: &Theme, - overlay_highlights: &mut Vec<OverlayHighlights>, - ) { + ) -> [Vec<(usize, std::ops::Range<usize>)>; 7] { use helix_core::diagnostic::{DiagnosticTag, Range, Severity}; let get_scope_of = |scope| { theme - .find_highlight_exact(scope) - // get one of the themes below as fallback values - .or_else(|| theme.find_highlight_exact("diagnostic")) - .or_else(|| theme.find_highlight_exact("ui.cursor")) - .or_else(|| theme.find_highlight_exact("ui.selection")) - .expect( - "at least one of the following scopes must be defined in the theme: `diagnostic`, `ui.cursor`, or `ui.selection`", - ) + .find_scope_index_exact(scope) + // get one of the themes below as fallback values + .or_else(|| theme.find_scope_index_exact("diagnostic")) + .or_else(|| theme.find_scope_index_exact("ui.cursor")) + .or_else(|| theme.find_scope_index_exact("ui.selection")) + .expect( + "at least one of the following scopes must be defined in the theme: `diagnostic`, `ui.cursor`, or `ui.selection`", + ) }; + // basically just queries the theme color defined in the config + let hint = get_scope_of("diagnostic.hint"); + let info = get_scope_of("diagnostic.info"); + let warning = get_scope_of("diagnostic.warning"); + let error = get_scope_of("diagnostic.error"); + let r#default = get_scope_of("diagnostic"); // this is a bit redundant but should be fine + // Diagnostic tags - let unnecessary = theme.find_highlight_exact("diagnostic.unnecessary"); - let deprecated = theme.find_highlight_exact("diagnostic.deprecated"); + let unnecessary = theme.find_scope_index_exact("diagnostic.unnecessary"); + let deprecated = theme.find_scope_index_exact("diagnostic.deprecated"); - let mut default_vec = Vec::new(); + let mut default_vec: Vec<(usize, std::ops::Range<usize>)> = Vec::new(); let mut info_vec = Vec::new(); let mut hint_vec = Vec::new(); let mut warning_vec = Vec::new(); @@ -368,30 +379,31 @@ impl EditorView { let mut unnecessary_vec = Vec::new(); let mut deprecated_vec = Vec::new(); - let push_diagnostic = |vec: &mut Vec<ops::Range<usize>>, range: Range| { - // If any diagnostic overlaps ranges with the prior diagnostic, - // merge the two together. Otherwise push a new span. - match vec.last_mut() { - Some(existing_range) if range.start <= existing_range.end => { - // This branch merges overlapping diagnostics, assuming that the current - // diagnostic starts on range.start or later. If this assertion fails, - // we will discard some part of `diagnostic`. This implies that - // `doc.diagnostics()` is not sorted by `diagnostic.range`. - debug_assert!(existing_range.start <= range.start); - existing_range.end = range.end.max(existing_range.end) + let push_diagnostic = + |vec: &mut Vec<(usize, std::ops::Range<usize>)>, scope, range: Range| { + // If any diagnostic overlaps ranges with the prior diagnostic, + // merge the two together. Otherwise push a new span. + match vec.last_mut() { + Some((_, existing_range)) if range.start <= existing_range.end => { + // This branch merges overlapping diagnostics, assuming that the current + // diagnostic starts on range.start or later. If this assertion fails, + // we will discard some part of `diagnostic`. This implies that + // `doc.diagnostics()` is not sorted by `diagnostic.range`. + debug_assert!(existing_range.start <= range.start); + existing_range.end = range.end.max(existing_range.end) + } + _ => vec.push((scope, range.start..range.end)), } - _ => vec.push(range.start..range.end), - } - }; + }; for diagnostic in doc.diagnostics() { // Separate diagnostics into different Vecs by severity. - let vec = match diagnostic.severity { - Some(Severity::Info) => &mut info_vec, - Some(Severity::Hint) => &mut hint_vec, - Some(Severity::Warning) => &mut warning_vec, - Some(Severity::Error) => &mut error_vec, - _ => &mut default_vec, + let (vec, scope) = match diagnostic.severity { + Some(Severity::Info) => (&mut info_vec, info), + Some(Severity::Hint) => (&mut hint_vec, hint), + Some(Severity::Warning) => (&mut warning_vec, warning), + Some(Severity::Error) => (&mut error_vec, error), + _ => (&mut default_vec, r#default), }; // If the diagnostic has tags and a non-warning/error severity, skip rendering @@ -404,59 +416,34 @@ impl EditorView { Some(Severity::Warning | Severity::Error) ) { - push_diagnostic(vec, diagnostic.range); + push_diagnostic(vec, scope, diagnostic.range); } for tag in &diagnostic.tags { match tag { DiagnosticTag::Unnecessary => { - if unnecessary.is_some() { - push_diagnostic(&mut unnecessary_vec, diagnostic.range) + if let Some(scope) = unnecessary { + push_diagnostic(&mut unnecessary_vec, scope, diagnostic.range) } } DiagnosticTag::Deprecated => { - if deprecated.is_some() { - push_diagnostic(&mut deprecated_vec, diagnostic.range) + if let Some(scope) = deprecated { + push_diagnostic(&mut deprecated_vec, scope, diagnostic.range) } } } } } - overlay_highlights.push(OverlayHighlights::Homogeneous { - highlight: get_scope_of("diagnostic"), - ranges: default_vec, - }); - if let Some(highlight) = unnecessary { - overlay_highlights.push(OverlayHighlights::Homogeneous { - highlight, - ranges: unnecessary_vec, - }); - } - if let Some(highlight) = deprecated { - overlay_highlights.push(OverlayHighlights::Homogeneous { - highlight, - ranges: deprecated_vec, - }); - } - overlay_highlights.extend([ - OverlayHighlights::Homogeneous { - highlight: get_scope_of("diagnostic.info"), - ranges: info_vec, - }, - OverlayHighlights::Homogeneous { - highlight: get_scope_of("diagnostic.hint"), - ranges: hint_vec, - }, - OverlayHighlights::Homogeneous { - highlight: get_scope_of("diagnostic.warning"), - ranges: warning_vec, - }, - OverlayHighlights::Homogeneous { - highlight: get_scope_of("diagnostic.error"), - ranges: error_vec, - }, - ]); + [ + default_vec, + unnecessary_vec, + deprecated_vec, + info_vec, + hint_vec, + warning_vec, + error_vec, + ] } /// Get highlight spans for selections in a document view. @@ -467,7 +454,7 @@ impl EditorView { theme: &Theme, cursor_shape_config: &CursorShapeConfig, is_terminal_focused: bool, - ) -> OverlayHighlights { + ) -> Vec<(usize, std::ops::Range<usize>)> { let text = doc.text().slice(..); let selection = doc.selection(view.id); let primary_idx = selection.primary_index(); @@ -476,34 +463,34 @@ impl EditorView { let cursor_is_block = cursorkind == CursorKind::Block; let selection_scope = theme - .find_highlight_exact("ui.selection") + .find_scope_index_exact("ui.selection") .expect("could not find `ui.selection` scope in the theme!"); let primary_selection_scope = theme - .find_highlight_exact("ui.selection.primary") + .find_scope_index_exact("ui.selection.primary") .unwrap_or(selection_scope); let base_cursor_scope = theme - .find_highlight_exact("ui.cursor") + .find_scope_index_exact("ui.cursor") .unwrap_or(selection_scope); let base_primary_cursor_scope = theme - .find_highlight("ui.cursor.primary") + .find_scope_index("ui.cursor.primary") .unwrap_or(base_cursor_scope); let cursor_scope = match mode { - Mode::Insert => theme.find_highlight_exact("ui.cursor.insert"), - Mode::Select => theme.find_highlight_exact("ui.cursor.select"), - Mode::Normal => theme.find_highlight_exact("ui.cursor.normal"), + Mode::Insert => theme.find_scope_index_exact("ui.cursor.insert"), + Mode::Select => theme.find_scope_index_exact("ui.cursor.select"), + Mode::Normal => theme.find_scope_index_exact("ui.cursor.normal"), } .unwrap_or(base_cursor_scope); let primary_cursor_scope = match mode { - Mode::Insert => theme.find_highlight_exact("ui.cursor.primary.insert"), - Mode::Select => theme.find_highlight_exact("ui.cursor.primary.select"), - Mode::Normal => theme.find_highlight_exact("ui.cursor.primary.normal"), + Mode::Insert => theme.find_scope_index_exact("ui.cursor.primary.insert"), + Mode::Select => theme.find_scope_index_exact("ui.cursor.primary.select"), + Mode::Normal => theme.find_scope_index_exact("ui.cursor.primary.normal"), } .unwrap_or(base_primary_cursor_scope); - let mut spans = Vec::new(); + let mut spans: Vec<(usize, std::ops::Range<usize>)> = Vec::new(); for (i, range) in selection.iter().enumerate() { let selection_is_primary = i == primary_idx; let (cursor_scope, selection_scope) = if selection_is_primary { @@ -538,7 +525,7 @@ impl EditorView { }; spans.push((selection_scope, range.anchor..selection_end)); // add block cursors - // skip primary cursor if terminal is unfocused - terminal cursor is used in that case + // skip primary cursor if terminal is unfocused - crossterm cursor is used in that case if !selection_is_primary || (cursor_is_block && is_terminal_focused) { spans.push((cursor_scope, cursor_start..range.head)); } @@ -546,7 +533,7 @@ impl EditorView { // Reverse case. let cursor_end = next_grapheme_boundary(text, range.head); // add block cursors - // skip primary cursor if terminal is unfocused - terminal cursor is used in that case + // skip primary cursor if terminal is unfocused - crossterm cursor is used in that case if !selection_is_primary || (cursor_is_block && is_terminal_focused) { spans.push((cursor_scope, range.head..cursor_end)); } @@ -563,7 +550,7 @@ impl EditorView { } } - OverlayHighlights::Heterogenous { highlights: spans } + spans } /// Render brace match, etc (meant for the focused view only) @@ -571,24 +558,23 @@ impl EditorView { view: &View, doc: &Document, theme: &Theme, - ) -> Option<OverlayHighlights> { + ) -> Vec<(usize, std::ops::Range<usize>)> { // Highlight matching braces - let syntax = doc.syntax()?; - let highlight = theme.find_highlight_exact("ui.cursor.match")?; - let text = doc.text().slice(..); - let pos = doc.selection(view.id).primary().cursor(text); - let pos = helix_core::match_brackets::find_matching_bracket(syntax, text, pos)?; - Some(OverlayHighlights::single(highlight, pos..pos + 1)) - } + if let Some(syntax) = doc.syntax() { + let text = doc.text().slice(..); + use helix_core::match_brackets; + let pos = doc.selection(view.id).primary().cursor(text); - pub fn tabstop_highlights(doc: &Document, theme: &Theme) -> Option<OverlayHighlights> { - let snippet = doc.active_snippet.as_ref()?; - let highlight = theme.find_highlight_exact("tabstop")?; - let mut ranges = Vec::new(); - for tabstop in snippet.tabstops() { - ranges.extend(tabstop.ranges.iter().map(|range| range.start..range.end)); + if let Some(pos) = + match_brackets::find_matching_bracket(syntax, doc.text().slice(..), pos) + { + // ensure col is on screen + if let Some(highlight) = theme.find_scope_index_exact("ui.cursor.match") { + return vec![(highlight, pos..pos + 1)]; + } + } } - Some(OverlayHighlights::Homogeneous { highlight, ranges }) + Vec::new() } /// Render bufferline at the top @@ -651,7 +637,7 @@ impl EditorView { viewport: Rect, theme: &Theme, is_focused: bool, - decoration_manager: &mut DecorationManager<'d>, + line_decorations: &mut Vec<Box<(dyn LineDecoration + 'd)>>, ) { let text = doc.text().slice(..); let cursors: Rc<[_]> = doc @@ -677,7 +663,7 @@ impl EditorView { // TODO handle softwrap in gutters let selected = cursors.contains(&pos.doc_line); let x = viewport.x + offset; - let y = pos.visual_line; + let y = viewport.y + pos.visual_line; let gutter_style = match (selected, pos.first_visual_line) { (false, true) => gutter_style, @@ -689,9 +675,11 @@ impl EditorView { if let Some(style) = gutter(pos.doc_line, selected, pos.first_visual_line, &mut text) { - renderer.set_stringn(x, y, &text, width, gutter_style.patch(style)); + renderer + .surface + .set_stringn(x, y, &text, width, gutter_style.patch(style)); } else { - renderer.set_style( + renderer.surface.set_style( Rect { x, y, @@ -703,7 +691,7 @@ impl EditorView { } text.clear(); }; - decoration_manager.add_decoration(gutter_decoration); + line_decorations.push(Box::new(gutter_decoration)); offset += width as u16; } @@ -773,7 +761,11 @@ impl EditorView { } /// Apply the highlighting on the lines where a cursor is active - pub fn cursorline(doc: &Document, view: &View, theme: &Theme) -> impl Decoration { + pub fn cursorline_decorator( + doc: &Document, + view: &View, + theme: &Theme, + ) -> Box<dyn LineDecoration> { let text = doc.text().slice(..); // TODO only highlight the visual line that contains the cursor instead of the full visual line let primary_line = doc.selection(view.id).primary().cursor_line(text); @@ -794,14 +786,16 @@ impl EditorView { let secondary_style = theme.get("ui.cursorline.secondary"); let viewport = view.area; - move |renderer: &mut TextRenderer, pos: LinePos| { - let area = Rect::new(viewport.x, pos.visual_line, viewport.width, 1); + let line_decoration = move |renderer: &mut TextRenderer, pos: LinePos| { + let area = Rect::new(viewport.x, viewport.y + pos.visual_line, viewport.width, 1); if primary_line == pos.doc_line { - renderer.set_style(area, primary_style); + renderer.surface.set_style(area, primary_style); } else if secondary_lines.binary_search(&pos.doc_line).is_ok() { - renderer.set_style(area, secondary_style); + renderer.surface.set_style(area, secondary_style); } - } + }; + + Box::new(line_decoration) } /// Apply the highlighting on the columns where a cursor is active @@ -829,7 +823,6 @@ impl EditorView { let inner_area = view.inner_area(doc); let selection = doc.selection(view.id); - let view_offset = doc.view_offset(view.id); let primary = selection.primary(); let text_format = doc.text_format(viewport.width, None); for range in selection.iter() { @@ -840,11 +833,11 @@ impl EditorView { visual_offset_from_block(text, cursor, cursor, &text_format, text_annotations).0; // if the cursor is horizontally in the view - if col >= view_offset.horizontal_offset - && inner_area.width > (col - view_offset.horizontal_offset) as u16 + if col >= view.offset.horizontal_offset + && inner_area.width > (col - view.offset.horizontal_offset) as u16 { let area = Rect::new( - inner_area.x + (col - view_offset.horizontal_offset) as u16, + inner_area.x + (col - view.offset.horizontal_offset) as u16, view.area.y, 1, view.area.height, @@ -917,10 +910,8 @@ impl EditorView { if let Some(keyresult) = self.handle_keymap_event(Mode::Insert, cx, event) { match keyresult { KeymapResult::NotFound => { - if !self.on_next_key(OnKeyCallbackKind::Fallback, cx, event) { - if let Some(ch) = event.char() { - commands::insert::insert_char(cx, ch) - } + if let Some(ch) = event.char() { + commands::insert::insert_char(cx, ch) } } KeymapResult::Cancelled(pending) => { @@ -1016,10 +1007,7 @@ impl EditorView { // set the register cxt.register = cxt.editor.selected_register.take(); - let res = self.handle_keymap_event(mode, cxt, event); - if matches!(&res, Some(KeymapResult::NotFound)) { - self.on_next_key(OnKeyCallbackKind::Fallback, cxt, event); - } + self.handle_keymap_event(mode, cxt, event); if self.keymaps.pending().is_empty() { cxt.editor.count = None } else { @@ -1033,11 +1021,12 @@ impl EditorView { pub fn set_completion( &mut self, editor: &mut Editor, + savepoint: Arc<SavePoint>, items: Vec<CompletionItem>, trigger_offset: usize, size: Rect, ) -> Option<Rect> { - let mut completion = Completion::new(editor, items, trigger_offset); + let mut completion = Completion::new(editor, savepoint, items, trigger_offset); if completion.is_empty() { // skip if we got no completion results @@ -1053,40 +1042,24 @@ impl EditorView { Some(area) } - pub fn clear_completion(&mut self, editor: &mut Editor) -> Option<OnKeyCallback> { + pub fn clear_completion(&mut self, editor: &mut Editor) { self.completion = None; - let mut on_next_key: Option<OnKeyCallback> = None; - editor.handlers.completions.request_controller.restart(); - editor.handlers.completions.active_completions.clear(); if let Some(last_completion) = editor.last_completion.take() { match last_completion { CompleteAction::Triggered => (), CompleteAction::Applied { trigger_offset, changes, - placeholder, - } => { - self.last_insert.1.push(InsertEvent::CompletionApply { - trigger_offset, - changes, - }); - on_next_key = placeholder.then_some(Box::new(|cx, key| { - if let Some(c) = key.char() { - let (view, doc) = current!(cx.editor); - if let Some(snippet) = &doc.active_snippet { - doc.apply(&snippet.delete_placeholder(doc.text()), view.id); - } - commands::insert::insert_char(cx, c); - } - })) - } + } => self.last_insert.1.push(InsertEvent::CompletionApply { + trigger_offset, + changes, + }), CompleteAction::Selected { savepoint } => { let (view, doc) = current!(editor); doc.restore(view, &savepoint, false); } } } - on_next_key } pub fn handle_idle_timeout(&mut self, cx: &mut commands::Context) -> EventResult { @@ -1110,7 +1083,7 @@ impl EditorView { modifiers: KeyModifiers::empty(), }; // dismiss any pending keys - if let Some((on_next_key, _)) = self.on_next_key.take() { + if let Some(on_next_key) = self.on_next_key.take() { on_next_key(cxt, null_key_event); } self.handle_keymap_event(cxt.editor.mode, cxt, null_key_event); @@ -1159,8 +1132,6 @@ impl EditorView { let editor = &mut cxt.editor; if let Some((pos, view_id)) = pos_and_view(editor, row, column, true) { - editor.focus(view_id); - let prev_view_id = view!(editor).id; let doc = doc_mut!(editor, &view!(editor, view_id).doc); @@ -1184,6 +1155,7 @@ impl EditorView { self.clear_completion(editor); } + editor.focus(view_id); editor.ensure_cursor_in_view(view_id); return EventResult::Consumed(None); @@ -1334,24 +1306,6 @@ impl EditorView { _ => EventResult::Ignored(None), } } - fn on_next_key( - &mut self, - kind: OnKeyCallbackKind, - ctx: &mut commands::Context, - event: KeyEvent, - ) -> bool { - if let Some((on_next_key, kind_)) = self.on_next_key.take() { - if kind == kind_ { - on_next_key(ctx, event); - true - } else { - self.on_next_key = Some((on_next_key, kind_)); - false - } - } else { - false - } - } } impl Component for EditorView { @@ -1403,7 +1357,10 @@ impl Component for EditorView { let mode = cx.editor.mode(); - if !self.on_next_key(OnKeyCallbackKind::PseudoPending, &mut cx, key) { + if let Some(on_next_key) = self.on_next_key.take() { + // if there's a command waiting input, do that first + on_next_key(&mut cx, key); + } else { match mode { Mode::Insert => { // let completion swallow the event if necessary @@ -1434,15 +1391,7 @@ impl Component for EditorView { if let Some(callback) = res { if callback.is_some() { // assume close_fn - if let Some(cb) = self.clear_completion(cx.editor) { - if consumed { - cx.on_next_key_callback = - Some((cb, OnKeyCallbackKind::Fallback)) - } else { - self.on_next_key = - Some((cb, OnKeyCallbackKind::Fallback)); - } - } + self.clear_completion(cx.editor); } } } @@ -1461,8 +1410,8 @@ impl Component for EditorView { self.on_next_key = cx.on_next_key_callback.take(); match self.on_next_key { - Some((_, OnKeyCallbackKind::PseudoPending)) => self.pseudo_pending.push(key), - _ => self.pseudo_pending.clear(), + Some(_) => self.pseudo_pending.push(key), + None => self.pseudo_pending.clear(), } // appease borrowck @@ -1507,12 +1456,7 @@ impl Component for EditorView { } Event::FocusLost => { if context.editor.config().auto_save.focus_lost { - let options = commands::WriteAllOptions { - force: false, - write_scratch: false, - auto_format: false, - }; - if let Err(e) = commands::typed::write_all_impl(context, options) { + if let Err(e) = commands::typed::write_all_impl(context, false, false) { context.editor.set_error(format!("{}", e)); } } @@ -1631,7 +1575,7 @@ impl Component for EditorView { if self.terminal_focused { (pos, CursorKind::Hidden) } else { - // use terminal cursor when terminal loses focus + // use crossterm cursor when terminal loses focus (pos, CursorKind::Underline) } } |