mirror of https://github.com/helix-editor/helix
parent
b42e1d20d2
commit
e9de036c16
|
@ -551,8 +551,28 @@ impl MappableCommand {
|
||||||
surround_add, "Surround add",
|
surround_add, "Surround add",
|
||||||
surround_replace, "Surround replace",
|
surround_replace, "Surround replace",
|
||||||
surround_delete, "Surround delete",
|
surround_delete, "Surround delete",
|
||||||
select_textobject_around, "Select around object",
|
select_textobject_inside_type, "Select inside type definition (tree-sitter)",
|
||||||
select_textobject_inner, "Select inside object",
|
select_textobject_around_type, "Select around type definition (tree-sitter)",
|
||||||
|
select_textobject_inside_function, "Select inside function (tree-sitter)",
|
||||||
|
select_textobject_around_function, "Select around function (tree-sitter)",
|
||||||
|
select_textobject_inside_parameter, "Select inside argument/parameter (tree-sitter)",
|
||||||
|
select_textobject_around_parameter, "Select around argument/parameter (tree-sitter)",
|
||||||
|
select_textobject_inside_comment, "Select inside comment (tree-sitter)",
|
||||||
|
select_textobject_around_comment, "Select around comment (tree-sitter)",
|
||||||
|
select_textobject_inside_test, "Select inside test (tree-sitter)",
|
||||||
|
select_textobject_around_test, "Select around test (tree-sitter)",
|
||||||
|
select_textobject_inside_entry, "Select inside data structure entry (tree-sitter)",
|
||||||
|
select_textobject_around_entry, "Select around data structure entry (tree-sitter)",
|
||||||
|
select_textobject_inside_paragraph, "Select inside paragraph",
|
||||||
|
select_textobject_around_paragraph, "Select around paragraph",
|
||||||
|
select_textobject_inside_closest_surrounding_pair, "Select inside closest surrounding pair (tree-sitter)",
|
||||||
|
select_textobject_around_closest_surrounding_pair, "Select around closest surrounding pair (tree-sitter)",
|
||||||
|
select_textobject_inside_word, "Select inside word",
|
||||||
|
select_textobject_around_word, "Select around word",
|
||||||
|
select_textobject_inside_WORD, "Select inside WORD",
|
||||||
|
select_textobject_around_WORD, "Select around WORD",
|
||||||
|
select_textobject_inside_change, "Select inside VCS change",
|
||||||
|
select_textobject_around_change, "Select around VCS change",
|
||||||
goto_next_function, "Goto next function",
|
goto_next_function, "Goto next function",
|
||||||
goto_prev_function, "Goto previous function",
|
goto_prev_function, "Goto previous function",
|
||||||
goto_next_class, "Goto next type definition",
|
goto_next_class, "Goto next type definition",
|
||||||
|
@ -703,6 +723,47 @@ impl PartialEq for MappableCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: this is mostly a copy of MappableCommand. Fold this into MappableCommand?
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct FallbackCommand {
|
||||||
|
name: &'static str,
|
||||||
|
fun: fn(cx: &mut Context, ch: char),
|
||||||
|
doc: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! static_fallback_commands {
|
||||||
|
( $($name:ident, $doc:literal,)* ) => {
|
||||||
|
$(
|
||||||
|
#[allow(non_upper_case_globals)]
|
||||||
|
pub const $name: Self = Self {
|
||||||
|
name: stringify!($name),
|
||||||
|
fun: $name,
|
||||||
|
doc: $doc
|
||||||
|
};
|
||||||
|
)*
|
||||||
|
|
||||||
|
pub const FALLBACK_COMMAND_LIST: &'static [Self] = &[
|
||||||
|
$( Self::$name, )*
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FallbackCommand {
|
||||||
|
pub fn execute(&self, cx: &mut Context, ch: char) {
|
||||||
|
(self.fun)(cx, ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn doc(&self) -> &str {
|
||||||
|
self.doc
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
static_fallback_commands!(
|
||||||
|
select_textobject_inside_surrounding_pair, "Select inside any character acting as a pair (tree-sitter)",
|
||||||
|
select_textobject_around_surrounding_pair, "Select around any character acting as a pair (tree-sitter)",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn no_op(_cx: &mut Context) {}
|
fn no_op(_cx: &mut Context) {}
|
||||||
|
|
||||||
type MoveFn =
|
type MoveFn =
|
||||||
|
@ -5847,48 +5908,179 @@ fn goto_prev_entry(cx: &mut Context) {
|
||||||
goto_ts_object_impl(cx, "entry", Direction::Backward)
|
goto_ts_object_impl(cx, "entry", Direction::Backward)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_textobject_around(cx: &mut Context) {
|
fn select_textobject_inside_type(cx: &mut Context) {
|
||||||
select_textobject(cx, textobject::TextObject::Around);
|
textobject_treesitter(cx, textobject::TextObject::Inside, "class");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_textobject_inner(cx: &mut Context) {
|
fn select_textobject_around_type(cx: &mut Context) {
|
||||||
select_textobject(cx, textobject::TextObject::Inside);
|
textobject_treesitter(cx, textobject::TextObject::Around, "class");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
|
fn select_textobject_inside_function(cx: &mut Context) {
|
||||||
|
textobject_treesitter(cx, textobject::TextObject::Inside, "function");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_textobject_around_function(cx: &mut Context) {
|
||||||
|
textobject_treesitter(cx, textobject::TextObject::Around, "function");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_textobject_inside_parameter(cx: &mut Context) {
|
||||||
|
textobject_treesitter(cx, textobject::TextObject::Inside, "parameter");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_textobject_around_parameter(cx: &mut Context) {
|
||||||
|
textobject_treesitter(cx, textobject::TextObject::Around, "parameter");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_textobject_inside_comment(cx: &mut Context) {
|
||||||
|
textobject_treesitter(cx, textobject::TextObject::Inside, "comment");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_textobject_around_comment(cx: &mut Context) {
|
||||||
|
textobject_treesitter(cx, textobject::TextObject::Around, "comment");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_textobject_inside_test(cx: &mut Context) {
|
||||||
|
textobject_treesitter(cx, textobject::TextObject::Inside, "test");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_textobject_around_test(cx: &mut Context) {
|
||||||
|
textobject_treesitter(cx, textobject::TextObject::Around, "test");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_textobject_inside_entry(cx: &mut Context) {
|
||||||
|
textobject_treesitter(cx, textobject::TextObject::Inside, "entry");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_textobject_around_entry(cx: &mut Context) {
|
||||||
|
textobject_treesitter(cx, textobject::TextObject::Around, "entry");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn textobject_treesitter(
|
||||||
|
cx: &mut Context,
|
||||||
|
obj_type: textobject::TextObject,
|
||||||
|
object_name: &'static str,
|
||||||
|
) {
|
||||||
let count = cx.count();
|
let count = cx.count();
|
||||||
|
let motion = move |editor: &mut Editor| {
|
||||||
cx.on_next_key(move |cx, event| {
|
|
||||||
cx.editor.autoinfo = None;
|
|
||||||
if let Some(ch) = event.char() {
|
|
||||||
let textobject = move |editor: &mut Editor| {
|
|
||||||
let (view, doc) = current!(editor);
|
let (view, doc) = current!(editor);
|
||||||
let text = doc.text().slice(..);
|
|
||||||
|
|
||||||
let textobject_treesitter = |obj_name: &str, range: Range| -> Range {
|
|
||||||
let (lang_config, syntax) = match doc.language_config().zip(doc.syntax()) {
|
let (lang_config, syntax) = match doc.language_config().zip(doc.syntax()) {
|
||||||
Some(t) => t,
|
Some(t) => t,
|
||||||
None => return range,
|
None => {
|
||||||
|
editor.set_status("Syntax information is not available in current buffer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
let text = doc.text().slice(..);
|
||||||
|
let selection = doc.selection(view.id).clone().transform(|range| {
|
||||||
textobject::textobject_treesitter(
|
textobject::textobject_treesitter(
|
||||||
text,
|
text,
|
||||||
range,
|
range,
|
||||||
objtype,
|
obj_type,
|
||||||
obj_name,
|
object_name,
|
||||||
syntax.tree().root_node(),
|
syntax.tree().root_node(),
|
||||||
lang_config,
|
lang_config,
|
||||||
count,
|
count,
|
||||||
)
|
)
|
||||||
|
});
|
||||||
|
doc.set_selection(view.id, selection);
|
||||||
};
|
};
|
||||||
|
cx.editor.apply_motion(motion);
|
||||||
|
}
|
||||||
|
|
||||||
if ch == 'g' && doc.diff_handle().is_none() {
|
fn select_textobject_inside_paragraph(cx: &mut Context) {
|
||||||
|
textobject_paragraph(cx, textobject::TextObject::Inside);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_textobject_around_paragraph(cx: &mut Context) {
|
||||||
|
textobject_paragraph(cx, textobject::TextObject::Around);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn textobject_paragraph(cx: &mut Context, textobject: textobject::TextObject) {
|
||||||
|
let count = cx.count();
|
||||||
|
let motion = move |editor: &mut Editor| {
|
||||||
|
let (view, doc) = current!(editor);
|
||||||
|
let text = doc.text().slice(..);
|
||||||
|
let selection = doc
|
||||||
|
.selection(view.id)
|
||||||
|
.clone()
|
||||||
|
.transform(|range| textobject::textobject_paragraph(text, range, textobject, count));
|
||||||
|
doc.set_selection(view.id, selection);
|
||||||
|
};
|
||||||
|
cx.editor.apply_motion(motion);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_textobject_inside_closest_surrounding_pair(cx: &mut Context) {
|
||||||
|
textobject_closest_surrounding_pair(cx, textobject::TextObject::Inside);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_textobject_around_closest_surrounding_pair(cx: &mut Context) {
|
||||||
|
textobject_closest_surrounding_pair(cx, textobject::TextObject::Around);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn textobject_closest_surrounding_pair(cx: &mut Context, textobject: textobject::TextObject) {
|
||||||
|
let count = cx.count();
|
||||||
|
let motion = move |editor: &mut Editor| {
|
||||||
|
let (view, doc) = current!(editor);
|
||||||
|
let text = doc.text().slice(..);
|
||||||
|
let syntax = doc.syntax();
|
||||||
|
let selection = doc.selection(view.id).clone().transform(|range| {
|
||||||
|
textobject::textobject_pair_surround_closest(syntax, text, range, textobject, count)
|
||||||
|
});
|
||||||
|
doc.set_selection(view.id, selection);
|
||||||
|
};
|
||||||
|
cx.editor.apply_motion(motion);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_textobject_inside_word(cx: &mut Context) {
|
||||||
|
textobject_word(cx, textobject::TextObject::Inside, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_textobject_around_word(cx: &mut Context) {
|
||||||
|
textobject_word(cx, textobject::TextObject::Around, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn select_textobject_inside_WORD(cx: &mut Context) {
|
||||||
|
textobject_word(cx, textobject::TextObject::Inside, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn select_textobject_around_WORD(cx: &mut Context) {
|
||||||
|
textobject_word(cx, textobject::TextObject::Around, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn textobject_word(cx: &mut Context, textobject: textobject::TextObject, longword: bool) {
|
||||||
|
let count = cx.count();
|
||||||
|
let motion = move |editor: &mut Editor| {
|
||||||
|
let (view, doc) = current!(editor);
|
||||||
|
let text = doc.text().slice(..);
|
||||||
|
let selection = doc.selection(view.id).clone().transform(|range| {
|
||||||
|
textobject::textobject_word(text, range, textobject, count, longword)
|
||||||
|
});
|
||||||
|
doc.set_selection(view.id, selection);
|
||||||
|
};
|
||||||
|
cx.editor.apply_motion(motion);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_textobject_inside_change(cx: &mut Context) {
|
||||||
|
textobject_change(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_textobject_around_change(cx: &mut Context) {
|
||||||
|
textobject_change(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn textobject_change(cx: &mut Context) {
|
||||||
|
let motion = move |editor: &mut Editor| {
|
||||||
|
let (view, doc) = current!(editor);
|
||||||
|
let Some(diff_handle) = doc.diff_handle() else {
|
||||||
editor.set_status("Diff is not available in current buffer");
|
editor.set_status("Diff is not available in current buffer");
|
||||||
return;
|
return;
|
||||||
}
|
};
|
||||||
|
|
||||||
let textobject_change = |range: Range| -> Range {
|
|
||||||
let diff_handle = doc.diff_handle().unwrap();
|
|
||||||
let diff = diff_handle.load();
|
let diff = diff_handle.load();
|
||||||
|
let text = doc.text().slice(..);
|
||||||
|
let selection = doc.selection(view.id).clone().transform(|range| {
|
||||||
let line = range.cursor_line(text);
|
let line = range.cursor_line(text);
|
||||||
let hunk_idx = if let Some(hunk_idx) = diff.hunk_at(line as u32, false) {
|
let hunk_idx = if let Some(hunk_idx) = diff.hunk_at(line as u32, false) {
|
||||||
hunk_idx
|
hunk_idx
|
||||||
|
@ -5900,66 +6092,41 @@ fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) {
|
||||||
let start = text.line_to_char(hunk.start as usize);
|
let start = text.line_to_char(hunk.start as usize);
|
||||||
let end = text.line_to_char(hunk.end as usize);
|
let end = text.line_to_char(hunk.end as usize);
|
||||||
Range::new(start, end).with_direction(range.direction())
|
Range::new(start, end).with_direction(range.direction())
|
||||||
|
});
|
||||||
|
drop(diff);
|
||||||
|
doc.set_selection(view.id, selection);
|
||||||
};
|
};
|
||||||
|
cx.editor.apply_motion(motion);
|
||||||
|
}
|
||||||
|
|
||||||
let selection = doc.selection(view.id).clone().transform(|range| {
|
fn select_textobject_inside_surrounding_pair(cx: &mut Context, ch: char) {
|
||||||
match ch {
|
textobject_surrounding_pair(cx, textobject::TextObject::Inside, ch);
|
||||||
'w' => textobject::textobject_word(text, range, objtype, count, false),
|
}
|
||||||
'W' => textobject::textobject_word(text, range, objtype, count, true),
|
|
||||||
't' => textobject_treesitter("class", range),
|
fn select_textobject_around_surrounding_pair(cx: &mut Context, ch: char) {
|
||||||
'f' => textobject_treesitter("function", range),
|
textobject_surrounding_pair(cx, textobject::TextObject::Around, ch);
|
||||||
'a' => textobject_treesitter("parameter", range),
|
}
|
||||||
'c' => textobject_treesitter("comment", range),
|
|
||||||
'T' => textobject_treesitter("test", range),
|
fn textobject_surrounding_pair(
|
||||||
'e' => textobject_treesitter("entry", range),
|
cx: &mut Context,
|
||||||
'p' => textobject::textobject_paragraph(text, range, objtype, count),
|
textobject: textobject::TextObject,
|
||||||
'm' => textobject::textobject_pair_surround_closest(
|
pair_char: char,
|
||||||
doc.syntax(),
|
) {
|
||||||
text,
|
if pair_char.is_ascii_alphanumeric() {
|
||||||
range,
|
return;
|
||||||
objtype,
|
|
||||||
count,
|
|
||||||
),
|
|
||||||
'g' => textobject_change(range),
|
|
||||||
// TODO: cancel new ranges if inconsistent surround matches across lines
|
|
||||||
ch if !ch.is_ascii_alphanumeric() => textobject::textobject_pair_surround(
|
|
||||||
doc.syntax(),
|
|
||||||
text,
|
|
||||||
range,
|
|
||||||
objtype,
|
|
||||||
ch,
|
|
||||||
count,
|
|
||||||
),
|
|
||||||
_ => range,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let count = cx.count();
|
||||||
|
let motion = move |editor: &mut Editor| {
|
||||||
|
let (view, doc) = current!(editor);
|
||||||
|
let text = doc.text().slice(..);
|
||||||
|
let syntax = doc.syntax();
|
||||||
|
let selection = doc.selection(view.id).clone().transform(|range| {
|
||||||
|
textobject::textobject_pair_surround(syntax, text, range, textobject, pair_char, count)
|
||||||
});
|
});
|
||||||
doc.set_selection(view.id, selection);
|
doc.set_selection(view.id, selection);
|
||||||
};
|
};
|
||||||
cx.editor.apply_motion(textobject);
|
cx.editor.apply_motion(motion);
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let title = match objtype {
|
|
||||||
textobject::TextObject::Inside => "Match inside",
|
|
||||||
textobject::TextObject::Around => "Match around",
|
|
||||||
_ => return,
|
|
||||||
};
|
|
||||||
let help_text = [
|
|
||||||
("w", "Word"),
|
|
||||||
("W", "WORD"),
|
|
||||||
("p", "Paragraph"),
|
|
||||||
("t", "Type definition (tree-sitter)"),
|
|
||||||
("f", "Function (tree-sitter)"),
|
|
||||||
("a", "Argument/parameter (tree-sitter)"),
|
|
||||||
("c", "Comment (tree-sitter)"),
|
|
||||||
("T", "Test (tree-sitter)"),
|
|
||||||
("e", "Data structure entry (tree-sitter)"),
|
|
||||||
("m", "Closest surrounding pair (tree-sitter)"),
|
|
||||||
("g", "Change"),
|
|
||||||
(" ", "... or any character acting as a pair"),
|
|
||||||
];
|
|
||||||
|
|
||||||
cx.editor.autoinfo = Some(Info::new(title, &help_text));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static SURROUND_HELP_TEXT: [(&str, &str); 6] = [
|
static SURROUND_HELP_TEXT: [(&str, &str); 6] = [
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
pub mod default;
|
pub mod default;
|
||||||
pub mod macros;
|
pub mod macros;
|
||||||
|
|
||||||
|
use crate::commands::FallbackCommand;
|
||||||
pub use crate::commands::MappableCommand;
|
pub use crate::commands::MappableCommand;
|
||||||
use arc_swap::{
|
use arc_swap::{
|
||||||
access::{DynAccess, DynGuard},
|
access::{DynAccess, DynGuard},
|
||||||
|
@ -24,7 +25,8 @@ pub struct KeyTrieNode {
|
||||||
name: String,
|
name: String,
|
||||||
map: HashMap<KeyEvent, KeyTrie>,
|
map: HashMap<KeyEvent, KeyTrie>,
|
||||||
order: Vec<KeyEvent>,
|
order: Vec<KeyEvent>,
|
||||||
pub is_sticky: bool,
|
is_sticky: bool,
|
||||||
|
fallback: Option<FallbackCommand>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for KeyTrieNode {
|
impl<'de> Deserialize<'de> for KeyTrieNode {
|
||||||
|
@ -49,6 +51,7 @@ impl KeyTrieNode {
|
||||||
map,
|
map,
|
||||||
order,
|
order,
|
||||||
is_sticky: false,
|
is_sticky: false,
|
||||||
|
fallback: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,13 +102,16 @@ impl KeyTrieNode {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
});
|
});
|
||||||
|
|
||||||
let body: Vec<_> = body
|
let mut body: Vec<_> = body
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(events, desc)| {
|
.map(|(events, desc)| {
|
||||||
let events = events.iter().map(ToString::to_string).collect::<Vec<_>>();
|
let events = events.iter().map(ToString::to_string).collect::<Vec<_>>();
|
||||||
(events.join(", "), desc)
|
(events.join(", "), desc)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
if let Some(fallback) = self.fallback.as_ref() {
|
||||||
|
body.push(("...".to_string(), fallback.doc()));
|
||||||
|
}
|
||||||
Info::new(self.name.clone(), &body)
|
Info::new(self.name.clone(), &body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -267,6 +273,28 @@ impl KeyTrie {
|
||||||
}
|
}
|
||||||
Some(trie)
|
Some(trie)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn search_fallback(&self, keys: &[KeyEvent]) -> Option<&FallbackCommand> {
|
||||||
|
// TODO: this is copied from above, hacky
|
||||||
|
let mut trie = self;
|
||||||
|
let mut keys = keys.iter().peekable();
|
||||||
|
while let Some(key) = keys.next() {
|
||||||
|
trie = match trie {
|
||||||
|
KeyTrie::Node(map) => match map.get(key) {
|
||||||
|
Some(i) => Some(i),
|
||||||
|
None => {
|
||||||
|
if keys.peek().is_none() {
|
||||||
|
return map.fallback.as_ref();
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// leaf encountered while keys left to process
|
||||||
|
KeyTrie::MappableCommand(_) | KeyTrie::Sequence(_) => None,
|
||||||
|
}?
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
@ -281,6 +309,7 @@ pub enum KeymapResult {
|
||||||
/// Key is invalid in combination with previous keys. Contains keys leading upto
|
/// Key is invalid in combination with previous keys. Contains keys leading upto
|
||||||
/// and including current (invalid) key.
|
/// and including current (invalid) key.
|
||||||
Cancelled(Vec<KeyEvent>),
|
Cancelled(Vec<KeyEvent>),
|
||||||
|
Fallback(FallbackCommand, char),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A map of command names to keybinds that will execute the command.
|
/// A map of command names to keybinds that will execute the command.
|
||||||
|
@ -376,7 +405,16 @@ impl Keymaps {
|
||||||
self.state.clear();
|
self.state.clear();
|
||||||
KeymapResult::MatchedSequence(cmds.clone())
|
KeymapResult::MatchedSequence(cmds.clone())
|
||||||
}
|
}
|
||||||
None => KeymapResult::Cancelled(self.state.drain(..).collect()),
|
None => {
|
||||||
|
if let Some(ch) = key.char() {
|
||||||
|
if let Some(fallback) = trie.search_fallback(&self.state[1..]) {
|
||||||
|
self.state.clear();
|
||||||
|
return KeymapResult::Fallback(fallback.clone(), ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KeymapResult::Cancelled(self.state.drain(..).collect())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,8 +104,32 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
|
||||||
"s" => surround_add,
|
"s" => surround_add,
|
||||||
"r" => surround_replace,
|
"r" => surround_replace,
|
||||||
"d" => surround_delete,
|
"d" => surround_delete,
|
||||||
"a" => select_textobject_around,
|
"i" => { "Match inside" fallback=select_textobject_inside_surrounding_pair
|
||||||
"i" => select_textobject_inner,
|
"w" => select_textobject_inside_word,
|
||||||
|
"W" => select_textobject_inside_WORD,
|
||||||
|
"p" => select_textobject_inside_paragraph,
|
||||||
|
"t" => select_textobject_inside_type,
|
||||||
|
"f" => select_textobject_inside_function,
|
||||||
|
"a" => select_textobject_inside_parameter,
|
||||||
|
"c" => select_textobject_inside_comment,
|
||||||
|
"T" => select_textobject_inside_test,
|
||||||
|
"e" => select_textobject_inside_entry,
|
||||||
|
"m" => select_textobject_inside_closest_surrounding_pair,
|
||||||
|
"g" => select_textobject_inside_change,
|
||||||
|
},
|
||||||
|
"a" => { "Match around" fallback=select_textobject_around_surrounding_pair
|
||||||
|
"w" => select_textobject_around_word,
|
||||||
|
"W" => select_textobject_around_WORD,
|
||||||
|
"p" => select_textobject_around_paragraph,
|
||||||
|
"t" => select_textobject_around_type,
|
||||||
|
"f" => select_textobject_around_function,
|
||||||
|
"a" => select_textobject_around_parameter,
|
||||||
|
"c" => select_textobject_around_comment,
|
||||||
|
"T" => select_textobject_around_test,
|
||||||
|
"e" => select_textobject_around_entry,
|
||||||
|
"m" => select_textobject_around_closest_surrounding_pair,
|
||||||
|
"g" => select_textobject_around_change,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"[" => { "Left bracket"
|
"[" => { "Left bracket"
|
||||||
"d" => goto_prev_diag,
|
"d" => goto_prev_diag,
|
||||||
|
|
|
@ -84,9 +84,9 @@ macro_rules! keymap {
|
||||||
};
|
};
|
||||||
|
|
||||||
(@trie
|
(@trie
|
||||||
{ $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }
|
{ $label:literal $(sticky=$sticky:literal)? $(fallback=$fallback:ident)? $($($key:literal)|+ => $value:tt,)+ }
|
||||||
) => {
|
) => {
|
||||||
keymap!({ $label $(sticky=$sticky)? $($($key)|+ => $value,)+ })
|
keymap!({ $label $(sticky=$sticky)? $(fallback=$fallback)? $($($key)|+ => $value,)+ })
|
||||||
};
|
};
|
||||||
|
|
||||||
(@trie [$($cmd:ident),* $(,)?]) => {
|
(@trie [$($cmd:ident),* $(,)?]) => {
|
||||||
|
@ -94,7 +94,7 @@ macro_rules! keymap {
|
||||||
};
|
};
|
||||||
|
|
||||||
(
|
(
|
||||||
{ $label:literal $(sticky=$sticky:literal)? $($($key:literal)|+ => $value:tt,)+ }
|
{ $label:literal $(sticky=$sticky:literal)? $(fallback=$fallback:ident)? $($($key:literal)|+ => $value:tt,)+ }
|
||||||
) => {
|
) => {
|
||||||
// modified from the hashmap! macro
|
// modified from the hashmap! macro
|
||||||
{
|
{
|
||||||
|
@ -113,6 +113,7 @@ macro_rules! keymap {
|
||||||
)+
|
)+
|
||||||
)*
|
)*
|
||||||
let mut _node = $crate::keymap::KeyTrieNode::new($label, _map, _order);
|
let mut _node = $crate::keymap::KeyTrieNode::new($label, _map, _order);
|
||||||
|
$( _node.fallback = Some($crate::commands::FallbackCommand::$fallback); )?
|
||||||
$( _node.is_sticky = $sticky; )?
|
$( _node.is_sticky = $sticky; )?
|
||||||
$crate::keymap::KeyTrie::Node(_node)
|
$crate::keymap::KeyTrie::Node(_node)
|
||||||
}
|
}
|
||||||
|
|
|
@ -926,6 +926,9 @@ impl EditorView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeymapResult::NotFound | KeymapResult::Cancelled(_) => return Some(key_result),
|
KeymapResult::NotFound | KeymapResult::Cancelled(_) => return Some(key_result),
|
||||||
|
KeymapResult::Fallback(fallback, ch) => {
|
||||||
|
fallback.execute(cxt, *ch);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue