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