Work on tests

This commit is contained in:
Conrad Irwin 2024-01-21 21:59:41 -07:00
parent 9d261cf859
commit 4143d3a36e
10 changed files with 181 additions and 35 deletions

View File

@ -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 {

View File

@ -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,
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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)]

View File

@ -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;
}

View File

@ -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>,

View File

@ -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() {

View 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"}}

View 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"}}