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