Compare commits
1 Commits
fix_devcon
...
vim-norm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdc996a87c |
@@ -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('-') {
|
||||
|
||||
@@ -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()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user