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, Platform, 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.test_platform
3282 .read_from_clipboard()
3283 .map(|item| item.text().to_owned()),
3284 Some("fox jumps over\n".to_owned())
3285 );
3286
3287 // Paste with three selections, noticing how the copied full-line selection is inserted
3288 // before the empty selections but replaces the selection that is non-empty.
3289 cx.set_state(indoc! {"
3290 Tˇhe quick brown
3291 «foˇ»x jumps over
3292 tˇhe lazy dog"});
3293 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3294 cx.assert_editor_state(indoc! {"
3295 fox jumps over
3296 Tˇhe quick brown
3297 fox jumps over
3298 ˇx jumps over
3299 fox jumps over
3300 tˇhe lazy dog"});
3301}
3302
3303#[gpui::test]
3304async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
3305 init_test(cx, |_| {});
3306
3307 let mut cx = EditorTestContext::new(cx).await;
3308 let language = Arc::new(Language::new(
3309 LanguageConfig::default(),
3310 Some(tree_sitter_rust::language()),
3311 ));
3312 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
3313
3314 // Cut an indented block, without the leading whitespace.
3315 cx.set_state(indoc! {"
3316 const a: B = (
3317 c(),
3318 «d(
3319 e,
3320 f
3321 )ˇ»
3322 );
3323 "});
3324 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3325 cx.assert_editor_state(indoc! {"
3326 const a: B = (
3327 c(),
3328 ˇ
3329 );
3330 "});
3331
3332 // Paste it at the same position.
3333 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3334 cx.assert_editor_state(indoc! {"
3335 const a: B = (
3336 c(),
3337 d(
3338 e,
3339 f
3340 )ˇ
3341 );
3342 "});
3343
3344 // Paste it at a line with a lower indent level.
3345 cx.set_state(indoc! {"
3346 ˇ
3347 const a: B = (
3348 c(),
3349 );
3350 "});
3351 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3352 cx.assert_editor_state(indoc! {"
3353 d(
3354 e,
3355 f
3356 )ˇ
3357 const a: B = (
3358 c(),
3359 );
3360 "});
3361
3362 // Cut an indented block, with the leading whitespace.
3363 cx.set_state(indoc! {"
3364 const a: B = (
3365 c(),
3366 « d(
3367 e,
3368 f
3369 )
3370 ˇ»);
3371 "});
3372 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3373 cx.assert_editor_state(indoc! {"
3374 const a: B = (
3375 c(),
3376 ˇ);
3377 "});
3378
3379 // Paste it at the same position.
3380 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3381 cx.assert_editor_state(indoc! {"
3382 const a: B = (
3383 c(),
3384 d(
3385 e,
3386 f
3387 )
3388 ˇ);
3389 "});
3390
3391 // Paste it at a line with a higher indent level.
3392 cx.set_state(indoc! {"
3393 const a: B = (
3394 c(),
3395 d(
3396 e,
3397 fˇ
3398 )
3399 );
3400 "});
3401 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3402 cx.assert_editor_state(indoc! {"
3403 const a: B = (
3404 c(),
3405 d(
3406 e,
3407 f d(
3408 e,
3409 f
3410 )
3411 ˇ
3412 )
3413 );
3414 "});
3415}
3416
3417#[gpui::test]
3418fn test_select_all(cx: &mut TestAppContext) {
3419 init_test(cx, |_| {});
3420
3421 let view = cx.add_window(|cx| {
3422 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
3423 build_editor(buffer, cx)
3424 });
3425 view.update(cx, |view, cx| {
3426 view.select_all(&SelectAll, cx);
3427 assert_eq!(
3428 view.selections.display_ranges(cx),
3429 &[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)]
3430 );
3431 });
3432}
3433
3434#[gpui::test]
3435fn test_select_line(cx: &mut TestAppContext) {
3436 init_test(cx, |_| {});
3437
3438 let view = cx.add_window(|cx| {
3439 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
3440 build_editor(buffer, cx)
3441 });
3442 view.update(cx, |view, cx| {
3443 view.change_selections(None, cx, |s| {
3444 s.select_display_ranges([
3445 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3446 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
3447 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
3448 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2),
3449 ])
3450 });
3451 view.select_line(&SelectLine, cx);
3452 assert_eq!(
3453 view.selections.display_ranges(cx),
3454 vec![
3455 DisplayPoint::new(0, 0)..DisplayPoint::new(2, 0),
3456 DisplayPoint::new(4, 0)..DisplayPoint::new(5, 0),
3457 ]
3458 );
3459 });
3460
3461 view.update(cx, |view, cx| {
3462 view.select_line(&SelectLine, cx);
3463 assert_eq!(
3464 view.selections.display_ranges(cx),
3465 vec![
3466 DisplayPoint::new(0, 0)..DisplayPoint::new(3, 0),
3467 DisplayPoint::new(4, 0)..DisplayPoint::new(5, 5),
3468 ]
3469 );
3470 });
3471
3472 view.update(cx, |view, cx| {
3473 view.select_line(&SelectLine, cx);
3474 assert_eq!(
3475 view.selections.display_ranges(cx),
3476 vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)]
3477 );
3478 });
3479}
3480
3481#[gpui::test]
3482fn test_split_selection_into_lines(cx: &mut TestAppContext) {
3483 init_test(cx, |_| {});
3484
3485 let view = cx.add_window(|cx| {
3486 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
3487 build_editor(buffer, cx)
3488 });
3489 view.update(cx, |view, cx| {
3490 view.fold_ranges(
3491 vec![
3492 Point::new(0, 2)..Point::new(1, 2),
3493 Point::new(2, 3)..Point::new(4, 1),
3494 Point::new(7, 0)..Point::new(8, 4),
3495 ],
3496 true,
3497 cx,
3498 );
3499 view.change_selections(None, cx, |s| {
3500 s.select_display_ranges([
3501 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3502 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
3503 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
3504 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
3505 ])
3506 });
3507 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
3508 });
3509
3510 view.update(cx, |view, cx| {
3511 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
3512 assert_eq!(
3513 view.display_text(cx),
3514 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
3515 );
3516 assert_eq!(
3517 view.selections.display_ranges(cx),
3518 [
3519 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
3520 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
3521 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
3522 DisplayPoint::new(5, 4)..DisplayPoint::new(5, 4)
3523 ]
3524 );
3525 });
3526
3527 view.update(cx, |view, cx| {
3528 view.change_selections(None, cx, |s| {
3529 s.select_display_ranges([DisplayPoint::new(5, 0)..DisplayPoint::new(0, 1)])
3530 });
3531 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
3532 assert_eq!(
3533 view.display_text(cx),
3534 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
3535 );
3536 assert_eq!(
3537 view.selections.display_ranges(cx),
3538 [
3539 DisplayPoint::new(0, 5)..DisplayPoint::new(0, 5),
3540 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
3541 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
3542 DisplayPoint::new(3, 5)..DisplayPoint::new(3, 5),
3543 DisplayPoint::new(4, 5)..DisplayPoint::new(4, 5),
3544 DisplayPoint::new(5, 5)..DisplayPoint::new(5, 5),
3545 DisplayPoint::new(6, 5)..DisplayPoint::new(6, 5),
3546 DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0)
3547 ]
3548 );
3549 });
3550}
3551
3552#[gpui::test]
3553async fn test_add_selection_above_below(cx: &mut TestAppContext) {
3554 init_test(cx, |_| {});
3555
3556 let mut cx = EditorTestContext::new(cx).await;
3557
3558 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
3559 cx.set_state(indoc!(
3560 r#"abc
3561 defˇghi
3562
3563 jk
3564 nlmo
3565 "#
3566 ));
3567
3568 cx.update_editor(|editor, cx| {
3569 editor.add_selection_above(&Default::default(), cx);
3570 });
3571
3572 cx.assert_editor_state(indoc!(
3573 r#"abcˇ
3574 defˇghi
3575
3576 jk
3577 nlmo
3578 "#
3579 ));
3580
3581 cx.update_editor(|editor, cx| {
3582 editor.add_selection_above(&Default::default(), cx);
3583 });
3584
3585 cx.assert_editor_state(indoc!(
3586 r#"abcˇ
3587 defˇghi
3588
3589 jk
3590 nlmo
3591 "#
3592 ));
3593
3594 cx.update_editor(|view, cx| {
3595 view.add_selection_below(&Default::default(), cx);
3596 });
3597
3598 cx.assert_editor_state(indoc!(
3599 r#"abc
3600 defˇghi
3601
3602 jk
3603 nlmo
3604 "#
3605 ));
3606
3607 cx.update_editor(|view, cx| {
3608 view.undo_selection(&Default::default(), cx);
3609 });
3610
3611 cx.assert_editor_state(indoc!(
3612 r#"abcˇ
3613 defˇghi
3614
3615 jk
3616 nlmo
3617 "#
3618 ));
3619
3620 cx.update_editor(|view, cx| {
3621 view.redo_selection(&Default::default(), cx);
3622 });
3623
3624 cx.assert_editor_state(indoc!(
3625 r#"abc
3626 defˇghi
3627
3628 jk
3629 nlmo
3630 "#
3631 ));
3632
3633 cx.update_editor(|view, cx| {
3634 view.add_selection_below(&Default::default(), cx);
3635 });
3636
3637 cx.assert_editor_state(indoc!(
3638 r#"abc
3639 defˇghi
3640
3641 jk
3642 nlmˇo
3643 "#
3644 ));
3645
3646 cx.update_editor(|view, cx| {
3647 view.add_selection_below(&Default::default(), cx);
3648 });
3649
3650 cx.assert_editor_state(indoc!(
3651 r#"abc
3652 defˇghi
3653
3654 jk
3655 nlmˇo
3656 "#
3657 ));
3658
3659 // change selections
3660 cx.set_state(indoc!(
3661 r#"abc
3662 def«ˇg»hi
3663
3664 jk
3665 nlmo
3666 "#
3667 ));
3668
3669 cx.update_editor(|view, cx| {
3670 view.add_selection_below(&Default::default(), cx);
3671 });
3672
3673 cx.assert_editor_state(indoc!(
3674 r#"abc
3675 def«ˇg»hi
3676
3677 jk
3678 nlm«ˇo»
3679 "#
3680 ));
3681
3682 cx.update_editor(|view, cx| {
3683 view.add_selection_below(&Default::default(), cx);
3684 });
3685
3686 cx.assert_editor_state(indoc!(
3687 r#"abc
3688 def«ˇg»hi
3689
3690 jk
3691 nlm«ˇo»
3692 "#
3693 ));
3694
3695 cx.update_editor(|view, cx| {
3696 view.add_selection_above(&Default::default(), cx);
3697 });
3698
3699 cx.assert_editor_state(indoc!(
3700 r#"abc
3701 def«ˇg»hi
3702
3703 jk
3704 nlmo
3705 "#
3706 ));
3707
3708 cx.update_editor(|view, cx| {
3709 view.add_selection_above(&Default::default(), cx);
3710 });
3711
3712 cx.assert_editor_state(indoc!(
3713 r#"abc
3714 def«ˇg»hi
3715
3716 jk
3717 nlmo
3718 "#
3719 ));
3720
3721 // Change selections again
3722 cx.set_state(indoc!(
3723 r#"a«bc
3724 defgˇ»hi
3725
3726 jk
3727 nlmo
3728 "#
3729 ));
3730
3731 cx.update_editor(|view, cx| {
3732 view.add_selection_below(&Default::default(), cx);
3733 });
3734
3735 cx.assert_editor_state(indoc!(
3736 r#"a«bcˇ»
3737 d«efgˇ»hi
3738
3739 j«kˇ»
3740 nlmo
3741 "#
3742 ));
3743
3744 cx.update_editor(|view, cx| {
3745 view.add_selection_below(&Default::default(), cx);
3746 });
3747 cx.assert_editor_state(indoc!(
3748 r#"a«bcˇ»
3749 d«efgˇ»hi
3750
3751 j«kˇ»
3752 n«lmoˇ»
3753 "#
3754 ));
3755 cx.update_editor(|view, cx| {
3756 view.add_selection_above(&Default::default(), cx);
3757 });
3758
3759 cx.assert_editor_state(indoc!(
3760 r#"a«bcˇ»
3761 d«efgˇ»hi
3762
3763 j«kˇ»
3764 nlmo
3765 "#
3766 ));
3767
3768 // Change selections again
3769 cx.set_state(indoc!(
3770 r#"abc
3771 d«ˇefghi
3772
3773 jk
3774 nlm»o
3775 "#
3776 ));
3777
3778 cx.update_editor(|view, cx| {
3779 view.add_selection_above(&Default::default(), cx);
3780 });
3781
3782 cx.assert_editor_state(indoc!(
3783 r#"a«ˇbc»
3784 d«ˇef»ghi
3785
3786 j«ˇk»
3787 n«ˇlm»o
3788 "#
3789 ));
3790
3791 cx.update_editor(|view, cx| {
3792 view.add_selection_below(&Default::default(), cx);
3793 });
3794
3795 cx.assert_editor_state(indoc!(
3796 r#"abc
3797 d«ˇef»ghi
3798
3799 j«ˇk»
3800 n«ˇlm»o
3801 "#
3802 ));
3803}
3804
3805#[gpui::test]
3806async fn test_select_next(cx: &mut gpui::TestAppContext) {
3807 init_test(cx, |_| {});
3808
3809 let mut cx = EditorTestContext::new(cx).await;
3810 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
3811
3812 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
3813 .unwrap();
3814 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3815
3816 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
3817 .unwrap();
3818 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
3819
3820 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3821 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3822
3823 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3824 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
3825
3826 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
3827 .unwrap();
3828 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3829
3830 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
3831 .unwrap();
3832 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3833}
3834
3835#[gpui::test]
3836async fn test_select_previous(cx: &mut gpui::TestAppContext) {
3837 init_test(cx, |_| {});
3838 {
3839 // `Select previous` without a selection (selects wordwise)
3840 let mut cx = EditorTestContext::new(cx).await;
3841 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
3842
3843 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3844 .unwrap();
3845 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3846
3847 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3848 .unwrap();
3849 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
3850
3851 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3852 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
3853
3854 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3855 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
3856
3857 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3858 .unwrap();
3859 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
3860
3861 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3862 .unwrap();
3863 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
3864 }
3865 {
3866 // `Select previous` with a selection
3867 let mut cx = EditorTestContext::new(cx).await;
3868 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
3869
3870 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3871 .unwrap();
3872 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
3873
3874 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3875 .unwrap();
3876 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
3877
3878 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
3879 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
3880
3881 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
3882 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
3883
3884 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3885 .unwrap();
3886 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
3887
3888 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
3889 .unwrap();
3890 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
3891 }
3892}
3893
3894#[gpui::test]
3895async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
3896 init_test(cx, |_| {});
3897
3898 let language = Arc::new(Language::new(
3899 LanguageConfig::default(),
3900 Some(tree_sitter_rust::language()),
3901 ));
3902
3903 let text = r#"
3904 use mod1::mod2::{mod3, mod4};
3905
3906 fn fn_1(param1: bool, param2: &str) {
3907 let var1 = "text";
3908 }
3909 "#
3910 .unindent();
3911
3912 let buffer = cx.build_model(|cx| {
3913 Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
3914 });
3915 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
3916 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
3917
3918 view.condition::<crate::EditorEvent>(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
3919 .await;
3920
3921 view.update(cx, |view, cx| {
3922 view.change_selections(None, cx, |s| {
3923 s.select_display_ranges([
3924 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3925 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3926 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
3927 ]);
3928 });
3929 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3930 });
3931 assert_eq!(
3932 view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
3933 &[
3934 DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
3935 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3936 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
3937 ]
3938 );
3939
3940 view.update(cx, |view, cx| {
3941 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3942 });
3943 assert_eq!(
3944 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3945 &[
3946 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3947 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
3948 ]
3949 );
3950
3951 view.update(cx, |view, cx| {
3952 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3953 });
3954 assert_eq!(
3955 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3956 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
3957 );
3958
3959 // Trying to expand the selected syntax node one more time has no effect.
3960 view.update(cx, |view, cx| {
3961 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
3962 });
3963 assert_eq!(
3964 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3965 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
3966 );
3967
3968 view.update(cx, |view, cx| {
3969 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3970 });
3971 assert_eq!(
3972 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3973 &[
3974 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
3975 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
3976 ]
3977 );
3978
3979 view.update(cx, |view, cx| {
3980 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3981 });
3982 assert_eq!(
3983 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3984 &[
3985 DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
3986 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
3987 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
3988 ]
3989 );
3990
3991 view.update(cx, |view, cx| {
3992 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
3993 });
3994 assert_eq!(
3995 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
3996 &[
3997 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
3998 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
3999 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
4000 ]
4001 );
4002
4003 // Trying to shrink the selected syntax node one more time has no effect.
4004 view.update(cx, |view, cx| {
4005 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4006 });
4007 assert_eq!(
4008 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4009 &[
4010 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
4011 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
4012 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
4013 ]
4014 );
4015
4016 // Ensure that we keep expanding the selection if the larger selection starts or ends within
4017 // a fold.
4018 view.update(cx, |view, cx| {
4019 view.fold_ranges(
4020 vec![
4021 Point::new(0, 21)..Point::new(0, 24),
4022 Point::new(3, 20)..Point::new(3, 22),
4023 ],
4024 true,
4025 cx,
4026 );
4027 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4028 });
4029 assert_eq!(
4030 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4031 &[
4032 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
4033 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
4034 DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23),
4035 ]
4036 );
4037}
4038
4039#[gpui::test]
4040async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
4041 init_test(cx, |_| {});
4042
4043 let language = Arc::new(
4044 Language::new(
4045 LanguageConfig {
4046 brackets: BracketPairConfig {
4047 pairs: vec![
4048 BracketPair {
4049 start: "{".to_string(),
4050 end: "}".to_string(),
4051 close: false,
4052 newline: true,
4053 },
4054 BracketPair {
4055 start: "(".to_string(),
4056 end: ")".to_string(),
4057 close: false,
4058 newline: true,
4059 },
4060 ],
4061 ..Default::default()
4062 },
4063 ..Default::default()
4064 },
4065 Some(tree_sitter_rust::language()),
4066 )
4067 .with_indents_query(
4068 r#"
4069 (_ "(" ")" @end) @indent
4070 (_ "{" "}" @end) @indent
4071 "#,
4072 )
4073 .unwrap(),
4074 );
4075
4076 let text = "fn a() {}";
4077
4078 let buffer = cx.build_model(|cx| {
4079 Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
4080 });
4081 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
4082 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4083 editor
4084 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
4085 .await;
4086
4087 editor.update(cx, |editor, cx| {
4088 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
4089 editor.newline(&Newline, cx);
4090 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
4091 assert_eq!(
4092 editor.selections.ranges(cx),
4093 &[
4094 Point::new(1, 4)..Point::new(1, 4),
4095 Point::new(3, 4)..Point::new(3, 4),
4096 Point::new(5, 0)..Point::new(5, 0)
4097 ]
4098 );
4099 });
4100}
4101
4102#[gpui::test]
4103async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
4104 init_test(cx, |_| {});
4105
4106 let mut cx = EditorTestContext::new(cx).await;
4107
4108 let language = Arc::new(Language::new(
4109 LanguageConfig {
4110 brackets: BracketPairConfig {
4111 pairs: vec![
4112 BracketPair {
4113 start: "{".to_string(),
4114 end: "}".to_string(),
4115 close: true,
4116 newline: true,
4117 },
4118 BracketPair {
4119 start: "(".to_string(),
4120 end: ")".to_string(),
4121 close: true,
4122 newline: true,
4123 },
4124 BracketPair {
4125 start: "/*".to_string(),
4126 end: " */".to_string(),
4127 close: true,
4128 newline: true,
4129 },
4130 BracketPair {
4131 start: "[".to_string(),
4132 end: "]".to_string(),
4133 close: false,
4134 newline: true,
4135 },
4136 BracketPair {
4137 start: "\"".to_string(),
4138 end: "\"".to_string(),
4139 close: true,
4140 newline: false,
4141 },
4142 ],
4143 ..Default::default()
4144 },
4145 autoclose_before: "})]".to_string(),
4146 ..Default::default()
4147 },
4148 Some(tree_sitter_rust::language()),
4149 ));
4150
4151 let registry = Arc::new(LanguageRegistry::test());
4152 registry.add(language.clone());
4153 cx.update_buffer(|buffer, cx| {
4154 buffer.set_language_registry(registry);
4155 buffer.set_language(Some(language), cx);
4156 });
4157
4158 cx.set_state(
4159 &r#"
4160 🏀ˇ
4161 εˇ
4162 ❤️ˇ
4163 "#
4164 .unindent(),
4165 );
4166
4167 // autoclose multiple nested brackets at multiple cursors
4168 cx.update_editor(|view, cx| {
4169 view.handle_input("{", cx);
4170 view.handle_input("{", cx);
4171 view.handle_input("{", cx);
4172 });
4173 cx.assert_editor_state(
4174 &"
4175 🏀{{{ˇ}}}
4176 ε{{{ˇ}}}
4177 ❤️{{{ˇ}}}
4178 "
4179 .unindent(),
4180 );
4181
4182 // insert a different closing bracket
4183 cx.update_editor(|view, cx| {
4184 view.handle_input(")", cx);
4185 });
4186 cx.assert_editor_state(
4187 &"
4188 🏀{{{)ˇ}}}
4189 ε{{{)ˇ}}}
4190 ❤️{{{)ˇ}}}
4191 "
4192 .unindent(),
4193 );
4194
4195 // skip over the auto-closed brackets when typing a closing bracket
4196 cx.update_editor(|view, cx| {
4197 view.move_right(&MoveRight, cx);
4198 view.handle_input("}", cx);
4199 view.handle_input("}", cx);
4200 view.handle_input("}", cx);
4201 });
4202 cx.assert_editor_state(
4203 &"
4204 🏀{{{)}}}}ˇ
4205 ε{{{)}}}}ˇ
4206 ❤️{{{)}}}}ˇ
4207 "
4208 .unindent(),
4209 );
4210
4211 // autoclose multi-character pairs
4212 cx.set_state(
4213 &"
4214 ˇ
4215 ˇ
4216 "
4217 .unindent(),
4218 );
4219 cx.update_editor(|view, cx| {
4220 view.handle_input("/", cx);
4221 view.handle_input("*", cx);
4222 });
4223 cx.assert_editor_state(
4224 &"
4225 /*ˇ */
4226 /*ˇ */
4227 "
4228 .unindent(),
4229 );
4230
4231 // one cursor autocloses a multi-character pair, one cursor
4232 // does not autoclose.
4233 cx.set_state(
4234 &"
4235 /ˇ
4236 ˇ
4237 "
4238 .unindent(),
4239 );
4240 cx.update_editor(|view, cx| view.handle_input("*", cx));
4241 cx.assert_editor_state(
4242 &"
4243 /*ˇ */
4244 *ˇ
4245 "
4246 .unindent(),
4247 );
4248
4249 // Don't autoclose if the next character isn't whitespace and isn't
4250 // listed in the language's "autoclose_before" section.
4251 cx.set_state("ˇa b");
4252 cx.update_editor(|view, cx| view.handle_input("{", cx));
4253 cx.assert_editor_state("{ˇa b");
4254
4255 // Don't autoclose if `close` is false for the bracket pair
4256 cx.set_state("ˇ");
4257 cx.update_editor(|view, cx| view.handle_input("[", cx));
4258 cx.assert_editor_state("[ˇ");
4259
4260 // Surround with brackets if text is selected
4261 cx.set_state("«aˇ» b");
4262 cx.update_editor(|view, cx| view.handle_input("{", cx));
4263 cx.assert_editor_state("{«aˇ»} b");
4264
4265 // Autclose pair where the start and end characters are the same
4266 cx.set_state("aˇ");
4267 cx.update_editor(|view, cx| view.handle_input("\"", cx));
4268 cx.assert_editor_state("a\"ˇ\"");
4269 cx.update_editor(|view, cx| view.handle_input("\"", cx));
4270 cx.assert_editor_state("a\"\"ˇ");
4271}
4272
4273#[gpui::test]
4274async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
4275 init_test(cx, |_| {});
4276
4277 let mut cx = EditorTestContext::new(cx).await;
4278
4279 let html_language = Arc::new(
4280 Language::new(
4281 LanguageConfig {
4282 name: "HTML".into(),
4283 brackets: BracketPairConfig {
4284 pairs: vec![
4285 BracketPair {
4286 start: "<".into(),
4287 end: ">".into(),
4288 close: true,
4289 ..Default::default()
4290 },
4291 BracketPair {
4292 start: "{".into(),
4293 end: "}".into(),
4294 close: true,
4295 ..Default::default()
4296 },
4297 BracketPair {
4298 start: "(".into(),
4299 end: ")".into(),
4300 close: true,
4301 ..Default::default()
4302 },
4303 ],
4304 ..Default::default()
4305 },
4306 autoclose_before: "})]>".into(),
4307 ..Default::default()
4308 },
4309 Some(tree_sitter_html::language()),
4310 )
4311 .with_injection_query(
4312 r#"
4313 (script_element
4314 (raw_text) @content
4315 (#set! "language" "javascript"))
4316 "#,
4317 )
4318 .unwrap(),
4319 );
4320
4321 let javascript_language = Arc::new(Language::new(
4322 LanguageConfig {
4323 name: "JavaScript".into(),
4324 brackets: BracketPairConfig {
4325 pairs: vec![
4326 BracketPair {
4327 start: "/*".into(),
4328 end: " */".into(),
4329 close: true,
4330 ..Default::default()
4331 },
4332 BracketPair {
4333 start: "{".into(),
4334 end: "}".into(),
4335 close: true,
4336 ..Default::default()
4337 },
4338 BracketPair {
4339 start: "(".into(),
4340 end: ")".into(),
4341 close: true,
4342 ..Default::default()
4343 },
4344 ],
4345 ..Default::default()
4346 },
4347 autoclose_before: "})]>".into(),
4348 ..Default::default()
4349 },
4350 Some(tree_sitter_typescript::language_tsx()),
4351 ));
4352
4353 let registry = Arc::new(LanguageRegistry::test());
4354 registry.add(html_language.clone());
4355 registry.add(javascript_language.clone());
4356
4357 cx.update_buffer(|buffer, cx| {
4358 buffer.set_language_registry(registry);
4359 buffer.set_language(Some(html_language), cx);
4360 });
4361
4362 cx.set_state(
4363 &r#"
4364 <body>ˇ
4365 <script>
4366 var x = 1;ˇ
4367 </script>
4368 </body>ˇ
4369 "#
4370 .unindent(),
4371 );
4372
4373 // Precondition: different languages are active at different locations.
4374 cx.update_editor(|editor, cx| {
4375 let snapshot = editor.snapshot(cx);
4376 let cursors = editor.selections.ranges::<usize>(cx);
4377 let languages = cursors
4378 .iter()
4379 .map(|c| snapshot.language_at(c.start).unwrap().name())
4380 .collect::<Vec<_>>();
4381 assert_eq!(
4382 languages,
4383 &["HTML".into(), "JavaScript".into(), "HTML".into()]
4384 );
4385 });
4386
4387 // Angle brackets autoclose in HTML, but not JavaScript.
4388 cx.update_editor(|editor, cx| {
4389 editor.handle_input("<", cx);
4390 editor.handle_input("a", cx);
4391 });
4392 cx.assert_editor_state(
4393 &r#"
4394 <body><aˇ>
4395 <script>
4396 var x = 1;<aˇ
4397 </script>
4398 </body><aˇ>
4399 "#
4400 .unindent(),
4401 );
4402
4403 // Curly braces and parens autoclose in both HTML and JavaScript.
4404 cx.update_editor(|editor, cx| {
4405 editor.handle_input(" b=", cx);
4406 editor.handle_input("{", cx);
4407 editor.handle_input("c", cx);
4408 editor.handle_input("(", cx);
4409 });
4410 cx.assert_editor_state(
4411 &r#"
4412 <body><a b={c(ˇ)}>
4413 <script>
4414 var x = 1;<a b={c(ˇ)}
4415 </script>
4416 </body><a b={c(ˇ)}>
4417 "#
4418 .unindent(),
4419 );
4420
4421 // Brackets that were already autoclosed are skipped.
4422 cx.update_editor(|editor, cx| {
4423 editor.handle_input(")", cx);
4424 editor.handle_input("d", cx);
4425 editor.handle_input("}", cx);
4426 });
4427 cx.assert_editor_state(
4428 &r#"
4429 <body><a b={c()d}ˇ>
4430 <script>
4431 var x = 1;<a b={c()d}ˇ
4432 </script>
4433 </body><a b={c()d}ˇ>
4434 "#
4435 .unindent(),
4436 );
4437 cx.update_editor(|editor, cx| {
4438 editor.handle_input(">", cx);
4439 });
4440 cx.assert_editor_state(
4441 &r#"
4442 <body><a b={c()d}>ˇ
4443 <script>
4444 var x = 1;<a b={c()d}>ˇ
4445 </script>
4446 </body><a b={c()d}>ˇ
4447 "#
4448 .unindent(),
4449 );
4450
4451 // Reset
4452 cx.set_state(
4453 &r#"
4454 <body>ˇ
4455 <script>
4456 var x = 1;ˇ
4457 </script>
4458 </body>ˇ
4459 "#
4460 .unindent(),
4461 );
4462
4463 cx.update_editor(|editor, cx| {
4464 editor.handle_input("<", cx);
4465 });
4466 cx.assert_editor_state(
4467 &r#"
4468 <body><ˇ>
4469 <script>
4470 var x = 1;<ˇ
4471 </script>
4472 </body><ˇ>
4473 "#
4474 .unindent(),
4475 );
4476
4477 // When backspacing, the closing angle brackets are removed.
4478 cx.update_editor(|editor, cx| {
4479 editor.backspace(&Backspace, cx);
4480 });
4481 cx.assert_editor_state(
4482 &r#"
4483 <body>ˇ
4484 <script>
4485 var x = 1;ˇ
4486 </script>
4487 </body>ˇ
4488 "#
4489 .unindent(),
4490 );
4491
4492 // Block comments autoclose in JavaScript, but not HTML.
4493 cx.update_editor(|editor, cx| {
4494 editor.handle_input("/", cx);
4495 editor.handle_input("*", cx);
4496 });
4497 cx.assert_editor_state(
4498 &r#"
4499 <body>/*ˇ
4500 <script>
4501 var x = 1;/*ˇ */
4502 </script>
4503 </body>/*ˇ
4504 "#
4505 .unindent(),
4506 );
4507}
4508
4509#[gpui::test]
4510async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
4511 init_test(cx, |_| {});
4512
4513 let mut cx = EditorTestContext::new(cx).await;
4514
4515 let rust_language = Arc::new(
4516 Language::new(
4517 LanguageConfig {
4518 name: "Rust".into(),
4519 brackets: serde_json::from_value(json!([
4520 { "start": "{", "end": "}", "close": true, "newline": true },
4521 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
4522 ]))
4523 .unwrap(),
4524 autoclose_before: "})]>".into(),
4525 ..Default::default()
4526 },
4527 Some(tree_sitter_rust::language()),
4528 )
4529 .with_override_query("(string_literal) @string")
4530 .unwrap(),
4531 );
4532
4533 let registry = Arc::new(LanguageRegistry::test());
4534 registry.add(rust_language.clone());
4535
4536 cx.update_buffer(|buffer, cx| {
4537 buffer.set_language_registry(registry);
4538 buffer.set_language(Some(rust_language), cx);
4539 });
4540
4541 cx.set_state(
4542 &r#"
4543 let x = ˇ
4544 "#
4545 .unindent(),
4546 );
4547
4548 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
4549 cx.update_editor(|editor, cx| {
4550 editor.handle_input("\"", cx);
4551 });
4552 cx.assert_editor_state(
4553 &r#"
4554 let x = "ˇ"
4555 "#
4556 .unindent(),
4557 );
4558
4559 // Inserting another quotation mark. The cursor moves across the existing
4560 // automatically-inserted quotation mark.
4561 cx.update_editor(|editor, cx| {
4562 editor.handle_input("\"", cx);
4563 });
4564 cx.assert_editor_state(
4565 &r#"
4566 let x = ""ˇ
4567 "#
4568 .unindent(),
4569 );
4570
4571 // Reset
4572 cx.set_state(
4573 &r#"
4574 let x = ˇ
4575 "#
4576 .unindent(),
4577 );
4578
4579 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
4580 cx.update_editor(|editor, cx| {
4581 editor.handle_input("\"", cx);
4582 editor.handle_input(" ", cx);
4583 editor.move_left(&Default::default(), cx);
4584 editor.handle_input("\\", cx);
4585 editor.handle_input("\"", cx);
4586 });
4587 cx.assert_editor_state(
4588 &r#"
4589 let x = "\"ˇ "
4590 "#
4591 .unindent(),
4592 );
4593
4594 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
4595 // mark. Nothing is inserted.
4596 cx.update_editor(|editor, cx| {
4597 editor.move_right(&Default::default(), cx);
4598 editor.handle_input("\"", cx);
4599 });
4600 cx.assert_editor_state(
4601 &r#"
4602 let x = "\" "ˇ
4603 "#
4604 .unindent(),
4605 );
4606}
4607
4608#[gpui::test]
4609async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
4610 init_test(cx, |_| {});
4611
4612 let language = Arc::new(Language::new(
4613 LanguageConfig {
4614 brackets: BracketPairConfig {
4615 pairs: vec![
4616 BracketPair {
4617 start: "{".to_string(),
4618 end: "}".to_string(),
4619 close: true,
4620 newline: true,
4621 },
4622 BracketPair {
4623 start: "/* ".to_string(),
4624 end: "*/".to_string(),
4625 close: true,
4626 ..Default::default()
4627 },
4628 ],
4629 ..Default::default()
4630 },
4631 ..Default::default()
4632 },
4633 Some(tree_sitter_rust::language()),
4634 ));
4635
4636 let text = r#"
4637 a
4638 b
4639 c
4640 "#
4641 .unindent();
4642
4643 let buffer = cx.build_model(|cx| {
4644 Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
4645 });
4646 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
4647 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4648 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4649 .await;
4650
4651 view.update(cx, |view, cx| {
4652 view.change_selections(None, cx, |s| {
4653 s.select_display_ranges([
4654 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4655 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4656 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
4657 ])
4658 });
4659
4660 view.handle_input("{", cx);
4661 view.handle_input("{", cx);
4662 view.handle_input("{", cx);
4663 assert_eq!(
4664 view.text(cx),
4665 "
4666 {{{a}}}
4667 {{{b}}}
4668 {{{c}}}
4669 "
4670 .unindent()
4671 );
4672 assert_eq!(
4673 view.selections.display_ranges(cx),
4674 [
4675 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 4),
4676 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4),
4677 DisplayPoint::new(2, 3)..DisplayPoint::new(2, 4)
4678 ]
4679 );
4680
4681 view.undo(&Undo, cx);
4682 view.undo(&Undo, cx);
4683 view.undo(&Undo, cx);
4684 assert_eq!(
4685 view.text(cx),
4686 "
4687 a
4688 b
4689 c
4690 "
4691 .unindent()
4692 );
4693 assert_eq!(
4694 view.selections.display_ranges(cx),
4695 [
4696 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4697 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4698 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
4699 ]
4700 );
4701
4702 // Ensure inserting the first character of a multi-byte bracket pair
4703 // doesn't surround the selections with the bracket.
4704 view.handle_input("/", cx);
4705 assert_eq!(
4706 view.text(cx),
4707 "
4708 /
4709 /
4710 /
4711 "
4712 .unindent()
4713 );
4714 assert_eq!(
4715 view.selections.display_ranges(cx),
4716 [
4717 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
4718 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
4719 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
4720 ]
4721 );
4722
4723 view.undo(&Undo, cx);
4724 assert_eq!(
4725 view.text(cx),
4726 "
4727 a
4728 b
4729 c
4730 "
4731 .unindent()
4732 );
4733 assert_eq!(
4734 view.selections.display_ranges(cx),
4735 [
4736 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4737 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4738 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
4739 ]
4740 );
4741
4742 // Ensure inserting the last character of a multi-byte bracket pair
4743 // doesn't surround the selections with the bracket.
4744 view.handle_input("*", cx);
4745 assert_eq!(
4746 view.text(cx),
4747 "
4748 *
4749 *
4750 *
4751 "
4752 .unindent()
4753 );
4754 assert_eq!(
4755 view.selections.display_ranges(cx),
4756 [
4757 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
4758 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
4759 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
4760 ]
4761 );
4762 });
4763}
4764
4765#[gpui::test]
4766async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
4767 init_test(cx, |_| {});
4768
4769 let language = Arc::new(Language::new(
4770 LanguageConfig {
4771 brackets: BracketPairConfig {
4772 pairs: vec![BracketPair {
4773 start: "{".to_string(),
4774 end: "}".to_string(),
4775 close: true,
4776 newline: true,
4777 }],
4778 ..Default::default()
4779 },
4780 autoclose_before: "}".to_string(),
4781 ..Default::default()
4782 },
4783 Some(tree_sitter_rust::language()),
4784 ));
4785
4786 let text = r#"
4787 a
4788 b
4789 c
4790 "#
4791 .unindent();
4792
4793 let buffer = cx.build_model(|cx| {
4794 Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
4795 });
4796 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
4797 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4798 editor
4799 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4800 .await;
4801
4802 editor.update(cx, |editor, cx| {
4803 editor.change_selections(None, cx, |s| {
4804 s.select_ranges([
4805 Point::new(0, 1)..Point::new(0, 1),
4806 Point::new(1, 1)..Point::new(1, 1),
4807 Point::new(2, 1)..Point::new(2, 1),
4808 ])
4809 });
4810
4811 editor.handle_input("{", cx);
4812 editor.handle_input("{", cx);
4813 editor.handle_input("_", cx);
4814 assert_eq!(
4815 editor.text(cx),
4816 "
4817 a{{_}}
4818 b{{_}}
4819 c{{_}}
4820 "
4821 .unindent()
4822 );
4823 assert_eq!(
4824 editor.selections.ranges::<Point>(cx),
4825 [
4826 Point::new(0, 4)..Point::new(0, 4),
4827 Point::new(1, 4)..Point::new(1, 4),
4828 Point::new(2, 4)..Point::new(2, 4)
4829 ]
4830 );
4831
4832 editor.backspace(&Default::default(), cx);
4833 editor.backspace(&Default::default(), cx);
4834 assert_eq!(
4835 editor.text(cx),
4836 "
4837 a{}
4838 b{}
4839 c{}
4840 "
4841 .unindent()
4842 );
4843 assert_eq!(
4844 editor.selections.ranges::<Point>(cx),
4845 [
4846 Point::new(0, 2)..Point::new(0, 2),
4847 Point::new(1, 2)..Point::new(1, 2),
4848 Point::new(2, 2)..Point::new(2, 2)
4849 ]
4850 );
4851
4852 editor.delete_to_previous_word_start(&Default::default(), cx);
4853 assert_eq!(
4854 editor.text(cx),
4855 "
4856 a
4857 b
4858 c
4859 "
4860 .unindent()
4861 );
4862 assert_eq!(
4863 editor.selections.ranges::<Point>(cx),
4864 [
4865 Point::new(0, 1)..Point::new(0, 1),
4866 Point::new(1, 1)..Point::new(1, 1),
4867 Point::new(2, 1)..Point::new(2, 1)
4868 ]
4869 );
4870 });
4871}
4872
4873// todo!(select_anchor_ranges)
4874#[gpui::test]
4875async fn test_snippets(cx: &mut gpui::TestAppContext) {
4876 init_test(cx, |_| {});
4877
4878 let (text, insertion_ranges) = marked_text_ranges(
4879 indoc! {"
4880 a.ˇ b
4881 a.ˇ b
4882 a.ˇ b
4883 "},
4884 false,
4885 );
4886
4887 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
4888 let (editor, mut cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4889
4890 editor.update(cx, |editor, cx| {
4891 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
4892
4893 editor
4894 .insert_snippet(&insertion_ranges, snippet, cx)
4895 .unwrap();
4896
4897 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
4898 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
4899 assert_eq!(editor.text(cx), expected_text);
4900 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
4901 }
4902
4903 assert(
4904 editor,
4905 cx,
4906 indoc! {"
4907 a.f(«one», two, «three») b
4908 a.f(«one», two, «three») b
4909 a.f(«one», two, «three») b
4910 "},
4911 );
4912
4913 // Can't move earlier than the first tab stop
4914 assert!(!editor.move_to_prev_snippet_tabstop(cx));
4915 assert(
4916 editor,
4917 cx,
4918 indoc! {"
4919 a.f(«one», two, «three») b
4920 a.f(«one», two, «three») b
4921 a.f(«one», two, «three») b
4922 "},
4923 );
4924
4925 assert!(editor.move_to_next_snippet_tabstop(cx));
4926 assert(
4927 editor,
4928 cx,
4929 indoc! {"
4930 a.f(one, «two», three) b
4931 a.f(one, «two», three) b
4932 a.f(one, «two», three) b
4933 "},
4934 );
4935
4936 editor.move_to_prev_snippet_tabstop(cx);
4937 assert(
4938 editor,
4939 cx,
4940 indoc! {"
4941 a.f(«one», two, «three») b
4942 a.f(«one», two, «three») b
4943 a.f(«one», two, «three») b
4944 "},
4945 );
4946
4947 assert!(editor.move_to_next_snippet_tabstop(cx));
4948 assert(
4949 editor,
4950 cx,
4951 indoc! {"
4952 a.f(one, «two», three) b
4953 a.f(one, «two», three) b
4954 a.f(one, «two», three) b
4955 "},
4956 );
4957 assert!(editor.move_to_next_snippet_tabstop(cx));
4958 assert(
4959 editor,
4960 cx,
4961 indoc! {"
4962 a.f(one, two, three)ˇ b
4963 a.f(one, two, three)ˇ b
4964 a.f(one, two, three)ˇ b
4965 "},
4966 );
4967
4968 // As soon as the last tab stop is reached, snippet state is gone
4969 editor.move_to_prev_snippet_tabstop(cx);
4970 assert(
4971 editor,
4972 cx,
4973 indoc! {"
4974 a.f(one, two, three)ˇ b
4975 a.f(one, two, three)ˇ b
4976 a.f(one, two, three)ˇ b
4977 "},
4978 );
4979 });
4980}
4981
4982#[gpui::test]
4983async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
4984 init_test(cx, |_| {});
4985
4986 let mut language = Language::new(
4987 LanguageConfig {
4988 name: "Rust".into(),
4989 path_suffixes: vec!["rs".to_string()],
4990 ..Default::default()
4991 },
4992 Some(tree_sitter_rust::language()),
4993 );
4994 let mut fake_servers = language
4995 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
4996 capabilities: lsp::ServerCapabilities {
4997 document_formatting_provider: Some(lsp::OneOf::Left(true)),
4998 ..Default::default()
4999 },
5000 ..Default::default()
5001 }))
5002 .await;
5003
5004 let fs = FakeFs::new(cx.executor());
5005 fs.insert_file("/file.rs", Default::default()).await;
5006
5007 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5008 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
5009 let buffer = project
5010 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
5011 .await
5012 .unwrap();
5013
5014 cx.executor().start_waiting();
5015 let fake_server = fake_servers.next().await.unwrap();
5016
5017 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
5018 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5019 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5020 assert!(cx.read(|cx| editor.is_dirty(cx)));
5021
5022 let save = editor
5023 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5024 .unwrap();
5025 fake_server
5026 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5027 assert_eq!(
5028 params.text_document.uri,
5029 lsp::Url::from_file_path("/file.rs").unwrap()
5030 );
5031 assert_eq!(params.options.tab_size, 4);
5032 Ok(Some(vec![lsp::TextEdit::new(
5033 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
5034 ", ".to_string(),
5035 )]))
5036 })
5037 .next()
5038 .await;
5039 cx.executor().start_waiting();
5040 let x = save.await;
5041
5042 assert_eq!(
5043 editor.update(cx, |editor, cx| editor.text(cx)),
5044 "one, two\nthree\n"
5045 );
5046 assert!(!cx.read(|cx| editor.is_dirty(cx)));
5047
5048 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5049 assert!(cx.read(|cx| editor.is_dirty(cx)));
5050
5051 // Ensure we can still save even if formatting hangs.
5052 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5053 assert_eq!(
5054 params.text_document.uri,
5055 lsp::Url::from_file_path("/file.rs").unwrap()
5056 );
5057 futures::future::pending::<()>().await;
5058 unreachable!()
5059 });
5060 let save = editor
5061 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5062 .unwrap();
5063 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
5064 cx.executor().start_waiting();
5065 save.await;
5066 assert_eq!(
5067 editor.update(cx, |editor, cx| editor.text(cx)),
5068 "one\ntwo\nthree\n"
5069 );
5070 assert!(!cx.read(|cx| editor.is_dirty(cx)));
5071
5072 // Set rust language override and assert overridden tabsize is sent to language server
5073 update_test_language_settings(cx, |settings| {
5074 settings.languages.insert(
5075 "Rust".into(),
5076 LanguageSettingsContent {
5077 tab_size: NonZeroU32::new(8),
5078 ..Default::default()
5079 },
5080 );
5081 });
5082
5083 let save = editor
5084 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5085 .unwrap();
5086 fake_server
5087 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5088 assert_eq!(
5089 params.text_document.uri,
5090 lsp::Url::from_file_path("/file.rs").unwrap()
5091 );
5092 assert_eq!(params.options.tab_size, 8);
5093 Ok(Some(vec![]))
5094 })
5095 .next()
5096 .await;
5097 cx.executor().start_waiting();
5098 save.await;
5099}
5100
5101#[gpui::test]
5102async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
5103 init_test(cx, |_| {});
5104
5105 let mut language = Language::new(
5106 LanguageConfig {
5107 name: "Rust".into(),
5108 path_suffixes: vec!["rs".to_string()],
5109 ..Default::default()
5110 },
5111 Some(tree_sitter_rust::language()),
5112 );
5113 let mut fake_servers = language
5114 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
5115 capabilities: lsp::ServerCapabilities {
5116 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
5117 ..Default::default()
5118 },
5119 ..Default::default()
5120 }))
5121 .await;
5122
5123 let fs = FakeFs::new(cx.executor());
5124 fs.insert_file("/file.rs", Default::default()).await;
5125
5126 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5127 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
5128 let buffer = project
5129 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
5130 .await
5131 .unwrap();
5132
5133 cx.executor().start_waiting();
5134 let fake_server = fake_servers.next().await.unwrap();
5135
5136 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
5137 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5138 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5139 assert!(cx.read(|cx| editor.is_dirty(cx)));
5140
5141 let save = editor
5142 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5143 .unwrap();
5144 fake_server
5145 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
5146 assert_eq!(
5147 params.text_document.uri,
5148 lsp::Url::from_file_path("/file.rs").unwrap()
5149 );
5150 assert_eq!(params.options.tab_size, 4);
5151 Ok(Some(vec![lsp::TextEdit::new(
5152 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
5153 ", ".to_string(),
5154 )]))
5155 })
5156 .next()
5157 .await;
5158 cx.executor().start_waiting();
5159 save.await;
5160 assert_eq!(
5161 editor.update(cx, |editor, cx| editor.text(cx)),
5162 "one, two\nthree\n"
5163 );
5164 assert!(!cx.read(|cx| editor.is_dirty(cx)));
5165
5166 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5167 assert!(cx.read(|cx| editor.is_dirty(cx)));
5168
5169 // Ensure we can still save even if formatting hangs.
5170 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
5171 move |params, _| async move {
5172 assert_eq!(
5173 params.text_document.uri,
5174 lsp::Url::from_file_path("/file.rs").unwrap()
5175 );
5176 futures::future::pending::<()>().await;
5177 unreachable!()
5178 },
5179 );
5180 let save = editor
5181 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5182 .unwrap();
5183 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
5184 cx.executor().start_waiting();
5185 save.await;
5186 assert_eq!(
5187 editor.update(cx, |editor, cx| editor.text(cx)),
5188 "one\ntwo\nthree\n"
5189 );
5190 assert!(!cx.read(|cx| editor.is_dirty(cx)));
5191
5192 // Set rust language override and assert overridden tabsize is sent to language server
5193 update_test_language_settings(cx, |settings| {
5194 settings.languages.insert(
5195 "Rust".into(),
5196 LanguageSettingsContent {
5197 tab_size: NonZeroU32::new(8),
5198 ..Default::default()
5199 },
5200 );
5201 });
5202
5203 let save = editor
5204 .update(cx, |editor, cx| editor.save(project.clone(), cx))
5205 .unwrap();
5206 fake_server
5207 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
5208 assert_eq!(
5209 params.text_document.uri,
5210 lsp::Url::from_file_path("/file.rs").unwrap()
5211 );
5212 assert_eq!(params.options.tab_size, 8);
5213 Ok(Some(vec![]))
5214 })
5215 .next()
5216 .await;
5217 cx.executor().start_waiting();
5218 save.await;
5219}
5220
5221#[gpui::test]
5222async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
5223 init_test(cx, |settings| {
5224 settings.defaults.formatter = Some(language_settings::Formatter::LanguageServer)
5225 });
5226
5227 let mut language = Language::new(
5228 LanguageConfig {
5229 name: "Rust".into(),
5230 path_suffixes: vec!["rs".to_string()],
5231 // Enable Prettier formatting for the same buffer, and ensure
5232 // LSP is called instead of Prettier.
5233 prettier_parser_name: Some("test_parser".to_string()),
5234 ..Default::default()
5235 },
5236 Some(tree_sitter_rust::language()),
5237 );
5238 let mut fake_servers = language
5239 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
5240 capabilities: lsp::ServerCapabilities {
5241 document_formatting_provider: Some(lsp::OneOf::Left(true)),
5242 ..Default::default()
5243 },
5244 ..Default::default()
5245 }))
5246 .await;
5247
5248 let fs = FakeFs::new(cx.executor());
5249 fs.insert_file("/file.rs", Default::default()).await;
5250
5251 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5252 project.update(cx, |project, _| {
5253 project.languages().add(Arc::new(language));
5254 });
5255 let buffer = project
5256 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
5257 .await
5258 .unwrap();
5259
5260 cx.executor().start_waiting();
5261 let fake_server = fake_servers.next().await.unwrap();
5262
5263 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
5264 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5265 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5266
5267 let format = editor
5268 .update(cx, |editor, cx| {
5269 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
5270 })
5271 .unwrap();
5272 fake_server
5273 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5274 assert_eq!(
5275 params.text_document.uri,
5276 lsp::Url::from_file_path("/file.rs").unwrap()
5277 );
5278 assert_eq!(params.options.tab_size, 4);
5279 Ok(Some(vec![lsp::TextEdit::new(
5280 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
5281 ", ".to_string(),
5282 )]))
5283 })
5284 .next()
5285 .await;
5286 cx.executor().start_waiting();
5287 format.await;
5288 assert_eq!(
5289 editor.update(cx, |editor, cx| editor.text(cx)),
5290 "one, two\nthree\n"
5291 );
5292
5293 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5294 // Ensure we don't lock if formatting hangs.
5295 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5296 assert_eq!(
5297 params.text_document.uri,
5298 lsp::Url::from_file_path("/file.rs").unwrap()
5299 );
5300 futures::future::pending::<()>().await;
5301 unreachable!()
5302 });
5303 let format = editor
5304 .update(cx, |editor, cx| {
5305 editor.perform_format(project, FormatTrigger::Manual, cx)
5306 })
5307 .unwrap();
5308 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
5309 cx.executor().start_waiting();
5310 format.await;
5311 assert_eq!(
5312 editor.update(cx, |editor, cx| editor.text(cx)),
5313 "one\ntwo\nthree\n"
5314 );
5315}
5316
5317#[gpui::test]
5318async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
5319 init_test(cx, |_| {});
5320
5321 let mut cx = EditorLspTestContext::new_rust(
5322 lsp::ServerCapabilities {
5323 document_formatting_provider: Some(lsp::OneOf::Left(true)),
5324 ..Default::default()
5325 },
5326 cx,
5327 )
5328 .await;
5329
5330 cx.set_state(indoc! {"
5331 one.twoˇ
5332 "});
5333
5334 // The format request takes a long time. When it completes, it inserts
5335 // a newline and an indent before the `.`
5336 cx.lsp
5337 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
5338 let executor = cx.background_executor().clone();
5339 async move {
5340 executor.timer(Duration::from_millis(100)).await;
5341 Ok(Some(vec![lsp::TextEdit {
5342 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
5343 new_text: "\n ".into(),
5344 }]))
5345 }
5346 });
5347
5348 // Submit a format request.
5349 let format_1 = cx
5350 .update_editor(|editor, cx| editor.format(&Format, cx))
5351 .unwrap();
5352 cx.executor().run_until_parked();
5353
5354 // Submit a second format request.
5355 let format_2 = cx
5356 .update_editor(|editor, cx| editor.format(&Format, cx))
5357 .unwrap();
5358 cx.executor().run_until_parked();
5359
5360 // Wait for both format requests to complete
5361 cx.executor().advance_clock(Duration::from_millis(200));
5362 cx.executor().start_waiting();
5363 format_1.await.unwrap();
5364 cx.executor().start_waiting();
5365 format_2.await.unwrap();
5366
5367 // The formatting edits only happens once.
5368 cx.assert_editor_state(indoc! {"
5369 one
5370 .twoˇ
5371 "});
5372}
5373
5374#[gpui::test]
5375async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
5376 init_test(cx, |settings| {
5377 settings.defaults.formatter = Some(language_settings::Formatter::Auto)
5378 });
5379
5380 let mut cx = EditorLspTestContext::new_rust(
5381 lsp::ServerCapabilities {
5382 document_formatting_provider: Some(lsp::OneOf::Left(true)),
5383 ..Default::default()
5384 },
5385 cx,
5386 )
5387 .await;
5388
5389 // Set up a buffer white some trailing whitespace and no trailing newline.
5390 cx.set_state(
5391 &[
5392 "one ", //
5393 "twoˇ", //
5394 "three ", //
5395 "four", //
5396 ]
5397 .join("\n"),
5398 );
5399
5400 // Submit a format request.
5401 let format = cx
5402 .update_editor(|editor, cx| editor.format(&Format, cx))
5403 .unwrap();
5404
5405 // Record which buffer changes have been sent to the language server
5406 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
5407 cx.lsp
5408 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
5409 let buffer_changes = buffer_changes.clone();
5410 move |params, _| {
5411 buffer_changes.lock().extend(
5412 params
5413 .content_changes
5414 .into_iter()
5415 .map(|e| (e.range.unwrap(), e.text)),
5416 );
5417 }
5418 });
5419
5420 // Handle formatting requests to the language server.
5421 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
5422 let buffer_changes = buffer_changes.clone();
5423 move |_, _| {
5424 // When formatting is requested, trailing whitespace has already been stripped,
5425 // and the trailing newline has already been added.
5426 assert_eq!(
5427 &buffer_changes.lock()[1..],
5428 &[
5429 (
5430 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
5431 "".into()
5432 ),
5433 (
5434 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
5435 "".into()
5436 ),
5437 (
5438 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
5439 "\n".into()
5440 ),
5441 ]
5442 );
5443
5444 // Insert blank lines between each line of the buffer.
5445 async move {
5446 Ok(Some(vec![
5447 lsp::TextEdit {
5448 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
5449 new_text: "\n".into(),
5450 },
5451 lsp::TextEdit {
5452 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
5453 new_text: "\n".into(),
5454 },
5455 ]))
5456 }
5457 }
5458 });
5459
5460 // After formatting the buffer, the trailing whitespace is stripped,
5461 // a newline is appended, and the edits provided by the language server
5462 // have been applied.
5463 format.await.unwrap();
5464 cx.assert_editor_state(
5465 &[
5466 "one", //
5467 "", //
5468 "twoˇ", //
5469 "", //
5470 "three", //
5471 "four", //
5472 "", //
5473 ]
5474 .join("\n"),
5475 );
5476
5477 // Undoing the formatting undoes the trailing whitespace removal, the
5478 // trailing newline, and the LSP edits.
5479 cx.update_buffer(|buffer, cx| buffer.undo(cx));
5480 cx.assert_editor_state(
5481 &[
5482 "one ", //
5483 "twoˇ", //
5484 "three ", //
5485 "four", //
5486 ]
5487 .join("\n"),
5488 );
5489}
5490
5491#[gpui::test]
5492async fn test_completion(cx: &mut gpui::TestAppContext) {
5493 init_test(cx, |_| {});
5494
5495 let mut cx = EditorLspTestContext::new_rust(
5496 lsp::ServerCapabilities {
5497 completion_provider: Some(lsp::CompletionOptions {
5498 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
5499 resolve_provider: Some(true),
5500 ..Default::default()
5501 }),
5502 ..Default::default()
5503 },
5504 cx,
5505 )
5506 .await;
5507
5508 cx.set_state(indoc! {"
5509 oneˇ
5510 two
5511 three
5512 "});
5513 cx.simulate_keystroke(".");
5514 handle_completion_request(
5515 &mut cx,
5516 indoc! {"
5517 one.|<>
5518 two
5519 three
5520 "},
5521 vec!["first_completion", "second_completion"],
5522 )
5523 .await;
5524 cx.condition(|editor, _| editor.context_menu_visible())
5525 .await;
5526 let apply_additional_edits = cx.update_editor(|editor, cx| {
5527 editor.context_menu_next(&Default::default(), cx);
5528 editor
5529 .confirm_completion(&ConfirmCompletion::default(), cx)
5530 .unwrap()
5531 });
5532 cx.assert_editor_state(indoc! {"
5533 one.second_completionˇ
5534 two
5535 three
5536 "});
5537
5538 handle_resolve_completion_request(
5539 &mut cx,
5540 Some(vec![
5541 (
5542 //This overlaps with the primary completion edit which is
5543 //misbehavior from the LSP spec, test that we filter it out
5544 indoc! {"
5545 one.second_ˇcompletion
5546 two
5547 threeˇ
5548 "},
5549 "overlapping additional edit",
5550 ),
5551 (
5552 indoc! {"
5553 one.second_completion
5554 two
5555 threeˇ
5556 "},
5557 "\nadditional edit",
5558 ),
5559 ]),
5560 )
5561 .await;
5562 apply_additional_edits.await.unwrap();
5563 cx.assert_editor_state(indoc! {"
5564 one.second_completionˇ
5565 two
5566 three
5567 additional edit
5568 "});
5569
5570 cx.set_state(indoc! {"
5571 one.second_completion
5572 twoˇ
5573 threeˇ
5574 additional edit
5575 "});
5576 cx.simulate_keystroke(" ");
5577 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5578 cx.simulate_keystroke("s");
5579 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5580
5581 cx.assert_editor_state(indoc! {"
5582 one.second_completion
5583 two sˇ
5584 three sˇ
5585 additional edit
5586 "});
5587 handle_completion_request(
5588 &mut cx,
5589 indoc! {"
5590 one.second_completion
5591 two s
5592 three <s|>
5593 additional edit
5594 "},
5595 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
5596 )
5597 .await;
5598 cx.condition(|editor, _| editor.context_menu_visible())
5599 .await;
5600
5601 cx.simulate_keystroke("i");
5602
5603 handle_completion_request(
5604 &mut cx,
5605 indoc! {"
5606 one.second_completion
5607 two si
5608 three <si|>
5609 additional edit
5610 "},
5611 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
5612 )
5613 .await;
5614 cx.condition(|editor, _| editor.context_menu_visible())
5615 .await;
5616
5617 let apply_additional_edits = cx.update_editor(|editor, cx| {
5618 editor
5619 .confirm_completion(&ConfirmCompletion::default(), cx)
5620 .unwrap()
5621 });
5622 cx.assert_editor_state(indoc! {"
5623 one.second_completion
5624 two sixth_completionˇ
5625 three sixth_completionˇ
5626 additional edit
5627 "});
5628
5629 handle_resolve_completion_request(&mut cx, None).await;
5630 apply_additional_edits.await.unwrap();
5631
5632 cx.update(|cx| {
5633 cx.update_global::<SettingsStore, _>(|settings, cx| {
5634 settings.update_user_settings::<EditorSettings>(cx, |settings| {
5635 settings.show_completions_on_input = Some(false);
5636 });
5637 })
5638 });
5639 cx.set_state("editorˇ");
5640 cx.simulate_keystroke(".");
5641 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5642 cx.simulate_keystroke("c");
5643 cx.simulate_keystroke("l");
5644 cx.simulate_keystroke("o");
5645 cx.assert_editor_state("editor.cloˇ");
5646 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5647 cx.update_editor(|editor, cx| {
5648 editor.show_completions(&ShowCompletions, cx);
5649 });
5650 handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
5651 cx.condition(|editor, _| editor.context_menu_visible())
5652 .await;
5653 let apply_additional_edits = cx.update_editor(|editor, cx| {
5654 editor
5655 .confirm_completion(&ConfirmCompletion::default(), cx)
5656 .unwrap()
5657 });
5658 cx.assert_editor_state("editor.closeˇ");
5659 handle_resolve_completion_request(&mut cx, None).await;
5660 apply_additional_edits.await.unwrap();
5661}
5662
5663#[gpui::test]
5664async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
5665 init_test(cx, |_| {});
5666 let mut cx = EditorTestContext::new(cx).await;
5667 let language = Arc::new(Language::new(
5668 LanguageConfig {
5669 line_comment: Some("// ".into()),
5670 ..Default::default()
5671 },
5672 Some(tree_sitter_rust::language()),
5673 ));
5674 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
5675
5676 // If multiple selections intersect a line, the line is only toggled once.
5677 cx.set_state(indoc! {"
5678 fn a() {
5679 «//b();
5680 ˇ»// «c();
5681 //ˇ» d();
5682 }
5683 "});
5684
5685 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5686
5687 cx.assert_editor_state(indoc! {"
5688 fn a() {
5689 «b();
5690 c();
5691 ˇ» d();
5692 }
5693 "});
5694
5695 // The comment prefix is inserted at the same column for every line in a
5696 // selection.
5697 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5698
5699 cx.assert_editor_state(indoc! {"
5700 fn a() {
5701 // «b();
5702 // c();
5703 ˇ»// d();
5704 }
5705 "});
5706
5707 // If a selection ends at the beginning of a line, that line is not toggled.
5708 cx.set_selections_state(indoc! {"
5709 fn a() {
5710 // b();
5711 «// c();
5712 ˇ» // d();
5713 }
5714 "});
5715
5716 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5717
5718 cx.assert_editor_state(indoc! {"
5719 fn a() {
5720 // b();
5721 «c();
5722 ˇ» // d();
5723 }
5724 "});
5725
5726 // If a selection span a single line and is empty, the line is toggled.
5727 cx.set_state(indoc! {"
5728 fn a() {
5729 a();
5730 b();
5731 ˇ
5732 }
5733 "});
5734
5735 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5736
5737 cx.assert_editor_state(indoc! {"
5738 fn a() {
5739 a();
5740 b();
5741 //•ˇ
5742 }
5743 "});
5744
5745 // If a selection span multiple lines, empty lines are not toggled.
5746 cx.set_state(indoc! {"
5747 fn a() {
5748 «a();
5749
5750 c();ˇ»
5751 }
5752 "});
5753
5754 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
5755
5756 cx.assert_editor_state(indoc! {"
5757 fn a() {
5758 // «a();
5759
5760 // c();ˇ»
5761 }
5762 "});
5763}
5764
5765#[gpui::test]
5766async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
5767 init_test(cx, |_| {});
5768
5769 let language = Arc::new(Language::new(
5770 LanguageConfig {
5771 line_comment: Some("// ".into()),
5772 ..Default::default()
5773 },
5774 Some(tree_sitter_rust::language()),
5775 ));
5776
5777 let registry = Arc::new(LanguageRegistry::test());
5778 registry.add(language.clone());
5779
5780 let mut cx = EditorTestContext::new(cx).await;
5781 cx.update_buffer(|buffer, cx| {
5782 buffer.set_language_registry(registry);
5783 buffer.set_language(Some(language), cx);
5784 });
5785
5786 let toggle_comments = &ToggleComments {
5787 advance_downwards: true,
5788 };
5789
5790 // Single cursor on one line -> advance
5791 // Cursor moves horizontally 3 characters as well on non-blank line
5792 cx.set_state(indoc!(
5793 "fn a() {
5794 ˇdog();
5795 cat();
5796 }"
5797 ));
5798 cx.update_editor(|editor, cx| {
5799 editor.toggle_comments(toggle_comments, cx);
5800 });
5801 cx.assert_editor_state(indoc!(
5802 "fn a() {
5803 // dog();
5804 catˇ();
5805 }"
5806 ));
5807
5808 // Single selection on one line -> don't advance
5809 cx.set_state(indoc!(
5810 "fn a() {
5811 «dog()ˇ»;
5812 cat();
5813 }"
5814 ));
5815 cx.update_editor(|editor, cx| {
5816 editor.toggle_comments(toggle_comments, cx);
5817 });
5818 cx.assert_editor_state(indoc!(
5819 "fn a() {
5820 // «dog()ˇ»;
5821 cat();
5822 }"
5823 ));
5824
5825 // Multiple cursors on one line -> advance
5826 cx.set_state(indoc!(
5827 "fn a() {
5828 ˇdˇog();
5829 cat();
5830 }"
5831 ));
5832 cx.update_editor(|editor, cx| {
5833 editor.toggle_comments(toggle_comments, cx);
5834 });
5835 cx.assert_editor_state(indoc!(
5836 "fn a() {
5837 // dog();
5838 catˇ(ˇ);
5839 }"
5840 ));
5841
5842 // Multiple cursors on one line, with selection -> don't advance
5843 cx.set_state(indoc!(
5844 "fn a() {
5845 ˇdˇog«()ˇ»;
5846 cat();
5847 }"
5848 ));
5849 cx.update_editor(|editor, cx| {
5850 editor.toggle_comments(toggle_comments, cx);
5851 });
5852 cx.assert_editor_state(indoc!(
5853 "fn a() {
5854 // ˇdˇog«()ˇ»;
5855 cat();
5856 }"
5857 ));
5858
5859 // Single cursor on one line -> advance
5860 // Cursor moves to column 0 on blank line
5861 cx.set_state(indoc!(
5862 "fn a() {
5863 ˇdog();
5864
5865 cat();
5866 }"
5867 ));
5868 cx.update_editor(|editor, cx| {
5869 editor.toggle_comments(toggle_comments, cx);
5870 });
5871 cx.assert_editor_state(indoc!(
5872 "fn a() {
5873 // dog();
5874 ˇ
5875 cat();
5876 }"
5877 ));
5878
5879 // Single cursor on one line -> advance
5880 // Cursor starts and ends at column 0
5881 cx.set_state(indoc!(
5882 "fn a() {
5883 ˇ dog();
5884 cat();
5885 }"
5886 ));
5887 cx.update_editor(|editor, cx| {
5888 editor.toggle_comments(toggle_comments, cx);
5889 });
5890 cx.assert_editor_state(indoc!(
5891 "fn a() {
5892 // dog();
5893 ˇ cat();
5894 }"
5895 ));
5896}
5897
5898#[gpui::test]
5899async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
5900 init_test(cx, |_| {});
5901
5902 let mut cx = EditorTestContext::new(cx).await;
5903
5904 let html_language = Arc::new(
5905 Language::new(
5906 LanguageConfig {
5907 name: "HTML".into(),
5908 block_comment: Some(("<!-- ".into(), " -->".into())),
5909 ..Default::default()
5910 },
5911 Some(tree_sitter_html::language()),
5912 )
5913 .with_injection_query(
5914 r#"
5915 (script_element
5916 (raw_text) @content
5917 (#set! "language" "javascript"))
5918 "#,
5919 )
5920 .unwrap(),
5921 );
5922
5923 let javascript_language = Arc::new(Language::new(
5924 LanguageConfig {
5925 name: "JavaScript".into(),
5926 line_comment: Some("// ".into()),
5927 ..Default::default()
5928 },
5929 Some(tree_sitter_typescript::language_tsx()),
5930 ));
5931
5932 let registry = Arc::new(LanguageRegistry::test());
5933 registry.add(html_language.clone());
5934 registry.add(javascript_language.clone());
5935
5936 cx.update_buffer(|buffer, cx| {
5937 buffer.set_language_registry(registry);
5938 buffer.set_language(Some(html_language), cx);
5939 });
5940
5941 // Toggle comments for empty selections
5942 cx.set_state(
5943 &r#"
5944 <p>A</p>ˇ
5945 <p>B</p>ˇ
5946 <p>C</p>ˇ
5947 "#
5948 .unindent(),
5949 );
5950 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5951 cx.assert_editor_state(
5952 &r#"
5953 <!-- <p>A</p>ˇ -->
5954 <!-- <p>B</p>ˇ -->
5955 <!-- <p>C</p>ˇ -->
5956 "#
5957 .unindent(),
5958 );
5959 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5960 cx.assert_editor_state(
5961 &r#"
5962 <p>A</p>ˇ
5963 <p>B</p>ˇ
5964 <p>C</p>ˇ
5965 "#
5966 .unindent(),
5967 );
5968
5969 // Toggle comments for mixture of empty and non-empty selections, where
5970 // multiple selections occupy a given line.
5971 cx.set_state(
5972 &r#"
5973 <p>A«</p>
5974 <p>ˇ»B</p>ˇ
5975 <p>C«</p>
5976 <p>ˇ»D</p>ˇ
5977 "#
5978 .unindent(),
5979 );
5980
5981 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5982 cx.assert_editor_state(
5983 &r#"
5984 <!-- <p>A«</p>
5985 <p>ˇ»B</p>ˇ -->
5986 <!-- <p>C«</p>
5987 <p>ˇ»D</p>ˇ -->
5988 "#
5989 .unindent(),
5990 );
5991 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
5992 cx.assert_editor_state(
5993 &r#"
5994 <p>A«</p>
5995 <p>ˇ»B</p>ˇ
5996 <p>C«</p>
5997 <p>ˇ»D</p>ˇ
5998 "#
5999 .unindent(),
6000 );
6001
6002 // Toggle comments when different languages are active for different
6003 // selections.
6004 cx.set_state(
6005 &r#"
6006 ˇ<script>
6007 ˇvar x = new Y();
6008 ˇ</script>
6009 "#
6010 .unindent(),
6011 );
6012 cx.executor().run_until_parked();
6013 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
6014 cx.assert_editor_state(
6015 &r#"
6016 <!-- ˇ<script> -->
6017 // ˇvar x = new Y();
6018 <!-- ˇ</script> -->
6019 "#
6020 .unindent(),
6021 );
6022}
6023
6024#[gpui::test]
6025fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
6026 init_test(cx, |_| {});
6027
6028 let buffer =
6029 cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
6030 let multibuffer = cx.build_model(|cx| {
6031 let mut multibuffer = MultiBuffer::new(0);
6032 multibuffer.push_excerpts(
6033 buffer.clone(),
6034 [
6035 ExcerptRange {
6036 context: Point::new(0, 0)..Point::new(0, 4),
6037 primary: None,
6038 },
6039 ExcerptRange {
6040 context: Point::new(1, 0)..Point::new(1, 4),
6041 primary: None,
6042 },
6043 ],
6044 cx,
6045 );
6046 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
6047 multibuffer
6048 });
6049
6050 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
6051 view.update(cx, |view, cx| {
6052 assert_eq!(view.text(cx), "aaaa\nbbbb");
6053 view.change_selections(None, cx, |s| {
6054 s.select_ranges([
6055 Point::new(0, 0)..Point::new(0, 0),
6056 Point::new(1, 0)..Point::new(1, 0),
6057 ])
6058 });
6059
6060 view.handle_input("X", cx);
6061 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
6062 assert_eq!(
6063 view.selections.ranges(cx),
6064 [
6065 Point::new(0, 1)..Point::new(0, 1),
6066 Point::new(1, 1)..Point::new(1, 1),
6067 ]
6068 );
6069
6070 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
6071 view.change_selections(None, cx, |s| {
6072 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
6073 });
6074 view.backspace(&Default::default(), cx);
6075 assert_eq!(view.text(cx), "Xa\nbbb");
6076 assert_eq!(
6077 view.selections.ranges(cx),
6078 [Point::new(1, 0)..Point::new(1, 0)]
6079 );
6080
6081 view.change_selections(None, cx, |s| {
6082 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
6083 });
6084 view.backspace(&Default::default(), cx);
6085 assert_eq!(view.text(cx), "X\nbb");
6086 assert_eq!(
6087 view.selections.ranges(cx),
6088 [Point::new(0, 1)..Point::new(0, 1)]
6089 );
6090 });
6091}
6092
6093#[gpui::test]
6094fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
6095 init_test(cx, |_| {});
6096
6097 let markers = vec![('[', ']').into(), ('(', ')').into()];
6098 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
6099 indoc! {"
6100 [aaaa
6101 (bbbb]
6102 cccc)",
6103 },
6104 markers.clone(),
6105 );
6106 let excerpt_ranges = markers.into_iter().map(|marker| {
6107 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
6108 ExcerptRange {
6109 context,
6110 primary: None,
6111 }
6112 });
6113 let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), initial_text));
6114 let multibuffer = cx.build_model(|cx| {
6115 let mut multibuffer = MultiBuffer::new(0);
6116 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
6117 multibuffer
6118 });
6119
6120 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
6121 view.update(cx, |view, cx| {
6122 let (expected_text, selection_ranges) = marked_text_ranges(
6123 indoc! {"
6124 aaaa
6125 bˇbbb
6126 bˇbbˇb
6127 cccc"
6128 },
6129 true,
6130 );
6131 assert_eq!(view.text(cx), expected_text);
6132 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
6133
6134 view.handle_input("X", cx);
6135
6136 let (expected_text, expected_selections) = marked_text_ranges(
6137 indoc! {"
6138 aaaa
6139 bXˇbbXb
6140 bXˇbbXˇb
6141 cccc"
6142 },
6143 false,
6144 );
6145 assert_eq!(view.text(cx), expected_text);
6146 assert_eq!(view.selections.ranges(cx), expected_selections);
6147
6148 view.newline(&Newline, cx);
6149 let (expected_text, expected_selections) = marked_text_ranges(
6150 indoc! {"
6151 aaaa
6152 bX
6153 ˇbbX
6154 b
6155 bX
6156 ˇbbX
6157 ˇb
6158 cccc"
6159 },
6160 false,
6161 );
6162 assert_eq!(view.text(cx), expected_text);
6163 assert_eq!(view.selections.ranges(cx), expected_selections);
6164 });
6165}
6166
6167#[gpui::test]
6168fn test_refresh_selections(cx: &mut TestAppContext) {
6169 init_test(cx, |_| {});
6170
6171 let buffer =
6172 cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
6173 let mut excerpt1_id = None;
6174 let multibuffer = cx.build_model(|cx| {
6175 let mut multibuffer = MultiBuffer::new(0);
6176 excerpt1_id = multibuffer
6177 .push_excerpts(
6178 buffer.clone(),
6179 [
6180 ExcerptRange {
6181 context: Point::new(0, 0)..Point::new(1, 4),
6182 primary: None,
6183 },
6184 ExcerptRange {
6185 context: Point::new(1, 0)..Point::new(2, 4),
6186 primary: None,
6187 },
6188 ],
6189 cx,
6190 )
6191 .into_iter()
6192 .next();
6193 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
6194 multibuffer
6195 });
6196
6197 let editor = cx.add_window(|cx| {
6198 let mut editor = build_editor(multibuffer.clone(), cx);
6199 let snapshot = editor.snapshot(cx);
6200 editor.change_selections(None, cx, |s| {
6201 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
6202 });
6203 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
6204 assert_eq!(
6205 editor.selections.ranges(cx),
6206 [
6207 Point::new(1, 3)..Point::new(1, 3),
6208 Point::new(2, 1)..Point::new(2, 1),
6209 ]
6210 );
6211 editor
6212 });
6213
6214 // Refreshing selections is a no-op when excerpts haven't changed.
6215 editor.update(cx, |editor, cx| {
6216 editor.change_selections(None, cx, |s| s.refresh());
6217 assert_eq!(
6218 editor.selections.ranges(cx),
6219 [
6220 Point::new(1, 3)..Point::new(1, 3),
6221 Point::new(2, 1)..Point::new(2, 1),
6222 ]
6223 );
6224 });
6225
6226 multibuffer.update(cx, |multibuffer, cx| {
6227 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
6228 });
6229 editor.update(cx, |editor, cx| {
6230 // Removing an excerpt causes the first selection to become degenerate.
6231 assert_eq!(
6232 editor.selections.ranges(cx),
6233 [
6234 Point::new(0, 0)..Point::new(0, 0),
6235 Point::new(0, 1)..Point::new(0, 1)
6236 ]
6237 );
6238
6239 // Refreshing selections will relocate the first selection to the original buffer
6240 // location.
6241 editor.change_selections(None, cx, |s| s.refresh());
6242 assert_eq!(
6243 editor.selections.ranges(cx),
6244 [
6245 Point::new(0, 1)..Point::new(0, 1),
6246 Point::new(0, 3)..Point::new(0, 3)
6247 ]
6248 );
6249 assert!(editor.selections.pending_anchor().is_some());
6250 });
6251}
6252
6253#[gpui::test]
6254fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
6255 init_test(cx, |_| {});
6256
6257 let buffer =
6258 cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(3, 4, 'a')));
6259 let mut excerpt1_id = None;
6260 let multibuffer = cx.build_model(|cx| {
6261 let mut multibuffer = MultiBuffer::new(0);
6262 excerpt1_id = multibuffer
6263 .push_excerpts(
6264 buffer.clone(),
6265 [
6266 ExcerptRange {
6267 context: Point::new(0, 0)..Point::new(1, 4),
6268 primary: None,
6269 },
6270 ExcerptRange {
6271 context: Point::new(1, 0)..Point::new(2, 4),
6272 primary: None,
6273 },
6274 ],
6275 cx,
6276 )
6277 .into_iter()
6278 .next();
6279 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
6280 multibuffer
6281 });
6282
6283 let editor = cx.add_window(|cx| {
6284 let mut editor = build_editor(multibuffer.clone(), cx);
6285 let snapshot = editor.snapshot(cx);
6286 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
6287 assert_eq!(
6288 editor.selections.ranges(cx),
6289 [Point::new(1, 3)..Point::new(1, 3)]
6290 );
6291 editor
6292 });
6293
6294 multibuffer.update(cx, |multibuffer, cx| {
6295 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
6296 });
6297 editor.update(cx, |editor, cx| {
6298 assert_eq!(
6299 editor.selections.ranges(cx),
6300 [Point::new(0, 0)..Point::new(0, 0)]
6301 );
6302
6303 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
6304 editor.change_selections(None, cx, |s| s.refresh());
6305 assert_eq!(
6306 editor.selections.ranges(cx),
6307 [Point::new(0, 3)..Point::new(0, 3)]
6308 );
6309 assert!(editor.selections.pending_anchor().is_some());
6310 });
6311}
6312
6313#[gpui::test]
6314async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
6315 init_test(cx, |_| {});
6316
6317 let language = Arc::new(
6318 Language::new(
6319 LanguageConfig {
6320 brackets: BracketPairConfig {
6321 pairs: vec![
6322 BracketPair {
6323 start: "{".to_string(),
6324 end: "}".to_string(),
6325 close: true,
6326 newline: true,
6327 },
6328 BracketPair {
6329 start: "/* ".to_string(),
6330 end: " */".to_string(),
6331 close: true,
6332 newline: true,
6333 },
6334 ],
6335 ..Default::default()
6336 },
6337 ..Default::default()
6338 },
6339 Some(tree_sitter_rust::language()),
6340 )
6341 .with_indents_query("")
6342 .unwrap(),
6343 );
6344
6345 let text = concat!(
6346 "{ }\n", //
6347 " x\n", //
6348 " /* */\n", //
6349 "x\n", //
6350 "{{} }\n", //
6351 );
6352
6353 let buffer = cx.build_model(|cx| {
6354 Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
6355 });
6356 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
6357 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6358 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6359 .await;
6360
6361 view.update(cx, |view, cx| {
6362 view.change_selections(None, cx, |s| {
6363 s.select_display_ranges([
6364 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
6365 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
6366 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
6367 ])
6368 });
6369 view.newline(&Newline, cx);
6370
6371 assert_eq!(
6372 view.buffer().read(cx).read(cx).text(),
6373 concat!(
6374 "{ \n", // Suppress rustfmt
6375 "\n", //
6376 "}\n", //
6377 " x\n", //
6378 " /* \n", //
6379 " \n", //
6380 " */\n", //
6381 "x\n", //
6382 "{{} \n", //
6383 "}\n", //
6384 )
6385 );
6386 });
6387}
6388
6389#[gpui::test]
6390fn test_highlighted_ranges(cx: &mut TestAppContext) {
6391 init_test(cx, |_| {});
6392
6393 let editor = cx.add_window(|cx| {
6394 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
6395 build_editor(buffer.clone(), cx)
6396 });
6397
6398 editor.update(cx, |editor, cx| {
6399 struct Type1;
6400 struct Type2;
6401
6402 let buffer = editor.buffer.read(cx).snapshot(cx);
6403
6404 let anchor_range =
6405 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
6406
6407 editor.highlight_background::<Type1>(
6408 vec![
6409 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
6410 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
6411 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
6412 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
6413 ],
6414 |_| Hsla::red(),
6415 cx,
6416 );
6417 editor.highlight_background::<Type2>(
6418 vec![
6419 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
6420 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
6421 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
6422 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
6423 ],
6424 |_| Hsla::green(),
6425 cx,
6426 );
6427
6428 let snapshot = editor.snapshot(cx);
6429 let mut highlighted_ranges = editor.background_highlights_in_range(
6430 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
6431 &snapshot,
6432 cx.theme().colors(),
6433 );
6434 // Enforce a consistent ordering based on color without relying on the ordering of the
6435 // highlight's `TypeId` which is non-executor.
6436 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
6437 assert_eq!(
6438 highlighted_ranges,
6439 &[
6440 (
6441 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
6442 Hsla::red(),
6443 ),
6444 (
6445 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
6446 Hsla::red(),
6447 ),
6448 (
6449 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
6450 Hsla::green(),
6451 ),
6452 (
6453 DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
6454 Hsla::green(),
6455 ),
6456 ]
6457 );
6458 assert_eq!(
6459 editor.background_highlights_in_range(
6460 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
6461 &snapshot,
6462 cx.theme().colors(),
6463 ),
6464 &[(
6465 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
6466 Hsla::red(),
6467 )]
6468 );
6469 });
6470}
6471
6472// todo!(following)
6473#[gpui::test]
6474async fn test_following(cx: &mut gpui::TestAppContext) {
6475 init_test(cx, |_| {});
6476
6477 let fs = FakeFs::new(cx.executor());
6478 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6479
6480 let buffer = project.update(cx, |project, cx| {
6481 let buffer = project
6482 .create_buffer(&sample_text(16, 8, 'a'), None, cx)
6483 .unwrap();
6484 cx.build_model(|cx| MultiBuffer::singleton(buffer, cx))
6485 });
6486 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
6487 let follower = cx.update(|cx| {
6488 cx.open_window(
6489 WindowOptions {
6490 bounds: WindowBounds::Fixed(Bounds::from_corners(
6491 gpui::Point::new((0. as f64).into(), (0. as f64).into()),
6492 gpui::Point::new((10. as f64).into(), (80. as f64).into()),
6493 )),
6494 ..Default::default()
6495 },
6496 |cx| cx.build_view(|cx| build_editor(buffer.clone(), cx)),
6497 )
6498 });
6499
6500 let is_still_following = Rc::new(RefCell::new(true));
6501 let follower_edit_event_count = Rc::new(RefCell::new(0));
6502 let pending_update = Rc::new(RefCell::new(None));
6503 follower.update(cx, {
6504 let update = pending_update.clone();
6505 let is_still_following = is_still_following.clone();
6506 let follower_edit_event_count = follower_edit_event_count.clone();
6507 |_, cx| {
6508 cx.subscribe(
6509 &leader.root_view(cx).unwrap(),
6510 move |_, leader, event, cx| {
6511 leader
6512 .read(cx)
6513 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
6514 },
6515 )
6516 .detach();
6517
6518 cx.subscribe(
6519 &follower.root_view(cx).unwrap(),
6520 move |_, _, event: &EditorEvent, cx| {
6521 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
6522 *is_still_following.borrow_mut() = false;
6523 }
6524
6525 if let EditorEvent::BufferEdited = event {
6526 *follower_edit_event_count.borrow_mut() += 1;
6527 }
6528 },
6529 )
6530 .detach();
6531 }
6532 });
6533
6534 // Update the selections only
6535 leader.update(cx, |leader, cx| {
6536 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
6537 });
6538 follower
6539 .update(cx, |follower, cx| {
6540 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6541 })
6542 .unwrap()
6543 .await
6544 .unwrap();
6545 follower.update(cx, |follower, cx| {
6546 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
6547 });
6548 assert_eq!(*is_still_following.borrow(), true);
6549 assert_eq!(*follower_edit_event_count.borrow(), 0);
6550
6551 // Update the scroll position only
6552 leader.update(cx, |leader, cx| {
6553 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
6554 });
6555 follower
6556 .update(cx, |follower, cx| {
6557 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6558 })
6559 .unwrap()
6560 .await
6561 .unwrap();
6562 assert_eq!(
6563 follower
6564 .update(cx, |follower, cx| follower.scroll_position(cx))
6565 .unwrap(),
6566 gpui::Point::new(1.5, 3.5)
6567 );
6568 assert_eq!(*is_still_following.borrow(), true);
6569 assert_eq!(*follower_edit_event_count.borrow(), 0);
6570
6571 // Update the selections and scroll position. The follower's scroll position is updated
6572 // via autoscroll, not via the leader's exact scroll position.
6573 leader.update(cx, |leader, cx| {
6574 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
6575 leader.request_autoscroll(Autoscroll::newest(), cx);
6576 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
6577 });
6578 follower
6579 .update(cx, |follower, cx| {
6580 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6581 })
6582 .unwrap()
6583 .await
6584 .unwrap();
6585 follower.update(cx, |follower, cx| {
6586 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
6587 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
6588 });
6589 assert_eq!(*is_still_following.borrow(), true);
6590
6591 // Creating a pending selection that precedes another selection
6592 leader.update(cx, |leader, cx| {
6593 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
6594 leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
6595 });
6596 follower
6597 .update(cx, |follower, cx| {
6598 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6599 })
6600 .unwrap()
6601 .await
6602 .unwrap();
6603 follower.update(cx, |follower, cx| {
6604 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
6605 });
6606 assert_eq!(*is_still_following.borrow(), true);
6607
6608 // Extend the pending selection so that it surrounds another selection
6609 leader.update(cx, |leader, cx| {
6610 leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
6611 });
6612 follower
6613 .update(cx, |follower, cx| {
6614 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6615 })
6616 .unwrap()
6617 .await
6618 .unwrap();
6619 follower.update(cx, |follower, cx| {
6620 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
6621 });
6622
6623 // Scrolling locally breaks the follow
6624 follower.update(cx, |follower, cx| {
6625 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
6626 follower.set_scroll_anchor(
6627 ScrollAnchor {
6628 anchor: top_anchor,
6629 offset: gpui::Point::new(0.0, 0.5),
6630 },
6631 cx,
6632 );
6633 });
6634 assert_eq!(*is_still_following.borrow(), false);
6635}
6636
6637#[gpui::test]
6638async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
6639 init_test(cx, |_| {});
6640
6641 let fs = FakeFs::new(cx.executor());
6642 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6643 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6644 let pane = workspace
6645 .update(cx, |workspace, _| workspace.active_pane().clone())
6646 .unwrap();
6647
6648 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6649
6650 let leader = pane.update(cx, |_, cx| {
6651 let multibuffer = cx.build_model(|_| MultiBuffer::new(0));
6652 cx.build_view(|cx| build_editor(multibuffer.clone(), cx))
6653 });
6654
6655 // Start following the editor when it has no excerpts.
6656 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
6657 let follower_1 = cx
6658 .update_window(*workspace.deref(), |_, cx| {
6659 Editor::from_state_proto(
6660 pane.clone(),
6661 workspace.root_view(cx).unwrap(),
6662 ViewId {
6663 creator: Default::default(),
6664 id: 0,
6665 },
6666 &mut state_message,
6667 cx,
6668 )
6669 })
6670 .unwrap()
6671 .unwrap()
6672 .await
6673 .unwrap();
6674
6675 let update_message = Rc::new(RefCell::new(None));
6676 follower_1.update(cx, {
6677 let update = update_message.clone();
6678 |_, cx| {
6679 cx.subscribe(&leader, move |_, leader, event, cx| {
6680 leader
6681 .read(cx)
6682 .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx);
6683 })
6684 .detach();
6685 }
6686 });
6687
6688 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
6689 (
6690 project
6691 .create_buffer("abc\ndef\nghi\njkl\n", None, cx)
6692 .unwrap(),
6693 project
6694 .create_buffer("mno\npqr\nstu\nvwx\n", None, cx)
6695 .unwrap(),
6696 )
6697 });
6698
6699 // Insert some excerpts.
6700 leader.update(cx, |leader, cx| {
6701 leader.buffer.update(cx, |multibuffer, cx| {
6702 let excerpt_ids = multibuffer.push_excerpts(
6703 buffer_1.clone(),
6704 [
6705 ExcerptRange {
6706 context: 1..6,
6707 primary: None,
6708 },
6709 ExcerptRange {
6710 context: 12..15,
6711 primary: None,
6712 },
6713 ExcerptRange {
6714 context: 0..3,
6715 primary: None,
6716 },
6717 ],
6718 cx,
6719 );
6720 multibuffer.insert_excerpts_after(
6721 excerpt_ids[0],
6722 buffer_2.clone(),
6723 [
6724 ExcerptRange {
6725 context: 8..12,
6726 primary: None,
6727 },
6728 ExcerptRange {
6729 context: 0..6,
6730 primary: None,
6731 },
6732 ],
6733 cx,
6734 );
6735 });
6736 });
6737
6738 // Apply the update of adding the excerpts.
6739 follower_1
6740 .update(cx, |follower, cx| {
6741 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6742 })
6743 .await
6744 .unwrap();
6745 assert_eq!(
6746 follower_1.update(cx, |editor, cx| editor.text(cx)),
6747 leader.update(cx, |editor, cx| editor.text(cx))
6748 );
6749 update_message.borrow_mut().take();
6750
6751 // Start following separately after it already has excerpts.
6752 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
6753 let follower_2 = cx
6754 .update_window(*workspace.deref(), |_, cx| {
6755 Editor::from_state_proto(
6756 pane.clone(),
6757 workspace.root_view(cx).unwrap().clone(),
6758 ViewId {
6759 creator: Default::default(),
6760 id: 0,
6761 },
6762 &mut state_message,
6763 cx,
6764 )
6765 })
6766 .unwrap()
6767 .unwrap()
6768 .await
6769 .unwrap();
6770 assert_eq!(
6771 follower_2.update(cx, |editor, cx| editor.text(cx)),
6772 leader.update(cx, |editor, cx| editor.text(cx))
6773 );
6774
6775 // Remove some excerpts.
6776 leader.update(cx, |leader, cx| {
6777 leader.buffer.update(cx, |multibuffer, cx| {
6778 let excerpt_ids = multibuffer.excerpt_ids();
6779 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
6780 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
6781 });
6782 });
6783
6784 // Apply the update of removing the excerpts.
6785 follower_1
6786 .update(cx, |follower, cx| {
6787 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6788 })
6789 .await
6790 .unwrap();
6791 follower_2
6792 .update(cx, |follower, cx| {
6793 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
6794 })
6795 .await
6796 .unwrap();
6797 update_message.borrow_mut().take();
6798 assert_eq!(
6799 follower_1.update(cx, |editor, cx| editor.text(cx)),
6800 leader.update(cx, |editor, cx| editor.text(cx))
6801 );
6802}
6803
6804#[gpui::test]
6805async fn go_to_prev_overlapping_diagnostic(
6806 executor: BackgroundExecutor,
6807 cx: &mut gpui::TestAppContext,
6808) {
6809 init_test(cx, |_| {});
6810
6811 let mut cx = EditorTestContext::new(cx).await;
6812 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
6813
6814 cx.set_state(indoc! {"
6815 ˇfn func(abc def: i32) -> u32 {
6816 }
6817 "});
6818
6819 cx.update(|cx| {
6820 project.update(cx, |project, cx| {
6821 project
6822 .update_diagnostics(
6823 LanguageServerId(0),
6824 lsp::PublishDiagnosticsParams {
6825 uri: lsp::Url::from_file_path("/root/file").unwrap(),
6826 version: None,
6827 diagnostics: vec![
6828 lsp::Diagnostic {
6829 range: lsp::Range::new(
6830 lsp::Position::new(0, 11),
6831 lsp::Position::new(0, 12),
6832 ),
6833 severity: Some(lsp::DiagnosticSeverity::ERROR),
6834 ..Default::default()
6835 },
6836 lsp::Diagnostic {
6837 range: lsp::Range::new(
6838 lsp::Position::new(0, 12),
6839 lsp::Position::new(0, 15),
6840 ),
6841 severity: Some(lsp::DiagnosticSeverity::ERROR),
6842 ..Default::default()
6843 },
6844 lsp::Diagnostic {
6845 range: lsp::Range::new(
6846 lsp::Position::new(0, 25),
6847 lsp::Position::new(0, 28),
6848 ),
6849 severity: Some(lsp::DiagnosticSeverity::ERROR),
6850 ..Default::default()
6851 },
6852 ],
6853 },
6854 &[],
6855 cx,
6856 )
6857 .unwrap()
6858 });
6859 });
6860
6861 executor.run_until_parked();
6862
6863 cx.update_editor(|editor, cx| {
6864 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
6865 });
6866
6867 cx.assert_editor_state(indoc! {"
6868 fn func(abc def: i32) -> ˇu32 {
6869 }
6870 "});
6871
6872 cx.update_editor(|editor, cx| {
6873 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
6874 });
6875
6876 cx.assert_editor_state(indoc! {"
6877 fn func(abc ˇdef: i32) -> u32 {
6878 }
6879 "});
6880
6881 cx.update_editor(|editor, cx| {
6882 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
6883 });
6884
6885 cx.assert_editor_state(indoc! {"
6886 fn func(abcˇ def: i32) -> u32 {
6887 }
6888 "});
6889
6890 cx.update_editor(|editor, cx| {
6891 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
6892 });
6893
6894 cx.assert_editor_state(indoc! {"
6895 fn func(abc def: i32) -> ˇu32 {
6896 }
6897 "});
6898}
6899
6900#[gpui::test]
6901async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
6902 init_test(cx, |_| {});
6903
6904 let mut cx = EditorTestContext::new(cx).await;
6905
6906 let diff_base = r#"
6907 use some::mod;
6908
6909 const A: u32 = 42;
6910
6911 fn main() {
6912 println!("hello");
6913
6914 println!("world");
6915 }
6916 "#
6917 .unindent();
6918
6919 // Edits are modified, removed, modified, added
6920 cx.set_state(
6921 &r#"
6922 use some::modified;
6923
6924 ˇ
6925 fn main() {
6926 println!("hello there");
6927
6928 println!("around the");
6929 println!("world");
6930 }
6931 "#
6932 .unindent(),
6933 );
6934
6935 cx.set_diff_base(Some(&diff_base));
6936 executor.run_until_parked();
6937
6938 cx.update_editor(|editor, cx| {
6939 //Wrap around the bottom of the buffer
6940 for _ in 0..3 {
6941 editor.go_to_hunk(&GoToHunk, cx);
6942 }
6943 });
6944
6945 cx.assert_editor_state(
6946 &r#"
6947 ˇuse some::modified;
6948
6949
6950 fn main() {
6951 println!("hello there");
6952
6953 println!("around the");
6954 println!("world");
6955 }
6956 "#
6957 .unindent(),
6958 );
6959
6960 cx.update_editor(|editor, cx| {
6961 //Wrap around the top of the buffer
6962 for _ in 0..2 {
6963 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
6964 }
6965 });
6966
6967 cx.assert_editor_state(
6968 &r#"
6969 use some::modified;
6970
6971
6972 fn main() {
6973 ˇ println!("hello there");
6974
6975 println!("around the");
6976 println!("world");
6977 }
6978 "#
6979 .unindent(),
6980 );
6981
6982 cx.update_editor(|editor, cx| {
6983 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
6984 });
6985
6986 cx.assert_editor_state(
6987 &r#"
6988 use some::modified;
6989
6990 ˇ
6991 fn main() {
6992 println!("hello there");
6993
6994 println!("around the");
6995 println!("world");
6996 }
6997 "#
6998 .unindent(),
6999 );
7000
7001 cx.update_editor(|editor, cx| {
7002 for _ in 0..3 {
7003 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
7004 }
7005 });
7006
7007 cx.assert_editor_state(
7008 &r#"
7009 use some::modified;
7010
7011
7012 fn main() {
7013 ˇ println!("hello there");
7014
7015 println!("around the");
7016 println!("world");
7017 }
7018 "#
7019 .unindent(),
7020 );
7021
7022 cx.update_editor(|editor, cx| {
7023 editor.fold(&Fold, cx);
7024
7025 //Make sure that the fold only gets one hunk
7026 for _ in 0..4 {
7027 editor.go_to_hunk(&GoToHunk, cx);
7028 }
7029 });
7030
7031 cx.assert_editor_state(
7032 &r#"
7033 ˇuse some::modified;
7034
7035
7036 fn main() {
7037 println!("hello there");
7038
7039 println!("around the");
7040 println!("world");
7041 }
7042 "#
7043 .unindent(),
7044 );
7045}
7046
7047#[test]
7048fn test_split_words() {
7049 fn split<'a>(text: &'a str) -> Vec<&'a str> {
7050 split_words(text).collect()
7051 }
7052
7053 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
7054 assert_eq!(split("hello_world"), &["hello_", "world"]);
7055 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
7056 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
7057 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
7058 assert_eq!(split("helloworld"), &["helloworld"]);
7059}
7060
7061#[gpui::test]
7062async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
7063 init_test(cx, |_| {});
7064
7065 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
7066 let mut assert = |before, after| {
7067 let _state_context = cx.set_state(before);
7068 cx.update_editor(|editor, cx| {
7069 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
7070 });
7071 cx.assert_editor_state(after);
7072 };
7073
7074 // Outside bracket jumps to outside of matching bracket
7075 assert("console.logˇ(var);", "console.log(var)ˇ;");
7076 assert("console.log(var)ˇ;", "console.logˇ(var);");
7077
7078 // Inside bracket jumps to inside of matching bracket
7079 assert("console.log(ˇvar);", "console.log(varˇ);");
7080 assert("console.log(varˇ);", "console.log(ˇvar);");
7081
7082 // When outside a bracket and inside, favor jumping to the inside bracket
7083 assert(
7084 "console.log('foo', [1, 2, 3]ˇ);",
7085 "console.log(ˇ'foo', [1, 2, 3]);",
7086 );
7087 assert(
7088 "console.log(ˇ'foo', [1, 2, 3]);",
7089 "console.log('foo', [1, 2, 3]ˇ);",
7090 );
7091
7092 // Bias forward if two options are equally likely
7093 assert(
7094 "let result = curried_fun()ˇ();",
7095 "let result = curried_fun()()ˇ;",
7096 );
7097
7098 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
7099 assert(
7100 indoc! {"
7101 function test() {
7102 console.log('test')ˇ
7103 }"},
7104 indoc! {"
7105 function test() {
7106 console.logˇ('test')
7107 }"},
7108 );
7109}
7110
7111// todo!(completions)
7112#[gpui::test(iterations = 10)]
7113async fn test_copilot(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
7114 // flaky
7115 init_test(cx, |_| {});
7116
7117 let (copilot, copilot_lsp) = Copilot::fake(cx);
7118 cx.update(|cx| cx.set_global(copilot));
7119 let mut cx = EditorLspTestContext::new_rust(
7120 lsp::ServerCapabilities {
7121 completion_provider: Some(lsp::CompletionOptions {
7122 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7123 ..Default::default()
7124 }),
7125 ..Default::default()
7126 },
7127 cx,
7128 )
7129 .await;
7130
7131 // When inserting, ensure autocompletion is favored over Copilot suggestions.
7132 cx.set_state(indoc! {"
7133 oneˇ
7134 two
7135 three
7136 "});
7137 cx.simulate_keystroke(".");
7138 let _ = handle_completion_request(
7139 &mut cx,
7140 indoc! {"
7141 one.|<>
7142 two
7143 three
7144 "},
7145 vec!["completion_a", "completion_b"],
7146 );
7147 handle_copilot_completion_request(
7148 &copilot_lsp,
7149 vec![copilot::request::Completion {
7150 text: "one.copilot1".into(),
7151 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
7152 ..Default::default()
7153 }],
7154 vec![],
7155 );
7156 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7157 cx.update_editor(|editor, cx| {
7158 assert!(editor.context_menu_visible());
7159 assert!(!editor.has_active_copilot_suggestion(cx));
7160
7161 // Confirming a completion inserts it and hides the context menu, without showing
7162 // the copilot suggestion afterwards.
7163 editor
7164 .confirm_completion(&Default::default(), cx)
7165 .unwrap()
7166 .detach();
7167 assert!(!editor.context_menu_visible());
7168 assert!(!editor.has_active_copilot_suggestion(cx));
7169 assert_eq!(editor.text(cx), "one.completion_a\ntwo\nthree\n");
7170 assert_eq!(editor.display_text(cx), "one.completion_a\ntwo\nthree\n");
7171 });
7172
7173 // Ensure Copilot suggestions are shown right away if no autocompletion is available.
7174 cx.set_state(indoc! {"
7175 oneˇ
7176 two
7177 three
7178 "});
7179 cx.simulate_keystroke(".");
7180 let _ = handle_completion_request(
7181 &mut cx,
7182 indoc! {"
7183 one.|<>
7184 two
7185 three
7186 "},
7187 vec![],
7188 );
7189 handle_copilot_completion_request(
7190 &copilot_lsp,
7191 vec![copilot::request::Completion {
7192 text: "one.copilot1".into(),
7193 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
7194 ..Default::default()
7195 }],
7196 vec![],
7197 );
7198 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7199 cx.update_editor(|editor, cx| {
7200 assert!(!editor.context_menu_visible());
7201 assert!(editor.has_active_copilot_suggestion(cx));
7202 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
7203 assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
7204 });
7205
7206 // Reset editor, and ensure autocompletion is still favored over Copilot suggestions.
7207 cx.set_state(indoc! {"
7208 oneˇ
7209 two
7210 three
7211 "});
7212 cx.simulate_keystroke(".");
7213 let _ = handle_completion_request(
7214 &mut cx,
7215 indoc! {"
7216 one.|<>
7217 two
7218 three
7219 "},
7220 vec!["completion_a", "completion_b"],
7221 );
7222 handle_copilot_completion_request(
7223 &copilot_lsp,
7224 vec![copilot::request::Completion {
7225 text: "one.copilot1".into(),
7226 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
7227 ..Default::default()
7228 }],
7229 vec![],
7230 );
7231 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7232 cx.update_editor(|editor, cx| {
7233 assert!(editor.context_menu_visible());
7234 assert!(!editor.has_active_copilot_suggestion(cx));
7235
7236 // When hiding the context menu, the Copilot suggestion becomes visible.
7237 editor.hide_context_menu(cx);
7238 assert!(!editor.context_menu_visible());
7239 assert!(editor.has_active_copilot_suggestion(cx));
7240 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
7241 assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
7242 });
7243
7244 // Ensure existing completion is interpolated when inserting again.
7245 cx.simulate_keystroke("c");
7246 executor.run_until_parked();
7247 cx.update_editor(|editor, cx| {
7248 assert!(!editor.context_menu_visible());
7249 assert!(editor.has_active_copilot_suggestion(cx));
7250 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
7251 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7252 });
7253
7254 // After debouncing, new Copilot completions should be requested.
7255 handle_copilot_completion_request(
7256 &copilot_lsp,
7257 vec![copilot::request::Completion {
7258 text: "one.copilot2".into(),
7259 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 5)),
7260 ..Default::default()
7261 }],
7262 vec![],
7263 );
7264 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7265 cx.update_editor(|editor, cx| {
7266 assert!(!editor.context_menu_visible());
7267 assert!(editor.has_active_copilot_suggestion(cx));
7268 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7269 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7270
7271 // Canceling should remove the active Copilot suggestion.
7272 editor.cancel(&Default::default(), cx);
7273 assert!(!editor.has_active_copilot_suggestion(cx));
7274 assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
7275 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7276
7277 // After canceling, tabbing shouldn't insert the previously shown suggestion.
7278 editor.tab(&Default::default(), cx);
7279 assert!(!editor.has_active_copilot_suggestion(cx));
7280 assert_eq!(editor.display_text(cx), "one.c \ntwo\nthree\n");
7281 assert_eq!(editor.text(cx), "one.c \ntwo\nthree\n");
7282
7283 // When undoing the previously active suggestion is shown again.
7284 editor.undo(&Default::default(), cx);
7285 assert!(editor.has_active_copilot_suggestion(cx));
7286 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7287 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7288 });
7289
7290 // If an edit occurs outside of this editor, the suggestion is still correctly interpolated.
7291 cx.update_buffer(|buffer, cx| buffer.edit([(5..5, "o")], None, cx));
7292 cx.update_editor(|editor, cx| {
7293 assert!(editor.has_active_copilot_suggestion(cx));
7294 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7295 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
7296
7297 // Tabbing when there is an active suggestion inserts it.
7298 editor.tab(&Default::default(), cx);
7299 assert!(!editor.has_active_copilot_suggestion(cx));
7300 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7301 assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
7302
7303 // When undoing the previously active suggestion is shown again.
7304 editor.undo(&Default::default(), cx);
7305 assert!(editor.has_active_copilot_suggestion(cx));
7306 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7307 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
7308
7309 // Hide suggestion.
7310 editor.cancel(&Default::default(), cx);
7311 assert!(!editor.has_active_copilot_suggestion(cx));
7312 assert_eq!(editor.display_text(cx), "one.co\ntwo\nthree\n");
7313 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
7314 });
7315
7316 // If an edit occurs outside of this editor but no suggestion is being shown,
7317 // we won't make it visible.
7318 cx.update_buffer(|buffer, cx| buffer.edit([(6..6, "p")], None, cx));
7319 cx.update_editor(|editor, cx| {
7320 assert!(!editor.has_active_copilot_suggestion(cx));
7321 assert_eq!(editor.display_text(cx), "one.cop\ntwo\nthree\n");
7322 assert_eq!(editor.text(cx), "one.cop\ntwo\nthree\n");
7323 });
7324
7325 // Reset the editor to verify how suggestions behave when tabbing on leading indentation.
7326 cx.update_editor(|editor, cx| {
7327 editor.set_text("fn foo() {\n \n}", cx);
7328 editor.change_selections(None, cx, |s| {
7329 s.select_ranges([Point::new(1, 2)..Point::new(1, 2)])
7330 });
7331 });
7332 handle_copilot_completion_request(
7333 &copilot_lsp,
7334 vec![copilot::request::Completion {
7335 text: " let x = 4;".into(),
7336 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
7337 ..Default::default()
7338 }],
7339 vec![],
7340 );
7341
7342 cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
7343 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7344 cx.update_editor(|editor, cx| {
7345 assert!(editor.has_active_copilot_suggestion(cx));
7346 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
7347 assert_eq!(editor.text(cx), "fn foo() {\n \n}");
7348
7349 // Tabbing inside of leading whitespace inserts indentation without accepting the suggestion.
7350 editor.tab(&Default::default(), cx);
7351 assert!(editor.has_active_copilot_suggestion(cx));
7352 assert_eq!(editor.text(cx), "fn foo() {\n \n}");
7353 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
7354
7355 // Tabbing again accepts the suggestion.
7356 editor.tab(&Default::default(), cx);
7357 assert!(!editor.has_active_copilot_suggestion(cx));
7358 assert_eq!(editor.text(cx), "fn foo() {\n let x = 4;\n}");
7359 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
7360 });
7361}
7362
7363#[gpui::test]
7364async fn test_copilot_completion_invalidation(
7365 executor: BackgroundExecutor,
7366 cx: &mut gpui::TestAppContext,
7367) {
7368 init_test(cx, |_| {});
7369
7370 let (copilot, copilot_lsp) = Copilot::fake(cx);
7371 cx.update(|cx| cx.set_global(copilot));
7372 let mut cx = EditorLspTestContext::new_rust(
7373 lsp::ServerCapabilities {
7374 completion_provider: Some(lsp::CompletionOptions {
7375 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7376 ..Default::default()
7377 }),
7378 ..Default::default()
7379 },
7380 cx,
7381 )
7382 .await;
7383
7384 cx.set_state(indoc! {"
7385 one
7386 twˇ
7387 three
7388 "});
7389
7390 handle_copilot_completion_request(
7391 &copilot_lsp,
7392 vec![copilot::request::Completion {
7393 text: "two.foo()".into(),
7394 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
7395 ..Default::default()
7396 }],
7397 vec![],
7398 );
7399 cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
7400 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7401 cx.update_editor(|editor, cx| {
7402 assert!(editor.has_active_copilot_suggestion(cx));
7403 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7404 assert_eq!(editor.text(cx), "one\ntw\nthree\n");
7405
7406 editor.backspace(&Default::default(), cx);
7407 assert!(editor.has_active_copilot_suggestion(cx));
7408 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7409 assert_eq!(editor.text(cx), "one\nt\nthree\n");
7410
7411 editor.backspace(&Default::default(), cx);
7412 assert!(editor.has_active_copilot_suggestion(cx));
7413 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7414 assert_eq!(editor.text(cx), "one\n\nthree\n");
7415
7416 // Deleting across the original suggestion range invalidates it.
7417 editor.backspace(&Default::default(), cx);
7418 assert!(!editor.has_active_copilot_suggestion(cx));
7419 assert_eq!(editor.display_text(cx), "one\nthree\n");
7420 assert_eq!(editor.text(cx), "one\nthree\n");
7421
7422 // Undoing the deletion restores the suggestion.
7423 editor.undo(&Default::default(), cx);
7424 assert!(editor.has_active_copilot_suggestion(cx));
7425 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7426 assert_eq!(editor.text(cx), "one\n\nthree\n");
7427 });
7428}
7429
7430#[gpui::test]
7431async fn test_copilot_multibuffer(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
7432 init_test(cx, |_| {});
7433
7434 let (copilot, copilot_lsp) = Copilot::fake(cx);
7435 cx.update(|cx| cx.set_global(copilot));
7436
7437 let buffer_1 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "a = 1\nb = 2\n"));
7438 let buffer_2 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "c = 3\nd = 4\n"));
7439 let multibuffer = cx.build_model(|cx| {
7440 let mut multibuffer = MultiBuffer::new(0);
7441 multibuffer.push_excerpts(
7442 buffer_1.clone(),
7443 [ExcerptRange {
7444 context: Point::new(0, 0)..Point::new(2, 0),
7445 primary: None,
7446 }],
7447 cx,
7448 );
7449 multibuffer.push_excerpts(
7450 buffer_2.clone(),
7451 [ExcerptRange {
7452 context: Point::new(0, 0)..Point::new(2, 0),
7453 primary: None,
7454 }],
7455 cx,
7456 );
7457 multibuffer
7458 });
7459 let editor = cx.add_window(|cx| build_editor(multibuffer, cx));
7460
7461 handle_copilot_completion_request(
7462 &copilot_lsp,
7463 vec![copilot::request::Completion {
7464 text: "b = 2 + a".into(),
7465 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 5)),
7466 ..Default::default()
7467 }],
7468 vec![],
7469 );
7470 editor.update(cx, |editor, cx| {
7471 // Ensure copilot suggestions are shown for the first excerpt.
7472 editor.change_selections(None, cx, |s| {
7473 s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
7474 });
7475 editor.next_copilot_suggestion(&Default::default(), cx);
7476 });
7477 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7478 editor.update(cx, |editor, cx| {
7479 assert!(editor.has_active_copilot_suggestion(cx));
7480 assert_eq!(
7481 editor.display_text(cx),
7482 "\n\na = 1\nb = 2 + a\n\n\n\nc = 3\nd = 4\n"
7483 );
7484 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
7485 });
7486
7487 handle_copilot_completion_request(
7488 &copilot_lsp,
7489 vec![copilot::request::Completion {
7490 text: "d = 4 + c".into(),
7491 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 6)),
7492 ..Default::default()
7493 }],
7494 vec![],
7495 );
7496 editor.update(cx, |editor, cx| {
7497 // Move to another excerpt, ensuring the suggestion gets cleared.
7498 editor.change_selections(None, cx, |s| {
7499 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
7500 });
7501 assert!(!editor.has_active_copilot_suggestion(cx));
7502 assert_eq!(
7503 editor.display_text(cx),
7504 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4\n"
7505 );
7506 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
7507
7508 // Type a character, ensuring we don't even try to interpolate the previous suggestion.
7509 editor.handle_input(" ", cx);
7510 assert!(!editor.has_active_copilot_suggestion(cx));
7511 assert_eq!(
7512 editor.display_text(cx),
7513 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 \n"
7514 );
7515 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
7516 });
7517
7518 // Ensure the new suggestion is displayed when the debounce timeout expires.
7519 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7520 editor.update(cx, |editor, cx| {
7521 assert!(editor.has_active_copilot_suggestion(cx));
7522 assert_eq!(
7523 editor.display_text(cx),
7524 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 + c\n"
7525 );
7526 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
7527 });
7528}
7529
7530#[gpui::test]
7531async fn test_copilot_disabled_globs(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
7532 init_test(cx, |settings| {
7533 settings
7534 .copilot
7535 .get_or_insert(Default::default())
7536 .disabled_globs = Some(vec![".env*".to_string()]);
7537 });
7538
7539 let (copilot, copilot_lsp) = Copilot::fake(cx);
7540 cx.update(|cx| cx.set_global(copilot));
7541
7542 let fs = FakeFs::new(cx.executor());
7543 fs.insert_tree(
7544 "/test",
7545 json!({
7546 ".env": "SECRET=something\n",
7547 "README.md": "hello\n"
7548 }),
7549 )
7550 .await;
7551 let project = Project::test(fs, ["/test".as_ref()], cx).await;
7552
7553 let private_buffer = project
7554 .update(cx, |project, cx| {
7555 project.open_local_buffer("/test/.env", cx)
7556 })
7557 .await
7558 .unwrap();
7559 let public_buffer = project
7560 .update(cx, |project, cx| {
7561 project.open_local_buffer("/test/README.md", cx)
7562 })
7563 .await
7564 .unwrap();
7565
7566 let multibuffer = cx.build_model(|cx| {
7567 let mut multibuffer = MultiBuffer::new(0);
7568 multibuffer.push_excerpts(
7569 private_buffer.clone(),
7570 [ExcerptRange {
7571 context: Point::new(0, 0)..Point::new(1, 0),
7572 primary: None,
7573 }],
7574 cx,
7575 );
7576 multibuffer.push_excerpts(
7577 public_buffer.clone(),
7578 [ExcerptRange {
7579 context: Point::new(0, 0)..Point::new(1, 0),
7580 primary: None,
7581 }],
7582 cx,
7583 );
7584 multibuffer
7585 });
7586 let editor = cx.add_window(|cx| build_editor(multibuffer, cx));
7587
7588 let mut copilot_requests = copilot_lsp
7589 .handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| async move {
7590 Ok(copilot::request::GetCompletionsResult {
7591 completions: vec![copilot::request::Completion {
7592 text: "next line".into(),
7593 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7594 ..Default::default()
7595 }],
7596 })
7597 });
7598
7599 editor.update(cx, |editor, cx| {
7600 editor.change_selections(None, cx, |selections| {
7601 selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
7602 });
7603 editor.next_copilot_suggestion(&Default::default(), cx);
7604 });
7605
7606 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7607 assert!(copilot_requests.try_next().is_err());
7608
7609 editor.update(cx, |editor, cx| {
7610 editor.change_selections(None, cx, |s| {
7611 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
7612 });
7613 editor.next_copilot_suggestion(&Default::default(), cx);
7614 });
7615
7616 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7617 assert!(copilot_requests.try_next().is_ok());
7618}
7619
7620#[gpui::test]
7621async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
7622 init_test(cx, |_| {});
7623
7624 let mut language = Language::new(
7625 LanguageConfig {
7626 name: "Rust".into(),
7627 path_suffixes: vec!["rs".to_string()],
7628 brackets: BracketPairConfig {
7629 pairs: vec![BracketPair {
7630 start: "{".to_string(),
7631 end: "}".to_string(),
7632 close: true,
7633 newline: true,
7634 }],
7635 disabled_scopes_by_bracket_ix: Vec::new(),
7636 },
7637 ..Default::default()
7638 },
7639 Some(tree_sitter_rust::language()),
7640 );
7641 let mut fake_servers = language
7642 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
7643 capabilities: lsp::ServerCapabilities {
7644 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
7645 first_trigger_character: "{".to_string(),
7646 more_trigger_character: None,
7647 }),
7648 ..Default::default()
7649 },
7650 ..Default::default()
7651 }))
7652 .await;
7653
7654 let fs = FakeFs::new(cx.executor());
7655 fs.insert_tree(
7656 "/a",
7657 json!({
7658 "main.rs": "fn main() { let a = 5; }",
7659 "other.rs": "// Test file",
7660 }),
7661 )
7662 .await;
7663 let project = Project::test(fs, ["/a".as_ref()], cx).await;
7664 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
7665 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7666
7667 let cx = &mut VisualTestContext::from_window(*workspace, cx);
7668
7669 let worktree_id = workspace
7670 .update(cx, |workspace, cx| {
7671 workspace.project().update(cx, |project, cx| {
7672 project.worktrees().next().unwrap().read(cx).id()
7673 })
7674 })
7675 .unwrap();
7676
7677 let buffer = project
7678 .update(cx, |project, cx| {
7679 project.open_local_buffer("/a/main.rs", cx)
7680 })
7681 .await
7682 .unwrap();
7683 cx.executor().run_until_parked();
7684 cx.executor().start_waiting();
7685 let fake_server = fake_servers.next().await.unwrap();
7686 let editor_handle = workspace
7687 .update(cx, |workspace, cx| {
7688 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
7689 })
7690 .unwrap()
7691 .await
7692 .unwrap()
7693 .downcast::<Editor>()
7694 .unwrap();
7695
7696 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
7697 assert_eq!(
7698 params.text_document_position.text_document.uri,
7699 lsp::Url::from_file_path("/a/main.rs").unwrap(),
7700 );
7701 assert_eq!(
7702 params.text_document_position.position,
7703 lsp::Position::new(0, 21),
7704 );
7705
7706 Ok(Some(vec![lsp::TextEdit {
7707 new_text: "]".to_string(),
7708 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
7709 }]))
7710 });
7711
7712 editor_handle.update(cx, |editor, cx| {
7713 editor.focus(cx);
7714 editor.change_selections(None, cx, |s| {
7715 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
7716 });
7717 editor.handle_input("{", cx);
7718 });
7719
7720 cx.executor().run_until_parked();
7721
7722 buffer.update(cx, |buffer, _| {
7723 assert_eq!(
7724 buffer.text(),
7725 "fn main() { let a = {5}; }",
7726 "No extra braces from on type formatting should appear in the buffer"
7727 )
7728 });
7729}
7730
7731#[gpui::test]
7732async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
7733 init_test(cx, |_| {});
7734
7735 let language_name: Arc<str> = "Rust".into();
7736 let mut language = Language::new(
7737 LanguageConfig {
7738 name: Arc::clone(&language_name),
7739 path_suffixes: vec!["rs".to_string()],
7740 ..Default::default()
7741 },
7742 Some(tree_sitter_rust::language()),
7743 );
7744
7745 let server_restarts = Arc::new(AtomicUsize::new(0));
7746 let closure_restarts = Arc::clone(&server_restarts);
7747 let language_server_name = "test language server";
7748 let mut fake_servers = language
7749 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
7750 name: language_server_name,
7751 initialization_options: Some(json!({
7752 "testOptionValue": true
7753 })),
7754 initializer: Some(Box::new(move |fake_server| {
7755 let task_restarts = Arc::clone(&closure_restarts);
7756 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
7757 task_restarts.fetch_add(1, atomic::Ordering::Release);
7758 futures::future::ready(Ok(()))
7759 });
7760 })),
7761 ..Default::default()
7762 }))
7763 .await;
7764
7765 let fs = FakeFs::new(cx.executor());
7766 fs.insert_tree(
7767 "/a",
7768 json!({
7769 "main.rs": "fn main() { let a = 5; }",
7770 "other.rs": "// Test file",
7771 }),
7772 )
7773 .await;
7774 let project = Project::test(fs, ["/a".as_ref()], cx).await;
7775 project.update(cx, |project, _| project.languages().add(Arc::new(language)));
7776 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7777 let _buffer = project
7778 .update(cx, |project, cx| {
7779 project.open_local_buffer("/a/main.rs", cx)
7780 })
7781 .await
7782 .unwrap();
7783 let _fake_server = fake_servers.next().await.unwrap();
7784 update_test_language_settings(cx, |language_settings| {
7785 language_settings.languages.insert(
7786 Arc::clone(&language_name),
7787 LanguageSettingsContent {
7788 tab_size: NonZeroU32::new(8),
7789 ..Default::default()
7790 },
7791 );
7792 });
7793 cx.executor().run_until_parked();
7794 assert_eq!(
7795 server_restarts.load(atomic::Ordering::Acquire),
7796 0,
7797 "Should not restart LSP server on an unrelated change"
7798 );
7799
7800 update_test_project_settings(cx, |project_settings| {
7801 project_settings.lsp.insert(
7802 "Some other server name".into(),
7803 LspSettings {
7804 initialization_options: Some(json!({
7805 "some other init value": false
7806 })),
7807 },
7808 );
7809 });
7810 cx.executor().run_until_parked();
7811 assert_eq!(
7812 server_restarts.load(atomic::Ordering::Acquire),
7813 0,
7814 "Should not restart LSP server on an unrelated LSP settings change"
7815 );
7816
7817 update_test_project_settings(cx, |project_settings| {
7818 project_settings.lsp.insert(
7819 language_server_name.into(),
7820 LspSettings {
7821 initialization_options: Some(json!({
7822 "anotherInitValue": false
7823 })),
7824 },
7825 );
7826 });
7827 cx.executor().run_until_parked();
7828 assert_eq!(
7829 server_restarts.load(atomic::Ordering::Acquire),
7830 1,
7831 "Should restart LSP server on a related LSP settings change"
7832 );
7833
7834 update_test_project_settings(cx, |project_settings| {
7835 project_settings.lsp.insert(
7836 language_server_name.into(),
7837 LspSettings {
7838 initialization_options: Some(json!({
7839 "anotherInitValue": false
7840 })),
7841 },
7842 );
7843 });
7844 cx.executor().run_until_parked();
7845 assert_eq!(
7846 server_restarts.load(atomic::Ordering::Acquire),
7847 1,
7848 "Should not restart LSP server on a related LSP settings change that is the same"
7849 );
7850
7851 update_test_project_settings(cx, |project_settings| {
7852 project_settings.lsp.insert(
7853 language_server_name.into(),
7854 LspSettings {
7855 initialization_options: None,
7856 },
7857 );
7858 });
7859 cx.executor().run_until_parked();
7860 assert_eq!(
7861 server_restarts.load(atomic::Ordering::Acquire),
7862 2,
7863 "Should restart LSP server on another related LSP settings change"
7864 );
7865}
7866
7867#[gpui::test]
7868async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
7869 init_test(cx, |_| {});
7870
7871 let mut cx = EditorLspTestContext::new_rust(
7872 lsp::ServerCapabilities {
7873 completion_provider: Some(lsp::CompletionOptions {
7874 trigger_characters: Some(vec![".".to_string()]),
7875 resolve_provider: Some(true),
7876 ..Default::default()
7877 }),
7878 ..Default::default()
7879 },
7880 cx,
7881 )
7882 .await;
7883
7884 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
7885 cx.simulate_keystroke(".");
7886 let completion_item = lsp::CompletionItem {
7887 label: "some".into(),
7888 kind: Some(lsp::CompletionItemKind::SNIPPET),
7889 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
7890 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
7891 kind: lsp::MarkupKind::Markdown,
7892 value: "```rust\nSome(2)\n```".to_string(),
7893 })),
7894 deprecated: Some(false),
7895 sort_text: Some("fffffff2".to_string()),
7896 filter_text: Some("some".to_string()),
7897 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
7898 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7899 range: lsp::Range {
7900 start: lsp::Position {
7901 line: 0,
7902 character: 22,
7903 },
7904 end: lsp::Position {
7905 line: 0,
7906 character: 22,
7907 },
7908 },
7909 new_text: "Some(2)".to_string(),
7910 })),
7911 additional_text_edits: Some(vec![lsp::TextEdit {
7912 range: lsp::Range {
7913 start: lsp::Position {
7914 line: 0,
7915 character: 20,
7916 },
7917 end: lsp::Position {
7918 line: 0,
7919 character: 22,
7920 },
7921 },
7922 new_text: "".to_string(),
7923 }]),
7924 ..Default::default()
7925 };
7926
7927 let closure_completion_item = completion_item.clone();
7928 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
7929 let task_completion_item = closure_completion_item.clone();
7930 async move {
7931 Ok(Some(lsp::CompletionResponse::Array(vec![
7932 task_completion_item,
7933 ])))
7934 }
7935 });
7936
7937 request.next().await;
7938
7939 cx.condition(|editor, _| editor.context_menu_visible())
7940 .await;
7941 let apply_additional_edits = cx.update_editor(|editor, cx| {
7942 editor
7943 .confirm_completion(&ConfirmCompletion::default(), cx)
7944 .unwrap()
7945 });
7946 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
7947
7948 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
7949 let task_completion_item = completion_item.clone();
7950 async move { Ok(task_completion_item) }
7951 })
7952 .next()
7953 .await
7954 .unwrap();
7955 apply_additional_edits.await.unwrap();
7956 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
7957}
7958
7959#[gpui::test]
7960async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
7961 init_test(cx, |_| {});
7962
7963 let mut cx = EditorLspTestContext::new(
7964 Language::new(
7965 LanguageConfig {
7966 path_suffixes: vec!["jsx".into()],
7967 overrides: [(
7968 "element".into(),
7969 LanguageConfigOverride {
7970 word_characters: Override::Set(['-'].into_iter().collect()),
7971 ..Default::default()
7972 },
7973 )]
7974 .into_iter()
7975 .collect(),
7976 ..Default::default()
7977 },
7978 Some(tree_sitter_typescript::language_tsx()),
7979 )
7980 .with_override_query("(jsx_self_closing_element) @element")
7981 .unwrap(),
7982 lsp::ServerCapabilities {
7983 completion_provider: Some(lsp::CompletionOptions {
7984 trigger_characters: Some(vec![":".to_string()]),
7985 ..Default::default()
7986 }),
7987 ..Default::default()
7988 },
7989 cx,
7990 )
7991 .await;
7992
7993 cx.lsp
7994 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
7995 Ok(Some(lsp::CompletionResponse::Array(vec![
7996 lsp::CompletionItem {
7997 label: "bg-blue".into(),
7998 ..Default::default()
7999 },
8000 lsp::CompletionItem {
8001 label: "bg-red".into(),
8002 ..Default::default()
8003 },
8004 lsp::CompletionItem {
8005 label: "bg-yellow".into(),
8006 ..Default::default()
8007 },
8008 ])))
8009 });
8010
8011 cx.set_state(r#"<p class="bgˇ" />"#);
8012
8013 // Trigger completion when typing a dash, because the dash is an extra
8014 // word character in the 'element' scope, which contains the cursor.
8015 cx.simulate_keystroke("-");
8016 cx.executor().run_until_parked();
8017 cx.update_editor(|editor, _| {
8018 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8019 assert_eq!(
8020 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8021 &["bg-red", "bg-blue", "bg-yellow"]
8022 );
8023 } else {
8024 panic!("expected completion menu to be open");
8025 }
8026 });
8027
8028 cx.simulate_keystroke("l");
8029 cx.executor().run_until_parked();
8030 cx.update_editor(|editor, _| {
8031 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8032 assert_eq!(
8033 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8034 &["bg-blue", "bg-yellow"]
8035 );
8036 } else {
8037 panic!("expected completion menu to be open");
8038 }
8039 });
8040
8041 // When filtering completions, consider the character after the '-' to
8042 // be the start of a subword.
8043 cx.set_state(r#"<p class="yelˇ" />"#);
8044 cx.simulate_keystroke("l");
8045 cx.executor().run_until_parked();
8046 cx.update_editor(|editor, _| {
8047 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8048 assert_eq!(
8049 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8050 &["bg-yellow"]
8051 );
8052 } else {
8053 panic!("expected completion menu to be open");
8054 }
8055 });
8056}
8057
8058#[gpui::test]
8059async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
8060 init_test(cx, |settings| {
8061 settings.defaults.formatter = Some(language_settings::Formatter::Prettier)
8062 });
8063
8064 let mut language = Language::new(
8065 LanguageConfig {
8066 name: "Rust".into(),
8067 path_suffixes: vec!["rs".to_string()],
8068 prettier_parser_name: Some("test_parser".to_string()),
8069 ..Default::default()
8070 },
8071 Some(tree_sitter_rust::language()),
8072 );
8073
8074 let test_plugin = "test_plugin";
8075 let _ = language
8076 .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
8077 prettier_plugins: vec![test_plugin],
8078 ..Default::default()
8079 }))
8080 .await;
8081
8082 let fs = FakeFs::new(cx.executor());
8083 fs.insert_file("/file.rs", Default::default()).await;
8084
8085 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8086 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
8087 project.update(cx, |project, _| {
8088 project.languages().add(Arc::new(language));
8089 });
8090 let buffer = project
8091 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
8092 .await
8093 .unwrap();
8094
8095 let buffer_text = "one\ntwo\nthree\n";
8096 let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
8097 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
8098 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
8099
8100 editor
8101 .update(cx, |editor, cx| {
8102 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
8103 })
8104 .unwrap()
8105 .await;
8106 assert_eq!(
8107 editor.update(cx, |editor, cx| editor.text(cx)),
8108 buffer_text.to_string() + prettier_format_suffix,
8109 "Test prettier formatting was not applied to the original buffer text",
8110 );
8111
8112 update_test_language_settings(cx, |settings| {
8113 settings.defaults.formatter = Some(language_settings::Formatter::Auto)
8114 });
8115 let format = editor.update(cx, |editor, cx| {
8116 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
8117 });
8118 format.await.unwrap();
8119 assert_eq!(
8120 editor.update(cx, |editor, cx| editor.text(cx)),
8121 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
8122 "Autoformatting (via test prettier) was not applied to the original buffer text",
8123 );
8124}
8125
8126fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
8127 let point = DisplayPoint::new(row as u32, column as u32);
8128 point..point
8129}
8130
8131fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
8132 let (text, ranges) = marked_text_ranges(marked_text, true);
8133 assert_eq!(view.text(cx), text);
8134 assert_eq!(
8135 view.selections.ranges(cx),
8136 ranges,
8137 "Assert selections are {}",
8138 marked_text
8139 );
8140}
8141
8142/// Handle completion request passing a marked string specifying where the completion
8143/// should be triggered from using '|' character, what range should be replaced, and what completions
8144/// should be returned using '<' and '>' to delimit the range
8145pub fn handle_completion_request<'a>(
8146 cx: &mut EditorLspTestContext<'a>,
8147 marked_string: &str,
8148 completions: Vec<&'static str>,
8149) -> impl Future<Output = ()> {
8150 let complete_from_marker: TextRangeMarker = '|'.into();
8151 let replace_range_marker: TextRangeMarker = ('<', '>').into();
8152 let (_, mut marked_ranges) = marked_text_ranges_by(
8153 marked_string,
8154 vec![complete_from_marker.clone(), replace_range_marker.clone()],
8155 );
8156
8157 let complete_from_position =
8158 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
8159 let replace_range =
8160 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
8161
8162 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
8163 let completions = completions.clone();
8164 async move {
8165 assert_eq!(params.text_document_position.text_document.uri, url.clone());
8166 assert_eq!(
8167 params.text_document_position.position,
8168 complete_from_position
8169 );
8170 Ok(Some(lsp::CompletionResponse::Array(
8171 completions
8172 .iter()
8173 .map(|completion_text| lsp::CompletionItem {
8174 label: completion_text.to_string(),
8175 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8176 range: replace_range,
8177 new_text: completion_text.to_string(),
8178 })),
8179 ..Default::default()
8180 })
8181 .collect(),
8182 )))
8183 }
8184 });
8185
8186 async move {
8187 request.next().await;
8188 }
8189}
8190
8191fn handle_resolve_completion_request<'a>(
8192 cx: &mut EditorLspTestContext<'a>,
8193 edits: Option<Vec<(&'static str, &'static str)>>,
8194) -> impl Future<Output = ()> {
8195 let edits = edits.map(|edits| {
8196 edits
8197 .iter()
8198 .map(|(marked_string, new_text)| {
8199 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
8200 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
8201 lsp::TextEdit::new(replace_range, new_text.to_string())
8202 })
8203 .collect::<Vec<_>>()
8204 });
8205
8206 let mut request =
8207 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
8208 let edits = edits.clone();
8209 async move {
8210 Ok(lsp::CompletionItem {
8211 additional_text_edits: edits,
8212 ..Default::default()
8213 })
8214 }
8215 });
8216
8217 async move {
8218 request.next().await;
8219 }
8220}
8221
8222fn handle_copilot_completion_request(
8223 lsp: &lsp::FakeLanguageServer,
8224 completions: Vec<copilot::request::Completion>,
8225 completions_cycling: Vec<copilot::request::Completion>,
8226) {
8227 lsp.handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| {
8228 let completions = completions.clone();
8229 async move {
8230 Ok(copilot::request::GetCompletionsResult {
8231 completions: completions.clone(),
8232 })
8233 }
8234 });
8235 lsp.handle_request::<copilot::request::GetCompletionsCycling, _, _>(move |_params, _cx| {
8236 let completions_cycling = completions_cycling.clone();
8237 async move {
8238 Ok(copilot::request::GetCompletionsResult {
8239 completions: completions_cycling.clone(),
8240 })
8241 }
8242 });
8243}
8244
8245pub(crate) fn update_test_language_settings(
8246 cx: &mut TestAppContext,
8247 f: impl Fn(&mut AllLanguageSettingsContent),
8248) {
8249 cx.update(|cx| {
8250 cx.update_global(|store: &mut SettingsStore, cx| {
8251 store.update_user_settings::<AllLanguageSettings>(cx, f);
8252 });
8253 });
8254}
8255
8256pub(crate) fn update_test_project_settings(
8257 cx: &mut TestAppContext,
8258 f: impl Fn(&mut ProjectSettings),
8259) {
8260 cx.update(|cx| {
8261 cx.update_global(|store: &mut SettingsStore, cx| {
8262 store.update_user_settings::<ProjectSettings>(cx, f);
8263 });
8264 });
8265}
8266
8267pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
8268 cx.update(|cx| {
8269 let store = SettingsStore::test(cx);
8270 cx.set_global(store);
8271 theme::init(theme::LoadThemes::JustBase, cx);
8272 client::init_settings(cx);
8273 language::init(cx);
8274 Project::init_settings(cx);
8275 workspace::init_settings(cx);
8276 crate::init(cx);
8277 });
8278
8279 update_test_language_settings(cx, f);
8280}