Refactor jump_to_label implementation

This splits the implemention of jump_to_label into more parts that allow
it to be reused in the future for other on-screen pickers.

The main way it will be used is by generating some ranges for the labels
(see generate_viewport_token_ranges, the function that generates the
ranges of visible tokens in the current viewport) and giving those labels
to the prompt_for_label function. prompt_for_label calls a callback
when a label has been typed.

An example of how this could be used in the future is to pick a selection
to set as the primary selection.
pull/10764/head
clo4 3 weeks ago
parent e76020ddb9
commit 49c59b0fa1

@ -6011,47 +6011,51 @@ fn replay_macro(cx: &mut Context) {
}
fn goto_word(cx: &mut Context) {
jump_to_word(cx, Movement::Move)
let ranges = generate_viewport_token_ranges(cx);
prompt_for_label(cx, ranges, |cx, range| {
let (view, doc) = current!(cx.editor);
doc.set_selection(view.id, range.with_direction(Direction::Forward).into());
})
}
fn extend_to_word(cx: &mut Context) {
jump_to_word(cx, Movement::Extend)
let ranges = generate_viewport_token_ranges(cx);
prompt_for_label(cx, ranges, |cx, range| {
let (view, doc) = current!(cx.editor);
let primary_selection = doc.selection(view.id).primary();
let anchor = if range.anchor < range.head {
let from = primary_selection.from();
if range.anchor < from {
range.anchor
} else {
from
}
} else {
let to = primary_selection.to();
if range.anchor > to {
range.anchor
} else {
to
}
};
let range = Range::new(anchor, range.head);
doc.set_selection(view.id, range.into());
})
}
fn jump_to_label(cx: &mut Context, labels: Vec<Range>, behaviour: Movement) {
let doc = doc!(cx.editor);
let alphabet = &cx.editor.config().jump_label_alphabet;
if labels.is_empty() {
return;
}
let alphabet_char = |i| {
let mut res = Tendril::new();
res.push(alphabet[i]);
res
};
/// Displays 2-character labels on the ranges provided, and runs the given
/// callback when the user types one of labels.
fn prompt_for_label(
cx: &mut Context,
label_ranges: Vec<Range>,
on_label_typed_callback: impl FnOnce(&mut Context, Range) + 'static,
) {
display_labels(cx, &label_ranges);
// Add label for each jump candidate to the View as virtual text.
let text = doc.text().slice(..);
let mut overlays: Vec<_> = labels
.iter()
.enumerate()
.flat_map(|(i, range)| {
[
Overlay::new(range.from(), alphabet_char(i / alphabet.len())),
Overlay::new(
graphemes::next_grapheme_boundary(text, range.from()),
alphabet_char(i % alphabet.len()),
),
]
})
.collect();
overlays.sort_unstable_by_key(|overlay| overlay.char_idx);
let (view, doc) = current!(cx.editor);
doc.set_jump_labels(view.id, overlays);
// Accept two characters matching a visible label. Jump to the candidate
// for that label if it exists.
let primary_selection = doc.selection(view.id).primary();
let view = view.id;
let doc = doc.id();
cx.on_next_key(move |cx, event| {
@ -6065,7 +6069,7 @@ fn jump_to_label(cx: &mut Context, labels: Vec<Range>, behaviour: Movement) {
};
let outer = i * alphabet.len();
// Bail if the given character cannot be a jump label.
if outer > labels.len() {
if outer > label_ranges.len() {
doc_mut!(cx.editor, &doc).remove_jump_labels(view);
return;
}
@ -6078,34 +6082,47 @@ fn jump_to_label(cx: &mut Context, labels: Vec<Range>, behaviour: Movement) {
else {
return;
};
if let Some(mut range) = labels.get(outer + inner).copied() {
range = if behaviour == Movement::Extend {
let anchor = if range.anchor < range.head {
let from = primary_selection.from();
if range.anchor < from {
range.anchor
} else {
from
}
} else {
let to = primary_selection.to();
if range.anchor > to {
range.anchor
} else {
to
}
};
Range::new(anchor, range.head)
} else {
range.with_direction(Direction::Forward)
};
doc_mut!(cx.editor, &doc).set_selection(view, range.into());
if let Some(range) = label_ranges.get(outer + inner).copied() {
on_label_typed_callback(cx, range);
}
});
});
}
fn jump_to_word(cx: &mut Context, behaviour: Movement) {
/// Displays 2-letter labels on the ranges provided.
fn display_labels(cx: &mut Context, label_ranges: &[Range]) {
let doc = doc!(cx.editor);
let alphabet = &cx.editor.config().jump_label_alphabet;
if label_ranges.is_empty() {
return;
}
let alphabet_char = |i| {
let mut res = Tendril::new();
res.push(alphabet[i]);
res
};
// Add label for each jump candidate to the View as virtual text.
let text = doc.text().slice(..);
let mut overlays: Vec<_> = label_ranges
.iter()
.enumerate()
.flat_map(|(i, range)| {
[
Overlay::new(range.from(), alphabet_char(i / alphabet.len())),
Overlay::new(
graphemes::next_grapheme_boundary(text, range.from()),
alphabet_char(i % alphabet.len()),
),
]
})
.collect();
overlays.sort_unstable_by_key(|overlay| overlay.char_idx);
let (view, doc) = current!(cx.editor);
doc.set_jump_labels(view.id, overlays);
}
fn generate_viewport_token_ranges(cx: &mut Context) -> Vec<Range> {
// Calculate the jump candidates: ranges for any visible words with two or
// more characters.
let alphabet = &cx.editor.config().jump_label_alphabet;
@ -6192,5 +6209,5 @@ fn jump_to_word(cx: &mut Context, behaviour: Movement) {
break;
}
}
jump_to_label(cx, words, behaviour)
words
}

Loading…
Cancel
Save