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