1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_hunks,
6 editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext,
7 expanded_hunks, expanded_hunks_background_highlights, select_ranges,
8 },
9 JoinLines,
10};
11use futures::StreamExt;
12use gpui::{
13 div, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds,
14 WindowOptions,
15};
16use indoc::indoc;
17use language::{
18 language_settings::{
19 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
20 },
21 BracketPairConfig,
22 Capability::ReadWrite,
23 FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher, Override,
24 ParsedMarkdown, Point,
25};
26use language_settings::{Formatter, FormatterList, IndentGuideSettings};
27use multi_buffer::MultiBufferIndentGuide;
28use parking_lot::Mutex;
29use project::FakeFs;
30use project::{
31 lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
32 project_settings::{LspSettings, ProjectSettings},
33};
34use serde_json::{self, json};
35use std::sync::atomic;
36use std::sync::atomic::AtomicUsize;
37use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
38use unindent::Unindent;
39use util::{
40 assert_set_eq,
41 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
42};
43use workspace::{
44 item::{FollowEvent, FollowableItem, Item, ItemHandle},
45 NavigationEntry, ViewId,
46};
47
48#[gpui::test]
49fn test_edit_events(cx: &mut TestAppContext) {
50 init_test(cx, |_| {});
51
52 let buffer = cx.new_model(|cx| {
53 let mut buffer = language::Buffer::local("123456", cx);
54 buffer.set_group_interval(Duration::from_secs(1));
55 buffer
56 });
57
58 let events = Rc::new(RefCell::new(Vec::new()));
59 let editor1 = cx.add_window({
60 let events = events.clone();
61 |cx| {
62 let view = cx.view().clone();
63 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
64 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
65 EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
66 _ => {}
67 })
68 .detach();
69 Editor::for_buffer(buffer.clone(), None, cx)
70 }
71 });
72
73 let editor2 = cx.add_window({
74 let events = events.clone();
75 |cx| {
76 cx.subscribe(
77 &cx.view().clone(),
78 move |_, _, event: &EditorEvent, _| match event {
79 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
80 EditorEvent::BufferEdited => {
81 events.borrow_mut().push(("editor2", "buffer edited"))
82 }
83 _ => {}
84 },
85 )
86 .detach();
87 Editor::for_buffer(buffer.clone(), None, cx)
88 }
89 });
90
91 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
92
93 // Mutating editor 1 will emit an `Edited` event only for that editor.
94 _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
95 assert_eq!(
96 mem::take(&mut *events.borrow_mut()),
97 [
98 ("editor1", "edited"),
99 ("editor1", "buffer edited"),
100 ("editor2", "buffer edited"),
101 ]
102 );
103
104 // Mutating editor 2 will emit an `Edited` event only for that editor.
105 _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
106 assert_eq!(
107 mem::take(&mut *events.borrow_mut()),
108 [
109 ("editor2", "edited"),
110 ("editor1", "buffer edited"),
111 ("editor2", "buffer edited"),
112 ]
113 );
114
115 // Undoing on editor 1 will emit an `Edited` event only for that editor.
116 _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
117 assert_eq!(
118 mem::take(&mut *events.borrow_mut()),
119 [
120 ("editor1", "edited"),
121 ("editor1", "buffer edited"),
122 ("editor2", "buffer edited"),
123 ]
124 );
125
126 // Redoing on editor 1 will emit an `Edited` event only for that editor.
127 _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
128 assert_eq!(
129 mem::take(&mut *events.borrow_mut()),
130 [
131 ("editor1", "edited"),
132 ("editor1", "buffer edited"),
133 ("editor2", "buffer edited"),
134 ]
135 );
136
137 // Undoing on editor 2 will emit an `Edited` event only for that editor.
138 _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
139 assert_eq!(
140 mem::take(&mut *events.borrow_mut()),
141 [
142 ("editor2", "edited"),
143 ("editor1", "buffer edited"),
144 ("editor2", "buffer edited"),
145 ]
146 );
147
148 // Redoing on editor 2 will emit an `Edited` event only for that editor.
149 _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
150 assert_eq!(
151 mem::take(&mut *events.borrow_mut()),
152 [
153 ("editor2", "edited"),
154 ("editor1", "buffer edited"),
155 ("editor2", "buffer edited"),
156 ]
157 );
158
159 // No event is emitted when the mutation is a no-op.
160 _ = editor2.update(cx, |editor, cx| {
161 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
162
163 editor.backspace(&Backspace, cx);
164 });
165 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
166}
167
168#[gpui::test]
169fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
170 init_test(cx, |_| {});
171
172 let mut now = Instant::now();
173 let buffer = cx.new_model(|cx| language::Buffer::local("123456", cx));
174 let group_interval = buffer.update(cx, |buffer, _| buffer.transaction_group_interval());
175 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
176 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
177
178 _ = editor.update(cx, |editor, cx| {
179 editor.start_transaction_at(now, cx);
180 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
181
182 editor.insert("cd", cx);
183 editor.end_transaction_at(now, cx);
184 assert_eq!(editor.text(cx), "12cd56");
185 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
186
187 editor.start_transaction_at(now, cx);
188 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
189 editor.insert("e", cx);
190 editor.end_transaction_at(now, cx);
191 assert_eq!(editor.text(cx), "12cde6");
192 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
193
194 now += group_interval + Duration::from_millis(1);
195 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
196
197 // Simulate an edit in another editor
198 _ = buffer.update(cx, |buffer, cx| {
199 buffer.start_transaction_at(now, cx);
200 buffer.edit([(0..1, "a")], None, cx);
201 buffer.edit([(1..1, "b")], None, cx);
202 buffer.end_transaction_at(now, cx);
203 });
204
205 assert_eq!(editor.text(cx), "ab2cde6");
206 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
207
208 // Last transaction happened past the group interval in a different editor.
209 // Undo it individually and don't restore selections.
210 editor.undo(&Undo, cx);
211 assert_eq!(editor.text(cx), "12cde6");
212 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
213
214 // First two transactions happened within the group interval in this editor.
215 // Undo them together and restore selections.
216 editor.undo(&Undo, cx);
217 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
218 assert_eq!(editor.text(cx), "123456");
219 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
220
221 // Redo the first two transactions together.
222 editor.redo(&Redo, cx);
223 assert_eq!(editor.text(cx), "12cde6");
224 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
225
226 // Redo the last transaction on its own.
227 editor.redo(&Redo, cx);
228 assert_eq!(editor.text(cx), "ab2cde6");
229 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
230
231 // Test empty transactions.
232 editor.start_transaction_at(now, cx);
233 editor.end_transaction_at(now, cx);
234 editor.undo(&Undo, cx);
235 assert_eq!(editor.text(cx), "12cde6");
236 });
237}
238
239#[gpui::test]
240fn test_ime_composition(cx: &mut TestAppContext) {
241 init_test(cx, |_| {});
242
243 let buffer = cx.new_model(|cx| {
244 let mut buffer = language::Buffer::local("abcde", cx);
245 // Ensure automatic grouping doesn't occur.
246 buffer.set_group_interval(Duration::ZERO);
247 buffer
248 });
249
250 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
251 cx.add_window(|cx| {
252 let mut editor = build_editor(buffer.clone(), cx);
253
254 // Start a new IME composition.
255 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
256 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
257 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
258 assert_eq!(editor.text(cx), "äbcde");
259 assert_eq!(
260 editor.marked_text_ranges(cx),
261 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
262 );
263
264 // Finalize IME composition.
265 editor.replace_text_in_range(None, "ā", cx);
266 assert_eq!(editor.text(cx), "ābcde");
267 assert_eq!(editor.marked_text_ranges(cx), None);
268
269 // IME composition edits are grouped and are undone/redone at once.
270 editor.undo(&Default::default(), cx);
271 assert_eq!(editor.text(cx), "abcde");
272 assert_eq!(editor.marked_text_ranges(cx), None);
273 editor.redo(&Default::default(), cx);
274 assert_eq!(editor.text(cx), "ābcde");
275 assert_eq!(editor.marked_text_ranges(cx), None);
276
277 // Start a new IME composition.
278 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
279 assert_eq!(
280 editor.marked_text_ranges(cx),
281 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
282 );
283
284 // Undoing during an IME composition cancels it.
285 editor.undo(&Default::default(), cx);
286 assert_eq!(editor.text(cx), "ābcde");
287 assert_eq!(editor.marked_text_ranges(cx), None);
288
289 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
290 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
291 assert_eq!(editor.text(cx), "ābcdè");
292 assert_eq!(
293 editor.marked_text_ranges(cx),
294 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
295 );
296
297 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
298 editor.replace_text_in_range(Some(4..999), "ę", cx);
299 assert_eq!(editor.text(cx), "ābcdę");
300 assert_eq!(editor.marked_text_ranges(cx), None);
301
302 // Start a new IME composition with multiple cursors.
303 editor.change_selections(None, cx, |s| {
304 s.select_ranges([
305 OffsetUtf16(1)..OffsetUtf16(1),
306 OffsetUtf16(3)..OffsetUtf16(3),
307 OffsetUtf16(5)..OffsetUtf16(5),
308 ])
309 });
310 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
311 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
312 assert_eq!(
313 editor.marked_text_ranges(cx),
314 Some(vec![
315 OffsetUtf16(0)..OffsetUtf16(3),
316 OffsetUtf16(4)..OffsetUtf16(7),
317 OffsetUtf16(8)..OffsetUtf16(11)
318 ])
319 );
320
321 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
322 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
323 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
324 assert_eq!(
325 editor.marked_text_ranges(cx),
326 Some(vec![
327 OffsetUtf16(1)..OffsetUtf16(2),
328 OffsetUtf16(5)..OffsetUtf16(6),
329 OffsetUtf16(9)..OffsetUtf16(10)
330 ])
331 );
332
333 // Finalize IME composition with multiple cursors.
334 editor.replace_text_in_range(Some(9..10), "2", cx);
335 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
336 assert_eq!(editor.marked_text_ranges(cx), None);
337
338 editor
339 });
340}
341
342#[gpui::test]
343fn test_selection_with_mouse(cx: &mut TestAppContext) {
344 init_test(cx, |_| {});
345
346 let editor = cx.add_window(|cx| {
347 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
348 build_editor(buffer, cx)
349 });
350
351 _ = editor.update(cx, |view, cx| {
352 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
353 });
354 assert_eq!(
355 editor
356 .update(cx, |view, cx| view.selections.display_ranges(cx))
357 .unwrap(),
358 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
359 );
360
361 _ = editor.update(cx, |view, cx| {
362 view.update_selection(
363 DisplayPoint::new(DisplayRow(3), 3),
364 0,
365 gpui::Point::<f32>::default(),
366 cx,
367 );
368 });
369
370 assert_eq!(
371 editor
372 .update(cx, |view, cx| view.selections.display_ranges(cx))
373 .unwrap(),
374 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
375 );
376
377 _ = editor.update(cx, |view, cx| {
378 view.update_selection(
379 DisplayPoint::new(DisplayRow(1), 1),
380 0,
381 gpui::Point::<f32>::default(),
382 cx,
383 );
384 });
385
386 assert_eq!(
387 editor
388 .update(cx, |view, cx| view.selections.display_ranges(cx))
389 .unwrap(),
390 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
391 );
392
393 _ = editor.update(cx, |view, cx| {
394 view.end_selection(cx);
395 view.update_selection(
396 DisplayPoint::new(DisplayRow(3), 3),
397 0,
398 gpui::Point::<f32>::default(),
399 cx,
400 );
401 });
402
403 assert_eq!(
404 editor
405 .update(cx, |view, cx| view.selections.display_ranges(cx))
406 .unwrap(),
407 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
408 );
409
410 _ = editor.update(cx, |view, cx| {
411 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
412 view.update_selection(
413 DisplayPoint::new(DisplayRow(0), 0),
414 0,
415 gpui::Point::<f32>::default(),
416 cx,
417 );
418 });
419
420 assert_eq!(
421 editor
422 .update(cx, |view, cx| view.selections.display_ranges(cx))
423 .unwrap(),
424 [
425 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
426 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
427 ]
428 );
429
430 _ = editor.update(cx, |view, cx| {
431 view.end_selection(cx);
432 });
433
434 assert_eq!(
435 editor
436 .update(cx, |view, cx| view.selections.display_ranges(cx))
437 .unwrap(),
438 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
439 );
440}
441
442#[gpui::test]
443fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
444 init_test(cx, |_| {});
445
446 let editor = cx.add_window(|cx| {
447 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
448 build_editor(buffer, cx)
449 });
450
451 _ = editor.update(cx, |view, cx| {
452 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
453 });
454
455 _ = editor.update(cx, |view, cx| {
456 view.end_selection(cx);
457 });
458
459 _ = editor.update(cx, |view, cx| {
460 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
461 });
462
463 _ = editor.update(cx, |view, cx| {
464 view.end_selection(cx);
465 });
466
467 assert_eq!(
468 editor
469 .update(cx, |view, cx| view.selections.display_ranges(cx))
470 .unwrap(),
471 [
472 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
473 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
474 ]
475 );
476
477 _ = editor.update(cx, |view, cx| {
478 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
479 });
480
481 _ = editor.update(cx, |view, cx| {
482 view.end_selection(cx);
483 });
484
485 assert_eq!(
486 editor
487 .update(cx, |view, cx| view.selections.display_ranges(cx))
488 .unwrap(),
489 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
490 );
491}
492
493#[gpui::test]
494fn test_canceling_pending_selection(cx: &mut TestAppContext) {
495 init_test(cx, |_| {});
496
497 let view = cx.add_window(|cx| {
498 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
499 build_editor(buffer, cx)
500 });
501
502 _ = view.update(cx, |view, cx| {
503 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
504 assert_eq!(
505 view.selections.display_ranges(cx),
506 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
507 );
508 });
509
510 _ = view.update(cx, |view, cx| {
511 view.update_selection(
512 DisplayPoint::new(DisplayRow(3), 3),
513 0,
514 gpui::Point::<f32>::default(),
515 cx,
516 );
517 assert_eq!(
518 view.selections.display_ranges(cx),
519 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
520 );
521 });
522
523 _ = view.update(cx, |view, cx| {
524 view.cancel(&Cancel, cx);
525 view.update_selection(
526 DisplayPoint::new(DisplayRow(1), 1),
527 0,
528 gpui::Point::<f32>::default(),
529 cx,
530 );
531 assert_eq!(
532 view.selections.display_ranges(cx),
533 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
534 );
535 });
536}
537
538#[gpui::test]
539fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
540 init_test(cx, |_| {});
541
542 let view = cx.add_window(|cx| {
543 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
544 build_editor(buffer, cx)
545 });
546
547 _ = view.update(cx, |view, cx| {
548 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
549 assert_eq!(
550 view.selections.display_ranges(cx),
551 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
552 );
553
554 view.move_down(&Default::default(), cx);
555 assert_eq!(
556 view.selections.display_ranges(cx),
557 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
558 );
559
560 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
561 assert_eq!(
562 view.selections.display_ranges(cx),
563 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
564 );
565
566 view.move_up(&Default::default(), cx);
567 assert_eq!(
568 view.selections.display_ranges(cx),
569 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
570 );
571 });
572}
573
574#[gpui::test]
575fn test_clone(cx: &mut TestAppContext) {
576 init_test(cx, |_| {});
577
578 let (text, selection_ranges) = marked_text_ranges(
579 indoc! {"
580 one
581 two
582 threeˇ
583 four
584 fiveˇ
585 "},
586 true,
587 );
588
589 let editor = cx.add_window(|cx| {
590 let buffer = MultiBuffer::build_simple(&text, cx);
591 build_editor(buffer, cx)
592 });
593
594 _ = editor.update(cx, |editor, cx| {
595 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
596 editor.fold_ranges(
597 [
598 (Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
599 (Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
600 ],
601 true,
602 cx,
603 );
604 });
605
606 let cloned_editor = editor
607 .update(cx, |editor, cx| {
608 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
609 })
610 .unwrap()
611 .unwrap();
612
613 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
614 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
615
616 assert_eq!(
617 cloned_editor
618 .update(cx, |e, cx| e.display_text(cx))
619 .unwrap(),
620 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
621 );
622 assert_eq!(
623 cloned_snapshot
624 .folds_in_range(0..text.len())
625 .collect::<Vec<_>>(),
626 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
627 );
628 assert_set_eq!(
629 cloned_editor
630 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
631 .unwrap(),
632 editor
633 .update(cx, |editor, cx| editor.selections.ranges(cx))
634 .unwrap()
635 );
636 assert_set_eq!(
637 cloned_editor
638 .update(cx, |e, cx| e.selections.display_ranges(cx))
639 .unwrap(),
640 editor
641 .update(cx, |e, cx| e.selections.display_ranges(cx))
642 .unwrap()
643 );
644}
645
646#[gpui::test]
647async fn test_navigation_history(cx: &mut TestAppContext) {
648 init_test(cx, |_| {});
649
650 use workspace::item::Item;
651
652 let fs = FakeFs::new(cx.executor());
653 let project = Project::test(fs, [], cx).await;
654 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
655 let pane = workspace
656 .update(cx, |workspace, _| workspace.active_pane().clone())
657 .unwrap();
658
659 _ = workspace.update(cx, |_v, cx| {
660 cx.new_view(|cx| {
661 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
662 let mut editor = build_editor(buffer.clone(), cx);
663 let handle = cx.view();
664 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
665
666 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
667 editor.nav_history.as_mut().unwrap().pop_backward(cx)
668 }
669
670 // Move the cursor a small distance.
671 // Nothing is added to the navigation history.
672 editor.change_selections(None, cx, |s| {
673 s.select_display_ranges([
674 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
675 ])
676 });
677 editor.change_selections(None, cx, |s| {
678 s.select_display_ranges([
679 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
680 ])
681 });
682 assert!(pop_history(&mut editor, cx).is_none());
683
684 // Move the cursor a large distance.
685 // The history can jump back to the previous position.
686 editor.change_selections(None, cx, |s| {
687 s.select_display_ranges([
688 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
689 ])
690 });
691 let nav_entry = pop_history(&mut editor, cx).unwrap();
692 editor.navigate(nav_entry.data.unwrap(), cx);
693 assert_eq!(nav_entry.item.id(), cx.entity_id());
694 assert_eq!(
695 editor.selections.display_ranges(cx),
696 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
697 );
698 assert!(pop_history(&mut editor, cx).is_none());
699
700 // Move the cursor a small distance via the mouse.
701 // Nothing is added to the navigation history.
702 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
703 editor.end_selection(cx);
704 assert_eq!(
705 editor.selections.display_ranges(cx),
706 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
707 );
708 assert!(pop_history(&mut editor, cx).is_none());
709
710 // Move the cursor a large distance via the mouse.
711 // The history can jump back to the previous position.
712 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
713 editor.end_selection(cx);
714 assert_eq!(
715 editor.selections.display_ranges(cx),
716 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
717 );
718 let nav_entry = pop_history(&mut editor, cx).unwrap();
719 editor.navigate(nav_entry.data.unwrap(), cx);
720 assert_eq!(nav_entry.item.id(), cx.entity_id());
721 assert_eq!(
722 editor.selections.display_ranges(cx),
723 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
724 );
725 assert!(pop_history(&mut editor, cx).is_none());
726
727 // Set scroll position to check later
728 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
729 let original_scroll_position = editor.scroll_manager.anchor();
730
731 // Jump to the end of the document and adjust scroll
732 editor.move_to_end(&MoveToEnd, cx);
733 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
734 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
735
736 let nav_entry = pop_history(&mut editor, cx).unwrap();
737 editor.navigate(nav_entry.data.unwrap(), cx);
738 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
739
740 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
741 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
742 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
743 let invalid_point = Point::new(9999, 0);
744 editor.navigate(
745 Box::new(NavigationData {
746 cursor_anchor: invalid_anchor,
747 cursor_position: invalid_point,
748 scroll_anchor: ScrollAnchor {
749 anchor: invalid_anchor,
750 offset: Default::default(),
751 },
752 scroll_top_row: invalid_point.row,
753 }),
754 cx,
755 );
756 assert_eq!(
757 editor.selections.display_ranges(cx),
758 &[editor.max_point(cx)..editor.max_point(cx)]
759 );
760 assert_eq!(
761 editor.scroll_position(cx),
762 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
763 );
764
765 editor
766 })
767 });
768}
769
770#[gpui::test]
771fn test_cancel(cx: &mut TestAppContext) {
772 init_test(cx, |_| {});
773
774 let view = cx.add_window(|cx| {
775 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
776 build_editor(buffer, cx)
777 });
778
779 _ = view.update(cx, |view, cx| {
780 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
781 view.update_selection(
782 DisplayPoint::new(DisplayRow(1), 1),
783 0,
784 gpui::Point::<f32>::default(),
785 cx,
786 );
787 view.end_selection(cx);
788
789 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
790 view.update_selection(
791 DisplayPoint::new(DisplayRow(0), 3),
792 0,
793 gpui::Point::<f32>::default(),
794 cx,
795 );
796 view.end_selection(cx);
797 assert_eq!(
798 view.selections.display_ranges(cx),
799 [
800 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
801 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
802 ]
803 );
804 });
805
806 _ = view.update(cx, |view, cx| {
807 view.cancel(&Cancel, cx);
808 assert_eq!(
809 view.selections.display_ranges(cx),
810 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
811 );
812 });
813
814 _ = view.update(cx, |view, cx| {
815 view.cancel(&Cancel, cx);
816 assert_eq!(
817 view.selections.display_ranges(cx),
818 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
819 );
820 });
821}
822
823#[gpui::test]
824fn test_fold_action(cx: &mut TestAppContext) {
825 init_test(cx, |_| {});
826
827 let view = cx.add_window(|cx| {
828 let buffer = MultiBuffer::build_simple(
829 &"
830 impl Foo {
831 // Hello!
832
833 fn a() {
834 1
835 }
836
837 fn b() {
838 2
839 }
840
841 fn c() {
842 3
843 }
844 }
845 "
846 .unindent(),
847 cx,
848 );
849 build_editor(buffer.clone(), cx)
850 });
851
852 _ = view.update(cx, |view, cx| {
853 view.change_selections(None, cx, |s| {
854 s.select_display_ranges([
855 DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(12), 0)
856 ]);
857 });
858 view.fold(&Fold, cx);
859 assert_eq!(
860 view.display_text(cx),
861 "
862 impl Foo {
863 // Hello!
864
865 fn a() {
866 1
867 }
868
869 fn b() {⋯
870 }
871
872 fn c() {⋯
873 }
874 }
875 "
876 .unindent(),
877 );
878
879 view.fold(&Fold, cx);
880 assert_eq!(
881 view.display_text(cx),
882 "
883 impl Foo {⋯
884 }
885 "
886 .unindent(),
887 );
888
889 view.unfold_lines(&UnfoldLines, cx);
890 assert_eq!(
891 view.display_text(cx),
892 "
893 impl Foo {
894 // Hello!
895
896 fn a() {
897 1
898 }
899
900 fn b() {⋯
901 }
902
903 fn c() {⋯
904 }
905 }
906 "
907 .unindent(),
908 );
909
910 view.unfold_lines(&UnfoldLines, cx);
911 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
912 });
913}
914
915#[gpui::test]
916fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
917 init_test(cx, |_| {});
918
919 let view = cx.add_window(|cx| {
920 let buffer = MultiBuffer::build_simple(
921 &"
922 class Foo:
923 # Hello!
924
925 def a():
926 print(1)
927
928 def b():
929 print(2)
930
931 def c():
932 print(3)
933 "
934 .unindent(),
935 cx,
936 );
937 build_editor(buffer.clone(), cx)
938 });
939
940 _ = view.update(cx, |view, cx| {
941 view.change_selections(None, cx, |s| {
942 s.select_display_ranges([
943 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(10), 0)
944 ]);
945 });
946 view.fold(&Fold, cx);
947 assert_eq!(
948 view.display_text(cx),
949 "
950 class Foo:
951 # Hello!
952
953 def a():
954 print(1)
955
956 def b():⋯
957
958 def c():⋯
959 "
960 .unindent(),
961 );
962
963 view.fold(&Fold, cx);
964 assert_eq!(
965 view.display_text(cx),
966 "
967 class Foo:⋯
968 "
969 .unindent(),
970 );
971
972 view.unfold_lines(&UnfoldLines, cx);
973 assert_eq!(
974 view.display_text(cx),
975 "
976 class Foo:
977 # Hello!
978
979 def a():
980 print(1)
981
982 def b():⋯
983
984 def c():⋯
985 "
986 .unindent(),
987 );
988
989 view.unfold_lines(&UnfoldLines, cx);
990 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
991 });
992}
993
994#[gpui::test]
995fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
996 init_test(cx, |_| {});
997
998 let view = cx.add_window(|cx| {
999 let buffer = MultiBuffer::build_simple(
1000 &"
1001 class Foo:
1002 # Hello!
1003
1004 def a():
1005 print(1)
1006
1007 def b():
1008 print(2)
1009
1010
1011 def c():
1012 print(3)
1013
1014
1015 "
1016 .unindent(),
1017 cx,
1018 );
1019 build_editor(buffer.clone(), cx)
1020 });
1021
1022 _ = view.update(cx, |view, cx| {
1023 view.change_selections(None, cx, |s| {
1024 s.select_display_ranges([
1025 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(11), 0)
1026 ]);
1027 });
1028 view.fold(&Fold, cx);
1029 assert_eq!(
1030 view.display_text(cx),
1031 "
1032 class Foo:
1033 # Hello!
1034
1035 def a():
1036 print(1)
1037
1038 def b():⋯
1039
1040
1041 def c():⋯
1042
1043
1044 "
1045 .unindent(),
1046 );
1047
1048 view.fold(&Fold, cx);
1049 assert_eq!(
1050 view.display_text(cx),
1051 "
1052 class Foo:⋯
1053
1054
1055 "
1056 .unindent(),
1057 );
1058
1059 view.unfold_lines(&UnfoldLines, cx);
1060 assert_eq!(
1061 view.display_text(cx),
1062 "
1063 class Foo:
1064 # Hello!
1065
1066 def a():
1067 print(1)
1068
1069 def b():⋯
1070
1071
1072 def c():⋯
1073
1074
1075 "
1076 .unindent(),
1077 );
1078
1079 view.unfold_lines(&UnfoldLines, cx);
1080 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1081 });
1082}
1083
1084#[gpui::test]
1085fn test_move_cursor(cx: &mut TestAppContext) {
1086 init_test(cx, |_| {});
1087
1088 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1089 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1090
1091 _ = buffer.update(cx, |buffer, cx| {
1092 buffer.edit(
1093 vec![
1094 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1095 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1096 ],
1097 None,
1098 cx,
1099 );
1100 });
1101 _ = view.update(cx, |view, cx| {
1102 assert_eq!(
1103 view.selections.display_ranges(cx),
1104 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1105 );
1106
1107 view.move_down(&MoveDown, cx);
1108 assert_eq!(
1109 view.selections.display_ranges(cx),
1110 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1111 );
1112
1113 view.move_right(&MoveRight, cx);
1114 assert_eq!(
1115 view.selections.display_ranges(cx),
1116 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1117 );
1118
1119 view.move_left(&MoveLeft, cx);
1120 assert_eq!(
1121 view.selections.display_ranges(cx),
1122 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1123 );
1124
1125 view.move_up(&MoveUp, cx);
1126 assert_eq!(
1127 view.selections.display_ranges(cx),
1128 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1129 );
1130
1131 view.move_to_end(&MoveToEnd, cx);
1132 assert_eq!(
1133 view.selections.display_ranges(cx),
1134 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1135 );
1136
1137 view.move_to_beginning(&MoveToBeginning, cx);
1138 assert_eq!(
1139 view.selections.display_ranges(cx),
1140 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1141 );
1142
1143 view.change_selections(None, cx, |s| {
1144 s.select_display_ranges([
1145 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1146 ]);
1147 });
1148 view.select_to_beginning(&SelectToBeginning, cx);
1149 assert_eq!(
1150 view.selections.display_ranges(cx),
1151 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1152 );
1153
1154 view.select_to_end(&SelectToEnd, cx);
1155 assert_eq!(
1156 view.selections.display_ranges(cx),
1157 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1158 );
1159 });
1160}
1161
1162// TODO: Re-enable this test
1163#[cfg(target_os = "macos")]
1164#[gpui::test]
1165fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1166 init_test(cx, |_| {});
1167
1168 let view = cx.add_window(|cx| {
1169 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1170 build_editor(buffer.clone(), cx)
1171 });
1172
1173 assert_eq!('ⓐ'.len_utf8(), 3);
1174 assert_eq!('α'.len_utf8(), 2);
1175
1176 _ = view.update(cx, |view, cx| {
1177 view.fold_ranges(
1178 vec![
1179 (Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1180 (Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1181 (Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1182 ],
1183 true,
1184 cx,
1185 );
1186 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1187
1188 view.move_right(&MoveRight, cx);
1189 assert_eq!(
1190 view.selections.display_ranges(cx),
1191 &[empty_range(0, "ⓐ".len())]
1192 );
1193 view.move_right(&MoveRight, cx);
1194 assert_eq!(
1195 view.selections.display_ranges(cx),
1196 &[empty_range(0, "ⓐⓑ".len())]
1197 );
1198 view.move_right(&MoveRight, cx);
1199 assert_eq!(
1200 view.selections.display_ranges(cx),
1201 &[empty_range(0, "ⓐⓑ⋯".len())]
1202 );
1203
1204 view.move_down(&MoveDown, cx);
1205 assert_eq!(
1206 view.selections.display_ranges(cx),
1207 &[empty_range(1, "ab⋯e".len())]
1208 );
1209 view.move_left(&MoveLeft, cx);
1210 assert_eq!(
1211 view.selections.display_ranges(cx),
1212 &[empty_range(1, "ab⋯".len())]
1213 );
1214 view.move_left(&MoveLeft, cx);
1215 assert_eq!(
1216 view.selections.display_ranges(cx),
1217 &[empty_range(1, "ab".len())]
1218 );
1219 view.move_left(&MoveLeft, cx);
1220 assert_eq!(
1221 view.selections.display_ranges(cx),
1222 &[empty_range(1, "a".len())]
1223 );
1224
1225 view.move_down(&MoveDown, cx);
1226 assert_eq!(
1227 view.selections.display_ranges(cx),
1228 &[empty_range(2, "α".len())]
1229 );
1230 view.move_right(&MoveRight, cx);
1231 assert_eq!(
1232 view.selections.display_ranges(cx),
1233 &[empty_range(2, "αβ".len())]
1234 );
1235 view.move_right(&MoveRight, cx);
1236 assert_eq!(
1237 view.selections.display_ranges(cx),
1238 &[empty_range(2, "αβ⋯".len())]
1239 );
1240 view.move_right(&MoveRight, cx);
1241 assert_eq!(
1242 view.selections.display_ranges(cx),
1243 &[empty_range(2, "αβ⋯ε".len())]
1244 );
1245
1246 view.move_up(&MoveUp, cx);
1247 assert_eq!(
1248 view.selections.display_ranges(cx),
1249 &[empty_range(1, "ab⋯e".len())]
1250 );
1251 view.move_down(&MoveDown, cx);
1252 assert_eq!(
1253 view.selections.display_ranges(cx),
1254 &[empty_range(2, "αβ⋯ε".len())]
1255 );
1256 view.move_up(&MoveUp, cx);
1257 assert_eq!(
1258 view.selections.display_ranges(cx),
1259 &[empty_range(1, "ab⋯e".len())]
1260 );
1261
1262 view.move_up(&MoveUp, cx);
1263 assert_eq!(
1264 view.selections.display_ranges(cx),
1265 &[empty_range(0, "ⓐⓑ".len())]
1266 );
1267 view.move_left(&MoveLeft, cx);
1268 assert_eq!(
1269 view.selections.display_ranges(cx),
1270 &[empty_range(0, "ⓐ".len())]
1271 );
1272 view.move_left(&MoveLeft, cx);
1273 assert_eq!(
1274 view.selections.display_ranges(cx),
1275 &[empty_range(0, "".len())]
1276 );
1277 });
1278}
1279
1280#[gpui::test]
1281fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1282 init_test(cx, |_| {});
1283
1284 let view = cx.add_window(|cx| {
1285 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1286 build_editor(buffer.clone(), cx)
1287 });
1288 _ = view.update(cx, |view, cx| {
1289 view.change_selections(None, cx, |s| {
1290 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1291 });
1292 view.move_down(&MoveDown, cx);
1293 assert_eq!(
1294 view.selections.display_ranges(cx),
1295 &[empty_range(1, "abcd".len())]
1296 );
1297
1298 view.move_down(&MoveDown, cx);
1299 assert_eq!(
1300 view.selections.display_ranges(cx),
1301 &[empty_range(2, "αβγ".len())]
1302 );
1303
1304 view.move_down(&MoveDown, cx);
1305 assert_eq!(
1306 view.selections.display_ranges(cx),
1307 &[empty_range(3, "abcd".len())]
1308 );
1309
1310 view.move_down(&MoveDown, cx);
1311 assert_eq!(
1312 view.selections.display_ranges(cx),
1313 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1314 );
1315
1316 view.move_up(&MoveUp, cx);
1317 assert_eq!(
1318 view.selections.display_ranges(cx),
1319 &[empty_range(3, "abcd".len())]
1320 );
1321
1322 view.move_up(&MoveUp, cx);
1323 assert_eq!(
1324 view.selections.display_ranges(cx),
1325 &[empty_range(2, "αβγ".len())]
1326 );
1327 });
1328}
1329
1330#[gpui::test]
1331fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1332 init_test(cx, |_| {});
1333 let move_to_beg = MoveToBeginningOfLine {
1334 stop_at_soft_wraps: true,
1335 };
1336
1337 let move_to_end = MoveToEndOfLine {
1338 stop_at_soft_wraps: true,
1339 };
1340
1341 let view = cx.add_window(|cx| {
1342 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1343 build_editor(buffer, cx)
1344 });
1345 _ = view.update(cx, |view, cx| {
1346 view.change_selections(None, cx, |s| {
1347 s.select_display_ranges([
1348 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1349 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1350 ]);
1351 });
1352 });
1353
1354 _ = view.update(cx, |view, cx| {
1355 view.move_to_beginning_of_line(&move_to_beg, cx);
1356 assert_eq!(
1357 view.selections.display_ranges(cx),
1358 &[
1359 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1360 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1361 ]
1362 );
1363 });
1364
1365 _ = view.update(cx, |view, cx| {
1366 view.move_to_beginning_of_line(&move_to_beg, cx);
1367 assert_eq!(
1368 view.selections.display_ranges(cx),
1369 &[
1370 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1371 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1372 ]
1373 );
1374 });
1375
1376 _ = view.update(cx, |view, cx| {
1377 view.move_to_beginning_of_line(&move_to_beg, cx);
1378 assert_eq!(
1379 view.selections.display_ranges(cx),
1380 &[
1381 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1382 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1383 ]
1384 );
1385 });
1386
1387 _ = view.update(cx, |view, cx| {
1388 view.move_to_end_of_line(&move_to_end, cx);
1389 assert_eq!(
1390 view.selections.display_ranges(cx),
1391 &[
1392 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1393 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1394 ]
1395 );
1396 });
1397
1398 // Moving to the end of line again is a no-op.
1399 _ = view.update(cx, |view, cx| {
1400 view.move_to_end_of_line(&move_to_end, cx);
1401 assert_eq!(
1402 view.selections.display_ranges(cx),
1403 &[
1404 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1405 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1406 ]
1407 );
1408 });
1409
1410 _ = view.update(cx, |view, cx| {
1411 view.move_left(&MoveLeft, cx);
1412 view.select_to_beginning_of_line(
1413 &SelectToBeginningOfLine {
1414 stop_at_soft_wraps: true,
1415 },
1416 cx,
1417 );
1418 assert_eq!(
1419 view.selections.display_ranges(cx),
1420 &[
1421 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1422 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1423 ]
1424 );
1425 });
1426
1427 _ = view.update(cx, |view, cx| {
1428 view.select_to_beginning_of_line(
1429 &SelectToBeginningOfLine {
1430 stop_at_soft_wraps: true,
1431 },
1432 cx,
1433 );
1434 assert_eq!(
1435 view.selections.display_ranges(cx),
1436 &[
1437 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1438 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1439 ]
1440 );
1441 });
1442
1443 _ = view.update(cx, |view, cx| {
1444 view.select_to_beginning_of_line(
1445 &SelectToBeginningOfLine {
1446 stop_at_soft_wraps: true,
1447 },
1448 cx,
1449 );
1450 assert_eq!(
1451 view.selections.display_ranges(cx),
1452 &[
1453 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1454 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1455 ]
1456 );
1457 });
1458
1459 _ = view.update(cx, |view, cx| {
1460 view.select_to_end_of_line(
1461 &SelectToEndOfLine {
1462 stop_at_soft_wraps: true,
1463 },
1464 cx,
1465 );
1466 assert_eq!(
1467 view.selections.display_ranges(cx),
1468 &[
1469 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1470 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1471 ]
1472 );
1473 });
1474
1475 _ = view.update(cx, |view, cx| {
1476 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1477 assert_eq!(view.display_text(cx), "ab\n de");
1478 assert_eq!(
1479 view.selections.display_ranges(cx),
1480 &[
1481 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1482 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1483 ]
1484 );
1485 });
1486
1487 _ = view.update(cx, |view, cx| {
1488 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1489 assert_eq!(view.display_text(cx), "\n");
1490 assert_eq!(
1491 view.selections.display_ranges(cx),
1492 &[
1493 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1494 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1495 ]
1496 );
1497 });
1498}
1499
1500#[gpui::test]
1501fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1502 init_test(cx, |_| {});
1503 let move_to_beg = MoveToBeginningOfLine {
1504 stop_at_soft_wraps: false,
1505 };
1506
1507 let move_to_end = MoveToEndOfLine {
1508 stop_at_soft_wraps: false,
1509 };
1510
1511 let view = cx.add_window(|cx| {
1512 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1513 build_editor(buffer, cx)
1514 });
1515
1516 _ = view.update(cx, |view, cx| {
1517 view.set_wrap_width(Some(140.0.into()), cx);
1518
1519 // We expect the following lines after wrapping
1520 // ```
1521 // thequickbrownfox
1522 // jumpedoverthelazydo
1523 // gs
1524 // ```
1525 // The final `gs` was soft-wrapped onto a new line.
1526 assert_eq!(
1527 "thequickbrownfox\njumpedoverthelaz\nydogs",
1528 view.display_text(cx),
1529 );
1530
1531 // First, let's assert behavior on the first line, that was not soft-wrapped.
1532 // Start the cursor at the `k` on the first line
1533 view.change_selections(None, cx, |s| {
1534 s.select_display_ranges([
1535 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1536 ]);
1537 });
1538
1539 // Moving to the beginning of the line should put us at the beginning of the line.
1540 view.move_to_beginning_of_line(&move_to_beg, cx);
1541 assert_eq!(
1542 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1543 view.selections.display_ranges(cx)
1544 );
1545
1546 // Moving to the end of the line should put us at the end of the line.
1547 view.move_to_end_of_line(&move_to_end, cx);
1548 assert_eq!(
1549 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1550 view.selections.display_ranges(cx)
1551 );
1552
1553 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1554 // Start the cursor at the last line (`y` that was wrapped to a new line)
1555 view.change_selections(None, cx, |s| {
1556 s.select_display_ranges([
1557 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1558 ]);
1559 });
1560
1561 // Moving to the beginning of the line should put us at the start of the second line of
1562 // display text, i.e., the `j`.
1563 view.move_to_beginning_of_line(&move_to_beg, cx);
1564 assert_eq!(
1565 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1566 view.selections.display_ranges(cx)
1567 );
1568
1569 // Moving to the beginning of the line again should be a no-op.
1570 view.move_to_beginning_of_line(&move_to_beg, cx);
1571 assert_eq!(
1572 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1573 view.selections.display_ranges(cx)
1574 );
1575
1576 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1577 // next display line.
1578 view.move_to_end_of_line(&move_to_end, cx);
1579 assert_eq!(
1580 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1581 view.selections.display_ranges(cx)
1582 );
1583
1584 // Moving to the end of the line again should be a no-op.
1585 view.move_to_end_of_line(&move_to_end, cx);
1586 assert_eq!(
1587 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1588 view.selections.display_ranges(cx)
1589 );
1590 });
1591}
1592
1593#[gpui::test]
1594fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1595 init_test(cx, |_| {});
1596
1597 let view = cx.add_window(|cx| {
1598 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1599 build_editor(buffer, cx)
1600 });
1601 _ = view.update(cx, |view, cx| {
1602 view.change_selections(None, cx, |s| {
1603 s.select_display_ranges([
1604 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1605 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1606 ])
1607 });
1608
1609 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1610 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1611
1612 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1613 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1614
1615 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1616 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1617
1618 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1619 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1620
1621 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1622 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1623
1624 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1625 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1626
1627 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1628 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1629
1630 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1631 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1632
1633 view.move_right(&MoveRight, cx);
1634 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1635 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1636
1637 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1638 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1639
1640 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1641 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1642 });
1643}
1644
1645#[gpui::test]
1646fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1647 init_test(cx, |_| {});
1648
1649 let view = cx.add_window(|cx| {
1650 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1651 build_editor(buffer, cx)
1652 });
1653
1654 _ = view.update(cx, |view, cx| {
1655 view.set_wrap_width(Some(140.0.into()), cx);
1656 assert_eq!(
1657 view.display_text(cx),
1658 "use one::{\n two::three::\n four::five\n};"
1659 );
1660
1661 view.change_selections(None, cx, |s| {
1662 s.select_display_ranges([
1663 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1664 ]);
1665 });
1666
1667 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1668 assert_eq!(
1669 view.selections.display_ranges(cx),
1670 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1671 );
1672
1673 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1674 assert_eq!(
1675 view.selections.display_ranges(cx),
1676 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1677 );
1678
1679 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1680 assert_eq!(
1681 view.selections.display_ranges(cx),
1682 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1683 );
1684
1685 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1686 assert_eq!(
1687 view.selections.display_ranges(cx),
1688 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1689 );
1690
1691 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1692 assert_eq!(
1693 view.selections.display_ranges(cx),
1694 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1695 );
1696
1697 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1698 assert_eq!(
1699 view.selections.display_ranges(cx),
1700 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1701 );
1702 });
1703}
1704
1705#[gpui::test]
1706async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1707 init_test(cx, |_| {});
1708 let mut cx = EditorTestContext::new(cx).await;
1709
1710 let line_height = cx.editor(|editor, cx| {
1711 editor
1712 .style()
1713 .unwrap()
1714 .text
1715 .line_height_in_pixels(cx.rem_size())
1716 });
1717 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1718
1719 cx.set_state(
1720 &r#"ˇone
1721 two
1722
1723 three
1724 fourˇ
1725 five
1726
1727 six"#
1728 .unindent(),
1729 );
1730
1731 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1732 cx.assert_editor_state(
1733 &r#"one
1734 two
1735 ˇ
1736 three
1737 four
1738 five
1739 ˇ
1740 six"#
1741 .unindent(),
1742 );
1743
1744 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1745 cx.assert_editor_state(
1746 &r#"one
1747 two
1748
1749 three
1750 four
1751 five
1752 ˇ
1753 sixˇ"#
1754 .unindent(),
1755 );
1756
1757 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1758 cx.assert_editor_state(
1759 &r#"one
1760 two
1761
1762 three
1763 four
1764 five
1765
1766 sixˇ"#
1767 .unindent(),
1768 );
1769
1770 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1771 cx.assert_editor_state(
1772 &r#"one
1773 two
1774
1775 three
1776 four
1777 five
1778 ˇ
1779 six"#
1780 .unindent(),
1781 );
1782
1783 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1784 cx.assert_editor_state(
1785 &r#"one
1786 two
1787 ˇ
1788 three
1789 four
1790 five
1791
1792 six"#
1793 .unindent(),
1794 );
1795
1796 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1797 cx.assert_editor_state(
1798 &r#"ˇone
1799 two
1800
1801 three
1802 four
1803 five
1804
1805 six"#
1806 .unindent(),
1807 );
1808}
1809
1810#[gpui::test]
1811async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1812 init_test(cx, |_| {});
1813 let mut cx = EditorTestContext::new(cx).await;
1814 let line_height = cx.editor(|editor, cx| {
1815 editor
1816 .style()
1817 .unwrap()
1818 .text
1819 .line_height_in_pixels(cx.rem_size())
1820 });
1821 let window = cx.window;
1822 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1823
1824 cx.set_state(
1825 &r#"ˇone
1826 two
1827 three
1828 four
1829 five
1830 six
1831 seven
1832 eight
1833 nine
1834 ten
1835 "#,
1836 );
1837
1838 cx.update_editor(|editor, cx| {
1839 assert_eq!(
1840 editor.snapshot(cx).scroll_position(),
1841 gpui::Point::new(0., 0.)
1842 );
1843 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1844 assert_eq!(
1845 editor.snapshot(cx).scroll_position(),
1846 gpui::Point::new(0., 3.)
1847 );
1848 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1849 assert_eq!(
1850 editor.snapshot(cx).scroll_position(),
1851 gpui::Point::new(0., 6.)
1852 );
1853 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1854 assert_eq!(
1855 editor.snapshot(cx).scroll_position(),
1856 gpui::Point::new(0., 3.)
1857 );
1858
1859 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1860 assert_eq!(
1861 editor.snapshot(cx).scroll_position(),
1862 gpui::Point::new(0., 1.)
1863 );
1864 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
1865 assert_eq!(
1866 editor.snapshot(cx).scroll_position(),
1867 gpui::Point::new(0., 3.)
1868 );
1869 });
1870}
1871
1872#[gpui::test]
1873async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
1874 init_test(cx, |_| {});
1875 let mut cx = EditorTestContext::new(cx).await;
1876
1877 let line_height = cx.update_editor(|editor, cx| {
1878 editor.set_vertical_scroll_margin(2, cx);
1879 editor
1880 .style()
1881 .unwrap()
1882 .text
1883 .line_height_in_pixels(cx.rem_size())
1884 });
1885 let window = cx.window;
1886 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
1887
1888 cx.set_state(
1889 &r#"ˇone
1890 two
1891 three
1892 four
1893 five
1894 six
1895 seven
1896 eight
1897 nine
1898 ten
1899 "#,
1900 );
1901 cx.update_editor(|editor, cx| {
1902 assert_eq!(
1903 editor.snapshot(cx).scroll_position(),
1904 gpui::Point::new(0., 0.0)
1905 );
1906 });
1907
1908 // Add a cursor below the visible area. Since both cursors cannot fit
1909 // on screen, the editor autoscrolls to reveal the newest cursor, and
1910 // allows the vertical scroll margin below that cursor.
1911 cx.update_editor(|editor, cx| {
1912 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1913 selections.select_ranges([
1914 Point::new(0, 0)..Point::new(0, 0),
1915 Point::new(6, 0)..Point::new(6, 0),
1916 ]);
1917 })
1918 });
1919 cx.update_editor(|editor, cx| {
1920 assert_eq!(
1921 editor.snapshot(cx).scroll_position(),
1922 gpui::Point::new(0., 3.0)
1923 );
1924 });
1925
1926 // Move down. The editor cursor scrolls down to track the newest cursor.
1927 cx.update_editor(|editor, cx| {
1928 editor.move_down(&Default::default(), cx);
1929 });
1930 cx.update_editor(|editor, cx| {
1931 assert_eq!(
1932 editor.snapshot(cx).scroll_position(),
1933 gpui::Point::new(0., 4.0)
1934 );
1935 });
1936
1937 // Add a cursor above the visible area. Since both cursors fit on screen,
1938 // the editor scrolls to show both.
1939 cx.update_editor(|editor, cx| {
1940 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1941 selections.select_ranges([
1942 Point::new(1, 0)..Point::new(1, 0),
1943 Point::new(6, 0)..Point::new(6, 0),
1944 ]);
1945 })
1946 });
1947 cx.update_editor(|editor, cx| {
1948 assert_eq!(
1949 editor.snapshot(cx).scroll_position(),
1950 gpui::Point::new(0., 1.0)
1951 );
1952 });
1953}
1954
1955#[gpui::test]
1956async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
1957 init_test(cx, |_| {});
1958 let mut cx = EditorTestContext::new(cx).await;
1959
1960 let line_height = cx.editor(|editor, cx| {
1961 editor
1962 .style()
1963 .unwrap()
1964 .text
1965 .line_height_in_pixels(cx.rem_size())
1966 });
1967 let window = cx.window;
1968 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
1969 cx.set_state(
1970 &r#"
1971 ˇone
1972 two
1973 threeˇ
1974 four
1975 five
1976 six
1977 seven
1978 eight
1979 nine
1980 ten
1981 "#
1982 .unindent(),
1983 );
1984
1985 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1986 cx.assert_editor_state(
1987 &r#"
1988 one
1989 two
1990 three
1991 ˇfour
1992 five
1993 sixˇ
1994 seven
1995 eight
1996 nine
1997 ten
1998 "#
1999 .unindent(),
2000 );
2001
2002 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2003 cx.assert_editor_state(
2004 &r#"
2005 one
2006 two
2007 three
2008 four
2009 five
2010 six
2011 ˇseven
2012 eight
2013 nineˇ
2014 ten
2015 "#
2016 .unindent(),
2017 );
2018
2019 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2020 cx.assert_editor_state(
2021 &r#"
2022 one
2023 two
2024 three
2025 ˇfour
2026 five
2027 sixˇ
2028 seven
2029 eight
2030 nine
2031 ten
2032 "#
2033 .unindent(),
2034 );
2035
2036 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2037 cx.assert_editor_state(
2038 &r#"
2039 ˇone
2040 two
2041 threeˇ
2042 four
2043 five
2044 six
2045 seven
2046 eight
2047 nine
2048 ten
2049 "#
2050 .unindent(),
2051 );
2052
2053 // Test select collapsing
2054 cx.update_editor(|editor, cx| {
2055 editor.move_page_down(&MovePageDown::default(), cx);
2056 editor.move_page_down(&MovePageDown::default(), cx);
2057 editor.move_page_down(&MovePageDown::default(), cx);
2058 });
2059 cx.assert_editor_state(
2060 &r#"
2061 one
2062 two
2063 three
2064 four
2065 five
2066 six
2067 seven
2068 eight
2069 nine
2070 ˇten
2071 ˇ"#
2072 .unindent(),
2073 );
2074}
2075
2076#[gpui::test]
2077async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2078 init_test(cx, |_| {});
2079 let mut cx = EditorTestContext::new(cx).await;
2080 cx.set_state("one «two threeˇ» four");
2081 cx.update_editor(|editor, cx| {
2082 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2083 assert_eq!(editor.text(cx), " four");
2084 });
2085}
2086
2087#[gpui::test]
2088fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2089 init_test(cx, |_| {});
2090
2091 let view = cx.add_window(|cx| {
2092 let buffer = MultiBuffer::build_simple("one two three four", cx);
2093 build_editor(buffer.clone(), cx)
2094 });
2095
2096 _ = view.update(cx, |view, cx| {
2097 view.change_selections(None, cx, |s| {
2098 s.select_display_ranges([
2099 // an empty selection - the preceding word fragment is deleted
2100 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2101 // characters selected - they are deleted
2102 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2103 ])
2104 });
2105 view.delete_to_previous_word_start(
2106 &DeleteToPreviousWordStart {
2107 ignore_newlines: false,
2108 },
2109 cx,
2110 );
2111 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2112 });
2113
2114 _ = view.update(cx, |view, cx| {
2115 view.change_selections(None, cx, |s| {
2116 s.select_display_ranges([
2117 // an empty selection - the following word fragment is deleted
2118 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2119 // characters selected - they are deleted
2120 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2121 ])
2122 });
2123 view.delete_to_next_word_end(
2124 &DeleteToNextWordEnd {
2125 ignore_newlines: false,
2126 },
2127 cx,
2128 );
2129 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2130 });
2131}
2132
2133#[gpui::test]
2134fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2135 init_test(cx, |_| {});
2136
2137 let view = cx.add_window(|cx| {
2138 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2139 build_editor(buffer.clone(), cx)
2140 });
2141 let del_to_prev_word_start = DeleteToPreviousWordStart {
2142 ignore_newlines: false,
2143 };
2144 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2145 ignore_newlines: true,
2146 };
2147
2148 _ = view.update(cx, |view, cx| {
2149 view.change_selections(None, cx, |s| {
2150 s.select_display_ranges([
2151 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2152 ])
2153 });
2154 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2155 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2156 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2157 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2158 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2159 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\n");
2160 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2161 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2");
2162 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2163 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n");
2164 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2165 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2166 });
2167}
2168
2169#[gpui::test]
2170fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2171 init_test(cx, |_| {});
2172
2173 let view = cx.add_window(|cx| {
2174 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2175 build_editor(buffer.clone(), cx)
2176 });
2177 let del_to_next_word_end = DeleteToNextWordEnd {
2178 ignore_newlines: false,
2179 };
2180 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2181 ignore_newlines: true,
2182 };
2183
2184 _ = view.update(cx, |view, cx| {
2185 view.change_selections(None, cx, |s| {
2186 s.select_display_ranges([
2187 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2188 ])
2189 });
2190 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2191 assert_eq!(
2192 view.buffer.read(cx).read(cx).text(),
2193 "one\n two\nthree\n four"
2194 );
2195 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2196 assert_eq!(
2197 view.buffer.read(cx).read(cx).text(),
2198 "\n two\nthree\n four"
2199 );
2200 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2201 assert_eq!(view.buffer.read(cx).read(cx).text(), "two\nthree\n four");
2202 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2203 assert_eq!(view.buffer.read(cx).read(cx).text(), "\nthree\n four");
2204 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2205 assert_eq!(view.buffer.read(cx).read(cx).text(), "\n four");
2206 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2207 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2208 });
2209}
2210
2211#[gpui::test]
2212fn test_newline(cx: &mut TestAppContext) {
2213 init_test(cx, |_| {});
2214
2215 let view = cx.add_window(|cx| {
2216 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2217 build_editor(buffer.clone(), cx)
2218 });
2219
2220 _ = view.update(cx, |view, cx| {
2221 view.change_selections(None, cx, |s| {
2222 s.select_display_ranges([
2223 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2224 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2225 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2226 ])
2227 });
2228
2229 view.newline(&Newline, cx);
2230 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2231 });
2232}
2233
2234#[gpui::test]
2235fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2236 init_test(cx, |_| {});
2237
2238 let editor = cx.add_window(|cx| {
2239 let buffer = MultiBuffer::build_simple(
2240 "
2241 a
2242 b(
2243 X
2244 )
2245 c(
2246 X
2247 )
2248 "
2249 .unindent()
2250 .as_str(),
2251 cx,
2252 );
2253 let mut editor = build_editor(buffer.clone(), cx);
2254 editor.change_selections(None, cx, |s| {
2255 s.select_ranges([
2256 Point::new(2, 4)..Point::new(2, 5),
2257 Point::new(5, 4)..Point::new(5, 5),
2258 ])
2259 });
2260 editor
2261 });
2262
2263 _ = editor.update(cx, |editor, cx| {
2264 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2265 editor.buffer.update(cx, |buffer, cx| {
2266 buffer.edit(
2267 [
2268 (Point::new(1, 2)..Point::new(3, 0), ""),
2269 (Point::new(4, 2)..Point::new(6, 0), ""),
2270 ],
2271 None,
2272 cx,
2273 );
2274 assert_eq!(
2275 buffer.read(cx).text(),
2276 "
2277 a
2278 b()
2279 c()
2280 "
2281 .unindent()
2282 );
2283 });
2284 assert_eq!(
2285 editor.selections.ranges(cx),
2286 &[
2287 Point::new(1, 2)..Point::new(1, 2),
2288 Point::new(2, 2)..Point::new(2, 2),
2289 ],
2290 );
2291
2292 editor.newline(&Newline, cx);
2293 assert_eq!(
2294 editor.text(cx),
2295 "
2296 a
2297 b(
2298 )
2299 c(
2300 )
2301 "
2302 .unindent()
2303 );
2304
2305 // The selections are moved after the inserted newlines
2306 assert_eq!(
2307 editor.selections.ranges(cx),
2308 &[
2309 Point::new(2, 0)..Point::new(2, 0),
2310 Point::new(4, 0)..Point::new(4, 0),
2311 ],
2312 );
2313 });
2314}
2315
2316#[gpui::test]
2317async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2318 init_test(cx, |settings| {
2319 settings.defaults.tab_size = NonZeroU32::new(4)
2320 });
2321
2322 let language = Arc::new(
2323 Language::new(
2324 LanguageConfig::default(),
2325 Some(tree_sitter_rust::language()),
2326 )
2327 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2328 .unwrap(),
2329 );
2330
2331 let mut cx = EditorTestContext::new(cx).await;
2332 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2333 cx.set_state(indoc! {"
2334 const a: ˇA = (
2335 (ˇ
2336 «const_functionˇ»(ˇ),
2337 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2338 )ˇ
2339 ˇ);ˇ
2340 "});
2341
2342 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2343 cx.assert_editor_state(indoc! {"
2344 ˇ
2345 const a: A = (
2346 ˇ
2347 (
2348 ˇ
2349 ˇ
2350 const_function(),
2351 ˇ
2352 ˇ
2353 ˇ
2354 ˇ
2355 something_else,
2356 ˇ
2357 )
2358 ˇ
2359 ˇ
2360 );
2361 "});
2362}
2363
2364#[gpui::test]
2365async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2366 init_test(cx, |settings| {
2367 settings.defaults.tab_size = NonZeroU32::new(4)
2368 });
2369
2370 let language = Arc::new(
2371 Language::new(
2372 LanguageConfig::default(),
2373 Some(tree_sitter_rust::language()),
2374 )
2375 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2376 .unwrap(),
2377 );
2378
2379 let mut cx = EditorTestContext::new(cx).await;
2380 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2381 cx.set_state(indoc! {"
2382 const a: ˇA = (
2383 (ˇ
2384 «const_functionˇ»(ˇ),
2385 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2386 )ˇ
2387 ˇ);ˇ
2388 "});
2389
2390 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2391 cx.assert_editor_state(indoc! {"
2392 const a: A = (
2393 ˇ
2394 (
2395 ˇ
2396 const_function(),
2397 ˇ
2398 ˇ
2399 something_else,
2400 ˇ
2401 ˇ
2402 ˇ
2403 ˇ
2404 )
2405 ˇ
2406 );
2407 ˇ
2408 ˇ
2409 "});
2410}
2411
2412#[gpui::test]
2413async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2414 init_test(cx, |settings| {
2415 settings.defaults.tab_size = NonZeroU32::new(4)
2416 });
2417
2418 let language = Arc::new(Language::new(
2419 LanguageConfig {
2420 line_comments: vec!["//".into()],
2421 ..LanguageConfig::default()
2422 },
2423 None,
2424 ));
2425 {
2426 let mut cx = EditorTestContext::new(cx).await;
2427 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2428 cx.set_state(indoc! {"
2429 // Fooˇ
2430 "});
2431
2432 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2433 cx.assert_editor_state(indoc! {"
2434 // Foo
2435 //ˇ
2436 "});
2437 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2438 cx.set_state(indoc! {"
2439 ˇ// Foo
2440 "});
2441 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2442 cx.assert_editor_state(indoc! {"
2443
2444 ˇ// Foo
2445 "});
2446 }
2447 // Ensure that comment continuations can be disabled.
2448 update_test_language_settings(cx, |settings| {
2449 settings.defaults.extend_comment_on_newline = Some(false);
2450 });
2451 let mut cx = EditorTestContext::new(cx).await;
2452 cx.set_state(indoc! {"
2453 // Fooˇ
2454 "});
2455 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2456 cx.assert_editor_state(indoc! {"
2457 // Foo
2458 ˇ
2459 "});
2460}
2461
2462#[gpui::test]
2463fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2464 init_test(cx, |_| {});
2465
2466 let editor = cx.add_window(|cx| {
2467 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2468 let mut editor = build_editor(buffer.clone(), cx);
2469 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2470 editor
2471 });
2472
2473 _ = editor.update(cx, |editor, cx| {
2474 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2475 editor.buffer.update(cx, |buffer, cx| {
2476 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2477 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2478 });
2479 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2480
2481 editor.insert("Z", cx);
2482 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2483
2484 // The selections are moved after the inserted characters
2485 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2486 });
2487}
2488
2489#[gpui::test]
2490async fn test_tab(cx: &mut gpui::TestAppContext) {
2491 init_test(cx, |settings| {
2492 settings.defaults.tab_size = NonZeroU32::new(3)
2493 });
2494
2495 let mut cx = EditorTestContext::new(cx).await;
2496 cx.set_state(indoc! {"
2497 ˇabˇc
2498 ˇ🏀ˇ🏀ˇefg
2499 dˇ
2500 "});
2501 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2502 cx.assert_editor_state(indoc! {"
2503 ˇab ˇc
2504 ˇ🏀 ˇ🏀 ˇefg
2505 d ˇ
2506 "});
2507
2508 cx.set_state(indoc! {"
2509 a
2510 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2511 "});
2512 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2513 cx.assert_editor_state(indoc! {"
2514 a
2515 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2516 "});
2517}
2518
2519#[gpui::test]
2520async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2521 init_test(cx, |_| {});
2522
2523 let mut cx = EditorTestContext::new(cx).await;
2524 let language = Arc::new(
2525 Language::new(
2526 LanguageConfig::default(),
2527 Some(tree_sitter_rust::language()),
2528 )
2529 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2530 .unwrap(),
2531 );
2532 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2533
2534 // cursors that are already at the suggested indent level insert
2535 // a soft tab. cursors that are to the left of the suggested indent
2536 // auto-indent their line.
2537 cx.set_state(indoc! {"
2538 ˇ
2539 const a: B = (
2540 c(
2541 d(
2542 ˇ
2543 )
2544 ˇ
2545 ˇ )
2546 );
2547 "});
2548 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2549 cx.assert_editor_state(indoc! {"
2550 ˇ
2551 const a: B = (
2552 c(
2553 d(
2554 ˇ
2555 )
2556 ˇ
2557 ˇ)
2558 );
2559 "});
2560
2561 // handle auto-indent when there are multiple cursors on the same line
2562 cx.set_state(indoc! {"
2563 const a: B = (
2564 c(
2565 ˇ ˇ
2566 ˇ )
2567 );
2568 "});
2569 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2570 cx.assert_editor_state(indoc! {"
2571 const a: B = (
2572 c(
2573 ˇ
2574 ˇ)
2575 );
2576 "});
2577}
2578
2579#[gpui::test]
2580async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2581 init_test(cx, |settings| {
2582 settings.defaults.tab_size = NonZeroU32::new(4)
2583 });
2584
2585 let language = Arc::new(
2586 Language::new(
2587 LanguageConfig::default(),
2588 Some(tree_sitter_rust::language()),
2589 )
2590 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2591 .unwrap(),
2592 );
2593
2594 let mut cx = EditorTestContext::new(cx).await;
2595 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2596 cx.set_state(indoc! {"
2597 fn a() {
2598 if b {
2599 \t ˇc
2600 }
2601 }
2602 "});
2603
2604 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2605 cx.assert_editor_state(indoc! {"
2606 fn a() {
2607 if b {
2608 ˇc
2609 }
2610 }
2611 "});
2612}
2613
2614#[gpui::test]
2615async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2616 init_test(cx, |settings| {
2617 settings.defaults.tab_size = NonZeroU32::new(4);
2618 });
2619
2620 let mut cx = EditorTestContext::new(cx).await;
2621
2622 cx.set_state(indoc! {"
2623 «oneˇ» «twoˇ»
2624 three
2625 four
2626 "});
2627 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2628 cx.assert_editor_state(indoc! {"
2629 «oneˇ» «twoˇ»
2630 three
2631 four
2632 "});
2633
2634 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2635 cx.assert_editor_state(indoc! {"
2636 «oneˇ» «twoˇ»
2637 three
2638 four
2639 "});
2640
2641 // select across line ending
2642 cx.set_state(indoc! {"
2643 one two
2644 t«hree
2645 ˇ» four
2646 "});
2647 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2648 cx.assert_editor_state(indoc! {"
2649 one two
2650 t«hree
2651 ˇ» four
2652 "});
2653
2654 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2655 cx.assert_editor_state(indoc! {"
2656 one two
2657 t«hree
2658 ˇ» four
2659 "});
2660
2661 // Ensure that indenting/outdenting works when the cursor is at column 0.
2662 cx.set_state(indoc! {"
2663 one two
2664 ˇthree
2665 four
2666 "});
2667 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2668 cx.assert_editor_state(indoc! {"
2669 one two
2670 ˇthree
2671 four
2672 "});
2673
2674 cx.set_state(indoc! {"
2675 one two
2676 ˇ three
2677 four
2678 "});
2679 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2680 cx.assert_editor_state(indoc! {"
2681 one two
2682 ˇthree
2683 four
2684 "});
2685}
2686
2687#[gpui::test]
2688async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2689 init_test(cx, |settings| {
2690 settings.defaults.hard_tabs = Some(true);
2691 });
2692
2693 let mut cx = EditorTestContext::new(cx).await;
2694
2695 // select two ranges on one line
2696 cx.set_state(indoc! {"
2697 «oneˇ» «twoˇ»
2698 three
2699 four
2700 "});
2701 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2702 cx.assert_editor_state(indoc! {"
2703 \t«oneˇ» «twoˇ»
2704 three
2705 four
2706 "});
2707 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2708 cx.assert_editor_state(indoc! {"
2709 \t\t«oneˇ» «twoˇ»
2710 three
2711 four
2712 "});
2713 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2714 cx.assert_editor_state(indoc! {"
2715 \t«oneˇ» «twoˇ»
2716 three
2717 four
2718 "});
2719 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2720 cx.assert_editor_state(indoc! {"
2721 «oneˇ» «twoˇ»
2722 three
2723 four
2724 "});
2725
2726 // select across a line ending
2727 cx.set_state(indoc! {"
2728 one two
2729 t«hree
2730 ˇ»four
2731 "});
2732 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2733 cx.assert_editor_state(indoc! {"
2734 one two
2735 \tt«hree
2736 ˇ»four
2737 "});
2738 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2739 cx.assert_editor_state(indoc! {"
2740 one two
2741 \t\tt«hree
2742 ˇ»four
2743 "});
2744 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2745 cx.assert_editor_state(indoc! {"
2746 one two
2747 \tt«hree
2748 ˇ»four
2749 "});
2750 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2751 cx.assert_editor_state(indoc! {"
2752 one two
2753 t«hree
2754 ˇ»four
2755 "});
2756
2757 // Ensure that indenting/outdenting works when the cursor is at column 0.
2758 cx.set_state(indoc! {"
2759 one two
2760 ˇthree
2761 four
2762 "});
2763 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2764 cx.assert_editor_state(indoc! {"
2765 one two
2766 ˇthree
2767 four
2768 "});
2769 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2770 cx.assert_editor_state(indoc! {"
2771 one two
2772 \tˇthree
2773 four
2774 "});
2775 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2776 cx.assert_editor_state(indoc! {"
2777 one two
2778 ˇthree
2779 four
2780 "});
2781}
2782
2783#[gpui::test]
2784fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2785 init_test(cx, |settings| {
2786 settings.languages.extend([
2787 (
2788 "TOML".into(),
2789 LanguageSettingsContent {
2790 tab_size: NonZeroU32::new(2),
2791 ..Default::default()
2792 },
2793 ),
2794 (
2795 "Rust".into(),
2796 LanguageSettingsContent {
2797 tab_size: NonZeroU32::new(4),
2798 ..Default::default()
2799 },
2800 ),
2801 ]);
2802 });
2803
2804 let toml_language = Arc::new(Language::new(
2805 LanguageConfig {
2806 name: "TOML".into(),
2807 ..Default::default()
2808 },
2809 None,
2810 ));
2811 let rust_language = Arc::new(Language::new(
2812 LanguageConfig {
2813 name: "Rust".into(),
2814 ..Default::default()
2815 },
2816 None,
2817 ));
2818
2819 let toml_buffer =
2820 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2821 let rust_buffer = cx.new_model(|cx| {
2822 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2823 });
2824 let multibuffer = cx.new_model(|cx| {
2825 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
2826 multibuffer.push_excerpts(
2827 toml_buffer.clone(),
2828 [ExcerptRange {
2829 context: Point::new(0, 0)..Point::new(2, 0),
2830 primary: None,
2831 }],
2832 cx,
2833 );
2834 multibuffer.push_excerpts(
2835 rust_buffer.clone(),
2836 [ExcerptRange {
2837 context: Point::new(0, 0)..Point::new(1, 0),
2838 primary: None,
2839 }],
2840 cx,
2841 );
2842 multibuffer
2843 });
2844
2845 cx.add_window(|cx| {
2846 let mut editor = build_editor(multibuffer, cx);
2847
2848 assert_eq!(
2849 editor.text(cx),
2850 indoc! {"
2851 a = 1
2852 b = 2
2853
2854 const c: usize = 3;
2855 "}
2856 );
2857
2858 select_ranges(
2859 &mut editor,
2860 indoc! {"
2861 «aˇ» = 1
2862 b = 2
2863
2864 «const c:ˇ» usize = 3;
2865 "},
2866 cx,
2867 );
2868
2869 editor.tab(&Tab, cx);
2870 assert_text_with_selections(
2871 &mut editor,
2872 indoc! {"
2873 «aˇ» = 1
2874 b = 2
2875
2876 «const c:ˇ» usize = 3;
2877 "},
2878 cx,
2879 );
2880 editor.tab_prev(&TabPrev, cx);
2881 assert_text_with_selections(
2882 &mut editor,
2883 indoc! {"
2884 «aˇ» = 1
2885 b = 2
2886
2887 «const c:ˇ» usize = 3;
2888 "},
2889 cx,
2890 );
2891
2892 editor
2893 });
2894}
2895
2896#[gpui::test]
2897async fn test_backspace(cx: &mut gpui::TestAppContext) {
2898 init_test(cx, |_| {});
2899
2900 let mut cx = EditorTestContext::new(cx).await;
2901
2902 // Basic backspace
2903 cx.set_state(indoc! {"
2904 onˇe two three
2905 fou«rˇ» five six
2906 seven «ˇeight nine
2907 »ten
2908 "});
2909 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2910 cx.assert_editor_state(indoc! {"
2911 oˇe two three
2912 fouˇ five six
2913 seven ˇten
2914 "});
2915
2916 // Test backspace inside and around indents
2917 cx.set_state(indoc! {"
2918 zero
2919 ˇone
2920 ˇtwo
2921 ˇ ˇ ˇ three
2922 ˇ ˇ four
2923 "});
2924 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2925 cx.assert_editor_state(indoc! {"
2926 zero
2927 ˇone
2928 ˇtwo
2929 ˇ threeˇ four
2930 "});
2931
2932 // Test backspace with line_mode set to true
2933 cx.update_editor(|e, _| e.selections.line_mode = true);
2934 cx.set_state(indoc! {"
2935 The ˇquick ˇbrown
2936 fox jumps over
2937 the lazy dog
2938 ˇThe qu«ick bˇ»rown"});
2939 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2940 cx.assert_editor_state(indoc! {"
2941 ˇfox jumps over
2942 the lazy dogˇ"});
2943}
2944
2945#[gpui::test]
2946async fn test_delete(cx: &mut gpui::TestAppContext) {
2947 init_test(cx, |_| {});
2948
2949 let mut cx = EditorTestContext::new(cx).await;
2950 cx.set_state(indoc! {"
2951 onˇe two three
2952 fou«rˇ» five six
2953 seven «ˇeight nine
2954 »ten
2955 "});
2956 cx.update_editor(|e, cx| e.delete(&Delete, cx));
2957 cx.assert_editor_state(indoc! {"
2958 onˇ two three
2959 fouˇ five six
2960 seven ˇten
2961 "});
2962
2963 // Test backspace with line_mode set to true
2964 cx.update_editor(|e, _| e.selections.line_mode = true);
2965 cx.set_state(indoc! {"
2966 The ˇquick ˇbrown
2967 fox «ˇjum»ps over
2968 the lazy dog
2969 ˇThe qu«ick bˇ»rown"});
2970 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2971 cx.assert_editor_state("ˇthe lazy dogˇ");
2972}
2973
2974#[gpui::test]
2975fn test_delete_line(cx: &mut TestAppContext) {
2976 init_test(cx, |_| {});
2977
2978 let view = cx.add_window(|cx| {
2979 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2980 build_editor(buffer, cx)
2981 });
2982 _ = view.update(cx, |view, cx| {
2983 view.change_selections(None, cx, |s| {
2984 s.select_display_ranges([
2985 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
2986 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
2987 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
2988 ])
2989 });
2990 view.delete_line(&DeleteLine, cx);
2991 assert_eq!(view.display_text(cx), "ghi");
2992 assert_eq!(
2993 view.selections.display_ranges(cx),
2994 vec![
2995 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
2996 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
2997 ]
2998 );
2999 });
3000
3001 let view = cx.add_window(|cx| {
3002 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3003 build_editor(buffer, cx)
3004 });
3005 _ = view.update(cx, |view, cx| {
3006 view.change_selections(None, cx, |s| {
3007 s.select_display_ranges([
3008 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3009 ])
3010 });
3011 view.delete_line(&DeleteLine, cx);
3012 assert_eq!(view.display_text(cx), "ghi\n");
3013 assert_eq!(
3014 view.selections.display_ranges(cx),
3015 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3016 );
3017 });
3018}
3019
3020#[gpui::test]
3021fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3022 init_test(cx, |_| {});
3023
3024 cx.add_window(|cx| {
3025 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3026 let mut editor = build_editor(buffer.clone(), cx);
3027 let buffer = buffer.read(cx).as_singleton().unwrap();
3028
3029 assert_eq!(
3030 editor.selections.ranges::<Point>(cx),
3031 &[Point::new(0, 0)..Point::new(0, 0)]
3032 );
3033
3034 // When on single line, replace newline at end by space
3035 editor.join_lines(&JoinLines, cx);
3036 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3037 assert_eq!(
3038 editor.selections.ranges::<Point>(cx),
3039 &[Point::new(0, 3)..Point::new(0, 3)]
3040 );
3041
3042 // When multiple lines are selected, remove newlines that are spanned by the selection
3043 editor.change_selections(None, cx, |s| {
3044 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3045 });
3046 editor.join_lines(&JoinLines, cx);
3047 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3048 assert_eq!(
3049 editor.selections.ranges::<Point>(cx),
3050 &[Point::new(0, 11)..Point::new(0, 11)]
3051 );
3052
3053 // Undo should be transactional
3054 editor.undo(&Undo, cx);
3055 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3056 assert_eq!(
3057 editor.selections.ranges::<Point>(cx),
3058 &[Point::new(0, 5)..Point::new(2, 2)]
3059 );
3060
3061 // When joining an empty line don't insert a space
3062 editor.change_selections(None, cx, |s| {
3063 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3064 });
3065 editor.join_lines(&JoinLines, cx);
3066 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3067 assert_eq!(
3068 editor.selections.ranges::<Point>(cx),
3069 [Point::new(2, 3)..Point::new(2, 3)]
3070 );
3071
3072 // We can remove trailing newlines
3073 editor.join_lines(&JoinLines, cx);
3074 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3075 assert_eq!(
3076 editor.selections.ranges::<Point>(cx),
3077 [Point::new(2, 3)..Point::new(2, 3)]
3078 );
3079
3080 // We don't blow up on the last line
3081 editor.join_lines(&JoinLines, cx);
3082 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3083 assert_eq!(
3084 editor.selections.ranges::<Point>(cx),
3085 [Point::new(2, 3)..Point::new(2, 3)]
3086 );
3087
3088 // reset to test indentation
3089 editor.buffer.update(cx, |buffer, cx| {
3090 buffer.edit(
3091 [
3092 (Point::new(1, 0)..Point::new(1, 2), " "),
3093 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3094 ],
3095 None,
3096 cx,
3097 )
3098 });
3099
3100 // We remove any leading spaces
3101 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3102 editor.change_selections(None, cx, |s| {
3103 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3104 });
3105 editor.join_lines(&JoinLines, cx);
3106 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3107
3108 // We don't insert a space for a line containing only spaces
3109 editor.join_lines(&JoinLines, cx);
3110 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3111
3112 // We ignore any leading tabs
3113 editor.join_lines(&JoinLines, cx);
3114 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3115
3116 editor
3117 });
3118}
3119
3120#[gpui::test]
3121fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3122 init_test(cx, |_| {});
3123
3124 cx.add_window(|cx| {
3125 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3126 let mut editor = build_editor(buffer.clone(), cx);
3127 let buffer = buffer.read(cx).as_singleton().unwrap();
3128
3129 editor.change_selections(None, cx, |s| {
3130 s.select_ranges([
3131 Point::new(0, 2)..Point::new(1, 1),
3132 Point::new(1, 2)..Point::new(1, 2),
3133 Point::new(3, 1)..Point::new(3, 2),
3134 ])
3135 });
3136
3137 editor.join_lines(&JoinLines, cx);
3138 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3139
3140 assert_eq!(
3141 editor.selections.ranges::<Point>(cx),
3142 [
3143 Point::new(0, 7)..Point::new(0, 7),
3144 Point::new(1, 3)..Point::new(1, 3)
3145 ]
3146 );
3147 editor
3148 });
3149}
3150
3151#[gpui::test]
3152async fn test_join_lines_with_git_diff_base(
3153 executor: BackgroundExecutor,
3154 cx: &mut gpui::TestAppContext,
3155) {
3156 init_test(cx, |_| {});
3157
3158 let mut cx = EditorTestContext::new(cx).await;
3159
3160 let diff_base = r#"
3161 Line 0
3162 Line 1
3163 Line 2
3164 Line 3
3165 "#
3166 .unindent();
3167
3168 cx.set_state(
3169 &r#"
3170 ˇLine 0
3171 Line 1
3172 Line 2
3173 Line 3
3174 "#
3175 .unindent(),
3176 );
3177
3178 cx.set_diff_base(Some(&diff_base));
3179 executor.run_until_parked();
3180
3181 // Join lines
3182 cx.update_editor(|editor, cx| {
3183 editor.join_lines(&JoinLines, cx);
3184 });
3185 executor.run_until_parked();
3186
3187 cx.assert_editor_state(
3188 &r#"
3189 Line 0ˇ Line 1
3190 Line 2
3191 Line 3
3192 "#
3193 .unindent(),
3194 );
3195 // Join again
3196 cx.update_editor(|editor, cx| {
3197 editor.join_lines(&JoinLines, cx);
3198 });
3199 executor.run_until_parked();
3200
3201 cx.assert_editor_state(
3202 &r#"
3203 Line 0 Line 1ˇ Line 2
3204 Line 3
3205 "#
3206 .unindent(),
3207 );
3208}
3209
3210#[gpui::test]
3211async fn test_custom_newlines_cause_no_false_positive_diffs(
3212 executor: BackgroundExecutor,
3213 cx: &mut gpui::TestAppContext,
3214) {
3215 init_test(cx, |_| {});
3216 let mut cx = EditorTestContext::new(cx).await;
3217 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3218 cx.set_diff_base(Some("Line 0\r\nLine 1\r\nLine 2\r\nLine 3"));
3219 executor.run_until_parked();
3220
3221 cx.update_editor(|editor, cx| {
3222 assert_eq!(
3223 editor
3224 .buffer()
3225 .read(cx)
3226 .snapshot(cx)
3227 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
3228 .collect::<Vec<_>>(),
3229 Vec::new(),
3230 "Should not have any diffs for files with custom newlines"
3231 );
3232 });
3233}
3234
3235#[gpui::test]
3236async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3237 init_test(cx, |_| {});
3238
3239 let mut cx = EditorTestContext::new(cx).await;
3240
3241 // Test sort_lines_case_insensitive()
3242 cx.set_state(indoc! {"
3243 «z
3244 y
3245 x
3246 Z
3247 Y
3248 Xˇ»
3249 "});
3250 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3251 cx.assert_editor_state(indoc! {"
3252 «x
3253 X
3254 y
3255 Y
3256 z
3257 Zˇ»
3258 "});
3259
3260 // Test reverse_lines()
3261 cx.set_state(indoc! {"
3262 «5
3263 4
3264 3
3265 2
3266 1ˇ»
3267 "});
3268 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3269 cx.assert_editor_state(indoc! {"
3270 «1
3271 2
3272 3
3273 4
3274 5ˇ»
3275 "});
3276
3277 // Skip testing shuffle_line()
3278
3279 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3280 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3281
3282 // Don't manipulate when cursor is on single line, but expand the selection
3283 cx.set_state(indoc! {"
3284 ddˇdd
3285 ccc
3286 bb
3287 a
3288 "});
3289 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3290 cx.assert_editor_state(indoc! {"
3291 «ddddˇ»
3292 ccc
3293 bb
3294 a
3295 "});
3296
3297 // Basic manipulate case
3298 // Start selection moves to column 0
3299 // End of selection shrinks to fit shorter line
3300 cx.set_state(indoc! {"
3301 dd«d
3302 ccc
3303 bb
3304 aaaaaˇ»
3305 "});
3306 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3307 cx.assert_editor_state(indoc! {"
3308 «aaaaa
3309 bb
3310 ccc
3311 dddˇ»
3312 "});
3313
3314 // Manipulate case with newlines
3315 cx.set_state(indoc! {"
3316 dd«d
3317 ccc
3318
3319 bb
3320 aaaaa
3321
3322 ˇ»
3323 "});
3324 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3325 cx.assert_editor_state(indoc! {"
3326 «
3327
3328 aaaaa
3329 bb
3330 ccc
3331 dddˇ»
3332
3333 "});
3334
3335 // Adding new line
3336 cx.set_state(indoc! {"
3337 aa«a
3338 bbˇ»b
3339 "});
3340 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3341 cx.assert_editor_state(indoc! {"
3342 «aaa
3343 bbb
3344 added_lineˇ»
3345 "});
3346
3347 // Removing line
3348 cx.set_state(indoc! {"
3349 aa«a
3350 bbbˇ»
3351 "});
3352 cx.update_editor(|e, cx| {
3353 e.manipulate_lines(cx, |lines| {
3354 lines.pop();
3355 })
3356 });
3357 cx.assert_editor_state(indoc! {"
3358 «aaaˇ»
3359 "});
3360
3361 // Removing all lines
3362 cx.set_state(indoc! {"
3363 aa«a
3364 bbbˇ»
3365 "});
3366 cx.update_editor(|e, cx| {
3367 e.manipulate_lines(cx, |lines| {
3368 lines.drain(..);
3369 })
3370 });
3371 cx.assert_editor_state(indoc! {"
3372 ˇ
3373 "});
3374}
3375
3376#[gpui::test]
3377async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3378 init_test(cx, |_| {});
3379
3380 let mut cx = EditorTestContext::new(cx).await;
3381
3382 // Consider continuous selection as single selection
3383 cx.set_state(indoc! {"
3384 Aaa«aa
3385 cˇ»c«c
3386 bb
3387 aaaˇ»aa
3388 "});
3389 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3390 cx.assert_editor_state(indoc! {"
3391 «Aaaaa
3392 ccc
3393 bb
3394 aaaaaˇ»
3395 "});
3396
3397 cx.set_state(indoc! {"
3398 Aaa«aa
3399 cˇ»c«c
3400 bb
3401 aaaˇ»aa
3402 "});
3403 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3404 cx.assert_editor_state(indoc! {"
3405 «Aaaaa
3406 ccc
3407 bbˇ»
3408 "});
3409
3410 // Consider non continuous selection as distinct dedup operations
3411 cx.set_state(indoc! {"
3412 «aaaaa
3413 bb
3414 aaaaa
3415 aaaaaˇ»
3416
3417 aaa«aaˇ»
3418 "});
3419 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3420 cx.assert_editor_state(indoc! {"
3421 «aaaaa
3422 bbˇ»
3423
3424 «aaaaaˇ»
3425 "});
3426}
3427
3428#[gpui::test]
3429async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3430 init_test(cx, |_| {});
3431
3432 let mut cx = EditorTestContext::new(cx).await;
3433
3434 cx.set_state(indoc! {"
3435 «Aaa
3436 aAa
3437 Aaaˇ»
3438 "});
3439 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3440 cx.assert_editor_state(indoc! {"
3441 «Aaa
3442 aAaˇ»
3443 "});
3444
3445 cx.set_state(indoc! {"
3446 «Aaa
3447 aAa
3448 aaAˇ»
3449 "});
3450 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3451 cx.assert_editor_state(indoc! {"
3452 «Aaaˇ»
3453 "});
3454}
3455
3456#[gpui::test]
3457async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3458 init_test(cx, |_| {});
3459
3460 let mut cx = EditorTestContext::new(cx).await;
3461
3462 // Manipulate with multiple selections on a single line
3463 cx.set_state(indoc! {"
3464 dd«dd
3465 cˇ»c«c
3466 bb
3467 aaaˇ»aa
3468 "});
3469 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3470 cx.assert_editor_state(indoc! {"
3471 «aaaaa
3472 bb
3473 ccc
3474 ddddˇ»
3475 "});
3476
3477 // Manipulate with multiple disjoin selections
3478 cx.set_state(indoc! {"
3479 5«
3480 4
3481 3
3482 2
3483 1ˇ»
3484
3485 dd«dd
3486 ccc
3487 bb
3488 aaaˇ»aa
3489 "});
3490 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3491 cx.assert_editor_state(indoc! {"
3492 «1
3493 2
3494 3
3495 4
3496 5ˇ»
3497
3498 «aaaaa
3499 bb
3500 ccc
3501 ddddˇ»
3502 "});
3503
3504 // Adding lines on each selection
3505 cx.set_state(indoc! {"
3506 2«
3507 1ˇ»
3508
3509 bb«bb
3510 aaaˇ»aa
3511 "});
3512 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3513 cx.assert_editor_state(indoc! {"
3514 «2
3515 1
3516 added lineˇ»
3517
3518 «bbbb
3519 aaaaa
3520 added lineˇ»
3521 "});
3522
3523 // Removing lines on each selection
3524 cx.set_state(indoc! {"
3525 2«
3526 1ˇ»
3527
3528 bb«bb
3529 aaaˇ»aa
3530 "});
3531 cx.update_editor(|e, cx| {
3532 e.manipulate_lines(cx, |lines| {
3533 lines.pop();
3534 })
3535 });
3536 cx.assert_editor_state(indoc! {"
3537 «2ˇ»
3538
3539 «bbbbˇ»
3540 "});
3541}
3542
3543#[gpui::test]
3544async fn test_manipulate_text(cx: &mut TestAppContext) {
3545 init_test(cx, |_| {});
3546
3547 let mut cx = EditorTestContext::new(cx).await;
3548
3549 // Test convert_to_upper_case()
3550 cx.set_state(indoc! {"
3551 «hello worldˇ»
3552 "});
3553 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3554 cx.assert_editor_state(indoc! {"
3555 «HELLO WORLDˇ»
3556 "});
3557
3558 // Test convert_to_lower_case()
3559 cx.set_state(indoc! {"
3560 «HELLO WORLDˇ»
3561 "});
3562 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3563 cx.assert_editor_state(indoc! {"
3564 «hello worldˇ»
3565 "});
3566
3567 // Test multiple line, single selection case
3568 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3569 cx.set_state(indoc! {"
3570 «The quick brown
3571 fox jumps over
3572 the lazy dogˇ»
3573 "});
3574 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3575 cx.assert_editor_state(indoc! {"
3576 «The Quick Brown
3577 Fox Jumps Over
3578 The Lazy Dogˇ»
3579 "});
3580
3581 // Test multiple line, single selection case
3582 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3583 cx.set_state(indoc! {"
3584 «The quick brown
3585 fox jumps over
3586 the lazy dogˇ»
3587 "});
3588 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3589 cx.assert_editor_state(indoc! {"
3590 «TheQuickBrown
3591 FoxJumpsOver
3592 TheLazyDogˇ»
3593 "});
3594
3595 // From here on out, test more complex cases of manipulate_text()
3596
3597 // Test no selection case - should affect words cursors are in
3598 // Cursor at beginning, middle, and end of word
3599 cx.set_state(indoc! {"
3600 ˇhello big beauˇtiful worldˇ
3601 "});
3602 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3603 cx.assert_editor_state(indoc! {"
3604 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3605 "});
3606
3607 // Test multiple selections on a single line and across multiple lines
3608 cx.set_state(indoc! {"
3609 «Theˇ» quick «brown
3610 foxˇ» jumps «overˇ»
3611 the «lazyˇ» dog
3612 "});
3613 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3614 cx.assert_editor_state(indoc! {"
3615 «THEˇ» quick «BROWN
3616 FOXˇ» jumps «OVERˇ»
3617 the «LAZYˇ» dog
3618 "});
3619
3620 // Test case where text length grows
3621 cx.set_state(indoc! {"
3622 «tschüߡ»
3623 "});
3624 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3625 cx.assert_editor_state(indoc! {"
3626 «TSCHÜSSˇ»
3627 "});
3628
3629 // Test to make sure we don't crash when text shrinks
3630 cx.set_state(indoc! {"
3631 aaa_bbbˇ
3632 "});
3633 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3634 cx.assert_editor_state(indoc! {"
3635 «aaaBbbˇ»
3636 "});
3637
3638 // Test to make sure we all aware of the fact that each word can grow and shrink
3639 // Final selections should be aware of this fact
3640 cx.set_state(indoc! {"
3641 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3642 "});
3643 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3644 cx.assert_editor_state(indoc! {"
3645 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3646 "});
3647
3648 cx.set_state(indoc! {"
3649 «hElLo, WoRld!ˇ»
3650 "});
3651 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3652 cx.assert_editor_state(indoc! {"
3653 «HeLlO, wOrLD!ˇ»
3654 "});
3655}
3656
3657#[gpui::test]
3658fn test_duplicate_line(cx: &mut TestAppContext) {
3659 init_test(cx, |_| {});
3660
3661 let view = cx.add_window(|cx| {
3662 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3663 build_editor(buffer, cx)
3664 });
3665 _ = view.update(cx, |view, cx| {
3666 view.change_selections(None, cx, |s| {
3667 s.select_display_ranges([
3668 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3669 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3670 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3671 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3672 ])
3673 });
3674 view.duplicate_line_down(&DuplicateLineDown, cx);
3675 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3676 assert_eq!(
3677 view.selections.display_ranges(cx),
3678 vec![
3679 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3680 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3681 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3682 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3683 ]
3684 );
3685 });
3686
3687 let view = cx.add_window(|cx| {
3688 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3689 build_editor(buffer, cx)
3690 });
3691 _ = view.update(cx, |view, cx| {
3692 view.change_selections(None, cx, |s| {
3693 s.select_display_ranges([
3694 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3695 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3696 ])
3697 });
3698 view.duplicate_line_down(&DuplicateLineDown, cx);
3699 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3700 assert_eq!(
3701 view.selections.display_ranges(cx),
3702 vec![
3703 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3704 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3705 ]
3706 );
3707 });
3708
3709 // With `move_upwards` the selections stay in place, except for
3710 // the lines inserted above them
3711 let view = cx.add_window(|cx| {
3712 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3713 build_editor(buffer, cx)
3714 });
3715 _ = view.update(cx, |view, cx| {
3716 view.change_selections(None, cx, |s| {
3717 s.select_display_ranges([
3718 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3719 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3720 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3721 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3722 ])
3723 });
3724 view.duplicate_line_up(&DuplicateLineUp, cx);
3725 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3726 assert_eq!(
3727 view.selections.display_ranges(cx),
3728 vec![
3729 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3730 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3731 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3732 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3733 ]
3734 );
3735 });
3736
3737 let view = cx.add_window(|cx| {
3738 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3739 build_editor(buffer, cx)
3740 });
3741 _ = view.update(cx, |view, cx| {
3742 view.change_selections(None, cx, |s| {
3743 s.select_display_ranges([
3744 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3745 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3746 ])
3747 });
3748 view.duplicate_line_up(&DuplicateLineUp, cx);
3749 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3750 assert_eq!(
3751 view.selections.display_ranges(cx),
3752 vec![
3753 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3754 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3755 ]
3756 );
3757 });
3758}
3759
3760#[gpui::test]
3761fn test_move_line_up_down(cx: &mut TestAppContext) {
3762 init_test(cx, |_| {});
3763
3764 let view = cx.add_window(|cx| {
3765 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3766 build_editor(buffer, cx)
3767 });
3768 _ = view.update(cx, |view, cx| {
3769 view.fold_ranges(
3770 vec![
3771 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3772 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3773 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3774 ],
3775 true,
3776 cx,
3777 );
3778 view.change_selections(None, cx, |s| {
3779 s.select_display_ranges([
3780 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3781 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3782 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3783 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3784 ])
3785 });
3786 assert_eq!(
3787 view.display_text(cx),
3788 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3789 );
3790
3791 view.move_line_up(&MoveLineUp, cx);
3792 assert_eq!(
3793 view.display_text(cx),
3794 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3795 );
3796 assert_eq!(
3797 view.selections.display_ranges(cx),
3798 vec![
3799 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3800 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3801 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3802 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3803 ]
3804 );
3805 });
3806
3807 _ = view.update(cx, |view, cx| {
3808 view.move_line_down(&MoveLineDown, cx);
3809 assert_eq!(
3810 view.display_text(cx),
3811 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3812 );
3813 assert_eq!(
3814 view.selections.display_ranges(cx),
3815 vec![
3816 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3817 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3818 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3819 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3820 ]
3821 );
3822 });
3823
3824 _ = view.update(cx, |view, cx| {
3825 view.move_line_down(&MoveLineDown, cx);
3826 assert_eq!(
3827 view.display_text(cx),
3828 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3829 );
3830 assert_eq!(
3831 view.selections.display_ranges(cx),
3832 vec![
3833 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3834 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3835 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3836 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3837 ]
3838 );
3839 });
3840
3841 _ = view.update(cx, |view, cx| {
3842 view.move_line_up(&MoveLineUp, cx);
3843 assert_eq!(
3844 view.display_text(cx),
3845 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3846 );
3847 assert_eq!(
3848 view.selections.display_ranges(cx),
3849 vec![
3850 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3851 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3852 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3853 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3854 ]
3855 );
3856 });
3857}
3858
3859#[gpui::test]
3860fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3861 init_test(cx, |_| {});
3862
3863 let editor = cx.add_window(|cx| {
3864 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3865 build_editor(buffer, cx)
3866 });
3867 _ = editor.update(cx, |editor, cx| {
3868 let snapshot = editor.buffer.read(cx).snapshot(cx);
3869 editor.insert_blocks(
3870 [BlockProperties {
3871 style: BlockStyle::Fixed,
3872 position: snapshot.anchor_after(Point::new(2, 0)),
3873 disposition: BlockDisposition::Below,
3874 height: 1,
3875 render: Box::new(|_| div().into_any()),
3876 priority: 0,
3877 }],
3878 Some(Autoscroll::fit()),
3879 cx,
3880 );
3881 editor.change_selections(None, cx, |s| {
3882 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
3883 });
3884 editor.move_line_down(&MoveLineDown, cx);
3885 });
3886}
3887
3888#[gpui::test]
3889fn test_transpose(cx: &mut TestAppContext) {
3890 init_test(cx, |_| {});
3891
3892 _ = cx.add_window(|cx| {
3893 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
3894 editor.set_style(EditorStyle::default(), cx);
3895 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
3896 editor.transpose(&Default::default(), cx);
3897 assert_eq!(editor.text(cx), "bac");
3898 assert_eq!(editor.selections.ranges(cx), [2..2]);
3899
3900 editor.transpose(&Default::default(), cx);
3901 assert_eq!(editor.text(cx), "bca");
3902 assert_eq!(editor.selections.ranges(cx), [3..3]);
3903
3904 editor.transpose(&Default::default(), cx);
3905 assert_eq!(editor.text(cx), "bac");
3906 assert_eq!(editor.selections.ranges(cx), [3..3]);
3907
3908 editor
3909 });
3910
3911 _ = cx.add_window(|cx| {
3912 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3913 editor.set_style(EditorStyle::default(), cx);
3914 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
3915 editor.transpose(&Default::default(), cx);
3916 assert_eq!(editor.text(cx), "acb\nde");
3917 assert_eq!(editor.selections.ranges(cx), [3..3]);
3918
3919 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3920 editor.transpose(&Default::default(), cx);
3921 assert_eq!(editor.text(cx), "acbd\ne");
3922 assert_eq!(editor.selections.ranges(cx), [5..5]);
3923
3924 editor.transpose(&Default::default(), cx);
3925 assert_eq!(editor.text(cx), "acbde\n");
3926 assert_eq!(editor.selections.ranges(cx), [6..6]);
3927
3928 editor.transpose(&Default::default(), cx);
3929 assert_eq!(editor.text(cx), "acbd\ne");
3930 assert_eq!(editor.selections.ranges(cx), [6..6]);
3931
3932 editor
3933 });
3934
3935 _ = cx.add_window(|cx| {
3936 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3937 editor.set_style(EditorStyle::default(), cx);
3938 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
3939 editor.transpose(&Default::default(), cx);
3940 assert_eq!(editor.text(cx), "bacd\ne");
3941 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
3942
3943 editor.transpose(&Default::default(), cx);
3944 assert_eq!(editor.text(cx), "bcade\n");
3945 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
3946
3947 editor.transpose(&Default::default(), cx);
3948 assert_eq!(editor.text(cx), "bcda\ne");
3949 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3950
3951 editor.transpose(&Default::default(), cx);
3952 assert_eq!(editor.text(cx), "bcade\n");
3953 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3954
3955 editor.transpose(&Default::default(), cx);
3956 assert_eq!(editor.text(cx), "bcaed\n");
3957 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
3958
3959 editor
3960 });
3961
3962 _ = cx.add_window(|cx| {
3963 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
3964 editor.set_style(EditorStyle::default(), cx);
3965 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3966 editor.transpose(&Default::default(), cx);
3967 assert_eq!(editor.text(cx), "🏀🍐✋");
3968 assert_eq!(editor.selections.ranges(cx), [8..8]);
3969
3970 editor.transpose(&Default::default(), cx);
3971 assert_eq!(editor.text(cx), "🏀✋🍐");
3972 assert_eq!(editor.selections.ranges(cx), [11..11]);
3973
3974 editor.transpose(&Default::default(), cx);
3975 assert_eq!(editor.text(cx), "🏀🍐✋");
3976 assert_eq!(editor.selections.ranges(cx), [11..11]);
3977
3978 editor
3979 });
3980}
3981
3982#[gpui::test]
3983async fn test_clipboard(cx: &mut gpui::TestAppContext) {
3984 init_test(cx, |_| {});
3985
3986 let mut cx = EditorTestContext::new(cx).await;
3987
3988 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
3989 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3990 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
3991
3992 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
3993 cx.set_state("two ˇfour ˇsix ˇ");
3994 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3995 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
3996
3997 // Paste again but with only two cursors. Since the number of cursors doesn't
3998 // match the number of slices in the clipboard, the entire clipboard text
3999 // is pasted at each cursor.
4000 cx.set_state("ˇtwo one✅ four three six five ˇ");
4001 cx.update_editor(|e, cx| {
4002 e.handle_input("( ", cx);
4003 e.paste(&Paste, cx);
4004 e.handle_input(") ", cx);
4005 });
4006 cx.assert_editor_state(
4007 &([
4008 "( one✅ ",
4009 "three ",
4010 "five ) ˇtwo one✅ four three six five ( one✅ ",
4011 "three ",
4012 "five ) ˇ",
4013 ]
4014 .join("\n")),
4015 );
4016
4017 // Cut with three selections, one of which is full-line.
4018 cx.set_state(indoc! {"
4019 1«2ˇ»3
4020 4ˇ567
4021 «8ˇ»9"});
4022 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4023 cx.assert_editor_state(indoc! {"
4024 1ˇ3
4025 ˇ9"});
4026
4027 // Paste with three selections, noticing how the copied selection that was full-line
4028 // gets inserted before the second cursor.
4029 cx.set_state(indoc! {"
4030 1ˇ3
4031 9ˇ
4032 «oˇ»ne"});
4033 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4034 cx.assert_editor_state(indoc! {"
4035 12ˇ3
4036 4567
4037 9ˇ
4038 8ˇne"});
4039
4040 // Copy with a single cursor only, which writes the whole line into the clipboard.
4041 cx.set_state(indoc! {"
4042 The quick brown
4043 fox juˇmps over
4044 the lazy dog"});
4045 cx.update_editor(|e, cx| e.copy(&Copy, cx));
4046 assert_eq!(
4047 cx.read_from_clipboard()
4048 .and_then(|item| item.text().as_deref().map(str::to_string)),
4049 Some("fox jumps over\n".to_string())
4050 );
4051
4052 // Paste with three selections, noticing how the copied full-line selection is inserted
4053 // before the empty selections but replaces the selection that is non-empty.
4054 cx.set_state(indoc! {"
4055 Tˇhe quick brown
4056 «foˇ»x jumps over
4057 tˇhe lazy dog"});
4058 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4059 cx.assert_editor_state(indoc! {"
4060 fox jumps over
4061 Tˇhe quick brown
4062 fox jumps over
4063 ˇx jumps over
4064 fox jumps over
4065 tˇhe lazy dog"});
4066}
4067
4068#[gpui::test]
4069async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4070 init_test(cx, |_| {});
4071
4072 let mut cx = EditorTestContext::new(cx).await;
4073 let language = Arc::new(Language::new(
4074 LanguageConfig::default(),
4075 Some(tree_sitter_rust::language()),
4076 ));
4077 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4078
4079 // Cut an indented block, without the leading whitespace.
4080 cx.set_state(indoc! {"
4081 const a: B = (
4082 c(),
4083 «d(
4084 e,
4085 f
4086 )ˇ»
4087 );
4088 "});
4089 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4090 cx.assert_editor_state(indoc! {"
4091 const a: B = (
4092 c(),
4093 ˇ
4094 );
4095 "});
4096
4097 // Paste it at the same position.
4098 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4099 cx.assert_editor_state(indoc! {"
4100 const a: B = (
4101 c(),
4102 d(
4103 e,
4104 f
4105 )ˇ
4106 );
4107 "});
4108
4109 // Paste it at a line with a lower indent level.
4110 cx.set_state(indoc! {"
4111 ˇ
4112 const a: B = (
4113 c(),
4114 );
4115 "});
4116 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4117 cx.assert_editor_state(indoc! {"
4118 d(
4119 e,
4120 f
4121 )ˇ
4122 const a: B = (
4123 c(),
4124 );
4125 "});
4126
4127 // Cut an indented block, with the leading whitespace.
4128 cx.set_state(indoc! {"
4129 const a: B = (
4130 c(),
4131 « d(
4132 e,
4133 f
4134 )
4135 ˇ»);
4136 "});
4137 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4138 cx.assert_editor_state(indoc! {"
4139 const a: B = (
4140 c(),
4141 ˇ);
4142 "});
4143
4144 // Paste it at the same position.
4145 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4146 cx.assert_editor_state(indoc! {"
4147 const a: B = (
4148 c(),
4149 d(
4150 e,
4151 f
4152 )
4153 ˇ);
4154 "});
4155
4156 // Paste it at a line with a higher indent level.
4157 cx.set_state(indoc! {"
4158 const a: B = (
4159 c(),
4160 d(
4161 e,
4162 fˇ
4163 )
4164 );
4165 "});
4166 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4167 cx.assert_editor_state(indoc! {"
4168 const a: B = (
4169 c(),
4170 d(
4171 e,
4172 f d(
4173 e,
4174 f
4175 )
4176 ˇ
4177 )
4178 );
4179 "});
4180}
4181
4182#[gpui::test]
4183fn test_select_all(cx: &mut TestAppContext) {
4184 init_test(cx, |_| {});
4185
4186 let view = cx.add_window(|cx| {
4187 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4188 build_editor(buffer, cx)
4189 });
4190 _ = view.update(cx, |view, cx| {
4191 view.select_all(&SelectAll, cx);
4192 assert_eq!(
4193 view.selections.display_ranges(cx),
4194 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4195 );
4196 });
4197}
4198
4199#[gpui::test]
4200fn test_select_line(cx: &mut TestAppContext) {
4201 init_test(cx, |_| {});
4202
4203 let view = cx.add_window(|cx| {
4204 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4205 build_editor(buffer, cx)
4206 });
4207 _ = view.update(cx, |view, cx| {
4208 view.change_selections(None, cx, |s| {
4209 s.select_display_ranges([
4210 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4211 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4212 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4213 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4214 ])
4215 });
4216 view.select_line(&SelectLine, cx);
4217 assert_eq!(
4218 view.selections.display_ranges(cx),
4219 vec![
4220 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4221 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4222 ]
4223 );
4224 });
4225
4226 _ = view.update(cx, |view, cx| {
4227 view.select_line(&SelectLine, cx);
4228 assert_eq!(
4229 view.selections.display_ranges(cx),
4230 vec![
4231 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4232 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4233 ]
4234 );
4235 });
4236
4237 _ = view.update(cx, |view, cx| {
4238 view.select_line(&SelectLine, cx);
4239 assert_eq!(
4240 view.selections.display_ranges(cx),
4241 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4242 );
4243 });
4244}
4245
4246#[gpui::test]
4247fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4248 init_test(cx, |_| {});
4249
4250 let view = cx.add_window(|cx| {
4251 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4252 build_editor(buffer, cx)
4253 });
4254 _ = view.update(cx, |view, cx| {
4255 view.fold_ranges(
4256 vec![
4257 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4258 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4259 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4260 ],
4261 true,
4262 cx,
4263 );
4264 view.change_selections(None, cx, |s| {
4265 s.select_display_ranges([
4266 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4267 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4268 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4269 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4270 ])
4271 });
4272 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4273 });
4274
4275 _ = view.update(cx, |view, cx| {
4276 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4277 assert_eq!(
4278 view.display_text(cx),
4279 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4280 );
4281 assert_eq!(
4282 view.selections.display_ranges(cx),
4283 [
4284 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4285 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4286 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4287 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4288 ]
4289 );
4290 });
4291
4292 _ = view.update(cx, |view, cx| {
4293 view.change_selections(None, cx, |s| {
4294 s.select_display_ranges([
4295 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4296 ])
4297 });
4298 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4299 assert_eq!(
4300 view.display_text(cx),
4301 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4302 );
4303 assert_eq!(
4304 view.selections.display_ranges(cx),
4305 [
4306 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4307 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4308 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4309 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4310 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4311 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4312 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4313 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4314 ]
4315 );
4316 });
4317}
4318
4319#[gpui::test]
4320async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4321 init_test(cx, |_| {});
4322
4323 let mut cx = EditorTestContext::new(cx).await;
4324
4325 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4326 cx.set_state(indoc!(
4327 r#"abc
4328 defˇghi
4329
4330 jk
4331 nlmo
4332 "#
4333 ));
4334
4335 cx.update_editor(|editor, cx| {
4336 editor.add_selection_above(&Default::default(), cx);
4337 });
4338
4339 cx.assert_editor_state(indoc!(
4340 r#"abcˇ
4341 defˇghi
4342
4343 jk
4344 nlmo
4345 "#
4346 ));
4347
4348 cx.update_editor(|editor, cx| {
4349 editor.add_selection_above(&Default::default(), cx);
4350 });
4351
4352 cx.assert_editor_state(indoc!(
4353 r#"abcˇ
4354 defˇghi
4355
4356 jk
4357 nlmo
4358 "#
4359 ));
4360
4361 cx.update_editor(|view, cx| {
4362 view.add_selection_below(&Default::default(), cx);
4363 });
4364
4365 cx.assert_editor_state(indoc!(
4366 r#"abc
4367 defˇghi
4368
4369 jk
4370 nlmo
4371 "#
4372 ));
4373
4374 cx.update_editor(|view, cx| {
4375 view.undo_selection(&Default::default(), cx);
4376 });
4377
4378 cx.assert_editor_state(indoc!(
4379 r#"abcˇ
4380 defˇghi
4381
4382 jk
4383 nlmo
4384 "#
4385 ));
4386
4387 cx.update_editor(|view, cx| {
4388 view.redo_selection(&Default::default(), cx);
4389 });
4390
4391 cx.assert_editor_state(indoc!(
4392 r#"abc
4393 defˇghi
4394
4395 jk
4396 nlmo
4397 "#
4398 ));
4399
4400 cx.update_editor(|view, cx| {
4401 view.add_selection_below(&Default::default(), cx);
4402 });
4403
4404 cx.assert_editor_state(indoc!(
4405 r#"abc
4406 defˇghi
4407
4408 jk
4409 nlmˇo
4410 "#
4411 ));
4412
4413 cx.update_editor(|view, cx| {
4414 view.add_selection_below(&Default::default(), cx);
4415 });
4416
4417 cx.assert_editor_state(indoc!(
4418 r#"abc
4419 defˇghi
4420
4421 jk
4422 nlmˇo
4423 "#
4424 ));
4425
4426 // change selections
4427 cx.set_state(indoc!(
4428 r#"abc
4429 def«ˇg»hi
4430
4431 jk
4432 nlmo
4433 "#
4434 ));
4435
4436 cx.update_editor(|view, cx| {
4437 view.add_selection_below(&Default::default(), cx);
4438 });
4439
4440 cx.assert_editor_state(indoc!(
4441 r#"abc
4442 def«ˇg»hi
4443
4444 jk
4445 nlm«ˇo»
4446 "#
4447 ));
4448
4449 cx.update_editor(|view, cx| {
4450 view.add_selection_below(&Default::default(), cx);
4451 });
4452
4453 cx.assert_editor_state(indoc!(
4454 r#"abc
4455 def«ˇg»hi
4456
4457 jk
4458 nlm«ˇo»
4459 "#
4460 ));
4461
4462 cx.update_editor(|view, cx| {
4463 view.add_selection_above(&Default::default(), cx);
4464 });
4465
4466 cx.assert_editor_state(indoc!(
4467 r#"abc
4468 def«ˇg»hi
4469
4470 jk
4471 nlmo
4472 "#
4473 ));
4474
4475 cx.update_editor(|view, cx| {
4476 view.add_selection_above(&Default::default(), cx);
4477 });
4478
4479 cx.assert_editor_state(indoc!(
4480 r#"abc
4481 def«ˇg»hi
4482
4483 jk
4484 nlmo
4485 "#
4486 ));
4487
4488 // Change selections again
4489 cx.set_state(indoc!(
4490 r#"a«bc
4491 defgˇ»hi
4492
4493 jk
4494 nlmo
4495 "#
4496 ));
4497
4498 cx.update_editor(|view, cx| {
4499 view.add_selection_below(&Default::default(), cx);
4500 });
4501
4502 cx.assert_editor_state(indoc!(
4503 r#"a«bcˇ»
4504 d«efgˇ»hi
4505
4506 j«kˇ»
4507 nlmo
4508 "#
4509 ));
4510
4511 cx.update_editor(|view, cx| {
4512 view.add_selection_below(&Default::default(), cx);
4513 });
4514 cx.assert_editor_state(indoc!(
4515 r#"a«bcˇ»
4516 d«efgˇ»hi
4517
4518 j«kˇ»
4519 n«lmoˇ»
4520 "#
4521 ));
4522 cx.update_editor(|view, cx| {
4523 view.add_selection_above(&Default::default(), cx);
4524 });
4525
4526 cx.assert_editor_state(indoc!(
4527 r#"a«bcˇ»
4528 d«efgˇ»hi
4529
4530 j«kˇ»
4531 nlmo
4532 "#
4533 ));
4534
4535 // Change selections again
4536 cx.set_state(indoc!(
4537 r#"abc
4538 d«ˇefghi
4539
4540 jk
4541 nlm»o
4542 "#
4543 ));
4544
4545 cx.update_editor(|view, cx| {
4546 view.add_selection_above(&Default::default(), cx);
4547 });
4548
4549 cx.assert_editor_state(indoc!(
4550 r#"a«ˇbc»
4551 d«ˇef»ghi
4552
4553 j«ˇk»
4554 n«ˇlm»o
4555 "#
4556 ));
4557
4558 cx.update_editor(|view, cx| {
4559 view.add_selection_below(&Default::default(), cx);
4560 });
4561
4562 cx.assert_editor_state(indoc!(
4563 r#"abc
4564 d«ˇef»ghi
4565
4566 j«ˇk»
4567 n«ˇlm»o
4568 "#
4569 ));
4570}
4571
4572#[gpui::test]
4573async fn test_select_next(cx: &mut gpui::TestAppContext) {
4574 init_test(cx, |_| {});
4575
4576 let mut cx = EditorTestContext::new(cx).await;
4577 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4578
4579 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4580 .unwrap();
4581 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4582
4583 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4584 .unwrap();
4585 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4586
4587 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4588 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4589
4590 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4591 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4592
4593 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4594 .unwrap();
4595 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4596
4597 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4598 .unwrap();
4599 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4600}
4601
4602#[gpui::test]
4603async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
4604 init_test(cx, |_| {});
4605
4606 let mut cx = EditorTestContext::new(cx).await;
4607 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4608
4609 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
4610 .unwrap();
4611 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4612}
4613
4614#[gpui::test]
4615async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4616 init_test(cx, |_| {});
4617
4618 let mut cx = EditorTestContext::new(cx).await;
4619 cx.set_state(
4620 r#"let foo = 2;
4621lˇet foo = 2;
4622let fooˇ = 2;
4623let foo = 2;
4624let foo = ˇ2;"#,
4625 );
4626
4627 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4628 .unwrap();
4629 cx.assert_editor_state(
4630 r#"let foo = 2;
4631«letˇ» foo = 2;
4632let «fooˇ» = 2;
4633let foo = 2;
4634let foo = «2ˇ»;"#,
4635 );
4636
4637 // noop for multiple selections with different contents
4638 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4639 .unwrap();
4640 cx.assert_editor_state(
4641 r#"let foo = 2;
4642«letˇ» foo = 2;
4643let «fooˇ» = 2;
4644let foo = 2;
4645let foo = «2ˇ»;"#,
4646 );
4647}
4648
4649#[gpui::test]
4650async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
4651 init_test(cx, |_| {});
4652
4653 let mut cx = EditorTestContext::new_multibuffer(
4654 cx,
4655 [
4656 &indoc! {
4657 "aaa\n«bbb\nccc\n»ddd"
4658 },
4659 &indoc! {
4660 "aaa\n«bbb\nccc\n»ddd"
4661 },
4662 ],
4663 );
4664
4665 cx.assert_editor_state(indoc! {"
4666 ˇbbb
4667 ccc
4668
4669 bbb
4670 ccc
4671 "});
4672 cx.dispatch_action(SelectPrevious::default());
4673 cx.assert_editor_state(indoc! {"
4674 «bbbˇ»
4675 ccc
4676
4677 bbb
4678 ccc
4679 "});
4680 cx.dispatch_action(SelectPrevious::default());
4681 cx.assert_editor_state(indoc! {"
4682 «bbbˇ»
4683 ccc
4684
4685 «bbbˇ»
4686 ccc
4687 "});
4688}
4689
4690#[gpui::test]
4691async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
4692 init_test(cx, |_| {});
4693
4694 let mut cx = EditorTestContext::new(cx).await;
4695 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4696
4697 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4698 .unwrap();
4699 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4700
4701 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4702 .unwrap();
4703 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4704
4705 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4706 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4707
4708 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4709 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4710
4711 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4712 .unwrap();
4713 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
4714
4715 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4716 .unwrap();
4717 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
4718
4719 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4720 .unwrap();
4721 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
4722}
4723
4724#[gpui::test]
4725async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4726 init_test(cx, |_| {});
4727
4728 let mut cx = EditorTestContext::new(cx).await;
4729 cx.set_state(
4730 r#"let foo = 2;
4731lˇet foo = 2;
4732let fooˇ = 2;
4733let foo = 2;
4734let foo = ˇ2;"#,
4735 );
4736
4737 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4738 .unwrap();
4739 cx.assert_editor_state(
4740 r#"let foo = 2;
4741«letˇ» foo = 2;
4742let «fooˇ» = 2;
4743let foo = 2;
4744let foo = «2ˇ»;"#,
4745 );
4746
4747 // noop for multiple selections with different contents
4748 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4749 .unwrap();
4750 cx.assert_editor_state(
4751 r#"let foo = 2;
4752«letˇ» foo = 2;
4753let «fooˇ» = 2;
4754let foo = 2;
4755let foo = «2ˇ»;"#,
4756 );
4757}
4758
4759#[gpui::test]
4760async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
4761 init_test(cx, |_| {});
4762
4763 let mut cx = EditorTestContext::new(cx).await;
4764 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
4765
4766 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4767 .unwrap();
4768 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
4769
4770 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4771 .unwrap();
4772 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
4773
4774 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4775 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
4776
4777 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4778 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
4779
4780 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4781 .unwrap();
4782 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
4783
4784 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4785 .unwrap();
4786 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
4787}
4788
4789#[gpui::test]
4790async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
4791 init_test(cx, |_| {});
4792
4793 let language = Arc::new(Language::new(
4794 LanguageConfig::default(),
4795 Some(tree_sitter_rust::language()),
4796 ));
4797
4798 let text = r#"
4799 use mod1::mod2::{mod3, mod4};
4800
4801 fn fn_1(param1: bool, param2: &str) {
4802 let var1 = "text";
4803 }
4804 "#
4805 .unindent();
4806
4807 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
4808 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4809 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4810
4811 editor
4812 .condition::<crate::EditorEvent>(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4813 .await;
4814
4815 editor.update(cx, |view, cx| {
4816 view.change_selections(None, cx, |s| {
4817 s.select_display_ranges([
4818 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
4819 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
4820 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
4821 ]);
4822 });
4823 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4824 });
4825 editor.update(cx, |editor, cx| {
4826 assert_text_with_selections(
4827 editor,
4828 indoc! {r#"
4829 use mod1::mod2::{mod3, «mod4ˇ»};
4830
4831 fn fn_1«ˇ(param1: bool, param2: &str)» {
4832 let var1 = "«textˇ»";
4833 }
4834 "#},
4835 cx,
4836 );
4837 });
4838
4839 editor.update(cx, |view, cx| {
4840 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4841 });
4842 editor.update(cx, |editor, cx| {
4843 assert_text_with_selections(
4844 editor,
4845 indoc! {r#"
4846 use mod1::mod2::«{mod3, mod4}ˇ»;
4847
4848 «ˇfn fn_1(param1: bool, param2: &str) {
4849 let var1 = "text";
4850 }»
4851 "#},
4852 cx,
4853 );
4854 });
4855
4856 editor.update(cx, |view, cx| {
4857 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4858 });
4859 assert_eq!(
4860 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
4861 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
4862 );
4863
4864 // Trying to expand the selected syntax node one more time has no effect.
4865 editor.update(cx, |view, cx| {
4866 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4867 });
4868 assert_eq!(
4869 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
4870 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
4871 );
4872
4873 editor.update(cx, |view, cx| {
4874 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4875 });
4876 editor.update(cx, |editor, cx| {
4877 assert_text_with_selections(
4878 editor,
4879 indoc! {r#"
4880 use mod1::mod2::«{mod3, mod4}ˇ»;
4881
4882 «ˇfn fn_1(param1: bool, param2: &str) {
4883 let var1 = "text";
4884 }»
4885 "#},
4886 cx,
4887 );
4888 });
4889
4890 editor.update(cx, |view, cx| {
4891 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4892 });
4893 editor.update(cx, |editor, cx| {
4894 assert_text_with_selections(
4895 editor,
4896 indoc! {r#"
4897 use mod1::mod2::{mod3, «mod4ˇ»};
4898
4899 fn fn_1«ˇ(param1: bool, param2: &str)» {
4900 let var1 = "«textˇ»";
4901 }
4902 "#},
4903 cx,
4904 );
4905 });
4906
4907 editor.update(cx, |view, cx| {
4908 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4909 });
4910 editor.update(cx, |editor, cx| {
4911 assert_text_with_selections(
4912 editor,
4913 indoc! {r#"
4914 use mod1::mod2::{mod3, mo«ˇ»d4};
4915
4916 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
4917 let var1 = "te«ˇ»xt";
4918 }
4919 "#},
4920 cx,
4921 );
4922 });
4923
4924 // Trying to shrink the selected syntax node one more time has no effect.
4925 editor.update(cx, |view, cx| {
4926 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4927 });
4928 editor.update(cx, |editor, cx| {
4929 assert_text_with_selections(
4930 editor,
4931 indoc! {r#"
4932 use mod1::mod2::{mod3, mo«ˇ»d4};
4933
4934 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
4935 let var1 = "te«ˇ»xt";
4936 }
4937 "#},
4938 cx,
4939 );
4940 });
4941
4942 // Ensure that we keep expanding the selection if the larger selection starts or ends within
4943 // a fold.
4944 editor.update(cx, |view, cx| {
4945 view.fold_ranges(
4946 vec![
4947 (
4948 Point::new(0, 21)..Point::new(0, 24),
4949 FoldPlaceholder::test(),
4950 ),
4951 (
4952 Point::new(3, 20)..Point::new(3, 22),
4953 FoldPlaceholder::test(),
4954 ),
4955 ],
4956 true,
4957 cx,
4958 );
4959 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4960 });
4961 editor.update(cx, |editor, cx| {
4962 assert_text_with_selections(
4963 editor,
4964 indoc! {r#"
4965 use mod1::mod2::«{mod3, mod4}ˇ»;
4966
4967 fn fn_1«ˇ(param1: bool, param2: &str)» {
4968 «let var1 = "text";ˇ»
4969 }
4970 "#},
4971 cx,
4972 );
4973 });
4974}
4975
4976#[gpui::test]
4977async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
4978 init_test(cx, |_| {});
4979
4980 let language = Arc::new(
4981 Language::new(
4982 LanguageConfig {
4983 brackets: BracketPairConfig {
4984 pairs: vec![
4985 BracketPair {
4986 start: "{".to_string(),
4987 end: "}".to_string(),
4988 close: false,
4989 surround: false,
4990 newline: true,
4991 },
4992 BracketPair {
4993 start: "(".to_string(),
4994 end: ")".to_string(),
4995 close: false,
4996 surround: false,
4997 newline: true,
4998 },
4999 ],
5000 ..Default::default()
5001 },
5002 ..Default::default()
5003 },
5004 Some(tree_sitter_rust::language()),
5005 )
5006 .with_indents_query(
5007 r#"
5008 (_ "(" ")" @end) @indent
5009 (_ "{" "}" @end) @indent
5010 "#,
5011 )
5012 .unwrap(),
5013 );
5014
5015 let text = "fn a() {}";
5016
5017 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5018 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5019 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5020 editor
5021 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5022 .await;
5023
5024 _ = editor.update(cx, |editor, cx| {
5025 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5026 editor.newline(&Newline, cx);
5027 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5028 assert_eq!(
5029 editor.selections.ranges(cx),
5030 &[
5031 Point::new(1, 4)..Point::new(1, 4),
5032 Point::new(3, 4)..Point::new(3, 4),
5033 Point::new(5, 0)..Point::new(5, 0)
5034 ]
5035 );
5036 });
5037}
5038
5039#[gpui::test]
5040async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5041 init_test(cx, |_| {});
5042
5043 let mut cx = EditorTestContext::new(cx).await;
5044
5045 let language = Arc::new(Language::new(
5046 LanguageConfig {
5047 brackets: BracketPairConfig {
5048 pairs: vec![
5049 BracketPair {
5050 start: "{".to_string(),
5051 end: "}".to_string(),
5052 close: true,
5053 surround: true,
5054 newline: true,
5055 },
5056 BracketPair {
5057 start: "(".to_string(),
5058 end: ")".to_string(),
5059 close: true,
5060 surround: true,
5061 newline: true,
5062 },
5063 BracketPair {
5064 start: "/*".to_string(),
5065 end: " */".to_string(),
5066 close: true,
5067 surround: true,
5068 newline: true,
5069 },
5070 BracketPair {
5071 start: "[".to_string(),
5072 end: "]".to_string(),
5073 close: false,
5074 surround: false,
5075 newline: true,
5076 },
5077 BracketPair {
5078 start: "\"".to_string(),
5079 end: "\"".to_string(),
5080 close: true,
5081 surround: true,
5082 newline: false,
5083 },
5084 BracketPair {
5085 start: "<".to_string(),
5086 end: ">".to_string(),
5087 close: false,
5088 surround: true,
5089 newline: true,
5090 },
5091 ],
5092 ..Default::default()
5093 },
5094 autoclose_before: "})]".to_string(),
5095 ..Default::default()
5096 },
5097 Some(tree_sitter_rust::language()),
5098 ));
5099
5100 cx.language_registry().add(language.clone());
5101 cx.update_buffer(|buffer, cx| {
5102 buffer.set_language(Some(language), cx);
5103 });
5104
5105 cx.set_state(
5106 &r#"
5107 🏀ˇ
5108 εˇ
5109 ❤️ˇ
5110 "#
5111 .unindent(),
5112 );
5113
5114 // autoclose multiple nested brackets at multiple cursors
5115 cx.update_editor(|view, cx| {
5116 view.handle_input("{", cx);
5117 view.handle_input("{", cx);
5118 view.handle_input("{", cx);
5119 });
5120 cx.assert_editor_state(
5121 &"
5122 🏀{{{ˇ}}}
5123 ε{{{ˇ}}}
5124 ❤️{{{ˇ}}}
5125 "
5126 .unindent(),
5127 );
5128
5129 // insert a different closing bracket
5130 cx.update_editor(|view, cx| {
5131 view.handle_input(")", cx);
5132 });
5133 cx.assert_editor_state(
5134 &"
5135 🏀{{{)ˇ}}}
5136 ε{{{)ˇ}}}
5137 ❤️{{{)ˇ}}}
5138 "
5139 .unindent(),
5140 );
5141
5142 // skip over the auto-closed brackets when typing a closing bracket
5143 cx.update_editor(|view, cx| {
5144 view.move_right(&MoveRight, cx);
5145 view.handle_input("}", cx);
5146 view.handle_input("}", cx);
5147 view.handle_input("}", cx);
5148 });
5149 cx.assert_editor_state(
5150 &"
5151 🏀{{{)}}}}ˇ
5152 ε{{{)}}}}ˇ
5153 ❤️{{{)}}}}ˇ
5154 "
5155 .unindent(),
5156 );
5157
5158 // autoclose multi-character pairs
5159 cx.set_state(
5160 &"
5161 ˇ
5162 ˇ
5163 "
5164 .unindent(),
5165 );
5166 cx.update_editor(|view, cx| {
5167 view.handle_input("/", cx);
5168 view.handle_input("*", cx);
5169 });
5170 cx.assert_editor_state(
5171 &"
5172 /*ˇ */
5173 /*ˇ */
5174 "
5175 .unindent(),
5176 );
5177
5178 // one cursor autocloses a multi-character pair, one cursor
5179 // does not autoclose.
5180 cx.set_state(
5181 &"
5182 /ˇ
5183 ˇ
5184 "
5185 .unindent(),
5186 );
5187 cx.update_editor(|view, cx| view.handle_input("*", cx));
5188 cx.assert_editor_state(
5189 &"
5190 /*ˇ */
5191 *ˇ
5192 "
5193 .unindent(),
5194 );
5195
5196 // Don't autoclose if the next character isn't whitespace and isn't
5197 // listed in the language's "autoclose_before" section.
5198 cx.set_state("ˇa b");
5199 cx.update_editor(|view, cx| view.handle_input("{", cx));
5200 cx.assert_editor_state("{ˇa b");
5201
5202 // Don't autoclose if `close` is false for the bracket pair
5203 cx.set_state("ˇ");
5204 cx.update_editor(|view, cx| view.handle_input("[", cx));
5205 cx.assert_editor_state("[ˇ");
5206
5207 // Surround with brackets if text is selected
5208 cx.set_state("«aˇ» b");
5209 cx.update_editor(|view, cx| view.handle_input("{", cx));
5210 cx.assert_editor_state("{«aˇ»} b");
5211
5212 // Autclose pair where the start and end characters are the same
5213 cx.set_state("aˇ");
5214 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5215 cx.assert_editor_state("a\"ˇ\"");
5216 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5217 cx.assert_editor_state("a\"\"ˇ");
5218
5219 // Don't autoclose pair if autoclose is disabled
5220 cx.set_state("ˇ");
5221 cx.update_editor(|view, cx| view.handle_input("<", cx));
5222 cx.assert_editor_state("<ˇ");
5223
5224 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5225 cx.set_state("«aˇ» b");
5226 cx.update_editor(|view, cx| view.handle_input("<", cx));
5227 cx.assert_editor_state("<«aˇ»> b");
5228}
5229
5230#[gpui::test]
5231async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5232 init_test(cx, |settings| {
5233 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5234 });
5235
5236 let mut cx = EditorTestContext::new(cx).await;
5237
5238 let language = Arc::new(Language::new(
5239 LanguageConfig {
5240 brackets: BracketPairConfig {
5241 pairs: vec![
5242 BracketPair {
5243 start: "{".to_string(),
5244 end: "}".to_string(),
5245 close: true,
5246 surround: true,
5247 newline: true,
5248 },
5249 BracketPair {
5250 start: "(".to_string(),
5251 end: ")".to_string(),
5252 close: true,
5253 surround: true,
5254 newline: true,
5255 },
5256 BracketPair {
5257 start: "[".to_string(),
5258 end: "]".to_string(),
5259 close: false,
5260 surround: false,
5261 newline: true,
5262 },
5263 ],
5264 ..Default::default()
5265 },
5266 autoclose_before: "})]".to_string(),
5267 ..Default::default()
5268 },
5269 Some(tree_sitter_rust::language()),
5270 ));
5271
5272 cx.language_registry().add(language.clone());
5273 cx.update_buffer(|buffer, cx| {
5274 buffer.set_language(Some(language), cx);
5275 });
5276
5277 cx.set_state(
5278 &"
5279 ˇ
5280 ˇ
5281 ˇ
5282 "
5283 .unindent(),
5284 );
5285
5286 // ensure only matching closing brackets are skipped over
5287 cx.update_editor(|view, cx| {
5288 view.handle_input("}", cx);
5289 view.move_left(&MoveLeft, cx);
5290 view.handle_input(")", cx);
5291 view.move_left(&MoveLeft, cx);
5292 });
5293 cx.assert_editor_state(
5294 &"
5295 ˇ)}
5296 ˇ)}
5297 ˇ)}
5298 "
5299 .unindent(),
5300 );
5301
5302 // skip-over closing brackets at multiple cursors
5303 cx.update_editor(|view, cx| {
5304 view.handle_input(")", cx);
5305 view.handle_input("}", cx);
5306 });
5307 cx.assert_editor_state(
5308 &"
5309 )}ˇ
5310 )}ˇ
5311 )}ˇ
5312 "
5313 .unindent(),
5314 );
5315
5316 // ignore non-close brackets
5317 cx.update_editor(|view, cx| {
5318 view.handle_input("]", cx);
5319 view.move_left(&MoveLeft, cx);
5320 view.handle_input("]", cx);
5321 });
5322 cx.assert_editor_state(
5323 &"
5324 )}]ˇ]
5325 )}]ˇ]
5326 )}]ˇ]
5327 "
5328 .unindent(),
5329 );
5330}
5331
5332#[gpui::test]
5333async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5334 init_test(cx, |_| {});
5335
5336 let mut cx = EditorTestContext::new(cx).await;
5337
5338 let html_language = Arc::new(
5339 Language::new(
5340 LanguageConfig {
5341 name: "HTML".into(),
5342 brackets: BracketPairConfig {
5343 pairs: vec![
5344 BracketPair {
5345 start: "<".into(),
5346 end: ">".into(),
5347 close: true,
5348 ..Default::default()
5349 },
5350 BracketPair {
5351 start: "{".into(),
5352 end: "}".into(),
5353 close: true,
5354 ..Default::default()
5355 },
5356 BracketPair {
5357 start: "(".into(),
5358 end: ")".into(),
5359 close: true,
5360 ..Default::default()
5361 },
5362 ],
5363 ..Default::default()
5364 },
5365 autoclose_before: "})]>".into(),
5366 ..Default::default()
5367 },
5368 Some(tree_sitter_html::language()),
5369 )
5370 .with_injection_query(
5371 r#"
5372 (script_element
5373 (raw_text) @content
5374 (#set! "language" "javascript"))
5375 "#,
5376 )
5377 .unwrap(),
5378 );
5379
5380 let javascript_language = Arc::new(Language::new(
5381 LanguageConfig {
5382 name: "JavaScript".into(),
5383 brackets: BracketPairConfig {
5384 pairs: vec![
5385 BracketPair {
5386 start: "/*".into(),
5387 end: " */".into(),
5388 close: true,
5389 ..Default::default()
5390 },
5391 BracketPair {
5392 start: "{".into(),
5393 end: "}".into(),
5394 close: true,
5395 ..Default::default()
5396 },
5397 BracketPair {
5398 start: "(".into(),
5399 end: ")".into(),
5400 close: true,
5401 ..Default::default()
5402 },
5403 ],
5404 ..Default::default()
5405 },
5406 autoclose_before: "})]>".into(),
5407 ..Default::default()
5408 },
5409 Some(tree_sitter_typescript::language_tsx()),
5410 ));
5411
5412 cx.language_registry().add(html_language.clone());
5413 cx.language_registry().add(javascript_language.clone());
5414
5415 cx.update_buffer(|buffer, cx| {
5416 buffer.set_language(Some(html_language), cx);
5417 });
5418
5419 cx.set_state(
5420 &r#"
5421 <body>ˇ
5422 <script>
5423 var x = 1;ˇ
5424 </script>
5425 </body>ˇ
5426 "#
5427 .unindent(),
5428 );
5429
5430 // Precondition: different languages are active at different locations.
5431 cx.update_editor(|editor, cx| {
5432 let snapshot = editor.snapshot(cx);
5433 let cursors = editor.selections.ranges::<usize>(cx);
5434 let languages = cursors
5435 .iter()
5436 .map(|c| snapshot.language_at(c.start).unwrap().name())
5437 .collect::<Vec<_>>();
5438 assert_eq!(
5439 languages,
5440 &["HTML".into(), "JavaScript".into(), "HTML".into()]
5441 );
5442 });
5443
5444 // Angle brackets autoclose in HTML, but not JavaScript.
5445 cx.update_editor(|editor, cx| {
5446 editor.handle_input("<", cx);
5447 editor.handle_input("a", cx);
5448 });
5449 cx.assert_editor_state(
5450 &r#"
5451 <body><aˇ>
5452 <script>
5453 var x = 1;<aˇ
5454 </script>
5455 </body><aˇ>
5456 "#
5457 .unindent(),
5458 );
5459
5460 // Curly braces and parens autoclose in both HTML and JavaScript.
5461 cx.update_editor(|editor, cx| {
5462 editor.handle_input(" b=", cx);
5463 editor.handle_input("{", cx);
5464 editor.handle_input("c", cx);
5465 editor.handle_input("(", cx);
5466 });
5467 cx.assert_editor_state(
5468 &r#"
5469 <body><a b={c(ˇ)}>
5470 <script>
5471 var x = 1;<a b={c(ˇ)}
5472 </script>
5473 </body><a b={c(ˇ)}>
5474 "#
5475 .unindent(),
5476 );
5477
5478 // Brackets that were already autoclosed are skipped.
5479 cx.update_editor(|editor, cx| {
5480 editor.handle_input(")", cx);
5481 editor.handle_input("d", cx);
5482 editor.handle_input("}", cx);
5483 });
5484 cx.assert_editor_state(
5485 &r#"
5486 <body><a b={c()d}ˇ>
5487 <script>
5488 var x = 1;<a b={c()d}ˇ
5489 </script>
5490 </body><a b={c()d}ˇ>
5491 "#
5492 .unindent(),
5493 );
5494 cx.update_editor(|editor, cx| {
5495 editor.handle_input(">", cx);
5496 });
5497 cx.assert_editor_state(
5498 &r#"
5499 <body><a b={c()d}>ˇ
5500 <script>
5501 var x = 1;<a b={c()d}>ˇ
5502 </script>
5503 </body><a b={c()d}>ˇ
5504 "#
5505 .unindent(),
5506 );
5507
5508 // Reset
5509 cx.set_state(
5510 &r#"
5511 <body>ˇ
5512 <script>
5513 var x = 1;ˇ
5514 </script>
5515 </body>ˇ
5516 "#
5517 .unindent(),
5518 );
5519
5520 cx.update_editor(|editor, cx| {
5521 editor.handle_input("<", cx);
5522 });
5523 cx.assert_editor_state(
5524 &r#"
5525 <body><ˇ>
5526 <script>
5527 var x = 1;<ˇ
5528 </script>
5529 </body><ˇ>
5530 "#
5531 .unindent(),
5532 );
5533
5534 // When backspacing, the closing angle brackets are removed.
5535 cx.update_editor(|editor, cx| {
5536 editor.backspace(&Backspace, cx);
5537 });
5538 cx.assert_editor_state(
5539 &r#"
5540 <body>ˇ
5541 <script>
5542 var x = 1;ˇ
5543 </script>
5544 </body>ˇ
5545 "#
5546 .unindent(),
5547 );
5548
5549 // Block comments autoclose in JavaScript, but not HTML.
5550 cx.update_editor(|editor, cx| {
5551 editor.handle_input("/", cx);
5552 editor.handle_input("*", cx);
5553 });
5554 cx.assert_editor_state(
5555 &r#"
5556 <body>/*ˇ
5557 <script>
5558 var x = 1;/*ˇ */
5559 </script>
5560 </body>/*ˇ
5561 "#
5562 .unindent(),
5563 );
5564}
5565
5566#[gpui::test]
5567async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
5568 init_test(cx, |_| {});
5569
5570 let mut cx = EditorTestContext::new(cx).await;
5571
5572 let rust_language = Arc::new(
5573 Language::new(
5574 LanguageConfig {
5575 name: "Rust".into(),
5576 brackets: serde_json::from_value(json!([
5577 { "start": "{", "end": "}", "close": true, "newline": true },
5578 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
5579 ]))
5580 .unwrap(),
5581 autoclose_before: "})]>".into(),
5582 ..Default::default()
5583 },
5584 Some(tree_sitter_rust::language()),
5585 )
5586 .with_override_query("(string_literal) @string")
5587 .unwrap(),
5588 );
5589
5590 cx.language_registry().add(rust_language.clone());
5591 cx.update_buffer(|buffer, cx| {
5592 buffer.set_language(Some(rust_language), cx);
5593 });
5594
5595 cx.set_state(
5596 &r#"
5597 let x = ˇ
5598 "#
5599 .unindent(),
5600 );
5601
5602 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
5603 cx.update_editor(|editor, cx| {
5604 editor.handle_input("\"", cx);
5605 });
5606 cx.assert_editor_state(
5607 &r#"
5608 let x = "ˇ"
5609 "#
5610 .unindent(),
5611 );
5612
5613 // Inserting another quotation mark. The cursor moves across the existing
5614 // automatically-inserted quotation mark.
5615 cx.update_editor(|editor, cx| {
5616 editor.handle_input("\"", cx);
5617 });
5618 cx.assert_editor_state(
5619 &r#"
5620 let x = ""ˇ
5621 "#
5622 .unindent(),
5623 );
5624
5625 // Reset
5626 cx.set_state(
5627 &r#"
5628 let x = ˇ
5629 "#
5630 .unindent(),
5631 );
5632
5633 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
5634 cx.update_editor(|editor, cx| {
5635 editor.handle_input("\"", cx);
5636 editor.handle_input(" ", cx);
5637 editor.move_left(&Default::default(), cx);
5638 editor.handle_input("\\", cx);
5639 editor.handle_input("\"", cx);
5640 });
5641 cx.assert_editor_state(
5642 &r#"
5643 let x = "\"ˇ "
5644 "#
5645 .unindent(),
5646 );
5647
5648 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
5649 // mark. Nothing is inserted.
5650 cx.update_editor(|editor, cx| {
5651 editor.move_right(&Default::default(), cx);
5652 editor.handle_input("\"", cx);
5653 });
5654 cx.assert_editor_state(
5655 &r#"
5656 let x = "\" "ˇ
5657 "#
5658 .unindent(),
5659 );
5660}
5661
5662#[gpui::test]
5663async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
5664 init_test(cx, |_| {});
5665
5666 let language = Arc::new(Language::new(
5667 LanguageConfig {
5668 brackets: BracketPairConfig {
5669 pairs: vec![
5670 BracketPair {
5671 start: "{".to_string(),
5672 end: "}".to_string(),
5673 close: true,
5674 surround: true,
5675 newline: true,
5676 },
5677 BracketPair {
5678 start: "/* ".to_string(),
5679 end: "*/".to_string(),
5680 close: true,
5681 surround: true,
5682 ..Default::default()
5683 },
5684 ],
5685 ..Default::default()
5686 },
5687 ..Default::default()
5688 },
5689 Some(tree_sitter_rust::language()),
5690 ));
5691
5692 let text = r#"
5693 a
5694 b
5695 c
5696 "#
5697 .unindent();
5698
5699 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5700 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5701 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5702 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5703 .await;
5704
5705 _ = view.update(cx, |view, cx| {
5706 view.change_selections(None, cx, |s| {
5707 s.select_display_ranges([
5708 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5709 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5710 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
5711 ])
5712 });
5713
5714 view.handle_input("{", cx);
5715 view.handle_input("{", cx);
5716 view.handle_input("{", cx);
5717 assert_eq!(
5718 view.text(cx),
5719 "
5720 {{{a}}}
5721 {{{b}}}
5722 {{{c}}}
5723 "
5724 .unindent()
5725 );
5726 assert_eq!(
5727 view.selections.display_ranges(cx),
5728 [
5729 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
5730 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
5731 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
5732 ]
5733 );
5734
5735 view.undo(&Undo, cx);
5736 view.undo(&Undo, cx);
5737 view.undo(&Undo, cx);
5738 assert_eq!(
5739 view.text(cx),
5740 "
5741 a
5742 b
5743 c
5744 "
5745 .unindent()
5746 );
5747 assert_eq!(
5748 view.selections.display_ranges(cx),
5749 [
5750 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5751 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5752 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
5753 ]
5754 );
5755
5756 // Ensure inserting the first character of a multi-byte bracket pair
5757 // doesn't surround the selections with the bracket.
5758 view.handle_input("/", cx);
5759 assert_eq!(
5760 view.text(cx),
5761 "
5762 /
5763 /
5764 /
5765 "
5766 .unindent()
5767 );
5768 assert_eq!(
5769 view.selections.display_ranges(cx),
5770 [
5771 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
5772 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
5773 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
5774 ]
5775 );
5776
5777 view.undo(&Undo, cx);
5778 assert_eq!(
5779 view.text(cx),
5780 "
5781 a
5782 b
5783 c
5784 "
5785 .unindent()
5786 );
5787 assert_eq!(
5788 view.selections.display_ranges(cx),
5789 [
5790 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5791 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5792 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
5793 ]
5794 );
5795
5796 // Ensure inserting the last character of a multi-byte bracket pair
5797 // doesn't surround the selections with the bracket.
5798 view.handle_input("*", cx);
5799 assert_eq!(
5800 view.text(cx),
5801 "
5802 *
5803 *
5804 *
5805 "
5806 .unindent()
5807 );
5808 assert_eq!(
5809 view.selections.display_ranges(cx),
5810 [
5811 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
5812 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
5813 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
5814 ]
5815 );
5816 });
5817}
5818
5819#[gpui::test]
5820async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
5821 init_test(cx, |_| {});
5822
5823 let language = Arc::new(Language::new(
5824 LanguageConfig {
5825 brackets: BracketPairConfig {
5826 pairs: vec![BracketPair {
5827 start: "{".to_string(),
5828 end: "}".to_string(),
5829 close: true,
5830 surround: true,
5831 newline: true,
5832 }],
5833 ..Default::default()
5834 },
5835 autoclose_before: "}".to_string(),
5836 ..Default::default()
5837 },
5838 Some(tree_sitter_rust::language()),
5839 ));
5840
5841 let text = r#"
5842 a
5843 b
5844 c
5845 "#
5846 .unindent();
5847
5848 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5849 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5850 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5851 editor
5852 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5853 .await;
5854
5855 _ = editor.update(cx, |editor, cx| {
5856 editor.change_selections(None, cx, |s| {
5857 s.select_ranges([
5858 Point::new(0, 1)..Point::new(0, 1),
5859 Point::new(1, 1)..Point::new(1, 1),
5860 Point::new(2, 1)..Point::new(2, 1),
5861 ])
5862 });
5863
5864 editor.handle_input("{", cx);
5865 editor.handle_input("{", cx);
5866 editor.handle_input("_", cx);
5867 assert_eq!(
5868 editor.text(cx),
5869 "
5870 a{{_}}
5871 b{{_}}
5872 c{{_}}
5873 "
5874 .unindent()
5875 );
5876 assert_eq!(
5877 editor.selections.ranges::<Point>(cx),
5878 [
5879 Point::new(0, 4)..Point::new(0, 4),
5880 Point::new(1, 4)..Point::new(1, 4),
5881 Point::new(2, 4)..Point::new(2, 4)
5882 ]
5883 );
5884
5885 editor.backspace(&Default::default(), cx);
5886 editor.backspace(&Default::default(), cx);
5887 assert_eq!(
5888 editor.text(cx),
5889 "
5890 a{}
5891 b{}
5892 c{}
5893 "
5894 .unindent()
5895 );
5896 assert_eq!(
5897 editor.selections.ranges::<Point>(cx),
5898 [
5899 Point::new(0, 2)..Point::new(0, 2),
5900 Point::new(1, 2)..Point::new(1, 2),
5901 Point::new(2, 2)..Point::new(2, 2)
5902 ]
5903 );
5904
5905 editor.delete_to_previous_word_start(&Default::default(), cx);
5906 assert_eq!(
5907 editor.text(cx),
5908 "
5909 a
5910 b
5911 c
5912 "
5913 .unindent()
5914 );
5915 assert_eq!(
5916 editor.selections.ranges::<Point>(cx),
5917 [
5918 Point::new(0, 1)..Point::new(0, 1),
5919 Point::new(1, 1)..Point::new(1, 1),
5920 Point::new(2, 1)..Point::new(2, 1)
5921 ]
5922 );
5923 });
5924}
5925
5926#[gpui::test]
5927async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
5928 init_test(cx, |settings| {
5929 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5930 });
5931
5932 let mut cx = EditorTestContext::new(cx).await;
5933
5934 let language = Arc::new(Language::new(
5935 LanguageConfig {
5936 brackets: BracketPairConfig {
5937 pairs: vec![
5938 BracketPair {
5939 start: "{".to_string(),
5940 end: "}".to_string(),
5941 close: true,
5942 surround: true,
5943 newline: true,
5944 },
5945 BracketPair {
5946 start: "(".to_string(),
5947 end: ")".to_string(),
5948 close: true,
5949 surround: true,
5950 newline: true,
5951 },
5952 BracketPair {
5953 start: "[".to_string(),
5954 end: "]".to_string(),
5955 close: false,
5956 surround: true,
5957 newline: true,
5958 },
5959 ],
5960 ..Default::default()
5961 },
5962 autoclose_before: "})]".to_string(),
5963 ..Default::default()
5964 },
5965 Some(tree_sitter_rust::language()),
5966 ));
5967
5968 cx.language_registry().add(language.clone());
5969 cx.update_buffer(|buffer, cx| {
5970 buffer.set_language(Some(language), cx);
5971 });
5972
5973 cx.set_state(
5974 &"
5975 {(ˇ)}
5976 [[ˇ]]
5977 {(ˇ)}
5978 "
5979 .unindent(),
5980 );
5981
5982 cx.update_editor(|view, cx| {
5983 view.backspace(&Default::default(), cx);
5984 view.backspace(&Default::default(), cx);
5985 });
5986
5987 cx.assert_editor_state(
5988 &"
5989 ˇ
5990 ˇ]]
5991 ˇ
5992 "
5993 .unindent(),
5994 );
5995
5996 cx.update_editor(|view, cx| {
5997 view.handle_input("{", cx);
5998 view.handle_input("{", cx);
5999 view.move_right(&MoveRight, cx);
6000 view.move_right(&MoveRight, cx);
6001 view.move_left(&MoveLeft, cx);
6002 view.move_left(&MoveLeft, cx);
6003 view.backspace(&Default::default(), cx);
6004 });
6005
6006 cx.assert_editor_state(
6007 &"
6008 {ˇ}
6009 {ˇ}]]
6010 {ˇ}
6011 "
6012 .unindent(),
6013 );
6014
6015 cx.update_editor(|view, cx| {
6016 view.backspace(&Default::default(), cx);
6017 });
6018
6019 cx.assert_editor_state(
6020 &"
6021 ˇ
6022 ˇ]]
6023 ˇ
6024 "
6025 .unindent(),
6026 );
6027}
6028
6029#[gpui::test]
6030async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6031 init_test(cx, |_| {});
6032
6033 let language = Arc::new(Language::new(
6034 LanguageConfig::default(),
6035 Some(tree_sitter_rust::language()),
6036 ));
6037
6038 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6039 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6040 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6041 editor
6042 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6043 .await;
6044
6045 _ = editor.update(cx, |editor, cx| {
6046 editor.set_auto_replace_emoji_shortcode(true);
6047
6048 editor.handle_input("Hello ", cx);
6049 editor.handle_input(":wave", cx);
6050 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6051
6052 editor.handle_input(":", cx);
6053 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6054
6055 editor.handle_input(" :smile", cx);
6056 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6057
6058 editor.handle_input(":", cx);
6059 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6060
6061 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6062 editor.handle_input(":wave", cx);
6063 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6064
6065 editor.handle_input(":", cx);
6066 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6067
6068 editor.handle_input(":1", cx);
6069 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6070
6071 editor.handle_input(":", cx);
6072 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6073
6074 // Ensure shortcode does not get replaced when it is part of a word
6075 editor.handle_input(" Test:wave", cx);
6076 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6077
6078 editor.handle_input(":", cx);
6079 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6080
6081 editor.set_auto_replace_emoji_shortcode(false);
6082
6083 // Ensure shortcode does not get replaced when auto replace is off
6084 editor.handle_input(" :wave", cx);
6085 assert_eq!(
6086 editor.text(cx),
6087 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6088 );
6089
6090 editor.handle_input(":", cx);
6091 assert_eq!(
6092 editor.text(cx),
6093 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6094 );
6095 });
6096}
6097
6098#[gpui::test]
6099async fn test_snippets(cx: &mut gpui::TestAppContext) {
6100 init_test(cx, |_| {});
6101
6102 let (text, insertion_ranges) = marked_text_ranges(
6103 indoc! {"
6104 a.ˇ b
6105 a.ˇ b
6106 a.ˇ b
6107 "},
6108 false,
6109 );
6110
6111 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6112 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6113
6114 _ = editor.update(cx, |editor, cx| {
6115 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6116
6117 editor
6118 .insert_snippet(&insertion_ranges, snippet, cx)
6119 .unwrap();
6120
6121 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6122 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6123 assert_eq!(editor.text(cx), expected_text);
6124 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6125 }
6126
6127 assert(
6128 editor,
6129 cx,
6130 indoc! {"
6131 a.f(«one», two, «three») b
6132 a.f(«one», two, «three») b
6133 a.f(«one», two, «three») b
6134 "},
6135 );
6136
6137 // Can't move earlier than the first tab stop
6138 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6139 assert(
6140 editor,
6141 cx,
6142 indoc! {"
6143 a.f(«one», two, «three») b
6144 a.f(«one», two, «three») b
6145 a.f(«one», two, «three») b
6146 "},
6147 );
6148
6149 assert!(editor.move_to_next_snippet_tabstop(cx));
6150 assert(
6151 editor,
6152 cx,
6153 indoc! {"
6154 a.f(one, «two», three) b
6155 a.f(one, «two», three) b
6156 a.f(one, «two», three) b
6157 "},
6158 );
6159
6160 editor.move_to_prev_snippet_tabstop(cx);
6161 assert(
6162 editor,
6163 cx,
6164 indoc! {"
6165 a.f(«one», two, «three») b
6166 a.f(«one», two, «three») b
6167 a.f(«one», two, «three») b
6168 "},
6169 );
6170
6171 assert!(editor.move_to_next_snippet_tabstop(cx));
6172 assert(
6173 editor,
6174 cx,
6175 indoc! {"
6176 a.f(one, «two», three) b
6177 a.f(one, «two», three) b
6178 a.f(one, «two», three) b
6179 "},
6180 );
6181 assert!(editor.move_to_next_snippet_tabstop(cx));
6182 assert(
6183 editor,
6184 cx,
6185 indoc! {"
6186 a.f(one, two, three)ˇ b
6187 a.f(one, two, three)ˇ b
6188 a.f(one, two, three)ˇ b
6189 "},
6190 );
6191
6192 // As soon as the last tab stop is reached, snippet state is gone
6193 editor.move_to_prev_snippet_tabstop(cx);
6194 assert(
6195 editor,
6196 cx,
6197 indoc! {"
6198 a.f(one, two, three)ˇ b
6199 a.f(one, two, three)ˇ b
6200 a.f(one, two, three)ˇ b
6201 "},
6202 );
6203 });
6204}
6205
6206#[gpui::test]
6207async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6208 init_test(cx, |_| {});
6209
6210 let fs = FakeFs::new(cx.executor());
6211 fs.insert_file("/file.rs", Default::default()).await;
6212
6213 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6214
6215 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6216 language_registry.add(rust_lang());
6217 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6218 "Rust",
6219 FakeLspAdapter {
6220 capabilities: lsp::ServerCapabilities {
6221 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6222 ..Default::default()
6223 },
6224 ..Default::default()
6225 },
6226 );
6227
6228 let buffer = project
6229 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6230 .await
6231 .unwrap();
6232
6233 cx.executor().start_waiting();
6234 let fake_server = fake_servers.next().await.unwrap();
6235
6236 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6237 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6238 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6239 assert!(cx.read(|cx| editor.is_dirty(cx)));
6240
6241 let save = editor
6242 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6243 .unwrap();
6244 fake_server
6245 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6246 assert_eq!(
6247 params.text_document.uri,
6248 lsp::Url::from_file_path("/file.rs").unwrap()
6249 );
6250 assert_eq!(params.options.tab_size, 4);
6251 Ok(Some(vec![lsp::TextEdit::new(
6252 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6253 ", ".to_string(),
6254 )]))
6255 })
6256 .next()
6257 .await;
6258 cx.executor().start_waiting();
6259 save.await;
6260
6261 assert_eq!(
6262 editor.update(cx, |editor, cx| editor.text(cx)),
6263 "one, two\nthree\n"
6264 );
6265 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6266
6267 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6268 assert!(cx.read(|cx| editor.is_dirty(cx)));
6269
6270 // Ensure we can still save even if formatting hangs.
6271 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6272 assert_eq!(
6273 params.text_document.uri,
6274 lsp::Url::from_file_path("/file.rs").unwrap()
6275 );
6276 futures::future::pending::<()>().await;
6277 unreachable!()
6278 });
6279 let save = editor
6280 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6281 .unwrap();
6282 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6283 cx.executor().start_waiting();
6284 save.await;
6285 assert_eq!(
6286 editor.update(cx, |editor, cx| editor.text(cx)),
6287 "one\ntwo\nthree\n"
6288 );
6289 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6290
6291 // For non-dirty buffer, no formatting request should be sent
6292 let save = editor
6293 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6294 .unwrap();
6295 let _pending_format_request = fake_server
6296 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6297 panic!("Should not be invoked on non-dirty buffer");
6298 })
6299 .next();
6300 cx.executor().start_waiting();
6301 save.await;
6302
6303 // Set rust language override and assert overridden tabsize is sent to language server
6304 update_test_language_settings(cx, |settings| {
6305 settings.languages.insert(
6306 "Rust".into(),
6307 LanguageSettingsContent {
6308 tab_size: NonZeroU32::new(8),
6309 ..Default::default()
6310 },
6311 );
6312 });
6313
6314 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6315 assert!(cx.read(|cx| editor.is_dirty(cx)));
6316 let save = editor
6317 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6318 .unwrap();
6319 fake_server
6320 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6321 assert_eq!(
6322 params.text_document.uri,
6323 lsp::Url::from_file_path("/file.rs").unwrap()
6324 );
6325 assert_eq!(params.options.tab_size, 8);
6326 Ok(Some(vec![]))
6327 })
6328 .next()
6329 .await;
6330 cx.executor().start_waiting();
6331 save.await;
6332}
6333
6334#[gpui::test]
6335async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6336 init_test(cx, |_| {});
6337
6338 let cols = 4;
6339 let rows = 10;
6340 let sample_text_1 = sample_text(rows, cols, 'a');
6341 assert_eq!(
6342 sample_text_1,
6343 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6344 );
6345 let sample_text_2 = sample_text(rows, cols, 'l');
6346 assert_eq!(
6347 sample_text_2,
6348 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6349 );
6350 let sample_text_3 = sample_text(rows, cols, 'v');
6351 assert_eq!(
6352 sample_text_3,
6353 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6354 );
6355
6356 let fs = FakeFs::new(cx.executor());
6357 fs.insert_tree(
6358 "/a",
6359 json!({
6360 "main.rs": sample_text_1,
6361 "other.rs": sample_text_2,
6362 "lib.rs": sample_text_3,
6363 }),
6364 )
6365 .await;
6366
6367 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6368 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6369 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6370
6371 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6372 language_registry.add(rust_lang());
6373 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6374 "Rust",
6375 FakeLspAdapter {
6376 capabilities: lsp::ServerCapabilities {
6377 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6378 ..Default::default()
6379 },
6380 ..Default::default()
6381 },
6382 );
6383
6384 let worktree = project.update(cx, |project, cx| {
6385 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
6386 assert_eq!(worktrees.len(), 1);
6387 worktrees.pop().unwrap()
6388 });
6389 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6390
6391 let buffer_1 = project
6392 .update(cx, |project, cx| {
6393 project.open_buffer((worktree_id, "main.rs"), cx)
6394 })
6395 .await
6396 .unwrap();
6397 let buffer_2 = project
6398 .update(cx, |project, cx| {
6399 project.open_buffer((worktree_id, "other.rs"), cx)
6400 })
6401 .await
6402 .unwrap();
6403 let buffer_3 = project
6404 .update(cx, |project, cx| {
6405 project.open_buffer((worktree_id, "lib.rs"), cx)
6406 })
6407 .await
6408 .unwrap();
6409
6410 let multi_buffer = cx.new_model(|cx| {
6411 let mut multi_buffer = MultiBuffer::new(0, ReadWrite);
6412 multi_buffer.push_excerpts(
6413 buffer_1.clone(),
6414 [
6415 ExcerptRange {
6416 context: Point::new(0, 0)..Point::new(3, 0),
6417 primary: None,
6418 },
6419 ExcerptRange {
6420 context: Point::new(5, 0)..Point::new(7, 0),
6421 primary: None,
6422 },
6423 ExcerptRange {
6424 context: Point::new(9, 0)..Point::new(10, 4),
6425 primary: None,
6426 },
6427 ],
6428 cx,
6429 );
6430 multi_buffer.push_excerpts(
6431 buffer_2.clone(),
6432 [
6433 ExcerptRange {
6434 context: Point::new(0, 0)..Point::new(3, 0),
6435 primary: None,
6436 },
6437 ExcerptRange {
6438 context: Point::new(5, 0)..Point::new(7, 0),
6439 primary: None,
6440 },
6441 ExcerptRange {
6442 context: Point::new(9, 0)..Point::new(10, 4),
6443 primary: None,
6444 },
6445 ],
6446 cx,
6447 );
6448 multi_buffer.push_excerpts(
6449 buffer_3.clone(),
6450 [
6451 ExcerptRange {
6452 context: Point::new(0, 0)..Point::new(3, 0),
6453 primary: None,
6454 },
6455 ExcerptRange {
6456 context: Point::new(5, 0)..Point::new(7, 0),
6457 primary: None,
6458 },
6459 ExcerptRange {
6460 context: Point::new(9, 0)..Point::new(10, 4),
6461 primary: None,
6462 },
6463 ],
6464 cx,
6465 );
6466 multi_buffer
6467 });
6468 let multi_buffer_editor = cx.new_view(|cx| {
6469 Editor::new(
6470 EditorMode::Full,
6471 multi_buffer,
6472 Some(project.clone()),
6473 true,
6474 cx,
6475 )
6476 });
6477
6478 multi_buffer_editor.update(cx, |editor, cx| {
6479 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
6480 editor.insert("|one|two|three|", cx);
6481 });
6482 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6483 multi_buffer_editor.update(cx, |editor, cx| {
6484 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
6485 s.select_ranges(Some(60..70))
6486 });
6487 editor.insert("|four|five|six|", cx);
6488 });
6489 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6490
6491 // First two buffers should be edited, but not the third one.
6492 assert_eq!(
6493 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6494 "a|one|two|three|aa\nbbbb\ncccc\n\nffff\ngggg\n\njjjj\nllll\nmmmm\nnnnn|four|five|six|\nr\n\nuuuu\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
6495 );
6496 buffer_1.update(cx, |buffer, _| {
6497 assert!(buffer.is_dirty());
6498 assert_eq!(
6499 buffer.text(),
6500 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
6501 )
6502 });
6503 buffer_2.update(cx, |buffer, _| {
6504 assert!(buffer.is_dirty());
6505 assert_eq!(
6506 buffer.text(),
6507 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
6508 )
6509 });
6510 buffer_3.update(cx, |buffer, _| {
6511 assert!(!buffer.is_dirty());
6512 assert_eq!(buffer.text(), sample_text_3,)
6513 });
6514
6515 cx.executor().start_waiting();
6516 let save = multi_buffer_editor
6517 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6518 .unwrap();
6519
6520 let fake_server = fake_servers.next().await.unwrap();
6521 fake_server
6522 .server
6523 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6524 Ok(Some(vec![lsp::TextEdit::new(
6525 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6526 format!("[{} formatted]", params.text_document.uri),
6527 )]))
6528 })
6529 .detach();
6530 save.await;
6531
6532 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
6533 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
6534 assert_eq!(
6535 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6536 "a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
6537 );
6538 buffer_1.update(cx, |buffer, _| {
6539 assert!(!buffer.is_dirty());
6540 assert_eq!(
6541 buffer.text(),
6542 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
6543 )
6544 });
6545 buffer_2.update(cx, |buffer, _| {
6546 assert!(!buffer.is_dirty());
6547 assert_eq!(
6548 buffer.text(),
6549 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
6550 )
6551 });
6552 buffer_3.update(cx, |buffer, _| {
6553 assert!(!buffer.is_dirty());
6554 assert_eq!(buffer.text(), sample_text_3,)
6555 });
6556}
6557
6558#[gpui::test]
6559async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
6560 init_test(cx, |_| {});
6561
6562 let fs = FakeFs::new(cx.executor());
6563 fs.insert_file("/file.rs", Default::default()).await;
6564
6565 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6566
6567 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6568 language_registry.add(rust_lang());
6569 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6570 "Rust",
6571 FakeLspAdapter {
6572 capabilities: lsp::ServerCapabilities {
6573 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
6574 ..Default::default()
6575 },
6576 ..Default::default()
6577 },
6578 );
6579
6580 let buffer = project
6581 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6582 .await
6583 .unwrap();
6584
6585 cx.executor().start_waiting();
6586 let fake_server = fake_servers.next().await.unwrap();
6587
6588 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6589 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6590 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6591 assert!(cx.read(|cx| editor.is_dirty(cx)));
6592
6593 let save = editor
6594 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6595 .unwrap();
6596 fake_server
6597 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6598 assert_eq!(
6599 params.text_document.uri,
6600 lsp::Url::from_file_path("/file.rs").unwrap()
6601 );
6602 assert_eq!(params.options.tab_size, 4);
6603 Ok(Some(vec![lsp::TextEdit::new(
6604 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6605 ", ".to_string(),
6606 )]))
6607 })
6608 .next()
6609 .await;
6610 cx.executor().start_waiting();
6611 save.await;
6612 assert_eq!(
6613 editor.update(cx, |editor, cx| editor.text(cx)),
6614 "one, two\nthree\n"
6615 );
6616 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6617
6618 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6619 assert!(cx.read(|cx| editor.is_dirty(cx)));
6620
6621 // Ensure we can still save even if formatting hangs.
6622 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
6623 move |params, _| async move {
6624 assert_eq!(
6625 params.text_document.uri,
6626 lsp::Url::from_file_path("/file.rs").unwrap()
6627 );
6628 futures::future::pending::<()>().await;
6629 unreachable!()
6630 },
6631 );
6632 let save = editor
6633 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6634 .unwrap();
6635 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6636 cx.executor().start_waiting();
6637 save.await;
6638 assert_eq!(
6639 editor.update(cx, |editor, cx| editor.text(cx)),
6640 "one\ntwo\nthree\n"
6641 );
6642 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6643
6644 // For non-dirty buffer, no formatting request should be sent
6645 let save = editor
6646 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6647 .unwrap();
6648 let _pending_format_request = fake_server
6649 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6650 panic!("Should not be invoked on non-dirty buffer");
6651 })
6652 .next();
6653 cx.executor().start_waiting();
6654 save.await;
6655
6656 // Set Rust language override and assert overridden tabsize is sent to language server
6657 update_test_language_settings(cx, |settings| {
6658 settings.languages.insert(
6659 "Rust".into(),
6660 LanguageSettingsContent {
6661 tab_size: NonZeroU32::new(8),
6662 ..Default::default()
6663 },
6664 );
6665 });
6666
6667 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6668 assert!(cx.read(|cx| editor.is_dirty(cx)));
6669 let save = editor
6670 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6671 .unwrap();
6672 fake_server
6673 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6674 assert_eq!(
6675 params.text_document.uri,
6676 lsp::Url::from_file_path("/file.rs").unwrap()
6677 );
6678 assert_eq!(params.options.tab_size, 8);
6679 Ok(Some(vec![]))
6680 })
6681 .next()
6682 .await;
6683 cx.executor().start_waiting();
6684 save.await;
6685}
6686
6687#[gpui::test]
6688async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
6689 init_test(cx, |settings| {
6690 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
6691 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
6692 ))
6693 });
6694
6695 let fs = FakeFs::new(cx.executor());
6696 fs.insert_file("/file.rs", Default::default()).await;
6697
6698 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6699
6700 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6701 language_registry.add(Arc::new(Language::new(
6702 LanguageConfig {
6703 name: "Rust".into(),
6704 matcher: LanguageMatcher {
6705 path_suffixes: vec!["rs".to_string()],
6706 ..Default::default()
6707 },
6708 ..LanguageConfig::default()
6709 },
6710 Some(tree_sitter_rust::language()),
6711 )));
6712 update_test_language_settings(cx, |settings| {
6713 // Enable Prettier formatting for the same buffer, and ensure
6714 // LSP is called instead of Prettier.
6715 settings.defaults.prettier = Some(PrettierSettings {
6716 allowed: true,
6717 ..PrettierSettings::default()
6718 });
6719 });
6720 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6721 "Rust",
6722 FakeLspAdapter {
6723 capabilities: lsp::ServerCapabilities {
6724 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6725 ..Default::default()
6726 },
6727 ..Default::default()
6728 },
6729 );
6730
6731 let buffer = project
6732 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6733 .await
6734 .unwrap();
6735
6736 cx.executor().start_waiting();
6737 let fake_server = fake_servers.next().await.unwrap();
6738
6739 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6740 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6741 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6742
6743 let format = editor
6744 .update(cx, |editor, cx| {
6745 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
6746 })
6747 .unwrap();
6748 fake_server
6749 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6750 assert_eq!(
6751 params.text_document.uri,
6752 lsp::Url::from_file_path("/file.rs").unwrap()
6753 );
6754 assert_eq!(params.options.tab_size, 4);
6755 Ok(Some(vec![lsp::TextEdit::new(
6756 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6757 ", ".to_string(),
6758 )]))
6759 })
6760 .next()
6761 .await;
6762 cx.executor().start_waiting();
6763 format.await;
6764 assert_eq!(
6765 editor.update(cx, |editor, cx| editor.text(cx)),
6766 "one, two\nthree\n"
6767 );
6768
6769 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6770 // Ensure we don't lock if formatting hangs.
6771 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6772 assert_eq!(
6773 params.text_document.uri,
6774 lsp::Url::from_file_path("/file.rs").unwrap()
6775 );
6776 futures::future::pending::<()>().await;
6777 unreachable!()
6778 });
6779 let format = editor
6780 .update(cx, |editor, cx| {
6781 editor.perform_format(project, FormatTrigger::Manual, cx)
6782 })
6783 .unwrap();
6784 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6785 cx.executor().start_waiting();
6786 format.await;
6787 assert_eq!(
6788 editor.update(cx, |editor, cx| editor.text(cx)),
6789 "one\ntwo\nthree\n"
6790 );
6791}
6792
6793#[gpui::test]
6794async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
6795 init_test(cx, |_| {});
6796
6797 let mut cx = EditorLspTestContext::new_rust(
6798 lsp::ServerCapabilities {
6799 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6800 ..Default::default()
6801 },
6802 cx,
6803 )
6804 .await;
6805
6806 cx.set_state(indoc! {"
6807 one.twoˇ
6808 "});
6809
6810 // The format request takes a long time. When it completes, it inserts
6811 // a newline and an indent before the `.`
6812 cx.lsp
6813 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
6814 let executor = cx.background_executor().clone();
6815 async move {
6816 executor.timer(Duration::from_millis(100)).await;
6817 Ok(Some(vec![lsp::TextEdit {
6818 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
6819 new_text: "\n ".into(),
6820 }]))
6821 }
6822 });
6823
6824 // Submit a format request.
6825 let format_1 = cx
6826 .update_editor(|editor, cx| editor.format(&Format, cx))
6827 .unwrap();
6828 cx.executor().run_until_parked();
6829
6830 // Submit a second format request.
6831 let format_2 = cx
6832 .update_editor(|editor, cx| editor.format(&Format, cx))
6833 .unwrap();
6834 cx.executor().run_until_parked();
6835
6836 // Wait for both format requests to complete
6837 cx.executor().advance_clock(Duration::from_millis(200));
6838 cx.executor().start_waiting();
6839 format_1.await.unwrap();
6840 cx.executor().start_waiting();
6841 format_2.await.unwrap();
6842
6843 // The formatting edits only happens once.
6844 cx.assert_editor_state(indoc! {"
6845 one
6846 .twoˇ
6847 "});
6848}
6849
6850#[gpui::test]
6851async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
6852 init_test(cx, |settings| {
6853 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
6854 });
6855
6856 let mut cx = EditorLspTestContext::new_rust(
6857 lsp::ServerCapabilities {
6858 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6859 ..Default::default()
6860 },
6861 cx,
6862 )
6863 .await;
6864
6865 // Set up a buffer white some trailing whitespace and no trailing newline.
6866 cx.set_state(
6867 &[
6868 "one ", //
6869 "twoˇ", //
6870 "three ", //
6871 "four", //
6872 ]
6873 .join("\n"),
6874 );
6875
6876 // Submit a format request.
6877 let format = cx
6878 .update_editor(|editor, cx| editor.format(&Format, cx))
6879 .unwrap();
6880
6881 // Record which buffer changes have been sent to the language server
6882 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
6883 cx.lsp
6884 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
6885 let buffer_changes = buffer_changes.clone();
6886 move |params, _| {
6887 buffer_changes.lock().extend(
6888 params
6889 .content_changes
6890 .into_iter()
6891 .map(|e| (e.range.unwrap(), e.text)),
6892 );
6893 }
6894 });
6895
6896 // Handle formatting requests to the language server.
6897 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
6898 let buffer_changes = buffer_changes.clone();
6899 move |_, _| {
6900 // When formatting is requested, trailing whitespace has already been stripped,
6901 // and the trailing newline has already been added.
6902 assert_eq!(
6903 &buffer_changes.lock()[1..],
6904 &[
6905 (
6906 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
6907 "".into()
6908 ),
6909 (
6910 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
6911 "".into()
6912 ),
6913 (
6914 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
6915 "\n".into()
6916 ),
6917 ]
6918 );
6919
6920 // Insert blank lines between each line of the buffer.
6921 async move {
6922 Ok(Some(vec![
6923 lsp::TextEdit {
6924 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
6925 new_text: "\n".into(),
6926 },
6927 lsp::TextEdit {
6928 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
6929 new_text: "\n".into(),
6930 },
6931 ]))
6932 }
6933 }
6934 });
6935
6936 // After formatting the buffer, the trailing whitespace is stripped,
6937 // a newline is appended, and the edits provided by the language server
6938 // have been applied.
6939 format.await.unwrap();
6940 cx.assert_editor_state(
6941 &[
6942 "one", //
6943 "", //
6944 "twoˇ", //
6945 "", //
6946 "three", //
6947 "four", //
6948 "", //
6949 ]
6950 .join("\n"),
6951 );
6952
6953 // Undoing the formatting undoes the trailing whitespace removal, the
6954 // trailing newline, and the LSP edits.
6955 cx.update_buffer(|buffer, cx| buffer.undo(cx));
6956 cx.assert_editor_state(
6957 &[
6958 "one ", //
6959 "twoˇ", //
6960 "three ", //
6961 "four", //
6962 ]
6963 .join("\n"),
6964 );
6965}
6966
6967#[gpui::test]
6968async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
6969 cx: &mut gpui::TestAppContext,
6970) {
6971 init_test(cx, |_| {});
6972
6973 cx.update(|cx| {
6974 cx.update_global::<SettingsStore, _>(|settings, cx| {
6975 settings.update_user_settings::<EditorSettings>(cx, |settings| {
6976 settings.auto_signature_help = Some(true);
6977 });
6978 });
6979 });
6980
6981 let mut cx = EditorLspTestContext::new_rust(
6982 lsp::ServerCapabilities {
6983 signature_help_provider: Some(lsp::SignatureHelpOptions {
6984 ..Default::default()
6985 }),
6986 ..Default::default()
6987 },
6988 cx,
6989 )
6990 .await;
6991
6992 let language = Language::new(
6993 LanguageConfig {
6994 name: "Rust".into(),
6995 brackets: BracketPairConfig {
6996 pairs: vec![
6997 BracketPair {
6998 start: "{".to_string(),
6999 end: "}".to_string(),
7000 close: true,
7001 surround: true,
7002 newline: true,
7003 },
7004 BracketPair {
7005 start: "(".to_string(),
7006 end: ")".to_string(),
7007 close: true,
7008 surround: true,
7009 newline: true,
7010 },
7011 BracketPair {
7012 start: "/*".to_string(),
7013 end: " */".to_string(),
7014 close: true,
7015 surround: true,
7016 newline: true,
7017 },
7018 BracketPair {
7019 start: "[".to_string(),
7020 end: "]".to_string(),
7021 close: false,
7022 surround: false,
7023 newline: true,
7024 },
7025 BracketPair {
7026 start: "\"".to_string(),
7027 end: "\"".to_string(),
7028 close: true,
7029 surround: true,
7030 newline: false,
7031 },
7032 BracketPair {
7033 start: "<".to_string(),
7034 end: ">".to_string(),
7035 close: false,
7036 surround: true,
7037 newline: true,
7038 },
7039 ],
7040 ..Default::default()
7041 },
7042 autoclose_before: "})]".to_string(),
7043 ..Default::default()
7044 },
7045 Some(tree_sitter_rust::language()),
7046 );
7047 let language = Arc::new(language);
7048
7049 cx.language_registry().add(language.clone());
7050 cx.update_buffer(|buffer, cx| {
7051 buffer.set_language(Some(language), cx);
7052 });
7053
7054 cx.set_state(
7055 &r#"
7056 fn main() {
7057 sampleˇ
7058 }
7059 "#
7060 .unindent(),
7061 );
7062
7063 cx.update_editor(|view, cx| {
7064 view.handle_input("(", cx);
7065 });
7066 cx.assert_editor_state(
7067 &"
7068 fn main() {
7069 sample(ˇ)
7070 }
7071 "
7072 .unindent(),
7073 );
7074
7075 let mocked_response = lsp::SignatureHelp {
7076 signatures: vec![lsp::SignatureInformation {
7077 label: "fn sample(param1: u8, param2: u8)".to_string(),
7078 documentation: None,
7079 parameters: Some(vec![
7080 lsp::ParameterInformation {
7081 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7082 documentation: None,
7083 },
7084 lsp::ParameterInformation {
7085 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7086 documentation: None,
7087 },
7088 ]),
7089 active_parameter: None,
7090 }],
7091 active_signature: Some(0),
7092 active_parameter: Some(0),
7093 };
7094 handle_signature_help_request(&mut cx, mocked_response).await;
7095
7096 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7097 .await;
7098
7099 cx.editor(|editor, _| {
7100 let signature_help_state = editor.signature_help_state.popover().cloned();
7101 assert!(signature_help_state.is_some());
7102 let ParsedMarkdown {
7103 text, highlights, ..
7104 } = signature_help_state.unwrap().parsed_content;
7105 assert_eq!(text, "param1: u8, param2: u8");
7106 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7107 });
7108}
7109
7110#[gpui::test]
7111async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7112 init_test(cx, |_| {});
7113
7114 cx.update(|cx| {
7115 cx.update_global::<SettingsStore, _>(|settings, cx| {
7116 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7117 settings.auto_signature_help = Some(false);
7118 settings.show_signature_help_after_edits = Some(false);
7119 });
7120 });
7121 });
7122
7123 let mut cx = EditorLspTestContext::new_rust(
7124 lsp::ServerCapabilities {
7125 signature_help_provider: Some(lsp::SignatureHelpOptions {
7126 ..Default::default()
7127 }),
7128 ..Default::default()
7129 },
7130 cx,
7131 )
7132 .await;
7133
7134 let language = Language::new(
7135 LanguageConfig {
7136 name: "Rust".into(),
7137 brackets: BracketPairConfig {
7138 pairs: vec![
7139 BracketPair {
7140 start: "{".to_string(),
7141 end: "}".to_string(),
7142 close: true,
7143 surround: true,
7144 newline: true,
7145 },
7146 BracketPair {
7147 start: "(".to_string(),
7148 end: ")".to_string(),
7149 close: true,
7150 surround: true,
7151 newline: true,
7152 },
7153 BracketPair {
7154 start: "/*".to_string(),
7155 end: " */".to_string(),
7156 close: true,
7157 surround: true,
7158 newline: true,
7159 },
7160 BracketPair {
7161 start: "[".to_string(),
7162 end: "]".to_string(),
7163 close: false,
7164 surround: false,
7165 newline: true,
7166 },
7167 BracketPair {
7168 start: "\"".to_string(),
7169 end: "\"".to_string(),
7170 close: true,
7171 surround: true,
7172 newline: false,
7173 },
7174 BracketPair {
7175 start: "<".to_string(),
7176 end: ">".to_string(),
7177 close: false,
7178 surround: true,
7179 newline: true,
7180 },
7181 ],
7182 ..Default::default()
7183 },
7184 autoclose_before: "})]".to_string(),
7185 ..Default::default()
7186 },
7187 Some(tree_sitter_rust::language()),
7188 );
7189 let language = Arc::new(language);
7190
7191 cx.language_registry().add(language.clone());
7192 cx.update_buffer(|buffer, cx| {
7193 buffer.set_language(Some(language), cx);
7194 });
7195
7196 // Ensure that signature_help is not called when no signature help is enabled.
7197 cx.set_state(
7198 &r#"
7199 fn main() {
7200 sampleˇ
7201 }
7202 "#
7203 .unindent(),
7204 );
7205 cx.update_editor(|view, cx| {
7206 view.handle_input("(", cx);
7207 });
7208 cx.assert_editor_state(
7209 &"
7210 fn main() {
7211 sample(ˇ)
7212 }
7213 "
7214 .unindent(),
7215 );
7216 cx.editor(|editor, _| {
7217 assert!(editor.signature_help_state.task().is_none());
7218 });
7219
7220 let mocked_response = lsp::SignatureHelp {
7221 signatures: vec![lsp::SignatureInformation {
7222 label: "fn sample(param1: u8, param2: u8)".to_string(),
7223 documentation: None,
7224 parameters: Some(vec![
7225 lsp::ParameterInformation {
7226 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7227 documentation: None,
7228 },
7229 lsp::ParameterInformation {
7230 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7231 documentation: None,
7232 },
7233 ]),
7234 active_parameter: None,
7235 }],
7236 active_signature: Some(0),
7237 active_parameter: Some(0),
7238 };
7239
7240 // Ensure that signature_help is called when enabled afte edits
7241 cx.update(|cx| {
7242 cx.update_global::<SettingsStore, _>(|settings, cx| {
7243 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7244 settings.auto_signature_help = Some(false);
7245 settings.show_signature_help_after_edits = Some(true);
7246 });
7247 });
7248 });
7249 cx.set_state(
7250 &r#"
7251 fn main() {
7252 sampleˇ
7253 }
7254 "#
7255 .unindent(),
7256 );
7257 cx.update_editor(|view, cx| {
7258 view.handle_input("(", cx);
7259 });
7260 cx.assert_editor_state(
7261 &"
7262 fn main() {
7263 sample(ˇ)
7264 }
7265 "
7266 .unindent(),
7267 );
7268 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7269 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7270 .await;
7271 cx.update_editor(|editor, _| {
7272 let signature_help_state = editor.signature_help_state.popover().cloned();
7273 assert!(signature_help_state.is_some());
7274 let ParsedMarkdown {
7275 text, highlights, ..
7276 } = signature_help_state.unwrap().parsed_content;
7277 assert_eq!(text, "param1: u8, param2: u8");
7278 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7279 editor.signature_help_state = SignatureHelpState::default();
7280 });
7281
7282 // Ensure that signature_help is called when auto signature help override is enabled
7283 cx.update(|cx| {
7284 cx.update_global::<SettingsStore, _>(|settings, cx| {
7285 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7286 settings.auto_signature_help = Some(true);
7287 settings.show_signature_help_after_edits = Some(false);
7288 });
7289 });
7290 });
7291 cx.set_state(
7292 &r#"
7293 fn main() {
7294 sampleˇ
7295 }
7296 "#
7297 .unindent(),
7298 );
7299 cx.update_editor(|view, cx| {
7300 view.handle_input("(", cx);
7301 });
7302 cx.assert_editor_state(
7303 &"
7304 fn main() {
7305 sample(ˇ)
7306 }
7307 "
7308 .unindent(),
7309 );
7310 handle_signature_help_request(&mut cx, mocked_response).await;
7311 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7312 .await;
7313 cx.editor(|editor, _| {
7314 let signature_help_state = editor.signature_help_state.popover().cloned();
7315 assert!(signature_help_state.is_some());
7316 let ParsedMarkdown {
7317 text, highlights, ..
7318 } = signature_help_state.unwrap().parsed_content;
7319 assert_eq!(text, "param1: u8, param2: u8");
7320 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7321 });
7322}
7323
7324#[gpui::test]
7325async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7326 init_test(cx, |_| {});
7327 cx.update(|cx| {
7328 cx.update_global::<SettingsStore, _>(|settings, cx| {
7329 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7330 settings.auto_signature_help = Some(true);
7331 });
7332 });
7333 });
7334
7335 let mut cx = EditorLspTestContext::new_rust(
7336 lsp::ServerCapabilities {
7337 signature_help_provider: Some(lsp::SignatureHelpOptions {
7338 ..Default::default()
7339 }),
7340 ..Default::default()
7341 },
7342 cx,
7343 )
7344 .await;
7345
7346 // A test that directly calls `show_signature_help`
7347 cx.update_editor(|editor, cx| {
7348 editor.show_signature_help(&ShowSignatureHelp, cx);
7349 });
7350
7351 let mocked_response = lsp::SignatureHelp {
7352 signatures: vec![lsp::SignatureInformation {
7353 label: "fn sample(param1: u8, param2: u8)".to_string(),
7354 documentation: None,
7355 parameters: Some(vec![
7356 lsp::ParameterInformation {
7357 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7358 documentation: None,
7359 },
7360 lsp::ParameterInformation {
7361 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7362 documentation: None,
7363 },
7364 ]),
7365 active_parameter: None,
7366 }],
7367 active_signature: Some(0),
7368 active_parameter: Some(0),
7369 };
7370 handle_signature_help_request(&mut cx, mocked_response).await;
7371
7372 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7373 .await;
7374
7375 cx.editor(|editor, _| {
7376 let signature_help_state = editor.signature_help_state.popover().cloned();
7377 assert!(signature_help_state.is_some());
7378 let ParsedMarkdown {
7379 text, highlights, ..
7380 } = signature_help_state.unwrap().parsed_content;
7381 assert_eq!(text, "param1: u8, param2: u8");
7382 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7383 });
7384
7385 // When exiting outside from inside the brackets, `signature_help` is closed.
7386 cx.set_state(indoc! {"
7387 fn main() {
7388 sample(ˇ);
7389 }
7390
7391 fn sample(param1: u8, param2: u8) {}
7392 "});
7393
7394 cx.update_editor(|editor, cx| {
7395 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
7396 });
7397
7398 let mocked_response = lsp::SignatureHelp {
7399 signatures: Vec::new(),
7400 active_signature: None,
7401 active_parameter: None,
7402 };
7403 handle_signature_help_request(&mut cx, mocked_response).await;
7404
7405 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7406 .await;
7407
7408 cx.editor(|editor, _| {
7409 assert!(!editor.signature_help_state.is_shown());
7410 });
7411
7412 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
7413 cx.set_state(indoc! {"
7414 fn main() {
7415 sample(ˇ);
7416 }
7417
7418 fn sample(param1: u8, param2: u8) {}
7419 "});
7420
7421 let mocked_response = lsp::SignatureHelp {
7422 signatures: vec![lsp::SignatureInformation {
7423 label: "fn sample(param1: u8, param2: u8)".to_string(),
7424 documentation: None,
7425 parameters: Some(vec![
7426 lsp::ParameterInformation {
7427 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7428 documentation: None,
7429 },
7430 lsp::ParameterInformation {
7431 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7432 documentation: None,
7433 },
7434 ]),
7435 active_parameter: None,
7436 }],
7437 active_signature: Some(0),
7438 active_parameter: Some(0),
7439 };
7440 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7441 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7442 .await;
7443 cx.editor(|editor, _| {
7444 assert!(editor.signature_help_state.is_shown());
7445 });
7446
7447 // Restore the popover with more parameter input
7448 cx.set_state(indoc! {"
7449 fn main() {
7450 sample(param1, param2ˇ);
7451 }
7452
7453 fn sample(param1: u8, param2: u8) {}
7454 "});
7455
7456 let mocked_response = lsp::SignatureHelp {
7457 signatures: vec![lsp::SignatureInformation {
7458 label: "fn sample(param1: u8, param2: u8)".to_string(),
7459 documentation: None,
7460 parameters: Some(vec![
7461 lsp::ParameterInformation {
7462 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7463 documentation: None,
7464 },
7465 lsp::ParameterInformation {
7466 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7467 documentation: None,
7468 },
7469 ]),
7470 active_parameter: None,
7471 }],
7472 active_signature: Some(0),
7473 active_parameter: Some(1),
7474 };
7475 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7476 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7477 .await;
7478
7479 // When selecting a range, the popover is gone.
7480 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
7481 cx.update_editor(|editor, cx| {
7482 editor.change_selections(None, cx, |s| {
7483 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7484 })
7485 });
7486 cx.assert_editor_state(indoc! {"
7487 fn main() {
7488 sample(param1, «ˇparam2»);
7489 }
7490
7491 fn sample(param1: u8, param2: u8) {}
7492 "});
7493 cx.editor(|editor, _| {
7494 assert!(!editor.signature_help_state.is_shown());
7495 });
7496
7497 // When unselecting again, the popover is back if within the brackets.
7498 cx.update_editor(|editor, cx| {
7499 editor.change_selections(None, cx, |s| {
7500 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7501 })
7502 });
7503 cx.assert_editor_state(indoc! {"
7504 fn main() {
7505 sample(param1, ˇparam2);
7506 }
7507
7508 fn sample(param1: u8, param2: u8) {}
7509 "});
7510 handle_signature_help_request(&mut cx, mocked_response).await;
7511 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7512 .await;
7513 cx.editor(|editor, _| {
7514 assert!(editor.signature_help_state.is_shown());
7515 });
7516
7517 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
7518 cx.update_editor(|editor, cx| {
7519 editor.change_selections(None, cx, |s| {
7520 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
7521 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7522 })
7523 });
7524 cx.assert_editor_state(indoc! {"
7525 fn main() {
7526 sample(param1, ˇparam2);
7527 }
7528
7529 fn sample(param1: u8, param2: u8) {}
7530 "});
7531
7532 let mocked_response = lsp::SignatureHelp {
7533 signatures: vec![lsp::SignatureInformation {
7534 label: "fn sample(param1: u8, param2: u8)".to_string(),
7535 documentation: None,
7536 parameters: Some(vec![
7537 lsp::ParameterInformation {
7538 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7539 documentation: None,
7540 },
7541 lsp::ParameterInformation {
7542 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7543 documentation: None,
7544 },
7545 ]),
7546 active_parameter: None,
7547 }],
7548 active_signature: Some(0),
7549 active_parameter: Some(1),
7550 };
7551 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7552 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7553 .await;
7554 cx.update_editor(|editor, cx| {
7555 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
7556 });
7557 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7558 .await;
7559 cx.update_editor(|editor, cx| {
7560 editor.change_selections(None, cx, |s| {
7561 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7562 })
7563 });
7564 cx.assert_editor_state(indoc! {"
7565 fn main() {
7566 sample(param1, «ˇparam2»);
7567 }
7568
7569 fn sample(param1: u8, param2: u8) {}
7570 "});
7571 cx.update_editor(|editor, cx| {
7572 editor.change_selections(None, cx, |s| {
7573 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7574 })
7575 });
7576 cx.assert_editor_state(indoc! {"
7577 fn main() {
7578 sample(param1, ˇparam2);
7579 }
7580
7581 fn sample(param1: u8, param2: u8) {}
7582 "});
7583 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
7584 .await;
7585}
7586
7587#[gpui::test]
7588async fn test_completion(cx: &mut gpui::TestAppContext) {
7589 init_test(cx, |_| {});
7590
7591 let mut cx = EditorLspTestContext::new_rust(
7592 lsp::ServerCapabilities {
7593 completion_provider: Some(lsp::CompletionOptions {
7594 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7595 resolve_provider: Some(true),
7596 ..Default::default()
7597 }),
7598 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
7599 ..Default::default()
7600 },
7601 cx,
7602 )
7603 .await;
7604 let counter = Arc::new(AtomicUsize::new(0));
7605
7606 cx.set_state(indoc! {"
7607 oneˇ
7608 two
7609 three
7610 "});
7611 cx.simulate_keystroke(".");
7612 handle_completion_request(
7613 &mut cx,
7614 indoc! {"
7615 one.|<>
7616 two
7617 three
7618 "},
7619 vec!["first_completion", "second_completion"],
7620 counter.clone(),
7621 )
7622 .await;
7623 cx.condition(|editor, _| editor.context_menu_visible())
7624 .await;
7625 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
7626
7627 let _handler = handle_signature_help_request(
7628 &mut cx,
7629 lsp::SignatureHelp {
7630 signatures: vec![lsp::SignatureInformation {
7631 label: "test signature".to_string(),
7632 documentation: None,
7633 parameters: Some(vec![lsp::ParameterInformation {
7634 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
7635 documentation: None,
7636 }]),
7637 active_parameter: None,
7638 }],
7639 active_signature: None,
7640 active_parameter: None,
7641 },
7642 );
7643 cx.update_editor(|editor, cx| {
7644 assert!(
7645 !editor.signature_help_state.is_shown(),
7646 "No signature help was called for"
7647 );
7648 editor.show_signature_help(&ShowSignatureHelp, cx);
7649 });
7650 cx.run_until_parked();
7651 cx.update_editor(|editor, _| {
7652 assert!(
7653 !editor.signature_help_state.is_shown(),
7654 "No signature help should be shown when completions menu is open"
7655 );
7656 });
7657
7658 let apply_additional_edits = cx.update_editor(|editor, cx| {
7659 editor.context_menu_next(&Default::default(), cx);
7660 editor
7661 .confirm_completion(&ConfirmCompletion::default(), cx)
7662 .unwrap()
7663 });
7664 cx.assert_editor_state(indoc! {"
7665 one.second_completionˇ
7666 two
7667 three
7668 "});
7669
7670 handle_resolve_completion_request(
7671 &mut cx,
7672 Some(vec![
7673 (
7674 //This overlaps with the primary completion edit which is
7675 //misbehavior from the LSP spec, test that we filter it out
7676 indoc! {"
7677 one.second_ˇcompletion
7678 two
7679 threeˇ
7680 "},
7681 "overlapping additional edit",
7682 ),
7683 (
7684 indoc! {"
7685 one.second_completion
7686 two
7687 threeˇ
7688 "},
7689 "\nadditional edit",
7690 ),
7691 ]),
7692 )
7693 .await;
7694 apply_additional_edits.await.unwrap();
7695 cx.assert_editor_state(indoc! {"
7696 one.second_completionˇ
7697 two
7698 three
7699 additional edit
7700 "});
7701
7702 cx.set_state(indoc! {"
7703 one.second_completion
7704 twoˇ
7705 threeˇ
7706 additional edit
7707 "});
7708 cx.simulate_keystroke(" ");
7709 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7710 cx.simulate_keystroke("s");
7711 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7712
7713 cx.assert_editor_state(indoc! {"
7714 one.second_completion
7715 two sˇ
7716 three sˇ
7717 additional edit
7718 "});
7719 handle_completion_request(
7720 &mut cx,
7721 indoc! {"
7722 one.second_completion
7723 two s
7724 three <s|>
7725 additional edit
7726 "},
7727 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
7728 counter.clone(),
7729 )
7730 .await;
7731 cx.condition(|editor, _| editor.context_menu_visible())
7732 .await;
7733 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
7734
7735 cx.simulate_keystroke("i");
7736
7737 handle_completion_request(
7738 &mut cx,
7739 indoc! {"
7740 one.second_completion
7741 two si
7742 three <si|>
7743 additional edit
7744 "},
7745 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
7746 counter.clone(),
7747 )
7748 .await;
7749 cx.condition(|editor, _| editor.context_menu_visible())
7750 .await;
7751 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
7752
7753 let apply_additional_edits = cx.update_editor(|editor, cx| {
7754 editor
7755 .confirm_completion(&ConfirmCompletion::default(), cx)
7756 .unwrap()
7757 });
7758 cx.assert_editor_state(indoc! {"
7759 one.second_completion
7760 two sixth_completionˇ
7761 three sixth_completionˇ
7762 additional edit
7763 "});
7764
7765 handle_resolve_completion_request(&mut cx, None).await;
7766 apply_additional_edits.await.unwrap();
7767
7768 _ = cx.update(|cx| {
7769 cx.update_global::<SettingsStore, _>(|settings, cx| {
7770 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7771 settings.show_completions_on_input = Some(false);
7772 });
7773 })
7774 });
7775 cx.set_state("editorˇ");
7776 cx.simulate_keystroke(".");
7777 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7778 cx.simulate_keystroke("c");
7779 cx.simulate_keystroke("l");
7780 cx.simulate_keystroke("o");
7781 cx.assert_editor_state("editor.cloˇ");
7782 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7783 cx.update_editor(|editor, cx| {
7784 editor.show_completions(&ShowCompletions { trigger: None }, cx);
7785 });
7786 handle_completion_request(
7787 &mut cx,
7788 "editor.<clo|>",
7789 vec!["close", "clobber"],
7790 counter.clone(),
7791 )
7792 .await;
7793 cx.condition(|editor, _| editor.context_menu_visible())
7794 .await;
7795 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
7796
7797 let apply_additional_edits = cx.update_editor(|editor, cx| {
7798 editor
7799 .confirm_completion(&ConfirmCompletion::default(), cx)
7800 .unwrap()
7801 });
7802 cx.assert_editor_state("editor.closeˇ");
7803 handle_resolve_completion_request(&mut cx, None).await;
7804 apply_additional_edits.await.unwrap();
7805}
7806
7807#[gpui::test]
7808async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
7809 init_test(cx, |_| {});
7810 let mut cx = EditorLspTestContext::new_rust(
7811 lsp::ServerCapabilities {
7812 completion_provider: Some(lsp::CompletionOptions {
7813 trigger_characters: Some(vec![".".to_string()]),
7814 ..Default::default()
7815 }),
7816 ..Default::default()
7817 },
7818 cx,
7819 )
7820 .await;
7821 cx.lsp
7822 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
7823 Ok(Some(lsp::CompletionResponse::Array(vec![
7824 lsp::CompletionItem {
7825 label: "first".into(),
7826 ..Default::default()
7827 },
7828 lsp::CompletionItem {
7829 label: "last".into(),
7830 ..Default::default()
7831 },
7832 ])))
7833 });
7834 cx.set_state("variableˇ");
7835 cx.simulate_keystroke(".");
7836 cx.executor().run_until_parked();
7837
7838 cx.update_editor(|editor, _| {
7839 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7840 assert_eq!(
7841 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
7842 &["first", "last"]
7843 );
7844 } else {
7845 panic!("expected completion menu to be open");
7846 }
7847 });
7848
7849 cx.update_editor(|editor, cx| {
7850 editor.move_page_down(&MovePageDown::default(), cx);
7851 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7852 assert!(
7853 menu.selected_item == 1,
7854 "expected PageDown to select the last item from the context menu"
7855 );
7856 } else {
7857 panic!("expected completion menu to stay open after PageDown");
7858 }
7859 });
7860
7861 cx.update_editor(|editor, cx| {
7862 editor.move_page_up(&MovePageUp::default(), cx);
7863 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7864 assert!(
7865 menu.selected_item == 0,
7866 "expected PageUp to select the first item from the context menu"
7867 );
7868 } else {
7869 panic!("expected completion menu to stay open after PageUp");
7870 }
7871 });
7872}
7873
7874#[gpui::test]
7875async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
7876 init_test(cx, |_| {});
7877
7878 let mut cx = EditorLspTestContext::new_rust(
7879 lsp::ServerCapabilities {
7880 completion_provider: Some(lsp::CompletionOptions {
7881 trigger_characters: Some(vec![".".to_string()]),
7882 resolve_provider: Some(true),
7883 ..Default::default()
7884 }),
7885 ..Default::default()
7886 },
7887 cx,
7888 )
7889 .await;
7890
7891 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
7892 cx.simulate_keystroke(".");
7893 let completion_item = lsp::CompletionItem {
7894 label: "Some".into(),
7895 kind: Some(lsp::CompletionItemKind::SNIPPET),
7896 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
7897 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
7898 kind: lsp::MarkupKind::Markdown,
7899 value: "```rust\nSome(2)\n```".to_string(),
7900 })),
7901 deprecated: Some(false),
7902 sort_text: Some("Some".to_string()),
7903 filter_text: Some("Some".to_string()),
7904 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
7905 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7906 range: lsp::Range {
7907 start: lsp::Position {
7908 line: 0,
7909 character: 22,
7910 },
7911 end: lsp::Position {
7912 line: 0,
7913 character: 22,
7914 },
7915 },
7916 new_text: "Some(2)".to_string(),
7917 })),
7918 additional_text_edits: Some(vec![lsp::TextEdit {
7919 range: lsp::Range {
7920 start: lsp::Position {
7921 line: 0,
7922 character: 20,
7923 },
7924 end: lsp::Position {
7925 line: 0,
7926 character: 22,
7927 },
7928 },
7929 new_text: "".to_string(),
7930 }]),
7931 ..Default::default()
7932 };
7933
7934 let closure_completion_item = completion_item.clone();
7935 let counter = Arc::new(AtomicUsize::new(0));
7936 let counter_clone = counter.clone();
7937 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
7938 let task_completion_item = closure_completion_item.clone();
7939 counter_clone.fetch_add(1, atomic::Ordering::Release);
7940 async move {
7941 Ok(Some(lsp::CompletionResponse::Array(vec![
7942 task_completion_item,
7943 ])))
7944 }
7945 });
7946
7947 cx.condition(|editor, _| editor.context_menu_visible())
7948 .await;
7949 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
7950 assert!(request.next().await.is_some());
7951 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
7952
7953 cx.simulate_keystroke("S");
7954 cx.simulate_keystroke("o");
7955 cx.simulate_keystroke("m");
7956 cx.condition(|editor, _| editor.context_menu_visible())
7957 .await;
7958 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
7959 assert!(request.next().await.is_some());
7960 assert!(request.next().await.is_some());
7961 assert!(request.next().await.is_some());
7962 request.close();
7963 assert!(request.next().await.is_none());
7964 assert_eq!(
7965 counter.load(atomic::Ordering::Acquire),
7966 4,
7967 "With the completions menu open, only one LSP request should happen per input"
7968 );
7969}
7970
7971#[gpui::test]
7972async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
7973 init_test(cx, |_| {});
7974 let mut cx = EditorTestContext::new(cx).await;
7975 let language = Arc::new(Language::new(
7976 LanguageConfig {
7977 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
7978 ..Default::default()
7979 },
7980 Some(tree_sitter_rust::language()),
7981 ));
7982 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
7983
7984 // If multiple selections intersect a line, the line is only toggled once.
7985 cx.set_state(indoc! {"
7986 fn a() {
7987 «//b();
7988 ˇ»// «c();
7989 //ˇ» d();
7990 }
7991 "});
7992
7993 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7994
7995 cx.assert_editor_state(indoc! {"
7996 fn a() {
7997 «b();
7998 c();
7999 ˇ» d();
8000 }
8001 "});
8002
8003 // The comment prefix is inserted at the same column for every line in a
8004 // selection.
8005 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8006
8007 cx.assert_editor_state(indoc! {"
8008 fn a() {
8009 // «b();
8010 // c();
8011 ˇ»// d();
8012 }
8013 "});
8014
8015 // If a selection ends at the beginning of a line, that line is not toggled.
8016 cx.set_selections_state(indoc! {"
8017 fn a() {
8018 // b();
8019 «// c();
8020 ˇ» // d();
8021 }
8022 "});
8023
8024 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8025
8026 cx.assert_editor_state(indoc! {"
8027 fn a() {
8028 // b();
8029 «c();
8030 ˇ» // d();
8031 }
8032 "});
8033
8034 // If a selection span a single line and is empty, the line is toggled.
8035 cx.set_state(indoc! {"
8036 fn a() {
8037 a();
8038 b();
8039 ˇ
8040 }
8041 "});
8042
8043 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8044
8045 cx.assert_editor_state(indoc! {"
8046 fn a() {
8047 a();
8048 b();
8049 //•ˇ
8050 }
8051 "});
8052
8053 // If a selection span multiple lines, empty lines are not toggled.
8054 cx.set_state(indoc! {"
8055 fn a() {
8056 «a();
8057
8058 c();ˇ»
8059 }
8060 "});
8061
8062 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8063
8064 cx.assert_editor_state(indoc! {"
8065 fn a() {
8066 // «a();
8067
8068 // c();ˇ»
8069 }
8070 "});
8071
8072 // If a selection includes multiple comment prefixes, all lines are uncommented.
8073 cx.set_state(indoc! {"
8074 fn a() {
8075 «// a();
8076 /// b();
8077 //! c();ˇ»
8078 }
8079 "});
8080
8081 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8082
8083 cx.assert_editor_state(indoc! {"
8084 fn a() {
8085 «a();
8086 b();
8087 c();ˇ»
8088 }
8089 "});
8090}
8091
8092#[gpui::test]
8093async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8094 init_test(cx, |_| {});
8095
8096 let language = Arc::new(Language::new(
8097 LanguageConfig {
8098 line_comments: vec!["// ".into()],
8099 ..Default::default()
8100 },
8101 Some(tree_sitter_rust::language()),
8102 ));
8103
8104 let mut cx = EditorTestContext::new(cx).await;
8105
8106 cx.language_registry().add(language.clone());
8107 cx.update_buffer(|buffer, cx| {
8108 buffer.set_language(Some(language), cx);
8109 });
8110
8111 let toggle_comments = &ToggleComments {
8112 advance_downwards: true,
8113 };
8114
8115 // Single cursor on one line -> advance
8116 // Cursor moves horizontally 3 characters as well on non-blank line
8117 cx.set_state(indoc!(
8118 "fn a() {
8119 ˇdog();
8120 cat();
8121 }"
8122 ));
8123 cx.update_editor(|editor, cx| {
8124 editor.toggle_comments(toggle_comments, cx);
8125 });
8126 cx.assert_editor_state(indoc!(
8127 "fn a() {
8128 // dog();
8129 catˇ();
8130 }"
8131 ));
8132
8133 // Single selection on one line -> don't advance
8134 cx.set_state(indoc!(
8135 "fn a() {
8136 «dog()ˇ»;
8137 cat();
8138 }"
8139 ));
8140 cx.update_editor(|editor, cx| {
8141 editor.toggle_comments(toggle_comments, cx);
8142 });
8143 cx.assert_editor_state(indoc!(
8144 "fn a() {
8145 // «dog()ˇ»;
8146 cat();
8147 }"
8148 ));
8149
8150 // Multiple cursors on one line -> advance
8151 cx.set_state(indoc!(
8152 "fn a() {
8153 ˇdˇog();
8154 cat();
8155 }"
8156 ));
8157 cx.update_editor(|editor, cx| {
8158 editor.toggle_comments(toggle_comments, cx);
8159 });
8160 cx.assert_editor_state(indoc!(
8161 "fn a() {
8162 // dog();
8163 catˇ(ˇ);
8164 }"
8165 ));
8166
8167 // Multiple cursors on one line, with selection -> don't advance
8168 cx.set_state(indoc!(
8169 "fn a() {
8170 ˇdˇog«()ˇ»;
8171 cat();
8172 }"
8173 ));
8174 cx.update_editor(|editor, cx| {
8175 editor.toggle_comments(toggle_comments, cx);
8176 });
8177 cx.assert_editor_state(indoc!(
8178 "fn a() {
8179 // ˇdˇog«()ˇ»;
8180 cat();
8181 }"
8182 ));
8183
8184 // Single cursor on one line -> advance
8185 // Cursor moves to column 0 on blank line
8186 cx.set_state(indoc!(
8187 "fn a() {
8188 ˇdog();
8189
8190 cat();
8191 }"
8192 ));
8193 cx.update_editor(|editor, cx| {
8194 editor.toggle_comments(toggle_comments, cx);
8195 });
8196 cx.assert_editor_state(indoc!(
8197 "fn a() {
8198 // dog();
8199 ˇ
8200 cat();
8201 }"
8202 ));
8203
8204 // Single cursor on one line -> advance
8205 // Cursor starts and ends at column 0
8206 cx.set_state(indoc!(
8207 "fn a() {
8208 ˇ dog();
8209 cat();
8210 }"
8211 ));
8212 cx.update_editor(|editor, cx| {
8213 editor.toggle_comments(toggle_comments, cx);
8214 });
8215 cx.assert_editor_state(indoc!(
8216 "fn a() {
8217 // dog();
8218 ˇ cat();
8219 }"
8220 ));
8221}
8222
8223#[gpui::test]
8224async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
8225 init_test(cx, |_| {});
8226
8227 let mut cx = EditorTestContext::new(cx).await;
8228
8229 let html_language = Arc::new(
8230 Language::new(
8231 LanguageConfig {
8232 name: "HTML".into(),
8233 block_comment: Some(("<!-- ".into(), " -->".into())),
8234 ..Default::default()
8235 },
8236 Some(tree_sitter_html::language()),
8237 )
8238 .with_injection_query(
8239 r#"
8240 (script_element
8241 (raw_text) @content
8242 (#set! "language" "javascript"))
8243 "#,
8244 )
8245 .unwrap(),
8246 );
8247
8248 let javascript_language = Arc::new(Language::new(
8249 LanguageConfig {
8250 name: "JavaScript".into(),
8251 line_comments: vec!["// ".into()],
8252 ..Default::default()
8253 },
8254 Some(tree_sitter_typescript::language_tsx()),
8255 ));
8256
8257 cx.language_registry().add(html_language.clone());
8258 cx.language_registry().add(javascript_language.clone());
8259 cx.update_buffer(|buffer, cx| {
8260 buffer.set_language(Some(html_language), cx);
8261 });
8262
8263 // Toggle comments for empty selections
8264 cx.set_state(
8265 &r#"
8266 <p>A</p>ˇ
8267 <p>B</p>ˇ
8268 <p>C</p>ˇ
8269 "#
8270 .unindent(),
8271 );
8272 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8273 cx.assert_editor_state(
8274 &r#"
8275 <!-- <p>A</p>ˇ -->
8276 <!-- <p>B</p>ˇ -->
8277 <!-- <p>C</p>ˇ -->
8278 "#
8279 .unindent(),
8280 );
8281 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8282 cx.assert_editor_state(
8283 &r#"
8284 <p>A</p>ˇ
8285 <p>B</p>ˇ
8286 <p>C</p>ˇ
8287 "#
8288 .unindent(),
8289 );
8290
8291 // Toggle comments for mixture of empty and non-empty selections, where
8292 // multiple selections occupy a given line.
8293 cx.set_state(
8294 &r#"
8295 <p>A«</p>
8296 <p>ˇ»B</p>ˇ
8297 <p>C«</p>
8298 <p>ˇ»D</p>ˇ
8299 "#
8300 .unindent(),
8301 );
8302
8303 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8304 cx.assert_editor_state(
8305 &r#"
8306 <!-- <p>A«</p>
8307 <p>ˇ»B</p>ˇ -->
8308 <!-- <p>C«</p>
8309 <p>ˇ»D</p>ˇ -->
8310 "#
8311 .unindent(),
8312 );
8313 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8314 cx.assert_editor_state(
8315 &r#"
8316 <p>A«</p>
8317 <p>ˇ»B</p>ˇ
8318 <p>C«</p>
8319 <p>ˇ»D</p>ˇ
8320 "#
8321 .unindent(),
8322 );
8323
8324 // Toggle comments when different languages are active for different
8325 // selections.
8326 cx.set_state(
8327 &r#"
8328 ˇ<script>
8329 ˇvar x = new Y();
8330 ˇ</script>
8331 "#
8332 .unindent(),
8333 );
8334 cx.executor().run_until_parked();
8335 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8336 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
8337 // Uncommenting and commenting from this position brings in even more wrong artifacts.
8338 cx.assert_editor_state(
8339 &r#"
8340 <!-- ˇ<script> -->
8341 // ˇvar x = new Y();
8342 // ˇ</script>
8343 "#
8344 .unindent(),
8345 );
8346}
8347
8348#[gpui::test]
8349fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
8350 init_test(cx, |_| {});
8351
8352 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8353 let multibuffer = cx.new_model(|cx| {
8354 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8355 multibuffer.push_excerpts(
8356 buffer.clone(),
8357 [
8358 ExcerptRange {
8359 context: Point::new(0, 0)..Point::new(0, 4),
8360 primary: None,
8361 },
8362 ExcerptRange {
8363 context: Point::new(1, 0)..Point::new(1, 4),
8364 primary: None,
8365 },
8366 ],
8367 cx,
8368 );
8369 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
8370 multibuffer
8371 });
8372
8373 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8374 _ = view.update(cx, |view, cx| {
8375 assert_eq!(view.text(cx), "aaaa\nbbbb");
8376 view.change_selections(None, cx, |s| {
8377 s.select_ranges([
8378 Point::new(0, 0)..Point::new(0, 0),
8379 Point::new(1, 0)..Point::new(1, 0),
8380 ])
8381 });
8382
8383 view.handle_input("X", cx);
8384 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
8385 assert_eq!(
8386 view.selections.ranges(cx),
8387 [
8388 Point::new(0, 1)..Point::new(0, 1),
8389 Point::new(1, 1)..Point::new(1, 1),
8390 ]
8391 );
8392
8393 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
8394 view.change_selections(None, cx, |s| {
8395 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
8396 });
8397 view.backspace(&Default::default(), cx);
8398 assert_eq!(view.text(cx), "Xa\nbbb");
8399 assert_eq!(
8400 view.selections.ranges(cx),
8401 [Point::new(1, 0)..Point::new(1, 0)]
8402 );
8403
8404 view.change_selections(None, cx, |s| {
8405 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
8406 });
8407 view.backspace(&Default::default(), cx);
8408 assert_eq!(view.text(cx), "X\nbb");
8409 assert_eq!(
8410 view.selections.ranges(cx),
8411 [Point::new(0, 1)..Point::new(0, 1)]
8412 );
8413 });
8414}
8415
8416#[gpui::test]
8417fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
8418 init_test(cx, |_| {});
8419
8420 let markers = vec![('[', ']').into(), ('(', ')').into()];
8421 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
8422 indoc! {"
8423 [aaaa
8424 (bbbb]
8425 cccc)",
8426 },
8427 markers.clone(),
8428 );
8429 let excerpt_ranges = markers.into_iter().map(|marker| {
8430 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
8431 ExcerptRange {
8432 context,
8433 primary: None,
8434 }
8435 });
8436 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
8437 let multibuffer = cx.new_model(|cx| {
8438 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8439 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
8440 multibuffer
8441 });
8442
8443 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8444 _ = view.update(cx, |view, cx| {
8445 let (expected_text, selection_ranges) = marked_text_ranges(
8446 indoc! {"
8447 aaaa
8448 bˇbbb
8449 bˇbbˇb
8450 cccc"
8451 },
8452 true,
8453 );
8454 assert_eq!(view.text(cx), expected_text);
8455 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
8456
8457 view.handle_input("X", cx);
8458
8459 let (expected_text, expected_selections) = marked_text_ranges(
8460 indoc! {"
8461 aaaa
8462 bXˇbbXb
8463 bXˇbbXˇb
8464 cccc"
8465 },
8466 false,
8467 );
8468 assert_eq!(view.text(cx), expected_text);
8469 assert_eq!(view.selections.ranges(cx), expected_selections);
8470
8471 view.newline(&Newline, cx);
8472 let (expected_text, expected_selections) = marked_text_ranges(
8473 indoc! {"
8474 aaaa
8475 bX
8476 ˇbbX
8477 b
8478 bX
8479 ˇbbX
8480 ˇb
8481 cccc"
8482 },
8483 false,
8484 );
8485 assert_eq!(view.text(cx), expected_text);
8486 assert_eq!(view.selections.ranges(cx), expected_selections);
8487 });
8488}
8489
8490#[gpui::test]
8491fn test_refresh_selections(cx: &mut TestAppContext) {
8492 init_test(cx, |_| {});
8493
8494 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8495 let mut excerpt1_id = None;
8496 let multibuffer = cx.new_model(|cx| {
8497 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8498 excerpt1_id = multibuffer
8499 .push_excerpts(
8500 buffer.clone(),
8501 [
8502 ExcerptRange {
8503 context: Point::new(0, 0)..Point::new(1, 4),
8504 primary: None,
8505 },
8506 ExcerptRange {
8507 context: Point::new(1, 0)..Point::new(2, 4),
8508 primary: None,
8509 },
8510 ],
8511 cx,
8512 )
8513 .into_iter()
8514 .next();
8515 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8516 multibuffer
8517 });
8518
8519 let editor = cx.add_window(|cx| {
8520 let mut editor = build_editor(multibuffer.clone(), cx);
8521 let snapshot = editor.snapshot(cx);
8522 editor.change_selections(None, cx, |s| {
8523 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
8524 });
8525 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
8526 assert_eq!(
8527 editor.selections.ranges(cx),
8528 [
8529 Point::new(1, 3)..Point::new(1, 3),
8530 Point::new(2, 1)..Point::new(2, 1),
8531 ]
8532 );
8533 editor
8534 });
8535
8536 // Refreshing selections is a no-op when excerpts haven't changed.
8537 _ = editor.update(cx, |editor, cx| {
8538 editor.change_selections(None, cx, |s| s.refresh());
8539 assert_eq!(
8540 editor.selections.ranges(cx),
8541 [
8542 Point::new(1, 3)..Point::new(1, 3),
8543 Point::new(2, 1)..Point::new(2, 1),
8544 ]
8545 );
8546 });
8547
8548 _ = multibuffer.update(cx, |multibuffer, cx| {
8549 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8550 });
8551 _ = editor.update(cx, |editor, cx| {
8552 // Removing an excerpt causes the first selection to become degenerate.
8553 assert_eq!(
8554 editor.selections.ranges(cx),
8555 [
8556 Point::new(0, 0)..Point::new(0, 0),
8557 Point::new(0, 1)..Point::new(0, 1)
8558 ]
8559 );
8560
8561 // Refreshing selections will relocate the first selection to the original buffer
8562 // location.
8563 editor.change_selections(None, cx, |s| s.refresh());
8564 assert_eq!(
8565 editor.selections.ranges(cx),
8566 [
8567 Point::new(0, 1)..Point::new(0, 1),
8568 Point::new(0, 3)..Point::new(0, 3)
8569 ]
8570 );
8571 assert!(editor.selections.pending_anchor().is_some());
8572 });
8573}
8574
8575#[gpui::test]
8576fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
8577 init_test(cx, |_| {});
8578
8579 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8580 let mut excerpt1_id = None;
8581 let multibuffer = cx.new_model(|cx| {
8582 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8583 excerpt1_id = multibuffer
8584 .push_excerpts(
8585 buffer.clone(),
8586 [
8587 ExcerptRange {
8588 context: Point::new(0, 0)..Point::new(1, 4),
8589 primary: None,
8590 },
8591 ExcerptRange {
8592 context: Point::new(1, 0)..Point::new(2, 4),
8593 primary: None,
8594 },
8595 ],
8596 cx,
8597 )
8598 .into_iter()
8599 .next();
8600 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8601 multibuffer
8602 });
8603
8604 let editor = cx.add_window(|cx| {
8605 let mut editor = build_editor(multibuffer.clone(), cx);
8606 let snapshot = editor.snapshot(cx);
8607 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
8608 assert_eq!(
8609 editor.selections.ranges(cx),
8610 [Point::new(1, 3)..Point::new(1, 3)]
8611 );
8612 editor
8613 });
8614
8615 _ = multibuffer.update(cx, |multibuffer, cx| {
8616 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8617 });
8618 _ = editor.update(cx, |editor, cx| {
8619 assert_eq!(
8620 editor.selections.ranges(cx),
8621 [Point::new(0, 0)..Point::new(0, 0)]
8622 );
8623
8624 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
8625 editor.change_selections(None, cx, |s| s.refresh());
8626 assert_eq!(
8627 editor.selections.ranges(cx),
8628 [Point::new(0, 3)..Point::new(0, 3)]
8629 );
8630 assert!(editor.selections.pending_anchor().is_some());
8631 });
8632}
8633
8634#[gpui::test]
8635async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
8636 init_test(cx, |_| {});
8637
8638 let language = Arc::new(
8639 Language::new(
8640 LanguageConfig {
8641 brackets: BracketPairConfig {
8642 pairs: vec![
8643 BracketPair {
8644 start: "{".to_string(),
8645 end: "}".to_string(),
8646 close: true,
8647 surround: true,
8648 newline: true,
8649 },
8650 BracketPair {
8651 start: "/* ".to_string(),
8652 end: " */".to_string(),
8653 close: true,
8654 surround: true,
8655 newline: true,
8656 },
8657 ],
8658 ..Default::default()
8659 },
8660 ..Default::default()
8661 },
8662 Some(tree_sitter_rust::language()),
8663 )
8664 .with_indents_query("")
8665 .unwrap(),
8666 );
8667
8668 let text = concat!(
8669 "{ }\n", //
8670 " x\n", //
8671 " /* */\n", //
8672 "x\n", //
8673 "{{} }\n", //
8674 );
8675
8676 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
8677 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
8678 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
8679 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
8680 .await;
8681
8682 _ = view.update(cx, |view, cx| {
8683 view.change_selections(None, cx, |s| {
8684 s.select_display_ranges([
8685 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
8686 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
8687 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
8688 ])
8689 });
8690 view.newline(&Newline, cx);
8691
8692 assert_eq!(
8693 view.buffer().read(cx).read(cx).text(),
8694 concat!(
8695 "{ \n", // Suppress rustfmt
8696 "\n", //
8697 "}\n", //
8698 " x\n", //
8699 " /* \n", //
8700 " \n", //
8701 " */\n", //
8702 "x\n", //
8703 "{{} \n", //
8704 "}\n", //
8705 )
8706 );
8707 });
8708}
8709
8710#[gpui::test]
8711fn test_highlighted_ranges(cx: &mut TestAppContext) {
8712 init_test(cx, |_| {});
8713
8714 let editor = cx.add_window(|cx| {
8715 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
8716 build_editor(buffer.clone(), cx)
8717 });
8718
8719 _ = editor.update(cx, |editor, cx| {
8720 struct Type1;
8721 struct Type2;
8722
8723 let buffer = editor.buffer.read(cx).snapshot(cx);
8724
8725 let anchor_range =
8726 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
8727
8728 editor.highlight_background::<Type1>(
8729 &[
8730 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
8731 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
8732 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
8733 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
8734 ],
8735 |_| Hsla::red(),
8736 cx,
8737 );
8738 editor.highlight_background::<Type2>(
8739 &[
8740 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
8741 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
8742 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
8743 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
8744 ],
8745 |_| Hsla::green(),
8746 cx,
8747 );
8748
8749 let snapshot = editor.snapshot(cx);
8750 let mut highlighted_ranges = editor.background_highlights_in_range(
8751 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
8752 &snapshot,
8753 cx.theme().colors(),
8754 );
8755 // Enforce a consistent ordering based on color without relying on the ordering of the
8756 // highlight's `TypeId` which is non-executor.
8757 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
8758 assert_eq!(
8759 highlighted_ranges,
8760 &[
8761 (
8762 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
8763 Hsla::red(),
8764 ),
8765 (
8766 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
8767 Hsla::red(),
8768 ),
8769 (
8770 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
8771 Hsla::green(),
8772 ),
8773 (
8774 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
8775 Hsla::green(),
8776 ),
8777 ]
8778 );
8779 assert_eq!(
8780 editor.background_highlights_in_range(
8781 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
8782 &snapshot,
8783 cx.theme().colors(),
8784 ),
8785 &[(
8786 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
8787 Hsla::red(),
8788 )]
8789 );
8790 });
8791}
8792
8793#[gpui::test]
8794async fn test_following(cx: &mut gpui::TestAppContext) {
8795 init_test(cx, |_| {});
8796
8797 let fs = FakeFs::new(cx.executor());
8798 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8799
8800 let buffer = project.update(cx, |project, cx| {
8801 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
8802 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
8803 });
8804 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
8805 let follower = cx.update(|cx| {
8806 cx.open_window(
8807 WindowOptions {
8808 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
8809 gpui::Point::new(px(0.), px(0.)),
8810 gpui::Point::new(px(10.), px(80.)),
8811 ))),
8812 ..Default::default()
8813 },
8814 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
8815 )
8816 .unwrap()
8817 });
8818
8819 let is_still_following = Rc::new(RefCell::new(true));
8820 let follower_edit_event_count = Rc::new(RefCell::new(0));
8821 let pending_update = Rc::new(RefCell::new(None));
8822 _ = follower.update(cx, {
8823 let update = pending_update.clone();
8824 let is_still_following = is_still_following.clone();
8825 let follower_edit_event_count = follower_edit_event_count.clone();
8826 |_, cx| {
8827 cx.subscribe(
8828 &leader.root_view(cx).unwrap(),
8829 move |_, leader, event, cx| {
8830 leader
8831 .read(cx)
8832 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
8833 },
8834 )
8835 .detach();
8836
8837 cx.subscribe(
8838 &follower.root_view(cx).unwrap(),
8839 move |_, _, event: &EditorEvent, _cx| {
8840 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
8841 *is_still_following.borrow_mut() = false;
8842 }
8843
8844 if let EditorEvent::BufferEdited = event {
8845 *follower_edit_event_count.borrow_mut() += 1;
8846 }
8847 },
8848 )
8849 .detach();
8850 }
8851 });
8852
8853 // Update the selections only
8854 _ = leader.update(cx, |leader, cx| {
8855 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
8856 });
8857 follower
8858 .update(cx, |follower, cx| {
8859 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8860 })
8861 .unwrap()
8862 .await
8863 .unwrap();
8864 _ = follower.update(cx, |follower, cx| {
8865 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
8866 });
8867 assert_eq!(*is_still_following.borrow(), true);
8868 assert_eq!(*follower_edit_event_count.borrow(), 0);
8869
8870 // Update the scroll position only
8871 _ = leader.update(cx, |leader, cx| {
8872 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
8873 });
8874 follower
8875 .update(cx, |follower, cx| {
8876 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8877 })
8878 .unwrap()
8879 .await
8880 .unwrap();
8881 assert_eq!(
8882 follower
8883 .update(cx, |follower, cx| follower.scroll_position(cx))
8884 .unwrap(),
8885 gpui::Point::new(1.5, 3.5)
8886 );
8887 assert_eq!(*is_still_following.borrow(), true);
8888 assert_eq!(*follower_edit_event_count.borrow(), 0);
8889
8890 // Update the selections and scroll position. The follower's scroll position is updated
8891 // via autoscroll, not via the leader's exact scroll position.
8892 _ = leader.update(cx, |leader, cx| {
8893 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
8894 leader.request_autoscroll(Autoscroll::newest(), cx);
8895 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
8896 });
8897 follower
8898 .update(cx, |follower, cx| {
8899 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8900 })
8901 .unwrap()
8902 .await
8903 .unwrap();
8904 _ = follower.update(cx, |follower, cx| {
8905 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
8906 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
8907 });
8908 assert_eq!(*is_still_following.borrow(), true);
8909
8910 // Creating a pending selection that precedes another selection
8911 _ = leader.update(cx, |leader, cx| {
8912 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
8913 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
8914 });
8915 follower
8916 .update(cx, |follower, cx| {
8917 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8918 })
8919 .unwrap()
8920 .await
8921 .unwrap();
8922 _ = follower.update(cx, |follower, cx| {
8923 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
8924 });
8925 assert_eq!(*is_still_following.borrow(), true);
8926
8927 // Extend the pending selection so that it surrounds another selection
8928 _ = leader.update(cx, |leader, cx| {
8929 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
8930 });
8931 follower
8932 .update(cx, |follower, cx| {
8933 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8934 })
8935 .unwrap()
8936 .await
8937 .unwrap();
8938 _ = follower.update(cx, |follower, cx| {
8939 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
8940 });
8941
8942 // Scrolling locally breaks the follow
8943 _ = follower.update(cx, |follower, cx| {
8944 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
8945 follower.set_scroll_anchor(
8946 ScrollAnchor {
8947 anchor: top_anchor,
8948 offset: gpui::Point::new(0.0, 0.5),
8949 },
8950 cx,
8951 );
8952 });
8953 assert_eq!(*is_still_following.borrow(), false);
8954}
8955
8956#[gpui::test]
8957async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
8958 init_test(cx, |_| {});
8959
8960 let fs = FakeFs::new(cx.executor());
8961 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8962 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8963 let pane = workspace
8964 .update(cx, |workspace, _| workspace.active_pane().clone())
8965 .unwrap();
8966
8967 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
8968
8969 let leader = pane.update(cx, |_, cx| {
8970 let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
8971 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
8972 });
8973
8974 // Start following the editor when it has no excerpts.
8975 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
8976 let follower_1 = cx
8977 .update_window(*workspace.deref(), |_, cx| {
8978 Editor::from_state_proto(
8979 workspace.root_view(cx).unwrap(),
8980 ViewId {
8981 creator: Default::default(),
8982 id: 0,
8983 },
8984 &mut state_message,
8985 cx,
8986 )
8987 })
8988 .unwrap()
8989 .unwrap()
8990 .await
8991 .unwrap();
8992
8993 let update_message = Rc::new(RefCell::new(None));
8994 follower_1.update(cx, {
8995 let update = update_message.clone();
8996 |_, cx| {
8997 cx.subscribe(&leader, move |_, leader, event, cx| {
8998 leader
8999 .read(cx)
9000 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9001 })
9002 .detach();
9003 }
9004 });
9005
9006 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
9007 (
9008 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9009 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9010 )
9011 });
9012
9013 // Insert some excerpts.
9014 _ = leader.update(cx, |leader, cx| {
9015 leader.buffer.update(cx, |multibuffer, cx| {
9016 let excerpt_ids = multibuffer.push_excerpts(
9017 buffer_1.clone(),
9018 [
9019 ExcerptRange {
9020 context: 1..6,
9021 primary: None,
9022 },
9023 ExcerptRange {
9024 context: 12..15,
9025 primary: None,
9026 },
9027 ExcerptRange {
9028 context: 0..3,
9029 primary: None,
9030 },
9031 ],
9032 cx,
9033 );
9034 multibuffer.insert_excerpts_after(
9035 excerpt_ids[0],
9036 buffer_2.clone(),
9037 [
9038 ExcerptRange {
9039 context: 8..12,
9040 primary: None,
9041 },
9042 ExcerptRange {
9043 context: 0..6,
9044 primary: None,
9045 },
9046 ],
9047 cx,
9048 );
9049 });
9050 });
9051
9052 // Apply the update of adding the excerpts.
9053 follower_1
9054 .update(cx, |follower, cx| {
9055 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9056 })
9057 .await
9058 .unwrap();
9059 assert_eq!(
9060 follower_1.update(cx, |editor, cx| editor.text(cx)),
9061 leader.update(cx, |editor, cx| editor.text(cx))
9062 );
9063 update_message.borrow_mut().take();
9064
9065 // Start following separately after it already has excerpts.
9066 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9067 let follower_2 = cx
9068 .update_window(*workspace.deref(), |_, cx| {
9069 Editor::from_state_proto(
9070 workspace.root_view(cx).unwrap().clone(),
9071 ViewId {
9072 creator: Default::default(),
9073 id: 0,
9074 },
9075 &mut state_message,
9076 cx,
9077 )
9078 })
9079 .unwrap()
9080 .unwrap()
9081 .await
9082 .unwrap();
9083 assert_eq!(
9084 follower_2.update(cx, |editor, cx| editor.text(cx)),
9085 leader.update(cx, |editor, cx| editor.text(cx))
9086 );
9087
9088 // Remove some excerpts.
9089 _ = leader.update(cx, |leader, cx| {
9090 leader.buffer.update(cx, |multibuffer, cx| {
9091 let excerpt_ids = multibuffer.excerpt_ids();
9092 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9093 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9094 });
9095 });
9096
9097 // Apply the update of removing the excerpts.
9098 follower_1
9099 .update(cx, |follower, cx| {
9100 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9101 })
9102 .await
9103 .unwrap();
9104 follower_2
9105 .update(cx, |follower, cx| {
9106 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9107 })
9108 .await
9109 .unwrap();
9110 update_message.borrow_mut().take();
9111 assert_eq!(
9112 follower_1.update(cx, |editor, cx| editor.text(cx)),
9113 leader.update(cx, |editor, cx| editor.text(cx))
9114 );
9115}
9116
9117#[gpui::test]
9118async fn go_to_prev_overlapping_diagnostic(
9119 executor: BackgroundExecutor,
9120 cx: &mut gpui::TestAppContext,
9121) {
9122 init_test(cx, |_| {});
9123
9124 let mut cx = EditorTestContext::new(cx).await;
9125 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9126
9127 cx.set_state(indoc! {"
9128 ˇfn func(abc def: i32) -> u32 {
9129 }
9130 "});
9131
9132 _ = cx.update(|cx| {
9133 _ = project.update(cx, |project, cx| {
9134 project
9135 .update_diagnostics(
9136 LanguageServerId(0),
9137 lsp::PublishDiagnosticsParams {
9138 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9139 version: None,
9140 diagnostics: vec![
9141 lsp::Diagnostic {
9142 range: lsp::Range::new(
9143 lsp::Position::new(0, 11),
9144 lsp::Position::new(0, 12),
9145 ),
9146 severity: Some(lsp::DiagnosticSeverity::ERROR),
9147 ..Default::default()
9148 },
9149 lsp::Diagnostic {
9150 range: lsp::Range::new(
9151 lsp::Position::new(0, 12),
9152 lsp::Position::new(0, 15),
9153 ),
9154 severity: Some(lsp::DiagnosticSeverity::ERROR),
9155 ..Default::default()
9156 },
9157 lsp::Diagnostic {
9158 range: lsp::Range::new(
9159 lsp::Position::new(0, 25),
9160 lsp::Position::new(0, 28),
9161 ),
9162 severity: Some(lsp::DiagnosticSeverity::ERROR),
9163 ..Default::default()
9164 },
9165 ],
9166 },
9167 &[],
9168 cx,
9169 )
9170 .unwrap()
9171 });
9172 });
9173
9174 executor.run_until_parked();
9175
9176 cx.update_editor(|editor, cx| {
9177 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9178 });
9179
9180 cx.assert_editor_state(indoc! {"
9181 fn func(abc def: i32) -> ˇu32 {
9182 }
9183 "});
9184
9185 cx.update_editor(|editor, cx| {
9186 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9187 });
9188
9189 cx.assert_editor_state(indoc! {"
9190 fn func(abc ˇdef: i32) -> u32 {
9191 }
9192 "});
9193
9194 cx.update_editor(|editor, cx| {
9195 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9196 });
9197
9198 cx.assert_editor_state(indoc! {"
9199 fn func(abcˇ def: i32) -> u32 {
9200 }
9201 "});
9202
9203 cx.update_editor(|editor, cx| {
9204 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9205 });
9206
9207 cx.assert_editor_state(indoc! {"
9208 fn func(abc def: i32) -> ˇu32 {
9209 }
9210 "});
9211}
9212
9213#[gpui::test]
9214async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
9215 init_test(cx, |_| {});
9216
9217 let mut cx = EditorTestContext::new(cx).await;
9218
9219 cx.set_state(indoc! {"
9220 fn func(abˇc def: i32) -> u32 {
9221 }
9222 "});
9223 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9224
9225 cx.update(|cx| {
9226 project.update(cx, |project, cx| {
9227 project.update_diagnostics(
9228 LanguageServerId(0),
9229 lsp::PublishDiagnosticsParams {
9230 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9231 version: None,
9232 diagnostics: vec![lsp::Diagnostic {
9233 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
9234 severity: Some(lsp::DiagnosticSeverity::ERROR),
9235 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
9236 ..Default::default()
9237 }],
9238 },
9239 &[],
9240 cx,
9241 )
9242 })
9243 }).unwrap();
9244 cx.run_until_parked();
9245 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
9246 cx.run_until_parked();
9247 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
9248}
9249
9250#[gpui::test]
9251async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9252 init_test(cx, |_| {});
9253
9254 let mut cx = EditorTestContext::new(cx).await;
9255
9256 let diff_base = r#"
9257 use some::mod;
9258
9259 const A: u32 = 42;
9260
9261 fn main() {
9262 println!("hello");
9263
9264 println!("world");
9265 }
9266 "#
9267 .unindent();
9268
9269 // Edits are modified, removed, modified, added
9270 cx.set_state(
9271 &r#"
9272 use some::modified;
9273
9274 ˇ
9275 fn main() {
9276 println!("hello there");
9277
9278 println!("around the");
9279 println!("world");
9280 }
9281 "#
9282 .unindent(),
9283 );
9284
9285 cx.set_diff_base(Some(&diff_base));
9286 executor.run_until_parked();
9287
9288 cx.update_editor(|editor, cx| {
9289 //Wrap around the bottom of the buffer
9290 for _ in 0..3 {
9291 editor.go_to_hunk(&GoToHunk, cx);
9292 }
9293 });
9294
9295 cx.assert_editor_state(
9296 &r#"
9297 ˇuse some::modified;
9298
9299
9300 fn main() {
9301 println!("hello there");
9302
9303 println!("around the");
9304 println!("world");
9305 }
9306 "#
9307 .unindent(),
9308 );
9309
9310 cx.update_editor(|editor, cx| {
9311 //Wrap around the top of the buffer
9312 for _ in 0..2 {
9313 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9314 }
9315 });
9316
9317 cx.assert_editor_state(
9318 &r#"
9319 use some::modified;
9320
9321
9322 fn main() {
9323 ˇ println!("hello there");
9324
9325 println!("around the");
9326 println!("world");
9327 }
9328 "#
9329 .unindent(),
9330 );
9331
9332 cx.update_editor(|editor, cx| {
9333 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9334 });
9335
9336 cx.assert_editor_state(
9337 &r#"
9338 use some::modified;
9339
9340 ˇ
9341 fn main() {
9342 println!("hello there");
9343
9344 println!("around the");
9345 println!("world");
9346 }
9347 "#
9348 .unindent(),
9349 );
9350
9351 cx.update_editor(|editor, cx| {
9352 for _ in 0..3 {
9353 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9354 }
9355 });
9356
9357 cx.assert_editor_state(
9358 &r#"
9359 use some::modified;
9360
9361
9362 fn main() {
9363 ˇ println!("hello there");
9364
9365 println!("around the");
9366 println!("world");
9367 }
9368 "#
9369 .unindent(),
9370 );
9371
9372 cx.update_editor(|editor, cx| {
9373 editor.fold(&Fold, cx);
9374
9375 //Make sure that the fold only gets one hunk
9376 for _ in 0..4 {
9377 editor.go_to_hunk(&GoToHunk, cx);
9378 }
9379 });
9380
9381 cx.assert_editor_state(
9382 &r#"
9383 ˇuse some::modified;
9384
9385
9386 fn main() {
9387 println!("hello there");
9388
9389 println!("around the");
9390 println!("world");
9391 }
9392 "#
9393 .unindent(),
9394 );
9395}
9396
9397#[test]
9398fn test_split_words() {
9399 fn split(text: &str) -> Vec<&str> {
9400 split_words(text).collect()
9401 }
9402
9403 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
9404 assert_eq!(split("hello_world"), &["hello_", "world"]);
9405 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
9406 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
9407 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
9408 assert_eq!(split("helloworld"), &["helloworld"]);
9409
9410 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
9411}
9412
9413#[gpui::test]
9414async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
9415 init_test(cx, |_| {});
9416
9417 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
9418 let mut assert = |before, after| {
9419 let _state_context = cx.set_state(before);
9420 cx.update_editor(|editor, cx| {
9421 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
9422 });
9423 cx.assert_editor_state(after);
9424 };
9425
9426 // Outside bracket jumps to outside of matching bracket
9427 assert("console.logˇ(var);", "console.log(var)ˇ;");
9428 assert("console.log(var)ˇ;", "console.logˇ(var);");
9429
9430 // Inside bracket jumps to inside of matching bracket
9431 assert("console.log(ˇvar);", "console.log(varˇ);");
9432 assert("console.log(varˇ);", "console.log(ˇvar);");
9433
9434 // When outside a bracket and inside, favor jumping to the inside bracket
9435 assert(
9436 "console.log('foo', [1, 2, 3]ˇ);",
9437 "console.log(ˇ'foo', [1, 2, 3]);",
9438 );
9439 assert(
9440 "console.log(ˇ'foo', [1, 2, 3]);",
9441 "console.log('foo', [1, 2, 3]ˇ);",
9442 );
9443
9444 // Bias forward if two options are equally likely
9445 assert(
9446 "let result = curried_fun()ˇ();",
9447 "let result = curried_fun()()ˇ;",
9448 );
9449
9450 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
9451 assert(
9452 indoc! {"
9453 function test() {
9454 console.log('test')ˇ
9455 }"},
9456 indoc! {"
9457 function test() {
9458 console.logˇ('test')
9459 }"},
9460 );
9461}
9462
9463#[gpui::test]
9464async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
9465 init_test(cx, |_| {});
9466
9467 let fs = FakeFs::new(cx.executor());
9468 fs.insert_tree(
9469 "/a",
9470 json!({
9471 "main.rs": "fn main() { let a = 5; }",
9472 "other.rs": "// Test file",
9473 }),
9474 )
9475 .await;
9476 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9477
9478 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9479 language_registry.add(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 brackets: BracketPairConfig {
9487 pairs: vec![BracketPair {
9488 start: "{".to_string(),
9489 end: "}".to_string(),
9490 close: true,
9491 surround: true,
9492 newline: true,
9493 }],
9494 disabled_scopes_by_bracket_ix: Vec::new(),
9495 },
9496 ..Default::default()
9497 },
9498 Some(tree_sitter_rust::language()),
9499 )));
9500 let mut fake_servers = language_registry.register_fake_lsp_adapter(
9501 "Rust",
9502 FakeLspAdapter {
9503 capabilities: lsp::ServerCapabilities {
9504 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
9505 first_trigger_character: "{".to_string(),
9506 more_trigger_character: None,
9507 }),
9508 ..Default::default()
9509 },
9510 ..Default::default()
9511 },
9512 );
9513
9514 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9515
9516 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9517
9518 let worktree_id = workspace
9519 .update(cx, |workspace, cx| {
9520 workspace.project().update(cx, |project, cx| {
9521 project.worktrees(cx).next().unwrap().read(cx).id()
9522 })
9523 })
9524 .unwrap();
9525
9526 let buffer = project
9527 .update(cx, |project, cx| {
9528 project.open_local_buffer("/a/main.rs", cx)
9529 })
9530 .await
9531 .unwrap();
9532 cx.executor().run_until_parked();
9533 cx.executor().start_waiting();
9534 let fake_server = fake_servers.next().await.unwrap();
9535 let editor_handle = workspace
9536 .update(cx, |workspace, cx| {
9537 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
9538 })
9539 .unwrap()
9540 .await
9541 .unwrap()
9542 .downcast::<Editor>()
9543 .unwrap();
9544
9545 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
9546 assert_eq!(
9547 params.text_document_position.text_document.uri,
9548 lsp::Url::from_file_path("/a/main.rs").unwrap(),
9549 );
9550 assert_eq!(
9551 params.text_document_position.position,
9552 lsp::Position::new(0, 21),
9553 );
9554
9555 Ok(Some(vec![lsp::TextEdit {
9556 new_text: "]".to_string(),
9557 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
9558 }]))
9559 });
9560
9561 editor_handle.update(cx, |editor, cx| {
9562 editor.focus(cx);
9563 editor.change_selections(None, cx, |s| {
9564 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
9565 });
9566 editor.handle_input("{", cx);
9567 });
9568
9569 cx.executor().run_until_parked();
9570
9571 _ = buffer.update(cx, |buffer, _| {
9572 assert_eq!(
9573 buffer.text(),
9574 "fn main() { let a = {5}; }",
9575 "No extra braces from on type formatting should appear in the buffer"
9576 )
9577 });
9578}
9579
9580#[gpui::test]
9581async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
9582 init_test(cx, |_| {});
9583
9584 let fs = FakeFs::new(cx.executor());
9585 fs.insert_tree(
9586 "/a",
9587 json!({
9588 "main.rs": "fn main() { let a = 5; }",
9589 "other.rs": "// Test file",
9590 }),
9591 )
9592 .await;
9593
9594 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9595
9596 let server_restarts = Arc::new(AtomicUsize::new(0));
9597 let closure_restarts = Arc::clone(&server_restarts);
9598 let language_server_name = "test language server";
9599 let language_name: Arc<str> = "Rust".into();
9600
9601 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9602 language_registry.add(Arc::new(Language::new(
9603 LanguageConfig {
9604 name: Arc::clone(&language_name),
9605 matcher: LanguageMatcher {
9606 path_suffixes: vec!["rs".to_string()],
9607 ..Default::default()
9608 },
9609 ..Default::default()
9610 },
9611 Some(tree_sitter_rust::language()),
9612 )));
9613 let mut fake_servers = language_registry.register_fake_lsp_adapter(
9614 "Rust",
9615 FakeLspAdapter {
9616 name: language_server_name,
9617 initialization_options: Some(json!({
9618 "testOptionValue": true
9619 })),
9620 initializer: Some(Box::new(move |fake_server| {
9621 let task_restarts = Arc::clone(&closure_restarts);
9622 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
9623 task_restarts.fetch_add(1, atomic::Ordering::Release);
9624 futures::future::ready(Ok(()))
9625 });
9626 })),
9627 ..Default::default()
9628 },
9629 );
9630
9631 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9632 let _buffer = project
9633 .update(cx, |project, cx| {
9634 project.open_local_buffer("/a/main.rs", cx)
9635 })
9636 .await
9637 .unwrap();
9638 let _fake_server = fake_servers.next().await.unwrap();
9639 update_test_language_settings(cx, |language_settings| {
9640 language_settings.languages.insert(
9641 Arc::clone(&language_name),
9642 LanguageSettingsContent {
9643 tab_size: NonZeroU32::new(8),
9644 ..Default::default()
9645 },
9646 );
9647 });
9648 cx.executor().run_until_parked();
9649 assert_eq!(
9650 server_restarts.load(atomic::Ordering::Acquire),
9651 0,
9652 "Should not restart LSP server on an unrelated change"
9653 );
9654
9655 update_test_project_settings(cx, |project_settings| {
9656 project_settings.lsp.insert(
9657 "Some other server name".into(),
9658 LspSettings {
9659 binary: None,
9660 settings: None,
9661 initialization_options: Some(json!({
9662 "some other init value": false
9663 })),
9664 },
9665 );
9666 });
9667 cx.executor().run_until_parked();
9668 assert_eq!(
9669 server_restarts.load(atomic::Ordering::Acquire),
9670 0,
9671 "Should not restart LSP server on an unrelated LSP settings change"
9672 );
9673
9674 update_test_project_settings(cx, |project_settings| {
9675 project_settings.lsp.insert(
9676 language_server_name.into(),
9677 LspSettings {
9678 binary: None,
9679 settings: None,
9680 initialization_options: Some(json!({
9681 "anotherInitValue": false
9682 })),
9683 },
9684 );
9685 });
9686 cx.executor().run_until_parked();
9687 assert_eq!(
9688 server_restarts.load(atomic::Ordering::Acquire),
9689 1,
9690 "Should restart LSP server on a related LSP settings change"
9691 );
9692
9693 update_test_project_settings(cx, |project_settings| {
9694 project_settings.lsp.insert(
9695 language_server_name.into(),
9696 LspSettings {
9697 binary: None,
9698 settings: None,
9699 initialization_options: Some(json!({
9700 "anotherInitValue": false
9701 })),
9702 },
9703 );
9704 });
9705 cx.executor().run_until_parked();
9706 assert_eq!(
9707 server_restarts.load(atomic::Ordering::Acquire),
9708 1,
9709 "Should not restart LSP server on a related LSP settings change that is the same"
9710 );
9711
9712 update_test_project_settings(cx, |project_settings| {
9713 project_settings.lsp.insert(
9714 language_server_name.into(),
9715 LspSettings {
9716 binary: None,
9717 settings: None,
9718 initialization_options: None,
9719 },
9720 );
9721 });
9722 cx.executor().run_until_parked();
9723 assert_eq!(
9724 server_restarts.load(atomic::Ordering::Acquire),
9725 2,
9726 "Should restart LSP server on another related LSP settings change"
9727 );
9728}
9729
9730#[gpui::test]
9731async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
9732 init_test(cx, |_| {});
9733
9734 let mut cx = EditorLspTestContext::new_rust(
9735 lsp::ServerCapabilities {
9736 completion_provider: Some(lsp::CompletionOptions {
9737 trigger_characters: Some(vec![".".to_string()]),
9738 resolve_provider: Some(true),
9739 ..Default::default()
9740 }),
9741 ..Default::default()
9742 },
9743 cx,
9744 )
9745 .await;
9746
9747 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9748 cx.simulate_keystroke(".");
9749 let completion_item = lsp::CompletionItem {
9750 label: "some".into(),
9751 kind: Some(lsp::CompletionItemKind::SNIPPET),
9752 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9753 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9754 kind: lsp::MarkupKind::Markdown,
9755 value: "```rust\nSome(2)\n```".to_string(),
9756 })),
9757 deprecated: Some(false),
9758 sort_text: Some("fffffff2".to_string()),
9759 filter_text: Some("some".to_string()),
9760 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9761 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9762 range: lsp::Range {
9763 start: lsp::Position {
9764 line: 0,
9765 character: 22,
9766 },
9767 end: lsp::Position {
9768 line: 0,
9769 character: 22,
9770 },
9771 },
9772 new_text: "Some(2)".to_string(),
9773 })),
9774 additional_text_edits: Some(vec![lsp::TextEdit {
9775 range: lsp::Range {
9776 start: lsp::Position {
9777 line: 0,
9778 character: 20,
9779 },
9780 end: lsp::Position {
9781 line: 0,
9782 character: 22,
9783 },
9784 },
9785 new_text: "".to_string(),
9786 }]),
9787 ..Default::default()
9788 };
9789
9790 let closure_completion_item = completion_item.clone();
9791 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9792 let task_completion_item = closure_completion_item.clone();
9793 async move {
9794 Ok(Some(lsp::CompletionResponse::Array(vec![
9795 task_completion_item,
9796 ])))
9797 }
9798 });
9799
9800 request.next().await;
9801
9802 cx.condition(|editor, _| editor.context_menu_visible())
9803 .await;
9804 let apply_additional_edits = cx.update_editor(|editor, cx| {
9805 editor
9806 .confirm_completion(&ConfirmCompletion::default(), cx)
9807 .unwrap()
9808 });
9809 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
9810
9811 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
9812 let task_completion_item = completion_item.clone();
9813 async move { Ok(task_completion_item) }
9814 })
9815 .next()
9816 .await
9817 .unwrap();
9818 apply_additional_edits.await.unwrap();
9819 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
9820}
9821
9822#[gpui::test]
9823async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
9824 init_test(cx, |_| {});
9825
9826 let mut cx = EditorLspTestContext::new(
9827 Language::new(
9828 LanguageConfig {
9829 matcher: LanguageMatcher {
9830 path_suffixes: vec!["jsx".into()],
9831 ..Default::default()
9832 },
9833 overrides: [(
9834 "element".into(),
9835 LanguageConfigOverride {
9836 word_characters: Override::Set(['-'].into_iter().collect()),
9837 ..Default::default()
9838 },
9839 )]
9840 .into_iter()
9841 .collect(),
9842 ..Default::default()
9843 },
9844 Some(tree_sitter_typescript::language_tsx()),
9845 )
9846 .with_override_query("(jsx_self_closing_element) @element")
9847 .unwrap(),
9848 lsp::ServerCapabilities {
9849 completion_provider: Some(lsp::CompletionOptions {
9850 trigger_characters: Some(vec![":".to_string()]),
9851 ..Default::default()
9852 }),
9853 ..Default::default()
9854 },
9855 cx,
9856 )
9857 .await;
9858
9859 cx.lsp
9860 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9861 Ok(Some(lsp::CompletionResponse::Array(vec![
9862 lsp::CompletionItem {
9863 label: "bg-blue".into(),
9864 ..Default::default()
9865 },
9866 lsp::CompletionItem {
9867 label: "bg-red".into(),
9868 ..Default::default()
9869 },
9870 lsp::CompletionItem {
9871 label: "bg-yellow".into(),
9872 ..Default::default()
9873 },
9874 ])))
9875 });
9876
9877 cx.set_state(r#"<p class="bgˇ" />"#);
9878
9879 // Trigger completion when typing a dash, because the dash is an extra
9880 // word character in the 'element' scope, which contains the cursor.
9881 cx.simulate_keystroke("-");
9882 cx.executor().run_until_parked();
9883 cx.update_editor(|editor, _| {
9884 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9885 assert_eq!(
9886 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9887 &["bg-red", "bg-blue", "bg-yellow"]
9888 );
9889 } else {
9890 panic!("expected completion menu to be open");
9891 }
9892 });
9893
9894 cx.simulate_keystroke("l");
9895 cx.executor().run_until_parked();
9896 cx.update_editor(|editor, _| {
9897 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9898 assert_eq!(
9899 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9900 &["bg-blue", "bg-yellow"]
9901 );
9902 } else {
9903 panic!("expected completion menu to be open");
9904 }
9905 });
9906
9907 // When filtering completions, consider the character after the '-' to
9908 // be the start of a subword.
9909 cx.set_state(r#"<p class="yelˇ" />"#);
9910 cx.simulate_keystroke("l");
9911 cx.executor().run_until_parked();
9912 cx.update_editor(|editor, _| {
9913 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9914 assert_eq!(
9915 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9916 &["bg-yellow"]
9917 );
9918 } else {
9919 panic!("expected completion menu to be open");
9920 }
9921 });
9922}
9923
9924#[gpui::test]
9925async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
9926 init_test(cx, |settings| {
9927 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
9928 FormatterList(vec![Formatter::Prettier].into()),
9929 ))
9930 });
9931
9932 let fs = FakeFs::new(cx.executor());
9933 fs.insert_file("/file.ts", Default::default()).await;
9934
9935 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
9936 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9937
9938 language_registry.add(Arc::new(Language::new(
9939 LanguageConfig {
9940 name: "TypeScript".into(),
9941 matcher: LanguageMatcher {
9942 path_suffixes: vec!["ts".to_string()],
9943 ..Default::default()
9944 },
9945 ..Default::default()
9946 },
9947 Some(tree_sitter_rust::language()),
9948 )));
9949 update_test_language_settings(cx, |settings| {
9950 settings.defaults.prettier = Some(PrettierSettings {
9951 allowed: true,
9952 ..PrettierSettings::default()
9953 });
9954 });
9955
9956 let test_plugin = "test_plugin";
9957 let _ = language_registry.register_fake_lsp_adapter(
9958 "TypeScript",
9959 FakeLspAdapter {
9960 prettier_plugins: vec![test_plugin],
9961 ..Default::default()
9962 },
9963 );
9964
9965 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
9966 let buffer = project
9967 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
9968 .await
9969 .unwrap();
9970
9971 let buffer_text = "one\ntwo\nthree\n";
9972 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9973 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9974 _ = editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
9975
9976 editor
9977 .update(cx, |editor, cx| {
9978 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
9979 })
9980 .unwrap()
9981 .await;
9982 assert_eq!(
9983 editor.update(cx, |editor, cx| editor.text(cx)),
9984 buffer_text.to_string() + prettier_format_suffix,
9985 "Test prettier formatting was not applied to the original buffer text",
9986 );
9987
9988 update_test_language_settings(cx, |settings| {
9989 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
9990 });
9991 let format = editor.update(cx, |editor, cx| {
9992 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
9993 });
9994 format.await.unwrap();
9995 assert_eq!(
9996 editor.update(cx, |editor, cx| editor.text(cx)),
9997 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
9998 "Autoformatting (via test prettier) was not applied to the original buffer text",
9999 );
10000}
10001
10002#[gpui::test]
10003async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
10004 init_test(cx, |_| {});
10005 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10006 let base_text = indoc! {r#"struct Row;
10007struct Row1;
10008struct Row2;
10009
10010struct Row4;
10011struct Row5;
10012struct Row6;
10013
10014struct Row8;
10015struct Row9;
10016struct Row10;"#};
10017
10018 // When addition hunks are not adjacent to carets, no hunk revert is performed
10019 assert_hunk_revert(
10020 indoc! {r#"struct Row;
10021 struct Row1;
10022 struct Row1.1;
10023 struct Row1.2;
10024 struct Row2;ˇ
10025
10026 struct Row4;
10027 struct Row5;
10028 struct Row6;
10029
10030 struct Row8;
10031 ˇstruct Row9;
10032 struct Row9.1;
10033 struct Row9.2;
10034 struct Row9.3;
10035 struct Row10;"#},
10036 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10037 indoc! {r#"struct Row;
10038 struct Row1;
10039 struct Row1.1;
10040 struct Row1.2;
10041 struct Row2;ˇ
10042
10043 struct Row4;
10044 struct Row5;
10045 struct Row6;
10046
10047 struct Row8;
10048 ˇstruct Row9;
10049 struct Row9.1;
10050 struct Row9.2;
10051 struct Row9.3;
10052 struct Row10;"#},
10053 base_text,
10054 &mut cx,
10055 );
10056 // Same for selections
10057 assert_hunk_revert(
10058 indoc! {r#"struct Row;
10059 struct Row1;
10060 struct Row2;
10061 struct Row2.1;
10062 struct Row2.2;
10063 «ˇ
10064 struct Row4;
10065 struct» Row5;
10066 «struct Row6;
10067 ˇ»
10068 struct Row9.1;
10069 struct Row9.2;
10070 struct Row9.3;
10071 struct Row8;
10072 struct Row9;
10073 struct Row10;"#},
10074 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10075 indoc! {r#"struct Row;
10076 struct Row1;
10077 struct Row2;
10078 struct Row2.1;
10079 struct Row2.2;
10080 «ˇ
10081 struct Row4;
10082 struct» Row5;
10083 «struct Row6;
10084 ˇ»
10085 struct Row9.1;
10086 struct Row9.2;
10087 struct Row9.3;
10088 struct Row8;
10089 struct Row9;
10090 struct Row10;"#},
10091 base_text,
10092 &mut cx,
10093 );
10094
10095 // When carets and selections intersect the addition hunks, those are reverted.
10096 // Adjacent carets got merged.
10097 assert_hunk_revert(
10098 indoc! {r#"struct Row;
10099 ˇ// something on the top
10100 struct Row1;
10101 struct Row2;
10102 struct Roˇw3.1;
10103 struct Row2.2;
10104 struct Row2.3;ˇ
10105
10106 struct Row4;
10107 struct ˇRow5.1;
10108 struct Row5.2;
10109 struct «Rowˇ»5.3;
10110 struct Row5;
10111 struct Row6;
10112 ˇ
10113 struct Row9.1;
10114 struct «Rowˇ»9.2;
10115 struct «ˇRow»9.3;
10116 struct Row8;
10117 struct Row9;
10118 «ˇ// something on bottom»
10119 struct Row10;"#},
10120 vec![
10121 DiffHunkStatus::Added,
10122 DiffHunkStatus::Added,
10123 DiffHunkStatus::Added,
10124 DiffHunkStatus::Added,
10125 DiffHunkStatus::Added,
10126 ],
10127 indoc! {r#"struct Row;
10128 ˇstruct Row1;
10129 struct Row2;
10130 ˇ
10131 struct Row4;
10132 ˇstruct Row5;
10133 struct Row6;
10134 ˇ
10135 ˇstruct Row8;
10136 struct Row9;
10137 ˇstruct Row10;"#},
10138 base_text,
10139 &mut cx,
10140 );
10141}
10142
10143#[gpui::test]
10144async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
10145 init_test(cx, |_| {});
10146 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10147 let base_text = indoc! {r#"struct Row;
10148struct Row1;
10149struct Row2;
10150
10151struct Row4;
10152struct Row5;
10153struct Row6;
10154
10155struct Row8;
10156struct Row9;
10157struct Row10;"#};
10158
10159 // Modification hunks behave the same as the addition ones.
10160 assert_hunk_revert(
10161 indoc! {r#"struct Row;
10162 struct Row1;
10163 struct Row33;
10164 ˇ
10165 struct Row4;
10166 struct Row5;
10167 struct Row6;
10168 ˇ
10169 struct Row99;
10170 struct Row9;
10171 struct Row10;"#},
10172 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10173 indoc! {r#"struct Row;
10174 struct Row1;
10175 struct Row33;
10176 ˇ
10177 struct Row4;
10178 struct Row5;
10179 struct Row6;
10180 ˇ
10181 struct Row99;
10182 struct Row9;
10183 struct Row10;"#},
10184 base_text,
10185 &mut cx,
10186 );
10187 assert_hunk_revert(
10188 indoc! {r#"struct Row;
10189 struct Row1;
10190 struct Row33;
10191 «ˇ
10192 struct Row4;
10193 struct» Row5;
10194 «struct Row6;
10195 ˇ»
10196 struct Row99;
10197 struct Row9;
10198 struct Row10;"#},
10199 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10200 indoc! {r#"struct Row;
10201 struct Row1;
10202 struct Row33;
10203 «ˇ
10204 struct Row4;
10205 struct» Row5;
10206 «struct Row6;
10207 ˇ»
10208 struct Row99;
10209 struct Row9;
10210 struct Row10;"#},
10211 base_text,
10212 &mut cx,
10213 );
10214
10215 assert_hunk_revert(
10216 indoc! {r#"ˇstruct Row1.1;
10217 struct Row1;
10218 «ˇstr»uct Row22;
10219
10220 struct ˇRow44;
10221 struct Row5;
10222 struct «Rˇ»ow66;ˇ
10223
10224 «struˇ»ct Row88;
10225 struct Row9;
10226 struct Row1011;ˇ"#},
10227 vec![
10228 DiffHunkStatus::Modified,
10229 DiffHunkStatus::Modified,
10230 DiffHunkStatus::Modified,
10231 DiffHunkStatus::Modified,
10232 DiffHunkStatus::Modified,
10233 DiffHunkStatus::Modified,
10234 ],
10235 indoc! {r#"struct Row;
10236 ˇstruct Row1;
10237 struct Row2;
10238 ˇ
10239 struct Row4;
10240 ˇstruct Row5;
10241 struct Row6;
10242 ˇ
10243 struct Row8;
10244 ˇstruct Row9;
10245 struct Row10;ˇ"#},
10246 base_text,
10247 &mut cx,
10248 );
10249}
10250
10251#[gpui::test]
10252async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
10253 init_test(cx, |_| {});
10254 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10255 let base_text = indoc! {r#"struct Row;
10256struct Row1;
10257struct Row2;
10258
10259struct Row4;
10260struct Row5;
10261struct Row6;
10262
10263struct Row8;
10264struct Row9;
10265struct Row10;"#};
10266
10267 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
10268 assert_hunk_revert(
10269 indoc! {r#"struct Row;
10270 struct Row2;
10271
10272 ˇstruct Row4;
10273 struct Row5;
10274 struct Row6;
10275 ˇ
10276 struct Row8;
10277 struct Row10;"#},
10278 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10279 indoc! {r#"struct Row;
10280 struct Row2;
10281
10282 ˇstruct Row4;
10283 struct Row5;
10284 struct Row6;
10285 ˇ
10286 struct Row8;
10287 struct Row10;"#},
10288 base_text,
10289 &mut cx,
10290 );
10291 assert_hunk_revert(
10292 indoc! {r#"struct Row;
10293 struct Row2;
10294
10295 «ˇstruct Row4;
10296 struct» Row5;
10297 «struct Row6;
10298 ˇ»
10299 struct Row8;
10300 struct Row10;"#},
10301 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10302 indoc! {r#"struct Row;
10303 struct Row2;
10304
10305 «ˇstruct Row4;
10306 struct» Row5;
10307 «struct Row6;
10308 ˇ»
10309 struct Row8;
10310 struct Row10;"#},
10311 base_text,
10312 &mut cx,
10313 );
10314
10315 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
10316 assert_hunk_revert(
10317 indoc! {r#"struct Row;
10318 ˇstruct Row2;
10319
10320 struct Row4;
10321 struct Row5;
10322 struct Row6;
10323
10324 struct Row8;ˇ
10325 struct Row10;"#},
10326 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10327 indoc! {r#"struct Row;
10328 struct Row1;
10329 ˇstruct Row2;
10330
10331 struct Row4;
10332 struct Row5;
10333 struct Row6;
10334
10335 struct Row8;ˇ
10336 struct Row9;
10337 struct Row10;"#},
10338 base_text,
10339 &mut cx,
10340 );
10341 assert_hunk_revert(
10342 indoc! {r#"struct Row;
10343 struct Row2«ˇ;
10344 struct Row4;
10345 struct» Row5;
10346 «struct Row6;
10347
10348 struct Row8;ˇ»
10349 struct Row10;"#},
10350 vec![
10351 DiffHunkStatus::Removed,
10352 DiffHunkStatus::Removed,
10353 DiffHunkStatus::Removed,
10354 ],
10355 indoc! {r#"struct Row;
10356 struct Row1;
10357 struct Row2«ˇ;
10358
10359 struct Row4;
10360 struct» Row5;
10361 «struct Row6;
10362
10363 struct Row8;ˇ»
10364 struct Row9;
10365 struct Row10;"#},
10366 base_text,
10367 &mut cx,
10368 );
10369}
10370
10371#[gpui::test]
10372async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
10373 init_test(cx, |_| {});
10374
10375 let cols = 4;
10376 let rows = 10;
10377 let sample_text_1 = sample_text(rows, cols, 'a');
10378 assert_eq!(
10379 sample_text_1,
10380 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10381 );
10382 let sample_text_2 = sample_text(rows, cols, 'l');
10383 assert_eq!(
10384 sample_text_2,
10385 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10386 );
10387 let sample_text_3 = sample_text(rows, cols, 'v');
10388 assert_eq!(
10389 sample_text_3,
10390 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10391 );
10392
10393 fn diff_every_buffer_row(
10394 buffer: &Model<Buffer>,
10395 sample_text: String,
10396 cols: usize,
10397 cx: &mut gpui::TestAppContext,
10398 ) {
10399 // revert first character in each row, creating one large diff hunk per buffer
10400 let is_first_char = |offset: usize| offset % cols == 0;
10401 buffer.update(cx, |buffer, cx| {
10402 buffer.set_text(
10403 sample_text
10404 .chars()
10405 .enumerate()
10406 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
10407 .collect::<String>(),
10408 cx,
10409 );
10410 buffer.set_diff_base(Some(sample_text), cx);
10411 });
10412 cx.executor().run_until_parked();
10413 }
10414
10415 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10416 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10417
10418 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10419 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10420
10421 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10422 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10423
10424 let multibuffer = cx.new_model(|cx| {
10425 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10426 multibuffer.push_excerpts(
10427 buffer_1.clone(),
10428 [
10429 ExcerptRange {
10430 context: Point::new(0, 0)..Point::new(3, 0),
10431 primary: None,
10432 },
10433 ExcerptRange {
10434 context: Point::new(5, 0)..Point::new(7, 0),
10435 primary: None,
10436 },
10437 ExcerptRange {
10438 context: Point::new(9, 0)..Point::new(10, 4),
10439 primary: None,
10440 },
10441 ],
10442 cx,
10443 );
10444 multibuffer.push_excerpts(
10445 buffer_2.clone(),
10446 [
10447 ExcerptRange {
10448 context: Point::new(0, 0)..Point::new(3, 0),
10449 primary: None,
10450 },
10451 ExcerptRange {
10452 context: Point::new(5, 0)..Point::new(7, 0),
10453 primary: None,
10454 },
10455 ExcerptRange {
10456 context: Point::new(9, 0)..Point::new(10, 4),
10457 primary: None,
10458 },
10459 ],
10460 cx,
10461 );
10462 multibuffer.push_excerpts(
10463 buffer_3.clone(),
10464 [
10465 ExcerptRange {
10466 context: Point::new(0, 0)..Point::new(3, 0),
10467 primary: None,
10468 },
10469 ExcerptRange {
10470 context: Point::new(5, 0)..Point::new(7, 0),
10471 primary: None,
10472 },
10473 ExcerptRange {
10474 context: Point::new(9, 0)..Point::new(10, 4),
10475 primary: None,
10476 },
10477 ],
10478 cx,
10479 );
10480 multibuffer
10481 });
10482
10483 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
10484 editor.update(cx, |editor, cx| {
10485 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");
10486 editor.select_all(&SelectAll, cx);
10487 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10488 });
10489 cx.executor().run_until_parked();
10490 // When all ranges are selected, all buffer hunks are reverted.
10491 editor.update(cx, |editor, cx| {
10492 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");
10493 });
10494 buffer_1.update(cx, |buffer, _| {
10495 assert_eq!(buffer.text(), sample_text_1);
10496 });
10497 buffer_2.update(cx, |buffer, _| {
10498 assert_eq!(buffer.text(), sample_text_2);
10499 });
10500 buffer_3.update(cx, |buffer, _| {
10501 assert_eq!(buffer.text(), sample_text_3);
10502 });
10503
10504 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10505 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10506 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10507 editor.update(cx, |editor, cx| {
10508 editor.change_selections(None, cx, |s| {
10509 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
10510 });
10511 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10512 });
10513 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
10514 // but not affect buffer_2 and its related excerpts.
10515 editor.update(cx, |editor, cx| {
10516 assert_eq!(
10517 editor.text(cx),
10518 "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"
10519 );
10520 });
10521 buffer_1.update(cx, |buffer, _| {
10522 assert_eq!(buffer.text(), sample_text_1);
10523 });
10524 buffer_2.update(cx, |buffer, _| {
10525 assert_eq!(
10526 buffer.text(),
10527 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
10528 );
10529 });
10530 buffer_3.update(cx, |buffer, _| {
10531 assert_eq!(
10532 buffer.text(),
10533 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
10534 );
10535 });
10536}
10537
10538#[gpui::test]
10539async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
10540 init_test(cx, |_| {});
10541
10542 let cols = 4;
10543 let rows = 10;
10544 let sample_text_1 = sample_text(rows, cols, 'a');
10545 assert_eq!(
10546 sample_text_1,
10547 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10548 );
10549 let sample_text_2 = sample_text(rows, cols, 'l');
10550 assert_eq!(
10551 sample_text_2,
10552 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10553 );
10554 let sample_text_3 = sample_text(rows, cols, 'v');
10555 assert_eq!(
10556 sample_text_3,
10557 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10558 );
10559
10560 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10561 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10562 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10563
10564 let multi_buffer = cx.new_model(|cx| {
10565 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10566 multibuffer.push_excerpts(
10567 buffer_1.clone(),
10568 [
10569 ExcerptRange {
10570 context: Point::new(0, 0)..Point::new(3, 0),
10571 primary: None,
10572 },
10573 ExcerptRange {
10574 context: Point::new(5, 0)..Point::new(7, 0),
10575 primary: None,
10576 },
10577 ExcerptRange {
10578 context: Point::new(9, 0)..Point::new(10, 4),
10579 primary: None,
10580 },
10581 ],
10582 cx,
10583 );
10584 multibuffer.push_excerpts(
10585 buffer_2.clone(),
10586 [
10587 ExcerptRange {
10588 context: Point::new(0, 0)..Point::new(3, 0),
10589 primary: None,
10590 },
10591 ExcerptRange {
10592 context: Point::new(5, 0)..Point::new(7, 0),
10593 primary: None,
10594 },
10595 ExcerptRange {
10596 context: Point::new(9, 0)..Point::new(10, 4),
10597 primary: None,
10598 },
10599 ],
10600 cx,
10601 );
10602 multibuffer.push_excerpts(
10603 buffer_3.clone(),
10604 [
10605 ExcerptRange {
10606 context: Point::new(0, 0)..Point::new(3, 0),
10607 primary: None,
10608 },
10609 ExcerptRange {
10610 context: Point::new(5, 0)..Point::new(7, 0),
10611 primary: None,
10612 },
10613 ExcerptRange {
10614 context: Point::new(9, 0)..Point::new(10, 4),
10615 primary: None,
10616 },
10617 ],
10618 cx,
10619 );
10620 multibuffer
10621 });
10622
10623 let fs = FakeFs::new(cx.executor());
10624 fs.insert_tree(
10625 "/a",
10626 json!({
10627 "main.rs": sample_text_1,
10628 "other.rs": sample_text_2,
10629 "lib.rs": sample_text_3,
10630 }),
10631 )
10632 .await;
10633 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10634 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10635 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10636 let multi_buffer_editor = cx.new_view(|cx| {
10637 Editor::new(
10638 EditorMode::Full,
10639 multi_buffer,
10640 Some(project.clone()),
10641 true,
10642 cx,
10643 )
10644 });
10645 let multibuffer_item_id = workspace
10646 .update(cx, |workspace, cx| {
10647 assert!(
10648 workspace.active_item(cx).is_none(),
10649 "active item should be None before the first item is added"
10650 );
10651 workspace.add_item_to_active_pane(
10652 Box::new(multi_buffer_editor.clone()),
10653 None,
10654 true,
10655 cx,
10656 );
10657 let active_item = workspace
10658 .active_item(cx)
10659 .expect("should have an active item after adding the multi buffer");
10660 assert!(
10661 !active_item.is_singleton(cx),
10662 "A multi buffer was expected to active after adding"
10663 );
10664 active_item.item_id()
10665 })
10666 .unwrap();
10667 cx.executor().run_until_parked();
10668
10669 multi_buffer_editor.update(cx, |editor, cx| {
10670 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
10671 editor.open_excerpts(&OpenExcerpts, cx);
10672 });
10673 cx.executor().run_until_parked();
10674 let first_item_id = workspace
10675 .update(cx, |workspace, cx| {
10676 let active_item = workspace
10677 .active_item(cx)
10678 .expect("should have an active item after navigating into the 1st buffer");
10679 let first_item_id = active_item.item_id();
10680 assert_ne!(
10681 first_item_id, multibuffer_item_id,
10682 "Should navigate into the 1st buffer and activate it"
10683 );
10684 assert!(
10685 active_item.is_singleton(cx),
10686 "New active item should be a singleton buffer"
10687 );
10688 assert_eq!(
10689 active_item
10690 .act_as::<Editor>(cx)
10691 .expect("should have navigated into an editor for the 1st buffer")
10692 .read(cx)
10693 .text(cx),
10694 sample_text_1
10695 );
10696
10697 workspace
10698 .go_back(workspace.active_pane().downgrade(), cx)
10699 .detach_and_log_err(cx);
10700
10701 first_item_id
10702 })
10703 .unwrap();
10704 cx.executor().run_until_parked();
10705 workspace
10706 .update(cx, |workspace, cx| {
10707 let active_item = workspace
10708 .active_item(cx)
10709 .expect("should have an active item after navigating back");
10710 assert_eq!(
10711 active_item.item_id(),
10712 multibuffer_item_id,
10713 "Should navigate back to the multi buffer"
10714 );
10715 assert!(!active_item.is_singleton(cx));
10716 })
10717 .unwrap();
10718
10719 multi_buffer_editor.update(cx, |editor, cx| {
10720 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
10721 s.select_ranges(Some(39..40))
10722 });
10723 editor.open_excerpts(&OpenExcerpts, cx);
10724 });
10725 cx.executor().run_until_parked();
10726 let second_item_id = workspace
10727 .update(cx, |workspace, cx| {
10728 let active_item = workspace
10729 .active_item(cx)
10730 .expect("should have an active item after navigating into the 2nd buffer");
10731 let second_item_id = active_item.item_id();
10732 assert_ne!(
10733 second_item_id, multibuffer_item_id,
10734 "Should navigate away from the multibuffer"
10735 );
10736 assert_ne!(
10737 second_item_id, first_item_id,
10738 "Should navigate into the 2nd buffer and activate it"
10739 );
10740 assert!(
10741 active_item.is_singleton(cx),
10742 "New active item should be a singleton buffer"
10743 );
10744 assert_eq!(
10745 active_item
10746 .act_as::<Editor>(cx)
10747 .expect("should have navigated into an editor")
10748 .read(cx)
10749 .text(cx),
10750 sample_text_2
10751 );
10752
10753 workspace
10754 .go_back(workspace.active_pane().downgrade(), cx)
10755 .detach_and_log_err(cx);
10756
10757 second_item_id
10758 })
10759 .unwrap();
10760 cx.executor().run_until_parked();
10761 workspace
10762 .update(cx, |workspace, cx| {
10763 let active_item = workspace
10764 .active_item(cx)
10765 .expect("should have an active item after navigating back from the 2nd buffer");
10766 assert_eq!(
10767 active_item.item_id(),
10768 multibuffer_item_id,
10769 "Should navigate back from the 2nd buffer to the multi buffer"
10770 );
10771 assert!(!active_item.is_singleton(cx));
10772 })
10773 .unwrap();
10774
10775 multi_buffer_editor.update(cx, |editor, cx| {
10776 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
10777 s.select_ranges(Some(60..70))
10778 });
10779 editor.open_excerpts(&OpenExcerpts, cx);
10780 });
10781 cx.executor().run_until_parked();
10782 workspace
10783 .update(cx, |workspace, cx| {
10784 let active_item = workspace
10785 .active_item(cx)
10786 .expect("should have an active item after navigating into the 3rd buffer");
10787 let third_item_id = active_item.item_id();
10788 assert_ne!(
10789 third_item_id, multibuffer_item_id,
10790 "Should navigate into the 3rd buffer and activate it"
10791 );
10792 assert_ne!(third_item_id, first_item_id);
10793 assert_ne!(third_item_id, second_item_id);
10794 assert!(
10795 active_item.is_singleton(cx),
10796 "New active item should be a singleton buffer"
10797 );
10798 assert_eq!(
10799 active_item
10800 .act_as::<Editor>(cx)
10801 .expect("should have navigated into an editor")
10802 .read(cx)
10803 .text(cx),
10804 sample_text_3
10805 );
10806
10807 workspace
10808 .go_back(workspace.active_pane().downgrade(), cx)
10809 .detach_and_log_err(cx);
10810 })
10811 .unwrap();
10812 cx.executor().run_until_parked();
10813 workspace
10814 .update(cx, |workspace, cx| {
10815 let active_item = workspace
10816 .active_item(cx)
10817 .expect("should have an active item after navigating back from the 3rd buffer");
10818 assert_eq!(
10819 active_item.item_id(),
10820 multibuffer_item_id,
10821 "Should navigate back from the 3rd buffer to the multi buffer"
10822 );
10823 assert!(!active_item.is_singleton(cx));
10824 })
10825 .unwrap();
10826}
10827
10828#[gpui::test]
10829async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10830 init_test(cx, |_| {});
10831
10832 let mut cx = EditorTestContext::new(cx).await;
10833
10834 let diff_base = r#"
10835 use some::mod;
10836
10837 const A: u32 = 42;
10838
10839 fn main() {
10840 println!("hello");
10841
10842 println!("world");
10843 }
10844 "#
10845 .unindent();
10846
10847 cx.set_state(
10848 &r#"
10849 use some::modified;
10850
10851 ˇ
10852 fn main() {
10853 println!("hello there");
10854
10855 println!("around the");
10856 println!("world");
10857 }
10858 "#
10859 .unindent(),
10860 );
10861
10862 cx.set_diff_base(Some(&diff_base));
10863 executor.run_until_parked();
10864 let unexpanded_hunks = vec![
10865 (
10866 "use some::mod;\n".to_string(),
10867 DiffHunkStatus::Modified,
10868 DisplayRow(0)..DisplayRow(1),
10869 ),
10870 (
10871 "const A: u32 = 42;\n".to_string(),
10872 DiffHunkStatus::Removed,
10873 DisplayRow(2)..DisplayRow(2),
10874 ),
10875 (
10876 " println!(\"hello\");\n".to_string(),
10877 DiffHunkStatus::Modified,
10878 DisplayRow(4)..DisplayRow(5),
10879 ),
10880 (
10881 "".to_string(),
10882 DiffHunkStatus::Added,
10883 DisplayRow(6)..DisplayRow(7),
10884 ),
10885 ];
10886 cx.update_editor(|editor, cx| {
10887 let snapshot = editor.snapshot(cx);
10888 let all_hunks = editor_hunks(editor, &snapshot, cx);
10889 assert_eq!(all_hunks, unexpanded_hunks);
10890 });
10891
10892 cx.update_editor(|editor, cx| {
10893 for _ in 0..4 {
10894 editor.go_to_hunk(&GoToHunk, cx);
10895 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10896 }
10897 });
10898 executor.run_until_parked();
10899 cx.assert_editor_state(
10900 &r#"
10901 use some::modified;
10902
10903 ˇ
10904 fn main() {
10905 println!("hello there");
10906
10907 println!("around the");
10908 println!("world");
10909 }
10910 "#
10911 .unindent(),
10912 );
10913 cx.update_editor(|editor, cx| {
10914 let snapshot = editor.snapshot(cx);
10915 let all_hunks = editor_hunks(editor, &snapshot, cx);
10916 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10917 assert_eq!(
10918 expanded_hunks_background_highlights(editor, cx),
10919 vec![DisplayRow(1)..=DisplayRow(1), DisplayRow(7)..=DisplayRow(7), DisplayRow(9)..=DisplayRow(9)],
10920 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
10921 );
10922 assert_eq!(
10923 all_hunks,
10924 vec![
10925 ("use some::mod;\n".to_string(), DiffHunkStatus::Modified, DisplayRow(1)..DisplayRow(2)),
10926 ("const A: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(4)..DisplayRow(4)),
10927 (" println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(7)..DisplayRow(8)),
10928 ("".to_string(), DiffHunkStatus::Added, DisplayRow(9)..DisplayRow(10)),
10929 ],
10930 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
10931 (from modified and removed hunks)"
10932 );
10933 assert_eq!(
10934 all_hunks, all_expanded_hunks,
10935 "Editor hunks should not change and all be expanded"
10936 );
10937 });
10938
10939 cx.update_editor(|editor, cx| {
10940 editor.cancel(&Cancel, cx);
10941
10942 let snapshot = editor.snapshot(cx);
10943 let all_hunks = editor_hunks(editor, &snapshot, cx);
10944 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
10945 assert_eq!(
10946 expanded_hunks_background_highlights(editor, cx),
10947 Vec::new(),
10948 "After cancelling in editor, no git highlights should be left"
10949 );
10950 assert_eq!(
10951 all_expanded_hunks,
10952 Vec::new(),
10953 "After cancelling in editor, no hunks should be expanded"
10954 );
10955 assert_eq!(
10956 all_hunks, unexpanded_hunks,
10957 "After cancelling in editor, regular hunks' coordinates should get back to normal"
10958 );
10959 });
10960}
10961
10962#[gpui::test]
10963async fn test_toggled_diff_base_change(
10964 executor: BackgroundExecutor,
10965 cx: &mut gpui::TestAppContext,
10966) {
10967 init_test(cx, |_| {});
10968
10969 let mut cx = EditorTestContext::new(cx).await;
10970
10971 let diff_base = r#"
10972 use some::mod1;
10973 use some::mod2;
10974
10975 const A: u32 = 42;
10976 const B: u32 = 42;
10977 const C: u32 = 42;
10978
10979 fn main(ˇ) {
10980 println!("hello");
10981
10982 println!("world");
10983 }
10984 "#
10985 .unindent();
10986
10987 cx.set_state(
10988 &r#"
10989 use some::mod2;
10990
10991 const A: u32 = 42;
10992 const C: u32 = 42;
10993
10994 fn main(ˇ) {
10995 //println!("hello");
10996
10997 println!("world");
10998 //
10999 //
11000 }
11001 "#
11002 .unindent(),
11003 );
11004
11005 cx.set_diff_base(Some(&diff_base));
11006 executor.run_until_parked();
11007 cx.update_editor(|editor, cx| {
11008 let snapshot = editor.snapshot(cx);
11009 let all_hunks = editor_hunks(editor, &snapshot, cx);
11010 assert_eq!(
11011 all_hunks,
11012 vec![
11013 (
11014 "use some::mod1;\n".to_string(),
11015 DiffHunkStatus::Removed,
11016 DisplayRow(0)..DisplayRow(0)
11017 ),
11018 (
11019 "const B: u32 = 42;\n".to_string(),
11020 DiffHunkStatus::Removed,
11021 DisplayRow(3)..DisplayRow(3)
11022 ),
11023 (
11024 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11025 DiffHunkStatus::Modified,
11026 DisplayRow(5)..DisplayRow(7)
11027 ),
11028 (
11029 "".to_string(),
11030 DiffHunkStatus::Added,
11031 DisplayRow(9)..DisplayRow(11)
11032 ),
11033 ]
11034 );
11035 });
11036
11037 cx.update_editor(|editor, cx| {
11038 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11039 });
11040 executor.run_until_parked();
11041 cx.assert_editor_state(
11042 &r#"
11043 use some::mod2;
11044
11045 const A: u32 = 42;
11046 const C: u32 = 42;
11047
11048 fn main(ˇ) {
11049 //println!("hello");
11050
11051 println!("world");
11052 //
11053 //
11054 }
11055 "#
11056 .unindent(),
11057 );
11058 cx.update_editor(|editor, cx| {
11059 let snapshot = editor.snapshot(cx);
11060 let all_hunks = editor_hunks(editor, &snapshot, cx);
11061 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11062 assert_eq!(
11063 expanded_hunks_background_highlights(editor, cx),
11064 vec![DisplayRow(9)..=DisplayRow(10), DisplayRow(13)..=DisplayRow(14)],
11065 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
11066 );
11067 assert_eq!(
11068 all_hunks,
11069 vec![
11070 ("use some::mod1;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(1)..DisplayRow(1)),
11071 ("const B: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(5)..DisplayRow(5)),
11072 ("fn main(ˇ) {\n println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(9)..DisplayRow(11)),
11073 ("".to_string(), DiffHunkStatus::Added, DisplayRow(13)..DisplayRow(15)),
11074 ],
11075 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
11076 (from modified and removed hunks)"
11077 );
11078 assert_eq!(
11079 all_hunks, all_expanded_hunks,
11080 "Editor hunks should not change and all be expanded"
11081 );
11082 });
11083
11084 cx.set_diff_base(Some("new diff base!"));
11085 executor.run_until_parked();
11086
11087 cx.update_editor(|editor, cx| {
11088 let snapshot = editor.snapshot(cx);
11089 let all_hunks = editor_hunks(editor, &snapshot, cx);
11090 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11091 assert_eq!(
11092 expanded_hunks_background_highlights(editor, cx),
11093 Vec::new(),
11094 "After diff base is changed, old git highlights should be removed"
11095 );
11096 assert_eq!(
11097 all_expanded_hunks,
11098 Vec::new(),
11099 "After diff base is changed, old git hunk expansions should be removed"
11100 );
11101 assert_eq!(
11102 all_hunks,
11103 vec![(
11104 "new diff base!".to_string(),
11105 DiffHunkStatus::Modified,
11106 DisplayRow(0)..snapshot.display_snapshot.max_point().row()
11107 )],
11108 "After diff base is changed, hunks should update"
11109 );
11110 });
11111}
11112
11113#[gpui::test]
11114async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11115 init_test(cx, |_| {});
11116
11117 let mut cx = EditorTestContext::new(cx).await;
11118
11119 let diff_base = r#"
11120 use some::mod1;
11121 use some::mod2;
11122
11123 const A: u32 = 42;
11124 const B: u32 = 42;
11125 const C: u32 = 42;
11126
11127 fn main(ˇ) {
11128 println!("hello");
11129
11130 println!("world");
11131 }
11132
11133 fn another() {
11134 println!("another");
11135 }
11136
11137 fn another2() {
11138 println!("another2");
11139 }
11140 "#
11141 .unindent();
11142
11143 cx.set_state(
11144 &r#"
11145 «use some::mod2;
11146
11147 const A: u32 = 42;
11148 const C: u32 = 42;
11149
11150 fn main() {
11151 //println!("hello");
11152
11153 println!("world");
11154 //
11155 //ˇ»
11156 }
11157
11158 fn another() {
11159 println!("another");
11160 println!("another");
11161 }
11162
11163 println!("another2");
11164 }
11165 "#
11166 .unindent(),
11167 );
11168
11169 cx.set_diff_base(Some(&diff_base));
11170 executor.run_until_parked();
11171 cx.update_editor(|editor, cx| {
11172 let snapshot = editor.snapshot(cx);
11173 let all_hunks = editor_hunks(editor, &snapshot, cx);
11174 assert_eq!(
11175 all_hunks,
11176 vec![
11177 (
11178 "use some::mod1;\n".to_string(),
11179 DiffHunkStatus::Removed,
11180 DisplayRow(0)..DisplayRow(0)
11181 ),
11182 (
11183 "const B: u32 = 42;\n".to_string(),
11184 DiffHunkStatus::Removed,
11185 DisplayRow(3)..DisplayRow(3)
11186 ),
11187 (
11188 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11189 DiffHunkStatus::Modified,
11190 DisplayRow(5)..DisplayRow(7)
11191 ),
11192 (
11193 "".to_string(),
11194 DiffHunkStatus::Added,
11195 DisplayRow(9)..DisplayRow(11)
11196 ),
11197 (
11198 "".to_string(),
11199 DiffHunkStatus::Added,
11200 DisplayRow(15)..DisplayRow(16)
11201 ),
11202 (
11203 "fn another2() {\n".to_string(),
11204 DiffHunkStatus::Removed,
11205 DisplayRow(18)..DisplayRow(18)
11206 ),
11207 ]
11208 );
11209 });
11210
11211 cx.update_editor(|editor, cx| {
11212 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11213 });
11214 executor.run_until_parked();
11215 cx.assert_editor_state(
11216 &r#"
11217 «use some::mod2;
11218
11219 const A: u32 = 42;
11220 const C: u32 = 42;
11221
11222 fn main() {
11223 //println!("hello");
11224
11225 println!("world");
11226 //
11227 //ˇ»
11228 }
11229
11230 fn another() {
11231 println!("another");
11232 println!("another");
11233 }
11234
11235 println!("another2");
11236 }
11237 "#
11238 .unindent(),
11239 );
11240 cx.update_editor(|editor, cx| {
11241 let snapshot = editor.snapshot(cx);
11242 let all_hunks = editor_hunks(editor, &snapshot, cx);
11243 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11244 assert_eq!(
11245 expanded_hunks_background_highlights(editor, cx),
11246 vec![
11247 DisplayRow(9)..=DisplayRow(10),
11248 DisplayRow(13)..=DisplayRow(14),
11249 DisplayRow(19)..=DisplayRow(19)
11250 ]
11251 );
11252 assert_eq!(
11253 all_hunks,
11254 vec![
11255 (
11256 "use some::mod1;\n".to_string(),
11257 DiffHunkStatus::Removed,
11258 DisplayRow(1)..DisplayRow(1)
11259 ),
11260 (
11261 "const B: u32 = 42;\n".to_string(),
11262 DiffHunkStatus::Removed,
11263 DisplayRow(5)..DisplayRow(5)
11264 ),
11265 (
11266 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11267 DiffHunkStatus::Modified,
11268 DisplayRow(9)..DisplayRow(11)
11269 ),
11270 (
11271 "".to_string(),
11272 DiffHunkStatus::Added,
11273 DisplayRow(13)..DisplayRow(15)
11274 ),
11275 (
11276 "".to_string(),
11277 DiffHunkStatus::Added,
11278 DisplayRow(19)..DisplayRow(20)
11279 ),
11280 (
11281 "fn another2() {\n".to_string(),
11282 DiffHunkStatus::Removed,
11283 DisplayRow(23)..DisplayRow(23)
11284 ),
11285 ],
11286 );
11287 assert_eq!(all_hunks, all_expanded_hunks);
11288 });
11289
11290 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
11291 cx.executor().run_until_parked();
11292 cx.assert_editor_state(
11293 &r#"
11294 «use some::mod2;
11295
11296 const A: u32 = 42;
11297 const C: u32 = 42;
11298
11299 fn main() {
11300 //println!("hello");
11301
11302 println!("world");
11303 //
11304 //ˇ»
11305 }
11306
11307 fn another() {
11308 println!("another");
11309 println!("another");
11310 }
11311
11312 println!("another2");
11313 }
11314 "#
11315 .unindent(),
11316 );
11317 cx.update_editor(|editor, cx| {
11318 let snapshot = editor.snapshot(cx);
11319 let all_hunks = editor_hunks(editor, &snapshot, cx);
11320 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11321 assert_eq!(
11322 expanded_hunks_background_highlights(editor, cx),
11323 vec![DisplayRow(0)..=DisplayRow(0), DisplayRow(5)..=DisplayRow(5)],
11324 "Only one hunk is left not folded, its highlight should be visible"
11325 );
11326 assert_eq!(
11327 all_hunks,
11328 vec![
11329 (
11330 "use some::mod1;\n".to_string(),
11331 DiffHunkStatus::Removed,
11332 DisplayRow(0)..DisplayRow(0)
11333 ),
11334 (
11335 "const B: u32 = 42;\n".to_string(),
11336 DiffHunkStatus::Removed,
11337 DisplayRow(0)..DisplayRow(0)
11338 ),
11339 (
11340 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11341 DiffHunkStatus::Modified,
11342 DisplayRow(0)..DisplayRow(0)
11343 ),
11344 (
11345 "".to_string(),
11346 DiffHunkStatus::Added,
11347 DisplayRow(0)..DisplayRow(1)
11348 ),
11349 (
11350 "".to_string(),
11351 DiffHunkStatus::Added,
11352 DisplayRow(5)..DisplayRow(6)
11353 ),
11354 (
11355 "fn another2() {\n".to_string(),
11356 DiffHunkStatus::Removed,
11357 DisplayRow(9)..DisplayRow(9)
11358 ),
11359 ],
11360 "Hunk list should still return shifted folded hunks"
11361 );
11362 assert_eq!(
11363 all_expanded_hunks,
11364 vec![
11365 (
11366 "".to_string(),
11367 DiffHunkStatus::Added,
11368 DisplayRow(5)..DisplayRow(6)
11369 ),
11370 (
11371 "fn another2() {\n".to_string(),
11372 DiffHunkStatus::Removed,
11373 DisplayRow(9)..DisplayRow(9)
11374 ),
11375 ],
11376 "Only non-folded hunks should be left expanded"
11377 );
11378 });
11379
11380 cx.update_editor(|editor, cx| {
11381 editor.select_all(&SelectAll, cx);
11382 editor.unfold_lines(&UnfoldLines, cx);
11383 });
11384 cx.executor().run_until_parked();
11385 cx.assert_editor_state(
11386 &r#"
11387 «use some::mod2;
11388
11389 const A: u32 = 42;
11390 const C: u32 = 42;
11391
11392 fn main() {
11393 //println!("hello");
11394
11395 println!("world");
11396 //
11397 //
11398 }
11399
11400 fn another() {
11401 println!("another");
11402 println!("another");
11403 }
11404
11405 println!("another2");
11406 }
11407 ˇ»"#
11408 .unindent(),
11409 );
11410 cx.update_editor(|editor, cx| {
11411 let snapshot = editor.snapshot(cx);
11412 let all_hunks = editor_hunks(editor, &snapshot, cx);
11413 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11414 assert_eq!(
11415 expanded_hunks_background_highlights(editor, cx),
11416 vec![
11417 DisplayRow(9)..=DisplayRow(10),
11418 DisplayRow(13)..=DisplayRow(14),
11419 DisplayRow(19)..=DisplayRow(19)
11420 ],
11421 "After unfolding, all hunk diffs should be visible again"
11422 );
11423 assert_eq!(
11424 all_hunks,
11425 vec![
11426 (
11427 "use some::mod1;\n".to_string(),
11428 DiffHunkStatus::Removed,
11429 DisplayRow(1)..DisplayRow(1)
11430 ),
11431 (
11432 "const B: u32 = 42;\n".to_string(),
11433 DiffHunkStatus::Removed,
11434 DisplayRow(5)..DisplayRow(5)
11435 ),
11436 (
11437 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11438 DiffHunkStatus::Modified,
11439 DisplayRow(9)..DisplayRow(11)
11440 ),
11441 (
11442 "".to_string(),
11443 DiffHunkStatus::Added,
11444 DisplayRow(13)..DisplayRow(15)
11445 ),
11446 (
11447 "".to_string(),
11448 DiffHunkStatus::Added,
11449 DisplayRow(19)..DisplayRow(20)
11450 ),
11451 (
11452 "fn another2() {\n".to_string(),
11453 DiffHunkStatus::Removed,
11454 DisplayRow(23)..DisplayRow(23)
11455 ),
11456 ],
11457 );
11458 assert_eq!(all_hunks, all_expanded_hunks);
11459 });
11460}
11461
11462#[gpui::test]
11463async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
11464 init_test(cx, |_| {});
11465
11466 let cols = 4;
11467 let rows = 10;
11468 let sample_text_1 = sample_text(rows, cols, 'a');
11469 assert_eq!(
11470 sample_text_1,
11471 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11472 );
11473 let modified_sample_text_1 = "aaaa\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
11474 let sample_text_2 = sample_text(rows, cols, 'l');
11475 assert_eq!(
11476 sample_text_2,
11477 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11478 );
11479 let modified_sample_text_2 = "llll\nmmmm\n1n1n1n1n1\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
11480 let sample_text_3 = sample_text(rows, cols, 'v');
11481 assert_eq!(
11482 sample_text_3,
11483 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11484 );
11485 let modified_sample_text_3 =
11486 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n@@@@\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
11487 let buffer_1 = cx.new_model(|cx| {
11488 let mut buffer = Buffer::local(modified_sample_text_1.to_string(), cx);
11489 buffer.set_diff_base(Some(sample_text_1.clone()), cx);
11490 buffer
11491 });
11492 let buffer_2 = cx.new_model(|cx| {
11493 let mut buffer = Buffer::local(modified_sample_text_2.to_string(), cx);
11494 buffer.set_diff_base(Some(sample_text_2.clone()), cx);
11495 buffer
11496 });
11497 let buffer_3 = cx.new_model(|cx| {
11498 let mut buffer = Buffer::local(modified_sample_text_3.to_string(), cx);
11499 buffer.set_diff_base(Some(sample_text_3.clone()), cx);
11500 buffer
11501 });
11502
11503 let multi_buffer = cx.new_model(|cx| {
11504 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
11505 multibuffer.push_excerpts(
11506 buffer_1.clone(),
11507 [
11508 ExcerptRange {
11509 context: Point::new(0, 0)..Point::new(3, 0),
11510 primary: None,
11511 },
11512 ExcerptRange {
11513 context: Point::new(5, 0)..Point::new(7, 0),
11514 primary: None,
11515 },
11516 ExcerptRange {
11517 context: Point::new(9, 0)..Point::new(10, 4),
11518 primary: None,
11519 },
11520 ],
11521 cx,
11522 );
11523 multibuffer.push_excerpts(
11524 buffer_2.clone(),
11525 [
11526 ExcerptRange {
11527 context: Point::new(0, 0)..Point::new(3, 0),
11528 primary: None,
11529 },
11530 ExcerptRange {
11531 context: Point::new(5, 0)..Point::new(7, 0),
11532 primary: None,
11533 },
11534 ExcerptRange {
11535 context: Point::new(9, 0)..Point::new(10, 4),
11536 primary: None,
11537 },
11538 ],
11539 cx,
11540 );
11541 multibuffer.push_excerpts(
11542 buffer_3.clone(),
11543 [
11544 ExcerptRange {
11545 context: Point::new(0, 0)..Point::new(3, 0),
11546 primary: None,
11547 },
11548 ExcerptRange {
11549 context: Point::new(5, 0)..Point::new(7, 0),
11550 primary: None,
11551 },
11552 ExcerptRange {
11553 context: Point::new(9, 0)..Point::new(10, 4),
11554 primary: None,
11555 },
11556 ],
11557 cx,
11558 );
11559 multibuffer
11560 });
11561
11562 let fs = FakeFs::new(cx.executor());
11563 fs.insert_tree(
11564 "/a",
11565 json!({
11566 "main.rs": modified_sample_text_1,
11567 "other.rs": modified_sample_text_2,
11568 "lib.rs": modified_sample_text_3,
11569 }),
11570 )
11571 .await;
11572
11573 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11574 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11575 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11576 let multi_buffer_editor = cx.new_view(|cx| {
11577 Editor::new(
11578 EditorMode::Full,
11579 multi_buffer,
11580 Some(project.clone()),
11581 true,
11582 cx,
11583 )
11584 });
11585 cx.executor().run_until_parked();
11586
11587 let expected_all_hunks = vec![
11588 (
11589 "bbbb\n".to_string(),
11590 DiffHunkStatus::Removed,
11591 DisplayRow(4)..DisplayRow(4),
11592 ),
11593 (
11594 "nnnn\n".to_string(),
11595 DiffHunkStatus::Modified,
11596 DisplayRow(21)..DisplayRow(22),
11597 ),
11598 (
11599 "".to_string(),
11600 DiffHunkStatus::Added,
11601 DisplayRow(41)..DisplayRow(42),
11602 ),
11603 ];
11604 let expected_all_hunks_shifted = vec![
11605 (
11606 "bbbb\n".to_string(),
11607 DiffHunkStatus::Removed,
11608 DisplayRow(5)..DisplayRow(5),
11609 ),
11610 (
11611 "nnnn\n".to_string(),
11612 DiffHunkStatus::Modified,
11613 DisplayRow(23)..DisplayRow(24),
11614 ),
11615 (
11616 "".to_string(),
11617 DiffHunkStatus::Added,
11618 DisplayRow(43)..DisplayRow(44),
11619 ),
11620 ];
11621
11622 multi_buffer_editor.update(cx, |editor, cx| {
11623 let snapshot = editor.snapshot(cx);
11624 let all_hunks = editor_hunks(editor, &snapshot, cx);
11625 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11626 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11627 assert_eq!(all_hunks, expected_all_hunks);
11628 assert_eq!(all_expanded_hunks, Vec::new());
11629 });
11630
11631 multi_buffer_editor.update(cx, |editor, cx| {
11632 editor.select_all(&SelectAll, cx);
11633 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11634 });
11635 cx.executor().run_until_parked();
11636 multi_buffer_editor.update(cx, |editor, cx| {
11637 let snapshot = editor.snapshot(cx);
11638 let all_hunks = editor_hunks(editor, &snapshot, cx);
11639 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11640 assert_eq!(
11641 expanded_hunks_background_highlights(editor, cx),
11642 vec![
11643 DisplayRow(23)..=DisplayRow(23),
11644 DisplayRow(43)..=DisplayRow(43)
11645 ],
11646 );
11647 assert_eq!(all_hunks, expected_all_hunks_shifted);
11648 assert_eq!(all_hunks, all_expanded_hunks);
11649 });
11650
11651 multi_buffer_editor.update(cx, |editor, cx| {
11652 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11653 });
11654 cx.executor().run_until_parked();
11655 multi_buffer_editor.update(cx, |editor, cx| {
11656 let snapshot = editor.snapshot(cx);
11657 let all_hunks = editor_hunks(editor, &snapshot, cx);
11658 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11659 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11660 assert_eq!(all_hunks, expected_all_hunks);
11661 assert_eq!(all_expanded_hunks, Vec::new());
11662 });
11663
11664 multi_buffer_editor.update(cx, |editor, cx| {
11665 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11666 });
11667 cx.executor().run_until_parked();
11668 multi_buffer_editor.update(cx, |editor, cx| {
11669 let snapshot = editor.snapshot(cx);
11670 let all_hunks = editor_hunks(editor, &snapshot, cx);
11671 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11672 assert_eq!(
11673 expanded_hunks_background_highlights(editor, cx),
11674 vec![
11675 DisplayRow(23)..=DisplayRow(23),
11676 DisplayRow(43)..=DisplayRow(43)
11677 ],
11678 );
11679 assert_eq!(all_hunks, expected_all_hunks_shifted);
11680 assert_eq!(all_hunks, all_expanded_hunks);
11681 });
11682
11683 multi_buffer_editor.update(cx, |editor, cx| {
11684 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11685 });
11686 cx.executor().run_until_parked();
11687 multi_buffer_editor.update(cx, |editor, cx| {
11688 let snapshot = editor.snapshot(cx);
11689 let all_hunks = editor_hunks(editor, &snapshot, cx);
11690 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11691 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11692 assert_eq!(all_hunks, expected_all_hunks);
11693 assert_eq!(all_expanded_hunks, Vec::new());
11694 });
11695}
11696
11697#[gpui::test]
11698async fn test_edits_around_toggled_additions(
11699 executor: BackgroundExecutor,
11700 cx: &mut gpui::TestAppContext,
11701) {
11702 init_test(cx, |_| {});
11703
11704 let mut cx = EditorTestContext::new(cx).await;
11705
11706 let diff_base = r#"
11707 use some::mod1;
11708 use some::mod2;
11709
11710 const A: u32 = 42;
11711
11712 fn main() {
11713 println!("hello");
11714
11715 println!("world");
11716 }
11717 "#
11718 .unindent();
11719 executor.run_until_parked();
11720 cx.set_state(
11721 &r#"
11722 use some::mod1;
11723 use some::mod2;
11724
11725 const A: u32 = 42;
11726 const B: u32 = 42;
11727 const C: u32 = 42;
11728 ˇ
11729
11730 fn main() {
11731 println!("hello");
11732
11733 println!("world");
11734 }
11735 "#
11736 .unindent(),
11737 );
11738
11739 cx.set_diff_base(Some(&diff_base));
11740 executor.run_until_parked();
11741 cx.update_editor(|editor, cx| {
11742 let snapshot = editor.snapshot(cx);
11743 let all_hunks = editor_hunks(editor, &snapshot, cx);
11744 assert_eq!(
11745 all_hunks,
11746 vec![(
11747 "".to_string(),
11748 DiffHunkStatus::Added,
11749 DisplayRow(4)..DisplayRow(7)
11750 )]
11751 );
11752 });
11753 cx.update_editor(|editor, cx| {
11754 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11755 });
11756 executor.run_until_parked();
11757 cx.assert_editor_state(
11758 &r#"
11759 use some::mod1;
11760 use some::mod2;
11761
11762 const A: u32 = 42;
11763 const B: u32 = 42;
11764 const C: u32 = 42;
11765 ˇ
11766
11767 fn main() {
11768 println!("hello");
11769
11770 println!("world");
11771 }
11772 "#
11773 .unindent(),
11774 );
11775 cx.update_editor(|editor, cx| {
11776 let snapshot = editor.snapshot(cx);
11777 let all_hunks = editor_hunks(editor, &snapshot, cx);
11778 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11779 assert_eq!(
11780 all_hunks,
11781 vec![(
11782 "".to_string(),
11783 DiffHunkStatus::Added,
11784 DisplayRow(4)..DisplayRow(7)
11785 )]
11786 );
11787 assert_eq!(
11788 expanded_hunks_background_highlights(editor, cx),
11789 vec![DisplayRow(4)..=DisplayRow(6)]
11790 );
11791 assert_eq!(all_hunks, all_expanded_hunks);
11792 });
11793
11794 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
11795 executor.run_until_parked();
11796 cx.assert_editor_state(
11797 &r#"
11798 use some::mod1;
11799 use some::mod2;
11800
11801 const A: u32 = 42;
11802 const B: u32 = 42;
11803 const C: u32 = 42;
11804 const D: u32 = 42;
11805 ˇ
11806
11807 fn main() {
11808 println!("hello");
11809
11810 println!("world");
11811 }
11812 "#
11813 .unindent(),
11814 );
11815 cx.update_editor(|editor, cx| {
11816 let snapshot = editor.snapshot(cx);
11817 let all_hunks = editor_hunks(editor, &snapshot, cx);
11818 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11819 assert_eq!(
11820 all_hunks,
11821 vec![(
11822 "".to_string(),
11823 DiffHunkStatus::Added,
11824 DisplayRow(4)..DisplayRow(8)
11825 )]
11826 );
11827 assert_eq!(
11828 expanded_hunks_background_highlights(editor, cx),
11829 vec![DisplayRow(4)..=DisplayRow(6)],
11830 "Edited hunk should have one more line added"
11831 );
11832 assert_eq!(
11833 all_hunks, all_expanded_hunks,
11834 "Expanded hunk should also grow with the addition"
11835 );
11836 });
11837
11838 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
11839 executor.run_until_parked();
11840 cx.assert_editor_state(
11841 &r#"
11842 use some::mod1;
11843 use some::mod2;
11844
11845 const A: u32 = 42;
11846 const B: u32 = 42;
11847 const C: u32 = 42;
11848 const D: u32 = 42;
11849 const E: u32 = 42;
11850 ˇ
11851
11852 fn main() {
11853 println!("hello");
11854
11855 println!("world");
11856 }
11857 "#
11858 .unindent(),
11859 );
11860 cx.update_editor(|editor, cx| {
11861 let snapshot = editor.snapshot(cx);
11862 let all_hunks = editor_hunks(editor, &snapshot, cx);
11863 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11864 assert_eq!(
11865 all_hunks,
11866 vec![(
11867 "".to_string(),
11868 DiffHunkStatus::Added,
11869 DisplayRow(4)..DisplayRow(9)
11870 )]
11871 );
11872 assert_eq!(
11873 expanded_hunks_background_highlights(editor, cx),
11874 vec![DisplayRow(4)..=DisplayRow(6)],
11875 "Edited hunk should have one more line added"
11876 );
11877 assert_eq!(all_hunks, all_expanded_hunks);
11878 });
11879
11880 cx.update_editor(|editor, cx| {
11881 editor.move_up(&MoveUp, cx);
11882 editor.delete_line(&DeleteLine, cx);
11883 });
11884 executor.run_until_parked();
11885 cx.assert_editor_state(
11886 &r#"
11887 use some::mod1;
11888 use some::mod2;
11889
11890 const A: u32 = 42;
11891 const B: u32 = 42;
11892 const C: u32 = 42;
11893 const D: u32 = 42;
11894 ˇ
11895
11896 fn main() {
11897 println!("hello");
11898
11899 println!("world");
11900 }
11901 "#
11902 .unindent(),
11903 );
11904 cx.update_editor(|editor, cx| {
11905 let snapshot = editor.snapshot(cx);
11906 let all_hunks = editor_hunks(editor, &snapshot, cx);
11907 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11908 assert_eq!(
11909 all_hunks,
11910 vec![(
11911 "".to_string(),
11912 DiffHunkStatus::Added,
11913 DisplayRow(4)..DisplayRow(8)
11914 )]
11915 );
11916 assert_eq!(
11917 expanded_hunks_background_highlights(editor, cx),
11918 vec![DisplayRow(4)..=DisplayRow(6)],
11919 "Deleting a line should shrint the hunk"
11920 );
11921 assert_eq!(
11922 all_hunks, all_expanded_hunks,
11923 "Expanded hunk should also shrink with the addition"
11924 );
11925 });
11926
11927 cx.update_editor(|editor, cx| {
11928 editor.move_up(&MoveUp, cx);
11929 editor.delete_line(&DeleteLine, cx);
11930 editor.move_up(&MoveUp, cx);
11931 editor.delete_line(&DeleteLine, cx);
11932 editor.move_up(&MoveUp, cx);
11933 editor.delete_line(&DeleteLine, cx);
11934 });
11935 executor.run_until_parked();
11936 cx.assert_editor_state(
11937 &r#"
11938 use some::mod1;
11939 use some::mod2;
11940
11941 const A: u32 = 42;
11942 ˇ
11943
11944 fn main() {
11945 println!("hello");
11946
11947 println!("world");
11948 }
11949 "#
11950 .unindent(),
11951 );
11952 cx.update_editor(|editor, cx| {
11953 let snapshot = editor.snapshot(cx);
11954 let all_hunks = editor_hunks(editor, &snapshot, cx);
11955 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11956 assert_eq!(
11957 all_hunks,
11958 vec![(
11959 "".to_string(),
11960 DiffHunkStatus::Added,
11961 DisplayRow(5)..DisplayRow(6)
11962 )]
11963 );
11964 assert_eq!(
11965 expanded_hunks_background_highlights(editor, cx),
11966 vec![DisplayRow(5)..=DisplayRow(5)]
11967 );
11968 assert_eq!(all_hunks, all_expanded_hunks);
11969 });
11970
11971 cx.update_editor(|editor, cx| {
11972 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
11973 editor.delete_line(&DeleteLine, cx);
11974 });
11975 executor.run_until_parked();
11976 cx.assert_editor_state(
11977 &r#"
11978 ˇ
11979
11980 fn main() {
11981 println!("hello");
11982
11983 println!("world");
11984 }
11985 "#
11986 .unindent(),
11987 );
11988 cx.update_editor(|editor, cx| {
11989 let snapshot = editor.snapshot(cx);
11990 let all_hunks = editor_hunks(editor, &snapshot, cx);
11991 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11992 assert_eq!(
11993 all_hunks,
11994 vec![
11995 (
11996 "use some::mod1;\nuse some::mod2;\n".to_string(),
11997 DiffHunkStatus::Removed,
11998 DisplayRow(0)..DisplayRow(0)
11999 ),
12000 (
12001 "const A: u32 = 42;\n".to_string(),
12002 DiffHunkStatus::Removed,
12003 DisplayRow(2)..DisplayRow(2)
12004 )
12005 ]
12006 );
12007 assert_eq!(
12008 expanded_hunks_background_highlights(editor, cx),
12009 Vec::new(),
12010 "Should close all stale expanded addition hunks"
12011 );
12012 assert_eq!(
12013 all_expanded_hunks,
12014 vec![(
12015 "const A: u32 = 42;\n".to_string(),
12016 DiffHunkStatus::Removed,
12017 DisplayRow(2)..DisplayRow(2)
12018 )],
12019 "Should open hunks that were adjacent to the stale addition one"
12020 );
12021 });
12022}
12023
12024#[gpui::test]
12025async fn test_edits_around_toggled_deletions(
12026 executor: BackgroundExecutor,
12027 cx: &mut gpui::TestAppContext,
12028) {
12029 init_test(cx, |_| {});
12030
12031 let mut cx = EditorTestContext::new(cx).await;
12032
12033 let diff_base = r#"
12034 use some::mod1;
12035 use some::mod2;
12036
12037 const A: u32 = 42;
12038 const B: u32 = 42;
12039 const C: u32 = 42;
12040
12041
12042 fn main() {
12043 println!("hello");
12044
12045 println!("world");
12046 }
12047 "#
12048 .unindent();
12049 executor.run_until_parked();
12050 cx.set_state(
12051 &r#"
12052 use some::mod1;
12053 use some::mod2;
12054
12055 ˇconst B: u32 = 42;
12056 const C: u32 = 42;
12057
12058
12059 fn main() {
12060 println!("hello");
12061
12062 println!("world");
12063 }
12064 "#
12065 .unindent(),
12066 );
12067
12068 cx.set_diff_base(Some(&diff_base));
12069 executor.run_until_parked();
12070 cx.update_editor(|editor, cx| {
12071 let snapshot = editor.snapshot(cx);
12072 let all_hunks = editor_hunks(editor, &snapshot, cx);
12073 assert_eq!(
12074 all_hunks,
12075 vec![(
12076 "const A: u32 = 42;\n".to_string(),
12077 DiffHunkStatus::Removed,
12078 DisplayRow(3)..DisplayRow(3)
12079 )]
12080 );
12081 });
12082 cx.update_editor(|editor, cx| {
12083 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12084 });
12085 executor.run_until_parked();
12086 cx.assert_editor_state(
12087 &r#"
12088 use some::mod1;
12089 use some::mod2;
12090
12091 ˇconst B: u32 = 42;
12092 const C: u32 = 42;
12093
12094
12095 fn main() {
12096 println!("hello");
12097
12098 println!("world");
12099 }
12100 "#
12101 .unindent(),
12102 );
12103 cx.update_editor(|editor, cx| {
12104 let snapshot = editor.snapshot(cx);
12105 let all_hunks = editor_hunks(editor, &snapshot, cx);
12106 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12107 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
12108 assert_eq!(
12109 all_hunks,
12110 vec![(
12111 "const A: u32 = 42;\n".to_string(),
12112 DiffHunkStatus::Removed,
12113 DisplayRow(4)..DisplayRow(4)
12114 )]
12115 );
12116 assert_eq!(all_hunks, all_expanded_hunks);
12117 });
12118
12119 cx.update_editor(|editor, cx| {
12120 editor.delete_line(&DeleteLine, cx);
12121 });
12122 executor.run_until_parked();
12123 cx.assert_editor_state(
12124 &r#"
12125 use some::mod1;
12126 use some::mod2;
12127
12128 ˇconst C: u32 = 42;
12129
12130
12131 fn main() {
12132 println!("hello");
12133
12134 println!("world");
12135 }
12136 "#
12137 .unindent(),
12138 );
12139 cx.update_editor(|editor, cx| {
12140 let snapshot = editor.snapshot(cx);
12141 let all_hunks = editor_hunks(editor, &snapshot, cx);
12142 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12143 assert_eq!(
12144 expanded_hunks_background_highlights(editor, cx),
12145 Vec::new(),
12146 "Deleted hunks do not highlight current editor's background"
12147 );
12148 assert_eq!(
12149 all_hunks,
12150 vec![(
12151 "const A: u32 = 42;\nconst B: u32 = 42;\n".to_string(),
12152 DiffHunkStatus::Removed,
12153 DisplayRow(5)..DisplayRow(5)
12154 )]
12155 );
12156 assert_eq!(all_hunks, all_expanded_hunks);
12157 });
12158
12159 cx.update_editor(|editor, cx| {
12160 editor.delete_line(&DeleteLine, cx);
12161 });
12162 executor.run_until_parked();
12163 cx.assert_editor_state(
12164 &r#"
12165 use some::mod1;
12166 use some::mod2;
12167
12168 ˇ
12169
12170 fn main() {
12171 println!("hello");
12172
12173 println!("world");
12174 }
12175 "#
12176 .unindent(),
12177 );
12178 cx.update_editor(|editor, cx| {
12179 let snapshot = editor.snapshot(cx);
12180 let all_hunks = editor_hunks(editor, &snapshot, cx);
12181 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12182 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
12183 assert_eq!(
12184 all_hunks,
12185 vec![(
12186 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12187 DiffHunkStatus::Removed,
12188 DisplayRow(6)..DisplayRow(6)
12189 )]
12190 );
12191 assert_eq!(all_hunks, all_expanded_hunks);
12192 });
12193
12194 cx.update_editor(|editor, cx| {
12195 editor.handle_input("replacement", cx);
12196 });
12197 executor.run_until_parked();
12198 cx.assert_editor_state(
12199 &r#"
12200 use some::mod1;
12201 use some::mod2;
12202
12203 replacementˇ
12204
12205 fn main() {
12206 println!("hello");
12207
12208 println!("world");
12209 }
12210 "#
12211 .unindent(),
12212 );
12213 cx.update_editor(|editor, cx| {
12214 let snapshot = editor.snapshot(cx);
12215 let all_hunks = editor_hunks(editor, &snapshot, cx);
12216 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12217 assert_eq!(
12218 all_hunks,
12219 vec![(
12220 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n\n".to_string(),
12221 DiffHunkStatus::Modified,
12222 DisplayRow(7)..DisplayRow(8)
12223 )]
12224 );
12225 assert_eq!(
12226 expanded_hunks_background_highlights(editor, cx),
12227 vec![DisplayRow(7)..=DisplayRow(7)],
12228 "Modified expanded hunks should display additions and highlight their background"
12229 );
12230 assert_eq!(all_hunks, all_expanded_hunks);
12231 });
12232}
12233
12234#[gpui::test]
12235async fn test_edits_around_toggled_modifications(
12236 executor: BackgroundExecutor,
12237 cx: &mut gpui::TestAppContext,
12238) {
12239 init_test(cx, |_| {});
12240
12241 let mut cx = EditorTestContext::new(cx).await;
12242
12243 let diff_base = r#"
12244 use some::mod1;
12245 use some::mod2;
12246
12247 const A: u32 = 42;
12248 const B: u32 = 42;
12249 const C: u32 = 42;
12250 const D: u32 = 42;
12251
12252
12253 fn main() {
12254 println!("hello");
12255
12256 println!("world");
12257 }"#
12258 .unindent();
12259 executor.run_until_parked();
12260 cx.set_state(
12261 &r#"
12262 use some::mod1;
12263 use some::mod2;
12264
12265 const A: u32 = 42;
12266 const B: u32 = 42;
12267 const C: u32 = 43ˇ
12268 const D: u32 = 42;
12269
12270
12271 fn main() {
12272 println!("hello");
12273
12274 println!("world");
12275 }"#
12276 .unindent(),
12277 );
12278
12279 cx.set_diff_base(Some(&diff_base));
12280 executor.run_until_parked();
12281 cx.update_editor(|editor, cx| {
12282 let snapshot = editor.snapshot(cx);
12283 let all_hunks = editor_hunks(editor, &snapshot, cx);
12284 assert_eq!(
12285 all_hunks,
12286 vec![(
12287 "const C: u32 = 42;\n".to_string(),
12288 DiffHunkStatus::Modified,
12289 DisplayRow(5)..DisplayRow(6)
12290 )]
12291 );
12292 });
12293 cx.update_editor(|editor, cx| {
12294 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12295 });
12296 executor.run_until_parked();
12297 cx.assert_editor_state(
12298 &r#"
12299 use some::mod1;
12300 use some::mod2;
12301
12302 const A: u32 = 42;
12303 const B: u32 = 42;
12304 const C: u32 = 43ˇ
12305 const D: u32 = 42;
12306
12307
12308 fn main() {
12309 println!("hello");
12310
12311 println!("world");
12312 }"#
12313 .unindent(),
12314 );
12315 cx.update_editor(|editor, cx| {
12316 let snapshot = editor.snapshot(cx);
12317 let all_hunks = editor_hunks(editor, &snapshot, cx);
12318 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12319 assert_eq!(
12320 expanded_hunks_background_highlights(editor, cx),
12321 vec![DisplayRow(6)..=DisplayRow(6)],
12322 );
12323 assert_eq!(
12324 all_hunks,
12325 vec![(
12326 "const C: u32 = 42;\n".to_string(),
12327 DiffHunkStatus::Modified,
12328 DisplayRow(6)..DisplayRow(7)
12329 )]
12330 );
12331 assert_eq!(all_hunks, all_expanded_hunks);
12332 });
12333
12334 cx.update_editor(|editor, cx| {
12335 editor.handle_input("\nnew_line\n", cx);
12336 });
12337 executor.run_until_parked();
12338 cx.assert_editor_state(
12339 &r#"
12340 use some::mod1;
12341 use some::mod2;
12342
12343 const A: u32 = 42;
12344 const B: u32 = 42;
12345 const C: u32 = 43
12346 new_line
12347 ˇ
12348 const D: u32 = 42;
12349
12350
12351 fn main() {
12352 println!("hello");
12353
12354 println!("world");
12355 }"#
12356 .unindent(),
12357 );
12358 cx.update_editor(|editor, cx| {
12359 let snapshot = editor.snapshot(cx);
12360 let all_hunks = editor_hunks(editor, &snapshot, cx);
12361 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12362 assert_eq!(
12363 expanded_hunks_background_highlights(editor, cx),
12364 vec![DisplayRow(6)..=DisplayRow(6)],
12365 "Modified hunk should grow highlighted lines on more text additions"
12366 );
12367 assert_eq!(
12368 all_hunks,
12369 vec![(
12370 "const C: u32 = 42;\n".to_string(),
12371 DiffHunkStatus::Modified,
12372 DisplayRow(6)..DisplayRow(9)
12373 )]
12374 );
12375 assert_eq!(all_hunks, all_expanded_hunks);
12376 });
12377
12378 cx.update_editor(|editor, cx| {
12379 editor.move_up(&MoveUp, cx);
12380 editor.move_up(&MoveUp, cx);
12381 editor.move_up(&MoveUp, cx);
12382 editor.delete_line(&DeleteLine, cx);
12383 });
12384 executor.run_until_parked();
12385 cx.assert_editor_state(
12386 &r#"
12387 use some::mod1;
12388 use some::mod2;
12389
12390 const A: u32 = 42;
12391 ˇconst C: u32 = 43
12392 new_line
12393
12394 const D: u32 = 42;
12395
12396
12397 fn main() {
12398 println!("hello");
12399
12400 println!("world");
12401 }"#
12402 .unindent(),
12403 );
12404 cx.update_editor(|editor, cx| {
12405 let snapshot = editor.snapshot(cx);
12406 let all_hunks = editor_hunks(editor, &snapshot, cx);
12407 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12408 assert_eq!(
12409 expanded_hunks_background_highlights(editor, cx),
12410 vec![DisplayRow(6)..=DisplayRow(8)],
12411 );
12412 assert_eq!(
12413 all_hunks,
12414 vec![(
12415 "const B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12416 DiffHunkStatus::Modified,
12417 DisplayRow(6)..DisplayRow(9)
12418 )],
12419 "Modified hunk should grow deleted lines on text deletions above"
12420 );
12421 assert_eq!(all_hunks, all_expanded_hunks);
12422 });
12423
12424 cx.update_editor(|editor, cx| {
12425 editor.move_up(&MoveUp, cx);
12426 editor.handle_input("v", cx);
12427 });
12428 executor.run_until_parked();
12429 cx.assert_editor_state(
12430 &r#"
12431 use some::mod1;
12432 use some::mod2;
12433
12434 vˇconst A: u32 = 42;
12435 const C: u32 = 43
12436 new_line
12437
12438 const D: u32 = 42;
12439
12440
12441 fn main() {
12442 println!("hello");
12443
12444 println!("world");
12445 }"#
12446 .unindent(),
12447 );
12448 cx.update_editor(|editor, cx| {
12449 let snapshot = editor.snapshot(cx);
12450 let all_hunks = editor_hunks(editor, &snapshot, cx);
12451 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12452 assert_eq!(
12453 expanded_hunks_background_highlights(editor, cx),
12454 vec![DisplayRow(6)..=DisplayRow(9)],
12455 "Modified hunk should grow deleted lines on text modifications above"
12456 );
12457 assert_eq!(
12458 all_hunks,
12459 vec![(
12460 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12461 DiffHunkStatus::Modified,
12462 DisplayRow(6)..DisplayRow(10)
12463 )]
12464 );
12465 assert_eq!(all_hunks, all_expanded_hunks);
12466 });
12467
12468 cx.update_editor(|editor, cx| {
12469 editor.move_down(&MoveDown, cx);
12470 editor.move_down(&MoveDown, cx);
12471 editor.delete_line(&DeleteLine, cx)
12472 });
12473 executor.run_until_parked();
12474 cx.assert_editor_state(
12475 &r#"
12476 use some::mod1;
12477 use some::mod2;
12478
12479 vconst A: u32 = 42;
12480 const C: u32 = 43
12481 ˇ
12482 const D: u32 = 42;
12483
12484
12485 fn main() {
12486 println!("hello");
12487
12488 println!("world");
12489 }"#
12490 .unindent(),
12491 );
12492 cx.update_editor(|editor, cx| {
12493 let snapshot = editor.snapshot(cx);
12494 let all_hunks = editor_hunks(editor, &snapshot, cx);
12495 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12496 assert_eq!(
12497 expanded_hunks_background_highlights(editor, cx),
12498 vec![DisplayRow(6)..=DisplayRow(8)],
12499 "Modified hunk should grow shrink lines on modification lines removal"
12500 );
12501 assert_eq!(
12502 all_hunks,
12503 vec![(
12504 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12505 DiffHunkStatus::Modified,
12506 DisplayRow(6)..DisplayRow(9)
12507 )]
12508 );
12509 assert_eq!(all_hunks, all_expanded_hunks);
12510 });
12511
12512 cx.update_editor(|editor, cx| {
12513 editor.move_up(&MoveUp, cx);
12514 editor.move_up(&MoveUp, cx);
12515 editor.select_down_by_lines(&SelectDownByLines { lines: 4 }, cx);
12516 editor.delete_line(&DeleteLine, cx)
12517 });
12518 executor.run_until_parked();
12519 cx.assert_editor_state(
12520 &r#"
12521 use some::mod1;
12522 use some::mod2;
12523
12524 ˇ
12525
12526 fn main() {
12527 println!("hello");
12528
12529 println!("world");
12530 }"#
12531 .unindent(),
12532 );
12533 cx.update_editor(|editor, cx| {
12534 let snapshot = editor.snapshot(cx);
12535 let all_hunks = editor_hunks(editor, &snapshot, cx);
12536 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12537 assert_eq!(
12538 expanded_hunks_background_highlights(editor, cx),
12539 Vec::new(),
12540 "Modified hunk should turn into a removed one on all modified lines removal"
12541 );
12542 assert_eq!(
12543 all_hunks,
12544 vec![(
12545 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\nconst D: u32 = 42;\n"
12546 .to_string(),
12547 DiffHunkStatus::Removed,
12548 DisplayRow(7)..DisplayRow(7)
12549 )]
12550 );
12551 assert_eq!(all_hunks, all_expanded_hunks);
12552 });
12553}
12554
12555#[gpui::test]
12556async fn test_multiple_expanded_hunks_merge(
12557 executor: BackgroundExecutor,
12558 cx: &mut gpui::TestAppContext,
12559) {
12560 init_test(cx, |_| {});
12561
12562 let mut cx = EditorTestContext::new(cx).await;
12563
12564 let diff_base = r#"
12565 use some::mod1;
12566 use some::mod2;
12567
12568 const A: u32 = 42;
12569 const B: u32 = 42;
12570 const C: u32 = 42;
12571 const D: u32 = 42;
12572
12573
12574 fn main() {
12575 println!("hello");
12576
12577 println!("world");
12578 }"#
12579 .unindent();
12580 executor.run_until_parked();
12581 cx.set_state(
12582 &r#"
12583 use some::mod1;
12584 use some::mod2;
12585
12586 const A: u32 = 42;
12587 const B: u32 = 42;
12588 const C: u32 = 43ˇ
12589 const D: u32 = 42;
12590
12591
12592 fn main() {
12593 println!("hello");
12594
12595 println!("world");
12596 }"#
12597 .unindent(),
12598 );
12599
12600 cx.set_diff_base(Some(&diff_base));
12601 executor.run_until_parked();
12602 cx.update_editor(|editor, cx| {
12603 let snapshot = editor.snapshot(cx);
12604 let all_hunks = editor_hunks(editor, &snapshot, cx);
12605 assert_eq!(
12606 all_hunks,
12607 vec![(
12608 "const C: u32 = 42;\n".to_string(),
12609 DiffHunkStatus::Modified,
12610 DisplayRow(5)..DisplayRow(6)
12611 )]
12612 );
12613 });
12614 cx.update_editor(|editor, cx| {
12615 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12616 });
12617 executor.run_until_parked();
12618 cx.assert_editor_state(
12619 &r#"
12620 use some::mod1;
12621 use some::mod2;
12622
12623 const A: u32 = 42;
12624 const B: u32 = 42;
12625 const C: u32 = 43ˇ
12626 const D: u32 = 42;
12627
12628
12629 fn main() {
12630 println!("hello");
12631
12632 println!("world");
12633 }"#
12634 .unindent(),
12635 );
12636 cx.update_editor(|editor, cx| {
12637 let snapshot = editor.snapshot(cx);
12638 let all_hunks = editor_hunks(editor, &snapshot, cx);
12639 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12640 assert_eq!(
12641 expanded_hunks_background_highlights(editor, cx),
12642 vec![DisplayRow(6)..=DisplayRow(6)],
12643 );
12644 assert_eq!(
12645 all_hunks,
12646 vec![(
12647 "const C: u32 = 42;\n".to_string(),
12648 DiffHunkStatus::Modified,
12649 DisplayRow(6)..DisplayRow(7)
12650 )]
12651 );
12652 assert_eq!(all_hunks, all_expanded_hunks);
12653 });
12654
12655 cx.update_editor(|editor, cx| {
12656 editor.handle_input("\nnew_line\n", cx);
12657 });
12658 executor.run_until_parked();
12659 cx.assert_editor_state(
12660 &r#"
12661 use some::mod1;
12662 use some::mod2;
12663
12664 const A: u32 = 42;
12665 const B: u32 = 42;
12666 const C: u32 = 43
12667 new_line
12668 ˇ
12669 const D: u32 = 42;
12670
12671
12672 fn main() {
12673 println!("hello");
12674
12675 println!("world");
12676 }"#
12677 .unindent(),
12678 );
12679}
12680
12681async fn setup_indent_guides_editor(
12682 text: &str,
12683 cx: &mut gpui::TestAppContext,
12684) -> (BufferId, EditorTestContext) {
12685 init_test(cx, |_| {});
12686
12687 let mut cx = EditorTestContext::new(cx).await;
12688
12689 let buffer_id = cx.update_editor(|editor, cx| {
12690 editor.set_text(text, cx);
12691 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12692 let buffer_id = buffer_ids[0];
12693 buffer_id
12694 });
12695
12696 (buffer_id, cx)
12697}
12698
12699fn assert_indent_guides(
12700 range: Range<u32>,
12701 expected: Vec<IndentGuide>,
12702 active_indices: Option<Vec<usize>>,
12703 cx: &mut EditorTestContext,
12704) {
12705 let indent_guides = cx.update_editor(|editor, cx| {
12706 let snapshot = editor.snapshot(cx).display_snapshot;
12707 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12708 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12709 true,
12710 &snapshot,
12711 cx,
12712 );
12713
12714 indent_guides.sort_by(|a, b| {
12715 a.depth.cmp(&b.depth).then(
12716 a.start_row
12717 .cmp(&b.start_row)
12718 .then(a.end_row.cmp(&b.end_row)),
12719 )
12720 });
12721 indent_guides
12722 });
12723
12724 if let Some(expected) = active_indices {
12725 let active_indices = cx.update_editor(|editor, cx| {
12726 let snapshot = editor.snapshot(cx).display_snapshot;
12727 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
12728 });
12729
12730 assert_eq!(
12731 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
12732 expected,
12733 "Active indent guide indices do not match"
12734 );
12735 }
12736
12737 let expected: Vec<_> = expected
12738 .into_iter()
12739 .map(|guide| MultiBufferIndentGuide {
12740 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
12741 buffer: guide,
12742 })
12743 .collect();
12744
12745 assert_eq!(indent_guides, expected, "Indent guides do not match");
12746}
12747
12748fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
12749 IndentGuide {
12750 buffer_id,
12751 start_row,
12752 end_row,
12753 depth,
12754 tab_size: 4,
12755 settings: IndentGuideSettings {
12756 enabled: true,
12757 line_width: 1,
12758 active_line_width: 1,
12759 ..Default::default()
12760 },
12761 }
12762}
12763
12764#[gpui::test]
12765async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12766 let (buffer_id, mut cx) = setup_indent_guides_editor(
12767 &"
12768 fn main() {
12769 let a = 1;
12770 }"
12771 .unindent(),
12772 cx,
12773 )
12774 .await;
12775
12776 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12777}
12778
12779#[gpui::test]
12780async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
12781 let (buffer_id, mut cx) = setup_indent_guides_editor(
12782 &"
12783 fn main() {
12784 let a = 1;
12785 let b = 2;
12786 }"
12787 .unindent(),
12788 cx,
12789 )
12790 .await;
12791
12792 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
12793}
12794
12795#[gpui::test]
12796async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
12797 let (buffer_id, mut cx) = setup_indent_guides_editor(
12798 &"
12799 fn main() {
12800 let a = 1;
12801 if a == 3 {
12802 let b = 2;
12803 } else {
12804 let c = 3;
12805 }
12806 }"
12807 .unindent(),
12808 cx,
12809 )
12810 .await;
12811
12812 assert_indent_guides(
12813 0..8,
12814 vec![
12815 indent_guide(buffer_id, 1, 6, 0),
12816 indent_guide(buffer_id, 3, 3, 1),
12817 indent_guide(buffer_id, 5, 5, 1),
12818 ],
12819 None,
12820 &mut cx,
12821 );
12822}
12823
12824#[gpui::test]
12825async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
12826 let (buffer_id, mut cx) = setup_indent_guides_editor(
12827 &"
12828 fn main() {
12829 let a = 1;
12830 let b = 2;
12831 let c = 3;
12832 }"
12833 .unindent(),
12834 cx,
12835 )
12836 .await;
12837
12838 assert_indent_guides(
12839 0..5,
12840 vec![
12841 indent_guide(buffer_id, 1, 3, 0),
12842 indent_guide(buffer_id, 2, 2, 1),
12843 ],
12844 None,
12845 &mut cx,
12846 );
12847}
12848
12849#[gpui::test]
12850async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
12851 let (buffer_id, mut cx) = setup_indent_guides_editor(
12852 &"
12853 fn main() {
12854 let a = 1;
12855
12856 let c = 3;
12857 }"
12858 .unindent(),
12859 cx,
12860 )
12861 .await;
12862
12863 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
12864}
12865
12866#[gpui::test]
12867async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
12868 let (buffer_id, mut cx) = setup_indent_guides_editor(
12869 &"
12870 fn main() {
12871 let a = 1;
12872
12873 let c = 3;
12874
12875 if a == 3 {
12876 let b = 2;
12877 } else {
12878 let c = 3;
12879 }
12880 }"
12881 .unindent(),
12882 cx,
12883 )
12884 .await;
12885
12886 assert_indent_guides(
12887 0..11,
12888 vec![
12889 indent_guide(buffer_id, 1, 9, 0),
12890 indent_guide(buffer_id, 6, 6, 1),
12891 indent_guide(buffer_id, 8, 8, 1),
12892 ],
12893 None,
12894 &mut cx,
12895 );
12896}
12897
12898#[gpui::test]
12899async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
12900 let (buffer_id, mut cx) = setup_indent_guides_editor(
12901 &"
12902 fn main() {
12903 let a = 1;
12904
12905 let c = 3;
12906
12907 if a == 3 {
12908 let b = 2;
12909 } else {
12910 let c = 3;
12911 }
12912 }"
12913 .unindent(),
12914 cx,
12915 )
12916 .await;
12917
12918 assert_indent_guides(
12919 1..11,
12920 vec![
12921 indent_guide(buffer_id, 1, 9, 0),
12922 indent_guide(buffer_id, 6, 6, 1),
12923 indent_guide(buffer_id, 8, 8, 1),
12924 ],
12925 None,
12926 &mut cx,
12927 );
12928}
12929
12930#[gpui::test]
12931async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
12932 let (buffer_id, mut cx) = setup_indent_guides_editor(
12933 &"
12934 fn main() {
12935 let a = 1;
12936
12937 let c = 3;
12938
12939 if a == 3 {
12940 let b = 2;
12941 } else {
12942 let c = 3;
12943 }
12944 }"
12945 .unindent(),
12946 cx,
12947 )
12948 .await;
12949
12950 assert_indent_guides(
12951 1..10,
12952 vec![
12953 indent_guide(buffer_id, 1, 9, 0),
12954 indent_guide(buffer_id, 6, 6, 1),
12955 indent_guide(buffer_id, 8, 8, 1),
12956 ],
12957 None,
12958 &mut cx,
12959 );
12960}
12961
12962#[gpui::test]
12963async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
12964 let (buffer_id, mut cx) = setup_indent_guides_editor(
12965 &"
12966 block1
12967 block2
12968 block3
12969 block4
12970 block2
12971 block1
12972 block1"
12973 .unindent(),
12974 cx,
12975 )
12976 .await;
12977
12978 assert_indent_guides(
12979 1..10,
12980 vec![
12981 indent_guide(buffer_id, 1, 4, 0),
12982 indent_guide(buffer_id, 2, 3, 1),
12983 indent_guide(buffer_id, 3, 3, 2),
12984 ],
12985 None,
12986 &mut cx,
12987 );
12988}
12989
12990#[gpui::test]
12991async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
12992 let (buffer_id, mut cx) = setup_indent_guides_editor(
12993 &"
12994 block1
12995 block2
12996 block3
12997
12998 block1
12999 block1"
13000 .unindent(),
13001 cx,
13002 )
13003 .await;
13004
13005 assert_indent_guides(
13006 0..6,
13007 vec![
13008 indent_guide(buffer_id, 1, 2, 0),
13009 indent_guide(buffer_id, 2, 2, 1),
13010 ],
13011 None,
13012 &mut cx,
13013 );
13014}
13015
13016#[gpui::test]
13017async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
13018 let (buffer_id, mut cx) = setup_indent_guides_editor(
13019 &"
13020 block1
13021
13022
13023
13024 block2
13025 "
13026 .unindent(),
13027 cx,
13028 )
13029 .await;
13030
13031 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13032}
13033
13034#[gpui::test]
13035async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
13036 let (buffer_id, mut cx) = setup_indent_guides_editor(
13037 &"
13038 def a:
13039 \tb = 3
13040 \tif True:
13041 \t\tc = 4
13042 \t\td = 5
13043 \tprint(b)
13044 "
13045 .unindent(),
13046 cx,
13047 )
13048 .await;
13049
13050 assert_indent_guides(
13051 0..6,
13052 vec![
13053 indent_guide(buffer_id, 1, 6, 0),
13054 indent_guide(buffer_id, 3, 4, 1),
13055 ],
13056 None,
13057 &mut cx,
13058 );
13059}
13060
13061#[gpui::test]
13062async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13063 let (buffer_id, mut cx) = setup_indent_guides_editor(
13064 &"
13065 fn main() {
13066 let a = 1;
13067 }"
13068 .unindent(),
13069 cx,
13070 )
13071 .await;
13072
13073 cx.update_editor(|editor, cx| {
13074 editor.change_selections(None, cx, |s| {
13075 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13076 });
13077 });
13078
13079 assert_indent_guides(
13080 0..3,
13081 vec![indent_guide(buffer_id, 1, 1, 0)],
13082 Some(vec![0]),
13083 &mut cx,
13084 );
13085}
13086
13087#[gpui::test]
13088async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13089 let (buffer_id, mut cx) = setup_indent_guides_editor(
13090 &"
13091 fn main() {
13092 if 1 == 2 {
13093 let a = 1;
13094 }
13095 }"
13096 .unindent(),
13097 cx,
13098 )
13099 .await;
13100
13101 cx.update_editor(|editor, cx| {
13102 editor.change_selections(None, cx, |s| {
13103 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13104 });
13105 });
13106
13107 assert_indent_guides(
13108 0..4,
13109 vec![
13110 indent_guide(buffer_id, 1, 3, 0),
13111 indent_guide(buffer_id, 2, 2, 1),
13112 ],
13113 Some(vec![1]),
13114 &mut cx,
13115 );
13116
13117 cx.update_editor(|editor, cx| {
13118 editor.change_selections(None, cx, |s| {
13119 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13120 });
13121 });
13122
13123 assert_indent_guides(
13124 0..4,
13125 vec![
13126 indent_guide(buffer_id, 1, 3, 0),
13127 indent_guide(buffer_id, 2, 2, 1),
13128 ],
13129 Some(vec![1]),
13130 &mut cx,
13131 );
13132
13133 cx.update_editor(|editor, cx| {
13134 editor.change_selections(None, cx, |s| {
13135 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13136 });
13137 });
13138
13139 assert_indent_guides(
13140 0..4,
13141 vec![
13142 indent_guide(buffer_id, 1, 3, 0),
13143 indent_guide(buffer_id, 2, 2, 1),
13144 ],
13145 Some(vec![0]),
13146 &mut cx,
13147 );
13148}
13149
13150#[gpui::test]
13151async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13152 let (buffer_id, mut cx) = setup_indent_guides_editor(
13153 &"
13154 fn main() {
13155 let a = 1;
13156
13157 let b = 2;
13158 }"
13159 .unindent(),
13160 cx,
13161 )
13162 .await;
13163
13164 cx.update_editor(|editor, cx| {
13165 editor.change_selections(None, cx, |s| {
13166 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13167 });
13168 });
13169
13170 assert_indent_guides(
13171 0..5,
13172 vec![indent_guide(buffer_id, 1, 3, 0)],
13173 Some(vec![0]),
13174 &mut cx,
13175 );
13176}
13177
13178#[gpui::test]
13179async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13180 let (buffer_id, mut cx) = setup_indent_guides_editor(
13181 &"
13182 def m:
13183 a = 1
13184 pass"
13185 .unindent(),
13186 cx,
13187 )
13188 .await;
13189
13190 cx.update_editor(|editor, cx| {
13191 editor.change_selections(None, cx, |s| {
13192 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13193 });
13194 });
13195
13196 assert_indent_guides(
13197 0..3,
13198 vec![indent_guide(buffer_id, 1, 2, 0)],
13199 Some(vec![0]),
13200 &mut cx,
13201 );
13202}
13203
13204#[gpui::test]
13205fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13206 init_test(cx, |_| {});
13207
13208 let editor = cx.add_window(|cx| {
13209 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13210 build_editor(buffer, cx)
13211 });
13212
13213 let render_args = Arc::new(Mutex::new(None));
13214 let snapshot = editor
13215 .update(cx, |editor, cx| {
13216 let snapshot = editor.buffer().read(cx).snapshot(cx);
13217 let range =
13218 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13219
13220 struct RenderArgs {
13221 row: MultiBufferRow,
13222 folded: bool,
13223 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13224 }
13225
13226 let crease = Crease::new(
13227 range,
13228 FoldPlaceholder::test(),
13229 {
13230 let toggle_callback = render_args.clone();
13231 move |row, folded, callback, _cx| {
13232 *toggle_callback.lock() = Some(RenderArgs {
13233 row,
13234 folded,
13235 callback,
13236 });
13237 div()
13238 }
13239 },
13240 |_row, _folded, _cx| div(),
13241 );
13242
13243 editor.insert_creases(Some(crease), cx);
13244 let snapshot = editor.snapshot(cx);
13245 let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13246 snapshot
13247 })
13248 .unwrap();
13249
13250 let render_args = render_args.lock().take().unwrap();
13251 assert_eq!(render_args.row, MultiBufferRow(1));
13252 assert_eq!(render_args.folded, false);
13253 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13254
13255 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13256 .unwrap();
13257 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13258 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13259
13260 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13261 .unwrap();
13262 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13263 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13264}
13265
13266#[gpui::test]
13267async fn test_input_text(cx: &mut gpui::TestAppContext) {
13268 init_test(cx, |_| {});
13269 let mut cx = EditorTestContext::new(cx).await;
13270
13271 cx.set_state(
13272 &r#"ˇone
13273 two
13274
13275 three
13276 fourˇ
13277 five
13278
13279 siˇx"#
13280 .unindent(),
13281 );
13282
13283 cx.dispatch_action(HandleInput(String::new()));
13284 cx.assert_editor_state(
13285 &r#"ˇone
13286 two
13287
13288 three
13289 fourˇ
13290 five
13291
13292 siˇx"#
13293 .unindent(),
13294 );
13295
13296 cx.dispatch_action(HandleInput("AAAA".to_string()));
13297 cx.assert_editor_state(
13298 &r#"AAAAˇone
13299 two
13300
13301 three
13302 fourAAAAˇ
13303 five
13304
13305 siAAAAˇx"#
13306 .unindent(),
13307 );
13308}
13309
13310#[gpui::test]
13311async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13312 init_test(cx, |_| {});
13313
13314 let mut cx = EditorTestContext::new(cx).await;
13315 cx.set_state(
13316 r#"let foo = 1;
13317let foo = 2;
13318let foo = 3;
13319let fooˇ = 4;
13320let foo = 5;
13321let foo = 6;
13322let foo = 7;
13323let foo = 8;
13324let foo = 9;
13325let foo = 10;
13326let foo = 11;
13327let foo = 12;
13328let foo = 13;
13329let foo = 14;
13330let foo = 15;"#,
13331 );
13332
13333 cx.update_editor(|e, cx| {
13334 assert_eq!(
13335 e.next_scroll_position,
13336 NextScrollCursorCenterTopBottom::Center,
13337 "Default next scroll direction is center",
13338 );
13339
13340 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13341 assert_eq!(
13342 e.next_scroll_position,
13343 NextScrollCursorCenterTopBottom::Top,
13344 "After center, next scroll direction should be top",
13345 );
13346
13347 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13348 assert_eq!(
13349 e.next_scroll_position,
13350 NextScrollCursorCenterTopBottom::Bottom,
13351 "After top, next scroll direction should be bottom",
13352 );
13353
13354 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13355 assert_eq!(
13356 e.next_scroll_position,
13357 NextScrollCursorCenterTopBottom::Center,
13358 "After bottom, scrolling should start over",
13359 );
13360
13361 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13362 assert_eq!(
13363 e.next_scroll_position,
13364 NextScrollCursorCenterTopBottom::Top,
13365 "Scrolling continues if retriggered fast enough"
13366 );
13367 });
13368
13369 cx.executor()
13370 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13371 cx.executor().run_until_parked();
13372 cx.update_editor(|e, _| {
13373 assert_eq!(
13374 e.next_scroll_position,
13375 NextScrollCursorCenterTopBottom::Center,
13376 "If scrolling is not triggered fast enough, it should reset"
13377 );
13378 });
13379}
13380
13381#[gpui::test]
13382async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13383 init_test(cx, |_| {});
13384 let mut cx = EditorLspTestContext::new_rust(
13385 lsp::ServerCapabilities {
13386 definition_provider: Some(lsp::OneOf::Left(true)),
13387 references_provider: Some(lsp::OneOf::Left(true)),
13388 ..lsp::ServerCapabilities::default()
13389 },
13390 cx,
13391 )
13392 .await;
13393
13394 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13395 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13396 move |params, _| async move {
13397 if empty_go_to_definition {
13398 Ok(None)
13399 } else {
13400 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13401 uri: params.text_document_position_params.text_document.uri,
13402 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13403 })))
13404 }
13405 },
13406 );
13407 let references =
13408 cx.lsp
13409 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13410 Ok(Some(vec![lsp::Location {
13411 uri: params.text_document_position.text_document.uri,
13412 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13413 }]))
13414 });
13415 (go_to_definition, references)
13416 };
13417
13418 cx.set_state(
13419 &r#"fn one() {
13420 let mut a = ˇtwo();
13421 }
13422
13423 fn two() {}"#
13424 .unindent(),
13425 );
13426 set_up_lsp_handlers(false, &mut cx);
13427 let navigated = cx
13428 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13429 .await
13430 .expect("Failed to navigate to definition");
13431 assert_eq!(
13432 navigated,
13433 Navigated::Yes,
13434 "Should have navigated to definition from the GetDefinition response"
13435 );
13436 cx.assert_editor_state(
13437 &r#"fn one() {
13438 let mut a = two();
13439 }
13440
13441 fn «twoˇ»() {}"#
13442 .unindent(),
13443 );
13444
13445 let editors = cx.update_workspace(|workspace, cx| {
13446 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13447 });
13448 cx.update_editor(|_, test_editor_cx| {
13449 assert_eq!(
13450 editors.len(),
13451 1,
13452 "Initially, only one, test, editor should be open in the workspace"
13453 );
13454 assert_eq!(
13455 test_editor_cx.view(),
13456 editors.last().expect("Asserted len is 1")
13457 );
13458 });
13459
13460 set_up_lsp_handlers(true, &mut cx);
13461 let navigated = cx
13462 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13463 .await
13464 .expect("Failed to navigate to lookup references");
13465 assert_eq!(
13466 navigated,
13467 Navigated::Yes,
13468 "Should have navigated to references as a fallback after empty GoToDefinition response"
13469 );
13470 // We should not change the selections in the existing file,
13471 // if opening another milti buffer with the references
13472 cx.assert_editor_state(
13473 &r#"fn one() {
13474 let mut a = two();
13475 }
13476
13477 fn «twoˇ»() {}"#
13478 .unindent(),
13479 );
13480 let editors = cx.update_workspace(|workspace, cx| {
13481 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13482 });
13483 cx.update_editor(|_, test_editor_cx| {
13484 assert_eq!(
13485 editors.len(),
13486 2,
13487 "After falling back to references search, we open a new editor with the results"
13488 );
13489 let references_fallback_text = editors
13490 .into_iter()
13491 .find(|new_editor| new_editor != test_editor_cx.view())
13492 .expect("Should have one non-test editor now")
13493 .read(test_editor_cx)
13494 .text(test_editor_cx);
13495 assert_eq!(
13496 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13497 "Should use the range from the references response and not the GoToDefinition one"
13498 );
13499 });
13500}
13501
13502fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13503 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13504 point..point
13505}
13506
13507fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13508 let (text, ranges) = marked_text_ranges(marked_text, true);
13509 assert_eq!(view.text(cx), text);
13510 assert_eq!(
13511 view.selections.ranges(cx),
13512 ranges,
13513 "Assert selections are {}",
13514 marked_text
13515 );
13516}
13517
13518pub fn handle_signature_help_request(
13519 cx: &mut EditorLspTestContext,
13520 mocked_response: lsp::SignatureHelp,
13521) -> impl Future<Output = ()> {
13522 let mut request =
13523 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13524 let mocked_response = mocked_response.clone();
13525 async move { Ok(Some(mocked_response)) }
13526 });
13527
13528 async move {
13529 request.next().await;
13530 }
13531}
13532
13533/// Handle completion request passing a marked string specifying where the completion
13534/// should be triggered from using '|' character, what range should be replaced, and what completions
13535/// should be returned using '<' and '>' to delimit the range
13536pub fn handle_completion_request(
13537 cx: &mut EditorLspTestContext,
13538 marked_string: &str,
13539 completions: Vec<&'static str>,
13540 counter: Arc<AtomicUsize>,
13541) -> impl Future<Output = ()> {
13542 let complete_from_marker: TextRangeMarker = '|'.into();
13543 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13544 let (_, mut marked_ranges) = marked_text_ranges_by(
13545 marked_string,
13546 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13547 );
13548
13549 let complete_from_position =
13550 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13551 let replace_range =
13552 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13553
13554 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13555 let completions = completions.clone();
13556 counter.fetch_add(1, atomic::Ordering::Release);
13557 async move {
13558 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13559 assert_eq!(
13560 params.text_document_position.position,
13561 complete_from_position
13562 );
13563 Ok(Some(lsp::CompletionResponse::Array(
13564 completions
13565 .iter()
13566 .map(|completion_text| lsp::CompletionItem {
13567 label: completion_text.to_string(),
13568 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13569 range: replace_range,
13570 new_text: completion_text.to_string(),
13571 })),
13572 ..Default::default()
13573 })
13574 .collect(),
13575 )))
13576 }
13577 });
13578
13579 async move {
13580 request.next().await;
13581 }
13582}
13583
13584fn handle_resolve_completion_request(
13585 cx: &mut EditorLspTestContext,
13586 edits: Option<Vec<(&'static str, &'static str)>>,
13587) -> impl Future<Output = ()> {
13588 let edits = edits.map(|edits| {
13589 edits
13590 .iter()
13591 .map(|(marked_string, new_text)| {
13592 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13593 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13594 lsp::TextEdit::new(replace_range, new_text.to_string())
13595 })
13596 .collect::<Vec<_>>()
13597 });
13598
13599 let mut request =
13600 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13601 let edits = edits.clone();
13602 async move {
13603 Ok(lsp::CompletionItem {
13604 additional_text_edits: edits,
13605 ..Default::default()
13606 })
13607 }
13608 });
13609
13610 async move {
13611 request.next().await;
13612 }
13613}
13614
13615pub(crate) fn update_test_language_settings(
13616 cx: &mut TestAppContext,
13617 f: impl Fn(&mut AllLanguageSettingsContent),
13618) {
13619 _ = cx.update(|cx| {
13620 SettingsStore::update_global(cx, |store, cx| {
13621 store.update_user_settings::<AllLanguageSettings>(cx, f);
13622 });
13623 });
13624}
13625
13626pub(crate) fn update_test_project_settings(
13627 cx: &mut TestAppContext,
13628 f: impl Fn(&mut ProjectSettings),
13629) {
13630 _ = cx.update(|cx| {
13631 SettingsStore::update_global(cx, |store, cx| {
13632 store.update_user_settings::<ProjectSettings>(cx, f);
13633 });
13634 });
13635}
13636
13637pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
13638 _ = cx.update(|cx| {
13639 assets::Assets.load_test_fonts(cx);
13640 let store = SettingsStore::test(cx);
13641 cx.set_global(store);
13642 theme::init(theme::LoadThemes::JustBase, cx);
13643 release_channel::init(SemanticVersion::default(), cx);
13644 client::init_settings(cx);
13645 language::init(cx);
13646 Project::init_settings(cx);
13647 workspace::init_settings(cx);
13648 crate::init(cx);
13649 });
13650
13651 update_test_language_settings(cx, f);
13652}
13653
13654pub(crate) fn rust_lang() -> Arc<Language> {
13655 Arc::new(Language::new(
13656 LanguageConfig {
13657 name: "Rust".into(),
13658 matcher: LanguageMatcher {
13659 path_suffixes: vec!["rs".to_string()],
13660 ..Default::default()
13661 },
13662 ..Default::default()
13663 },
13664 Some(tree_sitter_rust::language()),
13665 ))
13666}
13667
13668#[track_caller]
13669fn assert_hunk_revert(
13670 not_reverted_text_with_selections: &str,
13671 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
13672 expected_reverted_text_with_selections: &str,
13673 base_text: &str,
13674 cx: &mut EditorLspTestContext,
13675) {
13676 cx.set_state(not_reverted_text_with_selections);
13677 cx.update_editor(|editor, cx| {
13678 editor
13679 .buffer()
13680 .read(cx)
13681 .as_singleton()
13682 .unwrap()
13683 .update(cx, |buffer, cx| {
13684 buffer.set_diff_base(Some(base_text.into()), cx);
13685 });
13686 });
13687 cx.executor().run_until_parked();
13688
13689 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
13690 let snapshot = editor.buffer().read(cx).snapshot(cx);
13691 let reverted_hunk_statuses = snapshot
13692 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
13693 .map(|hunk| hunk_status(&hunk))
13694 .collect::<Vec<_>>();
13695
13696 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
13697 reverted_hunk_statuses
13698 });
13699 cx.executor().run_until_parked();
13700 cx.assert_editor_state(expected_reverted_text_with_selections);
13701 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
13702}