Re-entrant SendKeystrokes (#20277)

Release Notes:

- Improved `workspace::SendKeystrokes` to support re-binding keys. For
example you can now do: `"x": ["workspace::SendKeystrokes", "\" _ x"]`
in vim mode to ensure that `x` does not clobber your clipboard.
- Improved key binding documentation
This commit is contained in:
Conrad Irwin 2024-11-05 21:18:16 -07:00 committed by GitHub
parent 50069a2153
commit 38b1940251
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 137 additions and 475 deletions

View file

@ -1403,6 +1403,32 @@ async fn test_remap_nested_pineapple(cx: &mut gpui::TestAppContext) {
cx.shared_state().await.assert_eq("🍍ˇ");
}
#[gpui::test]
async fn test_remap_recursion(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.update(|cx| {
cx.bind_keys([KeyBinding::new(
"x",
workspace::SendKeystrokes("\" _ x".to_string()),
Some("VimControl"),
)]);
cx.bind_keys([KeyBinding::new(
"y",
workspace::SendKeystrokes("2 x".to_string()),
Some("VimControl"),
)])
});
cx.neovim.exec("noremap x \"_x").await;
cx.neovim.exec("map y 2x").await;
cx.set_shared_state("ˇhello").await;
cx.simulate_shared_keystrokes("d l").await;
cx.shared_clipboard().await.assert_eq("h");
cx.simulate_shared_keystrokes("y").await;
cx.shared_clipboard().await.assert_eq("h");
cx.shared_state().await.assert_eq("ˇlo");
}
#[gpui::test]
async fn test_escape_while_waiting(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;

View file

@ -0,0 +1,11 @@
{"Exec":{"command":"noremap x \"_x"}}
{"Exec":{"command":"map y 2x"}}
{"Put":{"state":"ˇhello"}}
{"Key":"d"}
{"Key":"l"}
{"Get":{"state":"ˇello","mode":"Normal"}}
{"ReadRegister":{"name":"\"","value":"h"}}
{"Key":"y"}
{"Get":{"state":"ˇlo","mode":"Normal"}}
{"ReadRegister":{"name":"\"","value":"h"}}
{"Get":{"state":"ˇlo","mode":"Normal"}}

View file

@ -752,7 +752,7 @@ pub struct Workspace {
leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
database_id: Option<WorkspaceId>,
app_state: Arc<AppState>,
dispatching_keystrokes: Rc<RefCell<Vec<Keystroke>>>,
dispatching_keystrokes: Rc<RefCell<(HashSet<String>, Vec<Keystroke>)>>,
_subscriptions: Vec<Subscription>,
_apply_leader_updates: Task<Result<()>>,
_observe_current_user: Task<Result<()>>,
@ -1784,6 +1784,11 @@ 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
.0
.split(' ')
@ -1791,16 +1796,16 @@ impl Workspace {
.collect();
keystrokes.reverse();
self.dispatching_keystrokes
.borrow_mut()
.append(&mut keystrokes);
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().pop() else {
let Some(keystroke) = keystrokes.borrow_mut().1.pop() else {
keystrokes.borrow_mut().0.clear();
return Ok(());
};
cx.update(|cx| {
@ -1817,7 +1822,8 @@ impl Workspace {
}
})?;
}
keystrokes.borrow_mut().clear();
*keystrokes.borrow_mut() = Default::default();
Err(anyhow!("over 100 keystrokes passed to send_keystrokes"))
})
.detach_and_log_err(cx);