add jump-label-follow-blacklist, an option that allows removing certain key combinations from the possible jump labels.

pull/11893/head
Talia-12 1 month ago
parent a7651f5bf0
commit 24a6413011
No known key found for this signature in database
GPG Key ID: 5A592C80EA158257

@ -51,6 +51,7 @@
| `popup-border` | Draw border around `popup`, `menu`, `all`, or `none` | `none` |
| `indent-heuristic` | How the indentation for a newly inserted line is computed: `simple` just copies the indentation level from the previous line, `tree-sitter` computes the indentation based on the syntax tree and `hybrid` combines both approaches. If the chosen heuristic is not available, a different one will be used as a fallback (the fallback order being `hybrid` -> `tree-sitter` -> `simple`). | `hybrid`
| `jump-label-alphabet` | The characters that are used to generate two character jump labels. Characters at the start of the alphabet are used first. | `"abcdefghijklmnopqrstuvwxyz"`
| `jump-label-follow-blacklist` | Used to remove jump label combinations that are hard to type. For example, to make the labels "fj" and "fy" not used, set this option to `{ f = "jy" }` | `{}`
| `end-of-line-diagnostics` | Minimum severity of diagnostics to render at the end of the line. Set to `disable` to disable entirely. Refer to the setting about `inline-diagnostics` for more details | "disable"
### `[editor.statusline]` Section

@ -6152,15 +6152,52 @@ fn extend_to_word(cx: &mut Context) {
}
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
let doc = doc!(cx.editor);
let alphabet = &cx.editor.config().jump_label_alphabet;
let follow_blacklist = &cx.editor.config().jump_label_follow_blacklist;
let jump_label_lookup = follow_blacklist.get_allow_list(alphabet);
let partial_sums = jump_label_lookup
.alphabet
.iter()
.map(|c| jump_label_lookup.follow_whitelist.get(c).unwrap())
.map(|v| v.len())
.fold(vec![0], |mut acc, curr| {
acc.push(acc.last().unwrap() + curr);
acc
});
let make_jump_label = |i| {
// finding, based on the total number of overlays generated so far, what the index into the
// first characters and second characters respectively should be.
let first_i = partial_sums.iter().take_while(|p| p <= &&i).count() - 1;
let second_i = i - partial_sums
.get(first_i)
.unwrap_or_else(|| panic!("first_i outside of partial_sums indices."));
let first = *jump_label_lookup.alphabet.get(first_i).unwrap_or_else(|| {
panic!(
"alphabet_char called with i ({}) greater than the number of valid pairs ({}).",
i,
partial_sums.last().unwrap()
)
});
let second = jump_label_lookup.follow_whitelist.get(&first)
.unwrap_or_else(|| {
panic!("there should not be any first chars created which don't have entries in follow_whitelist.")
})[second_i];
let mut first_res = Tendril::new();
let mut second_res = Tendril::new();
first_res.push(first);
second_res.push(second);
(first_res, second_res)
};
// Add label for each jump candidate to the View as virtual text.
@ -6169,15 +6206,18 @@ fn jump_to_label(cx: &mut Context, labels: Vec<Range>, behaviour: Movement) {
.iter()
.enumerate()
.flat_map(|(i, range)| {
let (first_grapheme, second_grapheme) = make_jump_label(i);
[
Overlay::new(range.from(), alphabet_char(i / alphabet.len())),
Overlay::new(range.from(), first_grapheme),
Overlay::new(
graphemes::next_grapheme_boundary(text, range.from()),
alphabet_char(i % alphabet.len()),
second_grapheme,
),
]
})
.collect();
overlays.sort_unstable_by_key(|overlay| overlay.char_idx);
let (view, doc) = current!(cx.editor);
doc.set_jump_labels(view.id, overlays);
@ -6188,15 +6228,17 @@ fn jump_to_label(cx: &mut Context, labels: Vec<Range>, behaviour: Movement) {
let view = view.id;
let doc = doc.id();
cx.on_next_key(move |cx, event| {
let alphabet = &cx.editor.config().jump_label_alphabet;
let Some(i) = event
.char()
.and_then(|ch| alphabet.iter().position(|&it| it == ch))
else {
let Some((first_ch, i)) = event.char().and_then(|ch| {
jump_label_lookup
.alphabet
.iter()
.position(|&it| it == ch)
.map(|i| (ch, i))
}) else {
doc_mut!(cx.editor, &doc).remove_jump_labels(view);
return;
};
let outer = i * alphabet.len();
let outer = partial_sums[i];
// Bail if the given character cannot be a jump label.
if outer > labels.len() {
doc_mut!(cx.editor, &doc).remove_jump_labels(view);
@ -6204,13 +6246,17 @@ fn jump_to_label(cx: &mut Context, labels: Vec<Range>, behaviour: Movement) {
}
cx.on_next_key(move |cx, event| {
doc_mut!(cx.editor, &doc).remove_jump_labels(view);
let alphabet = &cx.editor.config().jump_label_alphabet;
let Some(inner) = event
.char()
.and_then(|ch| alphabet.iter().position(|&it| it == ch))
else {
let Some(inner) = event.char().and_then(|ch| {
jump_label_lookup
.follow_whitelist
.get(&first_ch)
.unwrap_or(&vec![])
.iter()
.position(|&it| it == ch)
}) else {
return;
};
if let Some(mut range) = labels.get(outer + inner).copied() {
range = if behaviour == Movement::Extend {
let anchor = if range.anchor < range.head {
@ -6242,7 +6288,13 @@ fn jump_to_word(cx: &mut Context, behaviour: Movement) {
// Calculate the jump candidates: ranges for any visible words with two or
// more characters.
let alphabet = &cx.editor.config().jump_label_alphabet;
let jump_label_limit = alphabet.len() * alphabet.len();
let follow_blacklist = &cx.editor.config().jump_label_follow_blacklist;
let jump_label_lookup = follow_blacklist.get_allow_list(alphabet);
let jump_label_limit = jump_label_lookup
.follow_whitelist
.values()
.map(|v| v.len())
.sum();
let mut words = Vec::with_capacity(jump_label_limit);
let (view, doc) = current_ref!(cx.editor);
let text = doc.text().slice(..);

@ -21,7 +21,7 @@ use helix_lsp::{Call, LanguageServerId};
use tokio_stream::wrappers::UnboundedReceiverStream;
use std::{
borrow::Cow,
borrow::{Borrow, Cow},
cell::Cell,
collections::{BTreeMap, HashMap, HashSet},
fs,
@ -342,11 +342,117 @@ pub struct Config {
deserialize_with = "deserialize_alphabet"
)]
pub jump_label_alphabet: Vec<char>,
// characters not allowed to follow each starting character
pub jump_label_follow_blacklist: JumpLabelFollowBlacklist,
/// Display diagnostic below the line they occur.
pub inline_diagnostics: InlineDiagnosticsConfig,
pub end_of_line_diagnostics: DiagnosticFilter,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct JumpLabelFollowBlacklist(HashMap<char, Vec<char>>);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct JumpLabelLookup {
pub alphabet: Vec<char>,
pub follow_whitelist: HashMap<char, Vec<char>>,
}
impl JumpLabelFollowBlacklist {
pub fn get(&self, c: char) -> Option<&Vec<char>> {
self.0.get(&c)
}
pub fn get_allow_list(&self, alphabet: &Vec<char>) -> JumpLabelLookup {
let follow_whitelist: HashMap<_, _> = alphabet
.iter()
.filter_map(|c| {
let unique_chars: HashSet<_> = alphabet.iter().copied().collect();
let blacklist_chars: Option<HashSet<_>> =
self.get(*c).and_then(|v| Some(v.iter().copied().collect()));
let whitelist_chars = if let Some(blacklist_chars) = blacklist_chars {
unique_chars
.symmetric_difference(&blacklist_chars)
.map(|c| *c)
.collect()
} else {
alphabet.clone()
};
if whitelist_chars.len() > 0 {
Some((*c, whitelist_chars))
} else {
None
}
})
.collect();
let alphabet = alphabet
.iter()
.filter_map(|c| {
if follow_whitelist.contains_key(c) {
Some(*c)
} else {
None
}
})
.collect();
JumpLabelLookup {
alphabet,
follow_whitelist,
}
}
}
impl Serialize for JumpLabelFollowBlacklist {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.0
.iter()
.map(|(k, v)| (*k, v.iter().collect::<String>()))
.collect::<HashMap<_, _>>()
.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for JumpLabelFollowBlacklist {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::Error;
let map = HashMap::<char, String>::deserialize(deserializer)?;
let map = map
.into_iter()
.map(|(k, v)| {
let chars: Vec<_> = v.chars().collect();
let unique_chars: HashSet<_> = chars.iter().copied().collect();
if unique_chars.len() != chars.len() {
return Err(<D::Error as Error>::custom(
"jump-label-follow-blacklist lists must contain unique characters",
));
}
Ok((k, chars))
})
.collect::<Result<HashMap<_, _>, _>>()?;
Ok(Self(map))
}
}
impl Default for JumpLabelFollowBlacklist {
fn default() -> Self {
Self(HashMap::new())
}
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Eq, PartialOrd, Ord)]
#[serde(rename_all = "kebab-case", default)]
pub struct SmartTabConfig {
@ -980,6 +1086,7 @@ impl Default for Config {
popup_border: PopupBorderConfig::None,
indent_heuristic: IndentationHeuristic::default(),
jump_label_alphabet: ('a'..='z').collect(),
jump_label_follow_blacklist: JumpLabelFollowBlacklist::default(),
inline_diagnostics: InlineDiagnosticsConfig::default(),
end_of_line_diagnostics: DiagnosticFilter::Disable,
}

Loading…
Cancel
Save