From a55ee52ec95bc309bab9469b3e9ba4b330637cf9 Mon Sep 17 00:00:00 2001 From: Semin Park Date: Mon, 16 Jan 2023 13:02:43 +0800 Subject: [PATCH] Implement view dimming during jump mode --- helix-term/src/commands.rs | 13 +-- helix-term/src/commands/jump.rs | 2 +- helix-term/src/commands/jump/annotate.rs | 21 +++- helix-term/src/ui/editor.rs | 135 +++++++++++++++-------- helix-view/src/editor.rs | 2 + helix-view/src/view.rs | 5 + 6 files changed, 122 insertions(+), 56 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 7e937577a2557..5b257ea71770e 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -51,7 +51,7 @@ use insert::*; use movement::Movement; use self::jump::{ - apply_dimming, clear_dimming, find_all_char_occurrences, find_all_identifiers_in_view, + apply_dimming, cleanup, find_all_char_occurrences, find_all_identifiers_in_view, show_key_annotations_with_callback, sort_jump_targets, JumpSequencer, TrieNode, JUMP_KEYS, }; use crate::{ @@ -5335,10 +5335,10 @@ fn jump_with_char_input(cx: &mut Context, extend_selection: bool) { cx.on_next_key(move |cx, event| { let key = match event.char() { Some(key) => key, - _ => return clear_dimming(cx), + _ => return cleanup(cx), }; if !key.is_ascii() { - return clear_dimming(cx); + return cleanup(cx); } let jump_targets = find_all_char_occurrences(cx, key as u8); jump_with_targets(cx, jump_targets, extend_selection); @@ -5347,7 +5347,7 @@ fn jump_with_char_input(cx: &mut Context, extend_selection: bool) { fn jump_with_targets(ctx: &mut Context, mut jump_targets: Vec, extend_selection: bool) { if jump_targets.is_empty() { - return clear_dimming(ctx); + return cleanup(ctx); } // Jump targets are sorted based on their distance to the current cursor. jump_targets = sort_jump_targets(ctx, jump_targets); @@ -5356,7 +5356,7 @@ fn jump_with_targets(ctx: &mut Context, mut jump_targets: Vec, extend_sel } if jump_targets.len() == 1 { jump_to(ctx, jump_targets[0], extend_selection); - return clear_dimming(ctx); + return cleanup(ctx); } let root = TrieNode::build(JUMP_KEYS, jump_targets); show_key_annotations_with_callback(ctx, root.generate(), move |ctx, event| { @@ -5397,7 +5397,6 @@ fn handle_key( key: u8, extend_selection: bool, ) -> bool { - clear_dimming(ctx); match sequencer.choose(key) { Some(subnode) => { sequencer = *subnode; @@ -5438,7 +5437,7 @@ fn handle_key_event( None => true, }; if finished { - clear_dimming(ctx); + cleanup(ctx); } } diff --git a/helix-term/src/commands/jump.rs b/helix-term/src/commands/jump.rs index b6f96c6648f7a..4b91a0c899379 100644 --- a/helix-term/src/commands/jump.rs +++ b/helix-term/src/commands/jump.rs @@ -3,7 +3,7 @@ pub(crate) mod locations; pub(crate) mod score; pub(crate) mod sequencer; -pub use annotate::{apply_dimming, clear_dimming, show_key_annotations_with_callback, JUMP_KEYS}; +pub use annotate::{apply_dimming, cleanup, show_key_annotations_with_callback, JUMP_KEYS}; pub use locations::{find_all_char_occurrences, find_all_identifiers_in_view}; pub use score::sort_jump_targets; pub use sequencer::{JumpAnnotation, JumpSequence, JumpSequencer, TrieNode}; diff --git a/helix-term/src/commands/jump/annotate.rs b/helix-term/src/commands/jump/annotate.rs index f55e4622810b8..b53e3d4ead5ce 100644 --- a/helix-term/src/commands/jump/annotate.rs +++ b/helix-term/src/commands/jump/annotate.rs @@ -1,17 +1,30 @@ use super::JumpAnnotation; use crate::commands::Context; use helix_core::text_annotations::Overlay; -use helix_view::input::KeyEvent; +use helix_view::{input::KeyEvent, View}; use std::rc::Rc; pub const JUMP_KEYS: &[u8] = b"etovxqpdygfblzhckisuran"; -pub fn apply_dimming(_ctx: &mut Context) { - // TODO: Implement dimming +#[inline] +pub fn apply_dimming(ctx: &mut Context) { + let (view, doc) = current!(ctx.editor); + if doc.config.load().dim_during_jump { + view.dimmed = true; + } + view.in_visual_jump_mode = true; +} + +#[inline] +fn clear_dimming(view: &mut View) { + view.dimmed = false; + view.in_visual_jump_mode = false; } -pub fn clear_dimming(ctx: &mut Context) { +#[inline] +pub fn cleanup(ctx: &mut Context) { let mut view = view_mut!(ctx.editor); + clear_dimming(view); view.visual_jump_labels[0] = Rc::new([]); view.visual_jump_labels[1] = Rc::new([]); view.visual_jump_labels[2] = Rc::new([]); diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 915a5d090b76a..dcc292a5ac422 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -15,10 +15,10 @@ use helix_core::{ ensure_grapheme_boundary_next_byte, next_grapheme_boundary, prev_grapheme_boundary, }, movement::Direction, - syntax::{self, HighlightEvent}, + syntax::{self, Highlight, HighlightEvent}, text_annotations::TextAnnotations, unicode::width::UnicodeWidthStr, - visual_offset_from_block, Position, Range, Selection, Transaction, + visual_offset_from_block, Position, Range, RopeSlice, Selection, Transaction, }; use helix_view::{ apply_transaction, @@ -134,28 +134,44 @@ impl EditorView { Self::highlight_cursorcolumn(doc, view, surface, theme, inner, &text_annotations); } - let mut highlights = - Self::doc_syntax_highlights(doc, view.offset.anchor, inner.height, theme); - let overlay_highlights = Self::overlay_syntax_highlights( - doc, - view.offset.anchor, - inner.height, - &text_annotations, - ); - if !overlay_highlights.is_empty() { - highlights = Box::new(syntax::merge(highlights, overlay_highlights)); - } + let gather_base_hl = || { + if view.dimmed { + Self::dimmed_view(doc, view.offset.anchor, inner.height, theme) + } else { + Self::doc_syntax_highlights(doc, view.offset.anchor, inner.height, theme) + } + }; - 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; + let gather_overlay_hl = |highlights: Box>| { + let overlay_hl = Self::overlay_syntax_highlights( + doc, + view.offset.anchor, + inner.height, + &text_annotations, + ); + if overlay_hl.is_empty() { + highlights + } else { + Box::new(syntax::merge(highlights, overlay_hl)) } - highlights = Box::new(syntax::merge(highlights, diagnostic)); - } + }; + + let gather_diagnostic_hl = |mut highlights: Box>| { + 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; + } + highlights = Box::new(syntax::merge(highlights, diagnostic)); + } + highlights + }; - let highlights: Box> = if is_focused { + let gather_selection_hl = |highlights: Box>| { + if !is_focused { + return highlights; + } let highlights = syntax::merge( highlights, Self::doc_selection_highlights( @@ -172,8 +188,19 @@ impl EditorView { } else { Box::new(syntax::merge(highlights, focused_view_elements)) } + }; + + let highlights = if view.in_visual_jump_mode { + let mut hl = gather_base_hl(); + hl = gather_overlay_hl(hl); + hl = gather_selection_hl(hl); + hl } else { - Box::new(highlights) + let mut hl = gather_base_hl(); + hl = gather_overlay_hl(hl); + hl = gather_diagnostic_hl(hl); + hl = gather_selection_hl(hl); + hl }; Self::render_gutter( @@ -264,6 +291,18 @@ impl EditorView { .for_each(|area| surface.set_style(area, ruler_theme)) } + #[inline] + fn viewport_byte_range(text: RopeSlice, anchor: usize, height: u16) -> std::ops::Range { + let row = text.char_to_line(anchor.min(text.len_chars())); + // Saturating subs to make it inclusive zero indexing. + let last_line = text.len_lines().saturating_sub(1); + let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line); + let start = text.line_to_byte(row.min(last_line)); + let end = text.line_to_byte(last_visible_line + 1); + + start..end + } + pub fn overlay_syntax_highlights( doc: &Document, anchor: usize, @@ -272,15 +311,7 @@ impl EditorView { ) -> Vec<(usize, std::ops::Range)> { let text = doc.text().slice(..); - let range = { - let row = text.char_to_line(anchor); - let last_line = doc.text().len_lines().saturating_sub(1); - let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line); - let start = text.line_to_char(row.min(last_line)); - let end = text.line_to_char(last_visible_line + 1); - - start..end - }; + let range = Self::viewport_byte_range(text, anchor, height); text_annotations.collect_overlay_highlights(range) } @@ -294,19 +325,7 @@ impl EditorView { _theme: &Theme, ) -> Box + 'doc> { let text = doc.text().slice(..); - let row = text.char_to_line(anchor.min(text.len_chars())); - - let range = { - // Calculate viewport byte ranges: - // Saturating subs to make it inclusive zero indexing. - let last_line = text.len_lines().saturating_sub(1); - let last_visible_line = (row + height as usize).saturating_sub(1).min(last_line); - let start = text.line_to_byte(row.min(last_line)); - let end = text.line_to_byte(last_visible_line + 1); - - start..end - }; - + let range = Self::viewport_byte_range(text, anchor, height); match doc.syntax() { Some(syntax) => { let iter = syntax @@ -338,6 +357,34 @@ impl EditorView { } } + pub fn dimmed_view( + doc: &Document, + anchor: usize, + height: u16, + theme: &Theme, + ) -> Box> { + // Using comment style for dimming the view + let highlight = theme.find_scope_index("comment"); + if highlight.is_none() { + return Box::new(::std::iter::empty()); + } + let highlight = highlight.unwrap(); + + let text = doc.text().slice(..); + let range = Self::viewport_byte_range(text, anchor, height); + Box::new( + [ + HighlightEvent::HighlightStart(Highlight(highlight)), + HighlightEvent::Source { + start: text.byte_to_char(range.start), + end: text.byte_to_char(range.end), + }, + HighlightEvent::HighlightEnd, + ] + .into_iter(), + ) + } + /// Get highlight spans for document diagnostics pub fn doc_diagnostics_highlights( doc: &Document, diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 6dc3866d362a8..b7114796d94e7 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -181,6 +181,7 @@ pub struct Config { /// Whether to color modes with different colors. Defaults to `false`. pub color_modes: bool, pub soft_wrap: SoftWrap, + pub dim_during_jump: bool, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -674,6 +675,7 @@ impl Default for Config { indent_guides: IndentGuidesConfig::default(), color_modes: false, soft_wrap: SoftWrap::default(), + dim_during_jump: true, } } } diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index 149c34901ff7a..07121b8f5053d 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -108,6 +108,8 @@ pub struct View { pub offset: ViewPosition, pub area: Rect, pub doc: DocumentId, + // If true, greys out the view. + pub dimmed: bool, pub jumps: JumpList, // documents accessed from this view from the oldest one to last viewed one pub docs_access_history: Vec, @@ -133,6 +135,7 @@ pub struct View { // array contains the remaining characters. The purpose of this is such that the leading // character can be painted differently from the remaining characters. pub visual_jump_labels: [Rc<[Overlay]>; 3], + pub in_visual_jump_mode: bool, } impl fmt::Debug for View { @@ -155,6 +158,7 @@ impl View { horizontal_offset: 0, vertical_offset: 0, }, + dimmed: false, area: Rect::default(), // will get calculated upon inserting into tree jumps: JumpList::new((doc, Selection::point(0))), // TODO: use actual sel docs_access_history: Vec::new(), @@ -163,6 +167,7 @@ impl View { gutters: gutter_types, doc_revisions: HashMap::new(), visual_jump_labels: [Rc::new([]), Rc::new([]), Rc::new([])], + in_visual_jump_mode: false, } }