Copy changes from the-mikedavis

See
425fee957c...bf9dd58fc4?diff=split&w=
pull/11695/head
thomasschafer 2 months ago
parent 237cbe4bca
commit 81b0dbc71a
No known key found for this signature in database

@ -521,8 +521,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",
@ -669,6 +689,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 =
@ -5440,119 +5501,225 @@ 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_around_type(cx: &mut Context) {
textobject_treesitter(cx, textobject::TextObject::Around, "class");
}
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_inner(cx: &mut Context) { fn select_textobject_around_test(cx: &mut Context) {
select_textobject(cx, textobject::TextObject::Inside); textobject_treesitter(cx, textobject::TextObject::Around, "test");
} }
fn select_textobject(cx: &mut Context, objtype: textobject::TextObject) { 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| {
let (view, doc) = current!(editor);
let (lang_config, syntax) = match doc.language_config().zip(doc.syntax()) {
Some(t) => t,
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(
text,
range,
obj_type,
object_name,
syntax.tree().root_node(),
lang_config,
count,
)
});
doc.set_selection(view.id, selection);
};
cx.editor.apply_motion(motion);
}
cx.on_next_key(move |cx, event| { fn select_textobject_inside_paragraph(cx: &mut Context) {
cx.editor.autoinfo = None; textobject_paragraph(cx, textobject::TextObject::Inside);
if let Some(ch) = event.char() { }
let textobject = move |editor: &mut 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()) {
Some(t) => t,
None => return range,
};
textobject::textobject_treesitter(
text,
range,
objtype,
obj_name,
syntax.tree().root_node(),
lang_config,
count,
)
};
if ch == 'g' && doc.diff_handle().is_none() { fn select_textobject_around_paragraph(cx: &mut Context) {
editor.set_status("Diff is not available in current buffer"); textobject_paragraph(cx, textobject::TextObject::Around);
return; }
}
let textobject_change = |range: Range| -> Range { fn textobject_paragraph(cx: &mut Context, textobject: textobject::TextObject) {
let diff_handle = doc.diff_handle().unwrap(); let count = cx.count();
let diff = diff_handle.load(); let motion = move |editor: &mut Editor| {
let line = range.cursor_line(text); let (view, doc) = current!(editor);
let hunk_idx = if let Some(hunk_idx) = diff.hunk_at(line as u32, false) { let text = doc.text().slice(..);
hunk_idx let selection = doc
} else { .selection(view.id)
return range; .clone()
}; .transform(|range| textobject::textobject_paragraph(text, range, textobject, count));
let hunk = diff.nth_hunk(hunk_idx).after; doc.set_selection(view.id, selection);
};
cx.editor.apply_motion(motion);
}
let start = text.line_to_char(hunk.start as usize); fn select_textobject_inside_closest_surrounding_pair(cx: &mut Context) {
let end = text.line_to_char(hunk.end as usize); textobject_closest_surrounding_pair(cx, textobject::TextObject::Inside);
Range::new(start, end).with_direction(range.direction()) }
};
let selection = doc.selection(view.id).clone().transform(|range| { fn select_textobject_around_closest_surrounding_pair(cx: &mut Context) {
match ch { textobject_closest_surrounding_pair(cx, textobject::TextObject::Around);
'w' => textobject::textobject_word(text, range, objtype, count, false), }
'W' => textobject::textobject_word(text, range, objtype, count, true),
't' => textobject_treesitter("class", range), fn textobject_closest_surrounding_pair(cx: &mut Context, textobject: textobject::TextObject) {
'f' => textobject_treesitter("function", range), let count = cx.count();
'a' => textobject_treesitter("parameter", range), let motion = move |editor: &mut Editor| {
'c' => textobject_treesitter("comment", range), let (view, doc) = current!(editor);
'T' => textobject_treesitter("test", range), let text = doc.text().slice(..);
'e' => textobject_treesitter("entry", range), let syntax = doc.syntax();
'p' => textobject::textobject_paragraph(text, range, objtype, count), let selection = doc.selection(view.id).clone().transform(|range| {
'm' => textobject::textobject_pair_surround_closest( textobject::textobject_pair_surround_closest(syntax, text, range, textobject, count)
doc.syntax(), });
text, doc.set_selection(view.id, selection);
range, };
objtype, cx.editor.apply_motion(motion);
count, }
),
'g' => textobject_change(range), fn select_textobject_inside_word(cx: &mut Context) {
// TODO: cancel new ranges if inconsistent surround matches across lines textobject_word(cx, textobject::TextObject::Inside, false);
ch if !ch.is_ascii_alphanumeric() => textobject::textobject_pair_surround( }
doc.syntax(),
text, fn select_textobject_around_word(cx: &mut Context) {
range, textobject_word(cx, textobject::TextObject::Around, false);
objtype, }
ch,
count, #[allow(non_snake_case)]
), fn select_textobject_inside_WORD(cx: &mut Context) {
_ => range, textobject_word(cx, textobject::TextObject::Inside, true);
} }
});
doc.set_selection(view.id, selection); #[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");
return;
};
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 hunk_idx = if let Some(hunk_idx) = diff.hunk_at(line as u32, false) {
hunk_idx
} else {
return range;
}; };
cx.editor.apply_motion(textobject); let hunk = diff.nth_hunk(hunk_idx).after;
}
});
let title = match objtype { let start = text.line_to_char(hunk.start as usize);
textobject::TextObject::Inside => "Match inside", let end = text.line_to_char(hunk.end as usize);
textobject::TextObject::Around => "Match around", Range::new(start, end).with_direction(range.direction())
_ => return, });
drop(diff);
doc.set_selection(view.id, selection);
}; };
let help_text = [ cx.editor.apply_motion(motion);
("w", "Word"), }
("W", "WORD"),
("p", "Paragraph"), fn select_textobject_inside_surrounding_pair(cx: &mut Context, ch: char) {
("t", "Type definition (tree-sitter)"), textobject_surrounding_pair(cx, textobject::TextObject::Inside, ch);
("f", "Function (tree-sitter)"), }
("a", "Argument/parameter (tree-sitter)"),
("c", "Comment (tree-sitter)"), fn select_textobject_around_surrounding_pair(cx: &mut Context, ch: char) {
("T", "Test (tree-sitter)"), textobject_surrounding_pair(cx, textobject::TextObject::Around, ch);
("e", "Data structure entry (tree-sitter)"), }
("m", "Closest surrounding pair (tree-sitter)"),
("g", "Change"), fn textobject_surrounding_pair(
(" ", "... or any character acting as a pair"), cx: &mut Context,
]; textobject: textobject::TextObject,
pair_char: char,
) {
if pair_char.is_ascii_alphanumeric() {
return;
}
cx.editor.autoinfo = Some(Info::new(title, &help_text)); 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);
};
cx.editor.apply_motion(motion);
} }
fn surround_add(cx: &mut Context) { fn surround_add(cx: &mut Context) {

@ -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, &body) Info::new(&self.name, &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)
} }

@ -909,6 +909,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…
Cancel
Save