diff --git a/book/src/configuration.md b/book/src/configuration.md index 1b94ae856b5d..40b6dce6e4bf 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -347,3 +347,23 @@ max-wrap = 25 # increase value to reduce forced mid-word wrapping max-indent-retain = 0 wrap-indicator = "" # set wrap-indicator to "" to hide it ``` + +### `[editor.jump-mode]` Section + +Options for jump mode. If you are already familiar with vim/nvim's [easymotion](https://github.com/easymotion/vim-easymotion), [hop](https://github.com/phaazon/hop.nvim), [leap](https://github.com/ggandor/leap.nvim) etc, you +can think of jump mode as the equivalent in helix. + +| Key | Description | Default | +| --- | --- | --- | +| `dim-during-jump` | Whether to dim the view when in jump mode. | `true` | +| `num-chars-before-label` | How many characters the user should type before labelling the targets. | `1` | +| `jump-keys` | Keys used in labels. Should be ascii characters. | `"jwetovxqpdygfblzhckisuranm"` | + +Example: + +```toml +[editor.jump-mode] +dim-during-jump = true +num-chars-before-label = 2 +jump-keys = "laskdjfhgpmoinqzubwxyvecrt" +``` diff --git a/book/src/keymap.md b/book/src/keymap.md index 153f3b6483f1..abfef4254229 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -223,6 +223,8 @@ Jumps to various locations. | `.` | Go to last modification in current file | `goto_last_modification` | | `j` | Move down textual (instead of visual) line | `move_line_down` | | `k` | Move up textual (instead of visual) line | `move_line_up` | +| `w` | Word-wise jump mode | `jump_to_identifier_label` | +| `/` | Character or string search jump mode | `jump_to_str_label` | #### Match mode diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 1bd736523edc..6118702e488f 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1,4 +1,5 @@ pub(crate) mod dap; +pub(crate) mod jump; pub(crate) mod lsp; pub(crate) mod typed; @@ -48,6 +49,10 @@ use fuzzy_matcher::FuzzyMatcher; use insert::*; use movement::Movement; +use self::jump::{ + cleanup, find_all_identifiers_in_view, find_all_str_occurrences_in_view, jump_keys, + show_key_annotations_with_callback, sort_jump_targets, JumpSequencer, TrieNode, +}; use crate::{ args, compositor::{self, Component, Compositor}, @@ -62,7 +67,7 @@ use crate::{ use crate::job::{self, Jobs}; use futures_util::{stream::FuturesUnordered, StreamExt, TryStreamExt}; -use std::{collections::HashMap, fmt, future::Future}; +use std::{cmp::Ordering, collections::HashMap, fmt, future::Future}; use std::{collections::HashSet, num::NonZeroUsize}; use std::{ @@ -485,6 +490,10 @@ impl MappableCommand { decrement, "Decrement item under cursor", record_macro, "Record macro", replay_macro, "Replay macro", + jump_to_identifier_label, "Jump mode: word-wise", + jump_to_str_label, "Jump mode: character search", + jump_to_identifier_label_and_extend_selection, "Jump mode: extend selection with word-wise jump", + jump_to_str_label_and_extend_selection, "Jump mode: extend selection with character search", command_palette, "Open command palette", ); } @@ -5588,3 +5597,178 @@ fn replay_macro(cx: &mut Context) { cx.editor.macro_replaying.pop(); })); } + +fn jump_to_identifier_label(cx: &mut Context) { + let jump_targets = find_all_identifiers_in_view(cx); + jump_with_targets(cx, jump_targets, false); +} + +fn jump_to_identifier_label_and_extend_selection(cx: &mut Context) { + let jump_targets = find_all_identifiers_in_view(cx); + jump_with_targets(cx, jump_targets, true); +} + +fn jump_to_str_label(cx: &mut Context) { + find_and_jump_to_str(cx, false); +} + +fn jump_to_str_label_and_extend_selection(cx: &mut Context) { + find_and_jump_to_str(cx, true); +} + +fn find_and_jump_to_str(cx: &mut Context, extend_selection: bool) { + jump::setup(cx); + let nchars = doc!(cx.editor) + .config + .load() + .jump_mode + .num_chars_before_label as usize; + cx.editor.set_status(format!("Press {} key(s)", nchars)); + cx.on_next_key(move |cx, event| { + jump_with_str_input( + cx, + event, + extend_selection, + String::with_capacity(nchars), + nchars, + ) + }); +} + +fn jump_with_str_input( + cx: &mut Context, + event: KeyEvent, + extend_selection: bool, + mut s: String, + nchars: usize, +) { + let Some(c) = event.char().filter(|c| c.is_ascii()) else { + return cleanup(cx); + }; + s.push(c); + if s.len() == nchars { + let jump_targets = find_all_str_occurrences_in_view(cx, s); + jump_with_targets(cx, jump_targets, extend_selection); + return; + } + cx.editor + .set_status(format!("Press {} more key(s)", nchars - s.len())); + cx.on_next_key(move |cx, event| jump_with_str_input(cx, event, extend_selection, s, nchars)); +} + +fn jump_with_targets(cx: &mut Context, mut jump_targets: Vec, extend_selection: bool) { + if jump_targets.is_empty() { + return cleanup(cx); + } + // Jump targets are sorted based on their distance to the current cursor. + jump_targets = sort_jump_targets(cx, jump_targets); + if extend_selection { + jump_targets = extend_jump_selection(cx, jump_targets); + } + if jump_targets.len() == 1 { + jump_to(cx, jump_targets[0], extend_selection); + return cleanup(cx); + } + let root = TrieNode::build(&jump_keys(cx), jump_targets); + show_key_annotations_with_callback(cx, root.generate(), move |cx, event| { + handle_key_event(root, cx, event, extend_selection) + }); +} + +fn fix_extend_mode_off_by_one(selection: &Range, target: &mut Range) { + // Non-zero width ranges are always inclusive on the left and exclusive on + // the right. But when we're extending the selection, this often creates + // off-by-one behavior, where the cursor doesn't quite reach the target. + // Thus we need to increment the upper bound when the target head is after + // the current anchor. + if selection.anchor < target.head { + match target.anchor.cmp(&target.head) { + Ordering::Less => target.head += 1, + Ordering::Greater => target.anchor += 1, + Ordering::Equal => {} + }; + } +} + +fn jump_to(cx: &mut Context, mut range: Range, extend_selection: bool) { + let (view, doc) = current!(cx.editor); + push_jump(view, doc); + let selection = doc.selection(view.id).primary(); + if extend_selection { + fix_extend_mode_off_by_one(&selection, &mut range); + } + doc.set_selection(view.id, Selection::single(range.anchor, range.head)); +} + +/// Handle user key press. +/// Returns whether we are finished and should move out of jump mode. +fn handle_key( + mut sequencer: impl JumpSequencer + 'static, + cx: &mut Context, + key: u8, + extend_selection: bool, +) -> bool { + cleanup(cx); + match sequencer.choose(key) { + Some(subnode) => { + sequencer = *subnode; + } + // char `c` is not a valid character. Finish jump mode + None => return true, + } + match sequencer.try_get_range() { + Some(range) => { + jump_to(cx, range, extend_selection); + return true; + } + None => { + show_key_annotations_with_callback(cx, sequencer.generate(), move |cx, event| { + handle_key_event(sequencer, cx, event, extend_selection) + }); + } + } + false +} + +fn handle_key_event( + node: impl JumpSequencer + 'static, + cx: &mut Context, + event: KeyEvent, + extend_selection: bool, +) { + let finished = match event.char() { + Some(key) => { + if event.modifiers.is_empty() && key.is_ascii() { + handle_key(node, cx, key as u8, extend_selection) + } else { + // Only accept ascii characters with no modifiers. Otherwise, finish jump mode. + true + } + } + // We didn't get a valid character. Finish jump mode. + None => true, + }; + if finished { + cleanup(cx); + } +} + +fn extend_jump_selection(cx: &Context, jump_targets: Vec) -> Vec { + let (view, doc) = current_ref!(cx.editor); + // We only care about the primary selection + let mut cur = doc.selection(view.id).primary(); + jump_targets + .into_iter() + .map(|mut range| { + // We want to grow the selection, so if the new head crosses the + // old anchor, swap the old head and old anchor + let cross_fwd = cur.head < cur.anchor && cur.anchor < range.head; + let cross_bwd = range.head < cur.anchor && cur.anchor < cur.head; + if cross_fwd || cross_bwd { + std::mem::swap(&mut cur.head, &mut cur.anchor); + } + range.anchor = cur.anchor; + range + }) + .collect() +} diff --git a/helix-term/src/commands/jump.rs b/helix-term/src/commands/jump.rs new file mode 100644 index 000000000000..b7884f62e3d6 --- /dev/null +++ b/helix-term/src/commands/jump.rs @@ -0,0 +1,9 @@ +pub(crate) mod annotate; +pub(crate) mod locations; +pub(crate) mod score; +pub(crate) mod sequencer; + +pub use annotate::{cleanup, jump_keys, setup, show_key_annotations_with_callback}; +pub use locations::{find_all_identifiers_in_view, find_all_str_occurrences_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 new file mode 100644 index 000000000000..03378ecdfa96 --- /dev/null +++ b/helix-term/src/commands/jump/annotate.rs @@ -0,0 +1,86 @@ +use super::JumpAnnotation; +use crate::commands::Context; +use helix_core::chars::char_is_line_ending; +use helix_core::text_annotations::Overlay; +use helix_view::{input::KeyEvent, View}; +use std::rc::Rc; + +pub fn jump_keys(ctx: &mut Context) -> Vec { + doc!(ctx.editor) + .config + .load() + .jump_mode + .jump_keys + .clone() + .into_bytes() +} + +#[inline] +pub fn setup(ctx: &mut Context) { + let (view, doc) = current!(ctx.editor); + if doc.config.load().jump_mode.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; +} + +#[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([]); +} + +/// `annotations` should already be sorted by the `loc` attribute (= char_idx) +pub fn show_key_annotations_with_callback( + ctx: &mut Context, + annotations: Vec, + on_key_press_callback: F, +) where + F: FnOnce(&mut Context, KeyEvent) + 'static, +{ + setup(ctx); + let (view, doc) = current!(ctx.editor); + let text = doc.text().slice(..); + let mut overlays_single: Vec = Vec::new(); + let mut overlays_multi_first: Vec = Vec::new(); + let mut overlays_multi_rest: Vec = Vec::new(); + for jump in annotations.into_iter() { + if jump.keys.len() == 1 { + overlays_single.push(Overlay { + char_idx: jump.loc, + grapheme: jump.keys.into(), + }); + continue; + } + overlays_multi_first.push(Overlay { + char_idx: jump.loc, + grapheme: jump.keys.chars().next().unwrap().to_string().into(), + }); + for (i, c) in (1..jump.keys.len()).zip(jump.keys.chars().skip(1)) { + let char_idx = jump.loc + i; + let char = text.chars_at(char_idx).next().unwrap(); + // We shouldn't overlay anything on top of a line break. If we do, the next line will + // crawl up and concatenate with the current line. + if char_is_line_ending(char) { + break; + } + overlays_multi_rest.push(Overlay { + char_idx, + grapheme: c.to_string().into(), + }); + } + } + view.visual_jump_labels[0] = overlays_single.into(); + view.visual_jump_labels[1] = overlays_multi_first.into(); + view.visual_jump_labels[2] = overlays_multi_rest.into(); + ctx.on_next_key(on_key_press_callback); +} diff --git a/helix-term/src/commands/jump/locations.rs b/helix-term/src/commands/jump/locations.rs new file mode 100644 index 000000000000..7d3564ed5403 --- /dev/null +++ b/helix-term/src/commands/jump/locations.rs @@ -0,0 +1,68 @@ +use crate::commands::Context; +use helix_core::{chars::char_is_word, graphemes, movement, Position, Range}; + +fn view_boundary(cx: &Context) -> (usize, usize) { + let (view, doc) = current_ref!(cx.editor); + let text = doc.text().slice(..); + + let start_idx = text.line_to_char(text.char_to_line(view.offset.anchor)); + // end_idx can overshoot if there are virtual text such as soft wrap. But generating more + // targets should be okay as long as they are not visible. + let end_idx = text.line_to_char(view.estimate_last_doc_line(doc) + 1); + (start_idx, end_idx) +} + +pub fn cursor_at(cx: &Context) -> Position { + let (view, doc) = current_ref!(cx.editor); + let text = doc.text().slice(..); + let cur = doc.selection(view.id).primary().head; + let row = text.char_to_line(cur); + let col = cur - text.line_to_char(row); + Position { row, col } +} + +pub fn find_all_identifiers_in_view(cx: &mut Context) -> Vec { + let (start_idx, end_idx) = view_boundary(cx); + let text = doc!(cx.editor).text().slice(..); + + let mut jump_targets: Vec = Vec::new(); + let mut next = Range::new(start_idx, start_idx); + + // If the first line in view has a single character with no trailing whitespace, + // `move_next_word_start` will skip it. Thus we need to handle this edge case here. + if graphemes::is_grapheme_boundary(text, start_idx) { + // If there is an alphanumeric character on start_idx, consider it as a target. + let c = text.chars_at(start_idx).next().unwrap_or(' '); + if char_is_word(c) { + jump_targets.push(Range::point(start_idx)); + } + } + // Find other identifiers within this view. + loop { + next = movement::move_next_word_start(text, next, 1); + // next.anchor points to the start of the identifier, and next.head + // points to the end of the identifier. We want the cursor to be at + // the start of the identifier, so swap the head and anchor. + let (head, anchor) = (next.anchor, next.head); + if anchor >= end_idx { + break; + } + let c = text.chars_at(head).next().unwrap(); + if !char_is_word(c) { + continue; + } + jump_targets.push(Range::new(anchor, head)); + } + jump_targets +} + +pub fn find_all_str_occurrences_in_view(cx: &Context, s: String) -> Vec { + let (start_idx, end_idx) = view_boundary(cx); + let doc = doc!(cx.editor); + let text = doc.text().slice(..); + + (start_idx..end_idx) + .filter(|&idx| text.chars_at(idx).zip(s.chars()).all(|(a, b)| a == b)) + .map(Range::point) + .collect() +} diff --git a/helix-term/src/commands/jump/score.rs b/helix-term/src/commands/jump/score.rs new file mode 100644 index 000000000000..60b0c3ea9710 --- /dev/null +++ b/helix-term/src/commands/jump/score.rs @@ -0,0 +1,39 @@ +use super::locations::cursor_at; +use crate::commands::Context; +use helix_core::{Position, Range}; + +fn manhattan_distance(p1: &Position, p2: &Position) -> usize { + // Make it easier to travel along the x-axis + const Y_WEIGHT: usize = 10; + Y_WEIGHT + .saturating_mul(p1.row.abs_diff(p2.row)) + .saturating_add(p1.col.abs_diff(p2.col)) +} + +struct ScoredTarget { + range: Range, + distance: usize, +} + +pub fn sort_jump_targets(cx: &mut Context, jump_targets: Vec) -> Vec { + // Each jump target will be scored based on its distance to the cursor position. + // The position of each jump target in the view is only approximated due to performance issues. + let cursor = cursor_at(cx); + let text = doc!(cx.editor).text().slice(..); + let mut jump_targets: Vec<_> = jump_targets + .iter() + .zip((0..jump_targets.len()).map(|i| { + let cur = jump_targets[i].head; + let row = text.char_to_line(cur); + let col = cur - text.line_to_char(row); + Position { row, col } + })) + .map(|(range, pos)| ScoredTarget { + range: *range, + distance: manhattan_distance(&cursor, &pos), + }) + .collect(); + // Sort by the distance (shortest first) + jump_targets.sort_by(|a, b| a.distance.cmp(&b.distance)); + jump_targets.iter().map(|a| a.range).collect() +} diff --git a/helix-term/src/commands/jump/sequencer.rs b/helix-term/src/commands/jump/sequencer.rs new file mode 100644 index 000000000000..fa71441d0ebe --- /dev/null +++ b/helix-term/src/commands/jump/sequencer.rs @@ -0,0 +1,306 @@ +use std::collections::VecDeque; + +use helix_core::Range; + +/// Sequence of characters that need to be pressed to reach a destination. +#[derive(Debug, Eq, PartialEq)] +pub struct JumpSequence(Vec); + +impl JumpSequence { + /// Prefix the current sequence with the given character + pub fn prefix(&mut self, c: u8) { + assert!(c.is_ascii()); + // We are appending and not inserting to the 0th element, because the + // consumation order is LIFO. + self.0.push(c); + } +} + +impl From for JumpSequence { + fn from(key: u8) -> Self { + Self(vec![key]) + } +} + +impl From for String { + fn from(mut seq: JumpSequence) -> Self { + seq.0.reverse(); + String::from_utf8(seq.0).expect("Jump keys should be ascii letters") + } +} + +#[derive(Debug)] +pub struct JumpAnnotation { + // Starting location of a jump annotation. + pub loc: usize, + pub keys: String, +} + +/// Generator that generates a list of jump annotations +pub trait JumpSequencer { + /// Generates a list of JumpSequence. The order of the JumpSequence should + /// be highest priority first, lowest priority last + fn generate(&self) -> Vec; + // Advance the state machine + fn choose(self, key: u8) -> Option>; + // Returns Some if the sequencer is in a terminal state. None otherwise. + // The value represents the target position we should jump to. + fn try_get_range(&self) -> Option; +} + +#[derive(Debug)] +pub struct TrieNode { + key: u8, + children: Vec, + // Some if leaf node. None otherwise. + range_in_text: Option, +} + +impl From for TrieNode { + fn from(key: u8) -> Self { + TrieNode { + key, + children: vec![], + range_in_text: None, // Calculation happens after trie construction + } + } +} + +fn make_trie_children(keys: &[u8]) -> Vec { + keys.iter().map(|c| TrieNode::from(*c)).collect() +} + +fn attach_jump_targets_to_leaves( + node: &mut TrieNode, + jump_targets: &mut impl Iterator, +) { + if node.children.is_empty() { + node.range_in_text = jump_targets.next(); + return; + } + for child in node.children.iter_mut() { + attach_jump_targets_to_leaves(child, jump_targets); + } +} + +impl TrieNode { + pub fn build(keys: &[u8], jump_targets: Vec) -> Self { + assert!(!keys.is_empty()); + // Invalid key for the root node since it doesn't represent a key + let mut root = TrieNode::from(0); + let n = jump_targets.len(); + if n <= keys.len() { + root.children = make_trie_children(&keys[0..n]); + attach_jump_targets_to_leaves(&mut root, &mut jump_targets.into_iter()); + return root; + } + root.children = make_trie_children(keys); + + // Running BFS, expanding trie nodes along the way. + let mut queue = VecDeque::with_capacity(root.children.len()); + // Reverse-iterating the children such that the last key gets expanded first + queue.extend(root.children.iter_mut().rev()); + + let mut remaining = n - keys.len(); + loop { + let mut trie = queue.pop_front().unwrap(); + if remaining < keys.len() { + // We need to make remaining + 1 children because the current leaf + // node will no longer be a leaf node + trie.children = make_trie_children(&keys[0..remaining + 1]); + break; + } + trie.children = make_trie_children(keys); + // subtract 1 to account for the no-longer-leaf node + remaining -= keys.len() - 1; + queue.extend(trie.children.iter_mut().rev()); + } + attach_jump_targets_to_leaves(&mut root, &mut jump_targets.into_iter()); + root + } +} + +fn depth_first_search(node: &TrieNode) -> Vec<(Range, JumpSequence)> { + let key = node.key; + if node.children.is_empty() { + return vec![(node.range_in_text.unwrap(), JumpSequence::from(key))]; + } + node.children + .iter() + .flat_map(|child| { + depth_first_search(child).into_iter().map(|(pos, mut v)| { + v.prefix(key); + (pos, v) + }) + }) + .collect() +} + +impl JumpSequencer for TrieNode { + fn generate(&self) -> Vec { + if self.children.is_empty() { + return vec![]; + } + let mut annotations: Vec = self + .children + .iter() + .flat_map(|child| depth_first_search(child).into_iter()) + .map(|(range, sequence)| JumpAnnotation { + loc: range.head, + keys: String::from(sequence), + }) + .collect(); + annotations.sort_by_key(|annot| annot.loc); + annotations + } + + fn choose(self, key: u8) -> Option> { + for child in self.children { + if child.key == key { + return Some(Box::new(child)); + } + } + None + } + + fn try_get_range(&self) -> Option { + self.range_in_text + } +} + +#[cfg(test)] +mod jump_tests { + use super::*; + + fn next(it: &mut std::vec::IntoIter) -> Option<(String, usize)> { + match it.next() { + Some(jump) => Some((jump.keys, jump.loc)), + None => None, + } + } + + fn iota(n: usize) -> Vec { + (0..n).map(Range::point).collect() + } + + #[test] + fn more_keys_than_jump_targets() { + let mut paths = TrieNode::build(b"abcdefg", iota(2)).generate().into_iter(); + assert_eq!(next(&mut paths), Some((String::from("a"), 0))); + assert_eq!(next(&mut paths), Some((String::from("b"), 1))); + assert_eq!(next(&mut paths), None); + } + + #[test] + fn equal_number_of_keys_and_jump_targets() { + let mut paths = TrieNode::build(b"xyz", iota(3)).generate().into_iter(); + assert_eq!(next(&mut paths), Some((String::from("x"), 0))); + assert_eq!(next(&mut paths), Some((String::from("y"), 1))); + assert_eq!(next(&mut paths), Some((String::from("z"), 2))); + assert_eq!(next(&mut paths), None); + } + + #[test] + fn more_jump_targets_than_keys_1() { + let ranges = vec![1usize, 5, 9, 100] + .into_iter() + .map(Range::point) + .collect(); + let mut paths = TrieNode::build(b"xyz", ranges).generate().into_iter(); + assert_eq!(next(&mut paths), Some((String::from("x"), 1))); + assert_eq!(next(&mut paths), Some((String::from("y"), 5))); + assert_eq!(next(&mut paths), Some((String::from("zx"), 9))); + assert_eq!(next(&mut paths), Some((String::from("zy"), 100))); + assert_eq!(next(&mut paths), None); + } + + #[test] + fn more_jump_targets_than_keys_2() { + let mut paths = TrieNode::build(b"xyz", iota(5)).generate().into_iter(); + assert_eq!(next(&mut paths), Some((String::from("x"), 0))); + assert_eq!(next(&mut paths), Some((String::from("y"), 1))); + assert_eq!(next(&mut paths), Some((String::from("zx"), 2))); + assert_eq!(next(&mut paths), Some((String::from("zy"), 3))); + assert_eq!(next(&mut paths), Some((String::from("zz"), 4))); + assert_eq!(next(&mut paths), None); + } + + #[test] + fn more_jump_targets_than_keys_3() { + let mut paths = TrieNode::build(b"xyz", iota(6)).generate().into_iter(); + assert_eq!(next(&mut paths), Some((String::from("x"), 0))); + assert_eq!(next(&mut paths), Some((String::from("yx"), 1))); + assert_eq!(next(&mut paths), Some((String::from("yy"), 2))); + assert_eq!(next(&mut paths), Some((String::from("zx"), 3))); + assert_eq!(next(&mut paths), Some((String::from("zy"), 4))); + assert_eq!(next(&mut paths), Some((String::from("zz"), 5))); + assert_eq!(next(&mut paths), None); + } + + #[test] + fn more_jump_targets_than_keys_4() { + let mut paths = TrieNode::build(b"xyz", iota(7)).generate().into_iter(); + assert_eq!(next(&mut paths), Some((String::from("x"), 0))); + assert_eq!(next(&mut paths), Some((String::from("yx"), 1))); + assert_eq!(next(&mut paths), Some((String::from("yy"), 2))); + assert_eq!(next(&mut paths), Some((String::from("yz"), 3))); + assert_eq!(next(&mut paths), Some((String::from("zx"), 4))); + assert_eq!(next(&mut paths), Some((String::from("zy"), 5))); + assert_eq!(next(&mut paths), Some((String::from("zz"), 6))); + assert_eq!(next(&mut paths), None); + } + + #[test] + fn more_jump_targets_than_keys_5() { + let mut paths = TrieNode::build(b"xyz", iota(8)).generate().into_iter(); + assert_eq!(next(&mut paths), Some((String::from("xx"), 0))); + assert_eq!(next(&mut paths), Some((String::from("xy"), 1))); + assert_eq!(next(&mut paths), Some((String::from("yx"), 2))); + assert_eq!(next(&mut paths), Some((String::from("yy"), 3))); + assert_eq!(next(&mut paths), Some((String::from("yz"), 4))); + assert_eq!(next(&mut paths), Some((String::from("zx"), 5))); + assert_eq!(next(&mut paths), Some((String::from("zy"), 6))); + assert_eq!(next(&mut paths), Some((String::from("zz"), 7))); + assert_eq!(next(&mut paths), None); + } + + #[test] + fn more_jump_targets_than_keys_6() { + let mut paths = TrieNode::build(b"xyz", iota(9)).generate().into_iter(); + assert_eq!(next(&mut paths), Some((String::from("xx"), 0))); + assert_eq!(next(&mut paths), Some((String::from("xy"), 1))); + assert_eq!(next(&mut paths), Some((String::from("xz"), 2))); + assert_eq!(next(&mut paths), Some((String::from("yx"), 3))); + assert_eq!(next(&mut paths), Some((String::from("yy"), 4))); + assert_eq!(next(&mut paths), Some((String::from("yz"), 5))); + assert_eq!(next(&mut paths), Some((String::from("zx"), 6))); + assert_eq!(next(&mut paths), Some((String::from("zy"), 7))); + assert_eq!(next(&mut paths), Some((String::from("zz"), 8))); + assert_eq!(next(&mut paths), None); + } + + #[test] + fn more_jump_targets_than_keys_7() { + let root = TrieNode::build(b"xyz", iota(10)); + let mut paths = root.generate().into_iter(); + assert_eq!(next(&mut paths), Some((String::from("xx"), 0))); + assert_eq!(next(&mut paths), Some((String::from("xy"), 1))); + assert_eq!(next(&mut paths), Some((String::from("xz"), 2))); + assert_eq!(next(&mut paths), Some((String::from("yx"), 3))); + assert_eq!(next(&mut paths), Some((String::from("yy"), 4))); + assert_eq!(next(&mut paths), Some((String::from("yz"), 5))); + assert_eq!(next(&mut paths), Some((String::from("zx"), 6))); + assert_eq!(next(&mut paths), Some((String::from("zy"), 7))); + assert_eq!(next(&mut paths), Some((String::from("zzx"), 8))); + assert_eq!(next(&mut paths), Some((String::from("zzy"), 9))); + assert_eq!(next(&mut paths), None); + + let node = root.choose(b'z').unwrap(); + let mut paths = node.generate().into_iter(); + assert_eq!(next(&mut paths), Some((String::from("x"), 6))); + assert_eq!(next(&mut paths), Some((String::from("y"), 7))); + assert_eq!(next(&mut paths), Some((String::from("zx"), 8))); + assert_eq!(next(&mut paths), Some((String::from("zy"), 9))); + assert_eq!(next(&mut paths), None); + } +} diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index c84c616c6c0b..9df192c29788 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -58,6 +58,8 @@ pub fn default() -> HashMap { "k" => move_line_up, "j" => move_line_down, "." => goto_last_modification, + "w" => jump_to_identifier_label, + "/" => jump_to_str_label, }, ":" => command_mode, @@ -352,6 +354,8 @@ pub fn default() -> HashMap { "g" => { "Goto" "k" => extend_line_up, "j" => extend_line_down, + "w" => jump_to_identifier_label_and_extend_selection, + "/" => jump_to_str_label_and_extend_selection, }, })); let insert = keymap!({ "Insert mode" diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 5b5cda935043..6b4327ac3bff 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -16,10 +16,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, Change, Position, Range, Selection, Transaction, + visual_offset_from_block, Change, Position, Range, RopeSlice, Selection, Transaction, }; use helix_view::{ document::{Mode, SavePoint, SCRATCH_BUFFER_NAME}, @@ -121,28 +121,44 @@ impl EditorView { line_decorations.push(Box::new(line_decoration)); } - 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( @@ -159,8 +175,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( @@ -251,6 +278,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, @@ -258,19 +297,7 @@ impl EditorView { text_annotations: &TextAnnotations, ) -> Vec<(usize, std::ops::Range)> { 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); text_annotations.collect_overlay_highlights(range) } @@ -284,19 +311,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 @@ -328,6 +343,34 @@ impl EditorView { } } + pub fn dimmed_view( + doc: &Document, + anchor: usize, + height: u16, + theme: &Theme, + ) -> Box> { + let Some(highlight) = theme + .find_scope_index("ui.virtual.jump.dim") + .or_else(|| theme.find_scope_index("comment")) else { + // comment style as fallback + return Box::new(::std::iter::empty()); + }; + + 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 61d148d32c56..b41a71403356 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -288,6 +288,28 @@ pub struct Config { pub workspace_lsp_roots: Vec, /// Which line ending to choose for new documents. Defaults to `native`. i.e. `crlf` on Windows, otherwise `lf`. pub default_line_ending: LineEndingConfig, + pub jump_mode: JumpModeConfig, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(default, rename_all = "kebab-case", deny_unknown_fields)] +pub struct JumpModeConfig { + /// Whether or not to dim the view when in jump mode. Defaults to `true`. + pub dim_during_jump: bool, + /// How many characters the user should type before labelling the targets. + pub num_chars_before_label: u8, + /// Keys used in labels. Should be convertible to u8 + pub jump_keys: String, +} + +impl Default for JumpModeConfig { + fn default() -> Self { + JumpModeConfig { + dim_during_jump: true, + num_chars_before_label: 1, + jump_keys: String::from("jwetovxqpdygfblzhckisuranm"), + } + } } #[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -819,6 +841,7 @@ impl Default for Config { completion_replace: false, workspace_lsp_roots: Vec::new(), default_line_ending: LineEndingConfig::default(), + jump_mode: JumpModeConfig::default(), } } } diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index ee6fc1275deb..38498d497770 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -10,7 +10,7 @@ use helix_core::{ char_idx_at_visual_offset, doc_formatter::TextFormat, syntax::Highlight, - text_annotations::TextAnnotations, + text_annotations::{Overlay, TextAnnotations}, visual_offset_from_anchor, visual_offset_from_block, Position, RopeSlice, Selection, Transaction, VisualOffsetError::{PosAfterMaxRow, PosBeforeAnchorRow}, @@ -114,6 +114,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, @@ -131,6 +133,15 @@ pub struct View { /// mapping keeps track of the last applied history revision so that only new changes /// are applied. doc_revisions: HashMap, + // `visual_jump_labels` are annotated overlay texts that the user can type, to move the cursor + // to the annotated location. It's a single array from logical standpoint, but it's split into + // three arrays such that each array corresponds to one highlight. The first array contains + // single character jump labels. The second and third arrays collectively represent multi-char + // jump labels, but the second one contains the leading (first) character, whereas the third + // 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 { @@ -153,6 +164,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(), @@ -160,6 +172,8 @@ impl View { object_selections: Vec::new(), gutters, doc_revisions: HashMap::new(), + visual_jump_labels: [Rc::new([]), Rc::new([]), Rc::new([])], + in_visual_jump_mode: false, } } @@ -408,6 +422,31 @@ impl View { let mut text_annotations = doc.text_annotations(theme); + let mut add_overlay = |overlays: Rc<[Overlay]>, style| { + if !overlays.is_empty() { + text_annotations.add_overlay(overlays, style); + } + }; + + let try_get_style = + |scope: &str| theme.and_then(|t| t.find_scope_index(scope)).map(Highlight); + + // Overlays are added from lowest priority to highest, such that higher priority + // overlays can overwrite the lower ones. + add_overlay( + self.visual_jump_labels[2].clone(), + try_get_style("ui.virtual.jump.multi.rest"), + ); + add_overlay( + self.visual_jump_labels[1].clone(), + try_get_style("ui.virtual.jump.multi.first"), + ); + add_overlay( + self.visual_jump_labels[0].clone(), + try_get_style("ui.virtual.jump.single"), + ); + text_annotations.reset_pos(self.offset.anchor); + let DocumentInlayHints { id: _, type_inlay_hints, @@ -420,15 +459,9 @@ impl View { None => return text_annotations, }; - let type_style = theme - .and_then(|t| t.find_scope_index("ui.virtual.inlay-hint.type")) - .map(Highlight); - let parameter_style = theme - .and_then(|t| t.find_scope_index("ui.virtual.inlay-hint.parameter")) - .map(Highlight); - let other_style = theme - .and_then(|t| t.find_scope_index("ui.virtual.inlay-hint")) - .map(Highlight); + let type_style = try_get_style("ui.virtual.inlay-hint.type"); + let parameter_style = try_get_style("ui.virtual.inlay-hint.parameter"); + let other_style = try_get_style("ui.virtual.inlay-hint"); let mut add_annotations = |annotations: &Rc<[_]>, style| { if !annotations.is_empty() { diff --git a/runtime/themes/acme.toml b/runtime/themes/acme.toml index 650924741db9..1724efe9984f 100644 --- a/runtime/themes/acme.toml +++ b/runtime/themes/acme.toml @@ -10,6 +10,10 @@ "ui.statusline.inactive" = {fg="black", bg="acme_bar_inactive"} "ui.virtual" = "indent" "ui.virtual.ruler" = { bg = "acme_bar_bg" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.cursor.match" = {bg="acme_bar_bg"} "ui.cursor" = {bg="cursor", fg="white"} "ui.debug" = {fg="orange"} diff --git a/runtime/themes/autumn.toml b/runtime/themes/autumn.toml index 23af7d6f5fe9..067c9d633106 100644 --- a/runtime/themes/autumn.toml +++ b/runtime/themes/autumn.toml @@ -53,6 +53,10 @@ "ui.virtual.inlay-hint" = { fg = "my_gray4", bg="my_black", modifiers = ["normal"] } "ui.virtual.inlay-hint.parameter" = { fg = "my_gray4", modifiers = ["normal"] } "ui.virtual.inlay-hint.type" = { fg = "my_gray4", modifiers = ["italic"] } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "markup.heading" = "my_yellow1" "markup.list" = "my_white2" diff --git a/runtime/themes/ayu_dark.toml b/runtime/themes/ayu_dark.toml index 0b011b0eaf2f..45975b877993 100644 --- a/runtime/themes/ayu_dark.toml +++ b/runtime/themes/ayu_dark.toml @@ -55,6 +55,10 @@ "ui.virtual.whitespace" = "dark_gray" "ui.virtual.ruler" = { bg = "black" } "ui.virtual.inlay-hint" = { fg = "#e6b450", bg = "#302a20" } # original bg #e6b45033 +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.menu" = { fg = "foreground", bg = "black" } "ui.menu.selected" = { bg = "gray", fg = "background" } "ui.selection" = { bg = "dark_gray" } diff --git a/runtime/themes/ayu_light.toml b/runtime/themes/ayu_light.toml index 6c405381d044..dc46dce73b71 100644 --- a/runtime/themes/ayu_light.toml +++ b/runtime/themes/ayu_light.toml @@ -55,6 +55,10 @@ "ui.virtual.whitespace" = "dark_gray" "ui.virtual.ruler" = { bg = "black" } "ui.virtual.inlay-hint" = { fg = "#f4a028", bg = "#fcf2e3" } # bg original #ffaa3333 +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.menu" = { fg = "foreground", bg = "black" } "ui.menu.selected" = { bg = "gray", fg = "background" } "ui.selection" = { bg = "dark_gray" } diff --git a/runtime/themes/ayu_mirage.toml b/runtime/themes/ayu_mirage.toml index a4b74fcfbd77..0e060fa2092b 100644 --- a/runtime/themes/ayu_mirage.toml +++ b/runtime/themes/ayu_mirage.toml @@ -55,6 +55,10 @@ "ui.virtual.whitespace" = "dark_gray" "ui.virtual.ruler" = { bg = "black" } "ui.virtual.inlay-hint" = { fg = "#ffcc66", bg = "#47433d" } # original bg #ffcc6633 +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.menu" = { fg = "foreground", bg = "black" } "ui.menu.selected" = { bg = "gray", fg = "background" } "ui.selection" = { bg = "dark_gray" } diff --git a/runtime/themes/base16_default_dark.toml b/runtime/themes/base16_default_dark.toml index 1a38a6aebcfe..e152f4fed549 100644 --- a/runtime/themes/base16_default_dark.toml +++ b/runtime/themes/base16_default_dark.toml @@ -2,6 +2,10 @@ "ui.background" = { bg = "base00" } "ui.virtual.whitespace" = "base03" +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.menu" = { fg = "base05", bg = "base01" } "ui.menu.selected" = { fg = "base01", bg = "base04" } "ui.linenr" = { fg = "base03", bg = "base01" } diff --git a/runtime/themes/base16_default_light.toml b/runtime/themes/base16_default_light.toml index 84dab530564a..74ffb68ea927 100644 --- a/runtime/themes/base16_default_light.toml +++ b/runtime/themes/base16_default_light.toml @@ -13,6 +13,10 @@ "ui.cursor" = { fg = "base04", modifiers = ["reversed"] } "ui.cursor.primary" = { fg = "base05", modifiers = ["reversed"] } "ui.virtual.whitespace" = "base03" +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.text" = "base05" "operator" = "base05" "ui.text.focus" = "base05" diff --git a/runtime/themes/base16_terminal.toml b/runtime/themes/base16_terminal.toml index f397586118d5..f5155447447d 100644 --- a/runtime/themes/base16_terminal.toml +++ b/runtime/themes/base16_terminal.toml @@ -14,6 +14,10 @@ "ui.cursor" = { fg = "light-gray", modifiers = ["reversed"] } "ui.cursor.primary" = { fg = "light-gray", modifiers = ["reversed"] } "ui.virtual.whitespace" = "light-gray" +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "variable" = "light-red" "constant.numeric" = "yellow" "constant" = "yellow" diff --git a/runtime/themes/base16_transparent.toml b/runtime/themes/base16_transparent.toml index c314ae8c516e..e7a506d2b68e 100644 --- a/runtime/themes/base16_transparent.toml +++ b/runtime/themes/base16_transparent.toml @@ -31,6 +31,10 @@ "ui.virtual.inlay-hint.parameter" = { fg = "white", bg = "gray"} "ui.virtual.inlay-hint.type" = { fg = "white", bg = "gray"} "ui.virtual.wrap" = "gray" +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "variable" = "light-red" "constant.numeric" = "yellow" diff --git a/runtime/themes/bogster.toml b/runtime/themes/bogster.toml index 1ea13f1f528f..cd72d5b5b3ab 100644 --- a/runtime/themes/bogster.toml +++ b/runtime/themes/bogster.toml @@ -59,6 +59,10 @@ "ui.text.focus" = { fg = "bogster-fg1", modifiers= ["bold"] } "ui.virtual.whitespace" = "bogster-base5" "ui.virtual.ruler" = { bg = "bogster-base0" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.selection" = { bg = "bogster-base3" } "ui.cursor.match" = { fg = "bogster-base3", bg = "bogster-orange" } diff --git a/runtime/themes/bogster_light.toml b/runtime/themes/bogster_light.toml index e528d89d9e52..ba3ec87ac28e 100644 --- a/runtime/themes/bogster_light.toml +++ b/runtime/themes/bogster_light.toml @@ -59,6 +59,10 @@ "ui.text.focus" = { fg = "bogster-fg1", modifiers= ["bold"] } "ui.virtual.whitespace" = "bogster-base5" "ui.virtual.ruler" = { bg = "bogster-base00" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.selection" = { bg = "bogster-base1" } "ui.cursor.match" = { fg = "bogster-base2", bg = "bogster-orange" } diff --git a/runtime/themes/boo_berry.toml b/runtime/themes/boo_berry.toml index d3a3e22328f4..3ea83a4c75a5 100644 --- a/runtime/themes/boo_berry.toml +++ b/runtime/themes/boo_berry.toml @@ -53,6 +53,10 @@ "ui.virtual.ruler" = { bg = "berry_dim" } "ui.virtual.indent-guide" = { fg = "berry_fade" } "ui.virtual.inlay-hint" = { fg = "berry_desaturated" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "diff.plus" = { fg = "mint" } "diff.delta" = { fg = "gold" } diff --git a/runtime/themes/catppuccin_mocha.toml b/runtime/themes/catppuccin_mocha.toml index 126613bc70b8..b038249015e0 100644 --- a/runtime/themes/catppuccin_mocha.toml +++ b/runtime/themes/catppuccin_mocha.toml @@ -88,6 +88,10 @@ "ui.virtual.ruler" = { bg = "surface0" } "ui.virtual.indent-guide" = "surface0" "ui.virtual.inlay-hint" = { fg = "surface1", bg = "mantle" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.selection" = { bg = "surface1" } diff --git a/runtime/themes/darcula.toml b/runtime/themes/darcula.toml index 53a271bd23bc..f08d61f6c316 100644 --- a/runtime/themes/darcula.toml +++ b/runtime/themes/darcula.toml @@ -24,6 +24,10 @@ "ui.virtual.indent-guide" = "grey02" "ui.virtual.whitespace" = "grey03" "ui.virtual.inlay-hint" = { fg = "grey03", modifiers = ["italic"] } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.bufferline" = { fg = "grey04", bg = "grey00" } "ui.bufferline.active" = { fg = "grey07", bg = "grey02" } diff --git a/runtime/themes/dark_high_contrast.toml b/runtime/themes/dark_high_contrast.toml index 51701cfce935..dc0afd5aca73 100644 --- a/runtime/themes/dark_high_contrast.toml +++ b/runtime/themes/dark_high_contrast.toml @@ -16,6 +16,10 @@ "ui.virtual.inlay-hint.parameter" = { fg = "black", bg = "orange" } "ui.virtual.inlay-hint.type" = { fg = "black", bg = "orange" } "ui.virtual.wrap" = "gray" +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.statusline" = { fg = "white", bg = "deep_blue" } "ui.statusline.inactive" = { fg = "gray", bg = "deep_blue" } diff --git a/runtime/themes/dark_plus.toml b/runtime/themes/dark_plus.toml index 9f4d8b87e01c..2409a5d22584 100644 --- a/runtime/themes/dark_plus.toml +++ b/runtime/themes/dark_plus.toml @@ -90,6 +90,10 @@ "ui.virtual.ruler" = { bg = "borders" } "ui.virtual.indent-guide" = { fg = "dark_gray4" } "ui.virtual.inlay-hint" = { fg = "white", bg = "#444444" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "warning" = { fg = "gold2" } "error" = { fg = "red" } diff --git a/runtime/themes/doom_acario_dark.toml b/runtime/themes/doom_acario_dark.toml index 47f1dfca8bc7..a401c5ac28c5 100644 --- a/runtime/themes/doom_acario_dark.toml +++ b/runtime/themes/doom_acario_dark.toml @@ -65,6 +65,10 @@ 'ui.text.info' = { fg = 'fg' } 'ui.virtual.whitespace' = { fg = 'base2' } 'ui.virtual.ruler' = { bg = 'black' } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" 'ui.menu' = { fg = 'fg', bg = 'bg-alt' } 'ui.menu.selected' = { bg = 'base3', fg = 'fg' } 'ui.selection' = { bg = 'base2' } diff --git a/runtime/themes/dracula.toml b/runtime/themes/dracula.toml index 1ec5b4fe2765..8590c5f6c8ca 100644 --- a/runtime/themes/dracula.toml +++ b/runtime/themes/dracula.toml @@ -93,6 +93,10 @@ "ui.virtual.inlay-hint" = { fg = "cyan" } "ui.virtual.inlay-hint.parameter" = { fg = "cyan", modifiers = ["italic", "dim"] } "ui.virtual.inlay-hint.type" = { fg = "cyan", modifiers = ["italic", "dim"] } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "hint" = { fg = "purple" } "error" = { fg = "red" } "warning" = { fg = "yellow" } diff --git a/runtime/themes/dracula_at_night.toml b/runtime/themes/dracula_at_night.toml index b2e3b9a9fc11..0ec5b51835cf 100644 --- a/runtime/themes/dracula_at_night.toml +++ b/runtime/themes/dracula_at_night.toml @@ -44,6 +44,10 @@ "ui.text.focus" = { fg = "cyan" } "ui.window" = { fg = "foreground" } "ui.virtual.ruler" = { bg = "background_dark" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "error" = { fg = "red" } "warning" = { fg = "cyan" } diff --git a/runtime/themes/emacs.toml b/runtime/themes/emacs.toml index 513af60e165f..9890fb5a85a1 100644 --- a/runtime/themes/emacs.toml +++ b/runtime/themes/emacs.toml @@ -71,6 +71,10 @@ "ui.virtual.whitespace" = "highlight" "ui.virtual.ruler" = { bg = "gray95" } "ui.virtual.inlay-hint" = { fg = "gray75" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.cursorline.primary" = { bg = "darkseagreen2" } "ui.cursorline.secondary" = { bg = "darkseagreen2" } diff --git a/runtime/themes/everforest_dark.toml b/runtime/themes/everforest_dark.toml index 6903d1e5430c..6d0c50120eed 100644 --- a/runtime/themes/everforest_dark.toml +++ b/runtime/themes/everforest_dark.toml @@ -100,6 +100,10 @@ "ui.virtual.indent-guide" = { fg = "bg4" } "ui.virtual.inlay-hint" = { fg = "grey0" } "ui.virtual.wrap" = { fg = "grey0" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "hint" = "green" "info" = "blue" diff --git a/runtime/themes/everforest_light.toml b/runtime/themes/everforest_light.toml index cf39e23d62fd..51dfe1849e94 100644 --- a/runtime/themes/everforest_light.toml +++ b/runtime/themes/everforest_light.toml @@ -100,6 +100,10 @@ "ui.virtual.indent-guide" = { fg = "bg4" } "ui.virtual.inlay-hint" = { fg = "grey0" } "ui.virtual.wrap" = { fg = "grey0" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "hint" = "green" "info" = "blue" diff --git a/runtime/themes/flatwhite.toml b/runtime/themes/flatwhite.toml index d833d5c66eae..8d352775d673 100644 --- a/runtime/themes/flatwhite.toml +++ b/runtime/themes/flatwhite.toml @@ -62,6 +62,10 @@ "ui.virtual.inlay-hint" = { fg = "base4", modifiers = ["normal"] } "ui.virtual.inlay-hint.parameter" = { fg = "base3", modifiers = ["normal"] } "ui.virtual.inlay-hint.type" = { fg = "base3", modifiers = ["italic"] } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.linenr" = { bg = "base6" } "ui.linenr.selected" = { bg = "base6", modifiers = ["reversed"] } diff --git a/runtime/themes/fleet_dark.toml b/runtime/themes/fleet_dark.toml index b5ad1aa3ecc0..2daa7200c1b6 100644 --- a/runtime/themes/fleet_dark.toml +++ b/runtime/themes/fleet_dark.toml @@ -95,6 +95,10 @@ "ui.virtual" = "Gray 90" # .whitespace "ui.virtual.inlay-hint" = { fg = "Gray 70" } "ui.virtual.ruler" = { bg = "Gray 20" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "hint" = "Gray 80" "info" = "#A366C4" diff --git a/runtime/themes/github_dark.toml b/runtime/themes/github_dark.toml index 4f9aa562b0ed..a6013c95baf6 100644 --- a/runtime/themes/github_dark.toml +++ b/runtime/themes/github_dark.toml @@ -60,6 +60,10 @@ label = "scale.red.3" "ui.text.focus" = { fg = "fg.default" } "ui.text.inactive" = "fg.subtle" "ui.virtual" = { fg = "scale.gray.6" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.selection" = { bg = "scale.blue.8" } "ui.selection.primary" = { bg = "scale.blue.7" } diff --git a/runtime/themes/github_light.toml b/runtime/themes/github_light.toml index 3e2269698339..391b696324b1 100644 --- a/runtime/themes/github_light.toml +++ b/runtime/themes/github_light.toml @@ -60,6 +60,10 @@ label = "scale.red.5" "ui.text.focus" = { fg = "fg.default" } "ui.text.inactive" = "fg.subtle" "ui.virtual" = { fg = "scale.gray.2" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.selection" = { bg = "scale.blue.0" } "ui.selection.primary" = { bg = "scale.blue.1" } diff --git a/runtime/themes/gruvbox.toml b/runtime/themes/gruvbox.toml index c357e26c2f75..43f55fa48fb8 100644 --- a/runtime/themes/gruvbox.toml +++ b/runtime/themes/gruvbox.toml @@ -61,6 +61,10 @@ "ui.virtual.whitespace" = "bg2" "ui.virtual.ruler" = { bg = "bg1" } "ui.virtual.inlay-hint" = { fg = "gray1" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "diagnostic.warning" = { underline = { color = "orange1", style = "curl" } } "diagnostic.error" = { underline = { color = "red1", style = "curl" } } diff --git a/runtime/themes/heisenberg.toml b/runtime/themes/heisenberg.toml index 3c127a3e4bf9..f7ae99996bf2 100644 --- a/runtime/themes/heisenberg.toml +++ b/runtime/themes/heisenberg.toml @@ -51,6 +51,10 @@ "ui.virtual.whitespace" = { fg = "#08341B" } "ui.virtual.ruler" = { bg = "#041B0E" } "ui.virtual.indent-guide" = { fg = "#08341B" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.menu" = { fg = "crystal_blue", bg = "background" } "ui.menu.selected" = { bg = "hazmat_yellow", fg = "background" } "ui.selection" = { bg = "#1B0334" } diff --git a/runtime/themes/hex_steel.toml b/runtime/themes/hex_steel.toml index c81df4097371..c858ab0abf91 100644 --- a/runtime/themes/hex_steel.toml +++ b/runtime/themes/hex_steel.toml @@ -59,6 +59,10 @@ "ui.virtual.ruler" = { bg = "t1" } "ui.virtual.indent-guide" = { fg = "t3" } "ui.virtual.whitespace" = { fg = "t3" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "diagnostic.error" = { underline = { color = "error", style = "curl" } } "diagnostic.warning" = { underline = { color = "warning", style = "curl" } } diff --git a/runtime/themes/ingrid.toml b/runtime/themes/ingrid.toml index 8942c3ca2971..5f6734734002 100644 --- a/runtime/themes/ingrid.toml +++ b/runtime/themes/ingrid.toml @@ -57,6 +57,10 @@ "ui.text.focus" = { fg = "#250E07", modifiers= ["bold"] } "ui.virtual.whitespace" = "#A6B6CE" "ui.virtual.ruler" = { bg = "#F3EAE9" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.selection" = { bg = "#F3EAE9" } "ui.cursor.primary" = { bg = "#ED5466", fg = "#FFFCFD", modifiers = ["bold"] } diff --git a/runtime/themes/kanagawa.toml b/runtime/themes/kanagawa.toml index d0576a4e3747..b8b82e6e89cd 100644 --- a/runtime/themes/kanagawa.toml +++ b/runtime/themes/kanagawa.toml @@ -19,6 +19,10 @@ "ui.virtual.inlay-hint" = "fujiGray" "ui.virtual.inlay-hint.parameter" = { fg = "carpYellow", modifiers = ["dim"] } "ui.virtual.inlay-hint.type" = { fg = "waveAqua2", modifiers = ["dim"] } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.statusline" = { fg = "oldWhite", bg = "sumiInk0" } "ui.statusline.inactive" = { fg = "fujiGray", bg = "sumiInk0" } diff --git a/runtime/themes/meliora.toml b/runtime/themes/meliora.toml index e7037e18d7a6..8747a7b5c68e 100644 --- a/runtime/themes/meliora.toml +++ b/runtime/themes/meliora.toml @@ -53,6 +53,10 @@ "ui.virtual.ruler" = { bg = "light_black2" } "ui.virtual.indent-guide" = { fg = "dark_grey2" } "ui.virtual.whitespace" = { fg = "grey" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "diagnostic.error" = { underline = { color = "red", style = "curl" } } "diagnostic.warning" = { underline = { color = "orange", style = "curl" } } diff --git a/runtime/themes/mellow.toml b/runtime/themes/mellow.toml index 16d7b60866b5..d4d099a55c4c 100644 --- a/runtime/themes/mellow.toml +++ b/runtime/themes/mellow.toml @@ -81,6 +81,10 @@ "ui.virtual" = { fg = "gray02" } "ui.virtual.indent-guide" = { fg = "gray02" } "ui.virtual.inlay-hint" = { fg = "gray04" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.selection" = { bg = "gray03" } "ui.selection.primary" = { bg = "gray03" } diff --git a/runtime/themes/monokai.toml b/runtime/themes/monokai.toml index 913cc49b8870..f08de0464c56 100644 --- a/runtime/themes/monokai.toml +++ b/runtime/themes/monokai.toml @@ -34,6 +34,10 @@ "comment" = { fg = "#88846F" } "ui.virtual.whitespace" = "#88846F" "ui.virtual.ruler" = { bg = "#24241e" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "string" = { fg = "#e6db74" } "constant.character" = { fg = "#e6db74" } diff --git a/runtime/themes/monokai_pro.toml b/runtime/themes/monokai_pro.toml index 57bede94aefb..bd30c766feb1 100644 --- a/runtime/themes/monokai_pro.toml +++ b/runtime/themes/monokai_pro.toml @@ -8,6 +8,10 @@ "ui.menu.selected" = { fg = "base2", bg = "yellow" } "ui.virtual.whitespace" = "base5" "ui.virtual.ruler" = { bg = "base1" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "info" = "base8" "hint" = "base8" diff --git a/runtime/themes/monokai_pro_machine.toml b/runtime/themes/monokai_pro_machine.toml index b292e6b02517..cdb06cbce316 100644 --- a/runtime/themes/monokai_pro_machine.toml +++ b/runtime/themes/monokai_pro_machine.toml @@ -8,6 +8,10 @@ "ui.menu.selected" = { fg = "base2", bg = "yellow" } "ui.virtual.whitespace" = "base5" "ui.virtual.ruler" = { bg = "base1" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "info" = "base8" "hint" = "base8" diff --git a/runtime/themes/monokai_pro_octagon.toml b/runtime/themes/monokai_pro_octagon.toml index 3236fc167407..8f99d56d4f15 100644 --- a/runtime/themes/monokai_pro_octagon.toml +++ b/runtime/themes/monokai_pro_octagon.toml @@ -8,6 +8,10 @@ "ui.menu.selected" = { fg = "base2", bg = "yellow" } "ui.virtual.whitespace" = "base5" "ui.virtual.ruler" = { bg = "base1" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "info" = "base8" "hint" = "base8" diff --git a/runtime/themes/monokai_pro_ristretto.toml b/runtime/themes/monokai_pro_ristretto.toml index f897bddbd66b..f907c3e758f0 100644 --- a/runtime/themes/monokai_pro_ristretto.toml +++ b/runtime/themes/monokai_pro_ristretto.toml @@ -8,6 +8,10 @@ "ui.menu.selected" = { fg = "base2", bg = "yellow" } "ui.virtual.whitespace" = "base5" "ui.virtual.ruler" = { bg = "base1" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "info" = "base8" "hint" = "base8" diff --git a/runtime/themes/monokai_pro_spectrum.toml b/runtime/themes/monokai_pro_spectrum.toml index 74533404eb06..b02f7e6e0917 100644 --- a/runtime/themes/monokai_pro_spectrum.toml +++ b/runtime/themes/monokai_pro_spectrum.toml @@ -8,6 +8,10 @@ "ui.menu.selected" = { fg = "base2", bg = "yellow" } "ui.virtual.whitespace" = "base4" "ui.virtual.ruler" = { bg = "base1" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "info" = "base8" "hint" = "base8" diff --git a/runtime/themes/night_owl.toml b/runtime/themes/night_owl.toml index fc27dc7ccb4c..eb5f01ca4e43 100644 --- a/runtime/themes/night_owl.toml +++ b/runtime/themes/night_owl.toml @@ -36,6 +36,10 @@ 'ui.help' = { fg = 'gold', bg = 'background2'} 'ui.virtual.ruler' = { bg = 'grey4' } 'ui.virtual.whitespace' = { fg = 'grey4' } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" 'ui.cursorline.primary' = { bg = 'background2' } # SYNTAX diff --git a/runtime/themes/nightfox.toml b/runtime/themes/nightfox.toml index 069b32ab4d33..dd78ef50a217 100644 --- a/runtime/themes/nightfox.toml +++ b/runtime/themes/nightfox.toml @@ -34,6 +34,10 @@ "ui.virtual.whitespace" = { fg = "bg3" } # Whitespace markers in editing area. "ui.virtual.indent-guide" = { fg = "black" } # Vertical indent width guides "ui.virtual.inlay-hint" = { fg = "comment", bg = "bg2" } # Default style for inlay hints of all kinds +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.statusline" = { fg = "fg2", bg = "bg0" } # Status line. "ui.statusline.inactive" = { fg = "fg3", bg = "bg0" } # Status line in unfocused windows. diff --git a/runtime/themes/noctis.toml b/runtime/themes/noctis.toml index c7d33680ea3a..b84464aba28e 100644 --- a/runtime/themes/noctis.toml +++ b/runtime/themes/noctis.toml @@ -47,6 +47,10 @@ 'ui.virtual.ruler' = { bg = "mid-green" } # Vertical rulers (colored columns in editing area). 'ui.virtual.whitespace' = { fg = "light-gray"} # Whitespace markers in editing area. 'ui.virtual.indent-guide' = { fg = "light-gray" } # Indentation guides. +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" 'ui.statusline' = { fg = "light-green", bg = "autocomp-green"} # Status line. 'ui.statusline.inactive' = { fg = "white", bg = "mid-green"} # Status line in unfocused windows. diff --git a/runtime/themes/noctis_bordo.toml b/runtime/themes/noctis_bordo.toml index c3bbe79c9b2a..667f303569b0 100644 --- a/runtime/themes/noctis_bordo.toml +++ b/runtime/themes/noctis_bordo.toml @@ -25,6 +25,10 @@ "ui.background" = { bg = "base00" } "ui.virtual" = "base03" "ui.virtual.ruler" = { bg = "base01" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.menu" = { fg = "base05", bg = "base01" } "ui.menu.selected" = { fg = "base0B", bg = "base01" } "ui.popup" = { bg = "base01" } diff --git a/runtime/themes/nord.toml b/runtime/themes/nord.toml index f38ede547732..77503ed54fc0 100644 --- a/runtime/themes/nord.toml +++ b/runtime/themes/nord.toml @@ -6,6 +6,10 @@ "ui.menu" = { fg = "nord6", bg = "nord1" } "ui.menu.selected" = { fg = "nord8", bg = "nord2" } "ui.virtual.ruler" = { bg = "nord1" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "info" = "nord8" "hint" = "nord8" diff --git a/runtime/themes/nord_light.toml b/runtime/themes/nord_light.toml index b37d35962bc3..d096c5d0d057 100644 --- a/runtime/themes/nord_light.toml +++ b/runtime/themes/nord_light.toml @@ -8,6 +8,10 @@ "ui.statusline.inactive" = {bg="nord5", fg="nord0"} "ui.virtual" = "nord8" "ui.virtual.ruler" = {bg="nord4"} +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.cursor.match" = {bg="nord8"} "ui.cursor" = {bg="nord10", fg="nord6"} "ui.cursorline.primary" = {bg="nord5"} diff --git a/runtime/themes/onedark.toml b/runtime/themes/onedark.toml index 21101ea75031..b35ca6e15bc1 100644 --- a/runtime/themes/onedark.toml +++ b/runtime/themes/onedark.toml @@ -55,6 +55,10 @@ "ui.virtual.whitespace" = { fg = "light-gray" } "ui.virtual.ruler" = { bg = "gray" } "ui.virtual.inlay-hint" = { fg = "light-gray" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.cursor" = { fg = "white", modifiers = ["reversed"] } "ui.cursor.primary" = { fg = "white", modifiers = ["reversed"] } diff --git a/runtime/themes/onedarker.toml b/runtime/themes/onedarker.toml index 88b2871aba2b..b630e0277dd6 100644 --- a/runtime/themes/onedarker.toml +++ b/runtime/themes/onedarker.toml @@ -58,6 +58,10 @@ "ui.virtual.whitespace" = { fg = "light-gray" } "ui.virtual.ruler" = { bg = "gray" } "ui.virtual.inlay-hint" = { fg = "light-gray" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.cursor" = { fg = "white", modifiers = ["reversed"] } "ui.cursor.primary" = { fg = "white", modifiers = ["reversed"] } diff --git a/runtime/themes/onelight.toml b/runtime/themes/onelight.toml index e35abdb376da..6ae107b2993a 100644 --- a/runtime/themes/onelight.toml +++ b/runtime/themes/onelight.toml @@ -142,6 +142,10 @@ "ui.virtual.inlay-hint" = { fg = "grey-500" } "ui.virtual.inlay-hint.parameter" = { fg = "grey-500", modifiers = ["italic"] } "ui.virtual.inlay-hint.type" = { fg = "grey-500" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.menu" = { fg = "black", bg = "grey-300" } "ui.menu.selected" = { fg = "white", bg = "light-blue" } diff --git a/runtime/themes/papercolor-dark.toml b/runtime/themes/papercolor-dark.toml index eaaa36dcf52c..0eb7476d611d 100644 --- a/runtime/themes/papercolor-dark.toml +++ b/runtime/themes/papercolor-dark.toml @@ -14,6 +14,10 @@ "ui.statusline.inactive" = {bg="selection_foreground", fg="foreground"} "ui.virtual.whitespace" = { fg = "regular5" } "ui.virtual.ruler" = {bg="cursorline_background"} +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.cursor.match" = {bg = "regular5", fg = "regular0"} "ui.cursor" = {bg = "regular5", fg = "background"} "ui.window" = {bg = "#303030", fg = "bright2"} diff --git a/runtime/themes/papercolor-light.toml b/runtime/themes/papercolor-light.toml index 63671e1b3ff7..57d79f9d2b11 100644 --- a/runtime/themes/papercolor-light.toml +++ b/runtime/themes/papercolor-light.toml @@ -15,6 +15,10 @@ "ui.virtual" = "indent" "ui.virtual.whitespace" = { fg = "regular5" } "ui.virtual.ruler" = {bg="cursorline_background"} +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.cursor.match" = {bg = "regular5", fg = "regular0"} "ui.cursor" = {bg = "regular5", fg = "background"} "ui.window" = {bg = "#D0D0D0", fg = "bright2"} diff --git a/runtime/themes/penumbra+.toml b/runtime/themes/penumbra+.toml index bc53e82e07b0..64bb66945c15 100644 --- a/runtime/themes/penumbra+.toml +++ b/runtime/themes/penumbra+.toml @@ -84,6 +84,10 @@ error = "red" "ui.virtual.ruler" = { bg = "shade-" } "ui.virtual.whitespace" = "sky-" "ui.virtual.indent-guide" = "shade+" +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.menu" = { fg = "sky", bg = "shade-" } "ui.menu.selected" = { fg = "blue", modifiers = ["bold"] } diff --git a/runtime/themes/pop-dark.toml b/runtime/themes/pop-dark.toml index 6393dd730924..a59f0cfac3e1 100644 --- a/runtime/themes/pop-dark.toml +++ b/runtime/themes/pop-dark.toml @@ -40,6 +40,10 @@ error = { fg = 'brownD', bg = 'redE', modifiers = ['bold'] } 'ui.virtual.whitespace' = { fg = 'brownV' } 'ui.virtual.indent-guide' = { fg = 'brownR' } 'ui.virtual.inlay-hint' = {fg = 'brownD', bg = 'brownU'} +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" 'ui.menu' = { fg = 'greyT', bg = 'brownD' } 'ui.menu.selected' = { fg = 'orangeH', bg = 'brownH' } 'ui.popup' = { bg = 'brownD' } diff --git a/runtime/themes/rasmus.toml b/runtime/themes/rasmus.toml index bcfb0c66cfe6..4c0035b646fa 100644 --- a/runtime/themes/rasmus.toml +++ b/runtime/themes/rasmus.toml @@ -86,6 +86,10 @@ "ui.virtual" = { fg = "gray03" } "ui.virtual.indent-guide" = { fg = "gray04" } "ui.virtual.inlay-hint" = { fg = "gray05" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.selection" = { bg = "gray03" } "ui.selection.primary" = { bg = "gray03" } diff --git a/runtime/themes/rose_pine.toml b/runtime/themes/rose_pine.toml index e1d968941b52..2b5b96d4fa1d 100644 --- a/runtime/themes/rose_pine.toml +++ b/runtime/themes/rose_pine.toml @@ -39,6 +39,10 @@ "ui.virtual.whitespace" = { fg = "highlight_high" } "ui.virtual.indent-guide" = { fg = "muted" } "ui.virtual.inlay-hint" = { fg = "subtle" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.menu" = { fg = "subtle", bg = "surface" } "ui.menu.selected" = { fg = "text" } diff --git a/runtime/themes/serika-dark.toml b/runtime/themes/serika-dark.toml index d184197987d2..2297e8b8424e 100644 --- a/runtime/themes/serika-dark.toml +++ b/runtime/themes/serika-dark.toml @@ -53,6 +53,10 @@ "ui.virtual.whitespace" = "bg2" "ui.virtual.ruler" = { bg = "grey2" } "ui.virtual.inlay-hint" = { fg = "grey2", modifiers = ["italic"] } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "hint" = "blue" "info" = "aqua" diff --git a/runtime/themes/serika-light.toml b/runtime/themes/serika-light.toml index 1e3901ef24ed..54f991f4c3fe 100644 --- a/runtime/themes/serika-light.toml +++ b/runtime/themes/serika-light.toml @@ -53,6 +53,10 @@ "ui.virtual.whitespace" = { fg = "bg2" } "ui.virtual.ruler" = { bg = "bg01" } "ui.virtual.inlay-hint" = { fg = "grey2", modifiers = ["italic"] } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "hint" = "blue" "info" = "aqua" diff --git a/runtime/themes/snazzy.toml b/runtime/themes/snazzy.toml index eb88c5eb86b3..c92c2b8b9ecd 100644 --- a/runtime/themes/snazzy.toml +++ b/runtime/themes/snazzy.toml @@ -75,6 +75,10 @@ "ui.virtual.whitespace" = { fg = "comment" } "ui.virtual.indent-guide" = { fg = "opal" } "ui.virtual.ruler" = { bg = "background_dark" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "error" = { fg = "red" } "warning" = { fg = "cyan" } diff --git a/runtime/themes/solarized_dark.toml b/runtime/themes/solarized_dark.toml index 88f857d3dd36..aec01cb480c6 100644 --- a/runtime/themes/solarized_dark.toml +++ b/runtime/themes/solarized_dark.toml @@ -92,6 +92,10 @@ "ui.virtual.indent-guide" = { fg = "base02" } "ui.virtual.ruler" = { fg = "red" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" # normal模式的光标 "ui.cursor" = {fg = "base02", bg = "cyan"} diff --git a/runtime/themes/solarized_light.toml b/runtime/themes/solarized_light.toml index cd2d2b364748..aa76501d9237 100644 --- a/runtime/themes/solarized_light.toml +++ b/runtime/themes/solarized_light.toml @@ -107,6 +107,10 @@ "ui.virtual.indent-guide" = { fg = "base02" } "ui.virtual.ruler" = { fg = "red" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" # normal模式的光标 # normal mode cursor diff --git a/runtime/themes/sonokai.toml b/runtime/themes/sonokai.toml index f60bd4dbf55b..21cf14081248 100644 --- a/runtime/themes/sonokai.toml +++ b/runtime/themes/sonokai.toml @@ -72,6 +72,10 @@ "ui.menu.selected" = { fg = "bg0", bg = "green" } "ui.virtual.whitespace" = { fg = "grey_dim" } "ui.virtual.ruler" = { bg = "grey_dim" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" info = { fg = 'green', bg = 'bg2' } hint = { fg = 'blue', bg = 'bg2', modifiers = ['bold'] } diff --git a/runtime/themes/spacebones_light.toml b/runtime/themes/spacebones_light.toml index ad2a04d5ab26..d571ebd31a80 100644 --- a/runtime/themes/spacebones_light.toml +++ b/runtime/themes/spacebones_light.toml @@ -73,6 +73,10 @@ "ui.menu.selected" = { fg = "base", bg = "bg2", modifiers = ["bold"] } "ui.virtual" = "base-dim" "ui.virtual.ruler" = { bg = "bg1" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "diagnostic.warning" = { underline = { style = "curl", color = "meta" } } "diagnostic.error" = { underline = { style = "curl", color = "#e0211d" } } diff --git a/runtime/themes/tokyonight.toml b/runtime/themes/tokyonight.toml index cc99689fce7b..b02b08cc586f 100644 --- a/runtime/themes/tokyonight.toml +++ b/runtime/themes/tokyonight.toml @@ -49,6 +49,10 @@ "ui.virtual.ruler" = { bg = "foreground_gutter" } "ui.virtual.whitespace" = { fg = "foreground_gutter" } "ui.virtual.inlay-hint" = { fg = "comment" } +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.window" = { fg = "black" } "error" = { fg = "red" } diff --git a/runtime/themes/varua.toml b/runtime/themes/varua.toml index b07ab08dcd2d..042b0a592415 100644 --- a/runtime/themes/varua.toml +++ b/runtime/themes/varua.toml @@ -63,6 +63,10 @@ "ui.menu.selected" = { fg = "fg", bg = "bg1" } "ui.selection" = { bg = "bg3" } "ui.virtual.whitespace" = "grey0" +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "ui.statusline.insert" = { bg = "green", fg = "bg2" } "ui.statusline.select" = { bg = "blue", fg = "bg2" } "ui.virtual.wrap" = { fg = "grey0" } diff --git a/runtime/themes/zenburn.toml b/runtime/themes/zenburn.toml index 8518e78f8390..6428147179ab 100644 --- a/runtime/themes/zenburn.toml +++ b/runtime/themes/zenburn.toml @@ -34,6 +34,10 @@ "ui.help" = { fg = "white", bg = "black" } "ui.virtual.ruler" = { bg = "#484848" } "ui.virtual.whitespace" = { fg = "#5b605e", modifiers = ["bold"]} +"ui.virtual.jump.single" = { fg = "#ff007c", modifiers = ["bold"] } +"ui.virtual.jump.multi.first" = { fg = "#00dfff", modifiers = ["bold"] } +"ui.virtual.jump.multi.rest" = "#2b8db3" +"ui.virtual.jump.dim" = "#666666" "punctuation.delimiter" = "#8f8f8f"