1use super::*;
2use crate::test::{
3 assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
4 editor_test_context::EditorTestContext, select_ranges,
5};
6use drag_and_drop::DragAndDrop;
7use futures::StreamExt;
8use gpui::{
9 executor::Deterministic,
10 geometry::{rect::RectF, vector::vec2f},
11 platform::{WindowBounds, WindowOptions},
12 serde_json::{self, json},
13 TestAppContext,
14};
15use indoc::indoc;
16use language::{
17 language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent},
18 BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageRegistry, Point,
19};
20use parking_lot::Mutex;
21use project::FakeFs;
22use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
23use unindent::Unindent;
24use util::{
25 assert_set_eq,
26 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
27};
28use workspace::{
29 item::{FollowableItem, Item, ItemHandle},
30 NavigationEntry, ViewId,
31};
32
33#[gpui::test]
34fn test_edit_events(cx: &mut TestAppContext) {
35 init_test(cx, |_| {});
36
37 let buffer = cx.add_model(|cx| {
38 let mut buffer = language::Buffer::new(0, "123456", cx);
39 buffer.set_group_interval(Duration::from_secs(1));
40 buffer
41 });
42
43 let events = Rc::new(RefCell::new(Vec::new()));
44 let (_, editor1) = cx.add_window({
45 let events = events.clone();
46 |cx| {
47 cx.subscribe(&cx.handle(), move |_, _, event, _| {
48 if matches!(
49 event,
50 Event::Edited | Event::BufferEdited | Event::DirtyChanged
51 ) {
52 events.borrow_mut().push(("editor1", event.clone()));
53 }
54 })
55 .detach();
56 Editor::for_buffer(buffer.clone(), None, cx)
57 }
58 });
59 let (_, editor2) = cx.add_window({
60 let events = events.clone();
61 |cx| {
62 cx.subscribe(&cx.handle(), move |_, _, event, _| {
63 if matches!(
64 event,
65 Event::Edited | Event::BufferEdited | Event::DirtyChanged
66 ) {
67 events.borrow_mut().push(("editor2", event.clone()));
68 }
69 })
70 .detach();
71 Editor::for_buffer(buffer.clone(), None, cx)
72 }
73 });
74 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
75
76 // Mutating editor 1 will emit an `Edited` event only for that editor.
77 editor1.update(cx, |editor, cx| editor.insert("X", cx));
78 assert_eq!(
79 mem::take(&mut *events.borrow_mut()),
80 [
81 ("editor1", Event::Edited),
82 ("editor1", Event::BufferEdited),
83 ("editor2", Event::BufferEdited),
84 ("editor1", Event::DirtyChanged),
85 ("editor2", Event::DirtyChanged)
86 ]
87 );
88
89 // Mutating editor 2 will emit an `Edited` event only for that editor.
90 editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
91 assert_eq!(
92 mem::take(&mut *events.borrow_mut()),
93 [
94 ("editor2", Event::Edited),
95 ("editor1", Event::BufferEdited),
96 ("editor2", Event::BufferEdited),
97 ]
98 );
99
100 // Undoing on editor 1 will emit an `Edited` event only for that editor.
101 editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
102 assert_eq!(
103 mem::take(&mut *events.borrow_mut()),
104 [
105 ("editor1", Event::Edited),
106 ("editor1", Event::BufferEdited),
107 ("editor2", Event::BufferEdited),
108 ("editor1", Event::DirtyChanged),
109 ("editor2", Event::DirtyChanged),
110 ]
111 );
112
113 // Redoing on editor 1 will emit an `Edited` event only for that editor.
114 editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
115 assert_eq!(
116 mem::take(&mut *events.borrow_mut()),
117 [
118 ("editor1", Event::Edited),
119 ("editor1", Event::BufferEdited),
120 ("editor2", Event::BufferEdited),
121 ("editor1", Event::DirtyChanged),
122 ("editor2", Event::DirtyChanged),
123 ]
124 );
125
126 // Undoing on editor 2 will emit an `Edited` event only for that editor.
127 editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
128 assert_eq!(
129 mem::take(&mut *events.borrow_mut()),
130 [
131 ("editor2", Event::Edited),
132 ("editor1", Event::BufferEdited),
133 ("editor2", Event::BufferEdited),
134 ("editor1", Event::DirtyChanged),
135 ("editor2", Event::DirtyChanged),
136 ]
137 );
138
139 // Redoing on editor 2 will emit an `Edited` event only for that editor.
140 editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
141 assert_eq!(
142 mem::take(&mut *events.borrow_mut()),
143 [
144 ("editor2", Event::Edited),
145 ("editor1", Event::BufferEdited),
146 ("editor2", Event::BufferEdited),
147 ("editor1", Event::DirtyChanged),
148 ("editor2", Event::DirtyChanged),
149 ]
150 );
151
152 // No event is emitted when the mutation is a no-op.
153 editor2.update(cx, |editor, cx| {
154 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
155
156 editor.backspace(&Backspace, cx);
157 });
158 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
159}
160
161#[gpui::test]
162fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
163 init_test(cx, |_| {});
164
165 let mut now = Instant::now();
166 let buffer = cx.add_model(|cx| language::Buffer::new(0, "123456", cx));
167 let group_interval = buffer.read_with(cx, |buffer, _| buffer.transaction_group_interval());
168 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
169 let (_, editor) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
170
171 editor.update(cx, |editor, cx| {
172 editor.start_transaction_at(now, cx);
173 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
174
175 editor.insert("cd", cx);
176 editor.end_transaction_at(now, cx);
177 assert_eq!(editor.text(cx), "12cd56");
178 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
179
180 editor.start_transaction_at(now, cx);
181 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
182 editor.insert("e", cx);
183 editor.end_transaction_at(now, cx);
184 assert_eq!(editor.text(cx), "12cde6");
185 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
186
187 now += group_interval + Duration::from_millis(1);
188 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
189
190 // Simulate an edit in another editor
191 buffer.update(cx, |buffer, cx| {
192 buffer.start_transaction_at(now, cx);
193 buffer.edit([(0..1, "a")], None, cx);
194 buffer.edit([(1..1, "b")], None, cx);
195 buffer.end_transaction_at(now, cx);
196 });
197
198 assert_eq!(editor.text(cx), "ab2cde6");
199 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
200
201 // Last transaction happened past the group interval in a different editor.
202 // Undo it individually and don't restore selections.
203 editor.undo(&Undo, cx);
204 assert_eq!(editor.text(cx), "12cde6");
205 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
206
207 // First two transactions happened within the group interval in this editor.
208 // Undo them together and restore selections.
209 editor.undo(&Undo, cx);
210 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
211 assert_eq!(editor.text(cx), "123456");
212 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
213
214 // Redo the first two transactions together.
215 editor.redo(&Redo, cx);
216 assert_eq!(editor.text(cx), "12cde6");
217 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
218
219 // Redo the last transaction on its own.
220 editor.redo(&Redo, cx);
221 assert_eq!(editor.text(cx), "ab2cde6");
222 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
223
224 // Test empty transactions.
225 editor.start_transaction_at(now, cx);
226 editor.end_transaction_at(now, cx);
227 editor.undo(&Undo, cx);
228 assert_eq!(editor.text(cx), "12cde6");
229 });
230}
231
232#[gpui::test]
233fn test_ime_composition(cx: &mut TestAppContext) {
234 init_test(cx, |_| {});
235
236 let buffer = cx.add_model(|cx| {
237 let mut buffer = language::Buffer::new(0, "abcde", cx);
238 // Ensure automatic grouping doesn't occur.
239 buffer.set_group_interval(Duration::ZERO);
240 buffer
241 });
242
243 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
244 cx.add_window(|cx| {
245 let mut editor = build_editor(buffer.clone(), cx);
246
247 // Start a new IME composition.
248 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
249 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
250 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
251 assert_eq!(editor.text(cx), "äbcde");
252 assert_eq!(
253 editor.marked_text_ranges(cx),
254 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
255 );
256
257 // Finalize IME composition.
258 editor.replace_text_in_range(None, "ā", cx);
259 assert_eq!(editor.text(cx), "ābcde");
260 assert_eq!(editor.marked_text_ranges(cx), None);
261
262 // IME composition edits are grouped and are undone/redone at once.
263 editor.undo(&Default::default(), cx);
264 assert_eq!(editor.text(cx), "abcde");
265 assert_eq!(editor.marked_text_ranges(cx), None);
266 editor.redo(&Default::default(), cx);
267 assert_eq!(editor.text(cx), "ābcde");
268 assert_eq!(editor.marked_text_ranges(cx), None);
269
270 // Start a new IME composition.
271 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
272 assert_eq!(
273 editor.marked_text_ranges(cx),
274 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
275 );
276
277 // Undoing during an IME composition cancels it.
278 editor.undo(&Default::default(), cx);
279 assert_eq!(editor.text(cx), "ābcde");
280 assert_eq!(editor.marked_text_ranges(cx), None);
281
282 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
283 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
284 assert_eq!(editor.text(cx), "ābcdè");
285 assert_eq!(
286 editor.marked_text_ranges(cx),
287 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
288 );
289
290 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
291 editor.replace_text_in_range(Some(4..999), "ę", cx);
292 assert_eq!(editor.text(cx), "ābcdę");
293 assert_eq!(editor.marked_text_ranges(cx), None);
294
295 // Start a new IME composition with multiple cursors.
296 editor.change_selections(None, cx, |s| {
297 s.select_ranges([
298 OffsetUtf16(1)..OffsetUtf16(1),
299 OffsetUtf16(3)..OffsetUtf16(3),
300 OffsetUtf16(5)..OffsetUtf16(5),
301 ])
302 });
303 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
304 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
305 assert_eq!(
306 editor.marked_text_ranges(cx),
307 Some(vec![
308 OffsetUtf16(0)..OffsetUtf16(3),
309 OffsetUtf16(4)..OffsetUtf16(7),
310 OffsetUtf16(8)..OffsetUtf16(11)
311 ])
312 );
313
314 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
315 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
316 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
317 assert_eq!(
318 editor.marked_text_ranges(cx),
319 Some(vec![
320 OffsetUtf16(1)..OffsetUtf16(2),
321 OffsetUtf16(5)..OffsetUtf16(6),
322 OffsetUtf16(9)..OffsetUtf16(10)
323 ])
324 );
325
326 // Finalize IME composition with multiple cursors.
327 editor.replace_text_in_range(Some(9..10), "2", cx);
328 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
329 assert_eq!(editor.marked_text_ranges(cx), None);
330
331 editor
332 });
333}
334
335#[gpui::test]
336fn test_selection_with_mouse(cx: &mut TestAppContext) {
337 init_test(cx, |_| {});
338
339 let (_, editor) = cx.add_window(|cx| {
340 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
341 build_editor(buffer, cx)
342 });
343 editor.update(cx, |view, cx| {
344 view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
345 });
346 assert_eq!(
347 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
348 [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
349 );
350
351 editor.update(cx, |view, cx| {
352 view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
353 });
354
355 assert_eq!(
356 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
357 [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
358 );
359
360 editor.update(cx, |view, cx| {
361 view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
362 });
363
364 assert_eq!(
365 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
366 [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
367 );
368
369 editor.update(cx, |view, cx| {
370 view.end_selection(cx);
371 view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
372 });
373
374 assert_eq!(
375 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
376 [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
377 );
378
379 editor.update(cx, |view, cx| {
380 view.begin_selection(DisplayPoint::new(3, 3), true, 1, cx);
381 view.update_selection(DisplayPoint::new(0, 0), 0, Vector2F::zero(), cx);
382 });
383
384 assert_eq!(
385 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
386 [
387 DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1),
388 DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)
389 ]
390 );
391
392 editor.update(cx, |view, cx| {
393 view.end_selection(cx);
394 });
395
396 assert_eq!(
397 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
398 [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
399 );
400}
401
402#[gpui::test]
403fn test_canceling_pending_selection(cx: &mut TestAppContext) {
404 init_test(cx, |_| {});
405
406 let (_, view) = cx.add_window(|cx| {
407 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
408 build_editor(buffer, cx)
409 });
410
411 view.update(cx, |view, cx| {
412 view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
413 assert_eq!(
414 view.selections.display_ranges(cx),
415 [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
416 );
417 });
418
419 view.update(cx, |view, cx| {
420 view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx);
421 assert_eq!(
422 view.selections.display_ranges(cx),
423 [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
424 );
425 });
426
427 view.update(cx, |view, cx| {
428 view.cancel(&Cancel, cx);
429 view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
430 assert_eq!(
431 view.selections.display_ranges(cx),
432 [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
433 );
434 });
435}
436
437#[gpui::test]
438fn test_clone(cx: &mut TestAppContext) {
439 init_test(cx, |_| {});
440
441 let (text, selection_ranges) = marked_text_ranges(
442 indoc! {"
443 one
444 two
445 threeˇ
446 four
447 fiveˇ
448 "},
449 true,
450 );
451
452 let (_, editor) = cx.add_window(|cx| {
453 let buffer = MultiBuffer::build_simple(&text, cx);
454 build_editor(buffer, cx)
455 });
456
457 editor.update(cx, |editor, cx| {
458 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
459 editor.fold_ranges(
460 [
461 Point::new(1, 0)..Point::new(2, 0),
462 Point::new(3, 0)..Point::new(4, 0),
463 ],
464 true,
465 cx,
466 );
467 });
468
469 let (_, cloned_editor) = editor.update(cx, |editor, cx| {
470 cx.add_window(Default::default(), |cx| editor.clone(cx))
471 });
472
473 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx));
474 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx));
475
476 assert_eq!(
477 cloned_editor.update(cx, |e, cx| e.display_text(cx)),
478 editor.update(cx, |e, cx| e.display_text(cx))
479 );
480 assert_eq!(
481 cloned_snapshot
482 .folds_in_range(0..text.len())
483 .collect::<Vec<_>>(),
484 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
485 );
486 assert_set_eq!(
487 cloned_editor.read_with(cx, |editor, cx| editor.selections.ranges::<Point>(cx)),
488 editor.read_with(cx, |editor, cx| editor.selections.ranges(cx))
489 );
490 assert_set_eq!(
491 cloned_editor.update(cx, |e, cx| e.selections.display_ranges(cx)),
492 editor.update(cx, |e, cx| e.selections.display_ranges(cx))
493 );
494}
495
496#[gpui::test]
497async fn test_navigation_history(cx: &mut TestAppContext) {
498 init_test(cx, |_| {});
499
500 cx.set_global(DragAndDrop::<Workspace>::default());
501 use workspace::item::Item;
502
503 let fs = FakeFs::new(cx.background());
504 let project = Project::test(fs, [], cx).await;
505 let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
506 let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
507 cx.add_view(window_id, |cx| {
508 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
509 let mut editor = build_editor(buffer.clone(), cx);
510 let handle = cx.handle();
511 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
512
513 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
514 editor.nav_history.as_mut().unwrap().pop_backward(cx)
515 }
516
517 // Move the cursor a small distance.
518 // Nothing is added to the navigation history.
519 editor.change_selections(None, cx, |s| {
520 s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
521 });
522 editor.change_selections(None, cx, |s| {
523 s.select_display_ranges([DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)])
524 });
525 assert!(pop_history(&mut editor, cx).is_none());
526
527 // Move the cursor a large distance.
528 // The history can jump back to the previous position.
529 editor.change_selections(None, cx, |s| {
530 s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 3)])
531 });
532 let nav_entry = pop_history(&mut editor, cx).unwrap();
533 editor.navigate(nav_entry.data.unwrap(), cx);
534 assert_eq!(nav_entry.item.id(), cx.view_id());
535 assert_eq!(
536 editor.selections.display_ranges(cx),
537 &[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)]
538 );
539 assert!(pop_history(&mut editor, cx).is_none());
540
541 // Move the cursor a small distance via the mouse.
542 // Nothing is added to the navigation history.
543 editor.begin_selection(DisplayPoint::new(5, 0), false, 1, cx);
544 editor.end_selection(cx);
545 assert_eq!(
546 editor.selections.display_ranges(cx),
547 &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
548 );
549 assert!(pop_history(&mut editor, cx).is_none());
550
551 // Move the cursor a large distance via the mouse.
552 // The history can jump back to the previous position.
553 editor.begin_selection(DisplayPoint::new(15, 0), false, 1, cx);
554 editor.end_selection(cx);
555 assert_eq!(
556 editor.selections.display_ranges(cx),
557 &[DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)]
558 );
559 let nav_entry = pop_history(&mut editor, cx).unwrap();
560 editor.navigate(nav_entry.data.unwrap(), cx);
561 assert_eq!(nav_entry.item.id(), cx.view_id());
562 assert_eq!(
563 editor.selections.display_ranges(cx),
564 &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
565 );
566 assert!(pop_history(&mut editor, cx).is_none());
567
568 // Set scroll position to check later
569 editor.set_scroll_position(Vector2F::new(5.5, 5.5), cx);
570 let original_scroll_position = editor.scroll_manager.anchor();
571
572 // Jump to the end of the document and adjust scroll
573 editor.move_to_end(&MoveToEnd, cx);
574 editor.set_scroll_position(Vector2F::new(-2.5, -0.5), cx);
575 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
576
577 let nav_entry = pop_history(&mut editor, cx).unwrap();
578 editor.navigate(nav_entry.data.unwrap(), cx);
579 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
580
581 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
582 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
583 invalid_anchor.text_anchor.buffer_id = Some(999);
584 let invalid_point = Point::new(9999, 0);
585 editor.navigate(
586 Box::new(NavigationData {
587 cursor_anchor: invalid_anchor,
588 cursor_position: invalid_point,
589 scroll_anchor: ScrollAnchor {
590 anchor: invalid_anchor,
591 offset: Default::default(),
592 },
593 scroll_top_row: invalid_point.row,
594 }),
595 cx,
596 );
597 assert_eq!(
598 editor.selections.display_ranges(cx),
599 &[editor.max_point(cx)..editor.max_point(cx)]
600 );
601 assert_eq!(
602 editor.scroll_position(cx),
603 vec2f(0., editor.max_point(cx).row() as f32)
604 );
605
606 editor
607 });
608}
609
610#[gpui::test]
611fn test_cancel(cx: &mut TestAppContext) {
612 init_test(cx, |_| {});
613
614 let (_, view) = cx.add_window(|cx| {
615 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
616 build_editor(buffer, cx)
617 });
618
619 view.update(cx, |view, cx| {
620 view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
621 view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx);
622 view.end_selection(cx);
623
624 view.begin_selection(DisplayPoint::new(0, 1), true, 1, cx);
625 view.update_selection(DisplayPoint::new(0, 3), 0, Vector2F::zero(), cx);
626 view.end_selection(cx);
627 assert_eq!(
628 view.selections.display_ranges(cx),
629 [
630 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
631 DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1),
632 ]
633 );
634 });
635
636 view.update(cx, |view, cx| {
637 view.cancel(&Cancel, cx);
638 assert_eq!(
639 view.selections.display_ranges(cx),
640 [DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1)]
641 );
642 });
643
644 view.update(cx, |view, cx| {
645 view.cancel(&Cancel, cx);
646 assert_eq!(
647 view.selections.display_ranges(cx),
648 [DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1)]
649 );
650 });
651}
652
653#[gpui::test]
654fn test_fold_action(cx: &mut TestAppContext) {
655 init_test(cx, |_| {});
656
657 let (_, view) = cx.add_window(|cx| {
658 let buffer = MultiBuffer::build_simple(
659 &"
660 impl Foo {
661 // Hello!
662
663 fn a() {
664 1
665 }
666
667 fn b() {
668 2
669 }
670
671 fn c() {
672 3
673 }
674 }
675 "
676 .unindent(),
677 cx,
678 );
679 build_editor(buffer.clone(), cx)
680 });
681
682 view.update(cx, |view, cx| {
683 view.change_selections(None, cx, |s| {
684 s.select_display_ranges([DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)]);
685 });
686 view.fold(&Fold, cx);
687 assert_eq!(
688 view.display_text(cx),
689 "
690 impl Foo {
691 // Hello!
692
693 fn a() {
694 1
695 }
696
697 fn b() {⋯
698 }
699
700 fn c() {⋯
701 }
702 }
703 "
704 .unindent(),
705 );
706
707 view.fold(&Fold, cx);
708 assert_eq!(
709 view.display_text(cx),
710 "
711 impl Foo {⋯
712 }
713 "
714 .unindent(),
715 );
716
717 view.unfold_lines(&UnfoldLines, cx);
718 assert_eq!(
719 view.display_text(cx),
720 "
721 impl Foo {
722 // Hello!
723
724 fn a() {
725 1
726 }
727
728 fn b() {⋯
729 }
730
731 fn c() {⋯
732 }
733 }
734 "
735 .unindent(),
736 );
737
738 view.unfold_lines(&UnfoldLines, cx);
739 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
740 });
741}
742
743#[gpui::test]
744fn test_move_cursor(cx: &mut TestAppContext) {
745 init_test(cx, |_| {});
746
747 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
748 let (_, view) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
749
750 buffer.update(cx, |buffer, cx| {
751 buffer.edit(
752 vec![
753 (Point::new(1, 0)..Point::new(1, 0), "\t"),
754 (Point::new(1, 1)..Point::new(1, 1), "\t"),
755 ],
756 None,
757 cx,
758 );
759 });
760 view.update(cx, |view, cx| {
761 assert_eq!(
762 view.selections.display_ranges(cx),
763 &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
764 );
765
766 view.move_down(&MoveDown, cx);
767 assert_eq!(
768 view.selections.display_ranges(cx),
769 &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
770 );
771
772 view.move_right(&MoveRight, cx);
773 assert_eq!(
774 view.selections.display_ranges(cx),
775 &[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4)]
776 );
777
778 view.move_left(&MoveLeft, cx);
779 assert_eq!(
780 view.selections.display_ranges(cx),
781 &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
782 );
783
784 view.move_up(&MoveUp, cx);
785 assert_eq!(
786 view.selections.display_ranges(cx),
787 &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
788 );
789
790 view.move_to_end(&MoveToEnd, cx);
791 assert_eq!(
792 view.selections.display_ranges(cx),
793 &[DisplayPoint::new(5, 6)..DisplayPoint::new(5, 6)]
794 );
795
796 view.move_to_beginning(&MoveToBeginning, cx);
797 assert_eq!(
798 view.selections.display_ranges(cx),
799 &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
800 );
801
802 view.change_selections(None, cx, |s| {
803 s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)]);
804 });
805 view.select_to_beginning(&SelectToBeginning, cx);
806 assert_eq!(
807 view.selections.display_ranges(cx),
808 &[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 0)]
809 );
810
811 view.select_to_end(&SelectToEnd, cx);
812 assert_eq!(
813 view.selections.display_ranges(cx),
814 &[DisplayPoint::new(0, 1)..DisplayPoint::new(5, 6)]
815 );
816 });
817}
818
819#[gpui::test]
820fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
821 init_test(cx, |_| {});
822
823 let (_, view) = cx.add_window(|cx| {
824 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx);
825 build_editor(buffer.clone(), cx)
826 });
827
828 assert_eq!('ⓐ'.len_utf8(), 3);
829 assert_eq!('α'.len_utf8(), 2);
830
831 view.update(cx, |view, cx| {
832 view.fold_ranges(
833 vec![
834 Point::new(0, 6)..Point::new(0, 12),
835 Point::new(1, 2)..Point::new(1, 4),
836 Point::new(2, 4)..Point::new(2, 8),
837 ],
838 true,
839 cx,
840 );
841 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε\n");
842
843 view.move_right(&MoveRight, cx);
844 assert_eq!(
845 view.selections.display_ranges(cx),
846 &[empty_range(0, "ⓐ".len())]
847 );
848 view.move_right(&MoveRight, cx);
849 assert_eq!(
850 view.selections.display_ranges(cx),
851 &[empty_range(0, "ⓐⓑ".len())]
852 );
853 view.move_right(&MoveRight, cx);
854 assert_eq!(
855 view.selections.display_ranges(cx),
856 &[empty_range(0, "ⓐⓑ⋯".len())]
857 );
858
859 view.move_down(&MoveDown, cx);
860 assert_eq!(
861 view.selections.display_ranges(cx),
862 &[empty_range(1, "ab⋯".len())]
863 );
864 view.move_left(&MoveLeft, cx);
865 assert_eq!(
866 view.selections.display_ranges(cx),
867 &[empty_range(1, "ab".len())]
868 );
869 view.move_left(&MoveLeft, cx);
870 assert_eq!(
871 view.selections.display_ranges(cx),
872 &[empty_range(1, "a".len())]
873 );
874
875 view.move_down(&MoveDown, cx);
876 assert_eq!(
877 view.selections.display_ranges(cx),
878 &[empty_range(2, "α".len())]
879 );
880 view.move_right(&MoveRight, cx);
881 assert_eq!(
882 view.selections.display_ranges(cx),
883 &[empty_range(2, "αβ".len())]
884 );
885 view.move_right(&MoveRight, cx);
886 assert_eq!(
887 view.selections.display_ranges(cx),
888 &[empty_range(2, "αβ⋯".len())]
889 );
890 view.move_right(&MoveRight, cx);
891 assert_eq!(
892 view.selections.display_ranges(cx),
893 &[empty_range(2, "αβ⋯ε".len())]
894 );
895
896 view.move_up(&MoveUp, cx);
897 assert_eq!(
898 view.selections.display_ranges(cx),
899 &[empty_range(1, "ab⋯e".len())]
900 );
901 view.move_up(&MoveUp, cx);
902 assert_eq!(
903 view.selections.display_ranges(cx),
904 &[empty_range(0, "ⓐⓑ⋯ⓔ".len())]
905 );
906 view.move_left(&MoveLeft, cx);
907 assert_eq!(
908 view.selections.display_ranges(cx),
909 &[empty_range(0, "ⓐⓑ⋯".len())]
910 );
911 view.move_left(&MoveLeft, cx);
912 assert_eq!(
913 view.selections.display_ranges(cx),
914 &[empty_range(0, "ⓐⓑ".len())]
915 );
916 view.move_left(&MoveLeft, cx);
917 assert_eq!(
918 view.selections.display_ranges(cx),
919 &[empty_range(0, "ⓐ".len())]
920 );
921 });
922}
923
924#[gpui::test]
925fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
926 init_test(cx, |_| {});
927
928 let (_, view) = cx.add_window(|cx| {
929 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
930 build_editor(buffer.clone(), cx)
931 });
932 view.update(cx, |view, cx| {
933 view.change_selections(None, cx, |s| {
934 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
935 });
936 view.move_down(&MoveDown, cx);
937 assert_eq!(
938 view.selections.display_ranges(cx),
939 &[empty_range(1, "abcd".len())]
940 );
941
942 view.move_down(&MoveDown, cx);
943 assert_eq!(
944 view.selections.display_ranges(cx),
945 &[empty_range(2, "αβγ".len())]
946 );
947
948 view.move_down(&MoveDown, cx);
949 assert_eq!(
950 view.selections.display_ranges(cx),
951 &[empty_range(3, "abcd".len())]
952 );
953
954 view.move_down(&MoveDown, cx);
955 assert_eq!(
956 view.selections.display_ranges(cx),
957 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
958 );
959
960 view.move_up(&MoveUp, cx);
961 assert_eq!(
962 view.selections.display_ranges(cx),
963 &[empty_range(3, "abcd".len())]
964 );
965
966 view.move_up(&MoveUp, cx);
967 assert_eq!(
968 view.selections.display_ranges(cx),
969 &[empty_range(2, "αβγ".len())]
970 );
971 });
972}
973
974#[gpui::test]
975fn test_beginning_end_of_line(cx: &mut TestAppContext) {
976 init_test(cx, |_| {});
977
978 let (_, view) = cx.add_window(|cx| {
979 let buffer = MultiBuffer::build_simple("abc\n def", cx);
980 build_editor(buffer, cx)
981 });
982 view.update(cx, |view, cx| {
983 view.change_selections(None, cx, |s| {
984 s.select_display_ranges([
985 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
986 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
987 ]);
988 });
989 });
990
991 view.update(cx, |view, cx| {
992 view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
993 assert_eq!(
994 view.selections.display_ranges(cx),
995 &[
996 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
997 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
998 ]
999 );
1000 });
1001
1002 view.update(cx, |view, cx| {
1003 view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
1004 assert_eq!(
1005 view.selections.display_ranges(cx),
1006 &[
1007 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
1008 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
1009 ]
1010 );
1011 });
1012
1013 view.update(cx, |view, cx| {
1014 view.move_to_beginning_of_line(&MoveToBeginningOfLine, cx);
1015 assert_eq!(
1016 view.selections.display_ranges(cx),
1017 &[
1018 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
1019 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
1020 ]
1021 );
1022 });
1023
1024 view.update(cx, |view, cx| {
1025 view.move_to_end_of_line(&MoveToEndOfLine, cx);
1026 assert_eq!(
1027 view.selections.display_ranges(cx),
1028 &[
1029 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
1030 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
1031 ]
1032 );
1033 });
1034
1035 // Moving to the end of line again is a no-op.
1036 view.update(cx, |view, cx| {
1037 view.move_to_end_of_line(&MoveToEndOfLine, cx);
1038 assert_eq!(
1039 view.selections.display_ranges(cx),
1040 &[
1041 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
1042 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
1043 ]
1044 );
1045 });
1046
1047 view.update(cx, |view, cx| {
1048 view.move_left(&MoveLeft, cx);
1049 view.select_to_beginning_of_line(
1050 &SelectToBeginningOfLine {
1051 stop_at_soft_wraps: true,
1052 },
1053 cx,
1054 );
1055 assert_eq!(
1056 view.selections.display_ranges(cx),
1057 &[
1058 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
1059 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
1060 ]
1061 );
1062 });
1063
1064 view.update(cx, |view, cx| {
1065 view.select_to_beginning_of_line(
1066 &SelectToBeginningOfLine {
1067 stop_at_soft_wraps: true,
1068 },
1069 cx,
1070 );
1071 assert_eq!(
1072 view.selections.display_ranges(cx),
1073 &[
1074 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
1075 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 0),
1076 ]
1077 );
1078 });
1079
1080 view.update(cx, |view, cx| {
1081 view.select_to_beginning_of_line(
1082 &SelectToBeginningOfLine {
1083 stop_at_soft_wraps: true,
1084 },
1085 cx,
1086 );
1087 assert_eq!(
1088 view.selections.display_ranges(cx),
1089 &[
1090 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
1091 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
1092 ]
1093 );
1094 });
1095
1096 view.update(cx, |view, cx| {
1097 view.select_to_end_of_line(
1098 &SelectToEndOfLine {
1099 stop_at_soft_wraps: true,
1100 },
1101 cx,
1102 );
1103 assert_eq!(
1104 view.selections.display_ranges(cx),
1105 &[
1106 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
1107 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 5),
1108 ]
1109 );
1110 });
1111
1112 view.update(cx, |view, cx| {
1113 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1114 assert_eq!(view.display_text(cx), "ab\n de");
1115 assert_eq!(
1116 view.selections.display_ranges(cx),
1117 &[
1118 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
1119 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
1120 ]
1121 );
1122 });
1123
1124 view.update(cx, |view, cx| {
1125 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1126 assert_eq!(view.display_text(cx), "\n");
1127 assert_eq!(
1128 view.selections.display_ranges(cx),
1129 &[
1130 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
1131 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
1132 ]
1133 );
1134 });
1135}
1136
1137#[gpui::test]
1138fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1139 init_test(cx, |_| {});
1140
1141 let (_, view) = cx.add_window(|cx| {
1142 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1143 build_editor(buffer, cx)
1144 });
1145 view.update(cx, |view, cx| {
1146 view.change_selections(None, cx, |s| {
1147 s.select_display_ranges([
1148 DisplayPoint::new(0, 11)..DisplayPoint::new(0, 11),
1149 DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4),
1150 ])
1151 });
1152
1153 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1154 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1155
1156 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1157 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1158
1159 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1160 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1161
1162 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1163 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1164
1165 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1166 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1167
1168 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1169 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1170
1171 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1172 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1173
1174 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1175 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1176
1177 view.move_right(&MoveRight, cx);
1178 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1179 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1180
1181 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1182 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1183
1184 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1185 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1186 });
1187}
1188
1189#[gpui::test]
1190fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1191 init_test(cx, |_| {});
1192
1193 let (_, view) = cx.add_window(|cx| {
1194 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1195 build_editor(buffer, cx)
1196 });
1197
1198 view.update(cx, |view, cx| {
1199 view.set_wrap_width(Some(140.), cx);
1200 assert_eq!(
1201 view.display_text(cx),
1202 "use one::{\n two::three::\n four::five\n};"
1203 );
1204
1205 view.change_selections(None, cx, |s| {
1206 s.select_display_ranges([DisplayPoint::new(1, 7)..DisplayPoint::new(1, 7)]);
1207 });
1208
1209 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1210 assert_eq!(
1211 view.selections.display_ranges(cx),
1212 &[DisplayPoint::new(1, 9)..DisplayPoint::new(1, 9)]
1213 );
1214
1215 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1216 assert_eq!(
1217 view.selections.display_ranges(cx),
1218 &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
1219 );
1220
1221 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1222 assert_eq!(
1223 view.selections.display_ranges(cx),
1224 &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
1225 );
1226
1227 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1228 assert_eq!(
1229 view.selections.display_ranges(cx),
1230 &[DisplayPoint::new(2, 8)..DisplayPoint::new(2, 8)]
1231 );
1232
1233 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1234 assert_eq!(
1235 view.selections.display_ranges(cx),
1236 &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
1237 );
1238
1239 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1240 assert_eq!(
1241 view.selections.display_ranges(cx),
1242 &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
1243 );
1244 });
1245}
1246
1247#[gpui::test]
1248async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1249 init_test(cx, |_| {});
1250 let mut cx = EditorTestContext::new(cx).await;
1251
1252 let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
1253 cx.simulate_window_resize(cx.window_id, vec2f(100., 4. * line_height));
1254
1255 cx.set_state(
1256 &r#"ˇone
1257 two
1258
1259 three
1260 fourˇ
1261 five
1262
1263 six"#
1264 .unindent(),
1265 );
1266
1267 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1268 cx.assert_editor_state(
1269 &r#"one
1270 two
1271 ˇ
1272 three
1273 four
1274 five
1275 ˇ
1276 six"#
1277 .unindent(),
1278 );
1279
1280 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1281 cx.assert_editor_state(
1282 &r#"one
1283 two
1284
1285 three
1286 four
1287 five
1288 ˇ
1289 sixˇ"#
1290 .unindent(),
1291 );
1292
1293 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1294 cx.assert_editor_state(
1295 &r#"ˇone
1296 two
1297
1298 three
1299 four
1300 five
1301
1302 sixˇ"#
1303 .unindent(),
1304 );
1305
1306 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1307 cx.assert_editor_state(
1308 &r#"ˇone
1309 two
1310 ˇ
1311 three
1312 four
1313 five
1314
1315 six"#
1316 .unindent(),
1317 );
1318
1319 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1320 cx.assert_editor_state(
1321 &r#"ˇone
1322 two
1323
1324 three
1325 four
1326 five
1327
1328 sixˇ"#
1329 .unindent(),
1330 );
1331
1332 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1333 cx.assert_editor_state(
1334 &r#"one
1335 two
1336
1337 three
1338 four
1339 five
1340 ˇ
1341 sixˇ"#
1342 .unindent(),
1343 );
1344
1345 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1346 cx.assert_editor_state(
1347 &r#"one
1348 two
1349 ˇ
1350 three
1351 four
1352 five
1353 ˇ
1354 six"#
1355 .unindent(),
1356 );
1357}
1358
1359#[gpui::test]
1360async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
1361 init_test(cx, |_| {});
1362 let mut cx = EditorTestContext::new(cx).await;
1363
1364 let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
1365 cx.simulate_window_resize(cx.window_id, vec2f(100., 4. * line_height));
1366
1367 cx.set_state(
1368 &r#"
1369 ˇone
1370 two
1371 threeˇ
1372 four
1373 five
1374 six
1375 seven
1376 eight
1377 nine
1378 ten
1379 "#
1380 .unindent(),
1381 );
1382
1383 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1384 cx.assert_editor_state(
1385 &r#"
1386 one
1387 two
1388 three
1389 ˇfour
1390 five
1391 sixˇ
1392 seven
1393 eight
1394 nine
1395 ten
1396 "#
1397 .unindent(),
1398 );
1399
1400 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1401 cx.assert_editor_state(
1402 &r#"
1403 one
1404 two
1405 three
1406 four
1407 five
1408 six
1409 ˇseven
1410 eight
1411 nineˇ
1412 ten
1413 "#
1414 .unindent(),
1415 );
1416
1417 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
1418 cx.assert_editor_state(
1419 &r#"
1420 one
1421 two
1422 three
1423 ˇfour
1424 five
1425 sixˇ
1426 seven
1427 eight
1428 nine
1429 ten
1430 "#
1431 .unindent(),
1432 );
1433
1434 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
1435 cx.assert_editor_state(
1436 &r#"
1437 ˇone
1438 two
1439 threeˇ
1440 four
1441 five
1442 six
1443 seven
1444 eight
1445 nine
1446 ten
1447 "#
1448 .unindent(),
1449 );
1450
1451 // Test select collapsing
1452 cx.update_editor(|editor, cx| {
1453 editor.move_page_down(&MovePageDown::default(), cx);
1454 editor.move_page_down(&MovePageDown::default(), cx);
1455 editor.move_page_down(&MovePageDown::default(), cx);
1456 });
1457 cx.assert_editor_state(
1458 &r#"
1459 one
1460 two
1461 three
1462 four
1463 five
1464 six
1465 seven
1466 eight
1467 nine
1468 ˇten
1469 ˇ"#
1470 .unindent(),
1471 );
1472}
1473
1474#[gpui::test]
1475async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
1476 init_test(cx, |_| {});
1477 let mut cx = EditorTestContext::new(cx).await;
1478 cx.set_state("one «two threeˇ» four");
1479 cx.update_editor(|editor, cx| {
1480 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1481 assert_eq!(editor.text(cx), " four");
1482 });
1483}
1484
1485#[gpui::test]
1486fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
1487 init_test(cx, |_| {});
1488
1489 let (_, view) = cx.add_window(|cx| {
1490 let buffer = MultiBuffer::build_simple("one two three four", cx);
1491 build_editor(buffer.clone(), cx)
1492 });
1493
1494 view.update(cx, |view, cx| {
1495 view.change_selections(None, cx, |s| {
1496 s.select_display_ranges([
1497 // an empty selection - the preceding word fragment is deleted
1498 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
1499 // characters selected - they are deleted
1500 DisplayPoint::new(0, 9)..DisplayPoint::new(0, 12),
1501 ])
1502 });
1503 view.delete_to_previous_word_start(&DeleteToPreviousWordStart, cx);
1504 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
1505 });
1506
1507 view.update(cx, |view, cx| {
1508 view.change_selections(None, cx, |s| {
1509 s.select_display_ranges([
1510 // an empty selection - the following word fragment is deleted
1511 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
1512 // characters selected - they are deleted
1513 DisplayPoint::new(0, 9)..DisplayPoint::new(0, 10),
1514 ])
1515 });
1516 view.delete_to_next_word_end(&DeleteToNextWordEnd, cx);
1517 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
1518 });
1519}
1520
1521#[gpui::test]
1522fn test_newline(cx: &mut TestAppContext) {
1523 init_test(cx, |_| {});
1524
1525 let (_, view) = cx.add_window(|cx| {
1526 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
1527 build_editor(buffer.clone(), cx)
1528 });
1529
1530 view.update(cx, |view, cx| {
1531 view.change_selections(None, cx, |s| {
1532 s.select_display_ranges([
1533 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
1534 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
1535 DisplayPoint::new(1, 6)..DisplayPoint::new(1, 6),
1536 ])
1537 });
1538
1539 view.newline(&Newline, cx);
1540 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
1541 });
1542}
1543
1544#[gpui::test]
1545fn test_newline_with_old_selections(cx: &mut TestAppContext) {
1546 init_test(cx, |_| {});
1547
1548 let (_, editor) = cx.add_window(|cx| {
1549 let buffer = MultiBuffer::build_simple(
1550 "
1551 a
1552 b(
1553 X
1554 )
1555 c(
1556 X
1557 )
1558 "
1559 .unindent()
1560 .as_str(),
1561 cx,
1562 );
1563 let mut editor = build_editor(buffer.clone(), cx);
1564 editor.change_selections(None, cx, |s| {
1565 s.select_ranges([
1566 Point::new(2, 4)..Point::new(2, 5),
1567 Point::new(5, 4)..Point::new(5, 5),
1568 ])
1569 });
1570 editor
1571 });
1572
1573 editor.update(cx, |editor, cx| {
1574 // Edit the buffer directly, deleting ranges surrounding the editor's selections
1575 editor.buffer.update(cx, |buffer, cx| {
1576 buffer.edit(
1577 [
1578 (Point::new(1, 2)..Point::new(3, 0), ""),
1579 (Point::new(4, 2)..Point::new(6, 0), ""),
1580 ],
1581 None,
1582 cx,
1583 );
1584 assert_eq!(
1585 buffer.read(cx).text(),
1586 "
1587 a
1588 b()
1589 c()
1590 "
1591 .unindent()
1592 );
1593 });
1594 assert_eq!(
1595 editor.selections.ranges(cx),
1596 &[
1597 Point::new(1, 2)..Point::new(1, 2),
1598 Point::new(2, 2)..Point::new(2, 2),
1599 ],
1600 );
1601
1602 editor.newline(&Newline, cx);
1603 assert_eq!(
1604 editor.text(cx),
1605 "
1606 a
1607 b(
1608 )
1609 c(
1610 )
1611 "
1612 .unindent()
1613 );
1614
1615 // The selections are moved after the inserted newlines
1616 assert_eq!(
1617 editor.selections.ranges(cx),
1618 &[
1619 Point::new(2, 0)..Point::new(2, 0),
1620 Point::new(4, 0)..Point::new(4, 0),
1621 ],
1622 );
1623 });
1624}
1625
1626#[gpui::test]
1627async fn test_newline_above(cx: &mut gpui::TestAppContext) {
1628 init_test(cx, |settings| {
1629 settings.defaults.tab_size = NonZeroU32::new(4)
1630 });
1631
1632 let language = Arc::new(
1633 Language::new(
1634 LanguageConfig::default(),
1635 Some(tree_sitter_rust::language()),
1636 )
1637 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
1638 .unwrap(),
1639 );
1640
1641 let mut cx = EditorTestContext::new(cx).await;
1642 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1643 cx.set_state(indoc! {"
1644 const a: ˇA = (
1645 (ˇ
1646 «const_functionˇ»(ˇ),
1647 so«mˇ»et«hˇ»ing_ˇelse,ˇ
1648 )ˇ
1649 ˇ);ˇ
1650 "});
1651
1652 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
1653 cx.assert_editor_state(indoc! {"
1654 ˇ
1655 const a: A = (
1656 ˇ
1657 (
1658 ˇ
1659 ˇ
1660 const_function(),
1661 ˇ
1662 ˇ
1663 ˇ
1664 ˇ
1665 something_else,
1666 ˇ
1667 )
1668 ˇ
1669 ˇ
1670 );
1671 "});
1672}
1673
1674#[gpui::test]
1675async fn test_newline_below(cx: &mut gpui::TestAppContext) {
1676 init_test(cx, |settings| {
1677 settings.defaults.tab_size = NonZeroU32::new(4)
1678 });
1679
1680 let language = Arc::new(
1681 Language::new(
1682 LanguageConfig::default(),
1683 Some(tree_sitter_rust::language()),
1684 )
1685 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
1686 .unwrap(),
1687 );
1688
1689 let mut cx = EditorTestContext::new(cx).await;
1690 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1691 cx.set_state(indoc! {"
1692 const a: ˇA = (
1693 (ˇ
1694 «const_functionˇ»(ˇ),
1695 so«mˇ»et«hˇ»ing_ˇelse,ˇ
1696 )ˇ
1697 ˇ);ˇ
1698 "});
1699
1700 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
1701 cx.assert_editor_state(indoc! {"
1702 const a: A = (
1703 ˇ
1704 (
1705 ˇ
1706 const_function(),
1707 ˇ
1708 ˇ
1709 something_else,
1710 ˇ
1711 ˇ
1712 ˇ
1713 ˇ
1714 )
1715 ˇ
1716 );
1717 ˇ
1718 ˇ
1719 "});
1720}
1721
1722#[gpui::test]
1723async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
1724 init_test(cx, |settings| {
1725 settings.defaults.tab_size = NonZeroU32::new(4)
1726 });
1727
1728 let language = Arc::new(Language::new(
1729 LanguageConfig {
1730 line_comment: Some("//".into()),
1731 ..LanguageConfig::default()
1732 },
1733 None,
1734 ));
1735 {
1736 let mut cx = EditorTestContext::new(cx).await;
1737 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1738 cx.set_state(indoc! {"
1739 // Fooˇ
1740 "});
1741
1742 cx.update_editor(|e, cx| e.newline(&Newline, cx));
1743 cx.assert_editor_state(indoc! {"
1744 // Foo
1745 //ˇ
1746 "});
1747 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
1748 cx.set_state(indoc! {"
1749 ˇ// Foo
1750 "});
1751 cx.update_editor(|e, cx| e.newline(&Newline, cx));
1752 cx.assert_editor_state(indoc! {"
1753
1754 ˇ// Foo
1755 "});
1756 }
1757 // Ensure that comment continuations can be disabled.
1758 update_test_settings(cx, |settings| {
1759 settings.defaults.extend_comment_on_newline = Some(false);
1760 });
1761 let mut cx = EditorTestContext::new(cx).await;
1762 cx.set_state(indoc! {"
1763 // Fooˇ
1764 "});
1765 cx.update_editor(|e, cx| e.newline(&Newline, cx));
1766 cx.assert_editor_state(indoc! {"
1767 // Foo
1768 ˇ
1769 "});
1770}
1771
1772#[gpui::test]
1773fn test_insert_with_old_selections(cx: &mut TestAppContext) {
1774 init_test(cx, |_| {});
1775
1776 let (_, editor) = cx.add_window(|cx| {
1777 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
1778 let mut editor = build_editor(buffer.clone(), cx);
1779 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
1780 editor
1781 });
1782
1783 editor.update(cx, |editor, cx| {
1784 // Edit the buffer directly, deleting ranges surrounding the editor's selections
1785 editor.buffer.update(cx, |buffer, cx| {
1786 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
1787 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
1788 });
1789 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
1790
1791 editor.insert("Z", cx);
1792 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
1793
1794 // The selections are moved after the inserted characters
1795 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
1796 });
1797}
1798
1799#[gpui::test]
1800async fn test_tab(cx: &mut gpui::TestAppContext) {
1801 init_test(cx, |settings| {
1802 settings.defaults.tab_size = NonZeroU32::new(3)
1803 });
1804
1805 let mut cx = EditorTestContext::new(cx).await;
1806 cx.set_state(indoc! {"
1807 ˇabˇc
1808 ˇ🏀ˇ🏀ˇefg
1809 dˇ
1810 "});
1811 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1812 cx.assert_editor_state(indoc! {"
1813 ˇab ˇc
1814 ˇ🏀 ˇ🏀 ˇefg
1815 d ˇ
1816 "});
1817
1818 cx.set_state(indoc! {"
1819 a
1820 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
1821 "});
1822 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1823 cx.assert_editor_state(indoc! {"
1824 a
1825 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
1826 "});
1827}
1828
1829#[gpui::test]
1830async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
1831 init_test(cx, |_| {});
1832
1833 let mut cx = EditorTestContext::new(cx).await;
1834 let language = Arc::new(
1835 Language::new(
1836 LanguageConfig::default(),
1837 Some(tree_sitter_rust::language()),
1838 )
1839 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
1840 .unwrap(),
1841 );
1842 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1843
1844 // cursors that are already at the suggested indent level insert
1845 // a soft tab. cursors that are to the left of the suggested indent
1846 // auto-indent their line.
1847 cx.set_state(indoc! {"
1848 ˇ
1849 const a: B = (
1850 c(
1851 d(
1852 ˇ
1853 )
1854 ˇ
1855 ˇ )
1856 );
1857 "});
1858 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1859 cx.assert_editor_state(indoc! {"
1860 ˇ
1861 const a: B = (
1862 c(
1863 d(
1864 ˇ
1865 )
1866 ˇ
1867 ˇ)
1868 );
1869 "});
1870
1871 // handle auto-indent when there are multiple cursors on the same line
1872 cx.set_state(indoc! {"
1873 const a: B = (
1874 c(
1875 ˇ ˇ
1876 ˇ )
1877 );
1878 "});
1879 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1880 cx.assert_editor_state(indoc! {"
1881 const a: B = (
1882 c(
1883 ˇ
1884 ˇ)
1885 );
1886 "});
1887}
1888
1889#[gpui::test]
1890async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
1891 init_test(cx, |settings| {
1892 settings.defaults.tab_size = NonZeroU32::new(4)
1893 });
1894
1895 let language = Arc::new(
1896 Language::new(
1897 LanguageConfig::default(),
1898 Some(tree_sitter_rust::language()),
1899 )
1900 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
1901 .unwrap(),
1902 );
1903
1904 let mut cx = EditorTestContext::new(cx).await;
1905 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
1906 cx.set_state(indoc! {"
1907 fn a() {
1908 if b {
1909 \t ˇc
1910 }
1911 }
1912 "});
1913
1914 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1915 cx.assert_editor_state(indoc! {"
1916 fn a() {
1917 if b {
1918 ˇc
1919 }
1920 }
1921 "});
1922}
1923
1924#[gpui::test]
1925async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
1926 init_test(cx, |settings| {
1927 settings.defaults.tab_size = NonZeroU32::new(4);
1928 });
1929
1930 let mut cx = EditorTestContext::new(cx).await;
1931
1932 cx.set_state(indoc! {"
1933 «oneˇ» «twoˇ»
1934 three
1935 four
1936 "});
1937 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1938 cx.assert_editor_state(indoc! {"
1939 «oneˇ» «twoˇ»
1940 three
1941 four
1942 "});
1943
1944 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1945 cx.assert_editor_state(indoc! {"
1946 «oneˇ» «twoˇ»
1947 three
1948 four
1949 "});
1950
1951 // select across line ending
1952 cx.set_state(indoc! {"
1953 one two
1954 t«hree
1955 ˇ» four
1956 "});
1957 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1958 cx.assert_editor_state(indoc! {"
1959 one two
1960 t«hree
1961 ˇ» four
1962 "});
1963
1964 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1965 cx.assert_editor_state(indoc! {"
1966 one two
1967 t«hree
1968 ˇ» four
1969 "});
1970
1971 // Ensure that indenting/outdenting works when the cursor is at column 0.
1972 cx.set_state(indoc! {"
1973 one two
1974 ˇthree
1975 four
1976 "});
1977 cx.update_editor(|e, cx| e.tab(&Tab, cx));
1978 cx.assert_editor_state(indoc! {"
1979 one two
1980 ˇthree
1981 four
1982 "});
1983
1984 cx.set_state(indoc! {"
1985 one two
1986 ˇ three
1987 four
1988 "});
1989 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
1990 cx.assert_editor_state(indoc! {"
1991 one two
1992 ˇthree
1993 four
1994 "});
1995}
1996
1997#[gpui::test]
1998async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
1999 init_test(cx, |settings| {
2000 settings.defaults.hard_tabs = Some(true);
2001 });
2002
2003 let mut cx = EditorTestContext::new(cx).await;
2004
2005 // select two ranges on one line
2006 cx.set_state(indoc! {"
2007 «oneˇ» «twoˇ»
2008 three
2009 four
2010 "});
2011 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2012 cx.assert_editor_state(indoc! {"
2013 \t«oneˇ» «twoˇ»
2014 three
2015 four
2016 "});
2017 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2018 cx.assert_editor_state(indoc! {"
2019 \t\t«oneˇ» «twoˇ»
2020 three
2021 four
2022 "});
2023 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2024 cx.assert_editor_state(indoc! {"
2025 \t«oneˇ» «twoˇ»
2026 three
2027 four
2028 "});
2029 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2030 cx.assert_editor_state(indoc! {"
2031 «oneˇ» «twoˇ»
2032 three
2033 four
2034 "});
2035
2036 // select across a line ending
2037 cx.set_state(indoc! {"
2038 one two
2039 t«hree
2040 ˇ»four
2041 "});
2042 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2043 cx.assert_editor_state(indoc! {"
2044 one two
2045 \tt«hree
2046 ˇ»four
2047 "});
2048 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2049 cx.assert_editor_state(indoc! {"
2050 one two
2051 \t\tt«hree
2052 ˇ»four
2053 "});
2054 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2055 cx.assert_editor_state(indoc! {"
2056 one two
2057 \tt«hree
2058 ˇ»four
2059 "});
2060 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2061 cx.assert_editor_state(indoc! {"
2062 one two
2063 t«hree
2064 ˇ»four
2065 "});
2066
2067 // Ensure that indenting/outdenting works when the cursor is at column 0.
2068 cx.set_state(indoc! {"
2069 one two
2070 ˇthree
2071 four
2072 "});
2073 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2074 cx.assert_editor_state(indoc! {"
2075 one two
2076 ˇthree
2077 four
2078 "});
2079 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2080 cx.assert_editor_state(indoc! {"
2081 one two
2082 \tˇthree
2083 four
2084 "});
2085 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2086 cx.assert_editor_state(indoc! {"
2087 one two
2088 ˇthree
2089 four
2090 "});
2091}
2092
2093#[gpui::test]
2094fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2095 init_test(cx, |settings| {
2096 settings.languages.extend([
2097 (
2098 "TOML".into(),
2099 LanguageSettingsContent {
2100 tab_size: NonZeroU32::new(2),
2101 ..Default::default()
2102 },
2103 ),
2104 (
2105 "Rust".into(),
2106 LanguageSettingsContent {
2107 tab_size: NonZeroU32::new(4),
2108 ..Default::default()
2109 },
2110 ),
2111 ]);
2112 });
2113
2114 let toml_language = Arc::new(Language::new(
2115 LanguageConfig {
2116 name: "TOML".into(),
2117 ..Default::default()
2118 },
2119 None,
2120 ));
2121 let rust_language = Arc::new(Language::new(
2122 LanguageConfig {
2123 name: "Rust".into(),
2124 ..Default::default()
2125 },
2126 None,
2127 ));
2128
2129 let toml_buffer =
2130 cx.add_model(|cx| Buffer::new(0, "a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2131 let rust_buffer = cx.add_model(|cx| {
2132 Buffer::new(0, "const c: usize = 3;\n", cx).with_language(rust_language, cx)
2133 });
2134 let multibuffer = cx.add_model(|cx| {
2135 let mut multibuffer = MultiBuffer::new(0);
2136 multibuffer.push_excerpts(
2137 toml_buffer.clone(),
2138 [ExcerptRange {
2139 context: Point::new(0, 0)..Point::new(2, 0),
2140 primary: None,
2141 }],
2142 cx,
2143 );
2144 multibuffer.push_excerpts(
2145 rust_buffer.clone(),
2146 [ExcerptRange {
2147 context: Point::new(0, 0)..Point::new(1, 0),
2148 primary: None,
2149 }],
2150 cx,
2151 );
2152 multibuffer
2153 });
2154
2155 cx.add_window(|cx| {
2156 let mut editor = build_editor(multibuffer, cx);
2157
2158 assert_eq!(
2159 editor.text(cx),
2160 indoc! {"
2161 a = 1
2162 b = 2
2163
2164 const c: usize = 3;
2165 "}
2166 );
2167
2168 select_ranges(
2169 &mut editor,
2170 indoc! {"
2171 «aˇ» = 1
2172 b = 2
2173
2174 «const c:ˇ» usize = 3;
2175 "},
2176 cx,
2177 );
2178
2179 editor.tab(&Tab, cx);
2180 assert_text_with_selections(
2181 &mut editor,
2182 indoc! {"
2183 «aˇ» = 1
2184 b = 2
2185
2186 «const c:ˇ» usize = 3;
2187 "},
2188 cx,
2189 );
2190 editor.tab_prev(&TabPrev, cx);
2191 assert_text_with_selections(
2192 &mut editor,
2193 indoc! {"
2194 «aˇ» = 1
2195 b = 2
2196
2197 «const c:ˇ» usize = 3;
2198 "},
2199 cx,
2200 );
2201
2202 editor
2203 });
2204}
2205
2206#[gpui::test]
2207async fn test_backspace(cx: &mut gpui::TestAppContext) {
2208 init_test(cx, |_| {});
2209
2210 let mut cx = EditorTestContext::new(cx).await;
2211
2212 // Basic backspace
2213 cx.set_state(indoc! {"
2214 onˇe two three
2215 fou«rˇ» five six
2216 seven «ˇeight nine
2217 »ten
2218 "});
2219 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2220 cx.assert_editor_state(indoc! {"
2221 oˇe two three
2222 fouˇ five six
2223 seven ˇten
2224 "});
2225
2226 // Test backspace inside and around indents
2227 cx.set_state(indoc! {"
2228 zero
2229 ˇone
2230 ˇtwo
2231 ˇ ˇ ˇ three
2232 ˇ ˇ four
2233 "});
2234 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2235 cx.assert_editor_state(indoc! {"
2236 zero
2237 ˇone
2238 ˇtwo
2239 ˇ threeˇ four
2240 "});
2241
2242 // Test backspace with line_mode set to true
2243 cx.update_editor(|e, _| e.selections.line_mode = true);
2244 cx.set_state(indoc! {"
2245 The ˇquick ˇbrown
2246 fox jumps over
2247 the lazy dog
2248 ˇThe qu«ick bˇ»rown"});
2249 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2250 cx.assert_editor_state(indoc! {"
2251 ˇfox jumps over
2252 the lazy dogˇ"});
2253}
2254
2255#[gpui::test]
2256async fn test_delete(cx: &mut gpui::TestAppContext) {
2257 init_test(cx, |_| {});
2258
2259 let mut cx = EditorTestContext::new(cx).await;
2260 cx.set_state(indoc! {"
2261 onˇe two three
2262 fou«rˇ» five six
2263 seven «ˇeight nine
2264 »ten
2265 "});
2266 cx.update_editor(|e, cx| e.delete(&Delete, cx));
2267 cx.assert_editor_state(indoc! {"
2268 onˇ two three
2269 fouˇ five six
2270 seven ˇten
2271 "});
2272
2273 // Test backspace with line_mode set to true
2274 cx.update_editor(|e, _| e.selections.line_mode = true);
2275 cx.set_state(indoc! {"
2276 The ˇquick ˇbrown
2277 fox «ˇjum»ps over
2278 the lazy dog
2279 ˇThe qu«ick bˇ»rown"});
2280 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2281 cx.assert_editor_state("ˇthe lazy dogˇ");
2282}
2283
2284#[gpui::test]
2285fn test_delete_line(cx: &mut TestAppContext) {
2286 init_test(cx, |_| {});
2287
2288 let (_, view) = cx.add_window(|cx| {
2289 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2290 build_editor(buffer, cx)
2291 });
2292 view.update(cx, |view, cx| {
2293 view.change_selections(None, cx, |s| {
2294 s.select_display_ranges([
2295 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2296 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
2297 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2298 ])
2299 });
2300 view.delete_line(&DeleteLine, cx);
2301 assert_eq!(view.display_text(cx), "ghi");
2302 assert_eq!(
2303 view.selections.display_ranges(cx),
2304 vec![
2305 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
2306 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)
2307 ]
2308 );
2309 });
2310
2311 let (_, view) = cx.add_window(|cx| {
2312 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2313 build_editor(buffer, cx)
2314 });
2315 view.update(cx, |view, cx| {
2316 view.change_selections(None, cx, |s| {
2317 s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)])
2318 });
2319 view.delete_line(&DeleteLine, cx);
2320 assert_eq!(view.display_text(cx), "ghi\n");
2321 assert_eq!(
2322 view.selections.display_ranges(cx),
2323 vec![DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)]
2324 );
2325 });
2326}
2327
2328#[gpui::test]
2329fn test_duplicate_line(cx: &mut TestAppContext) {
2330 init_test(cx, |_| {});
2331
2332 let (_, view) = cx.add_window(|cx| {
2333 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2334 build_editor(buffer, cx)
2335 });
2336 view.update(cx, |view, cx| {
2337 view.change_selections(None, cx, |s| {
2338 s.select_display_ranges([
2339 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
2340 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2341 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
2342 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2343 ])
2344 });
2345 view.duplicate_line(&DuplicateLine, cx);
2346 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
2347 assert_eq!(
2348 view.selections.display_ranges(cx),
2349 vec![
2350 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
2351 DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
2352 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
2353 DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0),
2354 ]
2355 );
2356 });
2357
2358 let (_, view) = cx.add_window(|cx| {
2359 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2360 build_editor(buffer, cx)
2361 });
2362 view.update(cx, |view, cx| {
2363 view.change_selections(None, cx, |s| {
2364 s.select_display_ranges([
2365 DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
2366 DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
2367 ])
2368 });
2369 view.duplicate_line(&DuplicateLine, cx);
2370 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
2371 assert_eq!(
2372 view.selections.display_ranges(cx),
2373 vec![
2374 DisplayPoint::new(3, 1)..DisplayPoint::new(4, 1),
2375 DisplayPoint::new(4, 2)..DisplayPoint::new(5, 1),
2376 ]
2377 );
2378 });
2379}
2380
2381#[gpui::test]
2382fn test_move_line_up_down(cx: &mut TestAppContext) {
2383 init_test(cx, |_| {});
2384
2385 let (_, view) = cx.add_window(|cx| {
2386 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
2387 build_editor(buffer, cx)
2388 });
2389 view.update(cx, |view, cx| {
2390 view.fold_ranges(
2391 vec![
2392 Point::new(0, 2)..Point::new(1, 2),
2393 Point::new(2, 3)..Point::new(4, 1),
2394 Point::new(7, 0)..Point::new(8, 4),
2395 ],
2396 true,
2397 cx,
2398 );
2399 view.change_selections(None, cx, |s| {
2400 s.select_display_ranges([
2401 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2402 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2403 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2404 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2),
2405 ])
2406 });
2407 assert_eq!(
2408 view.display_text(cx),
2409 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
2410 );
2411
2412 view.move_line_up(&MoveLineUp, cx);
2413 assert_eq!(
2414 view.display_text(cx),
2415 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
2416 );
2417 assert_eq!(
2418 view.selections.display_ranges(cx),
2419 vec![
2420 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2421 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2422 DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
2423 DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
2424 ]
2425 );
2426 });
2427
2428 view.update(cx, |view, cx| {
2429 view.move_line_down(&MoveLineDown, cx);
2430 assert_eq!(
2431 view.display_text(cx),
2432 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
2433 );
2434 assert_eq!(
2435 view.selections.display_ranges(cx),
2436 vec![
2437 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
2438 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2439 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2440 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
2441 ]
2442 );
2443 });
2444
2445 view.update(cx, |view, cx| {
2446 view.move_line_down(&MoveLineDown, cx);
2447 assert_eq!(
2448 view.display_text(cx),
2449 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
2450 );
2451 assert_eq!(
2452 view.selections.display_ranges(cx),
2453 vec![
2454 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2455 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
2456 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
2457 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
2458 ]
2459 );
2460 });
2461
2462 view.update(cx, |view, cx| {
2463 view.move_line_up(&MoveLineUp, cx);
2464 assert_eq!(
2465 view.display_text(cx),
2466 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
2467 );
2468 assert_eq!(
2469 view.selections.display_ranges(cx),
2470 vec![
2471 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
2472 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
2473 DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
2474 DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
2475 ]
2476 );
2477 });
2478}
2479
2480#[gpui::test]
2481fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
2482 init_test(cx, |_| {});
2483
2484 let (_, editor) = cx.add_window(|cx| {
2485 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
2486 build_editor(buffer, cx)
2487 });
2488 editor.update(cx, |editor, cx| {
2489 let snapshot = editor.buffer.read(cx).snapshot(cx);
2490 editor.insert_blocks(
2491 [BlockProperties {
2492 style: BlockStyle::Fixed,
2493 position: snapshot.anchor_after(Point::new(2, 0)),
2494 disposition: BlockDisposition::Below,
2495 height: 1,
2496 render: Arc::new(|_| Empty::new().into_any()),
2497 }],
2498 cx,
2499 );
2500 editor.change_selections(None, cx, |s| {
2501 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
2502 });
2503 editor.move_line_down(&MoveLineDown, cx);
2504 });
2505}
2506
2507#[gpui::test]
2508fn test_transpose(cx: &mut TestAppContext) {
2509 init_test(cx, |_| {});
2510
2511 _ = cx
2512 .add_window(|cx| {
2513 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
2514
2515 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
2516 editor.transpose(&Default::default(), cx);
2517 assert_eq!(editor.text(cx), "bac");
2518 assert_eq!(editor.selections.ranges(cx), [2..2]);
2519
2520 editor.transpose(&Default::default(), cx);
2521 assert_eq!(editor.text(cx), "bca");
2522 assert_eq!(editor.selections.ranges(cx), [3..3]);
2523
2524 editor.transpose(&Default::default(), cx);
2525 assert_eq!(editor.text(cx), "bac");
2526 assert_eq!(editor.selections.ranges(cx), [3..3]);
2527
2528 editor
2529 })
2530 .1;
2531
2532 _ = cx
2533 .add_window(|cx| {
2534 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
2535
2536 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
2537 editor.transpose(&Default::default(), cx);
2538 assert_eq!(editor.text(cx), "acb\nde");
2539 assert_eq!(editor.selections.ranges(cx), [3..3]);
2540
2541 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
2542 editor.transpose(&Default::default(), cx);
2543 assert_eq!(editor.text(cx), "acbd\ne");
2544 assert_eq!(editor.selections.ranges(cx), [5..5]);
2545
2546 editor.transpose(&Default::default(), cx);
2547 assert_eq!(editor.text(cx), "acbde\n");
2548 assert_eq!(editor.selections.ranges(cx), [6..6]);
2549
2550 editor.transpose(&Default::default(), cx);
2551 assert_eq!(editor.text(cx), "acbd\ne");
2552 assert_eq!(editor.selections.ranges(cx), [6..6]);
2553
2554 editor
2555 })
2556 .1;
2557
2558 _ = cx
2559 .add_window(|cx| {
2560 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
2561
2562 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
2563 editor.transpose(&Default::default(), cx);
2564 assert_eq!(editor.text(cx), "bacd\ne");
2565 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
2566
2567 editor.transpose(&Default::default(), cx);
2568 assert_eq!(editor.text(cx), "bcade\n");
2569 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
2570
2571 editor.transpose(&Default::default(), cx);
2572 assert_eq!(editor.text(cx), "bcda\ne");
2573 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
2574
2575 editor.transpose(&Default::default(), cx);
2576 assert_eq!(editor.text(cx), "bcade\n");
2577 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
2578
2579 editor.transpose(&Default::default(), cx);
2580 assert_eq!(editor.text(cx), "bcaed\n");
2581 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
2582
2583 editor
2584 })
2585 .1;
2586
2587 _ = cx
2588 .add_window(|cx| {
2589 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
2590
2591 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
2592 editor.transpose(&Default::default(), cx);
2593 assert_eq!(editor.text(cx), "🏀🍐✋");
2594 assert_eq!(editor.selections.ranges(cx), [8..8]);
2595
2596 editor.transpose(&Default::default(), cx);
2597 assert_eq!(editor.text(cx), "🏀✋🍐");
2598 assert_eq!(editor.selections.ranges(cx), [11..11]);
2599
2600 editor.transpose(&Default::default(), cx);
2601 assert_eq!(editor.text(cx), "🏀🍐✋");
2602 assert_eq!(editor.selections.ranges(cx), [11..11]);
2603
2604 editor
2605 })
2606 .1;
2607}
2608
2609#[gpui::test]
2610async fn test_clipboard(cx: &mut gpui::TestAppContext) {
2611 init_test(cx, |_| {});
2612
2613 let mut cx = EditorTestContext::new(cx).await;
2614
2615 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
2616 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2617 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
2618
2619 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
2620 cx.set_state("two ˇfour ˇsix ˇ");
2621 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2622 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
2623
2624 // Paste again but with only two cursors. Since the number of cursors doesn't
2625 // match the number of slices in the clipboard, the entire clipboard text
2626 // is pasted at each cursor.
2627 cx.set_state("ˇtwo one✅ four three six five ˇ");
2628 cx.update_editor(|e, cx| {
2629 e.handle_input("( ", cx);
2630 e.paste(&Paste, cx);
2631 e.handle_input(") ", cx);
2632 });
2633 cx.assert_editor_state(
2634 &([
2635 "( one✅ ",
2636 "three ",
2637 "five ) ˇtwo one✅ four three six five ( one✅ ",
2638 "three ",
2639 "five ) ˇ",
2640 ]
2641 .join("\n")),
2642 );
2643
2644 // Cut with three selections, one of which is full-line.
2645 cx.set_state(indoc! {"
2646 1«2ˇ»3
2647 4ˇ567
2648 «8ˇ»9"});
2649 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2650 cx.assert_editor_state(indoc! {"
2651 1ˇ3
2652 ˇ9"});
2653
2654 // Paste with three selections, noticing how the copied selection that was full-line
2655 // gets inserted before the second cursor.
2656 cx.set_state(indoc! {"
2657 1ˇ3
2658 9ˇ
2659 «oˇ»ne"});
2660 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2661 cx.assert_editor_state(indoc! {"
2662 12ˇ3
2663 4567
2664 9ˇ
2665 8ˇne"});
2666
2667 // Copy with a single cursor only, which writes the whole line into the clipboard.
2668 cx.set_state(indoc! {"
2669 The quick brown
2670 fox juˇmps over
2671 the lazy dog"});
2672 cx.update_editor(|e, cx| e.copy(&Copy, cx));
2673 cx.cx.assert_clipboard_content(Some("fox jumps over\n"));
2674
2675 // Paste with three selections, noticing how the copied full-line selection is inserted
2676 // before the empty selections but replaces the selection that is non-empty.
2677 cx.set_state(indoc! {"
2678 Tˇhe quick brown
2679 «foˇ»x jumps over
2680 tˇhe lazy dog"});
2681 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2682 cx.assert_editor_state(indoc! {"
2683 fox jumps over
2684 Tˇhe quick brown
2685 fox jumps over
2686 ˇx jumps over
2687 fox jumps over
2688 tˇhe lazy dog"});
2689}
2690
2691#[gpui::test]
2692async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
2693 init_test(cx, |_| {});
2694
2695 let mut cx = EditorTestContext::new(cx).await;
2696 let language = Arc::new(Language::new(
2697 LanguageConfig::default(),
2698 Some(tree_sitter_rust::language()),
2699 ));
2700 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2701
2702 // Cut an indented block, without the leading whitespace.
2703 cx.set_state(indoc! {"
2704 const a: B = (
2705 c(),
2706 «d(
2707 e,
2708 f
2709 )ˇ»
2710 );
2711 "});
2712 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2713 cx.assert_editor_state(indoc! {"
2714 const a: B = (
2715 c(),
2716 ˇ
2717 );
2718 "});
2719
2720 // Paste it at the same position.
2721 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2722 cx.assert_editor_state(indoc! {"
2723 const a: B = (
2724 c(),
2725 d(
2726 e,
2727 f
2728 )ˇ
2729 );
2730 "});
2731
2732 // Paste it at a line with a lower indent level.
2733 cx.set_state(indoc! {"
2734 ˇ
2735 const a: B = (
2736 c(),
2737 );
2738 "});
2739 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2740 cx.assert_editor_state(indoc! {"
2741 d(
2742 e,
2743 f
2744 )ˇ
2745 const a: B = (
2746 c(),
2747 );
2748 "});
2749
2750 // Cut an indented block, with the leading whitespace.
2751 cx.set_state(indoc! {"
2752 const a: B = (
2753 c(),
2754 « d(
2755 e,
2756 f
2757 )
2758 ˇ»);
2759 "});
2760 cx.update_editor(|e, cx| e.cut(&Cut, cx));
2761 cx.assert_editor_state(indoc! {"
2762 const a: B = (
2763 c(),
2764 ˇ);
2765 "});
2766
2767 // Paste it at the same position.
2768 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2769 cx.assert_editor_state(indoc! {"
2770 const a: B = (
2771 c(),
2772 d(
2773 e,
2774 f
2775 )
2776 ˇ);
2777 "});
2778
2779 // Paste it at a line with a higher indent level.
2780 cx.set_state(indoc! {"
2781 const a: B = (
2782 c(),
2783 d(
2784 e,
2785 fˇ
2786 )
2787 );
2788 "});
2789 cx.update_editor(|e, cx| e.paste(&Paste, cx));
2790 cx.assert_editor_state(indoc! {"
2791 const a: B = (
2792 c(),
2793 d(
2794 e,
2795 f d(
2796 e,
2797 f
2798 )
2799 ˇ
2800 )
2801 );
2802 "});
2803}
2804
2805#[gpui::test]
2806fn test_select_all(cx: &mut TestAppContext) {
2807 init_test(cx, |_| {});
2808
2809 let (_, view) = cx.add_window(|cx| {
2810 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
2811 build_editor(buffer, cx)
2812 });
2813 view.update(cx, |view, cx| {
2814 view.select_all(&SelectAll, cx);
2815 assert_eq!(
2816 view.selections.display_ranges(cx),
2817 &[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)]
2818 );
2819 });
2820}
2821
2822#[gpui::test]
2823fn test_select_line(cx: &mut TestAppContext) {
2824 init_test(cx, |_| {});
2825
2826 let (_, view) = cx.add_window(|cx| {
2827 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
2828 build_editor(buffer, cx)
2829 });
2830 view.update(cx, |view, cx| {
2831 view.change_selections(None, cx, |s| {
2832 s.select_display_ranges([
2833 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
2834 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2835 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
2836 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2),
2837 ])
2838 });
2839 view.select_line(&SelectLine, cx);
2840 assert_eq!(
2841 view.selections.display_ranges(cx),
2842 vec![
2843 DisplayPoint::new(0, 0)..DisplayPoint::new(2, 0),
2844 DisplayPoint::new(4, 0)..DisplayPoint::new(5, 0),
2845 ]
2846 );
2847 });
2848
2849 view.update(cx, |view, cx| {
2850 view.select_line(&SelectLine, cx);
2851 assert_eq!(
2852 view.selections.display_ranges(cx),
2853 vec![
2854 DisplayPoint::new(0, 0)..DisplayPoint::new(3, 0),
2855 DisplayPoint::new(4, 0)..DisplayPoint::new(5, 5),
2856 ]
2857 );
2858 });
2859
2860 view.update(cx, |view, cx| {
2861 view.select_line(&SelectLine, cx);
2862 assert_eq!(
2863 view.selections.display_ranges(cx),
2864 vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)]
2865 );
2866 });
2867}
2868
2869#[gpui::test]
2870fn test_split_selection_into_lines(cx: &mut TestAppContext) {
2871 init_test(cx, |_| {});
2872
2873 let (_, view) = cx.add_window(|cx| {
2874 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
2875 build_editor(buffer, cx)
2876 });
2877 view.update(cx, |view, cx| {
2878 view.fold_ranges(
2879 vec![
2880 Point::new(0, 2)..Point::new(1, 2),
2881 Point::new(2, 3)..Point::new(4, 1),
2882 Point::new(7, 0)..Point::new(8, 4),
2883 ],
2884 true,
2885 cx,
2886 );
2887 view.change_selections(None, cx, |s| {
2888 s.select_display_ranges([
2889 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
2890 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2891 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
2892 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
2893 ])
2894 });
2895 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
2896 });
2897
2898 view.update(cx, |view, cx| {
2899 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
2900 assert_eq!(
2901 view.display_text(cx),
2902 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
2903 );
2904 assert_eq!(
2905 view.selections.display_ranges(cx),
2906 [
2907 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
2908 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
2909 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
2910 DisplayPoint::new(5, 4)..DisplayPoint::new(5, 4)
2911 ]
2912 );
2913 });
2914
2915 view.update(cx, |view, cx| {
2916 view.change_selections(None, cx, |s| {
2917 s.select_display_ranges([DisplayPoint::new(5, 0)..DisplayPoint::new(0, 1)])
2918 });
2919 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
2920 assert_eq!(
2921 view.display_text(cx),
2922 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
2923 );
2924 assert_eq!(
2925 view.selections.display_ranges(cx),
2926 [
2927 DisplayPoint::new(0, 5)..DisplayPoint::new(0, 5),
2928 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
2929 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
2930 DisplayPoint::new(3, 5)..DisplayPoint::new(3, 5),
2931 DisplayPoint::new(4, 5)..DisplayPoint::new(4, 5),
2932 DisplayPoint::new(5, 5)..DisplayPoint::new(5, 5),
2933 DisplayPoint::new(6, 5)..DisplayPoint::new(6, 5),
2934 DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0)
2935 ]
2936 );
2937 });
2938}
2939
2940#[gpui::test]
2941fn test_add_selection_above_below(cx: &mut TestAppContext) {
2942 init_test(cx, |_| {});
2943
2944 let (_, view) = cx.add_window(|cx| {
2945 let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
2946 build_editor(buffer, cx)
2947 });
2948
2949 view.update(cx, |view, cx| {
2950 view.change_selections(None, cx, |s| {
2951 s.select_display_ranges([DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)])
2952 });
2953 });
2954 view.update(cx, |view, cx| {
2955 view.add_selection_above(&AddSelectionAbove, cx);
2956 assert_eq!(
2957 view.selections.display_ranges(cx),
2958 vec![
2959 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
2960 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
2961 ]
2962 );
2963 });
2964
2965 view.update(cx, |view, cx| {
2966 view.add_selection_above(&AddSelectionAbove, cx);
2967 assert_eq!(
2968 view.selections.display_ranges(cx),
2969 vec![
2970 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
2971 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
2972 ]
2973 );
2974 });
2975
2976 view.update(cx, |view, cx| {
2977 view.add_selection_below(&AddSelectionBelow, cx);
2978 assert_eq!(
2979 view.selections.display_ranges(cx),
2980 vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)]
2981 );
2982
2983 view.undo_selection(&UndoSelection, cx);
2984 assert_eq!(
2985 view.selections.display_ranges(cx),
2986 vec![
2987 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
2988 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
2989 ]
2990 );
2991
2992 view.redo_selection(&RedoSelection, cx);
2993 assert_eq!(
2994 view.selections.display_ranges(cx),
2995 vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)]
2996 );
2997 });
2998
2999 view.update(cx, |view, cx| {
3000 view.add_selection_below(&AddSelectionBelow, cx);
3001 assert_eq!(
3002 view.selections.display_ranges(cx),
3003 vec![
3004 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3),
3005 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3)
3006 ]
3007 );
3008 });
3009
3010 view.update(cx, |view, cx| {
3011 view.add_selection_below(&AddSelectionBelow, cx);
3012 assert_eq!(
3013 view.selections.display_ranges(cx),
3014 vec![
3015 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3),
3016 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3)
3017 ]
3018 );
3019 });
3020
3021 view.update(cx, |view, cx| {
3022 view.change_selections(None, cx, |s| {
3023 s.select_display_ranges([DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)])
3024 });
3025 });
3026 view.update(cx, |view, cx| {
3027 view.add_selection_below(&AddSelectionBelow, cx);
3028 assert_eq!(
3029 view.selections.display_ranges(cx),
3030 vec![
3031 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
3032 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3)
3033 ]
3034 );
3035 });
3036
3037 view.update(cx, |view, cx| {
3038 view.add_selection_below(&AddSelectionBelow, cx);
3039 assert_eq!(
3040 view.selections.display_ranges(cx),
3041 vec![
3042 DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
3043 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3)
3044 ]
3045 );
3046 });
3047
3048 view.update(cx, |view, cx| {
3049 view.add_selection_above(&AddSelectionAbove, cx);
3050 assert_eq!(
3051 view.selections.display_ranges(cx),
3052 vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
3053 );
3054 });
3055
3056 view.update(cx, |view, cx| {
3057 view.add_selection_above(&AddSelectionAbove, cx);
3058 assert_eq!(
3059 view.selections.display_ranges(cx),
3060 vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
3061 );
3062 });
3063
3064 view.update(cx, |view, cx| {
3065 view.change_selections(None, cx, |s| {
3066 s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(1, 4)])
3067 });
3068 view.add_selection_below(&AddSelectionBelow, cx);
3069 assert_eq!(
3070 view.selections.display_ranges(cx),
3071 vec![
3072 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
3073 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
3074 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
3075 ]
3076 );
3077 });
3078
3079 view.update(cx, |view, cx| {
3080 view.add_selection_below(&AddSelectionBelow, cx);
3081 assert_eq!(
3082 view.selections.display_ranges(cx),
3083 vec![
3084 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
3085 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
3086 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
3087 DisplayPoint::new(4, 1)..DisplayPoint::new(4, 4),
3088 ]
3089 );
3090 });
3091
3092 view.update(cx, |view, cx| {
3093 view.add_selection_above(&AddSelectionAbove, cx);
3094 assert_eq!(
3095 view.selections.display_ranges(cx),
3096 vec![
3097 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
3098 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
3099 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
3100 ]
3101 );
3102 });
3103
3104 view.update(cx, |view, cx| {
3105 view.change_selections(None, cx, |s| {
3106 s.select_display_ranges([DisplayPoint::new(4, 3)..DisplayPoint::new(1, 1)])
3107 });
3108 });
3109 view.update(cx, |view, cx| {
3110 view.add_selection_above(&AddSelectionAbove, cx);
3111 assert_eq!(
3112 view.selections.display_ranges(cx),
3113 vec![
3114 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 1),
3115 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
3116 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
3117 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
3118 ]
3119 );
3120 });
3121
3122 view.update(cx, |view, cx| {
3123 view.add_selection_below(&AddSelectionBelow, cx);
3124 assert_eq!(
3125 view.selections.display_ranges(cx),
3126 vec![
3127 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
3128 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
3129 DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
3130 ]
3131 );
3132 });
3133}
3134
3135#[gpui::test]
3136async fn test_select_next(cx: &mut gpui::TestAppContext) {
3137 init_test(cx, |_| {});
3138
3139 let mut cx = EditorTestContext::new(cx).await;
3140 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
3141
3142 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3143 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3144
3145 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3146 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
3147
3148 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3149 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3150
3151 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3152 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
3153
3154 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3155 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3156
3157 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx));
3158 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3159}
3160
3161#[gpui::test]
3162async fn test_select_previous(cx: &mut gpui::TestAppContext) {
3163 init_test(cx, |_| {});
3164 {
3165 // `Select previous` without a selection (selects wordwise)
3166 let mut cx = EditorTestContext::new(cx).await;
3167 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
3168
3169 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3170 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3171
3172 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3173 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
3174
3175 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3176 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3177
3178 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3179 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
3180
3181 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3182 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
3183
3184 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3185 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3186 }
3187 {
3188 // `Select previous` with a selection
3189 let mut cx = EditorTestContext::new(cx).await;
3190 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
3191
3192 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3193 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
3194
3195 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3196 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
3197
3198 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3199 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
3200
3201 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3202 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
3203
3204 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3205 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
3206
3207 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx));
3208 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
3209 }
3210}
3211
3212#[gpui::test]
3213async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
3214 init_test(cx, |_| {});
3215
3216 let language = Arc::new(Language::new(
3217 LanguageConfig::default(),
3218 Some(tree_sitter_rust::language()),
3219 ));
3220
3221 let text = r#"
3222 use mod1::mod2::{mod3, mod4};
3223
3224 fn fn_1(param1: bool, param2: &str) {
3225 let var1 = "text";
3226 }
3227 "#
3228 .unindent();
3229
3230 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3231 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3232 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
3233 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
3234 .await;
3235
3236 view.update(cx, |view, cx| {
3237 view.change_selections(None, cx, |s| {
3238 s.select_display_ranges([
3239 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3240 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3241 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3242 ]);
3243 });
3244 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3245 });
3246 assert_eq!(
3247 view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
3248 &[
3249 DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
3250 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3251 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
3252 ]
3253 );
3254
3255 view.update(cx, |view, cx| {
3256 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3257 });
3258 assert_eq!(
3259 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3260 &[
3261 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3262 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
3263 ]
3264 );
3265
3266 view.update(cx, |view, cx| {
3267 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3268 });
3269 assert_eq!(
3270 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3271 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
3272 );
3273
3274 // Trying to expand the selected syntax node one more time has no effect.
3275 view.update(cx, |view, cx| {
3276 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3277 });
3278 assert_eq!(
3279 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3280 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
3281 );
3282
3283 view.update(cx, |view, cx| {
3284 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3285 });
3286 assert_eq!(
3287 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3288 &[
3289 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3290 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
3291 ]
3292 );
3293
3294 view.update(cx, |view, cx| {
3295 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3296 });
3297 assert_eq!(
3298 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3299 &[
3300 DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
3301 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3302 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
3303 ]
3304 );
3305
3306 view.update(cx, |view, cx| {
3307 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3308 });
3309 assert_eq!(
3310 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3311 &[
3312 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3313 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3314 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3315 ]
3316 );
3317
3318 // Trying to shrink the selected syntax node one more time has no effect.
3319 view.update(cx, |view, cx| {
3320 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3321 });
3322 assert_eq!(
3323 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3324 &[
3325 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3326 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3327 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3328 ]
3329 );
3330
3331 // Ensure that we keep expanding the selection if the larger selection starts or ends within
3332 // a fold.
3333 view.update(cx, |view, cx| {
3334 view.fold_ranges(
3335 vec![
3336 Point::new(0, 21)..Point::new(0, 24),
3337 Point::new(3, 20)..Point::new(3, 22),
3338 ],
3339 true,
3340 cx,
3341 );
3342 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3343 });
3344 assert_eq!(
3345 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3346 &[
3347 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3348 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3349 DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23),
3350 ]
3351 );
3352}
3353
3354#[gpui::test]
3355async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
3356 init_test(cx, |_| {});
3357
3358 let language = Arc::new(
3359 Language::new(
3360 LanguageConfig {
3361 brackets: BracketPairConfig {
3362 pairs: vec![
3363 BracketPair {
3364 start: "{".to_string(),
3365 end: "}".to_string(),
3366 close: false,
3367 newline: true,
3368 },
3369 BracketPair {
3370 start: "(".to_string(),
3371 end: ")".to_string(),
3372 close: false,
3373 newline: true,
3374 },
3375 ],
3376 ..Default::default()
3377 },
3378 ..Default::default()
3379 },
3380 Some(tree_sitter_rust::language()),
3381 )
3382 .with_indents_query(
3383 r#"
3384 (_ "(" ")" @end) @indent
3385 (_ "{" "}" @end) @indent
3386 "#,
3387 )
3388 .unwrap(),
3389 );
3390
3391 let text = "fn a() {}";
3392
3393 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3394 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3395 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
3396 editor
3397 .condition(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
3398 .await;
3399
3400 editor.update(cx, |editor, cx| {
3401 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
3402 editor.newline(&Newline, cx);
3403 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
3404 assert_eq!(
3405 editor.selections.ranges(cx),
3406 &[
3407 Point::new(1, 4)..Point::new(1, 4),
3408 Point::new(3, 4)..Point::new(3, 4),
3409 Point::new(5, 0)..Point::new(5, 0)
3410 ]
3411 );
3412 });
3413}
3414
3415#[gpui::test]
3416async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
3417 init_test(cx, |_| {});
3418
3419 let mut cx = EditorTestContext::new(cx).await;
3420
3421 let language = Arc::new(Language::new(
3422 LanguageConfig {
3423 brackets: BracketPairConfig {
3424 pairs: vec![
3425 BracketPair {
3426 start: "{".to_string(),
3427 end: "}".to_string(),
3428 close: true,
3429 newline: true,
3430 },
3431 BracketPair {
3432 start: "(".to_string(),
3433 end: ")".to_string(),
3434 close: true,
3435 newline: true,
3436 },
3437 BracketPair {
3438 start: "/*".to_string(),
3439 end: " */".to_string(),
3440 close: true,
3441 newline: true,
3442 },
3443 BracketPair {
3444 start: "[".to_string(),
3445 end: "]".to_string(),
3446 close: false,
3447 newline: true,
3448 },
3449 BracketPair {
3450 start: "\"".to_string(),
3451 end: "\"".to_string(),
3452 close: true,
3453 newline: false,
3454 },
3455 ],
3456 ..Default::default()
3457 },
3458 autoclose_before: "})]".to_string(),
3459 ..Default::default()
3460 },
3461 Some(tree_sitter_rust::language()),
3462 ));
3463
3464 let registry = Arc::new(LanguageRegistry::test());
3465 registry.add(language.clone());
3466 cx.update_buffer(|buffer, cx| {
3467 buffer.set_language_registry(registry);
3468 buffer.set_language(Some(language), cx);
3469 });
3470
3471 cx.set_state(
3472 &r#"
3473 🏀ˇ
3474 εˇ
3475 ❤️ˇ
3476 "#
3477 .unindent(),
3478 );
3479
3480 // autoclose multiple nested brackets at multiple cursors
3481 cx.update_editor(|view, cx| {
3482 view.handle_input("{", cx);
3483 view.handle_input("{", cx);
3484 view.handle_input("{", cx);
3485 });
3486 cx.assert_editor_state(
3487 &"
3488 🏀{{{ˇ}}}
3489 ε{{{ˇ}}}
3490 ❤️{{{ˇ}}}
3491 "
3492 .unindent(),
3493 );
3494
3495 // insert a different closing bracket
3496 cx.update_editor(|view, cx| {
3497 view.handle_input(")", cx);
3498 });
3499 cx.assert_editor_state(
3500 &"
3501 🏀{{{)ˇ}}}
3502 ε{{{)ˇ}}}
3503 ❤️{{{)ˇ}}}
3504 "
3505 .unindent(),
3506 );
3507
3508 // skip over the auto-closed brackets when typing a closing bracket
3509 cx.update_editor(|view, cx| {
3510 view.move_right(&MoveRight, cx);
3511 view.handle_input("}", cx);
3512 view.handle_input("}", cx);
3513 view.handle_input("}", cx);
3514 });
3515 cx.assert_editor_state(
3516 &"
3517 🏀{{{)}}}}ˇ
3518 ε{{{)}}}}ˇ
3519 ❤️{{{)}}}}ˇ
3520 "
3521 .unindent(),
3522 );
3523
3524 // autoclose multi-character pairs
3525 cx.set_state(
3526 &"
3527 ˇ
3528 ˇ
3529 "
3530 .unindent(),
3531 );
3532 cx.update_editor(|view, cx| {
3533 view.handle_input("/", cx);
3534 view.handle_input("*", cx);
3535 });
3536 cx.assert_editor_state(
3537 &"
3538 /*ˇ */
3539 /*ˇ */
3540 "
3541 .unindent(),
3542 );
3543
3544 // one cursor autocloses a multi-character pair, one cursor
3545 // does not autoclose.
3546 cx.set_state(
3547 &"
3548 /ˇ
3549 ˇ
3550 "
3551 .unindent(),
3552 );
3553 cx.update_editor(|view, cx| view.handle_input("*", cx));
3554 cx.assert_editor_state(
3555 &"
3556 /*ˇ */
3557 *ˇ
3558 "
3559 .unindent(),
3560 );
3561
3562 // Don't autoclose if the next character isn't whitespace and isn't
3563 // listed in the language's "autoclose_before" section.
3564 cx.set_state("ˇa b");
3565 cx.update_editor(|view, cx| view.handle_input("{", cx));
3566 cx.assert_editor_state("{ˇa b");
3567
3568 // Don't autoclose if `close` is false for the bracket pair
3569 cx.set_state("ˇ");
3570 cx.update_editor(|view, cx| view.handle_input("[", cx));
3571 cx.assert_editor_state("[ˇ");
3572
3573 // Surround with brackets if text is selected
3574 cx.set_state("«aˇ» b");
3575 cx.update_editor(|view, cx| view.handle_input("{", cx));
3576 cx.assert_editor_state("{«aˇ»} b");
3577
3578 // Autclose pair where the start and end characters are the same
3579 cx.set_state("aˇ");
3580 cx.update_editor(|view, cx| view.handle_input("\"", cx));
3581 cx.assert_editor_state("a\"ˇ\"");
3582 cx.update_editor(|view, cx| view.handle_input("\"", cx));
3583 cx.assert_editor_state("a\"\"ˇ");
3584}
3585
3586#[gpui::test]
3587async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
3588 init_test(cx, |_| {});
3589
3590 let mut cx = EditorTestContext::new(cx).await;
3591
3592 let html_language = Arc::new(
3593 Language::new(
3594 LanguageConfig {
3595 name: "HTML".into(),
3596 brackets: BracketPairConfig {
3597 pairs: vec![
3598 BracketPair {
3599 start: "<".into(),
3600 end: ">".into(),
3601 close: true,
3602 ..Default::default()
3603 },
3604 BracketPair {
3605 start: "{".into(),
3606 end: "}".into(),
3607 close: true,
3608 ..Default::default()
3609 },
3610 BracketPair {
3611 start: "(".into(),
3612 end: ")".into(),
3613 close: true,
3614 ..Default::default()
3615 },
3616 ],
3617 ..Default::default()
3618 },
3619 autoclose_before: "})]>".into(),
3620 ..Default::default()
3621 },
3622 Some(tree_sitter_html::language()),
3623 )
3624 .with_injection_query(
3625 r#"
3626 (script_element
3627 (raw_text) @content
3628 (#set! "language" "javascript"))
3629 "#,
3630 )
3631 .unwrap(),
3632 );
3633
3634 let javascript_language = Arc::new(Language::new(
3635 LanguageConfig {
3636 name: "JavaScript".into(),
3637 brackets: BracketPairConfig {
3638 pairs: vec![
3639 BracketPair {
3640 start: "/*".into(),
3641 end: " */".into(),
3642 close: true,
3643 ..Default::default()
3644 },
3645 BracketPair {
3646 start: "{".into(),
3647 end: "}".into(),
3648 close: true,
3649 ..Default::default()
3650 },
3651 BracketPair {
3652 start: "(".into(),
3653 end: ")".into(),
3654 close: true,
3655 ..Default::default()
3656 },
3657 ],
3658 ..Default::default()
3659 },
3660 autoclose_before: "})]>".into(),
3661 ..Default::default()
3662 },
3663 Some(tree_sitter_javascript::language()),
3664 ));
3665
3666 let registry = Arc::new(LanguageRegistry::test());
3667 registry.add(html_language.clone());
3668 registry.add(javascript_language.clone());
3669
3670 cx.update_buffer(|buffer, cx| {
3671 buffer.set_language_registry(registry);
3672 buffer.set_language(Some(html_language), cx);
3673 });
3674
3675 cx.set_state(
3676 &r#"
3677 <body>ˇ
3678 <script>
3679 var x = 1;ˇ
3680 </script>
3681 </body>ˇ
3682 "#
3683 .unindent(),
3684 );
3685
3686 // Precondition: different languages are active at different locations.
3687 cx.update_editor(|editor, cx| {
3688 let snapshot = editor.snapshot(cx);
3689 let cursors = editor.selections.ranges::<usize>(cx);
3690 let languages = cursors
3691 .iter()
3692 .map(|c| snapshot.language_at(c.start).unwrap().name())
3693 .collect::<Vec<_>>();
3694 assert_eq!(
3695 languages,
3696 &["HTML".into(), "JavaScript".into(), "HTML".into()]
3697 );
3698 });
3699
3700 // Angle brackets autoclose in HTML, but not JavaScript.
3701 cx.update_editor(|editor, cx| {
3702 editor.handle_input("<", cx);
3703 editor.handle_input("a", cx);
3704 });
3705 cx.assert_editor_state(
3706 &r#"
3707 <body><aˇ>
3708 <script>
3709 var x = 1;<aˇ
3710 </script>
3711 </body><aˇ>
3712 "#
3713 .unindent(),
3714 );
3715
3716 // Curly braces and parens autoclose in both HTML and JavaScript.
3717 cx.update_editor(|editor, cx| {
3718 editor.handle_input(" b=", cx);
3719 editor.handle_input("{", cx);
3720 editor.handle_input("c", cx);
3721 editor.handle_input("(", cx);
3722 });
3723 cx.assert_editor_state(
3724 &r#"
3725 <body><a b={c(ˇ)}>
3726 <script>
3727 var x = 1;<a b={c(ˇ)}
3728 </script>
3729 </body><a b={c(ˇ)}>
3730 "#
3731 .unindent(),
3732 );
3733
3734 // Brackets that were already autoclosed are skipped.
3735 cx.update_editor(|editor, cx| {
3736 editor.handle_input(")", cx);
3737 editor.handle_input("d", cx);
3738 editor.handle_input("}", cx);
3739 });
3740 cx.assert_editor_state(
3741 &r#"
3742 <body><a b={c()d}ˇ>
3743 <script>
3744 var x = 1;<a b={c()d}ˇ
3745 </script>
3746 </body><a b={c()d}ˇ>
3747 "#
3748 .unindent(),
3749 );
3750 cx.update_editor(|editor, cx| {
3751 editor.handle_input(">", cx);
3752 });
3753 cx.assert_editor_state(
3754 &r#"
3755 <body><a b={c()d}>ˇ
3756 <script>
3757 var x = 1;<a b={c()d}>ˇ
3758 </script>
3759 </body><a b={c()d}>ˇ
3760 "#
3761 .unindent(),
3762 );
3763
3764 // Reset
3765 cx.set_state(
3766 &r#"
3767 <body>ˇ
3768 <script>
3769 var x = 1;ˇ
3770 </script>
3771 </body>ˇ
3772 "#
3773 .unindent(),
3774 );
3775
3776 cx.update_editor(|editor, cx| {
3777 editor.handle_input("<", cx);
3778 });
3779 cx.assert_editor_state(
3780 &r#"
3781 <body><ˇ>
3782 <script>
3783 var x = 1;<ˇ
3784 </script>
3785 </body><ˇ>
3786 "#
3787 .unindent(),
3788 );
3789
3790 // When backspacing, the closing angle brackets are removed.
3791 cx.update_editor(|editor, cx| {
3792 editor.backspace(&Backspace, cx);
3793 });
3794 cx.assert_editor_state(
3795 &r#"
3796 <body>ˇ
3797 <script>
3798 var x = 1;ˇ
3799 </script>
3800 </body>ˇ
3801 "#
3802 .unindent(),
3803 );
3804
3805 // Block comments autoclose in JavaScript, but not HTML.
3806 cx.update_editor(|editor, cx| {
3807 editor.handle_input("/", cx);
3808 editor.handle_input("*", cx);
3809 });
3810 cx.assert_editor_state(
3811 &r#"
3812 <body>/*ˇ
3813 <script>
3814 var x = 1;/*ˇ */
3815 </script>
3816 </body>/*ˇ
3817 "#
3818 .unindent(),
3819 );
3820}
3821
3822#[gpui::test]
3823async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
3824 init_test(cx, |_| {});
3825
3826 let mut cx = EditorTestContext::new(cx).await;
3827
3828 let rust_language = Arc::new(
3829 Language::new(
3830 LanguageConfig {
3831 name: "Rust".into(),
3832 brackets: serde_json::from_value(json!([
3833 { "start": "{", "end": "}", "close": true, "newline": true },
3834 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
3835 ]))
3836 .unwrap(),
3837 autoclose_before: "})]>".into(),
3838 ..Default::default()
3839 },
3840 Some(tree_sitter_rust::language()),
3841 )
3842 .with_override_query("(string_literal) @string")
3843 .unwrap(),
3844 );
3845
3846 let registry = Arc::new(LanguageRegistry::test());
3847 registry.add(rust_language.clone());
3848
3849 cx.update_buffer(|buffer, cx| {
3850 buffer.set_language_registry(registry);
3851 buffer.set_language(Some(rust_language), cx);
3852 });
3853
3854 cx.set_state(
3855 &r#"
3856 let x = ˇ
3857 "#
3858 .unindent(),
3859 );
3860
3861 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
3862 cx.update_editor(|editor, cx| {
3863 editor.handle_input("\"", cx);
3864 });
3865 cx.assert_editor_state(
3866 &r#"
3867 let x = "ˇ"
3868 "#
3869 .unindent(),
3870 );
3871
3872 // Inserting another quotation mark. The cursor moves across the existing
3873 // automatically-inserted quotation mark.
3874 cx.update_editor(|editor, cx| {
3875 editor.handle_input("\"", cx);
3876 });
3877 cx.assert_editor_state(
3878 &r#"
3879 let x = ""ˇ
3880 "#
3881 .unindent(),
3882 );
3883
3884 // Reset
3885 cx.set_state(
3886 &r#"
3887 let x = ˇ
3888 "#
3889 .unindent(),
3890 );
3891
3892 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
3893 cx.update_editor(|editor, cx| {
3894 editor.handle_input("\"", cx);
3895 editor.handle_input(" ", cx);
3896 editor.move_left(&Default::default(), cx);
3897 editor.handle_input("\\", cx);
3898 editor.handle_input("\"", cx);
3899 });
3900 cx.assert_editor_state(
3901 &r#"
3902 let x = "\"ˇ "
3903 "#
3904 .unindent(),
3905 );
3906
3907 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
3908 // mark. Nothing is inserted.
3909 cx.update_editor(|editor, cx| {
3910 editor.move_right(&Default::default(), cx);
3911 editor.handle_input("\"", cx);
3912 });
3913 cx.assert_editor_state(
3914 &r#"
3915 let x = "\" "ˇ
3916 "#
3917 .unindent(),
3918 );
3919}
3920
3921#[gpui::test]
3922async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
3923 init_test(cx, |_| {});
3924
3925 let language = Arc::new(Language::new(
3926 LanguageConfig {
3927 brackets: BracketPairConfig {
3928 pairs: vec![
3929 BracketPair {
3930 start: "{".to_string(),
3931 end: "}".to_string(),
3932 close: true,
3933 newline: true,
3934 },
3935 BracketPair {
3936 start: "/* ".to_string(),
3937 end: "*/".to_string(),
3938 close: true,
3939 ..Default::default()
3940 },
3941 ],
3942 ..Default::default()
3943 },
3944 ..Default::default()
3945 },
3946 Some(tree_sitter_rust::language()),
3947 ));
3948
3949 let text = r#"
3950 a
3951 b
3952 c
3953 "#
3954 .unindent();
3955
3956 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
3957 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
3958 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
3959 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
3960 .await;
3961
3962 view.update(cx, |view, cx| {
3963 view.change_selections(None, cx, |s| {
3964 s.select_display_ranges([
3965 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3966 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
3967 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
3968 ])
3969 });
3970
3971 view.handle_input("{", cx);
3972 view.handle_input("{", cx);
3973 view.handle_input("{", cx);
3974 assert_eq!(
3975 view.text(cx),
3976 "
3977 {{{a}}}
3978 {{{b}}}
3979 {{{c}}}
3980 "
3981 .unindent()
3982 );
3983 assert_eq!(
3984 view.selections.display_ranges(cx),
3985 [
3986 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 4),
3987 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4),
3988 DisplayPoint::new(2, 3)..DisplayPoint::new(2, 4)
3989 ]
3990 );
3991
3992 view.undo(&Undo, cx);
3993 view.undo(&Undo, cx);
3994 view.undo(&Undo, cx);
3995 assert_eq!(
3996 view.text(cx),
3997 "
3998 a
3999 b
4000 c
4001 "
4002 .unindent()
4003 );
4004 assert_eq!(
4005 view.selections.display_ranges(cx),
4006 [
4007 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4008 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4009 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
4010 ]
4011 );
4012
4013 // Ensure inserting the first character of a multi-byte bracket pair
4014 // doesn't surround the selections with the bracket.
4015 view.handle_input("/", cx);
4016 assert_eq!(
4017 view.text(cx),
4018 "
4019 /
4020 /
4021 /
4022 "
4023 .unindent()
4024 );
4025 assert_eq!(
4026 view.selections.display_ranges(cx),
4027 [
4028 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
4029 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
4030 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
4031 ]
4032 );
4033
4034 view.undo(&Undo, cx);
4035 assert_eq!(
4036 view.text(cx),
4037 "
4038 a
4039 b
4040 c
4041 "
4042 .unindent()
4043 );
4044 assert_eq!(
4045 view.selections.display_ranges(cx),
4046 [
4047 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4048 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4049 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
4050 ]
4051 );
4052
4053 // Ensure inserting the last character of a multi-byte bracket pair
4054 // doesn't surround the selections with the bracket.
4055 view.handle_input("*", cx);
4056 assert_eq!(
4057 view.text(cx),
4058 "
4059 *
4060 *
4061 *
4062 "
4063 .unindent()
4064 );
4065 assert_eq!(
4066 view.selections.display_ranges(cx),
4067 [
4068 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
4069 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
4070 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
4071 ]
4072 );
4073 });
4074}
4075
4076#[gpui::test]
4077async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
4078 init_test(cx, |_| {});
4079
4080 let language = Arc::new(Language::new(
4081 LanguageConfig {
4082 brackets: BracketPairConfig {
4083 pairs: vec![BracketPair {
4084 start: "{".to_string(),
4085 end: "}".to_string(),
4086 close: true,
4087 newline: true,
4088 }],
4089 ..Default::default()
4090 },
4091 autoclose_before: "}".to_string(),
4092 ..Default::default()
4093 },
4094 Some(tree_sitter_rust::language()),
4095 ));
4096
4097 let text = r#"
4098 a
4099 b
4100 c
4101 "#
4102 .unindent();
4103
4104 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
4105 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4106 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4107 editor
4108 .condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4109 .await;
4110
4111 editor.update(cx, |editor, cx| {
4112 editor.change_selections(None, cx, |s| {
4113 s.select_ranges([
4114 Point::new(0, 1)..Point::new(0, 1),
4115 Point::new(1, 1)..Point::new(1, 1),
4116 Point::new(2, 1)..Point::new(2, 1),
4117 ])
4118 });
4119
4120 editor.handle_input("{", cx);
4121 editor.handle_input("{", cx);
4122 editor.handle_input("_", cx);
4123 assert_eq!(
4124 editor.text(cx),
4125 "
4126 a{{_}}
4127 b{{_}}
4128 c{{_}}
4129 "
4130 .unindent()
4131 );
4132 assert_eq!(
4133 editor.selections.ranges::<Point>(cx),
4134 [
4135 Point::new(0, 4)..Point::new(0, 4),
4136 Point::new(1, 4)..Point::new(1, 4),
4137 Point::new(2, 4)..Point::new(2, 4)
4138 ]
4139 );
4140
4141 editor.backspace(&Default::default(), cx);
4142 editor.backspace(&Default::default(), cx);
4143 assert_eq!(
4144 editor.text(cx),
4145 "
4146 a{}
4147 b{}
4148 c{}
4149 "
4150 .unindent()
4151 );
4152 assert_eq!(
4153 editor.selections.ranges::<Point>(cx),
4154 [
4155 Point::new(0, 2)..Point::new(0, 2),
4156 Point::new(1, 2)..Point::new(1, 2),
4157 Point::new(2, 2)..Point::new(2, 2)
4158 ]
4159 );
4160
4161 editor.delete_to_previous_word_start(&Default::default(), cx);
4162 assert_eq!(
4163 editor.text(cx),
4164 "
4165 a
4166 b
4167 c
4168 "
4169 .unindent()
4170 );
4171 assert_eq!(
4172 editor.selections.ranges::<Point>(cx),
4173 [
4174 Point::new(0, 1)..Point::new(0, 1),
4175 Point::new(1, 1)..Point::new(1, 1),
4176 Point::new(2, 1)..Point::new(2, 1)
4177 ]
4178 );
4179 });
4180}
4181
4182#[gpui::test]
4183async fn test_snippets(cx: &mut gpui::TestAppContext) {
4184 init_test(cx, |_| {});
4185
4186 let (text, insertion_ranges) = marked_text_ranges(
4187 indoc! {"
4188 a.ˇ b
4189 a.ˇ b
4190 a.ˇ b
4191 "},
4192 false,
4193 );
4194
4195 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
4196 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4197
4198 editor.update(cx, |editor, cx| {
4199 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
4200
4201 editor
4202 .insert_snippet(&insertion_ranges, snippet, cx)
4203 .unwrap();
4204
4205 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
4206 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
4207 assert_eq!(editor.text(cx), expected_text);
4208 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
4209 }
4210
4211 assert(
4212 editor,
4213 cx,
4214 indoc! {"
4215 a.f(«one», two, «three») b
4216 a.f(«one», two, «three») b
4217 a.f(«one», two, «three») b
4218 "},
4219 );
4220
4221 // Can't move earlier than the first tab stop
4222 assert!(!editor.move_to_prev_snippet_tabstop(cx));
4223 assert(
4224 editor,
4225 cx,
4226 indoc! {"
4227 a.f(«one», two, «three») b
4228 a.f(«one», two, «three») b
4229 a.f(«one», two, «three») b
4230 "},
4231 );
4232
4233 assert!(editor.move_to_next_snippet_tabstop(cx));
4234 assert(
4235 editor,
4236 cx,
4237 indoc! {"
4238 a.f(one, «two», three) b
4239 a.f(one, «two», three) b
4240 a.f(one, «two», three) b
4241 "},
4242 );
4243
4244 editor.move_to_prev_snippet_tabstop(cx);
4245 assert(
4246 editor,
4247 cx,
4248 indoc! {"
4249 a.f(«one», two, «three») b
4250 a.f(«one», two, «three») b
4251 a.f(«one», two, «three») b
4252 "},
4253 );
4254
4255 assert!(editor.move_to_next_snippet_tabstop(cx));
4256 assert(
4257 editor,
4258 cx,
4259 indoc! {"
4260 a.f(one, «two», three) b
4261 a.f(one, «two», three) b
4262 a.f(one, «two», three) b
4263 "},
4264 );
4265 assert!(editor.move_to_next_snippet_tabstop(cx));
4266 assert(
4267 editor,
4268 cx,
4269 indoc! {"
4270 a.f(one, two, three)ˇ b
4271 a.f(one, two, three)ˇ b
4272 a.f(one, two, three)ˇ b
4273 "},
4274 );
4275
4276 // As soon as the last tab stop is reached, snippet state is gone
4277 editor.move_to_prev_snippet_tabstop(cx);
4278 assert(
4279 editor,
4280 cx,
4281 indoc! {"
4282 a.f(one, two, three)ˇ b
4283 a.f(one, two, three)ˇ b
4284 a.f(one, two, three)ˇ b
4285 "},
4286 );
4287 });
4288}
4289
4290#[gpui::test]
4291async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
4292 init_test(cx, |_| {});
4293
4294 let mut language = Language::new(
4295 LanguageConfig {
4296 name: "Rust".into(),
4297 path_suffixes: vec!["rs".to_string()],
4298 ..Default::default()
4299 },
4300 Some(tree_sitter_rust::language()),
4301 );
4302 let mut fake_servers = language
4303 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4304 capabilities: lsp::ServerCapabilities {
4305 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4306 ..Default::default()
4307 },
4308 ..Default::default()
4309 }))
4310 .await;
4311
4312 let fs = FakeFs::new(cx.background());
4313 fs.insert_file("/file.rs", Default::default()).await;
4314
4315 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
4316 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
4317 let buffer = project
4318 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
4319 .await
4320 .unwrap();
4321
4322 cx.foreground().start_waiting();
4323 let fake_server = fake_servers.next().await.unwrap();
4324
4325 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4326 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4327 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4328 assert!(cx.read(|cx| editor.is_dirty(cx)));
4329
4330 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4331 fake_server
4332 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4333 assert_eq!(
4334 params.text_document.uri,
4335 lsp::Url::from_file_path("/file.rs").unwrap()
4336 );
4337 assert_eq!(params.options.tab_size, 4);
4338 Ok(Some(vec![lsp::TextEdit::new(
4339 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4340 ", ".to_string(),
4341 )]))
4342 })
4343 .next()
4344 .await;
4345 cx.foreground().start_waiting();
4346 save.await.unwrap();
4347 assert_eq!(
4348 editor.read_with(cx, |editor, cx| editor.text(cx)),
4349 "one, two\nthree\n"
4350 );
4351 assert!(!cx.read(|cx| editor.is_dirty(cx)));
4352
4353 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4354 assert!(cx.read(|cx| editor.is_dirty(cx)));
4355
4356 // Ensure we can still save even if formatting hangs.
4357 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4358 assert_eq!(
4359 params.text_document.uri,
4360 lsp::Url::from_file_path("/file.rs").unwrap()
4361 );
4362 futures::future::pending::<()>().await;
4363 unreachable!()
4364 });
4365 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4366 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
4367 cx.foreground().start_waiting();
4368 save.await.unwrap();
4369 assert_eq!(
4370 editor.read_with(cx, |editor, cx| editor.text(cx)),
4371 "one\ntwo\nthree\n"
4372 );
4373 assert!(!cx.read(|cx| editor.is_dirty(cx)));
4374
4375 // Set rust language override and assert overridden tabsize is sent to language server
4376 update_test_settings(cx, |settings| {
4377 settings.languages.insert(
4378 "Rust".into(),
4379 LanguageSettingsContent {
4380 tab_size: NonZeroU32::new(8),
4381 ..Default::default()
4382 },
4383 );
4384 });
4385
4386 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4387 fake_server
4388 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4389 assert_eq!(
4390 params.text_document.uri,
4391 lsp::Url::from_file_path("/file.rs").unwrap()
4392 );
4393 assert_eq!(params.options.tab_size, 8);
4394 Ok(Some(vec![]))
4395 })
4396 .next()
4397 .await;
4398 cx.foreground().start_waiting();
4399 save.await.unwrap();
4400}
4401
4402#[gpui::test]
4403async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
4404 init_test(cx, |_| {});
4405
4406 let mut language = Language::new(
4407 LanguageConfig {
4408 name: "Rust".into(),
4409 path_suffixes: vec!["rs".to_string()],
4410 ..Default::default()
4411 },
4412 Some(tree_sitter_rust::language()),
4413 );
4414 let mut fake_servers = language
4415 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4416 capabilities: lsp::ServerCapabilities {
4417 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
4418 ..Default::default()
4419 },
4420 ..Default::default()
4421 }))
4422 .await;
4423
4424 let fs = FakeFs::new(cx.background());
4425 fs.insert_file("/file.rs", Default::default()).await;
4426
4427 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
4428 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
4429 let buffer = project
4430 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
4431 .await
4432 .unwrap();
4433
4434 cx.foreground().start_waiting();
4435 let fake_server = fake_servers.next().await.unwrap();
4436
4437 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4438 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4439 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4440 assert!(cx.read(|cx| editor.is_dirty(cx)));
4441
4442 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4443 fake_server
4444 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
4445 assert_eq!(
4446 params.text_document.uri,
4447 lsp::Url::from_file_path("/file.rs").unwrap()
4448 );
4449 assert_eq!(params.options.tab_size, 4);
4450 Ok(Some(vec![lsp::TextEdit::new(
4451 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4452 ", ".to_string(),
4453 )]))
4454 })
4455 .next()
4456 .await;
4457 cx.foreground().start_waiting();
4458 save.await.unwrap();
4459 assert_eq!(
4460 editor.read_with(cx, |editor, cx| editor.text(cx)),
4461 "one, two\nthree\n"
4462 );
4463 assert!(!cx.read(|cx| editor.is_dirty(cx)));
4464
4465 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4466 assert!(cx.read(|cx| editor.is_dirty(cx)));
4467
4468 // Ensure we can still save even if formatting hangs.
4469 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
4470 move |params, _| async move {
4471 assert_eq!(
4472 params.text_document.uri,
4473 lsp::Url::from_file_path("/file.rs").unwrap()
4474 );
4475 futures::future::pending::<()>().await;
4476 unreachable!()
4477 },
4478 );
4479 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4480 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
4481 cx.foreground().start_waiting();
4482 save.await.unwrap();
4483 assert_eq!(
4484 editor.read_with(cx, |editor, cx| editor.text(cx)),
4485 "one\ntwo\nthree\n"
4486 );
4487 assert!(!cx.read(|cx| editor.is_dirty(cx)));
4488
4489 // Set rust language override and assert overridden tabsize is sent to language server
4490 update_test_settings(cx, |settings| {
4491 settings.languages.insert(
4492 "Rust".into(),
4493 LanguageSettingsContent {
4494 tab_size: NonZeroU32::new(8),
4495 ..Default::default()
4496 },
4497 );
4498 });
4499
4500 let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
4501 fake_server
4502 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
4503 assert_eq!(
4504 params.text_document.uri,
4505 lsp::Url::from_file_path("/file.rs").unwrap()
4506 );
4507 assert_eq!(params.options.tab_size, 8);
4508 Ok(Some(vec![]))
4509 })
4510 .next()
4511 .await;
4512 cx.foreground().start_waiting();
4513 save.await.unwrap();
4514}
4515
4516#[gpui::test]
4517async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
4518 init_test(cx, |_| {});
4519
4520 let mut language = Language::new(
4521 LanguageConfig {
4522 name: "Rust".into(),
4523 path_suffixes: vec!["rs".to_string()],
4524 ..Default::default()
4525 },
4526 Some(tree_sitter_rust::language()),
4527 );
4528 let mut fake_servers = language
4529 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4530 capabilities: lsp::ServerCapabilities {
4531 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4532 ..Default::default()
4533 },
4534 ..Default::default()
4535 }))
4536 .await;
4537
4538 let fs = FakeFs::new(cx.background());
4539 fs.insert_file("/file.rs", Default::default()).await;
4540
4541 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
4542 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
4543 let buffer = project
4544 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
4545 .await
4546 .unwrap();
4547
4548 cx.foreground().start_waiting();
4549 let fake_server = fake_servers.next().await.unwrap();
4550
4551 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4552 let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
4553 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4554
4555 let format = editor.update(cx, |editor, cx| {
4556 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
4557 });
4558 fake_server
4559 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4560 assert_eq!(
4561 params.text_document.uri,
4562 lsp::Url::from_file_path("/file.rs").unwrap()
4563 );
4564 assert_eq!(params.options.tab_size, 4);
4565 Ok(Some(vec![lsp::TextEdit::new(
4566 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
4567 ", ".to_string(),
4568 )]))
4569 })
4570 .next()
4571 .await;
4572 cx.foreground().start_waiting();
4573 format.await.unwrap();
4574 assert_eq!(
4575 editor.read_with(cx, |editor, cx| editor.text(cx)),
4576 "one, two\nthree\n"
4577 );
4578
4579 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
4580 // Ensure we don't lock if formatting hangs.
4581 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
4582 assert_eq!(
4583 params.text_document.uri,
4584 lsp::Url::from_file_path("/file.rs").unwrap()
4585 );
4586 futures::future::pending::<()>().await;
4587 unreachable!()
4588 });
4589 let format = editor.update(cx, |editor, cx| {
4590 editor.perform_format(project, FormatTrigger::Manual, cx)
4591 });
4592 cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
4593 cx.foreground().start_waiting();
4594 format.await.unwrap();
4595 assert_eq!(
4596 editor.read_with(cx, |editor, cx| editor.text(cx)),
4597 "one\ntwo\nthree\n"
4598 );
4599}
4600
4601#[gpui::test]
4602async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
4603 init_test(cx, |_| {});
4604
4605 let mut cx = EditorLspTestContext::new_rust(
4606 lsp::ServerCapabilities {
4607 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4608 ..Default::default()
4609 },
4610 cx,
4611 )
4612 .await;
4613
4614 cx.set_state(indoc! {"
4615 one.twoˇ
4616 "});
4617
4618 // The format request takes a long time. When it completes, it inserts
4619 // a newline and an indent before the `.`
4620 cx.lsp
4621 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
4622 let executor = cx.background();
4623 async move {
4624 executor.timer(Duration::from_millis(100)).await;
4625 Ok(Some(vec![lsp::TextEdit {
4626 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
4627 new_text: "\n ".into(),
4628 }]))
4629 }
4630 });
4631
4632 // Submit a format request.
4633 let format_1 = cx
4634 .update_editor(|editor, cx| editor.format(&Format, cx))
4635 .unwrap();
4636 cx.foreground().run_until_parked();
4637
4638 // Submit a second format request.
4639 let format_2 = cx
4640 .update_editor(|editor, cx| editor.format(&Format, cx))
4641 .unwrap();
4642 cx.foreground().run_until_parked();
4643
4644 // Wait for both format requests to complete
4645 cx.foreground().advance_clock(Duration::from_millis(200));
4646 cx.foreground().start_waiting();
4647 format_1.await.unwrap();
4648 cx.foreground().start_waiting();
4649 format_2.await.unwrap();
4650
4651 // The formatting edits only happens once.
4652 cx.assert_editor_state(indoc! {"
4653 one
4654 .twoˇ
4655 "});
4656}
4657
4658#[gpui::test]
4659async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
4660 init_test(cx, |_| {});
4661
4662 let mut cx = EditorLspTestContext::new_rust(
4663 lsp::ServerCapabilities {
4664 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4665 ..Default::default()
4666 },
4667 cx,
4668 )
4669 .await;
4670
4671 // Set up a buffer white some trailing whitespace and no trailing newline.
4672 cx.set_state(
4673 &[
4674 "one ", //
4675 "twoˇ", //
4676 "three ", //
4677 "four", //
4678 ]
4679 .join("\n"),
4680 );
4681
4682 // Submit a format request.
4683 let format = cx
4684 .update_editor(|editor, cx| editor.format(&Format, cx))
4685 .unwrap();
4686
4687 // Record which buffer changes have been sent to the language server
4688 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
4689 cx.lsp
4690 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
4691 let buffer_changes = buffer_changes.clone();
4692 move |params, _| {
4693 buffer_changes.lock().extend(
4694 params
4695 .content_changes
4696 .into_iter()
4697 .map(|e| (e.range.unwrap(), e.text)),
4698 );
4699 }
4700 });
4701
4702 // Handle formatting requests to the language server.
4703 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
4704 let buffer_changes = buffer_changes.clone();
4705 move |_, _| {
4706 // When formatting is requested, trailing whitespace has already been stripped,
4707 // and the trailing newline has already been added.
4708 assert_eq!(
4709 &buffer_changes.lock()[1..],
4710 &[
4711 (
4712 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
4713 "".into()
4714 ),
4715 (
4716 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
4717 "".into()
4718 ),
4719 (
4720 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
4721 "\n".into()
4722 ),
4723 ]
4724 );
4725
4726 // Insert blank lines between each line of the buffer.
4727 async move {
4728 Ok(Some(vec![
4729 lsp::TextEdit {
4730 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
4731 new_text: "\n".into(),
4732 },
4733 lsp::TextEdit {
4734 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
4735 new_text: "\n".into(),
4736 },
4737 ]))
4738 }
4739 }
4740 });
4741
4742 // After formatting the buffer, the trailing whitespace is stripped,
4743 // a newline is appended, and the edits provided by the language server
4744 // have been applied.
4745 format.await.unwrap();
4746 cx.assert_editor_state(
4747 &[
4748 "one", //
4749 "", //
4750 "twoˇ", //
4751 "", //
4752 "three", //
4753 "four", //
4754 "", //
4755 ]
4756 .join("\n"),
4757 );
4758
4759 // Undoing the formatting undoes the trailing whitespace removal, the
4760 // trailing newline, and the LSP edits.
4761 cx.update_buffer(|buffer, cx| buffer.undo(cx));
4762 cx.assert_editor_state(
4763 &[
4764 "one ", //
4765 "twoˇ", //
4766 "three ", //
4767 "four", //
4768 ]
4769 .join("\n"),
4770 );
4771}
4772
4773#[gpui::test]
4774async fn test_completion(cx: &mut gpui::TestAppContext) {
4775 init_test(cx, |_| {});
4776
4777 let mut cx = EditorLspTestContext::new_rust(
4778 lsp::ServerCapabilities {
4779 completion_provider: Some(lsp::CompletionOptions {
4780 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
4781 ..Default::default()
4782 }),
4783 ..Default::default()
4784 },
4785 cx,
4786 )
4787 .await;
4788
4789 cx.set_state(indoc! {"
4790 oneˇ
4791 two
4792 three
4793 "});
4794 cx.simulate_keystroke(".");
4795 handle_completion_request(
4796 &mut cx,
4797 indoc! {"
4798 one.|<>
4799 two
4800 three
4801 "},
4802 vec!["first_completion", "second_completion"],
4803 )
4804 .await;
4805 cx.condition(|editor, _| editor.context_menu_visible())
4806 .await;
4807 let apply_additional_edits = cx.update_editor(|editor, cx| {
4808 editor.move_down(&MoveDown, cx);
4809 editor
4810 .confirm_completion(&ConfirmCompletion::default(), cx)
4811 .unwrap()
4812 });
4813 cx.assert_editor_state(indoc! {"
4814 one.second_completionˇ
4815 two
4816 three
4817 "});
4818
4819 handle_resolve_completion_request(
4820 &mut cx,
4821 Some(vec![
4822 (
4823 //This overlaps with the primary completion edit which is
4824 //misbehavior from the LSP spec, test that we filter it out
4825 indoc! {"
4826 one.second_ˇcompletion
4827 two
4828 threeˇ
4829 "},
4830 "overlapping additional edit",
4831 ),
4832 (
4833 indoc! {"
4834 one.second_completion
4835 two
4836 threeˇ
4837 "},
4838 "\nadditional edit",
4839 ),
4840 ]),
4841 )
4842 .await;
4843 apply_additional_edits.await.unwrap();
4844 cx.assert_editor_state(indoc! {"
4845 one.second_completionˇ
4846 two
4847 three
4848 additional edit
4849 "});
4850
4851 cx.set_state(indoc! {"
4852 one.second_completion
4853 twoˇ
4854 threeˇ
4855 additional edit
4856 "});
4857 cx.simulate_keystroke(" ");
4858 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4859 cx.simulate_keystroke("s");
4860 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4861
4862 cx.assert_editor_state(indoc! {"
4863 one.second_completion
4864 two sˇ
4865 three sˇ
4866 additional edit
4867 "});
4868 handle_completion_request(
4869 &mut cx,
4870 indoc! {"
4871 one.second_completion
4872 two s
4873 three <s|>
4874 additional edit
4875 "},
4876 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
4877 )
4878 .await;
4879 cx.condition(|editor, _| editor.context_menu_visible())
4880 .await;
4881
4882 cx.simulate_keystroke("i");
4883
4884 handle_completion_request(
4885 &mut cx,
4886 indoc! {"
4887 one.second_completion
4888 two si
4889 three <si|>
4890 additional edit
4891 "},
4892 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
4893 )
4894 .await;
4895 cx.condition(|editor, _| editor.context_menu_visible())
4896 .await;
4897
4898 let apply_additional_edits = cx.update_editor(|editor, cx| {
4899 editor
4900 .confirm_completion(&ConfirmCompletion::default(), cx)
4901 .unwrap()
4902 });
4903 cx.assert_editor_state(indoc! {"
4904 one.second_completion
4905 two sixth_completionˇ
4906 three sixth_completionˇ
4907 additional edit
4908 "});
4909
4910 handle_resolve_completion_request(&mut cx, None).await;
4911 apply_additional_edits.await.unwrap();
4912
4913 cx.update(|cx| {
4914 cx.update_global::<SettingsStore, _, _>(|settings, cx| {
4915 settings.update_user_settings::<EditorSettings>(cx, |settings| {
4916 settings.show_completions_on_input = Some(false);
4917 });
4918 })
4919 });
4920 cx.set_state("editorˇ");
4921 cx.simulate_keystroke(".");
4922 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4923 cx.simulate_keystroke("c");
4924 cx.simulate_keystroke("l");
4925 cx.simulate_keystroke("o");
4926 cx.assert_editor_state("editor.cloˇ");
4927 assert!(cx.editor(|e, _| e.context_menu.is_none()));
4928 cx.update_editor(|editor, cx| {
4929 editor.show_completions(&ShowCompletions, cx);
4930 });
4931 handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
4932 cx.condition(|editor, _| editor.context_menu_visible())
4933 .await;
4934 let apply_additional_edits = cx.update_editor(|editor, cx| {
4935 editor
4936 .confirm_completion(&ConfirmCompletion::default(), cx)
4937 .unwrap()
4938 });
4939 cx.assert_editor_state("editor.closeˇ");
4940 handle_resolve_completion_request(&mut cx, None).await;
4941 apply_additional_edits.await.unwrap();
4942}
4943
4944#[gpui::test]
4945async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
4946 init_test(cx, |_| {});
4947
4948 let language = Arc::new(Language::new(
4949 LanguageConfig {
4950 line_comment: Some("// ".into()),
4951 ..Default::default()
4952 },
4953 Some(tree_sitter_rust::language()),
4954 ));
4955
4956 let text = "
4957 fn a() {
4958 //b();
4959 // c();
4960 // d();
4961 }
4962 "
4963 .unindent();
4964
4965 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
4966 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
4967 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
4968
4969 view.update(cx, |editor, cx| {
4970 // If multiple selections intersect a line, the line is only
4971 // toggled once.
4972 editor.change_selections(None, cx, |s| {
4973 s.select_display_ranges([
4974 DisplayPoint::new(1, 3)..DisplayPoint::new(2, 3),
4975 DisplayPoint::new(3, 5)..DisplayPoint::new(3, 6),
4976 ])
4977 });
4978 editor.toggle_comments(&ToggleComments::default(), cx);
4979 assert_eq!(
4980 editor.text(cx),
4981 "
4982 fn a() {
4983 b();
4984 c();
4985 d();
4986 }
4987 "
4988 .unindent()
4989 );
4990
4991 // The comment prefix is inserted at the same column for every line
4992 // in a selection.
4993 editor.change_selections(None, cx, |s| {
4994 s.select_display_ranges([DisplayPoint::new(1, 3)..DisplayPoint::new(3, 6)])
4995 });
4996 editor.toggle_comments(&ToggleComments::default(), cx);
4997 assert_eq!(
4998 editor.text(cx),
4999 "
5000 fn a() {
5001 // b();
5002 // c();
5003 // d();
5004 }
5005 "
5006 .unindent()
5007 );
5008
5009 // If a selection ends at the beginning of a line, that line is not toggled.
5010 editor.change_selections(None, cx, |s| {
5011 s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(3, 0)])
5012 });
5013 editor.toggle_comments(&ToggleComments::default(), cx);
5014 assert_eq!(
5015 editor.text(cx),
5016 "
5017 fn a() {
5018 // b();
5019 c();
5020 // d();
5021 }
5022 "
5023 .unindent()
5024 );
5025 });
5026}
5027
5028#[gpui::test]
5029async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
5030 init_test(cx, |_| {});
5031
5032 let language = Arc::new(Language::new(
5033 LanguageConfig {
5034 line_comment: Some("// ".into()),
5035 ..Default::default()
5036 },
5037 Some(tree_sitter_rust::language()),
5038 ));
5039
5040 let registry = Arc::new(LanguageRegistry::test());
5041 registry.add(language.clone());
5042
5043 let mut cx = EditorTestContext::new(cx).await;
5044 cx.update_buffer(|buffer, cx| {
5045 buffer.set_language_registry(registry);
5046 buffer.set_language(Some(language), cx);
5047 });
5048
5049 let toggle_comments = &ToggleComments {
5050 advance_downwards: true,
5051 };
5052
5053 // Single cursor on one line -> advance
5054 // Cursor moves horizontally 3 characters as well on non-blank line
5055 cx.set_state(indoc!(
5056 "fn a() {
5057 ˇdog();
5058 cat();
5059 }"
5060 ));
5061 cx.update_editor(|editor, cx| {
5062 editor.toggle_comments(toggle_comments, cx);
5063 });
5064 cx.assert_editor_state(indoc!(
5065 "fn a() {
5066 // dog();
5067 catˇ();
5068 }"
5069 ));
5070
5071 // Single selection on one line -> don't advance
5072 cx.set_state(indoc!(
5073 "fn a() {
5074 «dog()ˇ»;
5075 cat();
5076 }"
5077 ));
5078 cx.update_editor(|editor, cx| {
5079 editor.toggle_comments(toggle_comments, cx);
5080 });
5081 cx.assert_editor_state(indoc!(
5082 "fn a() {
5083 // «dog()ˇ»;
5084 cat();
5085 }"
5086 ));
5087
5088 // Multiple cursors on one line -> advance
5089 cx.set_state(indoc!(
5090 "fn a() {
5091 ˇdˇog();
5092 cat();
5093 }"
5094 ));
5095 cx.update_editor(|editor, cx| {
5096 editor.toggle_comments(toggle_comments, cx);
5097 });
5098 cx.assert_editor_state(indoc!(
5099 "fn a() {
5100 // dog();
5101 catˇ(ˇ);
5102 }"
5103 ));
5104
5105 // Multiple cursors on one line, with selection -> don't advance
5106 cx.set_state(indoc!(
5107 "fn a() {
5108 ˇdˇog«()ˇ»;
5109 cat();
5110 }"
5111 ));
5112 cx.update_editor(|editor, cx| {
5113 editor.toggle_comments(toggle_comments, cx);
5114 });
5115 cx.assert_editor_state(indoc!(
5116 "fn a() {
5117 // ˇdˇog«()ˇ»;
5118 cat();
5119 }"
5120 ));
5121
5122 // Single cursor on one line -> advance
5123 // Cursor moves to column 0 on blank line
5124 cx.set_state(indoc!(
5125 "fn a() {
5126 ˇdog();
5127
5128 cat();
5129 }"
5130 ));
5131 cx.update_editor(|editor, cx| {
5132 editor.toggle_comments(toggle_comments, cx);
5133 });
5134 cx.assert_editor_state(indoc!(
5135 "fn a() {
5136 // dog();
5137 ˇ
5138 cat();
5139 }"
5140 ));
5141
5142 // Single cursor on one line -> advance
5143 // Cursor starts and ends at column 0
5144 cx.set_state(indoc!(
5145 "fn a() {
5146 ˇ dog();
5147 cat();
5148 }"
5149 ));
5150 cx.update_editor(|editor, cx| {
5151 editor.toggle_comments(toggle_comments, cx);
5152 });
5153 cx.assert_editor_state(indoc!(
5154 "fn a() {
5155 // dog();
5156 ˇ cat();
5157 }"
5158 ));
5159}
5160
5161#[gpui::test]
5162async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
5163 init_test(cx, |_| {});
5164
5165 let mut cx = EditorTestContext::new(cx).await;
5166
5167 let html_language = Arc::new(
5168 Language::new(
5169 LanguageConfig {
5170 name: "HTML".into(),
5171 block_comment: Some(("<!-- ".into(), " -->".into())),
5172 ..Default::default()
5173 },
5174 Some(tree_sitter_html::language()),
5175 )
5176 .with_injection_query(
5177 r#"
5178 (script_element
5179 (raw_text) @content
5180 (#set! "language" "javascript"))
5181 "#,
5182 )
5183 .unwrap(),
5184 );
5185
5186 let javascript_language = Arc::new(Language::new(
5187 LanguageConfig {
5188 name: "JavaScript".into(),
5189 line_comment: Some("// ".into()),
5190 ..Default::default()
5191 },
5192 Some(tree_sitter_javascript::language()),
5193 ));
5194
5195 let registry = Arc::new(LanguageRegistry::test());
5196 registry.add(html_language.clone());
5197 registry.add(javascript_language.clone());
5198
5199 cx.update_buffer(|buffer, cx| {
5200 buffer.set_language_registry(registry);
5201 buffer.set_language(Some(html_language), cx);
5202 });
5203
5204 // Toggle comments for empty selections
5205 cx.set_state(
5206 &r#"
5207 <p>A</p>ˇ
5208 <p>B</p>ˇ
5209 <p>C</p>ˇ
5210 "#
5211 .unindent(),
5212 );
5213 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5214 cx.assert_editor_state(
5215 &r#"
5216 <!-- <p>A</p>ˇ -->
5217 <!-- <p>B</p>ˇ -->
5218 <!-- <p>C</p>ˇ -->
5219 "#
5220 .unindent(),
5221 );
5222 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5223 cx.assert_editor_state(
5224 &r#"
5225 <p>A</p>ˇ
5226 <p>B</p>ˇ
5227 <p>C</p>ˇ
5228 "#
5229 .unindent(),
5230 );
5231
5232 // Toggle comments for mixture of empty and non-empty selections, where
5233 // multiple selections occupy a given line.
5234 cx.set_state(
5235 &r#"
5236 <p>A«</p>
5237 <p>ˇ»B</p>ˇ
5238 <p>C«</p>
5239 <p>ˇ»D</p>ˇ
5240 "#
5241 .unindent(),
5242 );
5243
5244 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5245 cx.assert_editor_state(
5246 &r#"
5247 <!-- <p>A«</p>
5248 <p>ˇ»B</p>ˇ -->
5249 <!-- <p>C«</p>
5250 <p>ˇ»D</p>ˇ -->
5251 "#
5252 .unindent(),
5253 );
5254 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5255 cx.assert_editor_state(
5256 &r#"
5257 <p>A«</p>
5258 <p>ˇ»B</p>ˇ
5259 <p>C«</p>
5260 <p>ˇ»D</p>ˇ
5261 "#
5262 .unindent(),
5263 );
5264
5265 // Toggle comments when different languages are active for different
5266 // selections.
5267 cx.set_state(
5268 &r#"
5269 ˇ<script>
5270 ˇvar x = new Y();
5271 ˇ</script>
5272 "#
5273 .unindent(),
5274 );
5275 cx.foreground().run_until_parked();
5276 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5277 cx.assert_editor_state(
5278 &r#"
5279 <!-- ˇ<script> -->
5280 // ˇvar x = new Y();
5281 <!-- ˇ</script> -->
5282 "#
5283 .unindent(),
5284 );
5285}
5286
5287#[gpui::test]
5288fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
5289 init_test(cx, |_| {});
5290
5291 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
5292 let multibuffer = cx.add_model(|cx| {
5293 let mut multibuffer = MultiBuffer::new(0);
5294 multibuffer.push_excerpts(
5295 buffer.clone(),
5296 [
5297 ExcerptRange {
5298 context: Point::new(0, 0)..Point::new(0, 4),
5299 primary: None,
5300 },
5301 ExcerptRange {
5302 context: Point::new(1, 0)..Point::new(1, 4),
5303 primary: None,
5304 },
5305 ],
5306 cx,
5307 );
5308 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
5309 multibuffer
5310 });
5311
5312 let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
5313 view.update(cx, |view, cx| {
5314 assert_eq!(view.text(cx), "aaaa\nbbbb");
5315 view.change_selections(None, cx, |s| {
5316 s.select_ranges([
5317 Point::new(0, 0)..Point::new(0, 0),
5318 Point::new(1, 0)..Point::new(1, 0),
5319 ])
5320 });
5321
5322 view.handle_input("X", cx);
5323 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
5324 assert_eq!(
5325 view.selections.ranges(cx),
5326 [
5327 Point::new(0, 1)..Point::new(0, 1),
5328 Point::new(1, 1)..Point::new(1, 1),
5329 ]
5330 );
5331
5332 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
5333 view.change_selections(None, cx, |s| {
5334 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
5335 });
5336 view.backspace(&Default::default(), cx);
5337 assert_eq!(view.text(cx), "Xa\nbbb");
5338 assert_eq!(
5339 view.selections.ranges(cx),
5340 [Point::new(1, 0)..Point::new(1, 0)]
5341 );
5342
5343 view.change_selections(None, cx, |s| {
5344 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
5345 });
5346 view.backspace(&Default::default(), cx);
5347 assert_eq!(view.text(cx), "X\nbb");
5348 assert_eq!(
5349 view.selections.ranges(cx),
5350 [Point::new(0, 1)..Point::new(0, 1)]
5351 );
5352 });
5353}
5354
5355#[gpui::test]
5356fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
5357 init_test(cx, |_| {});
5358
5359 let markers = vec![('[', ']').into(), ('(', ')').into()];
5360 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
5361 indoc! {"
5362 [aaaa
5363 (bbbb]
5364 cccc)",
5365 },
5366 markers.clone(),
5367 );
5368 let excerpt_ranges = markers.into_iter().map(|marker| {
5369 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
5370 ExcerptRange {
5371 context,
5372 primary: None,
5373 }
5374 });
5375 let buffer = cx.add_model(|cx| Buffer::new(0, initial_text, cx));
5376 let multibuffer = cx.add_model(|cx| {
5377 let mut multibuffer = MultiBuffer::new(0);
5378 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
5379 multibuffer
5380 });
5381
5382 let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
5383 view.update(cx, |view, cx| {
5384 let (expected_text, selection_ranges) = marked_text_ranges(
5385 indoc! {"
5386 aaaa
5387 bˇbbb
5388 bˇbbˇb
5389 cccc"
5390 },
5391 true,
5392 );
5393 assert_eq!(view.text(cx), expected_text);
5394 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
5395
5396 view.handle_input("X", cx);
5397
5398 let (expected_text, expected_selections) = marked_text_ranges(
5399 indoc! {"
5400 aaaa
5401 bXˇbbXb
5402 bXˇbbXˇb
5403 cccc"
5404 },
5405 false,
5406 );
5407 assert_eq!(view.text(cx), expected_text);
5408 assert_eq!(view.selections.ranges(cx), expected_selections);
5409
5410 view.newline(&Newline, cx);
5411 let (expected_text, expected_selections) = marked_text_ranges(
5412 indoc! {"
5413 aaaa
5414 bX
5415 ˇbbX
5416 b
5417 bX
5418 ˇbbX
5419 ˇb
5420 cccc"
5421 },
5422 false,
5423 );
5424 assert_eq!(view.text(cx), expected_text);
5425 assert_eq!(view.selections.ranges(cx), expected_selections);
5426 });
5427}
5428
5429#[gpui::test]
5430fn test_refresh_selections(cx: &mut TestAppContext) {
5431 init_test(cx, |_| {});
5432
5433 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
5434 let mut excerpt1_id = None;
5435 let multibuffer = cx.add_model(|cx| {
5436 let mut multibuffer = MultiBuffer::new(0);
5437 excerpt1_id = multibuffer
5438 .push_excerpts(
5439 buffer.clone(),
5440 [
5441 ExcerptRange {
5442 context: Point::new(0, 0)..Point::new(1, 4),
5443 primary: None,
5444 },
5445 ExcerptRange {
5446 context: Point::new(1, 0)..Point::new(2, 4),
5447 primary: None,
5448 },
5449 ],
5450 cx,
5451 )
5452 .into_iter()
5453 .next();
5454 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
5455 multibuffer
5456 });
5457
5458 let (_, editor) = cx.add_window(|cx| {
5459 let mut editor = build_editor(multibuffer.clone(), cx);
5460 let snapshot = editor.snapshot(cx);
5461 editor.change_selections(None, cx, |s| {
5462 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
5463 });
5464 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
5465 assert_eq!(
5466 editor.selections.ranges(cx),
5467 [
5468 Point::new(1, 3)..Point::new(1, 3),
5469 Point::new(2, 1)..Point::new(2, 1),
5470 ]
5471 );
5472 editor
5473 });
5474
5475 // Refreshing selections is a no-op when excerpts haven't changed.
5476 editor.update(cx, |editor, cx| {
5477 editor.change_selections(None, cx, |s| s.refresh());
5478 assert_eq!(
5479 editor.selections.ranges(cx),
5480 [
5481 Point::new(1, 3)..Point::new(1, 3),
5482 Point::new(2, 1)..Point::new(2, 1),
5483 ]
5484 );
5485 });
5486
5487 multibuffer.update(cx, |multibuffer, cx| {
5488 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
5489 });
5490 editor.update(cx, |editor, cx| {
5491 // Removing an excerpt causes the first selection to become degenerate.
5492 assert_eq!(
5493 editor.selections.ranges(cx),
5494 [
5495 Point::new(0, 0)..Point::new(0, 0),
5496 Point::new(0, 1)..Point::new(0, 1)
5497 ]
5498 );
5499
5500 // Refreshing selections will relocate the first selection to the original buffer
5501 // location.
5502 editor.change_selections(None, cx, |s| s.refresh());
5503 assert_eq!(
5504 editor.selections.ranges(cx),
5505 [
5506 Point::new(0, 1)..Point::new(0, 1),
5507 Point::new(0, 3)..Point::new(0, 3)
5508 ]
5509 );
5510 assert!(editor.selections.pending_anchor().is_some());
5511 });
5512}
5513
5514#[gpui::test]
5515fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
5516 init_test(cx, |_| {});
5517
5518 let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
5519 let mut excerpt1_id = None;
5520 let multibuffer = cx.add_model(|cx| {
5521 let mut multibuffer = MultiBuffer::new(0);
5522 excerpt1_id = multibuffer
5523 .push_excerpts(
5524 buffer.clone(),
5525 [
5526 ExcerptRange {
5527 context: Point::new(0, 0)..Point::new(1, 4),
5528 primary: None,
5529 },
5530 ExcerptRange {
5531 context: Point::new(1, 0)..Point::new(2, 4),
5532 primary: None,
5533 },
5534 ],
5535 cx,
5536 )
5537 .into_iter()
5538 .next();
5539 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
5540 multibuffer
5541 });
5542
5543 let (_, editor) = cx.add_window(|cx| {
5544 let mut editor = build_editor(multibuffer.clone(), cx);
5545 let snapshot = editor.snapshot(cx);
5546 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
5547 assert_eq!(
5548 editor.selections.ranges(cx),
5549 [Point::new(1, 3)..Point::new(1, 3)]
5550 );
5551 editor
5552 });
5553
5554 multibuffer.update(cx, |multibuffer, cx| {
5555 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
5556 });
5557 editor.update(cx, |editor, cx| {
5558 assert_eq!(
5559 editor.selections.ranges(cx),
5560 [Point::new(0, 0)..Point::new(0, 0)]
5561 );
5562
5563 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
5564 editor.change_selections(None, cx, |s| s.refresh());
5565 assert_eq!(
5566 editor.selections.ranges(cx),
5567 [Point::new(0, 3)..Point::new(0, 3)]
5568 );
5569 assert!(editor.selections.pending_anchor().is_some());
5570 });
5571}
5572
5573#[gpui::test]
5574async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
5575 init_test(cx, |_| {});
5576
5577 let language = Arc::new(
5578 Language::new(
5579 LanguageConfig {
5580 brackets: BracketPairConfig {
5581 pairs: vec![
5582 BracketPair {
5583 start: "{".to_string(),
5584 end: "}".to_string(),
5585 close: true,
5586 newline: true,
5587 },
5588 BracketPair {
5589 start: "/* ".to_string(),
5590 end: " */".to_string(),
5591 close: true,
5592 newline: true,
5593 },
5594 ],
5595 ..Default::default()
5596 },
5597 ..Default::default()
5598 },
5599 Some(tree_sitter_rust::language()),
5600 )
5601 .with_indents_query("")
5602 .unwrap(),
5603 );
5604
5605 let text = concat!(
5606 "{ }\n", //
5607 " x\n", //
5608 " /* */\n", //
5609 "x\n", //
5610 "{{} }\n", //
5611 );
5612
5613 let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
5614 let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
5615 let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
5616 view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5617 .await;
5618
5619 view.update(cx, |view, cx| {
5620 view.change_selections(None, cx, |s| {
5621 s.select_display_ranges([
5622 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
5623 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
5624 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
5625 ])
5626 });
5627 view.newline(&Newline, cx);
5628
5629 assert_eq!(
5630 view.buffer().read(cx).read(cx).text(),
5631 concat!(
5632 "{ \n", // Suppress rustfmt
5633 "\n", //
5634 "}\n", //
5635 " x\n", //
5636 " /* \n", //
5637 " \n", //
5638 " */\n", //
5639 "x\n", //
5640 "{{} \n", //
5641 "}\n", //
5642 )
5643 );
5644 });
5645}
5646
5647#[gpui::test]
5648fn test_highlighted_ranges(cx: &mut TestAppContext) {
5649 init_test(cx, |_| {});
5650
5651 let (_, editor) = cx.add_window(|cx| {
5652 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
5653 build_editor(buffer.clone(), cx)
5654 });
5655
5656 editor.update(cx, |editor, cx| {
5657 struct Type1;
5658 struct Type2;
5659
5660 let buffer = editor.buffer.read(cx).snapshot(cx);
5661
5662 let anchor_range =
5663 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
5664
5665 editor.highlight_background::<Type1>(
5666 vec![
5667 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
5668 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
5669 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
5670 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
5671 ],
5672 |_| Color::red(),
5673 cx,
5674 );
5675 editor.highlight_background::<Type2>(
5676 vec![
5677 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
5678 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
5679 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
5680 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
5681 ],
5682 |_| Color::green(),
5683 cx,
5684 );
5685
5686 let snapshot = editor.snapshot(cx);
5687 let mut highlighted_ranges = editor.background_highlights_in_range(
5688 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
5689 &snapshot,
5690 theme::current(cx).as_ref(),
5691 );
5692 // Enforce a consistent ordering based on color without relying on the ordering of the
5693 // highlight's `TypeId` which is non-deterministic.
5694 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
5695 assert_eq!(
5696 highlighted_ranges,
5697 &[
5698 (
5699 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
5700 Color::green(),
5701 ),
5702 (
5703 DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
5704 Color::green(),
5705 ),
5706 (
5707 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
5708 Color::red(),
5709 ),
5710 (
5711 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
5712 Color::red(),
5713 ),
5714 ]
5715 );
5716 assert_eq!(
5717 editor.background_highlights_in_range(
5718 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
5719 &snapshot,
5720 theme::current(cx).as_ref(),
5721 ),
5722 &[(
5723 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
5724 Color::red(),
5725 )]
5726 );
5727 });
5728}
5729
5730#[gpui::test]
5731async fn test_following(cx: &mut gpui::TestAppContext) {
5732 init_test(cx, |_| {});
5733
5734 let fs = FakeFs::new(cx.background());
5735 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5736
5737 let buffer = project.update(cx, |project, cx| {
5738 let buffer = project
5739 .create_buffer(&sample_text(16, 8, 'a'), None, cx)
5740 .unwrap();
5741 cx.add_model(|cx| MultiBuffer::singleton(buffer, cx))
5742 });
5743 let (_, leader) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
5744 let (_, follower) = cx.update(|cx| {
5745 cx.add_window(
5746 WindowOptions {
5747 bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))),
5748 ..Default::default()
5749 },
5750 |cx| build_editor(buffer.clone(), cx),
5751 )
5752 });
5753
5754 let is_still_following = Rc::new(RefCell::new(true));
5755 let follower_edit_event_count = Rc::new(RefCell::new(0));
5756 let pending_update = Rc::new(RefCell::new(None));
5757 follower.update(cx, {
5758 let update = pending_update.clone();
5759 let is_still_following = is_still_following.clone();
5760 let follower_edit_event_count = follower_edit_event_count.clone();
5761 |_, cx| {
5762 cx.subscribe(&leader, move |_, leader, event, cx| {
5763 leader
5764 .read(cx)
5765 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
5766 })
5767 .detach();
5768
5769 cx.subscribe(&follower, move |_, _, event, cx| {
5770 if Editor::should_unfollow_on_event(event, cx) {
5771 *is_still_following.borrow_mut() = false;
5772 }
5773 if let Event::BufferEdited = event {
5774 *follower_edit_event_count.borrow_mut() += 1;
5775 }
5776 })
5777 .detach();
5778 }
5779 });
5780
5781 // Update the selections only
5782 leader.update(cx, |leader, cx| {
5783 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
5784 });
5785 follower
5786 .update(cx, |follower, cx| {
5787 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5788 })
5789 .await
5790 .unwrap();
5791 follower.read_with(cx, |follower, cx| {
5792 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
5793 });
5794 assert_eq!(*is_still_following.borrow(), true);
5795 assert_eq!(*follower_edit_event_count.borrow(), 0);
5796
5797 // Update the scroll position only
5798 leader.update(cx, |leader, cx| {
5799 leader.set_scroll_position(vec2f(1.5, 3.5), cx);
5800 });
5801 follower
5802 .update(cx, |follower, cx| {
5803 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5804 })
5805 .await
5806 .unwrap();
5807 assert_eq!(
5808 follower.update(cx, |follower, cx| follower.scroll_position(cx)),
5809 vec2f(1.5, 3.5)
5810 );
5811 assert_eq!(*is_still_following.borrow(), true);
5812 assert_eq!(*follower_edit_event_count.borrow(), 0);
5813
5814 // Update the selections and scroll position. The follower's scroll position is updated
5815 // via autoscroll, not via the leader's exact scroll position.
5816 leader.update(cx, |leader, cx| {
5817 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
5818 leader.request_autoscroll(Autoscroll::newest(), cx);
5819 leader.set_scroll_position(vec2f(1.5, 3.5), cx);
5820 });
5821 follower
5822 .update(cx, |follower, cx| {
5823 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5824 })
5825 .await
5826 .unwrap();
5827 follower.update(cx, |follower, cx| {
5828 assert_eq!(follower.scroll_position(cx), vec2f(1.5, 0.0));
5829 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
5830 });
5831 assert_eq!(*is_still_following.borrow(), true);
5832
5833 // Creating a pending selection that precedes another selection
5834 leader.update(cx, |leader, cx| {
5835 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
5836 leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
5837 });
5838 follower
5839 .update(cx, |follower, cx| {
5840 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5841 })
5842 .await
5843 .unwrap();
5844 follower.read_with(cx, |follower, cx| {
5845 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
5846 });
5847 assert_eq!(*is_still_following.borrow(), true);
5848
5849 // Extend the pending selection so that it surrounds another selection
5850 leader.update(cx, |leader, cx| {
5851 leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
5852 });
5853 follower
5854 .update(cx, |follower, cx| {
5855 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
5856 })
5857 .await
5858 .unwrap();
5859 follower.read_with(cx, |follower, cx| {
5860 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
5861 });
5862
5863 // Scrolling locally breaks the follow
5864 follower.update(cx, |follower, cx| {
5865 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
5866 follower.set_scroll_anchor(
5867 ScrollAnchor {
5868 anchor: top_anchor,
5869 offset: vec2f(0.0, 0.5),
5870 },
5871 cx,
5872 );
5873 });
5874 assert_eq!(*is_still_following.borrow(), false);
5875}
5876
5877#[gpui::test]
5878async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
5879 init_test(cx, |_| {});
5880
5881 let fs = FakeFs::new(cx.background());
5882 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5883 let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
5884 let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
5885
5886 let leader = pane.update(cx, |_, cx| {
5887 let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
5888 cx.add_view(|cx| build_editor(multibuffer.clone(), cx))
5889 });
5890
5891 // Start following the editor when it has no excerpts.
5892 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
5893 let follower_1 = cx
5894 .update(|cx| {
5895 Editor::from_state_proto(
5896 pane.clone(),
5897 project.clone(),
5898 ViewId {
5899 creator: Default::default(),
5900 id: 0,
5901 },
5902 &mut state_message,
5903 cx,
5904 )
5905 })
5906 .unwrap()
5907 .await
5908 .unwrap();
5909
5910 let update_message = Rc::new(RefCell::new(None));
5911 follower_1.update(cx, {
5912 let update = update_message.clone();
5913 |_, cx| {
5914 cx.subscribe(&leader, move |_, leader, event, cx| {
5915 leader
5916 .read(cx)
5917 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
5918 })
5919 .detach();
5920 }
5921 });
5922
5923 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
5924 (
5925 project
5926 .create_buffer("abc\ndef\nghi\njkl\n", None, cx)
5927 .unwrap(),
5928 project
5929 .create_buffer("mno\npqr\nstu\nvwx\n", None, cx)
5930 .unwrap(),
5931 )
5932 });
5933
5934 // Insert some excerpts.
5935 leader.update(cx, |leader, cx| {
5936 leader.buffer.update(cx, |multibuffer, cx| {
5937 let excerpt_ids = multibuffer.push_excerpts(
5938 buffer_1.clone(),
5939 [
5940 ExcerptRange {
5941 context: 1..6,
5942 primary: None,
5943 },
5944 ExcerptRange {
5945 context: 12..15,
5946 primary: None,
5947 },
5948 ExcerptRange {
5949 context: 0..3,
5950 primary: None,
5951 },
5952 ],
5953 cx,
5954 );
5955 multibuffer.insert_excerpts_after(
5956 excerpt_ids[0],
5957 buffer_2.clone(),
5958 [
5959 ExcerptRange {
5960 context: 8..12,
5961 primary: None,
5962 },
5963 ExcerptRange {
5964 context: 0..6,
5965 primary: None,
5966 },
5967 ],
5968 cx,
5969 );
5970 });
5971 });
5972
5973 // Apply the update of adding the excerpts.
5974 follower_1
5975 .update(cx, |follower, cx| {
5976 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
5977 })
5978 .await
5979 .unwrap();
5980 assert_eq!(
5981 follower_1.read_with(cx, |editor, cx| editor.text(cx)),
5982 leader.read_with(cx, |editor, cx| editor.text(cx))
5983 );
5984 update_message.borrow_mut().take();
5985
5986 // Start following separately after it already has excerpts.
5987 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
5988 let follower_2 = cx
5989 .update(|cx| {
5990 Editor::from_state_proto(
5991 pane.clone(),
5992 project.clone(),
5993 ViewId {
5994 creator: Default::default(),
5995 id: 0,
5996 },
5997 &mut state_message,
5998 cx,
5999 )
6000 })
6001 .unwrap()
6002 .await
6003 .unwrap();
6004 assert_eq!(
6005 follower_2.read_with(cx, |editor, cx| editor.text(cx)),
6006 leader.read_with(cx, |editor, cx| editor.text(cx))
6007 );
6008
6009 // Remove some excerpts.
6010 leader.update(cx, |leader, cx| {
6011 leader.buffer.update(cx, |multibuffer, cx| {
6012 let excerpt_ids = multibuffer.excerpt_ids();
6013 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
6014 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
6015 });
6016 });
6017
6018 // Apply the update of removing the excerpts.
6019 follower_1
6020 .update(cx, |follower, cx| {
6021 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6022 })
6023 .await
6024 .unwrap();
6025 follower_2
6026 .update(cx, |follower, cx| {
6027 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6028 })
6029 .await
6030 .unwrap();
6031 update_message.borrow_mut().take();
6032 assert_eq!(
6033 follower_1.read_with(cx, |editor, cx| editor.text(cx)),
6034 leader.read_with(cx, |editor, cx| editor.text(cx))
6035 );
6036}
6037
6038#[test]
6039fn test_combine_syntax_and_fuzzy_match_highlights() {
6040 let string = "abcdefghijklmnop";
6041 let syntax_ranges = [
6042 (
6043 0..3,
6044 HighlightStyle {
6045 color: Some(Color::red()),
6046 ..Default::default()
6047 },
6048 ),
6049 (
6050 4..8,
6051 HighlightStyle {
6052 color: Some(Color::green()),
6053 ..Default::default()
6054 },
6055 ),
6056 ];
6057 let match_indices = [4, 6, 7, 8];
6058 assert_eq!(
6059 combine_syntax_and_fuzzy_match_highlights(
6060 string,
6061 Default::default(),
6062 syntax_ranges.into_iter(),
6063 &match_indices,
6064 ),
6065 &[
6066 (
6067 0..3,
6068 HighlightStyle {
6069 color: Some(Color::red()),
6070 ..Default::default()
6071 },
6072 ),
6073 (
6074 4..5,
6075 HighlightStyle {
6076 color: Some(Color::green()),
6077 weight: Some(fonts::Weight::BOLD),
6078 ..Default::default()
6079 },
6080 ),
6081 (
6082 5..6,
6083 HighlightStyle {
6084 color: Some(Color::green()),
6085 ..Default::default()
6086 },
6087 ),
6088 (
6089 6..8,
6090 HighlightStyle {
6091 color: Some(Color::green()),
6092 weight: Some(fonts::Weight::BOLD),
6093 ..Default::default()
6094 },
6095 ),
6096 (
6097 8..9,
6098 HighlightStyle {
6099 weight: Some(fonts::Weight::BOLD),
6100 ..Default::default()
6101 },
6102 ),
6103 ]
6104 );
6105}
6106
6107#[gpui::test]
6108async fn go_to_hunk(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
6109 init_test(cx, |_| {});
6110
6111 let mut cx = EditorTestContext::new(cx).await;
6112
6113 let diff_base = r#"
6114 use some::mod;
6115
6116 const A: u32 = 42;
6117
6118 fn main() {
6119 println!("hello");
6120
6121 println!("world");
6122 }
6123 "#
6124 .unindent();
6125
6126 // Edits are modified, removed, modified, added
6127 cx.set_state(
6128 &r#"
6129 use some::modified;
6130
6131 ˇ
6132 fn main() {
6133 println!("hello there");
6134
6135 println!("around the");
6136 println!("world");
6137 }
6138 "#
6139 .unindent(),
6140 );
6141
6142 cx.set_diff_base(Some(&diff_base));
6143 deterministic.run_until_parked();
6144
6145 cx.update_editor(|editor, cx| {
6146 //Wrap around the bottom of the buffer
6147 for _ in 0..3 {
6148 editor.go_to_hunk(&GoToHunk, cx);
6149 }
6150 });
6151
6152 cx.assert_editor_state(
6153 &r#"
6154 ˇuse some::modified;
6155
6156
6157 fn main() {
6158 println!("hello there");
6159
6160 println!("around the");
6161 println!("world");
6162 }
6163 "#
6164 .unindent(),
6165 );
6166
6167 cx.update_editor(|editor, cx| {
6168 //Wrap around the top of the buffer
6169 for _ in 0..2 {
6170 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
6171 }
6172 });
6173
6174 cx.assert_editor_state(
6175 &r#"
6176 use some::modified;
6177
6178
6179 fn main() {
6180 ˇ println!("hello there");
6181
6182 println!("around the");
6183 println!("world");
6184 }
6185 "#
6186 .unindent(),
6187 );
6188
6189 cx.update_editor(|editor, cx| {
6190 editor.fold(&Fold, cx);
6191
6192 //Make sure that the fold only gets one hunk
6193 for _ in 0..4 {
6194 editor.go_to_hunk(&GoToHunk, cx);
6195 }
6196 });
6197
6198 cx.assert_editor_state(
6199 &r#"
6200 ˇuse some::modified;
6201
6202
6203 fn main() {
6204 println!("hello there");
6205
6206 println!("around the");
6207 println!("world");
6208 }
6209 "#
6210 .unindent(),
6211 );
6212}
6213
6214#[test]
6215fn test_split_words() {
6216 fn split<'a>(text: &'a str) -> Vec<&'a str> {
6217 split_words(text).collect()
6218 }
6219
6220 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
6221 assert_eq!(split("hello_world"), &["hello_", "world"]);
6222 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
6223 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
6224 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
6225 assert_eq!(split("helloworld"), &["helloworld"]);
6226}
6227
6228#[gpui::test]
6229async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
6230 init_test(cx, |_| {});
6231
6232 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
6233 let mut assert = |before, after| {
6234 let _state_context = cx.set_state(before);
6235 cx.update_editor(|editor, cx| {
6236 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
6237 });
6238 cx.assert_editor_state(after);
6239 };
6240
6241 // Outside bracket jumps to outside of matching bracket
6242 assert("console.logˇ(var);", "console.log(var)ˇ;");
6243 assert("console.log(var)ˇ;", "console.logˇ(var);");
6244
6245 // Inside bracket jumps to inside of matching bracket
6246 assert("console.log(ˇvar);", "console.log(varˇ);");
6247 assert("console.log(varˇ);", "console.log(ˇvar);");
6248
6249 // When outside a bracket and inside, favor jumping to the inside bracket
6250 assert(
6251 "console.log('foo', [1, 2, 3]ˇ);",
6252 "console.log(ˇ'foo', [1, 2, 3]);",
6253 );
6254 assert(
6255 "console.log(ˇ'foo', [1, 2, 3]);",
6256 "console.log('foo', [1, 2, 3]ˇ);",
6257 );
6258
6259 // Bias forward if two options are equally likely
6260 assert(
6261 "let result = curried_fun()ˇ();",
6262 "let result = curried_fun()()ˇ;",
6263 );
6264
6265 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
6266 assert(
6267 indoc! {"
6268 function test() {
6269 console.log('test')ˇ
6270 }"},
6271 indoc! {"
6272 function test() {
6273 console.logˇ('test')
6274 }"},
6275 );
6276}
6277
6278#[gpui::test(iterations = 10)]
6279async fn test_copilot(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
6280 init_test(cx, |_| {});
6281
6282 let (copilot, copilot_lsp) = Copilot::fake(cx);
6283 cx.update(|cx| cx.set_global(copilot));
6284 let mut cx = EditorLspTestContext::new_rust(
6285 lsp::ServerCapabilities {
6286 completion_provider: Some(lsp::CompletionOptions {
6287 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
6288 ..Default::default()
6289 }),
6290 ..Default::default()
6291 },
6292 cx,
6293 )
6294 .await;
6295
6296 // When inserting, ensure autocompletion is favored over Copilot suggestions.
6297 cx.set_state(indoc! {"
6298 oneˇ
6299 two
6300 three
6301 "});
6302 cx.simulate_keystroke(".");
6303 let _ = handle_completion_request(
6304 &mut cx,
6305 indoc! {"
6306 one.|<>
6307 two
6308 three
6309 "},
6310 vec!["completion_a", "completion_b"],
6311 );
6312 handle_copilot_completion_request(
6313 &copilot_lsp,
6314 vec![copilot::request::Completion {
6315 text: "one.copilot1".into(),
6316 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
6317 ..Default::default()
6318 }],
6319 vec![],
6320 );
6321 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6322 cx.update_editor(|editor, cx| {
6323 assert!(editor.context_menu_visible());
6324 assert!(!editor.has_active_copilot_suggestion(cx));
6325
6326 // Confirming a completion inserts it and hides the context menu, without showing
6327 // the copilot suggestion afterwards.
6328 editor
6329 .confirm_completion(&Default::default(), cx)
6330 .unwrap()
6331 .detach();
6332 assert!(!editor.context_menu_visible());
6333 assert!(!editor.has_active_copilot_suggestion(cx));
6334 assert_eq!(editor.text(cx), "one.completion_a\ntwo\nthree\n");
6335 assert_eq!(editor.display_text(cx), "one.completion_a\ntwo\nthree\n");
6336 });
6337
6338 // Ensure Copilot suggestions are shown right away if no autocompletion is available.
6339 cx.set_state(indoc! {"
6340 oneˇ
6341 two
6342 three
6343 "});
6344 cx.simulate_keystroke(".");
6345 let _ = handle_completion_request(
6346 &mut cx,
6347 indoc! {"
6348 one.|<>
6349 two
6350 three
6351 "},
6352 vec![],
6353 );
6354 handle_copilot_completion_request(
6355 &copilot_lsp,
6356 vec![copilot::request::Completion {
6357 text: "one.copilot1".into(),
6358 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
6359 ..Default::default()
6360 }],
6361 vec![],
6362 );
6363 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6364 cx.update_editor(|editor, cx| {
6365 assert!(!editor.context_menu_visible());
6366 assert!(editor.has_active_copilot_suggestion(cx));
6367 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
6368 assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
6369 });
6370
6371 // Reset editor, and ensure autocompletion is still favored over Copilot suggestions.
6372 cx.set_state(indoc! {"
6373 oneˇ
6374 two
6375 three
6376 "});
6377 cx.simulate_keystroke(".");
6378 let _ = handle_completion_request(
6379 &mut cx,
6380 indoc! {"
6381 one.|<>
6382 two
6383 three
6384 "},
6385 vec!["completion_a", "completion_b"],
6386 );
6387 handle_copilot_completion_request(
6388 &copilot_lsp,
6389 vec![copilot::request::Completion {
6390 text: "one.copilot1".into(),
6391 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
6392 ..Default::default()
6393 }],
6394 vec![],
6395 );
6396 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6397 cx.update_editor(|editor, cx| {
6398 assert!(editor.context_menu_visible());
6399 assert!(!editor.has_active_copilot_suggestion(cx));
6400
6401 // When hiding the context menu, the Copilot suggestion becomes visible.
6402 editor.hide_context_menu(cx);
6403 assert!(!editor.context_menu_visible());
6404 assert!(editor.has_active_copilot_suggestion(cx));
6405 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
6406 assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
6407 });
6408
6409 // Ensure existing completion is interpolated when inserting again.
6410 cx.simulate_keystroke("c");
6411 deterministic.run_until_parked();
6412 cx.update_editor(|editor, cx| {
6413 assert!(!editor.context_menu_visible());
6414 assert!(editor.has_active_copilot_suggestion(cx));
6415 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
6416 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6417 });
6418
6419 // After debouncing, new Copilot completions should be requested.
6420 handle_copilot_completion_request(
6421 &copilot_lsp,
6422 vec![copilot::request::Completion {
6423 text: "one.copilot2".into(),
6424 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 5)),
6425 ..Default::default()
6426 }],
6427 vec![],
6428 );
6429 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6430 cx.update_editor(|editor, cx| {
6431 assert!(!editor.context_menu_visible());
6432 assert!(editor.has_active_copilot_suggestion(cx));
6433 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6434 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6435
6436 // Canceling should remove the active Copilot suggestion.
6437 editor.cancel(&Default::default(), cx);
6438 assert!(!editor.has_active_copilot_suggestion(cx));
6439 assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
6440 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6441
6442 // After canceling, tabbing shouldn't insert the previously shown suggestion.
6443 editor.tab(&Default::default(), cx);
6444 assert!(!editor.has_active_copilot_suggestion(cx));
6445 assert_eq!(editor.display_text(cx), "one.c \ntwo\nthree\n");
6446 assert_eq!(editor.text(cx), "one.c \ntwo\nthree\n");
6447
6448 // When undoing the previously active suggestion is shown again.
6449 editor.undo(&Default::default(), cx);
6450 assert!(editor.has_active_copilot_suggestion(cx));
6451 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6452 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
6453 });
6454
6455 // If an edit occurs outside of this editor, the suggestion is still correctly interpolated.
6456 cx.update_buffer(|buffer, cx| buffer.edit([(5..5, "o")], None, cx));
6457 cx.update_editor(|editor, cx| {
6458 assert!(editor.has_active_copilot_suggestion(cx));
6459 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6460 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
6461
6462 // Tabbing when there is an active suggestion inserts it.
6463 editor.tab(&Default::default(), cx);
6464 assert!(!editor.has_active_copilot_suggestion(cx));
6465 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6466 assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
6467
6468 // When undoing the previously active suggestion is shown again.
6469 editor.undo(&Default::default(), cx);
6470 assert!(editor.has_active_copilot_suggestion(cx));
6471 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
6472 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
6473
6474 // Hide suggestion.
6475 editor.cancel(&Default::default(), cx);
6476 assert!(!editor.has_active_copilot_suggestion(cx));
6477 assert_eq!(editor.display_text(cx), "one.co\ntwo\nthree\n");
6478 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
6479 });
6480
6481 // If an edit occurs outside of this editor but no suggestion is being shown,
6482 // we won't make it visible.
6483 cx.update_buffer(|buffer, cx| buffer.edit([(6..6, "p")], None, cx));
6484 cx.update_editor(|editor, cx| {
6485 assert!(!editor.has_active_copilot_suggestion(cx));
6486 assert_eq!(editor.display_text(cx), "one.cop\ntwo\nthree\n");
6487 assert_eq!(editor.text(cx), "one.cop\ntwo\nthree\n");
6488 });
6489
6490 // Reset the editor to verify how suggestions behave when tabbing on leading indentation.
6491 cx.update_editor(|editor, cx| {
6492 editor.set_text("fn foo() {\n \n}", cx);
6493 editor.change_selections(None, cx, |s| {
6494 s.select_ranges([Point::new(1, 2)..Point::new(1, 2)])
6495 });
6496 });
6497 handle_copilot_completion_request(
6498 &copilot_lsp,
6499 vec![copilot::request::Completion {
6500 text: " let x = 4;".into(),
6501 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
6502 ..Default::default()
6503 }],
6504 vec![],
6505 );
6506
6507 cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
6508 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6509 cx.update_editor(|editor, cx| {
6510 assert!(editor.has_active_copilot_suggestion(cx));
6511 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
6512 assert_eq!(editor.text(cx), "fn foo() {\n \n}");
6513
6514 // Tabbing inside of leading whitespace inserts indentation without accepting the suggestion.
6515 editor.tab(&Default::default(), cx);
6516 assert!(editor.has_active_copilot_suggestion(cx));
6517 assert_eq!(editor.text(cx), "fn foo() {\n \n}");
6518 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
6519
6520 // Tabbing again accepts the suggestion.
6521 editor.tab(&Default::default(), cx);
6522 assert!(!editor.has_active_copilot_suggestion(cx));
6523 assert_eq!(editor.text(cx), "fn foo() {\n let x = 4;\n}");
6524 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
6525 });
6526}
6527
6528#[gpui::test]
6529async fn test_copilot_completion_invalidation(
6530 deterministic: Arc<Deterministic>,
6531 cx: &mut gpui::TestAppContext,
6532) {
6533 init_test(cx, |_| {});
6534
6535 let (copilot, copilot_lsp) = Copilot::fake(cx);
6536 cx.update(|cx| cx.set_global(copilot));
6537 let mut cx = EditorLspTestContext::new_rust(
6538 lsp::ServerCapabilities {
6539 completion_provider: Some(lsp::CompletionOptions {
6540 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
6541 ..Default::default()
6542 }),
6543 ..Default::default()
6544 },
6545 cx,
6546 )
6547 .await;
6548
6549 cx.set_state(indoc! {"
6550 one
6551 twˇ
6552 three
6553 "});
6554
6555 handle_copilot_completion_request(
6556 &copilot_lsp,
6557 vec![copilot::request::Completion {
6558 text: "two.foo()".into(),
6559 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
6560 ..Default::default()
6561 }],
6562 vec![],
6563 );
6564 cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
6565 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6566 cx.update_editor(|editor, cx| {
6567 assert!(editor.has_active_copilot_suggestion(cx));
6568 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
6569 assert_eq!(editor.text(cx), "one\ntw\nthree\n");
6570
6571 editor.backspace(&Default::default(), cx);
6572 assert!(editor.has_active_copilot_suggestion(cx));
6573 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
6574 assert_eq!(editor.text(cx), "one\nt\nthree\n");
6575
6576 editor.backspace(&Default::default(), cx);
6577 assert!(editor.has_active_copilot_suggestion(cx));
6578 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
6579 assert_eq!(editor.text(cx), "one\n\nthree\n");
6580
6581 // Deleting across the original suggestion range invalidates it.
6582 editor.backspace(&Default::default(), cx);
6583 assert!(!editor.has_active_copilot_suggestion(cx));
6584 assert_eq!(editor.display_text(cx), "one\nthree\n");
6585 assert_eq!(editor.text(cx), "one\nthree\n");
6586
6587 // Undoing the deletion restores the suggestion.
6588 editor.undo(&Default::default(), cx);
6589 assert!(editor.has_active_copilot_suggestion(cx));
6590 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
6591 assert_eq!(editor.text(cx), "one\n\nthree\n");
6592 });
6593}
6594
6595#[gpui::test]
6596async fn test_copilot_multibuffer(
6597 deterministic: Arc<Deterministic>,
6598 cx: &mut gpui::TestAppContext,
6599) {
6600 init_test(cx, |_| {});
6601
6602 let (copilot, copilot_lsp) = Copilot::fake(cx);
6603 cx.update(|cx| cx.set_global(copilot));
6604
6605 let buffer_1 = cx.add_model(|cx| Buffer::new(0, "a = 1\nb = 2\n", cx));
6606 let buffer_2 = cx.add_model(|cx| Buffer::new(0, "c = 3\nd = 4\n", cx));
6607 let multibuffer = cx.add_model(|cx| {
6608 let mut multibuffer = MultiBuffer::new(0);
6609 multibuffer.push_excerpts(
6610 buffer_1.clone(),
6611 [ExcerptRange {
6612 context: Point::new(0, 0)..Point::new(2, 0),
6613 primary: None,
6614 }],
6615 cx,
6616 );
6617 multibuffer.push_excerpts(
6618 buffer_2.clone(),
6619 [ExcerptRange {
6620 context: Point::new(0, 0)..Point::new(2, 0),
6621 primary: None,
6622 }],
6623 cx,
6624 );
6625 multibuffer
6626 });
6627 let (_, editor) = cx.add_window(|cx| build_editor(multibuffer, cx));
6628
6629 handle_copilot_completion_request(
6630 &copilot_lsp,
6631 vec![copilot::request::Completion {
6632 text: "b = 2 + a".into(),
6633 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 5)),
6634 ..Default::default()
6635 }],
6636 vec![],
6637 );
6638 editor.update(cx, |editor, cx| {
6639 // Ensure copilot suggestions are shown for the first excerpt.
6640 editor.change_selections(None, cx, |s| {
6641 s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
6642 });
6643 editor.next_copilot_suggestion(&Default::default(), cx);
6644 });
6645 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6646 editor.update(cx, |editor, cx| {
6647 assert!(editor.has_active_copilot_suggestion(cx));
6648 assert_eq!(
6649 editor.display_text(cx),
6650 "\n\na = 1\nb = 2 + a\n\n\n\nc = 3\nd = 4\n"
6651 );
6652 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
6653 });
6654
6655 handle_copilot_completion_request(
6656 &copilot_lsp,
6657 vec![copilot::request::Completion {
6658 text: "d = 4 + c".into(),
6659 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 6)),
6660 ..Default::default()
6661 }],
6662 vec![],
6663 );
6664 editor.update(cx, |editor, cx| {
6665 // Move to another excerpt, ensuring the suggestion gets cleared.
6666 editor.change_selections(None, cx, |s| {
6667 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
6668 });
6669 assert!(!editor.has_active_copilot_suggestion(cx));
6670 assert_eq!(
6671 editor.display_text(cx),
6672 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4\n"
6673 );
6674 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
6675
6676 // Type a character, ensuring we don't even try to interpolate the previous suggestion.
6677 editor.handle_input(" ", cx);
6678 assert!(!editor.has_active_copilot_suggestion(cx));
6679 assert_eq!(
6680 editor.display_text(cx),
6681 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 \n"
6682 );
6683 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
6684 });
6685
6686 // Ensure the new suggestion is displayed when the debounce timeout expires.
6687 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6688 editor.update(cx, |editor, cx| {
6689 assert!(editor.has_active_copilot_suggestion(cx));
6690 assert_eq!(
6691 editor.display_text(cx),
6692 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 + c\n"
6693 );
6694 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
6695 });
6696}
6697
6698#[gpui::test]
6699async fn test_copilot_disabled_globs(
6700 deterministic: Arc<Deterministic>,
6701 cx: &mut gpui::TestAppContext,
6702) {
6703 init_test(cx, |settings| {
6704 settings
6705 .copilot
6706 .get_or_insert(Default::default())
6707 .disabled_globs = Some(vec![".env*".to_string()]);
6708 });
6709
6710 let (copilot, copilot_lsp) = Copilot::fake(cx);
6711 cx.update(|cx| cx.set_global(copilot));
6712
6713 let fs = FakeFs::new(cx.background());
6714 fs.insert_tree(
6715 "/test",
6716 json!({
6717 ".env": "SECRET=something\n",
6718 "README.md": "hello\n"
6719 }),
6720 )
6721 .await;
6722 let project = Project::test(fs, ["/test".as_ref()], cx).await;
6723
6724 let private_buffer = project
6725 .update(cx, |project, cx| {
6726 project.open_local_buffer("/test/.env", cx)
6727 })
6728 .await
6729 .unwrap();
6730 let public_buffer = project
6731 .update(cx, |project, cx| {
6732 project.open_local_buffer("/test/README.md", cx)
6733 })
6734 .await
6735 .unwrap();
6736
6737 let multibuffer = cx.add_model(|cx| {
6738 let mut multibuffer = MultiBuffer::new(0);
6739 multibuffer.push_excerpts(
6740 private_buffer.clone(),
6741 [ExcerptRange {
6742 context: Point::new(0, 0)..Point::new(1, 0),
6743 primary: None,
6744 }],
6745 cx,
6746 );
6747 multibuffer.push_excerpts(
6748 public_buffer.clone(),
6749 [ExcerptRange {
6750 context: Point::new(0, 0)..Point::new(1, 0),
6751 primary: None,
6752 }],
6753 cx,
6754 );
6755 multibuffer
6756 });
6757 let (_, editor) = cx.add_window(|cx| build_editor(multibuffer, cx));
6758
6759 let mut copilot_requests = copilot_lsp
6760 .handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| async move {
6761 Ok(copilot::request::GetCompletionsResult {
6762 completions: vec![copilot::request::Completion {
6763 text: "next line".into(),
6764 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
6765 ..Default::default()
6766 }],
6767 })
6768 });
6769
6770 editor.update(cx, |editor, cx| {
6771 editor.change_selections(None, cx, |selections| {
6772 selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
6773 });
6774 editor.next_copilot_suggestion(&Default::default(), cx);
6775 });
6776
6777 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6778 assert!(copilot_requests.try_next().is_err());
6779
6780 editor.update(cx, |editor, cx| {
6781 editor.change_selections(None, cx, |s| {
6782 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
6783 });
6784 editor.next_copilot_suggestion(&Default::default(), cx);
6785 });
6786
6787 deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
6788 assert!(copilot_requests.try_next().is_ok());
6789}
6790
6791fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
6792 let point = DisplayPoint::new(row as u32, column as u32);
6793 point..point
6794}
6795
6796fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
6797 let (text, ranges) = marked_text_ranges(marked_text, true);
6798 assert_eq!(view.text(cx), text);
6799 assert_eq!(
6800 view.selections.ranges(cx),
6801 ranges,
6802 "Assert selections are {}",
6803 marked_text
6804 );
6805}
6806
6807/// Handle completion request passing a marked string specifying where the completion
6808/// should be triggered from using '|' character, what range should be replaced, and what completions
6809/// should be returned using '<' and '>' to delimit the range
6810fn handle_completion_request<'a>(
6811 cx: &mut EditorLspTestContext<'a>,
6812 marked_string: &str,
6813 completions: Vec<&'static str>,
6814) -> impl Future<Output = ()> {
6815 let complete_from_marker: TextRangeMarker = '|'.into();
6816 let replace_range_marker: TextRangeMarker = ('<', '>').into();
6817 let (_, mut marked_ranges) = marked_text_ranges_by(
6818 marked_string,
6819 vec![complete_from_marker.clone(), replace_range_marker.clone()],
6820 );
6821
6822 let complete_from_position =
6823 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
6824 let replace_range =
6825 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
6826
6827 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
6828 let completions = completions.clone();
6829 async move {
6830 assert_eq!(params.text_document_position.text_document.uri, url.clone());
6831 assert_eq!(
6832 params.text_document_position.position,
6833 complete_from_position
6834 );
6835 Ok(Some(lsp::CompletionResponse::Array(
6836 completions
6837 .iter()
6838 .map(|completion_text| lsp::CompletionItem {
6839 label: completion_text.to_string(),
6840 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
6841 range: replace_range,
6842 new_text: completion_text.to_string(),
6843 })),
6844 ..Default::default()
6845 })
6846 .collect(),
6847 )))
6848 }
6849 });
6850
6851 async move {
6852 request.next().await;
6853 }
6854}
6855
6856fn handle_resolve_completion_request<'a>(
6857 cx: &mut EditorLspTestContext<'a>,
6858 edits: Option<Vec<(&'static str, &'static str)>>,
6859) -> impl Future<Output = ()> {
6860 let edits = edits.map(|edits| {
6861 edits
6862 .iter()
6863 .map(|(marked_string, new_text)| {
6864 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
6865 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
6866 lsp::TextEdit::new(replace_range, new_text.to_string())
6867 })
6868 .collect::<Vec<_>>()
6869 });
6870
6871 let mut request =
6872 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
6873 let edits = edits.clone();
6874 async move {
6875 Ok(lsp::CompletionItem {
6876 additional_text_edits: edits,
6877 ..Default::default()
6878 })
6879 }
6880 });
6881
6882 async move {
6883 request.next().await;
6884 }
6885}
6886
6887fn handle_copilot_completion_request(
6888 lsp: &lsp::FakeLanguageServer,
6889 completions: Vec<copilot::request::Completion>,
6890 completions_cycling: Vec<copilot::request::Completion>,
6891) {
6892 lsp.handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| {
6893 let completions = completions.clone();
6894 async move {
6895 Ok(copilot::request::GetCompletionsResult {
6896 completions: completions.clone(),
6897 })
6898 }
6899 });
6900 lsp.handle_request::<copilot::request::GetCompletionsCycling, _, _>(move |_params, _cx| {
6901 let completions_cycling = completions_cycling.clone();
6902 async move {
6903 Ok(copilot::request::GetCompletionsResult {
6904 completions: completions_cycling.clone(),
6905 })
6906 }
6907 });
6908}
6909
6910pub(crate) fn update_test_settings(
6911 cx: &mut TestAppContext,
6912 f: impl Fn(&mut AllLanguageSettingsContent),
6913) {
6914 cx.update(|cx| {
6915 cx.update_global::<SettingsStore, _, _>(|store, cx| {
6916 store.update_user_settings::<AllLanguageSettings>(cx, f);
6917 });
6918 });
6919}
6920
6921pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
6922 cx.foreground().forbid_parking();
6923
6924 cx.update(|cx| {
6925 cx.set_global(SettingsStore::test(cx));
6926 theme::init((), cx);
6927 client::init_settings(cx);
6928 language::init(cx);
6929 Project::init_settings(cx);
6930 workspace::init_settings(cx);
6931 crate::init(cx);
6932 });
6933
6934 update_test_settings(cx, f);
6935}