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