Compare commits
1 Commits
optional-c
...
vim-norm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdc996a87c |
@@ -94,7 +94,7 @@ impl Keystroke {
|
|||||||
"alt" => alt = true,
|
"alt" => alt = true,
|
||||||
"shift" => shift = true,
|
"shift" => shift = true,
|
||||||
"fn" => function = true,
|
"fn" => function = true,
|
||||||
"cmd" | "super" | "win" => platform = true,
|
"cmd" | "super" | "win" | "" => platform = true,
|
||||||
_ => {
|
_ => {
|
||||||
if let Some(next) = components.peek() {
|
if let Some(next) = components.peek() {
|
||||||
if next.is_empty() && source.ends_with('-') {
|
if next.is_empty() && source.ends_with('-') {
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ use editor::{
|
|||||||
Bias, Editor, ToPoint,
|
Bias, Editor, ToPoint,
|
||||||
};
|
};
|
||||||
use gpui::{
|
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 language::Point;
|
||||||
use multi_buffer::MultiBufferRow;
|
use multi_buffer::MultiBufferRow;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
@@ -78,6 +80,7 @@ impl_internal_actions!(
|
|||||||
WithRange,
|
WithRange,
|
||||||
WithCount,
|
WithCount,
|
||||||
OnMatchingLines,
|
OnMatchingLines,
|
||||||
|
NormalCommand,
|
||||||
ShellExec
|
ShellExec
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -238,6 +241,10 @@ pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
|
|||||||
|
|
||||||
Vim::action(editor, cx, |vim, action: &ShellExec, cx| {
|
Vim::action(editor, cx, |vim, action: &ShellExec, cx| {
|
||||||
action.run(vim, 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 {
|
} else {
|
||||||
None
|
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('!') {
|
} else if query.contains('!') {
|
||||||
ShellExec::parse(query, range.clone())
|
ShellExec::parse(query, range.clone())
|
||||||
} else {
|
} else {
|
||||||
@@ -1072,14 +1093,37 @@ impl OnMatchingLines {
|
|||||||
editor.change_selections(None, cx, |s| {
|
editor.change_selections(None, cx, |s| {
|
||||||
s.replace_cursors_with(|_| new_selections);
|
s.replace_cursors_with(|_| new_selections);
|
||||||
});
|
});
|
||||||
cx.dispatch_action(action);
|
|
||||||
cx.defer(move |editor, cx| {
|
if let Some(NormalCommand { keystrokes, .. }) =
|
||||||
let newest = editor.selections.newest::<Point>(cx).clone();
|
action.as_any().downcast_ref::<NormalCommand>()
|
||||||
editor.change_selections(None, cx, |s| {
|
{
|
||||||
s.select(vec![newest]);
|
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();
|
.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)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct ShellExec {
|
pub struct ShellExec {
|
||||||
command: String,
|
command: String,
|
||||||
@@ -1101,7 +1250,8 @@ impl Vim {
|
|||||||
self.update_editor(cx, |_, editor, cx| {
|
self.update_editor(cx, |_, editor, cx| {
|
||||||
editor.transact(cx, |editor, _| {
|
editor.transact(cx, |editor, _| {
|
||||||
editor.clear_row_highlights::<ShellExec>();
|
editor.clear_row_highlights::<ShellExec>();
|
||||||
})
|
});
|
||||||
|
editor.workspace()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1846,49 +1846,59 @@ impl Workspace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn send_keystrokes(&mut self, action: &SendKeystrokes, cx: &mut ViewContext<Self>) {
|
fn send_keystrokes(&mut self, action: &SendKeystrokes, cx: &mut ViewContext<Self>) {
|
||||||
let mut state = self.dispatching_keystrokes.borrow_mut();
|
let keystrokes: Vec<Keystroke> = action
|
||||||
if !state.0.insert(action.0.clone()) {
|
|
||||||
cx.propagate();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let mut keystrokes: Vec<Keystroke> = action
|
|
||||||
.0
|
.0
|
||||||
.split(' ')
|
.split(' ')
|
||||||
.flat_map(|k| Keystroke::parse(k).log_err())
|
.flat_map(|k| Keystroke::parse(k).log_err())
|
||||||
.collect();
|
.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();
|
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);
|
state.1.append(&mut keystrokes);
|
||||||
drop(state);
|
drop(state);
|
||||||
|
|
||||||
let keystrokes = self.dispatching_keystrokes.clone();
|
let keystrokes = self.dispatching_keystrokes.clone();
|
||||||
cx.window_context()
|
cx.window_context().spawn(|mut cx| async move {
|
||||||
.spawn(|mut cx| async move {
|
// limit to 100 keystrokes to avoid infinite recursion.
|
||||||
// limit to 100 keystrokes to avoid infinite recursion.
|
for _ in 0..100 {
|
||||||
for _ in 0..100 {
|
let Some(keystroke) = keystrokes.borrow_mut().1.pop() else {
|
||||||
let Some(keystroke) = keystrokes.borrow_mut().1.pop() else {
|
keystrokes.borrow_mut().0.clear();
|
||||||
keystrokes.borrow_mut().0.clear();
|
return Ok(());
|
||||||
return Ok(());
|
};
|
||||||
};
|
cx.update(|cx| {
|
||||||
cx.update(|cx| {
|
let focused = cx.focused();
|
||||||
let focused = cx.focused();
|
cx.dispatch_keystroke(keystroke.clone());
|
||||||
cx.dispatch_keystroke(keystroke.clone());
|
if cx.focused() != focused {
|
||||||
if cx.focused() != focused {
|
// dispatch_keystroke may cause the focus to change.
|
||||||
// dispatch_keystroke may cause the focus to change.
|
// draw's side effect is to schedule the FocusChanged events in the current flush effect cycle
|
||||||
// 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...
|
||||||
// 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:
|
||||||
// (Note that the tests always do this implicitly, so you must manually test with something like:
|
// "bindings": { "g z": ["workspace::SendKeystrokes", ": j <enter> u"]}
|
||||||
// "bindings": { "g z": ["workspace::SendKeystrokes", ": j <enter> u"]}
|
// )
|
||||||
// )
|
cx.draw();
|
||||||
cx.draw();
|
}
|
||||||
}
|
})?;
|
||||||
})?;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
*keystrokes.borrow_mut() = Default::default();
|
*keystrokes.borrow_mut() = Default::default();
|
||||||
Err(anyhow!("over 100 keystrokes passed to send_keystrokes"))
|
Err(anyhow!("over 100 keystrokes passed to send_keystrokes"))
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_all_internal(
|
fn save_all_internal(
|
||||||
|
|||||||
Reference in New Issue
Block a user