Vim: enable sending multiple keystrokes from custom keybinding (#7965)
Release Notes: - Added `workspace::SendKeystrokes` to enable mapping from one key to a sequence of others ([#7033](https://github.com/zed-industries/zed/issues/7033)). Improves #7033. Big thank you to @ConradIrwin who did most of the heavy lifting on this one. This PR allows the user to send multiple keystrokes via custom keybinding. For example, the following keybinding would go down four lines and then right four characters. ```json [ { "context": "Editor && VimControl && !VimWaiting && !menu", "bindings": { "g z": [ "workspace::SendKeystrokes", "j j j j l l l l" ], } } ] ``` --------- Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
parent
8f5d7db875
commit
8a73bc4c7d
13 changed files with 343 additions and 157 deletions
|
@ -39,7 +39,7 @@ use std::any::{Any, TypeId};
|
|||
/// }
|
||||
/// register_action!(Paste);
|
||||
/// ```
|
||||
pub trait Action: 'static {
|
||||
pub trait Action: 'static + Send {
|
||||
/// Clone the action into a new box
|
||||
fn boxed_clone(&self) -> Box<dyn Action>;
|
||||
|
||||
|
|
|
@ -335,7 +335,7 @@ impl TestAppContext {
|
|||
.map(Keystroke::parse)
|
||||
.map(Result::unwrap)
|
||||
{
|
||||
self.dispatch_keystroke(window, keystroke.into(), false);
|
||||
self.dispatch_keystroke(window, keystroke.into());
|
||||
}
|
||||
|
||||
self.background_executor.run_until_parked()
|
||||
|
@ -347,21 +347,16 @@ impl TestAppContext {
|
|||
/// This will also run the background executor until it's parked.
|
||||
pub fn simulate_input(&mut self, window: AnyWindowHandle, input: &str) {
|
||||
for keystroke in input.split("").map(Keystroke::parse).map(Result::unwrap) {
|
||||
self.dispatch_keystroke(window, keystroke.into(), false);
|
||||
self.dispatch_keystroke(window, keystroke.into());
|
||||
}
|
||||
|
||||
self.background_executor.run_until_parked()
|
||||
}
|
||||
|
||||
/// dispatches a single Keystroke (see also `simulate_keystrokes` and `simulate_input`)
|
||||
pub fn dispatch_keystroke(
|
||||
&mut self,
|
||||
window: AnyWindowHandle,
|
||||
keystroke: Keystroke,
|
||||
is_held: bool,
|
||||
) {
|
||||
self.test_window(window)
|
||||
.simulate_keystroke(keystroke, is_held)
|
||||
pub fn dispatch_keystroke(&mut self, window: AnyWindowHandle, keystroke: Keystroke) {
|
||||
self.update_window(window, |_, cx| cx.dispatch_keystroke(keystroke))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Returns the `TestWindow` backing the given handle.
|
||||
|
|
|
@ -491,8 +491,8 @@ mod test {
|
|||
.update(cx, |test_view, cx| cx.focus(&test_view.focus_handle))
|
||||
.unwrap();
|
||||
|
||||
cx.dispatch_keystroke(*window, Keystroke::parse("a").unwrap(), false);
|
||||
cx.dispatch_keystroke(*window, Keystroke::parse("ctrl-g").unwrap(), false);
|
||||
cx.dispatch_keystroke(*window, Keystroke::parse("a").unwrap());
|
||||
cx.dispatch_keystroke(*window, Keystroke::parse("ctrl-g").unwrap());
|
||||
|
||||
window
|
||||
.update(cx, |test_view, _| {
|
||||
|
|
|
@ -412,7 +412,7 @@ impl PlatformInputHandler {
|
|||
.flatten()
|
||||
}
|
||||
|
||||
pub(crate) fn flush_pending_input(&mut self, input: &str, cx: &mut WindowContext) {
|
||||
pub(crate) fn dispatch_input(&mut self, input: &str, cx: &mut WindowContext) {
|
||||
self.handler.replace_text_in_range(None, input, cx);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
px, AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, KeyDownEvent, Keystroke,
|
||||
Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
|
||||
Point, Size, TestPlatform, TileId, WindowAppearance, WindowBounds, WindowOptions,
|
||||
px, AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, Pixels, PlatformAtlas,
|
||||
PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, Size,
|
||||
TestPlatform, TileId, WindowAppearance, WindowBounds, WindowOptions,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use parking_lot::Mutex;
|
||||
|
@ -112,41 +112,6 @@ impl TestWindow {
|
|||
self.0.lock().input_callback = Some(callback);
|
||||
result
|
||||
}
|
||||
|
||||
pub fn simulate_keystroke(&mut self, mut keystroke: Keystroke, is_held: bool) {
|
||||
if keystroke.ime_key.is_none()
|
||||
&& !keystroke.modifiers.command
|
||||
&& !keystroke.modifiers.control
|
||||
&& !keystroke.modifiers.function
|
||||
{
|
||||
keystroke.ime_key = Some(if keystroke.modifiers.shift {
|
||||
keystroke.key.to_ascii_uppercase().clone()
|
||||
} else {
|
||||
keystroke.key.clone()
|
||||
})
|
||||
}
|
||||
|
||||
if self.simulate_input(PlatformInput::KeyDown(KeyDownEvent {
|
||||
keystroke: keystroke.clone(),
|
||||
is_held,
|
||||
})) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut lock = self.0.lock();
|
||||
let Some(mut input_handler) = lock.input_handler.take() else {
|
||||
panic!(
|
||||
"simulate_keystroke {:?} input event was not handled and there was no active input",
|
||||
&keystroke
|
||||
);
|
||||
};
|
||||
drop(lock);
|
||||
if let Some(text) = keystroke.ime_key.as_ref() {
|
||||
input_handler.replace_text_in_range(None, &text);
|
||||
}
|
||||
|
||||
self.0.lock().input_handler = Some(input_handler);
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformWindow for TestWindow {
|
||||
|
|
|
@ -950,7 +950,7 @@ impl<'a> WindowContext<'a> {
|
|||
|
||||
/// Produces a new frame and assigns it to `rendered_frame`. To actually show
|
||||
/// the contents of the new [Scene], use [present].
|
||||
pub(crate) fn draw(&mut self) {
|
||||
pub fn draw(&mut self) {
|
||||
self.window.dirty.set(false);
|
||||
self.window.drawing = true;
|
||||
|
||||
|
@ -1099,6 +1099,38 @@ impl<'a> WindowContext<'a> {
|
|||
self.window.needs_present.set(false);
|
||||
}
|
||||
|
||||
/// Dispatch a given keystroke as though the user had typed it.
|
||||
/// You can create a keystroke with Keystroke::parse("").
|
||||
pub fn dispatch_keystroke(&mut self, mut keystroke: Keystroke) -> bool {
|
||||
if keystroke.ime_key.is_none()
|
||||
&& !keystroke.modifiers.command
|
||||
&& !keystroke.modifiers.control
|
||||
&& !keystroke.modifiers.function
|
||||
{
|
||||
keystroke.ime_key = Some(if keystroke.modifiers.shift {
|
||||
keystroke.key.to_uppercase().clone()
|
||||
} else {
|
||||
keystroke.key.clone()
|
||||
})
|
||||
}
|
||||
if self.dispatch_event(PlatformInput::KeyDown(KeyDownEvent {
|
||||
keystroke: keystroke.clone(),
|
||||
is_held: false,
|
||||
})) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if let Some(input) = keystroke.ime_key {
|
||||
if let Some(mut input_handler) = self.window.platform_window.take_input_handler() {
|
||||
input_handler.dispatch_input(&input, self);
|
||||
self.window.platform_window.set_input_handler(input_handler);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Dispatch a mouse or keyboard event on the window.
|
||||
pub fn dispatch_event(&mut self, event: PlatformInput) -> bool {
|
||||
self.window.last_input_timestamp.set(Instant::now());
|
||||
|
@ -1423,7 +1455,7 @@ impl<'a> WindowContext<'a> {
|
|||
|
||||
if !input.is_empty() {
|
||||
if let Some(mut input_handler) = self.window.platform_window.take_input_handler() {
|
||||
input_handler.flush_pending_input(&input, self);
|
||||
input_handler.dispatch_input(&input, self);
|
||||
self.window.platform_window.set_input_handler(input_handler)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue