1use std::ops::Deref;
2
3use editor::{display_map::ToDisplayPoint, DisplayPoint};
4use gpui::{json::json, keymap::Keystroke, ViewHandle};
5use language::{Point, Selection};
6use workspace::{WorkspaceHandle, WorkspaceParams};
7
8use crate::*;
9
10#[gpui::test]
11async fn test_insert_mode(cx: &mut gpui::TestAppContext) {
12 let mut cx = VimTestAppContext::new(cx, "").await;
13 assert_eq!(cx.mode(), Mode::Normal);
14 cx.simulate_keystroke("i");
15 assert_eq!(cx.mode(), Mode::Insert);
16 cx.simulate_keystrokes(&["T", "e", "s", "t"]);
17 assert_eq!(cx.editor_text(), "Test".to_owned());
18 cx.simulate_keystroke("escape");
19 assert_eq!(cx.mode(), Mode::Normal);
20}
21
22#[gpui::test]
23async fn test_normal_hjkl(cx: &mut gpui::TestAppContext) {
24 let mut cx = VimTestAppContext::new(cx, "Test\nTestTest\nTest").await;
25 assert_eq!(cx.mode(), Mode::Normal);
26 cx.simulate_keystroke("l");
27 assert_eq!(cx.newest_selection().head(), DisplayPoint::new(0, 1));
28 cx.simulate_keystroke("h");
29 assert_eq!(cx.newest_selection().head(), DisplayPoint::new(0, 0));
30 cx.simulate_keystroke("j");
31 assert_eq!(cx.newest_selection().head(), DisplayPoint::new(1, 0));
32 cx.simulate_keystroke("k");
33 assert_eq!(cx.newest_selection().head(), DisplayPoint::new(0, 0));
34
35 cx.simulate_keystroke("j");
36 assert_eq!(cx.newest_selection().head(), DisplayPoint::new(1, 0));
37
38 // When moving left, cursor does not wrap to the previous line
39 cx.simulate_keystroke("h");
40 assert_eq!(cx.newest_selection().head(), DisplayPoint::new(1, 0));
41
42 // When moving right, cursor does not reach the line end or wrap to the next line
43 for _ in 0..9 {
44 cx.simulate_keystroke("l");
45 }
46 assert_eq!(cx.newest_selection().head(), DisplayPoint::new(1, 7));
47
48 // Goal column respects the inability to reach the end of the line
49 cx.simulate_keystroke("k");
50 assert_eq!(cx.newest_selection().head(), DisplayPoint::new(0, 3));
51 cx.simulate_keystroke("j");
52 assert_eq!(cx.newest_selection().head(), DisplayPoint::new(1, 7));
53}
54
55struct VimTestAppContext<'a> {
56 cx: &'a mut gpui::TestAppContext,
57 window_id: usize,
58 editor: ViewHandle<Editor>,
59}
60
61impl<'a> VimTestAppContext<'a> {
62 async fn new(
63 cx: &'a mut gpui::TestAppContext,
64 initial_editor_text: &str,
65 ) -> VimTestAppContext<'a> {
66 cx.update(|cx| {
67 editor::init(cx);
68 crate::init(cx);
69 });
70 let params = cx.update(WorkspaceParams::test);
71 params
72 .fs
73 .as_fake()
74 .insert_tree(
75 "/root",
76 json!({ "dir": { "test.txt": initial_editor_text } }),
77 )
78 .await;
79
80 let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx));
81 params
82 .project
83 .update(cx, |project, cx| {
84 project.find_or_create_local_worktree("/root", true, cx)
85 })
86 .await
87 .unwrap();
88 cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
89 .await;
90
91 let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
92 let item = workspace
93 .update(cx, |workspace, cx| workspace.open_path(file, cx))
94 .await
95 .expect("Could not open test file");
96
97 let editor = cx.update(|cx| {
98 item.act_as::<Editor>(cx)
99 .expect("Opened test file wasn't an editor")
100 });
101 editor.update(cx, |_, cx| cx.focus_self());
102
103 Self {
104 cx,
105 window_id,
106 editor,
107 }
108 }
109
110 fn newest_selection(&mut self) -> Selection<DisplayPoint> {
111 self.editor.update(self.cx, |editor, cx| {
112 let snapshot = editor.snapshot(cx);
113 editor
114 .newest_selection::<Point>(cx)
115 .map(|point| point.to_display_point(&snapshot.display_snapshot))
116 })
117 }
118
119 fn mode(&mut self) -> Mode {
120 self.cx.update(|cx| cx.global::<VimState>().mode)
121 }
122
123 fn editor_text(&mut self) -> String {
124 self.editor
125 .update(self.cx, |editor, cx| editor.snapshot(cx).text())
126 }
127
128 fn simulate_keystroke(&mut self, keystroke_text: &str) {
129 let keystroke = Keystroke::parse(keystroke_text).unwrap();
130 let input = if keystroke.modified() {
131 None
132 } else {
133 Some(keystroke.key.clone())
134 };
135 self.cx
136 .dispatch_keystroke(self.window_id, keystroke, input, false);
137 }
138
139 fn simulate_keystrokes(&mut self, keystroke_texts: &[&str]) {
140 for keystroke_text in keystroke_texts.into_iter() {
141 self.simulate_keystroke(keystroke_text);
142 }
143 }
144}
145
146impl<'a> Deref for VimTestAppContext<'a> {
147 type Target = gpui::TestAppContext;
148
149 fn deref(&self) -> &Self::Target {
150 self.cx
151 }
152}