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, 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::default(), 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::default(), 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 // With `move_upwards` the selections stay in place, except for
3157 // the lines inserted above them
3158 let view = cx.add_window(|cx| {
3159 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3160 build_editor(buffer, cx)
3161 });
3162 _ = view.update(cx, |view, cx| {
3163 view.change_selections(None, cx, |s| {
3164 s.select_display_ranges([
3165 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3166 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
3167 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
3168 DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
3169 ])
3170 });
3171 view.duplicate_line(&DuplicateLine { move_upwards: true }, cx);
3172 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3173 assert_eq!(
3174 view.selections.display_ranges(cx),
3175 vec![
3176 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3177 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
3178 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
3179 DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0),
3180 ]
3181 );
3182 });
3183
3184 let view = cx.add_window(|cx| {
3185 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3186 build_editor(buffer, cx)
3187 });
3188 _ = view.update(cx, |view, cx| {
3189 view.change_selections(None, cx, |s| {
3190 s.select_display_ranges([
3191 DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
3192 DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
3193 ])
3194 });
3195 view.duplicate_line(&DuplicateLine { move_upwards: true }, cx);
3196 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3197 assert_eq!(
3198 view.selections.display_ranges(cx),
3199 vec![
3200 DisplayPoint::new(0, 1)..DisplayPoint::new(1, 1),
3201 DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
3202 ]
3203 );
3204 });
3205}
3206
3207#[gpui::test]
3208fn test_move_line_up_down(cx: &mut TestAppContext) {
3209 init_test(cx, |_| {});
3210
3211 let view = cx.add_window(|cx| {
3212 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3213 build_editor(buffer, cx)
3214 });
3215 _ = view.update(cx, |view, cx| {
3216 view.fold_ranges(
3217 vec![
3218 Point::new(0, 2)..Point::new(1, 2),
3219 Point::new(2, 3)..Point::new(4, 1),
3220 Point::new(7, 0)..Point::new(8, 4),
3221 ],
3222 true,
3223 cx,
3224 );
3225 view.change_selections(None, cx, |s| {
3226 s.select_display_ranges([
3227 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
3228 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
3229 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
3230 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2),
3231 ])
3232 });
3233 assert_eq!(
3234 view.display_text(cx),
3235 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3236 );
3237
3238 view.move_line_up(&MoveLineUp, cx);
3239 assert_eq!(
3240 view.display_text(cx),
3241 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3242 );
3243 assert_eq!(
3244 view.selections.display_ranges(cx),
3245 vec![
3246 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
3247 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
3248 DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
3249 DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
3250 ]
3251 );
3252 });
3253
3254 _ = view.update(cx, |view, cx| {
3255 view.move_line_down(&MoveLineDown, cx);
3256 assert_eq!(
3257 view.display_text(cx),
3258 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3259 );
3260 assert_eq!(
3261 view.selections.display_ranges(cx),
3262 vec![
3263 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
3264 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
3265 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
3266 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
3267 ]
3268 );
3269 });
3270
3271 _ = view.update(cx, |view, cx| {
3272 view.move_line_down(&MoveLineDown, cx);
3273 assert_eq!(
3274 view.display_text(cx),
3275 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3276 );
3277 assert_eq!(
3278 view.selections.display_ranges(cx),
3279 vec![
3280 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
3281 DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
3282 DisplayPoint::new(3, 2)..DisplayPoint::new(4, 3),
3283 DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
3284 ]
3285 );
3286 });
3287
3288 _ = view.update(cx, |view, cx| {
3289 view.move_line_up(&MoveLineUp, cx);
3290 assert_eq!(
3291 view.display_text(cx),
3292 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3293 );
3294 assert_eq!(
3295 view.selections.display_ranges(cx),
3296 vec![
3297 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
3298 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
3299 DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3),
3300 DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
3301 ]
3302 );
3303 });
3304}
3305
3306#[gpui::test]
3307fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3308 init_test(cx, |_| {});
3309
3310 let editor = cx.add_window(|cx| {
3311 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3312 build_editor(buffer, cx)
3313 });
3314 _ = editor.update(cx, |editor, cx| {
3315 let snapshot = editor.buffer.read(cx).snapshot(cx);
3316 editor.insert_blocks(
3317 [BlockProperties {
3318 style: BlockStyle::Fixed,
3319 position: snapshot.anchor_after(Point::new(2, 0)),
3320 disposition: BlockDisposition::Below,
3321 height: 1,
3322 render: Arc::new(|_| div().into_any()),
3323 }],
3324 Some(Autoscroll::fit()),
3325 cx,
3326 );
3327 editor.change_selections(None, cx, |s| {
3328 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
3329 });
3330 editor.move_line_down(&MoveLineDown, cx);
3331 });
3332}
3333
3334#[gpui::test]
3335fn test_transpose(cx: &mut TestAppContext) {
3336 init_test(cx, |_| {});
3337
3338 _ = cx.add_window(|cx| {
3339 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
3340 editor.set_style(EditorStyle::default(), cx);
3341 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
3342 editor.transpose(&Default::default(), cx);
3343 assert_eq!(editor.text(cx), "bac");
3344 assert_eq!(editor.selections.ranges(cx), [2..2]);
3345
3346 editor.transpose(&Default::default(), cx);
3347 assert_eq!(editor.text(cx), "bca");
3348 assert_eq!(editor.selections.ranges(cx), [3..3]);
3349
3350 editor.transpose(&Default::default(), cx);
3351 assert_eq!(editor.text(cx), "bac");
3352 assert_eq!(editor.selections.ranges(cx), [3..3]);
3353
3354 editor
3355 });
3356
3357 _ = cx.add_window(|cx| {
3358 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3359 editor.set_style(EditorStyle::default(), cx);
3360 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
3361 editor.transpose(&Default::default(), cx);
3362 assert_eq!(editor.text(cx), "acb\nde");
3363 assert_eq!(editor.selections.ranges(cx), [3..3]);
3364
3365 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3366 editor.transpose(&Default::default(), cx);
3367 assert_eq!(editor.text(cx), "acbd\ne");
3368 assert_eq!(editor.selections.ranges(cx), [5..5]);
3369
3370 editor.transpose(&Default::default(), cx);
3371 assert_eq!(editor.text(cx), "acbde\n");
3372 assert_eq!(editor.selections.ranges(cx), [6..6]);
3373
3374 editor.transpose(&Default::default(), cx);
3375 assert_eq!(editor.text(cx), "acbd\ne");
3376 assert_eq!(editor.selections.ranges(cx), [6..6]);
3377
3378 editor
3379 });
3380
3381 _ = cx.add_window(|cx| {
3382 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3383 editor.set_style(EditorStyle::default(), cx);
3384 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
3385 editor.transpose(&Default::default(), cx);
3386 assert_eq!(editor.text(cx), "bacd\ne");
3387 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
3388
3389 editor.transpose(&Default::default(), cx);
3390 assert_eq!(editor.text(cx), "bcade\n");
3391 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
3392
3393 editor.transpose(&Default::default(), cx);
3394 assert_eq!(editor.text(cx), "bcda\ne");
3395 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3396
3397 editor.transpose(&Default::default(), cx);
3398 assert_eq!(editor.text(cx), "bcade\n");
3399 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3400
3401 editor.transpose(&Default::default(), cx);
3402 assert_eq!(editor.text(cx), "bcaed\n");
3403 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
3404
3405 editor
3406 });
3407
3408 _ = cx.add_window(|cx| {
3409 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
3410 editor.set_style(EditorStyle::default(), cx);
3411 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3412 editor.transpose(&Default::default(), cx);
3413 assert_eq!(editor.text(cx), "🏀🍐✋");
3414 assert_eq!(editor.selections.ranges(cx), [8..8]);
3415
3416 editor.transpose(&Default::default(), cx);
3417 assert_eq!(editor.text(cx), "🏀✋🍐");
3418 assert_eq!(editor.selections.ranges(cx), [11..11]);
3419
3420 editor.transpose(&Default::default(), cx);
3421 assert_eq!(editor.text(cx), "🏀🍐✋");
3422 assert_eq!(editor.selections.ranges(cx), [11..11]);
3423
3424 editor
3425 });
3426}
3427
3428#[gpui::test]
3429async fn test_clipboard(cx: &mut gpui::TestAppContext) {
3430 init_test(cx, |_| {});
3431
3432 let mut cx = EditorTestContext::new(cx).await;
3433
3434 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
3435 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3436 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
3437
3438 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
3439 cx.set_state("two ˇfour ˇsix ˇ");
3440 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3441 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
3442
3443 // Paste again but with only two cursors. Since the number of cursors doesn't
3444 // match the number of slices in the clipboard, the entire clipboard text
3445 // is pasted at each cursor.
3446 cx.set_state("ˇtwo one✅ four three six five ˇ");
3447 cx.update_editor(|e, cx| {
3448 e.handle_input("( ", cx);
3449 e.paste(&Paste, cx);
3450 e.handle_input(") ", cx);
3451 });
3452 cx.assert_editor_state(
3453 &([
3454 "( one✅ ",
3455 "three ",
3456 "five ) ˇtwo one✅ four three six five ( one✅ ",
3457 "three ",
3458 "five ) ˇ",
3459 ]
3460 .join("\n")),
3461 );
3462
3463 // Cut with three selections, one of which is full-line.
3464 cx.set_state(indoc! {"
3465 1«2ˇ»3
3466 4ˇ567
3467 «8ˇ»9"});
3468 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3469 cx.assert_editor_state(indoc! {"
3470 1ˇ3
3471 ˇ9"});
3472
3473 // Paste with three selections, noticing how the copied selection that was full-line
3474 // gets inserted before the second cursor.
3475 cx.set_state(indoc! {"
3476 1ˇ3
3477 9ˇ
3478 «oˇ»ne"});
3479 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3480 cx.assert_editor_state(indoc! {"
3481 12ˇ3
3482 4567
3483 9ˇ
3484 8ˇne"});
3485
3486 // Copy with a single cursor only, which writes the whole line into the clipboard.
3487 cx.set_state(indoc! {"
3488 The quick brown
3489 fox juˇmps over
3490 the lazy dog"});
3491 cx.update_editor(|e, cx| e.copy(&Copy, cx));
3492 assert_eq!(
3493 cx.read_from_clipboard().map(|item| item.text().to_owned()),
3494 Some("fox jumps over\n".to_owned())
3495 );
3496
3497 // Paste with three selections, noticing how the copied full-line selection is inserted
3498 // before the empty selections but replaces the selection that is non-empty.
3499 cx.set_state(indoc! {"
3500 Tˇhe quick brown
3501 «foˇ»x jumps over
3502 tˇhe lazy dog"});
3503 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3504 cx.assert_editor_state(indoc! {"
3505 fox jumps over
3506 Tˇhe quick brown
3507 fox jumps over
3508 ˇx jumps over
3509 fox jumps over
3510 tˇhe lazy dog"});
3511}
3512
3513#[gpui::test]
3514async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
3515 init_test(cx, |_| {});
3516
3517 let mut cx = EditorTestContext::new(cx).await;
3518 let language = Arc::new(Language::new(
3519 LanguageConfig::default(),
3520 Some(tree_sitter_rust::language()),
3521 ));
3522 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
3523
3524 // Cut an indented block, without the leading whitespace.
3525 cx.set_state(indoc! {"
3526 const a: B = (
3527 c(),
3528 «d(
3529 e,
3530 f
3531 )ˇ»
3532 );
3533 "});
3534 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3535 cx.assert_editor_state(indoc! {"
3536 const a: B = (
3537 c(),
3538 ˇ
3539 );
3540 "});
3541
3542 // Paste it at the same position.
3543 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3544 cx.assert_editor_state(indoc! {"
3545 const a: B = (
3546 c(),
3547 d(
3548 e,
3549 f
3550 )ˇ
3551 );
3552 "});
3553
3554 // Paste it at a line with a lower indent level.
3555 cx.set_state(indoc! {"
3556 ˇ
3557 const a: B = (
3558 c(),
3559 );
3560 "});
3561 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3562 cx.assert_editor_state(indoc! {"
3563 d(
3564 e,
3565 f
3566 )ˇ
3567 const a: B = (
3568 c(),
3569 );
3570 "});
3571
3572 // Cut an indented block, with the leading whitespace.
3573 cx.set_state(indoc! {"
3574 const a: B = (
3575 c(),
3576 « d(
3577 e,
3578 f
3579 )
3580 ˇ»);
3581 "});
3582 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3583 cx.assert_editor_state(indoc! {"
3584 const a: B = (
3585 c(),
3586 ˇ);
3587 "});
3588
3589 // Paste it at the same position.
3590 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3591 cx.assert_editor_state(indoc! {"
3592 const a: B = (
3593 c(),
3594 d(
3595 e,
3596 f
3597 )
3598 ˇ);
3599 "});
3600
3601 // Paste it at a line with a higher indent level.
3602 cx.set_state(indoc! {"
3603 const a: B = (
3604 c(),
3605 d(
3606 e,
3607 fˇ
3608 )
3609 );
3610 "});
3611 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3612 cx.assert_editor_state(indoc! {"
3613 const a: B = (
3614 c(),
3615 d(
3616 e,
3617 f d(
3618 e,
3619 f
3620 )
3621 ˇ
3622 )
3623 );
3624 "});
3625}
3626
3627#[gpui::test]
3628fn test_select_all(cx: &mut TestAppContext) {
3629 init_test(cx, |_| {});
3630
3631 let view = cx.add_window(|cx| {
3632 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
3633 build_editor(buffer, cx)
3634 });
3635 _ = view.update(cx, |view, cx| {
3636 view.select_all(&SelectAll, cx);
3637 assert_eq!(
3638 view.selections.display_ranges(cx),
3639 &[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)]
3640 );
3641 });
3642}
3643
3644#[gpui::test]
3645fn test_select_line(cx: &mut TestAppContext) {
3646 init_test(cx, |_| {});
3647
3648 let view = cx.add_window(|cx| {
3649 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
3650 build_editor(buffer, cx)
3651 });
3652 _ = view.update(cx, |view, cx| {
3653 view.change_selections(None, cx, |s| {
3654 s.select_display_ranges([
3655 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3656 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
3657 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
3658 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2),
3659 ])
3660 });
3661 view.select_line(&SelectLine, cx);
3662 assert_eq!(
3663 view.selections.display_ranges(cx),
3664 vec![
3665 DisplayPoint::new(0, 0)..DisplayPoint::new(2, 0),
3666 DisplayPoint::new(4, 0)..DisplayPoint::new(5, 0),
3667 ]
3668 );
3669 });
3670
3671 _ = view.update(cx, |view, cx| {
3672 view.select_line(&SelectLine, cx);
3673 assert_eq!(
3674 view.selections.display_ranges(cx),
3675 vec![
3676 DisplayPoint::new(0, 0)..DisplayPoint::new(3, 0),
3677 DisplayPoint::new(4, 0)..DisplayPoint::new(5, 5),
3678 ]
3679 );
3680 });
3681
3682 _ = view.update(cx, |view, cx| {
3683 view.select_line(&SelectLine, cx);
3684 assert_eq!(
3685 view.selections.display_ranges(cx),
3686 vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)]
3687 );
3688 });
3689}
3690
3691#[gpui::test]
3692fn test_split_selection_into_lines(cx: &mut TestAppContext) {
3693 init_test(cx, |_| {});
3694
3695 let view = cx.add_window(|cx| {
3696 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
3697 build_editor(buffer, cx)
3698 });
3699 _ = view.update(cx, |view, cx| {
3700 view.fold_ranges(
3701 vec![
3702 Point::new(0, 2)..Point::new(1, 2),
3703 Point::new(2, 3)..Point::new(4, 1),
3704 Point::new(7, 0)..Point::new(8, 4),
3705 ],
3706 true,
3707 cx,
3708 );
3709 view.change_selections(None, cx, |s| {
3710 s.select_display_ranges([
3711 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
3712 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
3713 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
3714 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
3715 ])
3716 });
3717 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
3718 });
3719
3720 _ = view.update(cx, |view, cx| {
3721 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
3722 assert_eq!(
3723 view.display_text(cx),
3724 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
3725 );
3726 assert_eq!(
3727 view.selections.display_ranges(cx),
3728 [
3729 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
3730 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
3731 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
3732 DisplayPoint::new(5, 4)..DisplayPoint::new(5, 4)
3733 ]
3734 );
3735 });
3736
3737 _ = view.update(cx, |view, cx| {
3738 view.change_selections(None, cx, |s| {
3739 s.select_display_ranges([DisplayPoint::new(5, 0)..DisplayPoint::new(0, 1)])
3740 });
3741 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
3742 assert_eq!(
3743 view.display_text(cx),
3744 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
3745 );
3746 assert_eq!(
3747 view.selections.display_ranges(cx),
3748 [
3749 DisplayPoint::new(0, 5)..DisplayPoint::new(0, 5),
3750 DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
3751 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
3752 DisplayPoint::new(3, 5)..DisplayPoint::new(3, 5),
3753 DisplayPoint::new(4, 5)..DisplayPoint::new(4, 5),
3754 DisplayPoint::new(5, 5)..DisplayPoint::new(5, 5),
3755 DisplayPoint::new(6, 5)..DisplayPoint::new(6, 5),
3756 DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0)
3757 ]
3758 );
3759 });
3760}
3761
3762#[gpui::test]
3763async fn test_add_selection_above_below(cx: &mut TestAppContext) {
3764 init_test(cx, |_| {});
3765
3766 let mut cx = EditorTestContext::new(cx).await;
3767
3768 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
3769 cx.set_state(indoc!(
3770 r#"abc
3771 defˇghi
3772
3773 jk
3774 nlmo
3775 "#
3776 ));
3777
3778 cx.update_editor(|editor, cx| {
3779 editor.add_selection_above(&Default::default(), cx);
3780 });
3781
3782 cx.assert_editor_state(indoc!(
3783 r#"abcˇ
3784 defˇghi
3785
3786 jk
3787 nlmo
3788 "#
3789 ));
3790
3791 cx.update_editor(|editor, cx| {
3792 editor.add_selection_above(&Default::default(), cx);
3793 });
3794
3795 cx.assert_editor_state(indoc!(
3796 r#"abcˇ
3797 defˇghi
3798
3799 jk
3800 nlmo
3801 "#
3802 ));
3803
3804 cx.update_editor(|view, cx| {
3805 view.add_selection_below(&Default::default(), cx);
3806 });
3807
3808 cx.assert_editor_state(indoc!(
3809 r#"abc
3810 defˇghi
3811
3812 jk
3813 nlmo
3814 "#
3815 ));
3816
3817 cx.update_editor(|view, cx| {
3818 view.undo_selection(&Default::default(), cx);
3819 });
3820
3821 cx.assert_editor_state(indoc!(
3822 r#"abcˇ
3823 defˇghi
3824
3825 jk
3826 nlmo
3827 "#
3828 ));
3829
3830 cx.update_editor(|view, cx| {
3831 view.redo_selection(&Default::default(), cx);
3832 });
3833
3834 cx.assert_editor_state(indoc!(
3835 r#"abc
3836 defˇghi
3837
3838 jk
3839 nlmo
3840 "#
3841 ));
3842
3843 cx.update_editor(|view, cx| {
3844 view.add_selection_below(&Default::default(), cx);
3845 });
3846
3847 cx.assert_editor_state(indoc!(
3848 r#"abc
3849 defˇghi
3850
3851 jk
3852 nlmˇo
3853 "#
3854 ));
3855
3856 cx.update_editor(|view, cx| {
3857 view.add_selection_below(&Default::default(), cx);
3858 });
3859
3860 cx.assert_editor_state(indoc!(
3861 r#"abc
3862 defˇghi
3863
3864 jk
3865 nlmˇo
3866 "#
3867 ));
3868
3869 // change selections
3870 cx.set_state(indoc!(
3871 r#"abc
3872 def«ˇg»hi
3873
3874 jk
3875 nlmo
3876 "#
3877 ));
3878
3879 cx.update_editor(|view, cx| {
3880 view.add_selection_below(&Default::default(), cx);
3881 });
3882
3883 cx.assert_editor_state(indoc!(
3884 r#"abc
3885 def«ˇg»hi
3886
3887 jk
3888 nlm«ˇo»
3889 "#
3890 ));
3891
3892 cx.update_editor(|view, cx| {
3893 view.add_selection_below(&Default::default(), cx);
3894 });
3895
3896 cx.assert_editor_state(indoc!(
3897 r#"abc
3898 def«ˇg»hi
3899
3900 jk
3901 nlm«ˇo»
3902 "#
3903 ));
3904
3905 cx.update_editor(|view, cx| {
3906 view.add_selection_above(&Default::default(), cx);
3907 });
3908
3909 cx.assert_editor_state(indoc!(
3910 r#"abc
3911 def«ˇg»hi
3912
3913 jk
3914 nlmo
3915 "#
3916 ));
3917
3918 cx.update_editor(|view, cx| {
3919 view.add_selection_above(&Default::default(), cx);
3920 });
3921
3922 cx.assert_editor_state(indoc!(
3923 r#"abc
3924 def«ˇg»hi
3925
3926 jk
3927 nlmo
3928 "#
3929 ));
3930
3931 // Change selections again
3932 cx.set_state(indoc!(
3933 r#"a«bc
3934 defgˇ»hi
3935
3936 jk
3937 nlmo
3938 "#
3939 ));
3940
3941 cx.update_editor(|view, cx| {
3942 view.add_selection_below(&Default::default(), cx);
3943 });
3944
3945 cx.assert_editor_state(indoc!(
3946 r#"a«bcˇ»
3947 d«efgˇ»hi
3948
3949 j«kˇ»
3950 nlmo
3951 "#
3952 ));
3953
3954 cx.update_editor(|view, cx| {
3955 view.add_selection_below(&Default::default(), cx);
3956 });
3957 cx.assert_editor_state(indoc!(
3958 r#"a«bcˇ»
3959 d«efgˇ»hi
3960
3961 j«kˇ»
3962 n«lmoˇ»
3963 "#
3964 ));
3965 cx.update_editor(|view, cx| {
3966 view.add_selection_above(&Default::default(), cx);
3967 });
3968
3969 cx.assert_editor_state(indoc!(
3970 r#"a«bcˇ»
3971 d«efgˇ»hi
3972
3973 j«kˇ»
3974 nlmo
3975 "#
3976 ));
3977
3978 // Change selections again
3979 cx.set_state(indoc!(
3980 r#"abc
3981 d«ˇefghi
3982
3983 jk
3984 nlm»o
3985 "#
3986 ));
3987
3988 cx.update_editor(|view, cx| {
3989 view.add_selection_above(&Default::default(), cx);
3990 });
3991
3992 cx.assert_editor_state(indoc!(
3993 r#"a«ˇbc»
3994 d«ˇef»ghi
3995
3996 j«ˇk»
3997 n«ˇlm»o
3998 "#
3999 ));
4000
4001 cx.update_editor(|view, cx| {
4002 view.add_selection_below(&Default::default(), cx);
4003 });
4004
4005 cx.assert_editor_state(indoc!(
4006 r#"abc
4007 d«ˇef»ghi
4008
4009 j«ˇk»
4010 n«ˇlm»o
4011 "#
4012 ));
4013}
4014
4015#[gpui::test]
4016async fn test_select_next(cx: &mut gpui::TestAppContext) {
4017 init_test(cx, |_| {});
4018
4019 let mut cx = EditorTestContext::new(cx).await;
4020 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4021
4022 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4023 .unwrap();
4024 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4025
4026 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4027 .unwrap();
4028 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4029
4030 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4031 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4032
4033 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4034 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4035
4036 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4037 .unwrap();
4038 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4039
4040 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4041 .unwrap();
4042 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4043}
4044
4045#[gpui::test]
4046async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
4047 init_test(cx, |_| {});
4048
4049 let mut cx = EditorTestContext::new(cx).await;
4050 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4051
4052 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
4053 .unwrap();
4054 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4055}
4056
4057#[gpui::test]
4058async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4059 init_test(cx, |_| {});
4060
4061 let mut cx = EditorTestContext::new(cx).await;
4062 cx.set_state(
4063 r#"let foo = 2;
4064lˇet foo = 2;
4065let fooˇ = 2;
4066let foo = 2;
4067let foo = ˇ2;"#,
4068 );
4069
4070 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4071 .unwrap();
4072 cx.assert_editor_state(
4073 r#"let foo = 2;
4074«letˇ» foo = 2;
4075let «fooˇ» = 2;
4076let foo = 2;
4077let foo = «2ˇ»;"#,
4078 );
4079
4080 // noop for multiple selections with different contents
4081 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4082 .unwrap();
4083 cx.assert_editor_state(
4084 r#"let foo = 2;
4085«letˇ» foo = 2;
4086let «fooˇ» = 2;
4087let foo = 2;
4088let foo = «2ˇ»;"#,
4089 );
4090}
4091
4092#[gpui::test]
4093async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
4094 init_test(cx, |_| {});
4095
4096 let mut cx = EditorTestContext::new(cx).await;
4097 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4098
4099 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4100 .unwrap();
4101 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4102
4103 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4104 .unwrap();
4105 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4106
4107 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4108 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4109
4110 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4111 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4112
4113 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4114 .unwrap();
4115 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
4116
4117 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4118 .unwrap();
4119 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
4120
4121 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4122 .unwrap();
4123 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
4124}
4125
4126#[gpui::test]
4127async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4128 init_test(cx, |_| {});
4129
4130 let mut cx = EditorTestContext::new(cx).await;
4131 cx.set_state(
4132 r#"let foo = 2;
4133lˇet foo = 2;
4134let fooˇ = 2;
4135let foo = 2;
4136let foo = ˇ2;"#,
4137 );
4138
4139 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4140 .unwrap();
4141 cx.assert_editor_state(
4142 r#"let foo = 2;
4143«letˇ» foo = 2;
4144let «fooˇ» = 2;
4145let foo = 2;
4146let foo = «2ˇ»;"#,
4147 );
4148
4149 // noop for multiple selections with different contents
4150 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4151 .unwrap();
4152 cx.assert_editor_state(
4153 r#"let foo = 2;
4154«letˇ» foo = 2;
4155let «fooˇ» = 2;
4156let foo = 2;
4157let foo = «2ˇ»;"#,
4158 );
4159}
4160
4161#[gpui::test]
4162async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
4163 init_test(cx, |_| {});
4164
4165 let mut cx = EditorTestContext::new(cx).await;
4166 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
4167
4168 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4169 .unwrap();
4170 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
4171
4172 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4173 .unwrap();
4174 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
4175
4176 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4177 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
4178
4179 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4180 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
4181
4182 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4183 .unwrap();
4184 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
4185
4186 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4187 .unwrap();
4188 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
4189}
4190
4191#[gpui::test]
4192async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
4193 init_test(cx, |_| {});
4194
4195 let language = Arc::new(Language::new(
4196 LanguageConfig::default(),
4197 Some(tree_sitter_rust::language()),
4198 ));
4199
4200 let text = r#"
4201 use mod1::mod2::{mod3, mod4};
4202
4203 fn fn_1(param1: bool, param2: &str) {
4204 let var1 = "text";
4205 }
4206 "#
4207 .unindent();
4208
4209 let buffer = cx.new_model(|cx| {
4210 Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
4211 .with_language(language, cx)
4212 });
4213 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4214 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4215
4216 view.condition::<crate::EditorEvent>(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4217 .await;
4218
4219 _ = view.update(cx, |view, cx| {
4220 view.change_selections(None, cx, |s| {
4221 s.select_display_ranges([
4222 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
4223 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
4224 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
4225 ]);
4226 });
4227 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4228 });
4229 assert_eq!(
4230 view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
4231 &[
4232 DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
4233 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
4234 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
4235 ]
4236 );
4237
4238 _ = view.update(cx, |view, cx| {
4239 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4240 });
4241 assert_eq!(
4242 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4243 &[
4244 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
4245 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
4246 ]
4247 );
4248
4249 _ = view.update(cx, |view, cx| {
4250 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4251 });
4252 assert_eq!(
4253 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4254 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
4255 );
4256
4257 // Trying to expand the selected syntax node one more time has no effect.
4258 _ = view.update(cx, |view, cx| {
4259 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4260 });
4261 assert_eq!(
4262 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4263 &[DisplayPoint::new(5, 0)..DisplayPoint::new(0, 0)]
4264 );
4265
4266 _ = view.update(cx, |view, cx| {
4267 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4268 });
4269 assert_eq!(
4270 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4271 &[
4272 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
4273 DisplayPoint::new(4, 1)..DisplayPoint::new(2, 0),
4274 ]
4275 );
4276
4277 _ = view.update(cx, |view, cx| {
4278 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4279 });
4280 assert_eq!(
4281 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4282 &[
4283 DisplayPoint::new(0, 23)..DisplayPoint::new(0, 27),
4284 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
4285 DisplayPoint::new(3, 15)..DisplayPoint::new(3, 21),
4286 ]
4287 );
4288
4289 _ = view.update(cx, |view, cx| {
4290 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4291 });
4292 assert_eq!(
4293 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4294 &[
4295 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
4296 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
4297 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
4298 ]
4299 );
4300
4301 // Trying to shrink the selected syntax node one more time has no effect.
4302 _ = view.update(cx, |view, cx| {
4303 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4304 });
4305 assert_eq!(
4306 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4307 &[
4308 DisplayPoint::new(0, 25)..DisplayPoint::new(0, 25),
4309 DisplayPoint::new(2, 24)..DisplayPoint::new(2, 12),
4310 DisplayPoint::new(3, 18)..DisplayPoint::new(3, 18),
4311 ]
4312 );
4313
4314 // Ensure that we keep expanding the selection if the larger selection starts or ends within
4315 // a fold.
4316 _ = view.update(cx, |view, cx| {
4317 view.fold_ranges(
4318 vec![
4319 Point::new(0, 21)..Point::new(0, 24),
4320 Point::new(3, 20)..Point::new(3, 22),
4321 ],
4322 true,
4323 cx,
4324 );
4325 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4326 });
4327 assert_eq!(
4328 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4329 &[
4330 DisplayPoint::new(0, 16)..DisplayPoint::new(0, 28),
4331 DisplayPoint::new(2, 35)..DisplayPoint::new(2, 7),
4332 DisplayPoint::new(3, 4)..DisplayPoint::new(3, 23),
4333 ]
4334 );
4335}
4336
4337#[gpui::test]
4338async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
4339 init_test(cx, |_| {});
4340
4341 let language = Arc::new(
4342 Language::new(
4343 LanguageConfig {
4344 brackets: BracketPairConfig {
4345 pairs: vec![
4346 BracketPair {
4347 start: "{".to_string(),
4348 end: "}".to_string(),
4349 close: false,
4350 newline: true,
4351 },
4352 BracketPair {
4353 start: "(".to_string(),
4354 end: ")".to_string(),
4355 close: false,
4356 newline: true,
4357 },
4358 ],
4359 ..Default::default()
4360 },
4361 ..Default::default()
4362 },
4363 Some(tree_sitter_rust::language()),
4364 )
4365 .with_indents_query(
4366 r#"
4367 (_ "(" ")" @end) @indent
4368 (_ "{" "}" @end) @indent
4369 "#,
4370 )
4371 .unwrap(),
4372 );
4373
4374 let text = "fn a() {}";
4375
4376 let buffer = cx.new_model(|cx| {
4377 Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
4378 .with_language(language, cx)
4379 });
4380 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4381 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4382 editor
4383 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
4384 .await;
4385
4386 _ = editor.update(cx, |editor, cx| {
4387 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
4388 editor.newline(&Newline, cx);
4389 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
4390 assert_eq!(
4391 editor.selections.ranges(cx),
4392 &[
4393 Point::new(1, 4)..Point::new(1, 4),
4394 Point::new(3, 4)..Point::new(3, 4),
4395 Point::new(5, 0)..Point::new(5, 0)
4396 ]
4397 );
4398 });
4399}
4400
4401#[gpui::test]
4402async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
4403 init_test(cx, |_| {});
4404
4405 let mut cx = EditorTestContext::new(cx).await;
4406
4407 let language = Arc::new(Language::new(
4408 LanguageConfig {
4409 brackets: BracketPairConfig {
4410 pairs: vec![
4411 BracketPair {
4412 start: "{".to_string(),
4413 end: "}".to_string(),
4414 close: true,
4415 newline: true,
4416 },
4417 BracketPair {
4418 start: "(".to_string(),
4419 end: ")".to_string(),
4420 close: true,
4421 newline: true,
4422 },
4423 BracketPair {
4424 start: "/*".to_string(),
4425 end: " */".to_string(),
4426 close: true,
4427 newline: true,
4428 },
4429 BracketPair {
4430 start: "[".to_string(),
4431 end: "]".to_string(),
4432 close: false,
4433 newline: true,
4434 },
4435 BracketPair {
4436 start: "\"".to_string(),
4437 end: "\"".to_string(),
4438 close: true,
4439 newline: false,
4440 },
4441 ],
4442 ..Default::default()
4443 },
4444 autoclose_before: "})]".to_string(),
4445 ..Default::default()
4446 },
4447 Some(tree_sitter_rust::language()),
4448 ));
4449
4450 let registry = Arc::new(LanguageRegistry::test());
4451 registry.add(language.clone());
4452 cx.update_buffer(|buffer, cx| {
4453 buffer.set_language_registry(registry);
4454 buffer.set_language(Some(language), cx);
4455 });
4456
4457 cx.set_state(
4458 &r#"
4459 🏀ˇ
4460 εˇ
4461 ❤️ˇ
4462 "#
4463 .unindent(),
4464 );
4465
4466 // autoclose multiple nested brackets at multiple cursors
4467 cx.update_editor(|view, cx| {
4468 view.handle_input("{", cx);
4469 view.handle_input("{", cx);
4470 view.handle_input("{", cx);
4471 });
4472 cx.assert_editor_state(
4473 &"
4474 🏀{{{ˇ}}}
4475 ε{{{ˇ}}}
4476 ❤️{{{ˇ}}}
4477 "
4478 .unindent(),
4479 );
4480
4481 // insert a different closing bracket
4482 cx.update_editor(|view, cx| {
4483 view.handle_input(")", cx);
4484 });
4485 cx.assert_editor_state(
4486 &"
4487 🏀{{{)ˇ}}}
4488 ε{{{)ˇ}}}
4489 ❤️{{{)ˇ}}}
4490 "
4491 .unindent(),
4492 );
4493
4494 // skip over the auto-closed brackets when typing a closing bracket
4495 cx.update_editor(|view, cx| {
4496 view.move_right(&MoveRight, cx);
4497 view.handle_input("}", cx);
4498 view.handle_input("}", cx);
4499 view.handle_input("}", cx);
4500 });
4501 cx.assert_editor_state(
4502 &"
4503 🏀{{{)}}}}ˇ
4504 ε{{{)}}}}ˇ
4505 ❤️{{{)}}}}ˇ
4506 "
4507 .unindent(),
4508 );
4509
4510 // autoclose multi-character pairs
4511 cx.set_state(
4512 &"
4513 ˇ
4514 ˇ
4515 "
4516 .unindent(),
4517 );
4518 cx.update_editor(|view, cx| {
4519 view.handle_input("/", cx);
4520 view.handle_input("*", cx);
4521 });
4522 cx.assert_editor_state(
4523 &"
4524 /*ˇ */
4525 /*ˇ */
4526 "
4527 .unindent(),
4528 );
4529
4530 // one cursor autocloses a multi-character pair, one cursor
4531 // does not autoclose.
4532 cx.set_state(
4533 &"
4534 /ˇ
4535 ˇ
4536 "
4537 .unindent(),
4538 );
4539 cx.update_editor(|view, cx| view.handle_input("*", cx));
4540 cx.assert_editor_state(
4541 &"
4542 /*ˇ */
4543 *ˇ
4544 "
4545 .unindent(),
4546 );
4547
4548 // Don't autoclose if the next character isn't whitespace and isn't
4549 // listed in the language's "autoclose_before" section.
4550 cx.set_state("ˇa b");
4551 cx.update_editor(|view, cx| view.handle_input("{", cx));
4552 cx.assert_editor_state("{ˇa b");
4553
4554 // Don't autoclose if `close` is false for the bracket pair
4555 cx.set_state("ˇ");
4556 cx.update_editor(|view, cx| view.handle_input("[", cx));
4557 cx.assert_editor_state("[ˇ");
4558
4559 // Surround with brackets if text is selected
4560 cx.set_state("«aˇ» b");
4561 cx.update_editor(|view, cx| view.handle_input("{", cx));
4562 cx.assert_editor_state("{«aˇ»} b");
4563
4564 // Autclose pair where the start and end characters are the same
4565 cx.set_state("aˇ");
4566 cx.update_editor(|view, cx| view.handle_input("\"", cx));
4567 cx.assert_editor_state("a\"ˇ\"");
4568 cx.update_editor(|view, cx| view.handle_input("\"", cx));
4569 cx.assert_editor_state("a\"\"ˇ");
4570}
4571
4572#[gpui::test]
4573async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
4574 init_test(cx, |_| {});
4575
4576 let mut cx = EditorTestContext::new(cx).await;
4577
4578 let html_language = Arc::new(
4579 Language::new(
4580 LanguageConfig {
4581 name: "HTML".into(),
4582 brackets: BracketPairConfig {
4583 pairs: vec![
4584 BracketPair {
4585 start: "<".into(),
4586 end: ">".into(),
4587 close: true,
4588 ..Default::default()
4589 },
4590 BracketPair {
4591 start: "{".into(),
4592 end: "}".into(),
4593 close: true,
4594 ..Default::default()
4595 },
4596 BracketPair {
4597 start: "(".into(),
4598 end: ")".into(),
4599 close: true,
4600 ..Default::default()
4601 },
4602 ],
4603 ..Default::default()
4604 },
4605 autoclose_before: "})]>".into(),
4606 ..Default::default()
4607 },
4608 Some(tree_sitter_html::language()),
4609 )
4610 .with_injection_query(
4611 r#"
4612 (script_element
4613 (raw_text) @content
4614 (#set! "language" "javascript"))
4615 "#,
4616 )
4617 .unwrap(),
4618 );
4619
4620 let javascript_language = Arc::new(Language::new(
4621 LanguageConfig {
4622 name: "JavaScript".into(),
4623 brackets: BracketPairConfig {
4624 pairs: vec![
4625 BracketPair {
4626 start: "/*".into(),
4627 end: " */".into(),
4628 close: true,
4629 ..Default::default()
4630 },
4631 BracketPair {
4632 start: "{".into(),
4633 end: "}".into(),
4634 close: true,
4635 ..Default::default()
4636 },
4637 BracketPair {
4638 start: "(".into(),
4639 end: ")".into(),
4640 close: true,
4641 ..Default::default()
4642 },
4643 ],
4644 ..Default::default()
4645 },
4646 autoclose_before: "})]>".into(),
4647 ..Default::default()
4648 },
4649 Some(tree_sitter_typescript::language_tsx()),
4650 ));
4651
4652 let registry = Arc::new(LanguageRegistry::test());
4653 registry.add(html_language.clone());
4654 registry.add(javascript_language.clone());
4655
4656 cx.update_buffer(|buffer, cx| {
4657 buffer.set_language_registry(registry);
4658 buffer.set_language(Some(html_language), cx);
4659 });
4660
4661 cx.set_state(
4662 &r#"
4663 <body>ˇ
4664 <script>
4665 var x = 1;ˇ
4666 </script>
4667 </body>ˇ
4668 "#
4669 .unindent(),
4670 );
4671
4672 // Precondition: different languages are active at different locations.
4673 cx.update_editor(|editor, cx| {
4674 let snapshot = editor.snapshot(cx);
4675 let cursors = editor.selections.ranges::<usize>(cx);
4676 let languages = cursors
4677 .iter()
4678 .map(|c| snapshot.language_at(c.start).unwrap().name())
4679 .collect::<Vec<_>>();
4680 assert_eq!(
4681 languages,
4682 &["HTML".into(), "JavaScript".into(), "HTML".into()]
4683 );
4684 });
4685
4686 // Angle brackets autoclose in HTML, but not JavaScript.
4687 cx.update_editor(|editor, cx| {
4688 editor.handle_input("<", cx);
4689 editor.handle_input("a", cx);
4690 });
4691 cx.assert_editor_state(
4692 &r#"
4693 <body><aˇ>
4694 <script>
4695 var x = 1;<aˇ
4696 </script>
4697 </body><aˇ>
4698 "#
4699 .unindent(),
4700 );
4701
4702 // Curly braces and parens autoclose in both HTML and JavaScript.
4703 cx.update_editor(|editor, cx| {
4704 editor.handle_input(" b=", cx);
4705 editor.handle_input("{", cx);
4706 editor.handle_input("c", cx);
4707 editor.handle_input("(", cx);
4708 });
4709 cx.assert_editor_state(
4710 &r#"
4711 <body><a b={c(ˇ)}>
4712 <script>
4713 var x = 1;<a b={c(ˇ)}
4714 </script>
4715 </body><a b={c(ˇ)}>
4716 "#
4717 .unindent(),
4718 );
4719
4720 // Brackets that were already autoclosed are skipped.
4721 cx.update_editor(|editor, cx| {
4722 editor.handle_input(")", cx);
4723 editor.handle_input("d", cx);
4724 editor.handle_input("}", cx);
4725 });
4726 cx.assert_editor_state(
4727 &r#"
4728 <body><a b={c()d}ˇ>
4729 <script>
4730 var x = 1;<a b={c()d}ˇ
4731 </script>
4732 </body><a b={c()d}ˇ>
4733 "#
4734 .unindent(),
4735 );
4736 cx.update_editor(|editor, cx| {
4737 editor.handle_input(">", cx);
4738 });
4739 cx.assert_editor_state(
4740 &r#"
4741 <body><a b={c()d}>ˇ
4742 <script>
4743 var x = 1;<a b={c()d}>ˇ
4744 </script>
4745 </body><a b={c()d}>ˇ
4746 "#
4747 .unindent(),
4748 );
4749
4750 // Reset
4751 cx.set_state(
4752 &r#"
4753 <body>ˇ
4754 <script>
4755 var x = 1;ˇ
4756 </script>
4757 </body>ˇ
4758 "#
4759 .unindent(),
4760 );
4761
4762 cx.update_editor(|editor, cx| {
4763 editor.handle_input("<", cx);
4764 });
4765 cx.assert_editor_state(
4766 &r#"
4767 <body><ˇ>
4768 <script>
4769 var x = 1;<ˇ
4770 </script>
4771 </body><ˇ>
4772 "#
4773 .unindent(),
4774 );
4775
4776 // When backspacing, the closing angle brackets are removed.
4777 cx.update_editor(|editor, cx| {
4778 editor.backspace(&Backspace, cx);
4779 });
4780 cx.assert_editor_state(
4781 &r#"
4782 <body>ˇ
4783 <script>
4784 var x = 1;ˇ
4785 </script>
4786 </body>ˇ
4787 "#
4788 .unindent(),
4789 );
4790
4791 // Block comments autoclose in JavaScript, but not HTML.
4792 cx.update_editor(|editor, cx| {
4793 editor.handle_input("/", cx);
4794 editor.handle_input("*", cx);
4795 });
4796 cx.assert_editor_state(
4797 &r#"
4798 <body>/*ˇ
4799 <script>
4800 var x = 1;/*ˇ */
4801 </script>
4802 </body>/*ˇ
4803 "#
4804 .unindent(),
4805 );
4806}
4807
4808#[gpui::test]
4809async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
4810 init_test(cx, |_| {});
4811
4812 let mut cx = EditorTestContext::new(cx).await;
4813
4814 let rust_language = Arc::new(
4815 Language::new(
4816 LanguageConfig {
4817 name: "Rust".into(),
4818 brackets: serde_json::from_value(json!([
4819 { "start": "{", "end": "}", "close": true, "newline": true },
4820 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
4821 ]))
4822 .unwrap(),
4823 autoclose_before: "})]>".into(),
4824 ..Default::default()
4825 },
4826 Some(tree_sitter_rust::language()),
4827 )
4828 .with_override_query("(string_literal) @string")
4829 .unwrap(),
4830 );
4831
4832 let registry = Arc::new(LanguageRegistry::test());
4833 registry.add(rust_language.clone());
4834
4835 cx.update_buffer(|buffer, cx| {
4836 buffer.set_language_registry(registry);
4837 buffer.set_language(Some(rust_language), cx);
4838 });
4839
4840 cx.set_state(
4841 &r#"
4842 let x = ˇ
4843 "#
4844 .unindent(),
4845 );
4846
4847 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
4848 cx.update_editor(|editor, cx| {
4849 editor.handle_input("\"", cx);
4850 });
4851 cx.assert_editor_state(
4852 &r#"
4853 let x = "ˇ"
4854 "#
4855 .unindent(),
4856 );
4857
4858 // Inserting another quotation mark. The cursor moves across the existing
4859 // automatically-inserted quotation mark.
4860 cx.update_editor(|editor, cx| {
4861 editor.handle_input("\"", cx);
4862 });
4863 cx.assert_editor_state(
4864 &r#"
4865 let x = ""ˇ
4866 "#
4867 .unindent(),
4868 );
4869
4870 // Reset
4871 cx.set_state(
4872 &r#"
4873 let x = ˇ
4874 "#
4875 .unindent(),
4876 );
4877
4878 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
4879 cx.update_editor(|editor, cx| {
4880 editor.handle_input("\"", cx);
4881 editor.handle_input(" ", cx);
4882 editor.move_left(&Default::default(), cx);
4883 editor.handle_input("\\", cx);
4884 editor.handle_input("\"", cx);
4885 });
4886 cx.assert_editor_state(
4887 &r#"
4888 let x = "\"ˇ "
4889 "#
4890 .unindent(),
4891 );
4892
4893 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
4894 // mark. Nothing is inserted.
4895 cx.update_editor(|editor, cx| {
4896 editor.move_right(&Default::default(), cx);
4897 editor.handle_input("\"", cx);
4898 });
4899 cx.assert_editor_state(
4900 &r#"
4901 let x = "\" "ˇ
4902 "#
4903 .unindent(),
4904 );
4905}
4906
4907#[gpui::test]
4908async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
4909 init_test(cx, |_| {});
4910
4911 let language = Arc::new(Language::new(
4912 LanguageConfig {
4913 brackets: BracketPairConfig {
4914 pairs: vec![
4915 BracketPair {
4916 start: "{".to_string(),
4917 end: "}".to_string(),
4918 close: true,
4919 newline: true,
4920 },
4921 BracketPair {
4922 start: "/* ".to_string(),
4923 end: "*/".to_string(),
4924 close: true,
4925 ..Default::default()
4926 },
4927 ],
4928 ..Default::default()
4929 },
4930 ..Default::default()
4931 },
4932 Some(tree_sitter_rust::language()),
4933 ));
4934
4935 let text = r#"
4936 a
4937 b
4938 c
4939 "#
4940 .unindent();
4941
4942 let buffer = cx.new_model(|cx| {
4943 Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
4944 .with_language(language, cx)
4945 });
4946 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4947 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4948 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4949 .await;
4950
4951 _ = view.update(cx, |view, cx| {
4952 view.change_selections(None, cx, |s| {
4953 s.select_display_ranges([
4954 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4955 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4956 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
4957 ])
4958 });
4959
4960 view.handle_input("{", cx);
4961 view.handle_input("{", cx);
4962 view.handle_input("{", cx);
4963 assert_eq!(
4964 view.text(cx),
4965 "
4966 {{{a}}}
4967 {{{b}}}
4968 {{{c}}}
4969 "
4970 .unindent()
4971 );
4972 assert_eq!(
4973 view.selections.display_ranges(cx),
4974 [
4975 DisplayPoint::new(0, 3)..DisplayPoint::new(0, 4),
4976 DisplayPoint::new(1, 3)..DisplayPoint::new(1, 4),
4977 DisplayPoint::new(2, 3)..DisplayPoint::new(2, 4)
4978 ]
4979 );
4980
4981 view.undo(&Undo, cx);
4982 view.undo(&Undo, cx);
4983 view.undo(&Undo, cx);
4984 assert_eq!(
4985 view.text(cx),
4986 "
4987 a
4988 b
4989 c
4990 "
4991 .unindent()
4992 );
4993 assert_eq!(
4994 view.selections.display_ranges(cx),
4995 [
4996 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
4997 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
4998 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
4999 ]
5000 );
5001
5002 // Ensure inserting the first character of a multi-byte bracket pair
5003 // doesn't surround the selections with the bracket.
5004 view.handle_input("/", cx);
5005 assert_eq!(
5006 view.text(cx),
5007 "
5008 /
5009 /
5010 /
5011 "
5012 .unindent()
5013 );
5014 assert_eq!(
5015 view.selections.display_ranges(cx),
5016 [
5017 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
5018 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
5019 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
5020 ]
5021 );
5022
5023 view.undo(&Undo, cx);
5024 assert_eq!(
5025 view.text(cx),
5026 "
5027 a
5028 b
5029 c
5030 "
5031 .unindent()
5032 );
5033 assert_eq!(
5034 view.selections.display_ranges(cx),
5035 [
5036 DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
5037 DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
5038 DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1)
5039 ]
5040 );
5041
5042 // Ensure inserting the last character of a multi-byte bracket pair
5043 // doesn't surround the selections with the bracket.
5044 view.handle_input("*", cx);
5045 assert_eq!(
5046 view.text(cx),
5047 "
5048 *
5049 *
5050 *
5051 "
5052 .unindent()
5053 );
5054 assert_eq!(
5055 view.selections.display_ranges(cx),
5056 [
5057 DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
5058 DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
5059 DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1)
5060 ]
5061 );
5062 });
5063}
5064
5065#[gpui::test]
5066async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
5067 init_test(cx, |_| {});
5068
5069 let language = Arc::new(Language::new(
5070 LanguageConfig {
5071 brackets: BracketPairConfig {
5072 pairs: vec![BracketPair {
5073 start: "{".to_string(),
5074 end: "}".to_string(),
5075 close: true,
5076 newline: true,
5077 }],
5078 ..Default::default()
5079 },
5080 autoclose_before: "}".to_string(),
5081 ..Default::default()
5082 },
5083 Some(tree_sitter_rust::language()),
5084 ));
5085
5086 let text = r#"
5087 a
5088 b
5089 c
5090 "#
5091 .unindent();
5092
5093 let buffer = cx.new_model(|cx| {
5094 Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
5095 .with_language(language, cx)
5096 });
5097 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5098 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5099 editor
5100 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5101 .await;
5102
5103 _ = editor.update(cx, |editor, cx| {
5104 editor.change_selections(None, cx, |s| {
5105 s.select_ranges([
5106 Point::new(0, 1)..Point::new(0, 1),
5107 Point::new(1, 1)..Point::new(1, 1),
5108 Point::new(2, 1)..Point::new(2, 1),
5109 ])
5110 });
5111
5112 editor.handle_input("{", cx);
5113 editor.handle_input("{", cx);
5114 editor.handle_input("_", cx);
5115 assert_eq!(
5116 editor.text(cx),
5117 "
5118 a{{_}}
5119 b{{_}}
5120 c{{_}}
5121 "
5122 .unindent()
5123 );
5124 assert_eq!(
5125 editor.selections.ranges::<Point>(cx),
5126 [
5127 Point::new(0, 4)..Point::new(0, 4),
5128 Point::new(1, 4)..Point::new(1, 4),
5129 Point::new(2, 4)..Point::new(2, 4)
5130 ]
5131 );
5132
5133 editor.backspace(&Default::default(), cx);
5134 editor.backspace(&Default::default(), cx);
5135 assert_eq!(
5136 editor.text(cx),
5137 "
5138 a{}
5139 b{}
5140 c{}
5141 "
5142 .unindent()
5143 );
5144 assert_eq!(
5145 editor.selections.ranges::<Point>(cx),
5146 [
5147 Point::new(0, 2)..Point::new(0, 2),
5148 Point::new(1, 2)..Point::new(1, 2),
5149 Point::new(2, 2)..Point::new(2, 2)
5150 ]
5151 );
5152
5153 editor.delete_to_previous_word_start(&Default::default(), cx);
5154 assert_eq!(
5155 editor.text(cx),
5156 "
5157 a
5158 b
5159 c
5160 "
5161 .unindent()
5162 );
5163 assert_eq!(
5164 editor.selections.ranges::<Point>(cx),
5165 [
5166 Point::new(0, 1)..Point::new(0, 1),
5167 Point::new(1, 1)..Point::new(1, 1),
5168 Point::new(2, 1)..Point::new(2, 1)
5169 ]
5170 );
5171 });
5172}
5173
5174#[gpui::test]
5175async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
5176 init_test(cx, |_| {});
5177
5178 let language = Arc::new(Language::new(
5179 LanguageConfig::default(),
5180 Some(tree_sitter_rust::language()),
5181 ));
5182
5183 let buffer = cx.new_model(|cx| {
5184 Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "")
5185 .with_language(language, cx)
5186 });
5187 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5188 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5189 editor
5190 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5191 .await;
5192
5193 _ = editor.update(cx, |editor, cx| {
5194 editor.set_auto_replace_emoji_shortcode(true);
5195
5196 editor.handle_input("Hello ", cx);
5197 editor.handle_input(":wave", cx);
5198 assert_eq!(editor.text(cx), "Hello :wave".unindent());
5199
5200 editor.handle_input(":", cx);
5201 assert_eq!(editor.text(cx), "Hello 👋".unindent());
5202
5203 editor.handle_input(" :smile", cx);
5204 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
5205
5206 editor.handle_input(":", cx);
5207 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
5208
5209 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
5210 editor.handle_input(":wave", cx);
5211 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
5212
5213 editor.handle_input(":", cx);
5214 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
5215
5216 editor.handle_input(":1", cx);
5217 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
5218
5219 editor.handle_input(":", cx);
5220 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
5221
5222 // Ensure shortcode does not get replaced when it is part of a word
5223 editor.handle_input(" Test:wave", cx);
5224 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
5225
5226 editor.handle_input(":", cx);
5227 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
5228
5229 editor.set_auto_replace_emoji_shortcode(false);
5230
5231 // Ensure shortcode does not get replaced when auto replace is off
5232 editor.handle_input(" :wave", cx);
5233 assert_eq!(
5234 editor.text(cx),
5235 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
5236 );
5237
5238 editor.handle_input(":", cx);
5239 assert_eq!(
5240 editor.text(cx),
5241 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
5242 );
5243 });
5244}
5245
5246#[gpui::test]
5247async fn test_snippets(cx: &mut gpui::TestAppContext) {
5248 init_test(cx, |_| {});
5249
5250 let (text, insertion_ranges) = marked_text_ranges(
5251 indoc! {"
5252 a.ˇ b
5253 a.ˇ b
5254 a.ˇ b
5255 "},
5256 false,
5257 );
5258
5259 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
5260 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5261
5262 _ = editor.update(cx, |editor, cx| {
5263 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
5264
5265 editor
5266 .insert_snippet(&insertion_ranges, snippet, cx)
5267 .unwrap();
5268
5269 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
5270 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
5271 assert_eq!(editor.text(cx), expected_text);
5272 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
5273 }
5274
5275 assert(
5276 editor,
5277 cx,
5278 indoc! {"
5279 a.f(«one», two, «three») b
5280 a.f(«one», two, «three») b
5281 a.f(«one», two, «three») b
5282 "},
5283 );
5284
5285 // Can't move earlier than the first tab stop
5286 assert!(!editor.move_to_prev_snippet_tabstop(cx));
5287 assert(
5288 editor,
5289 cx,
5290 indoc! {"
5291 a.f(«one», two, «three») b
5292 a.f(«one», two, «three») b
5293 a.f(«one», two, «three») b
5294 "},
5295 );
5296
5297 assert!(editor.move_to_next_snippet_tabstop(cx));
5298 assert(
5299 editor,
5300 cx,
5301 indoc! {"
5302 a.f(one, «two», three) b
5303 a.f(one, «two», three) b
5304 a.f(one, «two», three) b
5305 "},
5306 );
5307
5308 editor.move_to_prev_snippet_tabstop(cx);
5309 assert(
5310 editor,
5311 cx,
5312 indoc! {"
5313 a.f(«one», two, «three») b
5314 a.f(«one», two, «three») b
5315 a.f(«one», two, «three») b
5316 "},
5317 );
5318
5319 assert!(editor.move_to_next_snippet_tabstop(cx));
5320 assert(
5321 editor,
5322 cx,
5323 indoc! {"
5324 a.f(one, «two», three) b
5325 a.f(one, «two», three) b
5326 a.f(one, «two», three) b
5327 "},
5328 );
5329 assert!(editor.move_to_next_snippet_tabstop(cx));
5330 assert(
5331 editor,
5332 cx,
5333 indoc! {"
5334 a.f(one, two, three)ˇ b
5335 a.f(one, two, three)ˇ b
5336 a.f(one, two, three)ˇ b
5337 "},
5338 );
5339
5340 // As soon as the last tab stop is reached, snippet state is gone
5341 editor.move_to_prev_snippet_tabstop(cx);
5342 assert(
5343 editor,
5344 cx,
5345 indoc! {"
5346 a.f(one, two, three)ˇ b
5347 a.f(one, two, three)ˇ b
5348 a.f(one, two, three)ˇ b
5349 "},
5350 );
5351 });
5352}
5353
5354#[gpui::test]
5355async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
5356 init_test(cx, |_| {});
5357
5358 let fs = FakeFs::new(cx.executor());
5359 fs.insert_file("/file.rs", Default::default()).await;
5360
5361 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5362
5363 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
5364 language_registry.add(rust_lang());
5365 let mut fake_servers = language_registry.register_fake_lsp_adapter(
5366 "Rust",
5367 FakeLspAdapter {
5368 capabilities: lsp::ServerCapabilities {
5369 document_formatting_provider: Some(lsp::OneOf::Left(true)),
5370 ..Default::default()
5371 },
5372 ..Default::default()
5373 },
5374 );
5375
5376 let buffer = project
5377 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
5378 .await
5379 .unwrap();
5380
5381 cx.executor().start_waiting();
5382 let fake_server = fake_servers.next().await.unwrap();
5383
5384 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5385 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5386 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5387 assert!(cx.read(|cx| editor.is_dirty(cx)));
5388
5389 let save = editor
5390 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
5391 .unwrap();
5392 fake_server
5393 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5394 assert_eq!(
5395 params.text_document.uri,
5396 lsp::Url::from_file_path("/file.rs").unwrap()
5397 );
5398 assert_eq!(params.options.tab_size, 4);
5399 Ok(Some(vec![lsp::TextEdit::new(
5400 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
5401 ", ".to_string(),
5402 )]))
5403 })
5404 .next()
5405 .await;
5406 cx.executor().start_waiting();
5407 let _x = save.await;
5408
5409 assert_eq!(
5410 editor.update(cx, |editor, cx| editor.text(cx)),
5411 "one, two\nthree\n"
5412 );
5413 assert!(!cx.read(|cx| editor.is_dirty(cx)));
5414
5415 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5416 assert!(cx.read(|cx| editor.is_dirty(cx)));
5417
5418 // Ensure we can still save even if formatting hangs.
5419 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5420 assert_eq!(
5421 params.text_document.uri,
5422 lsp::Url::from_file_path("/file.rs").unwrap()
5423 );
5424 futures::future::pending::<()>().await;
5425 unreachable!()
5426 });
5427 let save = editor
5428 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
5429 .unwrap();
5430 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
5431 cx.executor().start_waiting();
5432 save.await;
5433 assert_eq!(
5434 editor.update(cx, |editor, cx| editor.text(cx)),
5435 "one\ntwo\nthree\n"
5436 );
5437 assert!(!cx.read(|cx| editor.is_dirty(cx)));
5438
5439 // Set rust language override and assert overridden tabsize is sent to language server
5440 update_test_language_settings(cx, |settings| {
5441 settings.languages.insert(
5442 "Rust".into(),
5443 LanguageSettingsContent {
5444 tab_size: NonZeroU32::new(8),
5445 ..Default::default()
5446 },
5447 );
5448 });
5449
5450 let save = editor
5451 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
5452 .unwrap();
5453 fake_server
5454 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5455 assert_eq!(
5456 params.text_document.uri,
5457 lsp::Url::from_file_path("/file.rs").unwrap()
5458 );
5459 assert_eq!(params.options.tab_size, 8);
5460 Ok(Some(vec![]))
5461 })
5462 .next()
5463 .await;
5464 cx.executor().start_waiting();
5465 save.await;
5466}
5467
5468#[gpui::test]
5469async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
5470 init_test(cx, |_| {});
5471
5472 let fs = FakeFs::new(cx.executor());
5473 fs.insert_file("/file.rs", Default::default()).await;
5474
5475 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5476
5477 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
5478 language_registry.add(rust_lang());
5479 let mut fake_servers = language_registry.register_fake_lsp_adapter(
5480 "Rust",
5481 FakeLspAdapter {
5482 capabilities: lsp::ServerCapabilities {
5483 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
5484 ..Default::default()
5485 },
5486 ..Default::default()
5487 },
5488 );
5489
5490 let buffer = project
5491 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
5492 .await
5493 .unwrap();
5494
5495 cx.executor().start_waiting();
5496 let fake_server = fake_servers.next().await.unwrap();
5497
5498 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5499 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5500 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5501 assert!(cx.read(|cx| editor.is_dirty(cx)));
5502
5503 let save = editor
5504 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
5505 .unwrap();
5506 fake_server
5507 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
5508 assert_eq!(
5509 params.text_document.uri,
5510 lsp::Url::from_file_path("/file.rs").unwrap()
5511 );
5512 assert_eq!(params.options.tab_size, 4);
5513 Ok(Some(vec![lsp::TextEdit::new(
5514 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
5515 ", ".to_string(),
5516 )]))
5517 })
5518 .next()
5519 .await;
5520 cx.executor().start_waiting();
5521 save.await;
5522 assert_eq!(
5523 editor.update(cx, |editor, cx| editor.text(cx)),
5524 "one, two\nthree\n"
5525 );
5526 assert!(!cx.read(|cx| editor.is_dirty(cx)));
5527
5528 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5529 assert!(cx.read(|cx| editor.is_dirty(cx)));
5530
5531 // Ensure we can still save even if formatting hangs.
5532 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
5533 move |params, _| async move {
5534 assert_eq!(
5535 params.text_document.uri,
5536 lsp::Url::from_file_path("/file.rs").unwrap()
5537 );
5538 futures::future::pending::<()>().await;
5539 unreachable!()
5540 },
5541 );
5542 let save = editor
5543 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
5544 .unwrap();
5545 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
5546 cx.executor().start_waiting();
5547 save.await;
5548 assert_eq!(
5549 editor.update(cx, |editor, cx| editor.text(cx)),
5550 "one\ntwo\nthree\n"
5551 );
5552 assert!(!cx.read(|cx| editor.is_dirty(cx)));
5553
5554 // Set rust language override and assert overridden tabsize is sent to language server
5555 update_test_language_settings(cx, |settings| {
5556 settings.languages.insert(
5557 "Rust".into(),
5558 LanguageSettingsContent {
5559 tab_size: NonZeroU32::new(8),
5560 ..Default::default()
5561 },
5562 );
5563 });
5564
5565 let save = editor
5566 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
5567 .unwrap();
5568 fake_server
5569 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
5570 assert_eq!(
5571 params.text_document.uri,
5572 lsp::Url::from_file_path("/file.rs").unwrap()
5573 );
5574 assert_eq!(params.options.tab_size, 8);
5575 Ok(Some(vec![]))
5576 })
5577 .next()
5578 .await;
5579 cx.executor().start_waiting();
5580 save.await;
5581}
5582
5583#[gpui::test]
5584async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
5585 init_test(cx, |settings| {
5586 settings.defaults.formatter = Some(language_settings::Formatter::LanguageServer)
5587 });
5588
5589 let fs = FakeFs::new(cx.executor());
5590 fs.insert_file("/file.rs", Default::default()).await;
5591
5592 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
5593
5594 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
5595 language_registry.add(Arc::new(Language::new(
5596 LanguageConfig {
5597 name: "Rust".into(),
5598 matcher: LanguageMatcher {
5599 path_suffixes: vec!["rs".to_string()],
5600 ..Default::default()
5601 },
5602 // Enable Prettier formatting for the same buffer, and ensure
5603 // LSP is called instead of Prettier.
5604 prettier_parser_name: Some("test_parser".to_string()),
5605 ..Default::default()
5606 },
5607 Some(tree_sitter_rust::language()),
5608 )));
5609 let mut fake_servers = language_registry.register_fake_lsp_adapter(
5610 "Rust",
5611 FakeLspAdapter {
5612 capabilities: lsp::ServerCapabilities {
5613 document_formatting_provider: Some(lsp::OneOf::Left(true)),
5614 ..Default::default()
5615 },
5616 ..Default::default()
5617 },
5618 );
5619
5620 let buffer = project
5621 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
5622 .await
5623 .unwrap();
5624
5625 cx.executor().start_waiting();
5626 let fake_server = fake_servers.next().await.unwrap();
5627
5628 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5629 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5630 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5631
5632 let format = editor
5633 .update(cx, |editor, cx| {
5634 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
5635 })
5636 .unwrap();
5637 fake_server
5638 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5639 assert_eq!(
5640 params.text_document.uri,
5641 lsp::Url::from_file_path("/file.rs").unwrap()
5642 );
5643 assert_eq!(params.options.tab_size, 4);
5644 Ok(Some(vec![lsp::TextEdit::new(
5645 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
5646 ", ".to_string(),
5647 )]))
5648 })
5649 .next()
5650 .await;
5651 cx.executor().start_waiting();
5652 format.await;
5653 assert_eq!(
5654 editor.update(cx, |editor, cx| editor.text(cx)),
5655 "one, two\nthree\n"
5656 );
5657
5658 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
5659 // Ensure we don't lock if formatting hangs.
5660 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
5661 assert_eq!(
5662 params.text_document.uri,
5663 lsp::Url::from_file_path("/file.rs").unwrap()
5664 );
5665 futures::future::pending::<()>().await;
5666 unreachable!()
5667 });
5668 let format = editor
5669 .update(cx, |editor, cx| {
5670 editor.perform_format(project, FormatTrigger::Manual, cx)
5671 })
5672 .unwrap();
5673 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
5674 cx.executor().start_waiting();
5675 format.await;
5676 assert_eq!(
5677 editor.update(cx, |editor, cx| editor.text(cx)),
5678 "one\ntwo\nthree\n"
5679 );
5680}
5681
5682#[gpui::test]
5683async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
5684 init_test(cx, |_| {});
5685
5686 let mut cx = EditorLspTestContext::new_rust(
5687 lsp::ServerCapabilities {
5688 document_formatting_provider: Some(lsp::OneOf::Left(true)),
5689 ..Default::default()
5690 },
5691 cx,
5692 )
5693 .await;
5694
5695 cx.set_state(indoc! {"
5696 one.twoˇ
5697 "});
5698
5699 // The format request takes a long time. When it completes, it inserts
5700 // a newline and an indent before the `.`
5701 cx.lsp
5702 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
5703 let executor = cx.background_executor().clone();
5704 async move {
5705 executor.timer(Duration::from_millis(100)).await;
5706 Ok(Some(vec![lsp::TextEdit {
5707 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
5708 new_text: "\n ".into(),
5709 }]))
5710 }
5711 });
5712
5713 // Submit a format request.
5714 let format_1 = cx
5715 .update_editor(|editor, cx| editor.format(&Format, cx))
5716 .unwrap();
5717 cx.executor().run_until_parked();
5718
5719 // Submit a second format request.
5720 let format_2 = cx
5721 .update_editor(|editor, cx| editor.format(&Format, cx))
5722 .unwrap();
5723 cx.executor().run_until_parked();
5724
5725 // Wait for both format requests to complete
5726 cx.executor().advance_clock(Duration::from_millis(200));
5727 cx.executor().start_waiting();
5728 format_1.await.unwrap();
5729 cx.executor().start_waiting();
5730 format_2.await.unwrap();
5731
5732 // The formatting edits only happens once.
5733 cx.assert_editor_state(indoc! {"
5734 one
5735 .twoˇ
5736 "});
5737}
5738
5739#[gpui::test]
5740async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
5741 init_test(cx, |settings| {
5742 settings.defaults.formatter = Some(language_settings::Formatter::Auto)
5743 });
5744
5745 let mut cx = EditorLspTestContext::new_rust(
5746 lsp::ServerCapabilities {
5747 document_formatting_provider: Some(lsp::OneOf::Left(true)),
5748 ..Default::default()
5749 },
5750 cx,
5751 )
5752 .await;
5753
5754 // Set up a buffer white some trailing whitespace and no trailing newline.
5755 cx.set_state(
5756 &[
5757 "one ", //
5758 "twoˇ", //
5759 "three ", //
5760 "four", //
5761 ]
5762 .join("\n"),
5763 );
5764
5765 // Submit a format request.
5766 let format = cx
5767 .update_editor(|editor, cx| editor.format(&Format, cx))
5768 .unwrap();
5769
5770 // Record which buffer changes have been sent to the language server
5771 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
5772 cx.lsp
5773 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
5774 let buffer_changes = buffer_changes.clone();
5775 move |params, _| {
5776 buffer_changes.lock().extend(
5777 params
5778 .content_changes
5779 .into_iter()
5780 .map(|e| (e.range.unwrap(), e.text)),
5781 );
5782 }
5783 });
5784
5785 // Handle formatting requests to the language server.
5786 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
5787 let buffer_changes = buffer_changes.clone();
5788 move |_, _| {
5789 // When formatting is requested, trailing whitespace has already been stripped,
5790 // and the trailing newline has already been added.
5791 assert_eq!(
5792 &buffer_changes.lock()[1..],
5793 &[
5794 (
5795 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
5796 "".into()
5797 ),
5798 (
5799 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
5800 "".into()
5801 ),
5802 (
5803 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
5804 "\n".into()
5805 ),
5806 ]
5807 );
5808
5809 // Insert blank lines between each line of the buffer.
5810 async move {
5811 Ok(Some(vec![
5812 lsp::TextEdit {
5813 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
5814 new_text: "\n".into(),
5815 },
5816 lsp::TextEdit {
5817 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
5818 new_text: "\n".into(),
5819 },
5820 ]))
5821 }
5822 }
5823 });
5824
5825 // After formatting the buffer, the trailing whitespace is stripped,
5826 // a newline is appended, and the edits provided by the language server
5827 // have been applied.
5828 format.await.unwrap();
5829 cx.assert_editor_state(
5830 &[
5831 "one", //
5832 "", //
5833 "twoˇ", //
5834 "", //
5835 "three", //
5836 "four", //
5837 "", //
5838 ]
5839 .join("\n"),
5840 );
5841
5842 // Undoing the formatting undoes the trailing whitespace removal, the
5843 // trailing newline, and the LSP edits.
5844 cx.update_buffer(|buffer, cx| buffer.undo(cx));
5845 cx.assert_editor_state(
5846 &[
5847 "one ", //
5848 "twoˇ", //
5849 "three ", //
5850 "four", //
5851 ]
5852 .join("\n"),
5853 );
5854}
5855
5856#[gpui::test]
5857async fn test_completion(cx: &mut gpui::TestAppContext) {
5858 init_test(cx, |_| {});
5859
5860 let mut cx = EditorLspTestContext::new_rust(
5861 lsp::ServerCapabilities {
5862 completion_provider: Some(lsp::CompletionOptions {
5863 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
5864 resolve_provider: Some(true),
5865 ..Default::default()
5866 }),
5867 ..Default::default()
5868 },
5869 cx,
5870 )
5871 .await;
5872
5873 cx.set_state(indoc! {"
5874 oneˇ
5875 two
5876 three
5877 "});
5878 cx.simulate_keystroke(".");
5879 handle_completion_request(
5880 &mut cx,
5881 indoc! {"
5882 one.|<>
5883 two
5884 three
5885 "},
5886 vec!["first_completion", "second_completion"],
5887 )
5888 .await;
5889 cx.condition(|editor, _| editor.context_menu_visible())
5890 .await;
5891 let apply_additional_edits = cx.update_editor(|editor, cx| {
5892 editor.context_menu_next(&Default::default(), cx);
5893 editor
5894 .confirm_completion(&ConfirmCompletion::default(), cx)
5895 .unwrap()
5896 });
5897 cx.assert_editor_state(indoc! {"
5898 one.second_completionˇ
5899 two
5900 three
5901 "});
5902
5903 handle_resolve_completion_request(
5904 &mut cx,
5905 Some(vec![
5906 (
5907 //This overlaps with the primary completion edit which is
5908 //misbehavior from the LSP spec, test that we filter it out
5909 indoc! {"
5910 one.second_ˇcompletion
5911 two
5912 threeˇ
5913 "},
5914 "overlapping additional edit",
5915 ),
5916 (
5917 indoc! {"
5918 one.second_completion
5919 two
5920 threeˇ
5921 "},
5922 "\nadditional edit",
5923 ),
5924 ]),
5925 )
5926 .await;
5927 apply_additional_edits.await.unwrap();
5928 cx.assert_editor_state(indoc! {"
5929 one.second_completionˇ
5930 two
5931 three
5932 additional edit
5933 "});
5934
5935 cx.set_state(indoc! {"
5936 one.second_completion
5937 twoˇ
5938 threeˇ
5939 additional edit
5940 "});
5941 cx.simulate_keystroke(" ");
5942 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5943 cx.simulate_keystroke("s");
5944 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
5945
5946 cx.assert_editor_state(indoc! {"
5947 one.second_completion
5948 two sˇ
5949 three sˇ
5950 additional edit
5951 "});
5952 handle_completion_request(
5953 &mut cx,
5954 indoc! {"
5955 one.second_completion
5956 two s
5957 three <s|>
5958 additional edit
5959 "},
5960 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
5961 )
5962 .await;
5963 cx.condition(|editor, _| editor.context_menu_visible())
5964 .await;
5965
5966 cx.simulate_keystroke("i");
5967
5968 handle_completion_request(
5969 &mut cx,
5970 indoc! {"
5971 one.second_completion
5972 two si
5973 three <si|>
5974 additional edit
5975 "},
5976 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
5977 )
5978 .await;
5979 cx.condition(|editor, _| editor.context_menu_visible())
5980 .await;
5981
5982 let apply_additional_edits = cx.update_editor(|editor, cx| {
5983 editor
5984 .confirm_completion(&ConfirmCompletion::default(), cx)
5985 .unwrap()
5986 });
5987 cx.assert_editor_state(indoc! {"
5988 one.second_completion
5989 two sixth_completionˇ
5990 three sixth_completionˇ
5991 additional edit
5992 "});
5993
5994 handle_resolve_completion_request(&mut cx, None).await;
5995 apply_additional_edits.await.unwrap();
5996
5997 _ = cx.update(|cx| {
5998 cx.update_global::<SettingsStore, _>(|settings, cx| {
5999 settings.update_user_settings::<EditorSettings>(cx, |settings| {
6000 settings.show_completions_on_input = Some(false);
6001 });
6002 })
6003 });
6004 cx.set_state("editorˇ");
6005 cx.simulate_keystroke(".");
6006 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
6007 cx.simulate_keystroke("c");
6008 cx.simulate_keystroke("l");
6009 cx.simulate_keystroke("o");
6010 cx.assert_editor_state("editor.cloˇ");
6011 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
6012 cx.update_editor(|editor, cx| {
6013 editor.show_completions(&ShowCompletions, cx);
6014 });
6015 handle_completion_request(&mut cx, "editor.<clo|>", vec!["close", "clobber"]).await;
6016 cx.condition(|editor, _| editor.context_menu_visible())
6017 .await;
6018 let apply_additional_edits = cx.update_editor(|editor, cx| {
6019 editor
6020 .confirm_completion(&ConfirmCompletion::default(), cx)
6021 .unwrap()
6022 });
6023 cx.assert_editor_state("editor.closeˇ");
6024 handle_resolve_completion_request(&mut cx, None).await;
6025 apply_additional_edits.await.unwrap();
6026}
6027
6028#[gpui::test]
6029async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
6030 init_test(cx, |_| {});
6031 let mut cx = EditorTestContext::new(cx).await;
6032 let language = Arc::new(Language::new(
6033 LanguageConfig {
6034 line_comments: vec!["// ".into()],
6035 ..Default::default()
6036 },
6037 Some(tree_sitter_rust::language()),
6038 ));
6039 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
6040
6041 // If multiple selections intersect a line, the line is only toggled once.
6042 cx.set_state(indoc! {"
6043 fn a() {
6044 «//b();
6045 ˇ»// «c();
6046 //ˇ» d();
6047 }
6048 "});
6049
6050 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6051
6052 cx.assert_editor_state(indoc! {"
6053 fn a() {
6054 «b();
6055 c();
6056 ˇ» d();
6057 }
6058 "});
6059
6060 // The comment prefix is inserted at the same column for every line in a
6061 // selection.
6062 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6063
6064 cx.assert_editor_state(indoc! {"
6065 fn a() {
6066 // «b();
6067 // c();
6068 ˇ»// d();
6069 }
6070 "});
6071
6072 // If a selection ends at the beginning of a line, that line is not toggled.
6073 cx.set_selections_state(indoc! {"
6074 fn a() {
6075 // b();
6076 «// c();
6077 ˇ» // d();
6078 }
6079 "});
6080
6081 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6082
6083 cx.assert_editor_state(indoc! {"
6084 fn a() {
6085 // b();
6086 «c();
6087 ˇ» // d();
6088 }
6089 "});
6090
6091 // If a selection span a single line and is empty, the line is toggled.
6092 cx.set_state(indoc! {"
6093 fn a() {
6094 a();
6095 b();
6096 ˇ
6097 }
6098 "});
6099
6100 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6101
6102 cx.assert_editor_state(indoc! {"
6103 fn a() {
6104 a();
6105 b();
6106 //•ˇ
6107 }
6108 "});
6109
6110 // If a selection span multiple lines, empty lines are not toggled.
6111 cx.set_state(indoc! {"
6112 fn a() {
6113 «a();
6114
6115 c();ˇ»
6116 }
6117 "});
6118
6119 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
6120
6121 cx.assert_editor_state(indoc! {"
6122 fn a() {
6123 // «a();
6124
6125 // c();ˇ»
6126 }
6127 "});
6128}
6129
6130#[gpui::test]
6131async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
6132 init_test(cx, |_| {});
6133
6134 let language = Arc::new(Language::new(
6135 LanguageConfig {
6136 line_comments: vec!["// ".into()],
6137 ..Default::default()
6138 },
6139 Some(tree_sitter_rust::language()),
6140 ));
6141
6142 let registry = Arc::new(LanguageRegistry::test());
6143 registry.add(language.clone());
6144
6145 let mut cx = EditorTestContext::new(cx).await;
6146 cx.update_buffer(|buffer, cx| {
6147 buffer.set_language_registry(registry);
6148 buffer.set_language(Some(language), cx);
6149 });
6150
6151 let toggle_comments = &ToggleComments {
6152 advance_downwards: true,
6153 };
6154
6155 // Single cursor on one line -> advance
6156 // Cursor moves horizontally 3 characters as well on non-blank line
6157 cx.set_state(indoc!(
6158 "fn a() {
6159 ˇdog();
6160 cat();
6161 }"
6162 ));
6163 cx.update_editor(|editor, cx| {
6164 editor.toggle_comments(toggle_comments, cx);
6165 });
6166 cx.assert_editor_state(indoc!(
6167 "fn a() {
6168 // dog();
6169 catˇ();
6170 }"
6171 ));
6172
6173 // Single selection on one line -> don't advance
6174 cx.set_state(indoc!(
6175 "fn a() {
6176 «dog()ˇ»;
6177 cat();
6178 }"
6179 ));
6180 cx.update_editor(|editor, cx| {
6181 editor.toggle_comments(toggle_comments, cx);
6182 });
6183 cx.assert_editor_state(indoc!(
6184 "fn a() {
6185 // «dog()ˇ»;
6186 cat();
6187 }"
6188 ));
6189
6190 // Multiple cursors on one line -> advance
6191 cx.set_state(indoc!(
6192 "fn a() {
6193 ˇdˇog();
6194 cat();
6195 }"
6196 ));
6197 cx.update_editor(|editor, cx| {
6198 editor.toggle_comments(toggle_comments, cx);
6199 });
6200 cx.assert_editor_state(indoc!(
6201 "fn a() {
6202 // dog();
6203 catˇ(ˇ);
6204 }"
6205 ));
6206
6207 // Multiple cursors on one line, with selection -> don't advance
6208 cx.set_state(indoc!(
6209 "fn a() {
6210 ˇdˇog«()ˇ»;
6211 cat();
6212 }"
6213 ));
6214 cx.update_editor(|editor, cx| {
6215 editor.toggle_comments(toggle_comments, cx);
6216 });
6217 cx.assert_editor_state(indoc!(
6218 "fn a() {
6219 // ˇdˇog«()ˇ»;
6220 cat();
6221 }"
6222 ));
6223
6224 // Single cursor on one line -> advance
6225 // Cursor moves to column 0 on blank line
6226 cx.set_state(indoc!(
6227 "fn a() {
6228 ˇdog();
6229
6230 cat();
6231 }"
6232 ));
6233 cx.update_editor(|editor, cx| {
6234 editor.toggle_comments(toggle_comments, cx);
6235 });
6236 cx.assert_editor_state(indoc!(
6237 "fn a() {
6238 // dog();
6239 ˇ
6240 cat();
6241 }"
6242 ));
6243
6244 // Single cursor on one line -> advance
6245 // Cursor starts and ends at column 0
6246 cx.set_state(indoc!(
6247 "fn a() {
6248 ˇ dog();
6249 cat();
6250 }"
6251 ));
6252 cx.update_editor(|editor, cx| {
6253 editor.toggle_comments(toggle_comments, cx);
6254 });
6255 cx.assert_editor_state(indoc!(
6256 "fn a() {
6257 // dog();
6258 ˇ cat();
6259 }"
6260 ));
6261}
6262
6263#[gpui::test]
6264async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
6265 init_test(cx, |_| {});
6266
6267 let mut cx = EditorTestContext::new(cx).await;
6268
6269 let html_language = Arc::new(
6270 Language::new(
6271 LanguageConfig {
6272 name: "HTML".into(),
6273 block_comment: Some(("<!-- ".into(), " -->".into())),
6274 ..Default::default()
6275 },
6276 Some(tree_sitter_html::language()),
6277 )
6278 .with_injection_query(
6279 r#"
6280 (script_element
6281 (raw_text) @content
6282 (#set! "language" "javascript"))
6283 "#,
6284 )
6285 .unwrap(),
6286 );
6287
6288 let javascript_language = Arc::new(Language::new(
6289 LanguageConfig {
6290 name: "JavaScript".into(),
6291 line_comments: vec!["// ".into()],
6292 ..Default::default()
6293 },
6294 Some(tree_sitter_typescript::language_tsx()),
6295 ));
6296
6297 let registry = Arc::new(LanguageRegistry::test());
6298 registry.add(html_language.clone());
6299 registry.add(javascript_language.clone());
6300
6301 cx.update_buffer(|buffer, cx| {
6302 buffer.set_language_registry(registry);
6303 buffer.set_language(Some(html_language), cx);
6304 });
6305
6306 // Toggle comments for empty selections
6307 cx.set_state(
6308 &r#"
6309 <p>A</p>ˇ
6310 <p>B</p>ˇ
6311 <p>C</p>ˇ
6312 "#
6313 .unindent(),
6314 );
6315 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
6316 cx.assert_editor_state(
6317 &r#"
6318 <!-- <p>A</p>ˇ -->
6319 <!-- <p>B</p>ˇ -->
6320 <!-- <p>C</p>ˇ -->
6321 "#
6322 .unindent(),
6323 );
6324 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
6325 cx.assert_editor_state(
6326 &r#"
6327 <p>A</p>ˇ
6328 <p>B</p>ˇ
6329 <p>C</p>ˇ
6330 "#
6331 .unindent(),
6332 );
6333
6334 // Toggle comments for mixture of empty and non-empty selections, where
6335 // multiple selections occupy a given line.
6336 cx.set_state(
6337 &r#"
6338 <p>A«</p>
6339 <p>ˇ»B</p>ˇ
6340 <p>C«</p>
6341 <p>ˇ»D</p>ˇ
6342 "#
6343 .unindent(),
6344 );
6345
6346 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
6347 cx.assert_editor_state(
6348 &r#"
6349 <!-- <p>A«</p>
6350 <p>ˇ»B</p>ˇ -->
6351 <!-- <p>C«</p>
6352 <p>ˇ»D</p>ˇ -->
6353 "#
6354 .unindent(),
6355 );
6356 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
6357 cx.assert_editor_state(
6358 &r#"
6359 <p>A«</p>
6360 <p>ˇ»B</p>ˇ
6361 <p>C«</p>
6362 <p>ˇ»D</p>ˇ
6363 "#
6364 .unindent(),
6365 );
6366
6367 // Toggle comments when different languages are active for different
6368 // selections.
6369 cx.set_state(
6370 &r#"
6371 ˇ<script>
6372 ˇvar x = new Y();
6373 ˇ</script>
6374 "#
6375 .unindent(),
6376 );
6377 cx.executor().run_until_parked();
6378 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
6379 cx.assert_editor_state(
6380 &r#"
6381 <!-- ˇ<script> -->
6382 // ˇvar x = new Y();
6383 <!-- ˇ</script> -->
6384 "#
6385 .unindent(),
6386 );
6387}
6388
6389#[gpui::test]
6390fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
6391 init_test(cx, |_| {});
6392
6393 let buffer = cx.new_model(|cx| {
6394 Buffer::new(
6395 0,
6396 BufferId::new(cx.entity_id().as_u64()).unwrap(),
6397 sample_text(3, 4, 'a'),
6398 )
6399 });
6400 let multibuffer = cx.new_model(|cx| {
6401 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
6402 multibuffer.push_excerpts(
6403 buffer.clone(),
6404 [
6405 ExcerptRange {
6406 context: Point::new(0, 0)..Point::new(0, 4),
6407 primary: None,
6408 },
6409 ExcerptRange {
6410 context: Point::new(1, 0)..Point::new(1, 4),
6411 primary: None,
6412 },
6413 ],
6414 cx,
6415 );
6416 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
6417 multibuffer
6418 });
6419
6420 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
6421 _ = view.update(cx, |view, cx| {
6422 assert_eq!(view.text(cx), "aaaa\nbbbb");
6423 view.change_selections(None, cx, |s| {
6424 s.select_ranges([
6425 Point::new(0, 0)..Point::new(0, 0),
6426 Point::new(1, 0)..Point::new(1, 0),
6427 ])
6428 });
6429
6430 view.handle_input("X", cx);
6431 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
6432 assert_eq!(
6433 view.selections.ranges(cx),
6434 [
6435 Point::new(0, 1)..Point::new(0, 1),
6436 Point::new(1, 1)..Point::new(1, 1),
6437 ]
6438 );
6439
6440 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
6441 view.change_selections(None, cx, |s| {
6442 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
6443 });
6444 view.backspace(&Default::default(), cx);
6445 assert_eq!(view.text(cx), "Xa\nbbb");
6446 assert_eq!(
6447 view.selections.ranges(cx),
6448 [Point::new(1, 0)..Point::new(1, 0)]
6449 );
6450
6451 view.change_selections(None, cx, |s| {
6452 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
6453 });
6454 view.backspace(&Default::default(), cx);
6455 assert_eq!(view.text(cx), "X\nbb");
6456 assert_eq!(
6457 view.selections.ranges(cx),
6458 [Point::new(0, 1)..Point::new(0, 1)]
6459 );
6460 });
6461}
6462
6463#[gpui::test]
6464fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
6465 init_test(cx, |_| {});
6466
6467 let markers = vec![('[', ']').into(), ('(', ')').into()];
6468 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
6469 indoc! {"
6470 [aaaa
6471 (bbbb]
6472 cccc)",
6473 },
6474 markers.clone(),
6475 );
6476 let excerpt_ranges = markers.into_iter().map(|marker| {
6477 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
6478 ExcerptRange {
6479 context,
6480 primary: None,
6481 }
6482 });
6483 let buffer = cx.new_model(|cx| {
6484 Buffer::new(
6485 0,
6486 BufferId::new(cx.entity_id().as_u64()).unwrap(),
6487 initial_text,
6488 )
6489 });
6490 let multibuffer = cx.new_model(|cx| {
6491 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
6492 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
6493 multibuffer
6494 });
6495
6496 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
6497 _ = view.update(cx, |view, cx| {
6498 let (expected_text, selection_ranges) = marked_text_ranges(
6499 indoc! {"
6500 aaaa
6501 bˇbbb
6502 bˇbbˇb
6503 cccc"
6504 },
6505 true,
6506 );
6507 assert_eq!(view.text(cx), expected_text);
6508 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
6509
6510 view.handle_input("X", cx);
6511
6512 let (expected_text, expected_selections) = marked_text_ranges(
6513 indoc! {"
6514 aaaa
6515 bXˇbbXb
6516 bXˇbbXˇb
6517 cccc"
6518 },
6519 false,
6520 );
6521 assert_eq!(view.text(cx), expected_text);
6522 assert_eq!(view.selections.ranges(cx), expected_selections);
6523
6524 view.newline(&Newline, cx);
6525 let (expected_text, expected_selections) = marked_text_ranges(
6526 indoc! {"
6527 aaaa
6528 bX
6529 ˇbbX
6530 b
6531 bX
6532 ˇbbX
6533 ˇb
6534 cccc"
6535 },
6536 false,
6537 );
6538 assert_eq!(view.text(cx), expected_text);
6539 assert_eq!(view.selections.ranges(cx), expected_selections);
6540 });
6541}
6542
6543#[gpui::test]
6544fn test_refresh_selections(cx: &mut TestAppContext) {
6545 init_test(cx, |_| {});
6546
6547 let buffer = cx.new_model(|cx| {
6548 Buffer::new(
6549 0,
6550 BufferId::new(cx.entity_id().as_u64()).unwrap(),
6551 sample_text(3, 4, 'a'),
6552 )
6553 });
6554 let mut excerpt1_id = None;
6555 let multibuffer = cx.new_model(|cx| {
6556 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
6557 excerpt1_id = multibuffer
6558 .push_excerpts(
6559 buffer.clone(),
6560 [
6561 ExcerptRange {
6562 context: Point::new(0, 0)..Point::new(1, 4),
6563 primary: None,
6564 },
6565 ExcerptRange {
6566 context: Point::new(1, 0)..Point::new(2, 4),
6567 primary: None,
6568 },
6569 ],
6570 cx,
6571 )
6572 .into_iter()
6573 .next();
6574 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
6575 multibuffer
6576 });
6577
6578 let editor = cx.add_window(|cx| {
6579 let mut editor = build_editor(multibuffer.clone(), cx);
6580 let snapshot = editor.snapshot(cx);
6581 editor.change_selections(None, cx, |s| {
6582 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
6583 });
6584 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
6585 assert_eq!(
6586 editor.selections.ranges(cx),
6587 [
6588 Point::new(1, 3)..Point::new(1, 3),
6589 Point::new(2, 1)..Point::new(2, 1),
6590 ]
6591 );
6592 editor
6593 });
6594
6595 // Refreshing selections is a no-op when excerpts haven't changed.
6596 _ = editor.update(cx, |editor, cx| {
6597 editor.change_selections(None, cx, |s| s.refresh());
6598 assert_eq!(
6599 editor.selections.ranges(cx),
6600 [
6601 Point::new(1, 3)..Point::new(1, 3),
6602 Point::new(2, 1)..Point::new(2, 1),
6603 ]
6604 );
6605 });
6606
6607 _ = multibuffer.update(cx, |multibuffer, cx| {
6608 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
6609 });
6610 _ = editor.update(cx, |editor, cx| {
6611 // Removing an excerpt causes the first selection to become degenerate.
6612 assert_eq!(
6613 editor.selections.ranges(cx),
6614 [
6615 Point::new(0, 0)..Point::new(0, 0),
6616 Point::new(0, 1)..Point::new(0, 1)
6617 ]
6618 );
6619
6620 // Refreshing selections will relocate the first selection to the original buffer
6621 // location.
6622 editor.change_selections(None, cx, |s| s.refresh());
6623 assert_eq!(
6624 editor.selections.ranges(cx),
6625 [
6626 Point::new(0, 1)..Point::new(0, 1),
6627 Point::new(0, 3)..Point::new(0, 3)
6628 ]
6629 );
6630 assert!(editor.selections.pending_anchor().is_some());
6631 });
6632}
6633
6634#[gpui::test]
6635fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
6636 init_test(cx, |_| {});
6637
6638 let buffer = cx.new_model(|cx| {
6639 Buffer::new(
6640 0,
6641 BufferId::new(cx.entity_id().as_u64()).unwrap(),
6642 sample_text(3, 4, 'a'),
6643 )
6644 });
6645 let mut excerpt1_id = None;
6646 let multibuffer = cx.new_model(|cx| {
6647 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
6648 excerpt1_id = multibuffer
6649 .push_excerpts(
6650 buffer.clone(),
6651 [
6652 ExcerptRange {
6653 context: Point::new(0, 0)..Point::new(1, 4),
6654 primary: None,
6655 },
6656 ExcerptRange {
6657 context: Point::new(1, 0)..Point::new(2, 4),
6658 primary: None,
6659 },
6660 ],
6661 cx,
6662 )
6663 .into_iter()
6664 .next();
6665 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
6666 multibuffer
6667 });
6668
6669 let editor = cx.add_window(|cx| {
6670 let mut editor = build_editor(multibuffer.clone(), cx);
6671 let snapshot = editor.snapshot(cx);
6672 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
6673 assert_eq!(
6674 editor.selections.ranges(cx),
6675 [Point::new(1, 3)..Point::new(1, 3)]
6676 );
6677 editor
6678 });
6679
6680 _ = multibuffer.update(cx, |multibuffer, cx| {
6681 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
6682 });
6683 _ = editor.update(cx, |editor, cx| {
6684 assert_eq!(
6685 editor.selections.ranges(cx),
6686 [Point::new(0, 0)..Point::new(0, 0)]
6687 );
6688
6689 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
6690 editor.change_selections(None, cx, |s| s.refresh());
6691 assert_eq!(
6692 editor.selections.ranges(cx),
6693 [Point::new(0, 3)..Point::new(0, 3)]
6694 );
6695 assert!(editor.selections.pending_anchor().is_some());
6696 });
6697}
6698
6699#[gpui::test]
6700async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
6701 init_test(cx, |_| {});
6702
6703 let language = Arc::new(
6704 Language::new(
6705 LanguageConfig {
6706 brackets: BracketPairConfig {
6707 pairs: vec![
6708 BracketPair {
6709 start: "{".to_string(),
6710 end: "}".to_string(),
6711 close: true,
6712 newline: true,
6713 },
6714 BracketPair {
6715 start: "/* ".to_string(),
6716 end: " */".to_string(),
6717 close: true,
6718 newline: true,
6719 },
6720 ],
6721 ..Default::default()
6722 },
6723 ..Default::default()
6724 },
6725 Some(tree_sitter_rust::language()),
6726 )
6727 .with_indents_query("")
6728 .unwrap(),
6729 );
6730
6731 let text = concat!(
6732 "{ }\n", //
6733 " x\n", //
6734 " /* */\n", //
6735 "x\n", //
6736 "{{} }\n", //
6737 );
6738
6739 let buffer = cx.new_model(|cx| {
6740 Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), text)
6741 .with_language(language, cx)
6742 });
6743 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6744 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6745 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6746 .await;
6747
6748 _ = view.update(cx, |view, cx| {
6749 view.change_selections(None, cx, |s| {
6750 s.select_display_ranges([
6751 DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
6752 DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
6753 DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4),
6754 ])
6755 });
6756 view.newline(&Newline, cx);
6757
6758 assert_eq!(
6759 view.buffer().read(cx).read(cx).text(),
6760 concat!(
6761 "{ \n", // Suppress rustfmt
6762 "\n", //
6763 "}\n", //
6764 " x\n", //
6765 " /* \n", //
6766 " \n", //
6767 " */\n", //
6768 "x\n", //
6769 "{{} \n", //
6770 "}\n", //
6771 )
6772 );
6773 });
6774}
6775
6776#[gpui::test]
6777fn test_highlighted_ranges(cx: &mut TestAppContext) {
6778 init_test(cx, |_| {});
6779
6780 let editor = cx.add_window(|cx| {
6781 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
6782 build_editor(buffer.clone(), cx)
6783 });
6784
6785 _ = editor.update(cx, |editor, cx| {
6786 struct Type1;
6787 struct Type2;
6788
6789 let buffer = editor.buffer.read(cx).snapshot(cx);
6790
6791 let anchor_range =
6792 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
6793
6794 editor.highlight_background::<Type1>(
6795 vec![
6796 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
6797 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
6798 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
6799 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
6800 ],
6801 |_| Hsla::red(),
6802 cx,
6803 );
6804 editor.highlight_background::<Type2>(
6805 vec![
6806 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
6807 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
6808 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
6809 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
6810 ],
6811 |_| Hsla::green(),
6812 cx,
6813 );
6814
6815 let snapshot = editor.snapshot(cx);
6816 let mut highlighted_ranges = editor.background_highlights_in_range(
6817 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
6818 &snapshot,
6819 cx.theme().colors(),
6820 );
6821 // Enforce a consistent ordering based on color without relying on the ordering of the
6822 // highlight's `TypeId` which is non-executor.
6823 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
6824 assert_eq!(
6825 highlighted_ranges,
6826 &[
6827 (
6828 DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4),
6829 Hsla::red(),
6830 ),
6831 (
6832 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
6833 Hsla::red(),
6834 ),
6835 (
6836 DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5),
6837 Hsla::green(),
6838 ),
6839 (
6840 DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6),
6841 Hsla::green(),
6842 ),
6843 ]
6844 );
6845 assert_eq!(
6846 editor.background_highlights_in_range(
6847 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
6848 &snapshot,
6849 cx.theme().colors(),
6850 ),
6851 &[(
6852 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
6853 Hsla::red(),
6854 )]
6855 );
6856 });
6857}
6858
6859#[gpui::test]
6860async fn test_following(cx: &mut gpui::TestAppContext) {
6861 init_test(cx, |_| {});
6862
6863 let fs = FakeFs::new(cx.executor());
6864 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6865
6866 let buffer = project.update(cx, |project, cx| {
6867 let buffer = project
6868 .create_buffer(&sample_text(16, 8, 'a'), None, cx)
6869 .unwrap();
6870 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
6871 });
6872 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
6873 let follower = cx.update(|cx| {
6874 cx.open_window(
6875 WindowOptions {
6876 bounds: Some(Bounds::from_corners(
6877 gpui::Point::new(0_f64.into(), 0_f64.into()),
6878 gpui::Point::new(10_f64.into(), 80_f64.into()),
6879 )),
6880 ..Default::default()
6881 },
6882 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
6883 )
6884 });
6885
6886 let is_still_following = Rc::new(RefCell::new(true));
6887 let follower_edit_event_count = Rc::new(RefCell::new(0));
6888 let pending_update = Rc::new(RefCell::new(None));
6889 _ = follower.update(cx, {
6890 let update = pending_update.clone();
6891 let is_still_following = is_still_following.clone();
6892 let follower_edit_event_count = follower_edit_event_count.clone();
6893 |_, cx| {
6894 cx.subscribe(
6895 &leader.root_view(cx).unwrap(),
6896 move |_, leader, event, cx| {
6897 leader
6898 .read(cx)
6899 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
6900 },
6901 )
6902 .detach();
6903
6904 cx.subscribe(
6905 &follower.root_view(cx).unwrap(),
6906 move |_, _, event: &EditorEvent, _cx| {
6907 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
6908 *is_still_following.borrow_mut() = false;
6909 }
6910
6911 if let EditorEvent::BufferEdited = event {
6912 *follower_edit_event_count.borrow_mut() += 1;
6913 }
6914 },
6915 )
6916 .detach();
6917 }
6918 });
6919
6920 // Update the selections only
6921 _ = leader.update(cx, |leader, cx| {
6922 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
6923 });
6924 follower
6925 .update(cx, |follower, cx| {
6926 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6927 })
6928 .unwrap()
6929 .await
6930 .unwrap();
6931 _ = follower.update(cx, |follower, cx| {
6932 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
6933 });
6934 assert_eq!(*is_still_following.borrow(), true);
6935 assert_eq!(*follower_edit_event_count.borrow(), 0);
6936
6937 // Update the scroll position only
6938 _ = leader.update(cx, |leader, cx| {
6939 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
6940 });
6941 follower
6942 .update(cx, |follower, cx| {
6943 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6944 })
6945 .unwrap()
6946 .await
6947 .unwrap();
6948 assert_eq!(
6949 follower
6950 .update(cx, |follower, cx| follower.scroll_position(cx))
6951 .unwrap(),
6952 gpui::Point::new(1.5, 3.5)
6953 );
6954 assert_eq!(*is_still_following.borrow(), true);
6955 assert_eq!(*follower_edit_event_count.borrow(), 0);
6956
6957 // Update the selections and scroll position. The follower's scroll position is updated
6958 // via autoscroll, not via the leader's exact scroll position.
6959 _ = leader.update(cx, |leader, cx| {
6960 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
6961 leader.request_autoscroll(Autoscroll::newest(), cx);
6962 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
6963 });
6964 follower
6965 .update(cx, |follower, cx| {
6966 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6967 })
6968 .unwrap()
6969 .await
6970 .unwrap();
6971 _ = follower.update(cx, |follower, cx| {
6972 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
6973 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
6974 });
6975 assert_eq!(*is_still_following.borrow(), true);
6976
6977 // Creating a pending selection that precedes another selection
6978 _ = leader.update(cx, |leader, cx| {
6979 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
6980 leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx);
6981 });
6982 follower
6983 .update(cx, |follower, cx| {
6984 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
6985 })
6986 .unwrap()
6987 .await
6988 .unwrap();
6989 _ = follower.update(cx, |follower, cx| {
6990 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
6991 });
6992 assert_eq!(*is_still_following.borrow(), true);
6993
6994 // Extend the pending selection so that it surrounds another selection
6995 _ = leader.update(cx, |leader, cx| {
6996 leader.extend_selection(DisplayPoint::new(0, 2), 1, cx);
6997 });
6998 follower
6999 .update(cx, |follower, cx| {
7000 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
7001 })
7002 .unwrap()
7003 .await
7004 .unwrap();
7005 _ = follower.update(cx, |follower, cx| {
7006 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
7007 });
7008
7009 // Scrolling locally breaks the follow
7010 _ = follower.update(cx, |follower, cx| {
7011 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
7012 follower.set_scroll_anchor(
7013 ScrollAnchor {
7014 anchor: top_anchor,
7015 offset: gpui::Point::new(0.0, 0.5),
7016 },
7017 cx,
7018 );
7019 });
7020 assert_eq!(*is_still_following.borrow(), false);
7021}
7022
7023#[gpui::test]
7024async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
7025 init_test(cx, |_| {});
7026
7027 let fs = FakeFs::new(cx.executor());
7028 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7029 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7030 let pane = workspace
7031 .update(cx, |workspace, _| workspace.active_pane().clone())
7032 .unwrap();
7033
7034 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7035
7036 let leader = pane.update(cx, |_, cx| {
7037 let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
7038 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
7039 });
7040
7041 // Start following the editor when it has no excerpts.
7042 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
7043 let follower_1 = cx
7044 .update_window(*workspace.deref(), |_, cx| {
7045 Editor::from_state_proto(
7046 pane.clone(),
7047 workspace.root_view(cx).unwrap(),
7048 ViewId {
7049 creator: Default::default(),
7050 id: 0,
7051 },
7052 &mut state_message,
7053 cx,
7054 )
7055 })
7056 .unwrap()
7057 .unwrap()
7058 .await
7059 .unwrap();
7060
7061 let update_message = Rc::new(RefCell::new(None));
7062 follower_1.update(cx, {
7063 let update = update_message.clone();
7064 |_, cx| {
7065 cx.subscribe(&leader, move |_, leader, event, cx| {
7066 leader
7067 .read(cx)
7068 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
7069 })
7070 .detach();
7071 }
7072 });
7073
7074 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
7075 (
7076 project
7077 .create_buffer("abc\ndef\nghi\njkl\n", None, cx)
7078 .unwrap(),
7079 project
7080 .create_buffer("mno\npqr\nstu\nvwx\n", None, cx)
7081 .unwrap(),
7082 )
7083 });
7084
7085 // Insert some excerpts.
7086 _ = leader.update(cx, |leader, cx| {
7087 leader.buffer.update(cx, |multibuffer, cx| {
7088 let excerpt_ids = multibuffer.push_excerpts(
7089 buffer_1.clone(),
7090 [
7091 ExcerptRange {
7092 context: 1..6,
7093 primary: None,
7094 },
7095 ExcerptRange {
7096 context: 12..15,
7097 primary: None,
7098 },
7099 ExcerptRange {
7100 context: 0..3,
7101 primary: None,
7102 },
7103 ],
7104 cx,
7105 );
7106 multibuffer.insert_excerpts_after(
7107 excerpt_ids[0],
7108 buffer_2.clone(),
7109 [
7110 ExcerptRange {
7111 context: 8..12,
7112 primary: None,
7113 },
7114 ExcerptRange {
7115 context: 0..6,
7116 primary: None,
7117 },
7118 ],
7119 cx,
7120 );
7121 });
7122 });
7123
7124 // Apply the update of adding the excerpts.
7125 follower_1
7126 .update(cx, |follower, cx| {
7127 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
7128 })
7129 .await
7130 .unwrap();
7131 assert_eq!(
7132 follower_1.update(cx, |editor, cx| editor.text(cx)),
7133 leader.update(cx, |editor, cx| editor.text(cx))
7134 );
7135 update_message.borrow_mut().take();
7136
7137 // Start following separately after it already has excerpts.
7138 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
7139 let follower_2 = cx
7140 .update_window(*workspace.deref(), |_, cx| {
7141 Editor::from_state_proto(
7142 pane.clone(),
7143 workspace.root_view(cx).unwrap().clone(),
7144 ViewId {
7145 creator: Default::default(),
7146 id: 0,
7147 },
7148 &mut state_message,
7149 cx,
7150 )
7151 })
7152 .unwrap()
7153 .unwrap()
7154 .await
7155 .unwrap();
7156 assert_eq!(
7157 follower_2.update(cx, |editor, cx| editor.text(cx)),
7158 leader.update(cx, |editor, cx| editor.text(cx))
7159 );
7160
7161 // Remove some excerpts.
7162 _ = leader.update(cx, |leader, cx| {
7163 leader.buffer.update(cx, |multibuffer, cx| {
7164 let excerpt_ids = multibuffer.excerpt_ids();
7165 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
7166 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
7167 });
7168 });
7169
7170 // Apply the update of removing the excerpts.
7171 follower_1
7172 .update(cx, |follower, cx| {
7173 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
7174 })
7175 .await
7176 .unwrap();
7177 follower_2
7178 .update(cx, |follower, cx| {
7179 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
7180 })
7181 .await
7182 .unwrap();
7183 update_message.borrow_mut().take();
7184 assert_eq!(
7185 follower_1.update(cx, |editor, cx| editor.text(cx)),
7186 leader.update(cx, |editor, cx| editor.text(cx))
7187 );
7188}
7189
7190#[gpui::test]
7191async fn go_to_prev_overlapping_diagnostic(
7192 executor: BackgroundExecutor,
7193 cx: &mut gpui::TestAppContext,
7194) {
7195 init_test(cx, |_| {});
7196
7197 let mut cx = EditorTestContext::new(cx).await;
7198 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
7199
7200 cx.set_state(indoc! {"
7201 ˇfn func(abc def: i32) -> u32 {
7202 }
7203 "});
7204
7205 _ = cx.update(|cx| {
7206 _ = project.update(cx, |project, cx| {
7207 project
7208 .update_diagnostics(
7209 LanguageServerId(0),
7210 lsp::PublishDiagnosticsParams {
7211 uri: lsp::Url::from_file_path("/root/file").unwrap(),
7212 version: None,
7213 diagnostics: vec![
7214 lsp::Diagnostic {
7215 range: lsp::Range::new(
7216 lsp::Position::new(0, 11),
7217 lsp::Position::new(0, 12),
7218 ),
7219 severity: Some(lsp::DiagnosticSeverity::ERROR),
7220 ..Default::default()
7221 },
7222 lsp::Diagnostic {
7223 range: lsp::Range::new(
7224 lsp::Position::new(0, 12),
7225 lsp::Position::new(0, 15),
7226 ),
7227 severity: Some(lsp::DiagnosticSeverity::ERROR),
7228 ..Default::default()
7229 },
7230 lsp::Diagnostic {
7231 range: lsp::Range::new(
7232 lsp::Position::new(0, 25),
7233 lsp::Position::new(0, 28),
7234 ),
7235 severity: Some(lsp::DiagnosticSeverity::ERROR),
7236 ..Default::default()
7237 },
7238 ],
7239 },
7240 &[],
7241 cx,
7242 )
7243 .unwrap()
7244 });
7245 });
7246
7247 executor.run_until_parked();
7248
7249 cx.update_editor(|editor, cx| {
7250 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
7251 });
7252
7253 cx.assert_editor_state(indoc! {"
7254 fn func(abc def: i32) -> ˇu32 {
7255 }
7256 "});
7257
7258 cx.update_editor(|editor, cx| {
7259 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
7260 });
7261
7262 cx.assert_editor_state(indoc! {"
7263 fn func(abc ˇdef: i32) -> u32 {
7264 }
7265 "});
7266
7267 cx.update_editor(|editor, cx| {
7268 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
7269 });
7270
7271 cx.assert_editor_state(indoc! {"
7272 fn func(abcˇ def: i32) -> u32 {
7273 }
7274 "});
7275
7276 cx.update_editor(|editor, cx| {
7277 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
7278 });
7279
7280 cx.assert_editor_state(indoc! {"
7281 fn func(abc def: i32) -> ˇu32 {
7282 }
7283 "});
7284}
7285
7286#[gpui::test]
7287async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
7288 init_test(cx, |_| {});
7289
7290 let mut cx = EditorTestContext::new(cx).await;
7291
7292 let diff_base = r#"
7293 use some::mod;
7294
7295 const A: u32 = 42;
7296
7297 fn main() {
7298 println!("hello");
7299
7300 println!("world");
7301 }
7302 "#
7303 .unindent();
7304
7305 // Edits are modified, removed, modified, added
7306 cx.set_state(
7307 &r#"
7308 use some::modified;
7309
7310 ˇ
7311 fn main() {
7312 println!("hello there");
7313
7314 println!("around the");
7315 println!("world");
7316 }
7317 "#
7318 .unindent(),
7319 );
7320
7321 cx.set_diff_base(Some(&diff_base));
7322 executor.run_until_parked();
7323
7324 cx.update_editor(|editor, cx| {
7325 //Wrap around the bottom of the buffer
7326 for _ in 0..3 {
7327 editor.go_to_hunk(&GoToHunk, cx);
7328 }
7329 });
7330
7331 cx.assert_editor_state(
7332 &r#"
7333 ˇuse some::modified;
7334
7335
7336 fn main() {
7337 println!("hello there");
7338
7339 println!("around the");
7340 println!("world");
7341 }
7342 "#
7343 .unindent(),
7344 );
7345
7346 cx.update_editor(|editor, cx| {
7347 //Wrap around the top of the buffer
7348 for _ in 0..2 {
7349 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
7350 }
7351 });
7352
7353 cx.assert_editor_state(
7354 &r#"
7355 use some::modified;
7356
7357
7358 fn main() {
7359 ˇ println!("hello there");
7360
7361 println!("around the");
7362 println!("world");
7363 }
7364 "#
7365 .unindent(),
7366 );
7367
7368 cx.update_editor(|editor, cx| {
7369 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
7370 });
7371
7372 cx.assert_editor_state(
7373 &r#"
7374 use some::modified;
7375
7376 ˇ
7377 fn main() {
7378 println!("hello there");
7379
7380 println!("around the");
7381 println!("world");
7382 }
7383 "#
7384 .unindent(),
7385 );
7386
7387 cx.update_editor(|editor, cx| {
7388 for _ in 0..3 {
7389 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
7390 }
7391 });
7392
7393 cx.assert_editor_state(
7394 &r#"
7395 use some::modified;
7396
7397
7398 fn main() {
7399 ˇ println!("hello there");
7400
7401 println!("around the");
7402 println!("world");
7403 }
7404 "#
7405 .unindent(),
7406 );
7407
7408 cx.update_editor(|editor, cx| {
7409 editor.fold(&Fold, cx);
7410
7411 //Make sure that the fold only gets one hunk
7412 for _ in 0..4 {
7413 editor.go_to_hunk(&GoToHunk, cx);
7414 }
7415 });
7416
7417 cx.assert_editor_state(
7418 &r#"
7419 ˇuse some::modified;
7420
7421
7422 fn main() {
7423 println!("hello there");
7424
7425 println!("around the");
7426 println!("world");
7427 }
7428 "#
7429 .unindent(),
7430 );
7431}
7432
7433#[test]
7434fn test_split_words() {
7435 fn split(text: &str) -> Vec<&str> {
7436 split_words(text).collect()
7437 }
7438
7439 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
7440 assert_eq!(split("hello_world"), &["hello_", "world"]);
7441 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
7442 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
7443 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
7444 assert_eq!(split("helloworld"), &["helloworld"]);
7445
7446 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
7447}
7448
7449#[gpui::test]
7450async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
7451 init_test(cx, |_| {});
7452
7453 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
7454 let mut assert = |before, after| {
7455 let _state_context = cx.set_state(before);
7456 cx.update_editor(|editor, cx| {
7457 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
7458 });
7459 cx.assert_editor_state(after);
7460 };
7461
7462 // Outside bracket jumps to outside of matching bracket
7463 assert("console.logˇ(var);", "console.log(var)ˇ;");
7464 assert("console.log(var)ˇ;", "console.logˇ(var);");
7465
7466 // Inside bracket jumps to inside of matching bracket
7467 assert("console.log(ˇvar);", "console.log(varˇ);");
7468 assert("console.log(varˇ);", "console.log(ˇvar);");
7469
7470 // When outside a bracket and inside, favor jumping to the inside bracket
7471 assert(
7472 "console.log('foo', [1, 2, 3]ˇ);",
7473 "console.log(ˇ'foo', [1, 2, 3]);",
7474 );
7475 assert(
7476 "console.log(ˇ'foo', [1, 2, 3]);",
7477 "console.log('foo', [1, 2, 3]ˇ);",
7478 );
7479
7480 // Bias forward if two options are equally likely
7481 assert(
7482 "let result = curried_fun()ˇ();",
7483 "let result = curried_fun()()ˇ;",
7484 );
7485
7486 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
7487 assert(
7488 indoc! {"
7489 function test() {
7490 console.log('test')ˇ
7491 }"},
7492 indoc! {"
7493 function test() {
7494 console.logˇ('test')
7495 }"},
7496 );
7497}
7498
7499#[gpui::test(iterations = 10)]
7500async fn test_copilot(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
7501 // flaky
7502 init_test(cx, |_| {});
7503
7504 let (copilot, copilot_lsp) = Copilot::fake(cx);
7505 _ = cx.update(|cx| Copilot::set_global(copilot, cx));
7506 let mut cx = EditorLspTestContext::new_rust(
7507 lsp::ServerCapabilities {
7508 completion_provider: Some(lsp::CompletionOptions {
7509 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7510 ..Default::default()
7511 }),
7512 ..Default::default()
7513 },
7514 cx,
7515 )
7516 .await;
7517
7518 // When inserting, ensure autocompletion is favored over Copilot suggestions.
7519 cx.set_state(indoc! {"
7520 oneˇ
7521 two
7522 three
7523 "});
7524 cx.simulate_keystroke(".");
7525 let _ = handle_completion_request(
7526 &mut cx,
7527 indoc! {"
7528 one.|<>
7529 two
7530 three
7531 "},
7532 vec!["completion_a", "completion_b"],
7533 );
7534 handle_copilot_completion_request(
7535 &copilot_lsp,
7536 vec![copilot::request::Completion {
7537 text: "one.copilot1".into(),
7538 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
7539 ..Default::default()
7540 }],
7541 vec![],
7542 );
7543 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7544 cx.update_editor(|editor, cx| {
7545 assert!(editor.context_menu_visible());
7546 assert!(!editor.has_active_copilot_suggestion(cx));
7547
7548 // Confirming a completion inserts it and hides the context menu, without showing
7549 // the copilot suggestion afterwards.
7550 editor
7551 .confirm_completion(&Default::default(), cx)
7552 .unwrap()
7553 .detach();
7554 assert!(!editor.context_menu_visible());
7555 assert!(!editor.has_active_copilot_suggestion(cx));
7556 assert_eq!(editor.text(cx), "one.completion_a\ntwo\nthree\n");
7557 assert_eq!(editor.display_text(cx), "one.completion_a\ntwo\nthree\n");
7558 });
7559
7560 // Ensure Copilot suggestions are shown right away if no autocompletion is available.
7561 cx.set_state(indoc! {"
7562 oneˇ
7563 two
7564 three
7565 "});
7566 cx.simulate_keystroke(".");
7567 let _ = handle_completion_request(
7568 &mut cx,
7569 indoc! {"
7570 one.|<>
7571 two
7572 three
7573 "},
7574 vec![],
7575 );
7576 handle_copilot_completion_request(
7577 &copilot_lsp,
7578 vec![copilot::request::Completion {
7579 text: "one.copilot1".into(),
7580 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
7581 ..Default::default()
7582 }],
7583 vec![],
7584 );
7585 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7586 cx.update_editor(|editor, cx| {
7587 assert!(!editor.context_menu_visible());
7588 assert!(editor.has_active_copilot_suggestion(cx));
7589 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
7590 assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
7591 });
7592
7593 // Reset editor, and ensure autocompletion is still favored over Copilot suggestions.
7594 cx.set_state(indoc! {"
7595 oneˇ
7596 two
7597 three
7598 "});
7599 cx.simulate_keystroke(".");
7600 let _ = handle_completion_request(
7601 &mut cx,
7602 indoc! {"
7603 one.|<>
7604 two
7605 three
7606 "},
7607 vec!["completion_a", "completion_b"],
7608 );
7609 handle_copilot_completion_request(
7610 &copilot_lsp,
7611 vec![copilot::request::Completion {
7612 text: "one.copilot1".into(),
7613 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
7614 ..Default::default()
7615 }],
7616 vec![],
7617 );
7618 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7619 cx.update_editor(|editor, cx| {
7620 assert!(editor.context_menu_visible());
7621 assert!(!editor.has_active_copilot_suggestion(cx));
7622
7623 // When hiding the context menu, the Copilot suggestion becomes visible.
7624 editor.hide_context_menu(cx);
7625 assert!(!editor.context_menu_visible());
7626 assert!(editor.has_active_copilot_suggestion(cx));
7627 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
7628 assert_eq!(editor.text(cx), "one.\ntwo\nthree\n");
7629 });
7630
7631 // Ensure existing completion is interpolated when inserting again.
7632 cx.simulate_keystroke("c");
7633 executor.run_until_parked();
7634 cx.update_editor(|editor, cx| {
7635 assert!(!editor.context_menu_visible());
7636 assert!(editor.has_active_copilot_suggestion(cx));
7637 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
7638 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7639 });
7640
7641 // After debouncing, new Copilot completions should be requested.
7642 handle_copilot_completion_request(
7643 &copilot_lsp,
7644 vec![copilot::request::Completion {
7645 text: "one.copilot2".into(),
7646 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 5)),
7647 ..Default::default()
7648 }],
7649 vec![],
7650 );
7651 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7652 cx.update_editor(|editor, cx| {
7653 assert!(!editor.context_menu_visible());
7654 assert!(editor.has_active_copilot_suggestion(cx));
7655 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7656 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7657
7658 // Canceling should remove the active Copilot suggestion.
7659 editor.cancel(&Default::default(), cx);
7660 assert!(!editor.has_active_copilot_suggestion(cx));
7661 assert_eq!(editor.display_text(cx), "one.c\ntwo\nthree\n");
7662 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7663
7664 // After canceling, tabbing shouldn't insert the previously shown suggestion.
7665 editor.tab(&Default::default(), cx);
7666 assert!(!editor.has_active_copilot_suggestion(cx));
7667 assert_eq!(editor.display_text(cx), "one.c \ntwo\nthree\n");
7668 assert_eq!(editor.text(cx), "one.c \ntwo\nthree\n");
7669
7670 // When undoing the previously active suggestion is shown again.
7671 editor.undo(&Default::default(), cx);
7672 assert!(editor.has_active_copilot_suggestion(cx));
7673 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7674 assert_eq!(editor.text(cx), "one.c\ntwo\nthree\n");
7675 });
7676
7677 // If an edit occurs outside of this editor, the suggestion is still correctly interpolated.
7678 cx.update_buffer(|buffer, cx| buffer.edit([(5..5, "o")], None, cx));
7679 cx.update_editor(|editor, cx| {
7680 assert!(editor.has_active_copilot_suggestion(cx));
7681 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7682 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
7683
7684 // Tabbing when there is an active suggestion inserts it.
7685 editor.tab(&Default::default(), cx);
7686 assert!(!editor.has_active_copilot_suggestion(cx));
7687 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7688 assert_eq!(editor.text(cx), "one.copilot2\ntwo\nthree\n");
7689
7690 // When undoing the previously active suggestion is shown again.
7691 editor.undo(&Default::default(), cx);
7692 assert!(editor.has_active_copilot_suggestion(cx));
7693 assert_eq!(editor.display_text(cx), "one.copilot2\ntwo\nthree\n");
7694 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
7695
7696 // Hide suggestion.
7697 editor.cancel(&Default::default(), cx);
7698 assert!(!editor.has_active_copilot_suggestion(cx));
7699 assert_eq!(editor.display_text(cx), "one.co\ntwo\nthree\n");
7700 assert_eq!(editor.text(cx), "one.co\ntwo\nthree\n");
7701 });
7702
7703 // If an edit occurs outside of this editor but no suggestion is being shown,
7704 // we won't make it visible.
7705 cx.update_buffer(|buffer, cx| buffer.edit([(6..6, "p")], None, cx));
7706 cx.update_editor(|editor, cx| {
7707 assert!(!editor.has_active_copilot_suggestion(cx));
7708 assert_eq!(editor.display_text(cx), "one.cop\ntwo\nthree\n");
7709 assert_eq!(editor.text(cx), "one.cop\ntwo\nthree\n");
7710 });
7711
7712 // Reset the editor to verify how suggestions behave when tabbing on leading indentation.
7713 cx.update_editor(|editor, cx| {
7714 editor.set_text("fn foo() {\n \n}", cx);
7715 editor.change_selections(None, cx, |s| {
7716 s.select_ranges([Point::new(1, 2)..Point::new(1, 2)])
7717 });
7718 });
7719 handle_copilot_completion_request(
7720 &copilot_lsp,
7721 vec![copilot::request::Completion {
7722 text: " let x = 4;".into(),
7723 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
7724 ..Default::default()
7725 }],
7726 vec![],
7727 );
7728
7729 cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
7730 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7731 cx.update_editor(|editor, cx| {
7732 assert!(editor.has_active_copilot_suggestion(cx));
7733 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
7734 assert_eq!(editor.text(cx), "fn foo() {\n \n}");
7735
7736 // Tabbing inside of leading whitespace inserts indentation without accepting the suggestion.
7737 editor.tab(&Default::default(), cx);
7738 assert!(editor.has_active_copilot_suggestion(cx));
7739 assert_eq!(editor.text(cx), "fn foo() {\n \n}");
7740 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
7741
7742 // Tabbing again accepts the suggestion.
7743 editor.tab(&Default::default(), cx);
7744 assert!(!editor.has_active_copilot_suggestion(cx));
7745 assert_eq!(editor.text(cx), "fn foo() {\n let x = 4;\n}");
7746 assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
7747 });
7748}
7749
7750#[gpui::test(iterations = 10)]
7751async fn test_accept_partial_copilot_suggestion(
7752 executor: BackgroundExecutor,
7753 cx: &mut gpui::TestAppContext,
7754) {
7755 // flaky
7756 init_test(cx, |_| {});
7757
7758 let (copilot, copilot_lsp) = Copilot::fake(cx);
7759 _ = cx.update(|cx| Copilot::set_global(copilot, cx));
7760 let mut cx = EditorLspTestContext::new_rust(
7761 lsp::ServerCapabilities {
7762 completion_provider: Some(lsp::CompletionOptions {
7763 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7764 ..Default::default()
7765 }),
7766 ..Default::default()
7767 },
7768 cx,
7769 )
7770 .await;
7771
7772 // Setup the editor with a completion request.
7773 cx.set_state(indoc! {"
7774 oneˇ
7775 two
7776 three
7777 "});
7778 cx.simulate_keystroke(".");
7779 let _ = handle_completion_request(
7780 &mut cx,
7781 indoc! {"
7782 one.|<>
7783 two
7784 three
7785 "},
7786 vec![],
7787 );
7788 handle_copilot_completion_request(
7789 &copilot_lsp,
7790 vec![copilot::request::Completion {
7791 text: "one.copilot1".into(),
7792 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
7793 ..Default::default()
7794 }],
7795 vec![],
7796 );
7797 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7798 cx.update_editor(|editor, cx| {
7799 assert!(editor.has_active_copilot_suggestion(cx));
7800
7801 // Accepting the first word of the suggestion should only accept the first word and still show the rest.
7802 editor.accept_partial_copilot_suggestion(&Default::default(), cx);
7803 assert!(editor.has_active_copilot_suggestion(cx));
7804 assert_eq!(editor.text(cx), "one.copilot\ntwo\nthree\n");
7805 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
7806
7807 // Accepting next word should accept the non-word and copilot suggestion should be gone
7808 editor.accept_partial_copilot_suggestion(&Default::default(), cx);
7809 assert!(!editor.has_active_copilot_suggestion(cx));
7810 assert_eq!(editor.text(cx), "one.copilot1\ntwo\nthree\n");
7811 assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n");
7812 });
7813
7814 // Reset the editor and check non-word and whitespace completion
7815 cx.set_state(indoc! {"
7816 oneˇ
7817 two
7818 three
7819 "});
7820 cx.simulate_keystroke(".");
7821 let _ = handle_completion_request(
7822 &mut cx,
7823 indoc! {"
7824 one.|<>
7825 two
7826 three
7827 "},
7828 vec![],
7829 );
7830 handle_copilot_completion_request(
7831 &copilot_lsp,
7832 vec![copilot::request::Completion {
7833 text: "one.123. copilot\n 456".into(),
7834 range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
7835 ..Default::default()
7836 }],
7837 vec![],
7838 );
7839 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7840 cx.update_editor(|editor, cx| {
7841 assert!(editor.has_active_copilot_suggestion(cx));
7842
7843 // Accepting the first word (non-word) of the suggestion should only accept the first word and still show the rest.
7844 editor.accept_partial_copilot_suggestion(&Default::default(), cx);
7845 assert!(editor.has_active_copilot_suggestion(cx));
7846 assert_eq!(editor.text(cx), "one.123. \ntwo\nthree\n");
7847 assert_eq!(
7848 editor.display_text(cx),
7849 "one.123. copilot\n 456\ntwo\nthree\n"
7850 );
7851
7852 // Accepting next word should accept the next word and copilot suggestion should still exist
7853 editor.accept_partial_copilot_suggestion(&Default::default(), cx);
7854 assert!(editor.has_active_copilot_suggestion(cx));
7855 assert_eq!(editor.text(cx), "one.123. copilot\ntwo\nthree\n");
7856 assert_eq!(
7857 editor.display_text(cx),
7858 "one.123. copilot\n 456\ntwo\nthree\n"
7859 );
7860
7861 // Accepting the whitespace should accept the non-word/whitespaces with newline and copilot suggestion should be gone
7862 editor.accept_partial_copilot_suggestion(&Default::default(), cx);
7863 assert!(!editor.has_active_copilot_suggestion(cx));
7864 assert_eq!(editor.text(cx), "one.123. copilot\n 456\ntwo\nthree\n");
7865 assert_eq!(
7866 editor.display_text(cx),
7867 "one.123. copilot\n 456\ntwo\nthree\n"
7868 );
7869 });
7870}
7871
7872#[gpui::test]
7873async fn test_copilot_completion_invalidation(
7874 executor: BackgroundExecutor,
7875 cx: &mut gpui::TestAppContext,
7876) {
7877 init_test(cx, |_| {});
7878
7879 let (copilot, copilot_lsp) = Copilot::fake(cx);
7880 _ = cx.update(|cx| Copilot::set_global(copilot, cx));
7881 let mut cx = EditorLspTestContext::new_rust(
7882 lsp::ServerCapabilities {
7883 completion_provider: Some(lsp::CompletionOptions {
7884 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7885 ..Default::default()
7886 }),
7887 ..Default::default()
7888 },
7889 cx,
7890 )
7891 .await;
7892
7893 cx.set_state(indoc! {"
7894 one
7895 twˇ
7896 three
7897 "});
7898
7899 handle_copilot_completion_request(
7900 &copilot_lsp,
7901 vec![copilot::request::Completion {
7902 text: "two.foo()".into(),
7903 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
7904 ..Default::default()
7905 }],
7906 vec![],
7907 );
7908 cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
7909 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7910 cx.update_editor(|editor, cx| {
7911 assert!(editor.has_active_copilot_suggestion(cx));
7912 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7913 assert_eq!(editor.text(cx), "one\ntw\nthree\n");
7914
7915 editor.backspace(&Default::default(), cx);
7916 assert!(editor.has_active_copilot_suggestion(cx));
7917 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7918 assert_eq!(editor.text(cx), "one\nt\nthree\n");
7919
7920 editor.backspace(&Default::default(), cx);
7921 assert!(editor.has_active_copilot_suggestion(cx));
7922 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7923 assert_eq!(editor.text(cx), "one\n\nthree\n");
7924
7925 // Deleting across the original suggestion range invalidates it.
7926 editor.backspace(&Default::default(), cx);
7927 assert!(!editor.has_active_copilot_suggestion(cx));
7928 assert_eq!(editor.display_text(cx), "one\nthree\n");
7929 assert_eq!(editor.text(cx), "one\nthree\n");
7930
7931 // Undoing the deletion restores the suggestion.
7932 editor.undo(&Default::default(), cx);
7933 assert!(editor.has_active_copilot_suggestion(cx));
7934 assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
7935 assert_eq!(editor.text(cx), "one\n\nthree\n");
7936 });
7937}
7938
7939#[gpui::test]
7940async fn test_copilot_multibuffer(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
7941 init_test(cx, |_| {});
7942
7943 let (copilot, copilot_lsp) = Copilot::fake(cx);
7944 _ = cx.update(|cx| Copilot::set_global(copilot, cx));
7945
7946 let buffer_1 = cx.new_model(|cx| {
7947 Buffer::new(
7948 0,
7949 BufferId::new(cx.entity_id().as_u64()).unwrap(),
7950 "a = 1\nb = 2\n",
7951 )
7952 });
7953 let buffer_2 = cx.new_model(|cx| {
7954 Buffer::new(
7955 0,
7956 BufferId::new(cx.entity_id().as_u64()).unwrap(),
7957 "c = 3\nd = 4\n",
7958 )
7959 });
7960 let multibuffer = cx.new_model(|cx| {
7961 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
7962 multibuffer.push_excerpts(
7963 buffer_1.clone(),
7964 [ExcerptRange {
7965 context: Point::new(0, 0)..Point::new(2, 0),
7966 primary: None,
7967 }],
7968 cx,
7969 );
7970 multibuffer.push_excerpts(
7971 buffer_2.clone(),
7972 [ExcerptRange {
7973 context: Point::new(0, 0)..Point::new(2, 0),
7974 primary: None,
7975 }],
7976 cx,
7977 );
7978 multibuffer
7979 });
7980 let editor = cx.add_window(|cx| build_editor(multibuffer, cx));
7981
7982 handle_copilot_completion_request(
7983 &copilot_lsp,
7984 vec![copilot::request::Completion {
7985 text: "b = 2 + a".into(),
7986 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 5)),
7987 ..Default::default()
7988 }],
7989 vec![],
7990 );
7991 _ = editor.update(cx, |editor, cx| {
7992 // Ensure copilot suggestions are shown for the first excerpt.
7993 editor.change_selections(None, cx, |s| {
7994 s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
7995 });
7996 editor.next_copilot_suggestion(&Default::default(), cx);
7997 });
7998 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
7999 _ = editor.update(cx, |editor, cx| {
8000 assert!(editor.has_active_copilot_suggestion(cx));
8001 assert_eq!(
8002 editor.display_text(cx),
8003 "\n\na = 1\nb = 2 + a\n\n\n\nc = 3\nd = 4\n"
8004 );
8005 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
8006 });
8007
8008 handle_copilot_completion_request(
8009 &copilot_lsp,
8010 vec![copilot::request::Completion {
8011 text: "d = 4 + c".into(),
8012 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 6)),
8013 ..Default::default()
8014 }],
8015 vec![],
8016 );
8017 _ = editor.update(cx, |editor, cx| {
8018 // Move to another excerpt, ensuring the suggestion gets cleared.
8019 editor.change_selections(None, cx, |s| {
8020 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
8021 });
8022 assert!(!editor.has_active_copilot_suggestion(cx));
8023 assert_eq!(
8024 editor.display_text(cx),
8025 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4\n"
8026 );
8027 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4\n");
8028
8029 // Type a character, ensuring we don't even try to interpolate the previous suggestion.
8030 editor.handle_input(" ", cx);
8031 assert!(!editor.has_active_copilot_suggestion(cx));
8032 assert_eq!(
8033 editor.display_text(cx),
8034 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 \n"
8035 );
8036 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
8037 });
8038
8039 // Ensure the new suggestion is displayed when the debounce timeout expires.
8040 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
8041 _ = editor.update(cx, |editor, cx| {
8042 assert!(editor.has_active_copilot_suggestion(cx));
8043 assert_eq!(
8044 editor.display_text(cx),
8045 "\n\na = 1\nb = 2\n\n\n\nc = 3\nd = 4 + c\n"
8046 );
8047 assert_eq!(editor.text(cx), "a = 1\nb = 2\n\nc = 3\nd = 4 \n");
8048 });
8049}
8050
8051#[gpui::test]
8052async fn test_copilot_disabled_globs(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
8053 init_test(cx, |settings| {
8054 settings
8055 .copilot
8056 .get_or_insert(Default::default())
8057 .disabled_globs = Some(vec![".env*".to_string()]);
8058 });
8059
8060 let (copilot, copilot_lsp) = Copilot::fake(cx);
8061 _ = cx.update(|cx| Copilot::set_global(copilot, cx));
8062
8063 let fs = FakeFs::new(cx.executor());
8064 fs.insert_tree(
8065 "/test",
8066 json!({
8067 ".env": "SECRET=something\n",
8068 "README.md": "hello\n"
8069 }),
8070 )
8071 .await;
8072 let project = Project::test(fs, ["/test".as_ref()], cx).await;
8073
8074 let private_buffer = project
8075 .update(cx, |project, cx| {
8076 project.open_local_buffer("/test/.env", cx)
8077 })
8078 .await
8079 .unwrap();
8080 let public_buffer = project
8081 .update(cx, |project, cx| {
8082 project.open_local_buffer("/test/README.md", cx)
8083 })
8084 .await
8085 .unwrap();
8086
8087 let multibuffer = cx.new_model(|cx| {
8088 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8089 multibuffer.push_excerpts(
8090 private_buffer.clone(),
8091 [ExcerptRange {
8092 context: Point::new(0, 0)..Point::new(1, 0),
8093 primary: None,
8094 }],
8095 cx,
8096 );
8097 multibuffer.push_excerpts(
8098 public_buffer.clone(),
8099 [ExcerptRange {
8100 context: Point::new(0, 0)..Point::new(1, 0),
8101 primary: None,
8102 }],
8103 cx,
8104 );
8105 multibuffer
8106 });
8107 let editor = cx.add_window(|cx| build_editor(multibuffer, cx));
8108
8109 let mut copilot_requests = copilot_lsp
8110 .handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| async move {
8111 Ok(copilot::request::GetCompletionsResult {
8112 completions: vec![copilot::request::Completion {
8113 text: "next line".into(),
8114 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
8115 ..Default::default()
8116 }],
8117 })
8118 });
8119
8120 _ = editor.update(cx, |editor, cx| {
8121 editor.change_selections(None, cx, |selections| {
8122 selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
8123 });
8124 editor.next_copilot_suggestion(&Default::default(), cx);
8125 });
8126
8127 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
8128 assert!(copilot_requests.try_next().is_err());
8129
8130 _ = editor.update(cx, |editor, cx| {
8131 editor.change_selections(None, cx, |s| {
8132 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
8133 });
8134 editor.next_copilot_suggestion(&Default::default(), cx);
8135 });
8136
8137 executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
8138 assert!(copilot_requests.try_next().is_ok());
8139}
8140
8141#[gpui::test]
8142async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
8143 init_test(cx, |_| {});
8144
8145 let fs = FakeFs::new(cx.executor());
8146 fs.insert_tree(
8147 "/a",
8148 json!({
8149 "main.rs": "fn main() { let a = 5; }",
8150 "other.rs": "// Test file",
8151 }),
8152 )
8153 .await;
8154 let project = Project::test(fs, ["/a".as_ref()], cx).await;
8155
8156 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8157 language_registry.add(Arc::new(Language::new(
8158 LanguageConfig {
8159 name: "Rust".into(),
8160 matcher: LanguageMatcher {
8161 path_suffixes: vec!["rs".to_string()],
8162 ..Default::default()
8163 },
8164 brackets: BracketPairConfig {
8165 pairs: vec![BracketPair {
8166 start: "{".to_string(),
8167 end: "}".to_string(),
8168 close: true,
8169 newline: true,
8170 }],
8171 disabled_scopes_by_bracket_ix: Vec::new(),
8172 },
8173 ..Default::default()
8174 },
8175 Some(tree_sitter_rust::language()),
8176 )));
8177 let mut fake_servers = language_registry.register_fake_lsp_adapter(
8178 "Rust",
8179 FakeLspAdapter {
8180 capabilities: lsp::ServerCapabilities {
8181 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
8182 first_trigger_character: "{".to_string(),
8183 more_trigger_character: None,
8184 }),
8185 ..Default::default()
8186 },
8187 ..Default::default()
8188 },
8189 );
8190
8191 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8192
8193 let cx = &mut VisualTestContext::from_window(*workspace, cx);
8194
8195 let worktree_id = workspace
8196 .update(cx, |workspace, cx| {
8197 workspace.project().update(cx, |project, cx| {
8198 project.worktrees().next().unwrap().read(cx).id()
8199 })
8200 })
8201 .unwrap();
8202
8203 let buffer = project
8204 .update(cx, |project, cx| {
8205 project.open_local_buffer("/a/main.rs", cx)
8206 })
8207 .await
8208 .unwrap();
8209 cx.executor().run_until_parked();
8210 cx.executor().start_waiting();
8211 let fake_server = fake_servers.next().await.unwrap();
8212 let editor_handle = workspace
8213 .update(cx, |workspace, cx| {
8214 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
8215 })
8216 .unwrap()
8217 .await
8218 .unwrap()
8219 .downcast::<Editor>()
8220 .unwrap();
8221
8222 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
8223 assert_eq!(
8224 params.text_document_position.text_document.uri,
8225 lsp::Url::from_file_path("/a/main.rs").unwrap(),
8226 );
8227 assert_eq!(
8228 params.text_document_position.position,
8229 lsp::Position::new(0, 21),
8230 );
8231
8232 Ok(Some(vec![lsp::TextEdit {
8233 new_text: "]".to_string(),
8234 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
8235 }]))
8236 });
8237
8238 editor_handle.update(cx, |editor, cx| {
8239 editor.focus(cx);
8240 editor.change_selections(None, cx, |s| {
8241 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
8242 });
8243 editor.handle_input("{", cx);
8244 });
8245
8246 cx.executor().run_until_parked();
8247
8248 _ = buffer.update(cx, |buffer, _| {
8249 assert_eq!(
8250 buffer.text(),
8251 "fn main() { let a = {5}; }",
8252 "No extra braces from on type formatting should appear in the buffer"
8253 )
8254 });
8255}
8256
8257#[gpui::test]
8258async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
8259 init_test(cx, |_| {});
8260
8261 let fs = FakeFs::new(cx.executor());
8262 fs.insert_tree(
8263 "/a",
8264 json!({
8265 "main.rs": "fn main() { let a = 5; }",
8266 "other.rs": "// Test file",
8267 }),
8268 )
8269 .await;
8270
8271 let project = Project::test(fs, ["/a".as_ref()], cx).await;
8272
8273 let server_restarts = Arc::new(AtomicUsize::new(0));
8274 let closure_restarts = Arc::clone(&server_restarts);
8275 let language_server_name = "test language server";
8276 let language_name: Arc<str> = "Rust".into();
8277
8278 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8279 language_registry.add(Arc::new(Language::new(
8280 LanguageConfig {
8281 name: Arc::clone(&language_name),
8282 matcher: LanguageMatcher {
8283 path_suffixes: vec!["rs".to_string()],
8284 ..Default::default()
8285 },
8286 ..Default::default()
8287 },
8288 Some(tree_sitter_rust::language()),
8289 )));
8290 let mut fake_servers = language_registry.register_fake_lsp_adapter(
8291 "Rust",
8292 FakeLspAdapter {
8293 name: language_server_name,
8294 initialization_options: Some(json!({
8295 "testOptionValue": true
8296 })),
8297 initializer: Some(Box::new(move |fake_server| {
8298 let task_restarts = Arc::clone(&closure_restarts);
8299 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
8300 task_restarts.fetch_add(1, atomic::Ordering::Release);
8301 futures::future::ready(Ok(()))
8302 });
8303 })),
8304 ..Default::default()
8305 },
8306 );
8307
8308 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8309 let _buffer = project
8310 .update(cx, |project, cx| {
8311 project.open_local_buffer("/a/main.rs", cx)
8312 })
8313 .await
8314 .unwrap();
8315 let _fake_server = fake_servers.next().await.unwrap();
8316 update_test_language_settings(cx, |language_settings| {
8317 language_settings.languages.insert(
8318 Arc::clone(&language_name),
8319 LanguageSettingsContent {
8320 tab_size: NonZeroU32::new(8),
8321 ..Default::default()
8322 },
8323 );
8324 });
8325 cx.executor().run_until_parked();
8326 assert_eq!(
8327 server_restarts.load(atomic::Ordering::Acquire),
8328 0,
8329 "Should not restart LSP server on an unrelated change"
8330 );
8331
8332 update_test_project_settings(cx, |project_settings| {
8333 project_settings.lsp.insert(
8334 "Some other server name".into(),
8335 LspSettings {
8336 settings: None,
8337 initialization_options: Some(json!({
8338 "some other init value": false
8339 })),
8340 },
8341 );
8342 });
8343 cx.executor().run_until_parked();
8344 assert_eq!(
8345 server_restarts.load(atomic::Ordering::Acquire),
8346 0,
8347 "Should not restart LSP server on an unrelated LSP settings change"
8348 );
8349
8350 update_test_project_settings(cx, |project_settings| {
8351 project_settings.lsp.insert(
8352 language_server_name.into(),
8353 LspSettings {
8354 settings: None,
8355 initialization_options: Some(json!({
8356 "anotherInitValue": false
8357 })),
8358 },
8359 );
8360 });
8361 cx.executor().run_until_parked();
8362 assert_eq!(
8363 server_restarts.load(atomic::Ordering::Acquire),
8364 1,
8365 "Should restart LSP server on a related LSP settings change"
8366 );
8367
8368 update_test_project_settings(cx, |project_settings| {
8369 project_settings.lsp.insert(
8370 language_server_name.into(),
8371 LspSettings {
8372 settings: None,
8373 initialization_options: Some(json!({
8374 "anotherInitValue": false
8375 })),
8376 },
8377 );
8378 });
8379 cx.executor().run_until_parked();
8380 assert_eq!(
8381 server_restarts.load(atomic::Ordering::Acquire),
8382 1,
8383 "Should not restart LSP server on a related LSP settings change that is the same"
8384 );
8385
8386 update_test_project_settings(cx, |project_settings| {
8387 project_settings.lsp.insert(
8388 language_server_name.into(),
8389 LspSettings {
8390 settings: None,
8391 initialization_options: None,
8392 },
8393 );
8394 });
8395 cx.executor().run_until_parked();
8396 assert_eq!(
8397 server_restarts.load(atomic::Ordering::Acquire),
8398 2,
8399 "Should restart LSP server on another related LSP settings change"
8400 );
8401}
8402
8403#[gpui::test]
8404async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
8405 init_test(cx, |_| {});
8406
8407 let mut cx = EditorLspTestContext::new_rust(
8408 lsp::ServerCapabilities {
8409 completion_provider: Some(lsp::CompletionOptions {
8410 trigger_characters: Some(vec![".".to_string()]),
8411 resolve_provider: Some(true),
8412 ..Default::default()
8413 }),
8414 ..Default::default()
8415 },
8416 cx,
8417 )
8418 .await;
8419
8420 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8421 cx.simulate_keystroke(".");
8422 let completion_item = lsp::CompletionItem {
8423 label: "some".into(),
8424 kind: Some(lsp::CompletionItemKind::SNIPPET),
8425 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8426 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8427 kind: lsp::MarkupKind::Markdown,
8428 value: "```rust\nSome(2)\n```".to_string(),
8429 })),
8430 deprecated: Some(false),
8431 sort_text: Some("fffffff2".to_string()),
8432 filter_text: Some("some".to_string()),
8433 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8434 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8435 range: lsp::Range {
8436 start: lsp::Position {
8437 line: 0,
8438 character: 22,
8439 },
8440 end: lsp::Position {
8441 line: 0,
8442 character: 22,
8443 },
8444 },
8445 new_text: "Some(2)".to_string(),
8446 })),
8447 additional_text_edits: Some(vec![lsp::TextEdit {
8448 range: lsp::Range {
8449 start: lsp::Position {
8450 line: 0,
8451 character: 20,
8452 },
8453 end: lsp::Position {
8454 line: 0,
8455 character: 22,
8456 },
8457 },
8458 new_text: "".to_string(),
8459 }]),
8460 ..Default::default()
8461 };
8462
8463 let closure_completion_item = completion_item.clone();
8464 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8465 let task_completion_item = closure_completion_item.clone();
8466 async move {
8467 Ok(Some(lsp::CompletionResponse::Array(vec![
8468 task_completion_item,
8469 ])))
8470 }
8471 });
8472
8473 request.next().await;
8474
8475 cx.condition(|editor, _| editor.context_menu_visible())
8476 .await;
8477 let apply_additional_edits = cx.update_editor(|editor, cx| {
8478 editor
8479 .confirm_completion(&ConfirmCompletion::default(), cx)
8480 .unwrap()
8481 });
8482 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
8483
8484 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
8485 let task_completion_item = completion_item.clone();
8486 async move { Ok(task_completion_item) }
8487 })
8488 .next()
8489 .await
8490 .unwrap();
8491 apply_additional_edits.await.unwrap();
8492 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
8493}
8494
8495#[gpui::test]
8496async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
8497 init_test(cx, |_| {});
8498
8499 let mut cx = EditorLspTestContext::new(
8500 Language::new(
8501 LanguageConfig {
8502 matcher: LanguageMatcher {
8503 path_suffixes: vec!["jsx".into()],
8504 ..Default::default()
8505 },
8506 overrides: [(
8507 "element".into(),
8508 LanguageConfigOverride {
8509 word_characters: Override::Set(['-'].into_iter().collect()),
8510 ..Default::default()
8511 },
8512 )]
8513 .into_iter()
8514 .collect(),
8515 ..Default::default()
8516 },
8517 Some(tree_sitter_typescript::language_tsx()),
8518 )
8519 .with_override_query("(jsx_self_closing_element) @element")
8520 .unwrap(),
8521 lsp::ServerCapabilities {
8522 completion_provider: Some(lsp::CompletionOptions {
8523 trigger_characters: Some(vec![":".to_string()]),
8524 ..Default::default()
8525 }),
8526 ..Default::default()
8527 },
8528 cx,
8529 )
8530 .await;
8531
8532 cx.lsp
8533 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8534 Ok(Some(lsp::CompletionResponse::Array(vec![
8535 lsp::CompletionItem {
8536 label: "bg-blue".into(),
8537 ..Default::default()
8538 },
8539 lsp::CompletionItem {
8540 label: "bg-red".into(),
8541 ..Default::default()
8542 },
8543 lsp::CompletionItem {
8544 label: "bg-yellow".into(),
8545 ..Default::default()
8546 },
8547 ])))
8548 });
8549
8550 cx.set_state(r#"<p class="bgˇ" />"#);
8551
8552 // Trigger completion when typing a dash, because the dash is an extra
8553 // word character in the 'element' scope, which contains the cursor.
8554 cx.simulate_keystroke("-");
8555 cx.executor().run_until_parked();
8556 cx.update_editor(|editor, _| {
8557 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8558 assert_eq!(
8559 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8560 &["bg-red", "bg-blue", "bg-yellow"]
8561 );
8562 } else {
8563 panic!("expected completion menu to be open");
8564 }
8565 });
8566
8567 cx.simulate_keystroke("l");
8568 cx.executor().run_until_parked();
8569 cx.update_editor(|editor, _| {
8570 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8571 assert_eq!(
8572 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8573 &["bg-blue", "bg-yellow"]
8574 );
8575 } else {
8576 panic!("expected completion menu to be open");
8577 }
8578 });
8579
8580 // When filtering completions, consider the character after the '-' to
8581 // be the start of a subword.
8582 cx.set_state(r#"<p class="yelˇ" />"#);
8583 cx.simulate_keystroke("l");
8584 cx.executor().run_until_parked();
8585 cx.update_editor(|editor, _| {
8586 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8587 assert_eq!(
8588 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8589 &["bg-yellow"]
8590 );
8591 } else {
8592 panic!("expected completion menu to be open");
8593 }
8594 });
8595}
8596
8597#[gpui::test]
8598async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
8599 init_test(cx, |settings| {
8600 settings.defaults.formatter = Some(language_settings::Formatter::Prettier)
8601 });
8602
8603 let fs = FakeFs::new(cx.executor());
8604 fs.insert_file("/file.rs", Default::default()).await;
8605
8606 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8607 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8608
8609 language_registry.add(Arc::new(Language::new(
8610 LanguageConfig {
8611 name: "Rust".into(),
8612 matcher: LanguageMatcher {
8613 path_suffixes: vec!["rs".to_string()],
8614 ..Default::default()
8615 },
8616 prettier_parser_name: Some("test_parser".to_string()),
8617 ..Default::default()
8618 },
8619 Some(tree_sitter_rust::language()),
8620 )));
8621
8622 let test_plugin = "test_plugin";
8623 let _ = language_registry.register_fake_lsp_adapter(
8624 "Rust",
8625 FakeLspAdapter {
8626 prettier_plugins: vec![test_plugin],
8627 ..Default::default()
8628 },
8629 );
8630
8631 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
8632 let buffer = project
8633 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
8634 .await
8635 .unwrap();
8636
8637 let buffer_text = "one\ntwo\nthree\n";
8638 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
8639 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
8640 _ = editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
8641
8642 editor
8643 .update(cx, |editor, cx| {
8644 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
8645 })
8646 .unwrap()
8647 .await;
8648 assert_eq!(
8649 editor.update(cx, |editor, cx| editor.text(cx)),
8650 buffer_text.to_string() + prettier_format_suffix,
8651 "Test prettier formatting was not applied to the original buffer text",
8652 );
8653
8654 update_test_language_settings(cx, |settings| {
8655 settings.defaults.formatter = Some(language_settings::Formatter::Auto)
8656 });
8657 let format = editor.update(cx, |editor, cx| {
8658 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
8659 });
8660 format.await.unwrap();
8661 assert_eq!(
8662 editor.update(cx, |editor, cx| editor.text(cx)),
8663 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
8664 "Autoformatting (via test prettier) was not applied to the original buffer text",
8665 );
8666}
8667
8668#[gpui::test]
8669async fn test_find_all_references(cx: &mut gpui::TestAppContext) {
8670 init_test(cx, |_| {});
8671
8672 let mut cx = EditorLspTestContext::new_rust(
8673 lsp::ServerCapabilities {
8674 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8675 ..Default::default()
8676 },
8677 cx,
8678 )
8679 .await;
8680
8681 cx.set_state(indoc! {"
8682 fn foo(«paramˇ»: i64) {
8683 println!(param);
8684 }
8685 "});
8686
8687 cx.lsp
8688 .handle_request::<lsp::request::References, _, _>(move |_, _| async move {
8689 Ok(Some(vec![
8690 lsp::Location {
8691 uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(),
8692 range: lsp::Range::new(lsp::Position::new(0, 7), lsp::Position::new(0, 12)),
8693 },
8694 lsp::Location {
8695 uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(),
8696 range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 18)),
8697 },
8698 ]))
8699 });
8700
8701 let references = cx
8702 .update_editor(|editor, cx| editor.find_all_references(&FindAllReferences, cx))
8703 .unwrap();
8704
8705 cx.executor().run_until_parked();
8706
8707 cx.executor().start_waiting();
8708 references.await.unwrap();
8709
8710 cx.assert_editor_state(indoc! {"
8711 fn foo(param: i64) {
8712 println!(«paramˇ»);
8713 }
8714 "});
8715
8716 let references = cx
8717 .update_editor(|editor, cx| editor.find_all_references(&FindAllReferences, cx))
8718 .unwrap();
8719
8720 cx.executor().run_until_parked();
8721
8722 cx.executor().start_waiting();
8723 references.await.unwrap();
8724
8725 cx.assert_editor_state(indoc! {"
8726 fn foo(«paramˇ»: i64) {
8727 println!(param);
8728 }
8729 "});
8730
8731 cx.set_state(indoc! {"
8732 fn foo(param: i64) {
8733 let a = param;
8734 let aˇ = param;
8735 let a = param;
8736 println!(param);
8737 }
8738 "});
8739
8740 cx.lsp
8741 .handle_request::<lsp::request::References, _, _>(move |_, _| async move {
8742 Ok(Some(vec![lsp::Location {
8743 uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(),
8744 range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 9)),
8745 }]))
8746 });
8747
8748 let references = cx
8749 .update_editor(|editor, cx| editor.find_all_references(&FindAllReferences, cx))
8750 .unwrap();
8751
8752 cx.executor().run_until_parked();
8753
8754 cx.executor().start_waiting();
8755 references.await.unwrap();
8756
8757 cx.assert_editor_state(indoc! {"
8758 fn foo(param: i64) {
8759 let a = param;
8760 let «aˇ» = param;
8761 let a = param;
8762 println!(param);
8763 }
8764 "});
8765}
8766
8767#[gpui::test]
8768async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
8769 init_test(cx, |_| {});
8770 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
8771 let base_text = indoc! {r#"struct Row;
8772struct Row1;
8773struct Row2;
8774
8775struct Row4;
8776struct Row5;
8777struct Row6;
8778
8779struct Row8;
8780struct Row9;
8781struct Row10;"#};
8782
8783 // When addition hunks are not adjacent to carets, no hunk revert is performed
8784 assert_hunk_revert(
8785 indoc! {r#"struct Row;
8786 struct Row1;
8787 struct Row1.1;
8788 struct Row1.2;
8789 struct Row2;ˇ
8790
8791 struct Row4;
8792 struct Row5;
8793 struct Row6;
8794
8795 struct Row8;
8796 ˇstruct Row9;
8797 struct Row9.1;
8798 struct Row9.2;
8799 struct Row9.3;
8800 struct Row10;"#},
8801 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
8802 indoc! {r#"struct Row;
8803 struct Row1;
8804 struct Row1.1;
8805 struct Row1.2;
8806 struct Row2;ˇ
8807
8808 struct Row4;
8809 struct Row5;
8810 struct Row6;
8811
8812 struct Row8;
8813 ˇstruct Row9;
8814 struct Row9.1;
8815 struct Row9.2;
8816 struct Row9.3;
8817 struct Row10;"#},
8818 base_text,
8819 &mut cx,
8820 );
8821 // Same for selections
8822 assert_hunk_revert(
8823 indoc! {r#"struct Row;
8824 struct Row1;
8825 struct Row2;
8826 struct Row2.1;
8827 struct Row2.2;
8828 «ˇ
8829 struct Row4;
8830 struct» Row5;
8831 «struct Row6;
8832 ˇ»
8833 struct Row9.1;
8834 struct Row9.2;
8835 struct Row9.3;
8836 struct Row8;
8837 struct Row9;
8838 struct Row10;"#},
8839 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
8840 indoc! {r#"struct Row;
8841 struct Row1;
8842 struct Row2;
8843 struct Row2.1;
8844 struct Row2.2;
8845 «ˇ
8846 struct Row4;
8847 struct» Row5;
8848 «struct Row6;
8849 ˇ»
8850 struct Row9.1;
8851 struct Row9.2;
8852 struct Row9.3;
8853 struct Row8;
8854 struct Row9;
8855 struct Row10;"#},
8856 base_text,
8857 &mut cx,
8858 );
8859
8860 // When carets and selections intersect the addition hunks, those are reverted.
8861 // Adjacent carets got merged.
8862 assert_hunk_revert(
8863 indoc! {r#"struct Row;
8864 ˇ// something on the top
8865 struct Row1;
8866 struct Row2;
8867 struct Roˇw3.1;
8868 struct Row2.2;
8869 struct Row2.3;ˇ
8870
8871 struct Row4;
8872 struct ˇRow5.1;
8873 struct Row5.2;
8874 struct «Rowˇ»5.3;
8875 struct Row5;
8876 struct Row6;
8877 ˇ
8878 struct Row9.1;
8879 struct «Rowˇ»9.2;
8880 struct «ˇRow»9.3;
8881 struct Row8;
8882 struct Row9;
8883 «ˇ// something on bottom»
8884 struct Row10;"#},
8885 vec![
8886 DiffHunkStatus::Added,
8887 DiffHunkStatus::Added,
8888 DiffHunkStatus::Added,
8889 DiffHunkStatus::Added,
8890 DiffHunkStatus::Added,
8891 ],
8892 indoc! {r#"struct Row;
8893 ˇstruct Row1;
8894 struct Row2;
8895 ˇ
8896 struct Row4;
8897 ˇstruct Row5;
8898 struct Row6;
8899 ˇ
8900 ˇstruct Row8;
8901 struct Row9;
8902 ˇstruct Row10;"#},
8903 base_text,
8904 &mut cx,
8905 );
8906}
8907
8908#[gpui::test]
8909async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
8910 init_test(cx, |_| {});
8911 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
8912 let base_text = indoc! {r#"struct Row;
8913struct Row1;
8914struct Row2;
8915
8916struct Row4;
8917struct Row5;
8918struct Row6;
8919
8920struct Row8;
8921struct Row9;
8922struct Row10;"#};
8923
8924 // Modification hunks behave the same as the addition ones.
8925 assert_hunk_revert(
8926 indoc! {r#"struct Row;
8927 struct Row1;
8928 struct Row33;
8929 ˇ
8930 struct Row4;
8931 struct Row5;
8932 struct Row6;
8933 ˇ
8934 struct Row99;
8935 struct Row9;
8936 struct Row10;"#},
8937 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
8938 indoc! {r#"struct Row;
8939 struct Row1;
8940 struct Row33;
8941 ˇ
8942 struct Row4;
8943 struct Row5;
8944 struct Row6;
8945 ˇ
8946 struct Row99;
8947 struct Row9;
8948 struct Row10;"#},
8949 base_text,
8950 &mut cx,
8951 );
8952 assert_hunk_revert(
8953 indoc! {r#"struct Row;
8954 struct Row1;
8955 struct Row33;
8956 «ˇ
8957 struct Row4;
8958 struct» Row5;
8959 «struct Row6;
8960 ˇ»
8961 struct Row99;
8962 struct Row9;
8963 struct Row10;"#},
8964 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
8965 indoc! {r#"struct Row;
8966 struct Row1;
8967 struct Row33;
8968 «ˇ
8969 struct Row4;
8970 struct» Row5;
8971 «struct Row6;
8972 ˇ»
8973 struct Row99;
8974 struct Row9;
8975 struct Row10;"#},
8976 base_text,
8977 &mut cx,
8978 );
8979
8980 assert_hunk_revert(
8981 indoc! {r#"ˇstruct Row1.1;
8982 struct Row1;
8983 «ˇstr»uct Row22;
8984
8985 struct ˇRow44;
8986 struct Row5;
8987 struct «Rˇ»ow66;ˇ
8988
8989 «struˇ»ct Row88;
8990 struct Row9;
8991 struct Row1011;ˇ"#},
8992 vec![
8993 DiffHunkStatus::Modified,
8994 DiffHunkStatus::Modified,
8995 DiffHunkStatus::Modified,
8996 DiffHunkStatus::Modified,
8997 DiffHunkStatus::Modified,
8998 DiffHunkStatus::Modified,
8999 ],
9000 indoc! {r#"struct Row;
9001 ˇstruct Row1;
9002 struct Row2;
9003 ˇ
9004 struct Row4;
9005 ˇstruct Row5;
9006 struct Row6;
9007 ˇ
9008 struct Row8;
9009 ˇstruct Row9;
9010 struct Row10;ˇ"#},
9011 base_text,
9012 &mut cx,
9013 );
9014}
9015
9016#[gpui::test]
9017async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
9018 init_test(cx, |_| {});
9019 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9020 let base_text = indoc! {r#"struct Row;
9021struct Row1;
9022struct Row2;
9023
9024struct Row4;
9025struct Row5;
9026struct Row6;
9027
9028struct Row8;
9029struct Row9;
9030struct Row10;"#};
9031
9032 // Deletion hunks trigger with carets on ajacent rows, so carets and selections have to stay farther to avoid the revert
9033 assert_hunk_revert(
9034 indoc! {r#"struct Row;
9035 struct Row2;
9036
9037 ˇstruct Row4;
9038 struct Row5;
9039 struct Row6;
9040 ˇ
9041 struct Row8;
9042 struct Row10;"#},
9043 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
9044 indoc! {r#"struct Row;
9045 struct Row2;
9046
9047 ˇstruct Row4;
9048 struct Row5;
9049 struct Row6;
9050 ˇ
9051 struct Row8;
9052 struct Row10;"#},
9053 base_text,
9054 &mut cx,
9055 );
9056 assert_hunk_revert(
9057 indoc! {r#"struct Row;
9058 struct Row2;
9059
9060 «ˇstruct Row4;
9061 struct» Row5;
9062 «struct Row6;
9063 ˇ»
9064 struct Row8;
9065 struct Row10;"#},
9066 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
9067 indoc! {r#"struct Row;
9068 struct Row2;
9069
9070 «ˇstruct Row4;
9071 struct» Row5;
9072 «struct Row6;
9073 ˇ»
9074 struct Row8;
9075 struct Row10;"#},
9076 base_text,
9077 &mut cx,
9078 );
9079
9080 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
9081 assert_hunk_revert(
9082 indoc! {r#"struct Row;
9083 ˇstruct Row2;
9084
9085 struct Row4;
9086 struct Row5;
9087 struct Row6;
9088
9089 struct Row8;ˇ
9090 struct Row10;"#},
9091 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
9092 indoc! {r#"struct Row;
9093 struct Row1;
9094 ˇstruct Row2;
9095
9096 struct Row4;
9097 struct Row5;
9098 struct Row6;
9099
9100 struct Row8;ˇ
9101 struct Row9;
9102 struct Row10;"#},
9103 base_text,
9104 &mut cx,
9105 );
9106 assert_hunk_revert(
9107 indoc! {r#"struct Row;
9108 struct Row2«ˇ;
9109 struct Row4;
9110 struct» Row5;
9111 «struct Row6;
9112
9113 struct Row8;ˇ»
9114 struct Row10;"#},
9115 vec![
9116 DiffHunkStatus::Removed,
9117 DiffHunkStatus::Removed,
9118 DiffHunkStatus::Removed,
9119 ],
9120 indoc! {r#"struct Row;
9121 struct Row1;
9122 struct Row2«ˇ;
9123
9124 struct Row4;
9125 struct» Row5;
9126 «struct Row6;
9127
9128 struct Row8;ˇ»
9129 struct Row9;
9130 struct Row10;"#},
9131 base_text,
9132 &mut cx,
9133 );
9134}
9135
9136#[gpui::test]
9137async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
9138 init_test(cx, |_| {});
9139
9140 let cols = 4;
9141 let rows = 10;
9142 let sample_text_1 = sample_text(rows, cols, 'a');
9143 assert_eq!(
9144 sample_text_1,
9145 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
9146 );
9147 let sample_text_2 = sample_text(rows, cols, 'l');
9148 assert_eq!(
9149 sample_text_2,
9150 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
9151 );
9152 let sample_text_3 = sample_text(rows, cols, 'v');
9153 assert_eq!(
9154 sample_text_3,
9155 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
9156 );
9157
9158 fn diff_every_buffer_row(
9159 buffer: &Model<Buffer>,
9160 sample_text: String,
9161 cols: usize,
9162 cx: &mut gpui::TestAppContext,
9163 ) {
9164 // revert first character in each row, creating one large diff hunk per buffer
9165 let is_first_char = |offset: usize| offset % cols == 0;
9166 buffer.update(cx, |buffer, cx| {
9167 buffer.set_text(
9168 sample_text
9169 .chars()
9170 .enumerate()
9171 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
9172 .collect::<String>(),
9173 cx,
9174 );
9175 buffer.set_diff_base(Some(sample_text), cx);
9176 });
9177 cx.executor().run_until_parked();
9178 }
9179
9180 let buffer_1 = cx.new_model(|cx| {
9181 Buffer::new(
9182 0,
9183 BufferId::new(cx.entity_id().as_u64()).unwrap(),
9184 sample_text_1.clone(),
9185 )
9186 });
9187 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
9188
9189 let buffer_2 = cx.new_model(|cx| {
9190 Buffer::new(
9191 1,
9192 BufferId::new(cx.entity_id().as_u64() + 1).unwrap(),
9193 sample_text_2.clone(),
9194 )
9195 });
9196 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
9197
9198 let buffer_3 = cx.new_model(|cx| {
9199 Buffer::new(
9200 2,
9201 BufferId::new(cx.entity_id().as_u64() + 2).unwrap(),
9202 sample_text_3.clone(),
9203 )
9204 });
9205 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
9206
9207 let multibuffer = cx.new_model(|cx| {
9208 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
9209 multibuffer.push_excerpts(
9210 buffer_1.clone(),
9211 [
9212 ExcerptRange {
9213 context: Point::new(0, 0)..Point::new(3, 0),
9214 primary: None,
9215 },
9216 ExcerptRange {
9217 context: Point::new(5, 0)..Point::new(7, 0),
9218 primary: None,
9219 },
9220 ExcerptRange {
9221 context: Point::new(9, 0)..Point::new(10, 4),
9222 primary: None,
9223 },
9224 ],
9225 cx,
9226 );
9227 multibuffer.push_excerpts(
9228 buffer_2.clone(),
9229 [
9230 ExcerptRange {
9231 context: Point::new(0, 0)..Point::new(3, 0),
9232 primary: None,
9233 },
9234 ExcerptRange {
9235 context: Point::new(5, 0)..Point::new(7, 0),
9236 primary: None,
9237 },
9238 ExcerptRange {
9239 context: Point::new(9, 0)..Point::new(10, 4),
9240 primary: None,
9241 },
9242 ],
9243 cx,
9244 );
9245 multibuffer.push_excerpts(
9246 buffer_3.clone(),
9247 [
9248 ExcerptRange {
9249 context: Point::new(0, 0)..Point::new(3, 0),
9250 primary: None,
9251 },
9252 ExcerptRange {
9253 context: Point::new(5, 0)..Point::new(7, 0),
9254 primary: None,
9255 },
9256 ExcerptRange {
9257 context: Point::new(9, 0)..Point::new(10, 4),
9258 primary: None,
9259 },
9260 ],
9261 cx,
9262 );
9263 multibuffer
9264 });
9265
9266 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9267 editor.update(cx, |editor, cx| {
9268 assert_eq!(editor.text(cx), "XaaaXbbbX\nccXc\ndXdd\n\nhXhh\nXiiiXjjjX\n\nXlllXmmmX\nnnXn\noXoo\n\nsXss\nXtttXuuuX\n\nXvvvXwwwX\nxxXx\nyXyy\n\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X\n");
9269 editor.select_all(&SelectAll, cx);
9270 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
9271 });
9272 cx.executor().run_until_parked();
9273 // When all ranges are selected, all buffer hunks are reverted.
9274 editor.update(cx, |editor, cx| {
9275 assert_eq!(editor.text(cx), "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nllll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu\n\n\nvvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}\n\n");
9276 });
9277 buffer_1.update(cx, |buffer, _| {
9278 assert_eq!(buffer.text(), sample_text_1);
9279 });
9280 buffer_2.update(cx, |buffer, _| {
9281 assert_eq!(buffer.text(), sample_text_2);
9282 });
9283 buffer_3.update(cx, |buffer, _| {
9284 assert_eq!(buffer.text(), sample_text_3);
9285 });
9286
9287 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
9288 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
9289 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
9290 editor.update(cx, |editor, cx| {
9291 editor.change_selections(None, cx, |s| {
9292 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
9293 });
9294 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
9295 });
9296 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
9297 // but not affect buffer_2 and its related excerpts.
9298 editor.update(cx, |editor, cx| {
9299 assert_eq!(
9300 editor.text(cx),
9301 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nXlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX\n\n\nXvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X\n\n"
9302 );
9303 });
9304 buffer_1.update(cx, |buffer, _| {
9305 assert_eq!(buffer.text(), sample_text_1);
9306 });
9307 buffer_2.update(cx, |buffer, _| {
9308 assert_eq!(
9309 buffer.text(),
9310 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
9311 );
9312 });
9313 buffer_3.update(cx, |buffer, _| {
9314 assert_eq!(
9315 buffer.text(),
9316 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
9317 );
9318 });
9319}
9320
9321fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
9322 let point = DisplayPoint::new(row as u32, column as u32);
9323 point..point
9324}
9325
9326fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
9327 let (text, ranges) = marked_text_ranges(marked_text, true);
9328 assert_eq!(view.text(cx), text);
9329 assert_eq!(
9330 view.selections.ranges(cx),
9331 ranges,
9332 "Assert selections are {}",
9333 marked_text
9334 );
9335}
9336
9337/// Handle completion request passing a marked string specifying where the completion
9338/// should be triggered from using '|' character, what range should be replaced, and what completions
9339/// should be returned using '<' and '>' to delimit the range
9340pub fn handle_completion_request(
9341 cx: &mut EditorLspTestContext,
9342 marked_string: &str,
9343 completions: Vec<&'static str>,
9344) -> impl Future<Output = ()> {
9345 let complete_from_marker: TextRangeMarker = '|'.into();
9346 let replace_range_marker: TextRangeMarker = ('<', '>').into();
9347 let (_, mut marked_ranges) = marked_text_ranges_by(
9348 marked_string,
9349 vec![complete_from_marker.clone(), replace_range_marker.clone()],
9350 );
9351
9352 let complete_from_position =
9353 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
9354 let replace_range =
9355 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
9356
9357 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
9358 let completions = completions.clone();
9359 async move {
9360 assert_eq!(params.text_document_position.text_document.uri, url.clone());
9361 assert_eq!(
9362 params.text_document_position.position,
9363 complete_from_position
9364 );
9365 Ok(Some(lsp::CompletionResponse::Array(
9366 completions
9367 .iter()
9368 .map(|completion_text| lsp::CompletionItem {
9369 label: completion_text.to_string(),
9370 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9371 range: replace_range,
9372 new_text: completion_text.to_string(),
9373 })),
9374 ..Default::default()
9375 })
9376 .collect(),
9377 )))
9378 }
9379 });
9380
9381 async move {
9382 request.next().await;
9383 }
9384}
9385
9386fn handle_resolve_completion_request(
9387 cx: &mut EditorLspTestContext,
9388 edits: Option<Vec<(&'static str, &'static str)>>,
9389) -> impl Future<Output = ()> {
9390 let edits = edits.map(|edits| {
9391 edits
9392 .iter()
9393 .map(|(marked_string, new_text)| {
9394 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
9395 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
9396 lsp::TextEdit::new(replace_range, new_text.to_string())
9397 })
9398 .collect::<Vec<_>>()
9399 });
9400
9401 let mut request =
9402 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
9403 let edits = edits.clone();
9404 async move {
9405 Ok(lsp::CompletionItem {
9406 additional_text_edits: edits,
9407 ..Default::default()
9408 })
9409 }
9410 });
9411
9412 async move {
9413 request.next().await;
9414 }
9415}
9416
9417fn handle_copilot_completion_request(
9418 lsp: &lsp::FakeLanguageServer,
9419 completions: Vec<copilot::request::Completion>,
9420 completions_cycling: Vec<copilot::request::Completion>,
9421) {
9422 lsp.handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| {
9423 let completions = completions.clone();
9424 async move {
9425 Ok(copilot::request::GetCompletionsResult {
9426 completions: completions.clone(),
9427 })
9428 }
9429 });
9430 lsp.handle_request::<copilot::request::GetCompletionsCycling, _, _>(move |_params, _cx| {
9431 let completions_cycling = completions_cycling.clone();
9432 async move {
9433 Ok(copilot::request::GetCompletionsResult {
9434 completions: completions_cycling.clone(),
9435 })
9436 }
9437 });
9438}
9439
9440pub(crate) fn update_test_language_settings(
9441 cx: &mut TestAppContext,
9442 f: impl Fn(&mut AllLanguageSettingsContent),
9443) {
9444 _ = cx.update(|cx| {
9445 cx.update_global(|store: &mut SettingsStore, cx| {
9446 store.update_user_settings::<AllLanguageSettings>(cx, f);
9447 });
9448 });
9449}
9450
9451pub(crate) fn update_test_project_settings(
9452 cx: &mut TestAppContext,
9453 f: impl Fn(&mut ProjectSettings),
9454) {
9455 _ = cx.update(|cx| {
9456 cx.update_global(|store: &mut SettingsStore, cx| {
9457 store.update_user_settings::<ProjectSettings>(cx, f);
9458 });
9459 });
9460}
9461
9462pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
9463 _ = cx.update(|cx| {
9464 let store = SettingsStore::test(cx);
9465 cx.set_global(store);
9466 theme::init(theme::LoadThemes::JustBase, cx);
9467 release_channel::init("0.0.0", cx);
9468 client::init_settings(cx);
9469 language::init(cx);
9470 Project::init_settings(cx);
9471 workspace::init_settings(cx);
9472 crate::init(cx);
9473 });
9474
9475 update_test_language_settings(cx, f);
9476}
9477
9478pub(crate) fn rust_lang() -> Arc<Language> {
9479 Arc::new(Language::new(
9480 LanguageConfig {
9481 name: "Rust".into(),
9482 matcher: LanguageMatcher {
9483 path_suffixes: vec!["rs".to_string()],
9484 ..Default::default()
9485 },
9486 ..Default::default()
9487 },
9488 Some(tree_sitter_rust::language()),
9489 ))
9490}
9491
9492#[track_caller]
9493fn assert_hunk_revert(
9494 not_reverted_text_with_selections: &str,
9495 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
9496 expected_reverted_text_with_selections: &str,
9497 base_text: &str,
9498 cx: &mut EditorLspTestContext,
9499) {
9500 cx.set_state(not_reverted_text_with_selections);
9501 cx.update_editor(|editor, cx| {
9502 editor
9503 .buffer()
9504 .read(cx)
9505 .as_singleton()
9506 .unwrap()
9507 .update(cx, |buffer, cx| {
9508 buffer.set_diff_base(Some(base_text.to_string()), cx);
9509 });
9510 });
9511 cx.executor().run_until_parked();
9512
9513 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
9514 let snapshot = editor
9515 .buffer()
9516 .read(cx)
9517 .as_singleton()
9518 .unwrap()
9519 .read(cx)
9520 .snapshot();
9521 let reverted_hunk_statuses = snapshot
9522 .git_diff_hunks_in_row_range(0..u32::MAX)
9523 .map(|hunk| hunk.status())
9524 .collect::<Vec<_>>();
9525
9526 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
9527 reverted_hunk_statuses
9528 });
9529 cx.executor().run_until_parked();
9530 cx.assert_editor_state(expected_reverted_text_with_selections);
9531 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
9532}