Compare commits

...

1 Commits

Author SHA1 Message Date
Conrad Irwin
fdc996a87c Some of :norm in vim mode 2025-01-23 10:42:29 -07:00
3 changed files with 202 additions and 42 deletions

View File

@@ -94,7 +94,7 @@ impl Keystroke {
"alt" => alt = true,
"shift" => shift = true,
"fn" => function = true,
"cmd" | "super" | "win" => platform = true,
"cmd" | "super" | "win" | "" => platform = true,
_ => {
if let Some(next) = components.peek() {
if next.is_empty() && source.ends_with('-') {

View File

@@ -8,8 +8,10 @@ use editor::{
Bias, Editor, ToPoint,
};
use gpui::{
actions, impl_internal_actions, Action, AppContext, Global, ViewContext, WindowContext,
actions, impl_internal_actions, Action, AppContext, Global, Keystroke, ViewContext,
WindowContext,
};
use itertools::Itertools;
use language::Point;
use multi_buffer::MultiBufferRow;
use regex::Regex;
@@ -78,6 +80,7 @@ impl_internal_actions!(
WithRange,
WithCount,
OnMatchingLines,
NormalCommand,
ShellExec
]
);
@@ -238,6 +241,10 @@ pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
Vim::action(editor, cx, |vim, action: &ShellExec, cx| {
action.run(vim, cx)
});
Vim::action(editor, cx, |vim, action: &NormalCommand, cx| {
action.run(vim, cx)
})
}
@@ -846,6 +853,20 @@ pub fn command_interceptor(mut input: &str, cx: &AppContext) -> Option<CommandIn
} else {
None
}
} else if query.starts_with("norm") {
let mut normal = "normal".chars().peekable();
let mut query = query.chars().peekable();
while normal.peek().is_some_and(|char| Some(char) == query.peek()) {
normal.next();
query.next();
}
if query.next() == Some(' ') {
let remainder = query.collect::<String>();
NormalCommand::parse(&remainder, range.clone())
} else {
None
}
} else if query.contains('!') {
ShellExec::parse(query, range.clone())
} else {
@@ -1072,14 +1093,37 @@ impl OnMatchingLines {
editor.change_selections(None, cx, |s| {
s.replace_cursors_with(|_| new_selections);
});
cx.dispatch_action(action);
cx.defer(move |editor, cx| {
let newest = editor.selections.newest::<Point>(cx).clone();
editor.change_selections(None, cx, |s| {
s.select(vec![newest]);
if let Some(NormalCommand { keystrokes, .. }) =
action.as_any().downcast_ref::<NormalCommand>()
{
let Some(workspace) = editor.workspace() else {
return;
};
let task = workspace.update(cx, |workspace, cx| {
workspace.send_keystrokes_impl(keystrokes.clone(), cx)
});
editor.end_transaction_at(Instant::now(), cx);
})
cx.spawn(|editor, mut cx| async move {
task.await?;
editor.update(&mut cx, move |editor, cx| {
let newest = editor.selections.newest::<Point>(cx).clone();
editor.change_selections(None, cx, |s| {
s.select(vec![newest]);
});
editor.end_transaction_at(Instant::now(), cx);
})
})
.detach_and_log_err(cx);
} else {
cx.dispatch_action(action);
cx.defer(move |editor, cx| {
let newest = editor.selections.newest::<Point>(cx).clone();
editor.change_selections(None, cx, |s| {
s.select(vec![newest]);
});
editor.end_transaction_at(Instant::now(), cx);
})
}
})
.ok();
})
@@ -1088,6 +1132,111 @@ impl OnMatchingLines {
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct NormalCommand {
range: Option<CommandRange>,
keystrokes: Vec<Keystroke>,
}
fn preprocess_keystroke(input: &str) -> String {
let parts = input.split("-").collect::<Vec<&str>>();
parts
.into_iter()
.enumerate()
.map(|(i, part)| {
if i + 1 < parts.len() {
match &part.to_ascii_lowercase() {
"s" => "shift",
"c" => "ctrl",
"d" => "command",
"a" | "m" | "t" => "alt",
part => part,
}
} else {
match &part.to_ascii_lowercase() {
"bs" => "backspace",
"lt" => "<",
"bar" => "|",
"bslash" => "\\",
"cr" | "return" => "enter",
"esc" => "escape",
part => part,
}
}
})
.join("-")
}
impl NormalCommand {
fn parse(query: &str, range: Option<CommandRange>) -> Option<Box<dyn Action>> {
let mut keystrokes: Vec<Keystroke> = Vec::default();
let mut chars = query.chars();
let mut in_angled = false;
let mut angled = "".to_string();
while let Some(char) = chars.next() {
if in_angled {
if char == '>' {
keystrokes.push(Keystroke::parse(&preprocess_keystrke(&angled)).log_err()?);
in_angled = false;
} else {
angled.push(char);
}
} else if char.to_ascii_lowercase() != char {
keystrokes.push(
Keystroke::parse(&format!("shift-{}", char.to_ascii_lowercase())).log_err()?,
)
} else if char == '<' {
in_angled = true;
angled = "".to_string();
} else {
keystrokes.push(Keystroke::parse(&format!("{}", char)).log_err()?)
}
}
if in_angled {
return None;
}
let keystrokes: Result<Vec<Keystroke>> = query
.chars()
.map(|char| {
let input = if char.to_ascii_lowercase() != char {
format!("shift-{}", char.to_ascii_lowercase())
} else {
char.to_string()
};
Ok(Keystroke::parse(&input)?)
})
.collect();
if let Ok(keystrokes) = keystrokes {
Some(Self { range, keystrokes }.boxed_clone())
// let zed_keystrokes = keystrokes.into_iter().map(|k| k.unparse()).join(" ");
// let mut action = workspace::SendKeystrokes(zed_keystrokes).boxed_clone();
// if let Some(range) = range.clone() {
// action = WithRange {
// restore_selection: false,
// range,
// action: WrappedAction(action),
// }
// .boxed_clone()
// }
// Some(action)
} else {
None
}
}
pub fn run(&self, vim: &mut Vim, cx: &mut ViewContext<Vim>) {
let zed_keystrokes = self.keystrokes.iter().map(|k| k.unparse()).join(" ");
let mut action = workspace::SendKeystrokes(zed_keystrokes).boxed_clone();
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ShellExec {
command: String,
@@ -1101,7 +1250,8 @@ impl Vim {
self.update_editor(cx, |_, editor, cx| {
editor.transact(cx, |editor, _| {
editor.clear_row_highlights::<ShellExec>();
})
});
editor.workspace()
});
}
}

View File

@@ -1846,49 +1846,59 @@ impl Workspace {
}
fn send_keystrokes(&mut self, action: &SendKeystrokes, cx: &mut ViewContext<Self>) {
let mut state = self.dispatching_keystrokes.borrow_mut();
if !state.0.insert(action.0.clone()) {
cx.propagate();
return;
}
let mut keystrokes: Vec<Keystroke> = action
let keystrokes: Vec<Keystroke> = action
.0
.split(' ')
.flat_map(|k| Keystroke::parse(k).log_err())
.collect();
self.send_keystrokes_impl(keystrokes, cx)
.detach_and_log_err(cx);
}
pub fn send_keystrokes_impl(
&mut self,
mut keystrokes: Vec<Keystroke>,
cx: &mut ViewContext<Self>,
) -> Task<Result<()>> {
keystrokes.reverse();
let key = keystrokes.iter().map(|k| k.unparse()).join(" ");
let mut state = self.dispatching_keystrokes.borrow_mut();
if !state.0.insert(key) {
cx.propagate();
return Task::ready(Ok(()));
}
state.1.append(&mut keystrokes);
drop(state);
let keystrokes = self.dispatching_keystrokes.clone();
cx.window_context()
.spawn(|mut cx| async move {
// limit to 100 keystrokes to avoid infinite recursion.
for _ in 0..100 {
let Some(keystroke) = keystrokes.borrow_mut().1.pop() else {
keystrokes.borrow_mut().0.clear();
return Ok(());
};
cx.update(|cx| {
let focused = cx.focused();
cx.dispatch_keystroke(keystroke.clone());
if cx.focused() != focused {
// dispatch_keystroke may cause the focus to change.
// draw's side effect is to schedule the FocusChanged events in the current flush effect cycle
// And we need that to happen before the next keystroke to keep vim mode happy...
// (Note that the tests always do this implicitly, so you must manually test with something like:
// "bindings": { "g z": ["workspace::SendKeystrokes", ": j <enter> u"]}
// )
cx.draw();
}
})?;
}
cx.window_context().spawn(|mut cx| async move {
// limit to 100 keystrokes to avoid infinite recursion.
for _ in 0..100 {
let Some(keystroke) = keystrokes.borrow_mut().1.pop() else {
keystrokes.borrow_mut().0.clear();
return Ok(());
};
cx.update(|cx| {
let focused = cx.focused();
cx.dispatch_keystroke(keystroke.clone());
if cx.focused() != focused {
// dispatch_keystroke may cause the focus to change.
// draw's side effect is to schedule the FocusChanged events in the current flush effect cycle
// And we need that to happen before the next keystroke to keep vim mode happy...
// (Note that the tests always do this implicitly, so you must manually test with something like:
// "bindings": { "g z": ["workspace::SendKeystrokes", ": j <enter> u"]}
// )
cx.draw();
}
})?;
}
*keystrokes.borrow_mut() = Default::default();
Err(anyhow!("over 100 keystrokes passed to send_keystrokes"))
})
.detach_and_log_err(cx);
*keystrokes.borrow_mut() = Default::default();
Err(anyhow!("over 100 keystrokes passed to send_keystrokes"))
})
}
fn save_all_internal(