mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-08 07:35:01 +03:00
Work on tests
This commit is contained in:
parent
9d261cf859
commit
4143d3a36e
@ -277,7 +277,7 @@ impl DispatchTree {
|
||||
keystroke: &Keystroke,
|
||||
dispatch_path: &SmallVec<[DispatchNodeId; 32]>,
|
||||
) -> KeymatchResult {
|
||||
let mut actions = SmallVec::new();
|
||||
let mut bindings = SmallVec::new();
|
||||
let mut pending = false;
|
||||
|
||||
let mut context_stack: SmallVec<[KeyContext; 4]> = SmallVec::new();
|
||||
@ -297,11 +297,11 @@ impl DispatchTree {
|
||||
|
||||
let mut result = keystroke_matcher.match_keystroke(keystroke, &context_stack);
|
||||
pending = result.pending || pending;
|
||||
actions.append(&mut result.actions);
|
||||
bindings.append(&mut result.bindings);
|
||||
context_stack.pop();
|
||||
}
|
||||
|
||||
KeymatchResult { actions, pending }
|
||||
KeymatchResult { bindings, pending }
|
||||
}
|
||||
|
||||
pub fn has_pending_keystrokes(&self) -> bool {
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::{Action, KeyContext, Keymap, KeymapVersion, Keystroke};
|
||||
use crate::{KeyBinding, KeyContext, Keymap, KeymapVersion, Keystroke};
|
||||
use parking_lot::Mutex;
|
||||
use smallvec::SmallVec;
|
||||
use std::sync::Arc;
|
||||
@ -10,7 +10,7 @@ pub(crate) struct KeystrokeMatcher {
|
||||
}
|
||||
|
||||
pub struct KeymatchResult {
|
||||
pub actions: SmallVec<[Box<dyn Action>; 1]>,
|
||||
pub bindings: SmallVec<[KeyBinding; 1]>,
|
||||
pub pending: bool,
|
||||
}
|
||||
|
||||
@ -24,10 +24,6 @@ impl KeystrokeMatcher {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_pending(&mut self) {
|
||||
self.pending_keystrokes.clear();
|
||||
}
|
||||
|
||||
pub fn has_pending_keystrokes(&self) -> bool {
|
||||
!self.pending_keystrokes.is_empty()
|
||||
}
|
||||
@ -54,7 +50,7 @@ impl KeystrokeMatcher {
|
||||
}
|
||||
|
||||
let mut pending_key = None;
|
||||
let mut actions = SmallVec::new();
|
||||
let mut bindings = SmallVec::new();
|
||||
|
||||
for binding in keymap.bindings().rev() {
|
||||
if !keymap.binding_enabled(binding, context_stack) {
|
||||
@ -65,7 +61,7 @@ impl KeystrokeMatcher {
|
||||
self.pending_keystrokes.push(candidate.clone());
|
||||
match binding.match_keystrokes(&self.pending_keystrokes) {
|
||||
KeyMatch::Matched => {
|
||||
actions.push(binding.action.boxed_clone());
|
||||
bindings.push(binding.clone());
|
||||
}
|
||||
KeyMatch::Pending => {
|
||||
pending_key.get_or_insert(candidate);
|
||||
@ -76,6 +72,12 @@ impl KeystrokeMatcher {
|
||||
}
|
||||
}
|
||||
|
||||
if bindings.len() == 0 && pending_key.is_none() && self.pending_keystrokes.len() > 0 {
|
||||
drop(keymap);
|
||||
self.pending_keystrokes.remove(0);
|
||||
return self.match_keystroke(keystroke, context_stack);
|
||||
}
|
||||
|
||||
let pending = if let Some(pending_key) = pending_key {
|
||||
self.pending_keystrokes.push(pending_key);
|
||||
true
|
||||
@ -84,7 +86,7 @@ impl KeystrokeMatcher {
|
||||
false
|
||||
};
|
||||
|
||||
KeymatchResult { actions, pending }
|
||||
KeymatchResult { bindings, pending }
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,4 +100,3 @@ pub enum KeyMatch {
|
||||
Pending,
|
||||
Matched,
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
use crate::{
|
||||
px, AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, KeyDownEvent, Keystroke,
|
||||
Pixels, Platform, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler,
|
||||
PlatformWindow, Point, Size, TestPlatform, TileId, WindowAppearance, WindowBounds,
|
||||
WindowOptions,
|
||||
Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
|
||||
Point, Size, TestPlatform, TileId, WindowAppearance, WindowBounds, WindowOptions,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use parking_lot::Mutex;
|
||||
@ -97,7 +96,19 @@ impl TestWindow {
|
||||
result
|
||||
}
|
||||
|
||||
pub fn simulate_keystroke(&mut self, keystroke: Keystroke, is_held: bool) {
|
||||
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,
|
||||
@ -113,8 +124,9 @@ impl TestWindow {
|
||||
);
|
||||
};
|
||||
drop(lock);
|
||||
let text = keystroke.ime_key.unwrap_or(keystroke.key);
|
||||
input_handler.replace_text_in_range(None, &text);
|
||||
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);
|
||||
}
|
||||
|
@ -289,10 +289,10 @@ pub struct Window {
|
||||
pub(crate) focus_invalidated: bool,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Debug)]
|
||||
struct PendingInput {
|
||||
text: String,
|
||||
actions: SmallVec<[Box<dyn Action>; 1]>,
|
||||
bindings: SmallVec<[KeyBinding; 1]>,
|
||||
focus: Option<FocusId>,
|
||||
timer: Option<Task<()>>,
|
||||
}
|
||||
@ -1796,7 +1796,7 @@ impl<'a> WindowContext<'a> {
|
||||
.dispatch_path(node_id);
|
||||
|
||||
if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
|
||||
let KeymatchResult { actions, pending } = self
|
||||
let KeymatchResult { bindings, pending } = self
|
||||
.window
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
@ -1812,8 +1812,8 @@ impl<'a> WindowContext<'a> {
|
||||
if let Some(new_text) = &key_down_event.keystroke.ime_key.as_ref() {
|
||||
currently_pending.text += new_text
|
||||
}
|
||||
for action in actions {
|
||||
currently_pending.actions.push(action);
|
||||
for binding in bindings {
|
||||
currently_pending.bindings.push(binding);
|
||||
}
|
||||
|
||||
currently_pending.timer = Some(self.spawn(|mut cx| async move {
|
||||
@ -1832,20 +1832,30 @@ impl<'a> WindowContext<'a> {
|
||||
self.propagate_event = false;
|
||||
return;
|
||||
} else if let Some(currently_pending) = self.window.pending_input.take() {
|
||||
if actions.is_empty() {
|
||||
// if you have bound , to one thing, and ,w to another.
|
||||
// then typing ,i should trigger the comma actions, then the i actions.
|
||||
// in that scenario "binding.keystrokes" is "i" and "pending.keystrokes" is ",".
|
||||
// on the other hand if you type ,, it should not trigger the , action.
|
||||
// in that scenario "binding.keystrokes" is ",w" and "pending.keystrokes" is ",".
|
||||
|
||||
if bindings.iter().all(|binding| {
|
||||
currently_pending.bindings.iter().all(|pending| {
|
||||
dbg!(!dbg!(binding.keystrokes()).starts_with(dbg!(&pending.keystrokes)))
|
||||
})
|
||||
}) {
|
||||
self.replay_pending_input(currently_pending)
|
||||
}
|
||||
}
|
||||
|
||||
if !actions.is_empty() {
|
||||
if !bindings.is_empty() {
|
||||
self.clear_pending_keystrokes();
|
||||
}
|
||||
|
||||
self.propagate_event = true;
|
||||
for action in actions {
|
||||
self.dispatch_action_on_node(node_id, action.boxed_clone());
|
||||
for binding in bindings {
|
||||
self.dispatch_action_on_node(node_id, binding.action.boxed_clone());
|
||||
if !self.propagate_event {
|
||||
self.dispatch_keystroke_observers(event, Some(action));
|
||||
self.dispatch_keystroke_observers(event, Some(binding.action));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -1903,8 +1913,8 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
|
||||
self.propagate_event = true;
|
||||
for action in currently_pending.actions {
|
||||
self.dispatch_action_on_node(node_id, action);
|
||||
for binding in currently_pending.bindings {
|
||||
self.dispatch_action_on_node(node_id, binding.action.boxed_clone());
|
||||
if !self.propagate_event {
|
||||
return;
|
||||
}
|
||||
|
@ -73,9 +73,9 @@ pub(crate) struct Up {
|
||||
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Down {
|
||||
pub(crate) struct Down {
|
||||
#[serde(default)]
|
||||
display_lines: bool,
|
||||
pub(crate) display_lines: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, PartialEq)]
|
||||
|
@ -3,8 +3,11 @@ mod neovim_backed_test_context;
|
||||
mod neovim_connection;
|
||||
mod vim_test_context;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use command_palette::CommandPalette;
|
||||
use editor::DisplayPoint;
|
||||
use gpui::{Action, KeyBinding};
|
||||
pub use neovim_backed_binding_test_context::*;
|
||||
pub use neovim_backed_test_context::*;
|
||||
pub use vim_test_context::*;
|
||||
@ -12,7 +15,7 @@ pub use vim_test_context::*;
|
||||
use indoc::indoc;
|
||||
use search::BufferSearchBar;
|
||||
|
||||
use crate::{state::Mode, ModeIndicator};
|
||||
use crate::{insert::NormalBefore, motion, normal::InsertLineBelow, state::Mode, ModeIndicator};
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_initially_disabled(cx: &mut gpui::TestAppContext) {
|
||||
@ -774,3 +777,73 @@ async fn test_select_all_issue_2170(cx: &mut gpui::TestAppContext) {
|
||||
Mode::Visual,
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_jk(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
||||
cx.update(|cx| {
|
||||
cx.bind_keys([KeyBinding::new(
|
||||
"j k",
|
||||
NormalBefore,
|
||||
Some("vim_mode == insert"),
|
||||
)])
|
||||
});
|
||||
cx.neovim.exec("imap jk <esc>").await;
|
||||
|
||||
cx.set_shared_state("ˇhello").await;
|
||||
cx.simulate_shared_keystrokes(["i", "j", "o", "j", "k"])
|
||||
.await;
|
||||
cx.assert_shared_state("jˇohello").await;
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_jk_delay(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = VimTestContext::new(cx, true).await;
|
||||
|
||||
cx.update(|cx| {
|
||||
cx.bind_keys([KeyBinding::new(
|
||||
"j k",
|
||||
NormalBefore,
|
||||
Some("vim_mode == insert"),
|
||||
)])
|
||||
});
|
||||
|
||||
cx.set_state("ˇhello", Mode::Normal);
|
||||
cx.simulate_keystrokes(["i", "j"]);
|
||||
cx.executor().advance_clock(Duration::from_millis(500));
|
||||
cx.run_until_parked();
|
||||
cx.assert_state("ˇhello", Mode::Insert);
|
||||
cx.executor().advance_clock(Duration::from_millis(500));
|
||||
cx.run_until_parked();
|
||||
cx.assert_state("jˇhello", Mode::Insert);
|
||||
cx.simulate_keystrokes(["k", "j", "k"]);
|
||||
cx.assert_state("jˇkhello", Mode::Normal);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_comma_w(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
||||
cx.update(|cx| {
|
||||
cx.bind_keys([KeyBinding::new(
|
||||
", w",
|
||||
motion::Down {
|
||||
display_lines: false,
|
||||
},
|
||||
Some("vim_mode == normal"),
|
||||
)])
|
||||
});
|
||||
cx.neovim.exec("map ,w j").await;
|
||||
|
||||
cx.set_shared_state("ˇhello hello\nhello hello").await;
|
||||
cx.simulate_shared_keystrokes(["f", "o", ";", ",", "w"])
|
||||
.await;
|
||||
cx.assert_shared_state("hello hello\nhello hellˇo").await;
|
||||
|
||||
cx.set_shared_state("ˇhello hello\nhello hello").await;
|
||||
cx.simulate_shared_keystrokes(["f", "o", ";", ",", "i"])
|
||||
.await;
|
||||
cx.assert_shared_state("hellˇo hello\nhello hello").await;
|
||||
cx.assert_shared_mode(Mode::Insert).await;
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ pub struct NeovimBackedTestContext {
|
||||
// Lookup for exempted assertions. Keyed by the insertion text, and with a value indicating which
|
||||
// bindings are exempted. If None, all bindings are ignored for that insertion text.
|
||||
exemptions: HashMap<String, Option<HashSet<String>>>,
|
||||
neovim: NeovimConnection,
|
||||
pub(crate) neovim: NeovimConnection,
|
||||
|
||||
last_set_state: Option<String>,
|
||||
recent_keystrokes: Vec<String>,
|
||||
|
@ -42,6 +42,7 @@ pub enum NeovimData {
|
||||
Key(String),
|
||||
Get { state: String, mode: Option<Mode> },
|
||||
ReadRegister { name: char, value: String },
|
||||
Exec { command: String },
|
||||
SetOption { value: String },
|
||||
}
|
||||
|
||||
@ -269,6 +270,32 @@ impl NeovimConnection {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "neovim")]
|
||||
pub async fn exec(&mut self, value: &str) {
|
||||
self.nvim
|
||||
.command_output(format!("{}", value).as_str())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
self.data.push_back(NeovimData::Exec {
|
||||
command: value.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "neovim"))]
|
||||
pub async fn exec(&mut self, value: &str) {
|
||||
if let Some(NeovimData::Get { .. }) = self.data.front() {
|
||||
self.data.pop_front();
|
||||
};
|
||||
assert_eq!(
|
||||
self.data.pop_front(),
|
||||
Some(NeovimData::Exec {
|
||||
command: value.to_string(),
|
||||
}),
|
||||
"operation does not match recorded script. re-record with --features=neovim"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "neovim"))]
|
||||
pub async fn read_register(&mut self, register: char) -> String {
|
||||
if let Some(NeovimData::Get { .. }) = self.data.front() {
|
||||
|
15
crates/vim/test_data/test_comma_w.json
Normal file
15
crates/vim/test_data/test_comma_w.json
Normal file
@ -0,0 +1,15 @@
|
||||
{"Exec":{"command":"map ,w j"}}
|
||||
{"Put":{"state":"ˇhello hello\nhello hello"}}
|
||||
{"Key":"f"}
|
||||
{"Key":"o"}
|
||||
{"Key":";"}
|
||||
{"Key":","}
|
||||
{"Key":"w"}
|
||||
{"Get":{"state":"hello hello\nhello hellˇo","mode":"Normal"}}
|
||||
{"Put":{"state":"ˇhello hello\nhello hello"}}
|
||||
{"Key":"f"}
|
||||
{"Key":"o"}
|
||||
{"Key":";"}
|
||||
{"Key":","}
|
||||
{"Key":"i"}
|
||||
{"Get":{"state":"hellˇo hello\nhello hello","mode":"Insert"}}
|
8
crates/vim/test_data/test_jk.json
Normal file
8
crates/vim/test_data/test_jk.json
Normal file
@ -0,0 +1,8 @@
|
||||
{"Exec":{"command":"imap jk <esc>"}}
|
||||
{"Put":{"state":"ˇhello"}}
|
||||
{"Key":"i"}
|
||||
{"Key":"j"}
|
||||
{"Key":"o"}
|
||||
{"Key":"j"}
|
||||
{"Key":"k"}
|
||||
{"Get":{"state":"jˇohello","mode":"Normal"}}
|
Loading…
Reference in New Issue
Block a user