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,
24 LanguageName, Override, 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 =
4654 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
4655
4656 cx.assert_editor_state(indoc! {"
4657 ˇbbb
4658 ccc
4659
4660 bbb
4661 ccc
4662 "});
4663 cx.dispatch_action(SelectPrevious::default());
4664 cx.assert_editor_state(indoc! {"
4665 «bbbˇ»
4666 ccc
4667
4668 bbb
4669 ccc
4670 "});
4671 cx.dispatch_action(SelectPrevious::default());
4672 cx.assert_editor_state(indoc! {"
4673 «bbbˇ»
4674 ccc
4675
4676 «bbbˇ»
4677 ccc
4678 "});
4679}
4680
4681#[gpui::test]
4682async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
4683 init_test(cx, |_| {});
4684
4685 let mut cx = EditorTestContext::new(cx).await;
4686 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4687
4688 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4689 .unwrap();
4690 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4691
4692 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4693 .unwrap();
4694 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4695
4696 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4697 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4698
4699 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4700 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4701
4702 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4703 .unwrap();
4704 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
4705
4706 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4707 .unwrap();
4708 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
4709
4710 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4711 .unwrap();
4712 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
4713}
4714
4715#[gpui::test]
4716async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4717 init_test(cx, |_| {});
4718
4719 let mut cx = EditorTestContext::new(cx).await;
4720 cx.set_state(
4721 r#"let foo = 2;
4722lˇet foo = 2;
4723let fooˇ = 2;
4724let foo = 2;
4725let foo = ˇ2;"#,
4726 );
4727
4728 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4729 .unwrap();
4730 cx.assert_editor_state(
4731 r#"let foo = 2;
4732«letˇ» foo = 2;
4733let «fooˇ» = 2;
4734let foo = 2;
4735let foo = «2ˇ»;"#,
4736 );
4737
4738 // noop for multiple selections with different contents
4739 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4740 .unwrap();
4741 cx.assert_editor_state(
4742 r#"let foo = 2;
4743«letˇ» foo = 2;
4744let «fooˇ» = 2;
4745let foo = 2;
4746let foo = «2ˇ»;"#,
4747 );
4748}
4749
4750#[gpui::test]
4751async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
4752 init_test(cx, |_| {});
4753
4754 let mut cx = EditorTestContext::new(cx).await;
4755 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
4756
4757 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4758 .unwrap();
4759 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
4760
4761 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4762 .unwrap();
4763 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
4764
4765 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4766 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
4767
4768 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4769 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
4770
4771 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4772 .unwrap();
4773 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
4774
4775 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4776 .unwrap();
4777 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
4778}
4779
4780#[gpui::test]
4781async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
4782 init_test(cx, |_| {});
4783
4784 let language = Arc::new(Language::new(
4785 LanguageConfig::default(),
4786 Some(tree_sitter_rust::language()),
4787 ));
4788
4789 let text = r#"
4790 use mod1::mod2::{mod3, mod4};
4791
4792 fn fn_1(param1: bool, param2: &str) {
4793 let var1 = "text";
4794 }
4795 "#
4796 .unindent();
4797
4798 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
4799 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4800 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4801
4802 editor
4803 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4804 .await;
4805
4806 editor.update(cx, |view, cx| {
4807 view.change_selections(None, cx, |s| {
4808 s.select_display_ranges([
4809 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
4810 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
4811 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
4812 ]);
4813 });
4814 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4815 });
4816 editor.update(cx, |editor, cx| {
4817 assert_text_with_selections(
4818 editor,
4819 indoc! {r#"
4820 use mod1::mod2::{mod3, «mod4ˇ»};
4821
4822 fn fn_1«ˇ(param1: bool, param2: &str)» {
4823 let var1 = "«textˇ»";
4824 }
4825 "#},
4826 cx,
4827 );
4828 });
4829
4830 editor.update(cx, |view, cx| {
4831 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4832 });
4833 editor.update(cx, |editor, cx| {
4834 assert_text_with_selections(
4835 editor,
4836 indoc! {r#"
4837 use mod1::mod2::«{mod3, mod4}ˇ»;
4838
4839 «ˇfn fn_1(param1: bool, param2: &str) {
4840 let var1 = "text";
4841 }»
4842 "#},
4843 cx,
4844 );
4845 });
4846
4847 editor.update(cx, |view, cx| {
4848 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4849 });
4850 assert_eq!(
4851 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
4852 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
4853 );
4854
4855 // Trying to expand the selected syntax node one more time has no effect.
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 editor.update(cx, |view, cx| {
4865 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4866 });
4867 editor.update(cx, |editor, cx| {
4868 assert_text_with_selections(
4869 editor,
4870 indoc! {r#"
4871 use mod1::mod2::«{mod3, mod4}ˇ»;
4872
4873 «ˇfn fn_1(param1: bool, param2: &str) {
4874 let var1 = "text";
4875 }»
4876 "#},
4877 cx,
4878 );
4879 });
4880
4881 editor.update(cx, |view, cx| {
4882 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4883 });
4884 editor.update(cx, |editor, cx| {
4885 assert_text_with_selections(
4886 editor,
4887 indoc! {r#"
4888 use mod1::mod2::{mod3, «mod4ˇ»};
4889
4890 fn fn_1«ˇ(param1: bool, param2: &str)» {
4891 let var1 = "«textˇ»";
4892 }
4893 "#},
4894 cx,
4895 );
4896 });
4897
4898 editor.update(cx, |view, cx| {
4899 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4900 });
4901 editor.update(cx, |editor, cx| {
4902 assert_text_with_selections(
4903 editor,
4904 indoc! {r#"
4905 use mod1::mod2::{mod3, mo«ˇ»d4};
4906
4907 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
4908 let var1 = "te«ˇ»xt";
4909 }
4910 "#},
4911 cx,
4912 );
4913 });
4914
4915 // Trying to shrink the selected syntax node one more time has no effect.
4916 editor.update(cx, |view, cx| {
4917 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4918 });
4919 editor.update(cx, |editor, cx| {
4920 assert_text_with_selections(
4921 editor,
4922 indoc! {r#"
4923 use mod1::mod2::{mod3, mo«ˇ»d4};
4924
4925 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
4926 let var1 = "te«ˇ»xt";
4927 }
4928 "#},
4929 cx,
4930 );
4931 });
4932
4933 // Ensure that we keep expanding the selection if the larger selection starts or ends within
4934 // a fold.
4935 editor.update(cx, |view, cx| {
4936 view.fold_ranges(
4937 vec![
4938 (
4939 Point::new(0, 21)..Point::new(0, 24),
4940 FoldPlaceholder::test(),
4941 ),
4942 (
4943 Point::new(3, 20)..Point::new(3, 22),
4944 FoldPlaceholder::test(),
4945 ),
4946 ],
4947 true,
4948 cx,
4949 );
4950 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4951 });
4952 editor.update(cx, |editor, cx| {
4953 assert_text_with_selections(
4954 editor,
4955 indoc! {r#"
4956 use mod1::mod2::«{mod3, mod4}ˇ»;
4957
4958 fn fn_1«ˇ(param1: bool, param2: &str)» {
4959 «let var1 = "text";ˇ»
4960 }
4961 "#},
4962 cx,
4963 );
4964 });
4965}
4966
4967#[gpui::test]
4968async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
4969 init_test(cx, |_| {});
4970
4971 let language = Arc::new(
4972 Language::new(
4973 LanguageConfig {
4974 brackets: BracketPairConfig {
4975 pairs: vec![
4976 BracketPair {
4977 start: "{".to_string(),
4978 end: "}".to_string(),
4979 close: false,
4980 surround: false,
4981 newline: true,
4982 },
4983 BracketPair {
4984 start: "(".to_string(),
4985 end: ")".to_string(),
4986 close: false,
4987 surround: false,
4988 newline: true,
4989 },
4990 ],
4991 ..Default::default()
4992 },
4993 ..Default::default()
4994 },
4995 Some(tree_sitter_rust::language()),
4996 )
4997 .with_indents_query(
4998 r#"
4999 (_ "(" ")" @end) @indent
5000 (_ "{" "}" @end) @indent
5001 "#,
5002 )
5003 .unwrap(),
5004 );
5005
5006 let text = "fn a() {}";
5007
5008 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5009 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5010 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5011 editor
5012 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5013 .await;
5014
5015 editor.update(cx, |editor, cx| {
5016 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5017 editor.newline(&Newline, cx);
5018 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5019 assert_eq!(
5020 editor.selections.ranges(cx),
5021 &[
5022 Point::new(1, 4)..Point::new(1, 4),
5023 Point::new(3, 4)..Point::new(3, 4),
5024 Point::new(5, 0)..Point::new(5, 0)
5025 ]
5026 );
5027 });
5028}
5029
5030#[gpui::test]
5031async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5032 init_test(cx, |_| {});
5033
5034 let mut cx = EditorTestContext::new(cx).await;
5035
5036 let language = Arc::new(Language::new(
5037 LanguageConfig {
5038 brackets: BracketPairConfig {
5039 pairs: vec![
5040 BracketPair {
5041 start: "{".to_string(),
5042 end: "}".to_string(),
5043 close: true,
5044 surround: true,
5045 newline: true,
5046 },
5047 BracketPair {
5048 start: "(".to_string(),
5049 end: ")".to_string(),
5050 close: true,
5051 surround: true,
5052 newline: true,
5053 },
5054 BracketPair {
5055 start: "/*".to_string(),
5056 end: " */".to_string(),
5057 close: true,
5058 surround: true,
5059 newline: true,
5060 },
5061 BracketPair {
5062 start: "[".to_string(),
5063 end: "]".to_string(),
5064 close: false,
5065 surround: false,
5066 newline: true,
5067 },
5068 BracketPair {
5069 start: "\"".to_string(),
5070 end: "\"".to_string(),
5071 close: true,
5072 surround: true,
5073 newline: false,
5074 },
5075 BracketPair {
5076 start: "<".to_string(),
5077 end: ">".to_string(),
5078 close: false,
5079 surround: true,
5080 newline: true,
5081 },
5082 ],
5083 ..Default::default()
5084 },
5085 autoclose_before: "})]".to_string(),
5086 ..Default::default()
5087 },
5088 Some(tree_sitter_rust::language()),
5089 ));
5090
5091 cx.language_registry().add(language.clone());
5092 cx.update_buffer(|buffer, cx| {
5093 buffer.set_language(Some(language), cx);
5094 });
5095
5096 cx.set_state(
5097 &r#"
5098 🏀ˇ
5099 εˇ
5100 ❤️ˇ
5101 "#
5102 .unindent(),
5103 );
5104
5105 // autoclose multiple nested brackets at multiple cursors
5106 cx.update_editor(|view, cx| {
5107 view.handle_input("{", cx);
5108 view.handle_input("{", cx);
5109 view.handle_input("{", cx);
5110 });
5111 cx.assert_editor_state(
5112 &"
5113 🏀{{{ˇ}}}
5114 ε{{{ˇ}}}
5115 ❤️{{{ˇ}}}
5116 "
5117 .unindent(),
5118 );
5119
5120 // insert a different closing bracket
5121 cx.update_editor(|view, cx| {
5122 view.handle_input(")", cx);
5123 });
5124 cx.assert_editor_state(
5125 &"
5126 🏀{{{)ˇ}}}
5127 ε{{{)ˇ}}}
5128 ❤️{{{)ˇ}}}
5129 "
5130 .unindent(),
5131 );
5132
5133 // skip over the auto-closed brackets when typing a closing bracket
5134 cx.update_editor(|view, cx| {
5135 view.move_right(&MoveRight, cx);
5136 view.handle_input("}", cx);
5137 view.handle_input("}", cx);
5138 view.handle_input("}", cx);
5139 });
5140 cx.assert_editor_state(
5141 &"
5142 🏀{{{)}}}}ˇ
5143 ε{{{)}}}}ˇ
5144 ❤️{{{)}}}}ˇ
5145 "
5146 .unindent(),
5147 );
5148
5149 // autoclose multi-character pairs
5150 cx.set_state(
5151 &"
5152 ˇ
5153 ˇ
5154 "
5155 .unindent(),
5156 );
5157 cx.update_editor(|view, cx| {
5158 view.handle_input("/", cx);
5159 view.handle_input("*", cx);
5160 });
5161 cx.assert_editor_state(
5162 &"
5163 /*ˇ */
5164 /*ˇ */
5165 "
5166 .unindent(),
5167 );
5168
5169 // one cursor autocloses a multi-character pair, one cursor
5170 // does not autoclose.
5171 cx.set_state(
5172 &"
5173 /ˇ
5174 ˇ
5175 "
5176 .unindent(),
5177 );
5178 cx.update_editor(|view, cx| view.handle_input("*", cx));
5179 cx.assert_editor_state(
5180 &"
5181 /*ˇ */
5182 *ˇ
5183 "
5184 .unindent(),
5185 );
5186
5187 // Don't autoclose if the next character isn't whitespace and isn't
5188 // listed in the language's "autoclose_before" section.
5189 cx.set_state("ˇa b");
5190 cx.update_editor(|view, cx| view.handle_input("{", cx));
5191 cx.assert_editor_state("{ˇa b");
5192
5193 // Don't autoclose if `close` is false for the bracket pair
5194 cx.set_state("ˇ");
5195 cx.update_editor(|view, cx| view.handle_input("[", cx));
5196 cx.assert_editor_state("[ˇ");
5197
5198 // Surround with brackets if text is selected
5199 cx.set_state("«aˇ» b");
5200 cx.update_editor(|view, cx| view.handle_input("{", cx));
5201 cx.assert_editor_state("{«aˇ»} b");
5202
5203 // Autclose pair where the start and end characters are the same
5204 cx.set_state("aˇ");
5205 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5206 cx.assert_editor_state("a\"ˇ\"");
5207 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5208 cx.assert_editor_state("a\"\"ˇ");
5209
5210 // Don't autoclose pair if autoclose is disabled
5211 cx.set_state("ˇ");
5212 cx.update_editor(|view, cx| view.handle_input("<", cx));
5213 cx.assert_editor_state("<ˇ");
5214
5215 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5216 cx.set_state("«aˇ» b");
5217 cx.update_editor(|view, cx| view.handle_input("<", cx));
5218 cx.assert_editor_state("<«aˇ»> b");
5219}
5220
5221#[gpui::test]
5222async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5223 init_test(cx, |settings| {
5224 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5225 });
5226
5227 let mut cx = EditorTestContext::new(cx).await;
5228
5229 let language = Arc::new(Language::new(
5230 LanguageConfig {
5231 brackets: BracketPairConfig {
5232 pairs: vec![
5233 BracketPair {
5234 start: "{".to_string(),
5235 end: "}".to_string(),
5236 close: true,
5237 surround: true,
5238 newline: true,
5239 },
5240 BracketPair {
5241 start: "(".to_string(),
5242 end: ")".to_string(),
5243 close: true,
5244 surround: true,
5245 newline: true,
5246 },
5247 BracketPair {
5248 start: "[".to_string(),
5249 end: "]".to_string(),
5250 close: false,
5251 surround: false,
5252 newline: true,
5253 },
5254 ],
5255 ..Default::default()
5256 },
5257 autoclose_before: "})]".to_string(),
5258 ..Default::default()
5259 },
5260 Some(tree_sitter_rust::language()),
5261 ));
5262
5263 cx.language_registry().add(language.clone());
5264 cx.update_buffer(|buffer, cx| {
5265 buffer.set_language(Some(language), cx);
5266 });
5267
5268 cx.set_state(
5269 &"
5270 ˇ
5271 ˇ
5272 ˇ
5273 "
5274 .unindent(),
5275 );
5276
5277 // ensure only matching closing brackets are skipped over
5278 cx.update_editor(|view, cx| {
5279 view.handle_input("}", cx);
5280 view.move_left(&MoveLeft, cx);
5281 view.handle_input(")", cx);
5282 view.move_left(&MoveLeft, cx);
5283 });
5284 cx.assert_editor_state(
5285 &"
5286 ˇ)}
5287 ˇ)}
5288 ˇ)}
5289 "
5290 .unindent(),
5291 );
5292
5293 // skip-over closing brackets at multiple cursors
5294 cx.update_editor(|view, cx| {
5295 view.handle_input(")", cx);
5296 view.handle_input("}", cx);
5297 });
5298 cx.assert_editor_state(
5299 &"
5300 )}ˇ
5301 )}ˇ
5302 )}ˇ
5303 "
5304 .unindent(),
5305 );
5306
5307 // ignore non-close brackets
5308 cx.update_editor(|view, cx| {
5309 view.handle_input("]", cx);
5310 view.move_left(&MoveLeft, cx);
5311 view.handle_input("]", cx);
5312 });
5313 cx.assert_editor_state(
5314 &"
5315 )}]ˇ]
5316 )}]ˇ]
5317 )}]ˇ]
5318 "
5319 .unindent(),
5320 );
5321}
5322
5323#[gpui::test]
5324async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5325 init_test(cx, |_| {});
5326
5327 let mut cx = EditorTestContext::new(cx).await;
5328
5329 let html_language = Arc::new(
5330 Language::new(
5331 LanguageConfig {
5332 name: "HTML".into(),
5333 brackets: BracketPairConfig {
5334 pairs: vec![
5335 BracketPair {
5336 start: "<".into(),
5337 end: ">".into(),
5338 close: true,
5339 ..Default::default()
5340 },
5341 BracketPair {
5342 start: "{".into(),
5343 end: "}".into(),
5344 close: true,
5345 ..Default::default()
5346 },
5347 BracketPair {
5348 start: "(".into(),
5349 end: ")".into(),
5350 close: true,
5351 ..Default::default()
5352 },
5353 ],
5354 ..Default::default()
5355 },
5356 autoclose_before: "})]>".into(),
5357 ..Default::default()
5358 },
5359 Some(tree_sitter_html::language()),
5360 )
5361 .with_injection_query(
5362 r#"
5363 (script_element
5364 (raw_text) @content
5365 (#set! "language" "javascript"))
5366 "#,
5367 )
5368 .unwrap(),
5369 );
5370
5371 let javascript_language = Arc::new(Language::new(
5372 LanguageConfig {
5373 name: "JavaScript".into(),
5374 brackets: BracketPairConfig {
5375 pairs: vec![
5376 BracketPair {
5377 start: "/*".into(),
5378 end: " */".into(),
5379 close: true,
5380 ..Default::default()
5381 },
5382 BracketPair {
5383 start: "{".into(),
5384 end: "}".into(),
5385 close: true,
5386 ..Default::default()
5387 },
5388 BracketPair {
5389 start: "(".into(),
5390 end: ")".into(),
5391 close: true,
5392 ..Default::default()
5393 },
5394 ],
5395 ..Default::default()
5396 },
5397 autoclose_before: "})]>".into(),
5398 ..Default::default()
5399 },
5400 Some(tree_sitter_typescript::language_tsx()),
5401 ));
5402
5403 cx.language_registry().add(html_language.clone());
5404 cx.language_registry().add(javascript_language.clone());
5405
5406 cx.update_buffer(|buffer, cx| {
5407 buffer.set_language(Some(html_language), cx);
5408 });
5409
5410 cx.set_state(
5411 &r#"
5412 <body>ˇ
5413 <script>
5414 var x = 1;ˇ
5415 </script>
5416 </body>ˇ
5417 "#
5418 .unindent(),
5419 );
5420
5421 // Precondition: different languages are active at different locations.
5422 cx.update_editor(|editor, cx| {
5423 let snapshot = editor.snapshot(cx);
5424 let cursors = editor.selections.ranges::<usize>(cx);
5425 let languages = cursors
5426 .iter()
5427 .map(|c| snapshot.language_at(c.start).unwrap().name())
5428 .collect::<Vec<_>>();
5429 assert_eq!(
5430 languages,
5431 &["HTML".into(), "JavaScript".into(), "HTML".into()]
5432 );
5433 });
5434
5435 // Angle brackets autoclose in HTML, but not JavaScript.
5436 cx.update_editor(|editor, cx| {
5437 editor.handle_input("<", cx);
5438 editor.handle_input("a", cx);
5439 });
5440 cx.assert_editor_state(
5441 &r#"
5442 <body><aˇ>
5443 <script>
5444 var x = 1;<aˇ
5445 </script>
5446 </body><aˇ>
5447 "#
5448 .unindent(),
5449 );
5450
5451 // Curly braces and parens autoclose in both HTML and JavaScript.
5452 cx.update_editor(|editor, cx| {
5453 editor.handle_input(" b=", cx);
5454 editor.handle_input("{", cx);
5455 editor.handle_input("c", cx);
5456 editor.handle_input("(", cx);
5457 });
5458 cx.assert_editor_state(
5459 &r#"
5460 <body><a b={c(ˇ)}>
5461 <script>
5462 var x = 1;<a b={c(ˇ)}
5463 </script>
5464 </body><a b={c(ˇ)}>
5465 "#
5466 .unindent(),
5467 );
5468
5469 // Brackets that were already autoclosed are skipped.
5470 cx.update_editor(|editor, cx| {
5471 editor.handle_input(")", cx);
5472 editor.handle_input("d", cx);
5473 editor.handle_input("}", cx);
5474 });
5475 cx.assert_editor_state(
5476 &r#"
5477 <body><a b={c()d}ˇ>
5478 <script>
5479 var x = 1;<a b={c()d}ˇ
5480 </script>
5481 </body><a b={c()d}ˇ>
5482 "#
5483 .unindent(),
5484 );
5485 cx.update_editor(|editor, cx| {
5486 editor.handle_input(">", cx);
5487 });
5488 cx.assert_editor_state(
5489 &r#"
5490 <body><a b={c()d}>ˇ
5491 <script>
5492 var x = 1;<a b={c()d}>ˇ
5493 </script>
5494 </body><a b={c()d}>ˇ
5495 "#
5496 .unindent(),
5497 );
5498
5499 // Reset
5500 cx.set_state(
5501 &r#"
5502 <body>ˇ
5503 <script>
5504 var x = 1;ˇ
5505 </script>
5506 </body>ˇ
5507 "#
5508 .unindent(),
5509 );
5510
5511 cx.update_editor(|editor, cx| {
5512 editor.handle_input("<", cx);
5513 });
5514 cx.assert_editor_state(
5515 &r#"
5516 <body><ˇ>
5517 <script>
5518 var x = 1;<ˇ
5519 </script>
5520 </body><ˇ>
5521 "#
5522 .unindent(),
5523 );
5524
5525 // When backspacing, the closing angle brackets are removed.
5526 cx.update_editor(|editor, cx| {
5527 editor.backspace(&Backspace, cx);
5528 });
5529 cx.assert_editor_state(
5530 &r#"
5531 <body>ˇ
5532 <script>
5533 var x = 1;ˇ
5534 </script>
5535 </body>ˇ
5536 "#
5537 .unindent(),
5538 );
5539
5540 // Block comments autoclose in JavaScript, but not HTML.
5541 cx.update_editor(|editor, cx| {
5542 editor.handle_input("/", cx);
5543 editor.handle_input("*", cx);
5544 });
5545 cx.assert_editor_state(
5546 &r#"
5547 <body>/*ˇ
5548 <script>
5549 var x = 1;/*ˇ */
5550 </script>
5551 </body>/*ˇ
5552 "#
5553 .unindent(),
5554 );
5555}
5556
5557#[gpui::test]
5558async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
5559 init_test(cx, |_| {});
5560
5561 let mut cx = EditorTestContext::new(cx).await;
5562
5563 let rust_language = Arc::new(
5564 Language::new(
5565 LanguageConfig {
5566 name: "Rust".into(),
5567 brackets: serde_json::from_value(json!([
5568 { "start": "{", "end": "}", "close": true, "newline": true },
5569 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
5570 ]))
5571 .unwrap(),
5572 autoclose_before: "})]>".into(),
5573 ..Default::default()
5574 },
5575 Some(tree_sitter_rust::language()),
5576 )
5577 .with_override_query("(string_literal) @string")
5578 .unwrap(),
5579 );
5580
5581 cx.language_registry().add(rust_language.clone());
5582 cx.update_buffer(|buffer, cx| {
5583 buffer.set_language(Some(rust_language), cx);
5584 });
5585
5586 cx.set_state(
5587 &r#"
5588 let x = ˇ
5589 "#
5590 .unindent(),
5591 );
5592
5593 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
5594 cx.update_editor(|editor, cx| {
5595 editor.handle_input("\"", cx);
5596 });
5597 cx.assert_editor_state(
5598 &r#"
5599 let x = "ˇ"
5600 "#
5601 .unindent(),
5602 );
5603
5604 // Inserting another quotation mark. The cursor moves across the existing
5605 // automatically-inserted quotation mark.
5606 cx.update_editor(|editor, cx| {
5607 editor.handle_input("\"", cx);
5608 });
5609 cx.assert_editor_state(
5610 &r#"
5611 let x = ""ˇ
5612 "#
5613 .unindent(),
5614 );
5615
5616 // Reset
5617 cx.set_state(
5618 &r#"
5619 let x = ˇ
5620 "#
5621 .unindent(),
5622 );
5623
5624 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
5625 cx.update_editor(|editor, cx| {
5626 editor.handle_input("\"", cx);
5627 editor.handle_input(" ", cx);
5628 editor.move_left(&Default::default(), cx);
5629 editor.handle_input("\\", cx);
5630 editor.handle_input("\"", cx);
5631 });
5632 cx.assert_editor_state(
5633 &r#"
5634 let x = "\"ˇ "
5635 "#
5636 .unindent(),
5637 );
5638
5639 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
5640 // mark. Nothing is inserted.
5641 cx.update_editor(|editor, cx| {
5642 editor.move_right(&Default::default(), cx);
5643 editor.handle_input("\"", cx);
5644 });
5645 cx.assert_editor_state(
5646 &r#"
5647 let x = "\" "ˇ
5648 "#
5649 .unindent(),
5650 );
5651}
5652
5653#[gpui::test]
5654async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
5655 init_test(cx, |_| {});
5656
5657 let language = Arc::new(Language::new(
5658 LanguageConfig {
5659 brackets: BracketPairConfig {
5660 pairs: vec![
5661 BracketPair {
5662 start: "{".to_string(),
5663 end: "}".to_string(),
5664 close: true,
5665 surround: true,
5666 newline: true,
5667 },
5668 BracketPair {
5669 start: "/* ".to_string(),
5670 end: "*/".to_string(),
5671 close: true,
5672 surround: true,
5673 ..Default::default()
5674 },
5675 ],
5676 ..Default::default()
5677 },
5678 ..Default::default()
5679 },
5680 Some(tree_sitter_rust::language()),
5681 ));
5682
5683 let text = r#"
5684 a
5685 b
5686 c
5687 "#
5688 .unindent();
5689
5690 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5691 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5692 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5693 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5694 .await;
5695
5696 view.update(cx, |view, cx| {
5697 view.change_selections(None, cx, |s| {
5698 s.select_display_ranges([
5699 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5700 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5701 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
5702 ])
5703 });
5704
5705 view.handle_input("{", cx);
5706 view.handle_input("{", cx);
5707 view.handle_input("{", cx);
5708 assert_eq!(
5709 view.text(cx),
5710 "
5711 {{{a}}}
5712 {{{b}}}
5713 {{{c}}}
5714 "
5715 .unindent()
5716 );
5717 assert_eq!(
5718 view.selections.display_ranges(cx),
5719 [
5720 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
5721 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
5722 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
5723 ]
5724 );
5725
5726 view.undo(&Undo, cx);
5727 view.undo(&Undo, cx);
5728 view.undo(&Undo, cx);
5729 assert_eq!(
5730 view.text(cx),
5731 "
5732 a
5733 b
5734 c
5735 "
5736 .unindent()
5737 );
5738 assert_eq!(
5739 view.selections.display_ranges(cx),
5740 [
5741 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5742 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5743 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
5744 ]
5745 );
5746
5747 // Ensure inserting the first character of a multi-byte bracket pair
5748 // doesn't surround the selections with the bracket.
5749 view.handle_input("/", cx);
5750 assert_eq!(
5751 view.text(cx),
5752 "
5753 /
5754 /
5755 /
5756 "
5757 .unindent()
5758 );
5759 assert_eq!(
5760 view.selections.display_ranges(cx),
5761 [
5762 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
5763 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
5764 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
5765 ]
5766 );
5767
5768 view.undo(&Undo, cx);
5769 assert_eq!(
5770 view.text(cx),
5771 "
5772 a
5773 b
5774 c
5775 "
5776 .unindent()
5777 );
5778 assert_eq!(
5779 view.selections.display_ranges(cx),
5780 [
5781 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5782 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5783 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
5784 ]
5785 );
5786
5787 // Ensure inserting the last character of a multi-byte bracket pair
5788 // doesn't surround the selections with the bracket.
5789 view.handle_input("*", cx);
5790 assert_eq!(
5791 view.text(cx),
5792 "
5793 *
5794 *
5795 *
5796 "
5797 .unindent()
5798 );
5799 assert_eq!(
5800 view.selections.display_ranges(cx),
5801 [
5802 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
5803 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
5804 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
5805 ]
5806 );
5807 });
5808}
5809
5810#[gpui::test]
5811async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
5812 init_test(cx, |_| {});
5813
5814 let language = Arc::new(Language::new(
5815 LanguageConfig {
5816 brackets: BracketPairConfig {
5817 pairs: vec![BracketPair {
5818 start: "{".to_string(),
5819 end: "}".to_string(),
5820 close: true,
5821 surround: true,
5822 newline: true,
5823 }],
5824 ..Default::default()
5825 },
5826 autoclose_before: "}".to_string(),
5827 ..Default::default()
5828 },
5829 Some(tree_sitter_rust::language()),
5830 ));
5831
5832 let text = r#"
5833 a
5834 b
5835 c
5836 "#
5837 .unindent();
5838
5839 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5840 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5841 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5842 editor
5843 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5844 .await;
5845
5846 editor.update(cx, |editor, cx| {
5847 editor.change_selections(None, cx, |s| {
5848 s.select_ranges([
5849 Point::new(0, 1)..Point::new(0, 1),
5850 Point::new(1, 1)..Point::new(1, 1),
5851 Point::new(2, 1)..Point::new(2, 1),
5852 ])
5853 });
5854
5855 editor.handle_input("{", cx);
5856 editor.handle_input("{", cx);
5857 editor.handle_input("_", cx);
5858 assert_eq!(
5859 editor.text(cx),
5860 "
5861 a{{_}}
5862 b{{_}}
5863 c{{_}}
5864 "
5865 .unindent()
5866 );
5867 assert_eq!(
5868 editor.selections.ranges::<Point>(cx),
5869 [
5870 Point::new(0, 4)..Point::new(0, 4),
5871 Point::new(1, 4)..Point::new(1, 4),
5872 Point::new(2, 4)..Point::new(2, 4)
5873 ]
5874 );
5875
5876 editor.backspace(&Default::default(), cx);
5877 editor.backspace(&Default::default(), cx);
5878 assert_eq!(
5879 editor.text(cx),
5880 "
5881 a{}
5882 b{}
5883 c{}
5884 "
5885 .unindent()
5886 );
5887 assert_eq!(
5888 editor.selections.ranges::<Point>(cx),
5889 [
5890 Point::new(0, 2)..Point::new(0, 2),
5891 Point::new(1, 2)..Point::new(1, 2),
5892 Point::new(2, 2)..Point::new(2, 2)
5893 ]
5894 );
5895
5896 editor.delete_to_previous_word_start(&Default::default(), cx);
5897 assert_eq!(
5898 editor.text(cx),
5899 "
5900 a
5901 b
5902 c
5903 "
5904 .unindent()
5905 );
5906 assert_eq!(
5907 editor.selections.ranges::<Point>(cx),
5908 [
5909 Point::new(0, 1)..Point::new(0, 1),
5910 Point::new(1, 1)..Point::new(1, 1),
5911 Point::new(2, 1)..Point::new(2, 1)
5912 ]
5913 );
5914 });
5915}
5916
5917#[gpui::test]
5918async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
5919 init_test(cx, |settings| {
5920 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5921 });
5922
5923 let mut cx = EditorTestContext::new(cx).await;
5924
5925 let language = Arc::new(Language::new(
5926 LanguageConfig {
5927 brackets: BracketPairConfig {
5928 pairs: vec![
5929 BracketPair {
5930 start: "{".to_string(),
5931 end: "}".to_string(),
5932 close: true,
5933 surround: true,
5934 newline: true,
5935 },
5936 BracketPair {
5937 start: "(".to_string(),
5938 end: ")".to_string(),
5939 close: true,
5940 surround: true,
5941 newline: true,
5942 },
5943 BracketPair {
5944 start: "[".to_string(),
5945 end: "]".to_string(),
5946 close: false,
5947 surround: true,
5948 newline: true,
5949 },
5950 ],
5951 ..Default::default()
5952 },
5953 autoclose_before: "})]".to_string(),
5954 ..Default::default()
5955 },
5956 Some(tree_sitter_rust::language()),
5957 ));
5958
5959 cx.language_registry().add(language.clone());
5960 cx.update_buffer(|buffer, cx| {
5961 buffer.set_language(Some(language), cx);
5962 });
5963
5964 cx.set_state(
5965 &"
5966 {(ˇ)}
5967 [[ˇ]]
5968 {(ˇ)}
5969 "
5970 .unindent(),
5971 );
5972
5973 cx.update_editor(|view, cx| {
5974 view.backspace(&Default::default(), cx);
5975 view.backspace(&Default::default(), cx);
5976 });
5977
5978 cx.assert_editor_state(
5979 &"
5980 ˇ
5981 ˇ]]
5982 ˇ
5983 "
5984 .unindent(),
5985 );
5986
5987 cx.update_editor(|view, cx| {
5988 view.handle_input("{", cx);
5989 view.handle_input("{", cx);
5990 view.move_right(&MoveRight, cx);
5991 view.move_right(&MoveRight, cx);
5992 view.move_left(&MoveLeft, cx);
5993 view.move_left(&MoveLeft, cx);
5994 view.backspace(&Default::default(), cx);
5995 });
5996
5997 cx.assert_editor_state(
5998 &"
5999 {ˇ}
6000 {ˇ}]]
6001 {ˇ}
6002 "
6003 .unindent(),
6004 );
6005
6006 cx.update_editor(|view, cx| {
6007 view.backspace(&Default::default(), cx);
6008 });
6009
6010 cx.assert_editor_state(
6011 &"
6012 ˇ
6013 ˇ]]
6014 ˇ
6015 "
6016 .unindent(),
6017 );
6018}
6019
6020#[gpui::test]
6021async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6022 init_test(cx, |_| {});
6023
6024 let language = Arc::new(Language::new(
6025 LanguageConfig::default(),
6026 Some(tree_sitter_rust::language()),
6027 ));
6028
6029 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6030 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6031 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6032 editor
6033 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6034 .await;
6035
6036 editor.update(cx, |editor, cx| {
6037 editor.set_auto_replace_emoji_shortcode(true);
6038
6039 editor.handle_input("Hello ", cx);
6040 editor.handle_input(":wave", cx);
6041 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6042
6043 editor.handle_input(":", cx);
6044 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6045
6046 editor.handle_input(" :smile", cx);
6047 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6048
6049 editor.handle_input(":", cx);
6050 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6051
6052 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6053 editor.handle_input(":wave", cx);
6054 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6055
6056 editor.handle_input(":", cx);
6057 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6058
6059 editor.handle_input(":1", cx);
6060 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6061
6062 editor.handle_input(":", cx);
6063 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6064
6065 // Ensure shortcode does not get replaced when it is part of a word
6066 editor.handle_input(" Test:wave", cx);
6067 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6068
6069 editor.handle_input(":", cx);
6070 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6071
6072 editor.set_auto_replace_emoji_shortcode(false);
6073
6074 // Ensure shortcode does not get replaced when auto replace is off
6075 editor.handle_input(" :wave", cx);
6076 assert_eq!(
6077 editor.text(cx),
6078 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6079 );
6080
6081 editor.handle_input(":", cx);
6082 assert_eq!(
6083 editor.text(cx),
6084 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6085 );
6086 });
6087}
6088
6089#[gpui::test]
6090async fn test_snippets(cx: &mut gpui::TestAppContext) {
6091 init_test(cx, |_| {});
6092
6093 let (text, insertion_ranges) = marked_text_ranges(
6094 indoc! {"
6095 a.ˇ b
6096 a.ˇ b
6097 a.ˇ b
6098 "},
6099 false,
6100 );
6101
6102 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6103 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6104
6105 editor.update(cx, |editor, cx| {
6106 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6107
6108 editor
6109 .insert_snippet(&insertion_ranges, snippet, cx)
6110 .unwrap();
6111
6112 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6113 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6114 assert_eq!(editor.text(cx), expected_text);
6115 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6116 }
6117
6118 assert(
6119 editor,
6120 cx,
6121 indoc! {"
6122 a.f(«one», two, «three») b
6123 a.f(«one», two, «three») b
6124 a.f(«one», two, «three») b
6125 "},
6126 );
6127
6128 // Can't move earlier than the first tab stop
6129 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6130 assert(
6131 editor,
6132 cx,
6133 indoc! {"
6134 a.f(«one», two, «three») b
6135 a.f(«one», two, «three») b
6136 a.f(«one», two, «three») b
6137 "},
6138 );
6139
6140 assert!(editor.move_to_next_snippet_tabstop(cx));
6141 assert(
6142 editor,
6143 cx,
6144 indoc! {"
6145 a.f(one, «two», three) b
6146 a.f(one, «two», three) b
6147 a.f(one, «two», three) b
6148 "},
6149 );
6150
6151 editor.move_to_prev_snippet_tabstop(cx);
6152 assert(
6153 editor,
6154 cx,
6155 indoc! {"
6156 a.f(«one», two, «three») b
6157 a.f(«one», two, «three») b
6158 a.f(«one», two, «three») b
6159 "},
6160 );
6161
6162 assert!(editor.move_to_next_snippet_tabstop(cx));
6163 assert(
6164 editor,
6165 cx,
6166 indoc! {"
6167 a.f(one, «two», three) b
6168 a.f(one, «two», three) b
6169 a.f(one, «two», three) b
6170 "},
6171 );
6172 assert!(editor.move_to_next_snippet_tabstop(cx));
6173 assert(
6174 editor,
6175 cx,
6176 indoc! {"
6177 a.f(one, two, three)ˇ b
6178 a.f(one, two, three)ˇ b
6179 a.f(one, two, three)ˇ b
6180 "},
6181 );
6182
6183 // As soon as the last tab stop is reached, snippet state is gone
6184 editor.move_to_prev_snippet_tabstop(cx);
6185 assert(
6186 editor,
6187 cx,
6188 indoc! {"
6189 a.f(one, two, three)ˇ b
6190 a.f(one, two, three)ˇ b
6191 a.f(one, two, three)ˇ b
6192 "},
6193 );
6194 });
6195}
6196
6197#[gpui::test]
6198async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6199 init_test(cx, |_| {});
6200
6201 let fs = FakeFs::new(cx.executor());
6202 fs.insert_file("/file.rs", Default::default()).await;
6203
6204 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6205
6206 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6207 language_registry.add(rust_lang());
6208 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6209 "Rust",
6210 FakeLspAdapter {
6211 capabilities: lsp::ServerCapabilities {
6212 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6213 ..Default::default()
6214 },
6215 ..Default::default()
6216 },
6217 );
6218
6219 let buffer = project
6220 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6221 .await
6222 .unwrap();
6223
6224 cx.executor().start_waiting();
6225 let fake_server = fake_servers.next().await.unwrap();
6226
6227 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6228 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6229 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6230 assert!(cx.read(|cx| editor.is_dirty(cx)));
6231
6232 let save = editor
6233 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6234 .unwrap();
6235 fake_server
6236 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6237 assert_eq!(
6238 params.text_document.uri,
6239 lsp::Url::from_file_path("/file.rs").unwrap()
6240 );
6241 assert_eq!(params.options.tab_size, 4);
6242 Ok(Some(vec![lsp::TextEdit::new(
6243 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6244 ", ".to_string(),
6245 )]))
6246 })
6247 .next()
6248 .await;
6249 cx.executor().start_waiting();
6250 save.await;
6251
6252 assert_eq!(
6253 editor.update(cx, |editor, cx| editor.text(cx)),
6254 "one, two\nthree\n"
6255 );
6256 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6257
6258 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6259 assert!(cx.read(|cx| editor.is_dirty(cx)));
6260
6261 // Ensure we can still save even if formatting hangs.
6262 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6263 assert_eq!(
6264 params.text_document.uri,
6265 lsp::Url::from_file_path("/file.rs").unwrap()
6266 );
6267 futures::future::pending::<()>().await;
6268 unreachable!()
6269 });
6270 let save = editor
6271 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6272 .unwrap();
6273 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6274 cx.executor().start_waiting();
6275 save.await;
6276 assert_eq!(
6277 editor.update(cx, |editor, cx| editor.text(cx)),
6278 "one\ntwo\nthree\n"
6279 );
6280 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6281
6282 // For non-dirty buffer, no formatting request should be sent
6283 let save = editor
6284 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6285 .unwrap();
6286 let _pending_format_request = fake_server
6287 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6288 panic!("Should not be invoked on non-dirty buffer");
6289 })
6290 .next();
6291 cx.executor().start_waiting();
6292 save.await;
6293
6294 // Set rust language override and assert overridden tabsize is sent to language server
6295 update_test_language_settings(cx, |settings| {
6296 settings.languages.insert(
6297 "Rust".into(),
6298 LanguageSettingsContent {
6299 tab_size: NonZeroU32::new(8),
6300 ..Default::default()
6301 },
6302 );
6303 });
6304
6305 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6306 assert!(cx.read(|cx| editor.is_dirty(cx)));
6307 let save = editor
6308 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6309 .unwrap();
6310 fake_server
6311 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6312 assert_eq!(
6313 params.text_document.uri,
6314 lsp::Url::from_file_path("/file.rs").unwrap()
6315 );
6316 assert_eq!(params.options.tab_size, 8);
6317 Ok(Some(vec![]))
6318 })
6319 .next()
6320 .await;
6321 cx.executor().start_waiting();
6322 save.await;
6323}
6324
6325#[gpui::test]
6326async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6327 init_test(cx, |_| {});
6328
6329 let cols = 4;
6330 let rows = 10;
6331 let sample_text_1 = sample_text(rows, cols, 'a');
6332 assert_eq!(
6333 sample_text_1,
6334 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6335 );
6336 let sample_text_2 = sample_text(rows, cols, 'l');
6337 assert_eq!(
6338 sample_text_2,
6339 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6340 );
6341 let sample_text_3 = sample_text(rows, cols, 'v');
6342 assert_eq!(
6343 sample_text_3,
6344 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6345 );
6346
6347 let fs = FakeFs::new(cx.executor());
6348 fs.insert_tree(
6349 "/a",
6350 json!({
6351 "main.rs": sample_text_1,
6352 "other.rs": sample_text_2,
6353 "lib.rs": sample_text_3,
6354 }),
6355 )
6356 .await;
6357
6358 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6359 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6360 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6361
6362 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6363 language_registry.add(rust_lang());
6364 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6365 "Rust",
6366 FakeLspAdapter {
6367 capabilities: lsp::ServerCapabilities {
6368 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6369 ..Default::default()
6370 },
6371 ..Default::default()
6372 },
6373 );
6374
6375 let worktree = project.update(cx, |project, cx| {
6376 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
6377 assert_eq!(worktrees.len(), 1);
6378 worktrees.pop().unwrap()
6379 });
6380 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6381
6382 let buffer_1 = project
6383 .update(cx, |project, cx| {
6384 project.open_buffer((worktree_id, "main.rs"), cx)
6385 })
6386 .await
6387 .unwrap();
6388 let buffer_2 = project
6389 .update(cx, |project, cx| {
6390 project.open_buffer((worktree_id, "other.rs"), cx)
6391 })
6392 .await
6393 .unwrap();
6394 let buffer_3 = project
6395 .update(cx, |project, cx| {
6396 project.open_buffer((worktree_id, "lib.rs"), cx)
6397 })
6398 .await
6399 .unwrap();
6400
6401 let multi_buffer = cx.new_model(|cx| {
6402 let mut multi_buffer = MultiBuffer::new(0, ReadWrite);
6403 multi_buffer.push_excerpts(
6404 buffer_1.clone(),
6405 [
6406 ExcerptRange {
6407 context: Point::new(0, 0)..Point::new(3, 0),
6408 primary: None,
6409 },
6410 ExcerptRange {
6411 context: Point::new(5, 0)..Point::new(7, 0),
6412 primary: None,
6413 },
6414 ExcerptRange {
6415 context: Point::new(9, 0)..Point::new(10, 4),
6416 primary: None,
6417 },
6418 ],
6419 cx,
6420 );
6421 multi_buffer.push_excerpts(
6422 buffer_2.clone(),
6423 [
6424 ExcerptRange {
6425 context: Point::new(0, 0)..Point::new(3, 0),
6426 primary: None,
6427 },
6428 ExcerptRange {
6429 context: Point::new(5, 0)..Point::new(7, 0),
6430 primary: None,
6431 },
6432 ExcerptRange {
6433 context: Point::new(9, 0)..Point::new(10, 4),
6434 primary: None,
6435 },
6436 ],
6437 cx,
6438 );
6439 multi_buffer.push_excerpts(
6440 buffer_3.clone(),
6441 [
6442 ExcerptRange {
6443 context: Point::new(0, 0)..Point::new(3, 0),
6444 primary: None,
6445 },
6446 ExcerptRange {
6447 context: Point::new(5, 0)..Point::new(7, 0),
6448 primary: None,
6449 },
6450 ExcerptRange {
6451 context: Point::new(9, 0)..Point::new(10, 4),
6452 primary: None,
6453 },
6454 ],
6455 cx,
6456 );
6457 multi_buffer
6458 });
6459 let multi_buffer_editor = cx.new_view(|cx| {
6460 Editor::new(
6461 EditorMode::Full,
6462 multi_buffer,
6463 Some(project.clone()),
6464 true,
6465 cx,
6466 )
6467 });
6468
6469 multi_buffer_editor.update(cx, |editor, cx| {
6470 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
6471 editor.insert("|one|two|three|", cx);
6472 });
6473 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6474 multi_buffer_editor.update(cx, |editor, cx| {
6475 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
6476 s.select_ranges(Some(60..70))
6477 });
6478 editor.insert("|four|five|six|", cx);
6479 });
6480 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6481
6482 // First two buffers should be edited, but not the third one.
6483 assert_eq!(
6484 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6485 "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}",
6486 );
6487 buffer_1.update(cx, |buffer, _| {
6488 assert!(buffer.is_dirty());
6489 assert_eq!(
6490 buffer.text(),
6491 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
6492 )
6493 });
6494 buffer_2.update(cx, |buffer, _| {
6495 assert!(buffer.is_dirty());
6496 assert_eq!(
6497 buffer.text(),
6498 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
6499 )
6500 });
6501 buffer_3.update(cx, |buffer, _| {
6502 assert!(!buffer.is_dirty());
6503 assert_eq!(buffer.text(), sample_text_3,)
6504 });
6505
6506 cx.executor().start_waiting();
6507 let save = multi_buffer_editor
6508 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6509 .unwrap();
6510
6511 let fake_server = fake_servers.next().await.unwrap();
6512 fake_server
6513 .server
6514 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6515 Ok(Some(vec![lsp::TextEdit::new(
6516 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6517 format!("[{} formatted]", params.text_document.uri),
6518 )]))
6519 })
6520 .detach();
6521 save.await;
6522
6523 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
6524 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
6525 assert_eq!(
6526 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6527 "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}",
6528 );
6529 buffer_1.update(cx, |buffer, _| {
6530 assert!(!buffer.is_dirty());
6531 assert_eq!(
6532 buffer.text(),
6533 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
6534 )
6535 });
6536 buffer_2.update(cx, |buffer, _| {
6537 assert!(!buffer.is_dirty());
6538 assert_eq!(
6539 buffer.text(),
6540 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
6541 )
6542 });
6543 buffer_3.update(cx, |buffer, _| {
6544 assert!(!buffer.is_dirty());
6545 assert_eq!(buffer.text(), sample_text_3,)
6546 });
6547}
6548
6549#[gpui::test]
6550async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
6551 init_test(cx, |_| {});
6552
6553 let fs = FakeFs::new(cx.executor());
6554 fs.insert_file("/file.rs", Default::default()).await;
6555
6556 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6557
6558 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6559 language_registry.add(rust_lang());
6560 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6561 "Rust",
6562 FakeLspAdapter {
6563 capabilities: lsp::ServerCapabilities {
6564 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
6565 ..Default::default()
6566 },
6567 ..Default::default()
6568 },
6569 );
6570
6571 let buffer = project
6572 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6573 .await
6574 .unwrap();
6575
6576 cx.executor().start_waiting();
6577 let fake_server = fake_servers.next().await.unwrap();
6578
6579 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6580 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6581 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6582 assert!(cx.read(|cx| editor.is_dirty(cx)));
6583
6584 let save = editor
6585 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6586 .unwrap();
6587 fake_server
6588 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6589 assert_eq!(
6590 params.text_document.uri,
6591 lsp::Url::from_file_path("/file.rs").unwrap()
6592 );
6593 assert_eq!(params.options.tab_size, 4);
6594 Ok(Some(vec![lsp::TextEdit::new(
6595 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6596 ", ".to_string(),
6597 )]))
6598 })
6599 .next()
6600 .await;
6601 cx.executor().start_waiting();
6602 save.await;
6603 assert_eq!(
6604 editor.update(cx, |editor, cx| editor.text(cx)),
6605 "one, two\nthree\n"
6606 );
6607 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6608
6609 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6610 assert!(cx.read(|cx| editor.is_dirty(cx)));
6611
6612 // Ensure we can still save even if formatting hangs.
6613 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
6614 move |params, _| async move {
6615 assert_eq!(
6616 params.text_document.uri,
6617 lsp::Url::from_file_path("/file.rs").unwrap()
6618 );
6619 futures::future::pending::<()>().await;
6620 unreachable!()
6621 },
6622 );
6623 let save = editor
6624 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6625 .unwrap();
6626 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6627 cx.executor().start_waiting();
6628 save.await;
6629 assert_eq!(
6630 editor.update(cx, |editor, cx| editor.text(cx)),
6631 "one\ntwo\nthree\n"
6632 );
6633 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6634
6635 // For non-dirty buffer, no formatting request should be sent
6636 let save = editor
6637 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6638 .unwrap();
6639 let _pending_format_request = fake_server
6640 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6641 panic!("Should not be invoked on non-dirty buffer");
6642 })
6643 .next();
6644 cx.executor().start_waiting();
6645 save.await;
6646
6647 // Set Rust language override and assert overridden tabsize is sent to language server
6648 update_test_language_settings(cx, |settings| {
6649 settings.languages.insert(
6650 "Rust".into(),
6651 LanguageSettingsContent {
6652 tab_size: NonZeroU32::new(8),
6653 ..Default::default()
6654 },
6655 );
6656 });
6657
6658 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6659 assert!(cx.read(|cx| editor.is_dirty(cx)));
6660 let save = editor
6661 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6662 .unwrap();
6663 fake_server
6664 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6665 assert_eq!(
6666 params.text_document.uri,
6667 lsp::Url::from_file_path("/file.rs").unwrap()
6668 );
6669 assert_eq!(params.options.tab_size, 8);
6670 Ok(Some(vec![]))
6671 })
6672 .next()
6673 .await;
6674 cx.executor().start_waiting();
6675 save.await;
6676}
6677
6678#[gpui::test]
6679async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
6680 init_test(cx, |settings| {
6681 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
6682 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
6683 ))
6684 });
6685
6686 let fs = FakeFs::new(cx.executor());
6687 fs.insert_file("/file.rs", Default::default()).await;
6688
6689 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6690
6691 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6692 language_registry.add(Arc::new(Language::new(
6693 LanguageConfig {
6694 name: "Rust".into(),
6695 matcher: LanguageMatcher {
6696 path_suffixes: vec!["rs".to_string()],
6697 ..Default::default()
6698 },
6699 ..LanguageConfig::default()
6700 },
6701 Some(tree_sitter_rust::language()),
6702 )));
6703 update_test_language_settings(cx, |settings| {
6704 // Enable Prettier formatting for the same buffer, and ensure
6705 // LSP is called instead of Prettier.
6706 settings.defaults.prettier = Some(PrettierSettings {
6707 allowed: true,
6708 ..PrettierSettings::default()
6709 });
6710 });
6711 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6712 "Rust",
6713 FakeLspAdapter {
6714 capabilities: lsp::ServerCapabilities {
6715 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6716 ..Default::default()
6717 },
6718 ..Default::default()
6719 },
6720 );
6721
6722 let buffer = project
6723 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6724 .await
6725 .unwrap();
6726
6727 cx.executor().start_waiting();
6728 let fake_server = fake_servers.next().await.unwrap();
6729
6730 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6731 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6732 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6733
6734 let format = editor
6735 .update(cx, |editor, cx| {
6736 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
6737 })
6738 .unwrap();
6739 fake_server
6740 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6741 assert_eq!(
6742 params.text_document.uri,
6743 lsp::Url::from_file_path("/file.rs").unwrap()
6744 );
6745 assert_eq!(params.options.tab_size, 4);
6746 Ok(Some(vec![lsp::TextEdit::new(
6747 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6748 ", ".to_string(),
6749 )]))
6750 })
6751 .next()
6752 .await;
6753 cx.executor().start_waiting();
6754 format.await;
6755 assert_eq!(
6756 editor.update(cx, |editor, cx| editor.text(cx)),
6757 "one, two\nthree\n"
6758 );
6759
6760 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6761 // Ensure we don't lock if formatting hangs.
6762 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6763 assert_eq!(
6764 params.text_document.uri,
6765 lsp::Url::from_file_path("/file.rs").unwrap()
6766 );
6767 futures::future::pending::<()>().await;
6768 unreachable!()
6769 });
6770 let format = editor
6771 .update(cx, |editor, cx| {
6772 editor.perform_format(project, FormatTrigger::Manual, cx)
6773 })
6774 .unwrap();
6775 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6776 cx.executor().start_waiting();
6777 format.await;
6778 assert_eq!(
6779 editor.update(cx, |editor, cx| editor.text(cx)),
6780 "one\ntwo\nthree\n"
6781 );
6782}
6783
6784#[gpui::test]
6785async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
6786 init_test(cx, |_| {});
6787
6788 let mut cx = EditorLspTestContext::new_rust(
6789 lsp::ServerCapabilities {
6790 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6791 ..Default::default()
6792 },
6793 cx,
6794 )
6795 .await;
6796
6797 cx.set_state(indoc! {"
6798 one.twoˇ
6799 "});
6800
6801 // The format request takes a long time. When it completes, it inserts
6802 // a newline and an indent before the `.`
6803 cx.lsp
6804 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
6805 let executor = cx.background_executor().clone();
6806 async move {
6807 executor.timer(Duration::from_millis(100)).await;
6808 Ok(Some(vec![lsp::TextEdit {
6809 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
6810 new_text: "\n ".into(),
6811 }]))
6812 }
6813 });
6814
6815 // Submit a format request.
6816 let format_1 = cx
6817 .update_editor(|editor, cx| editor.format(&Format, cx))
6818 .unwrap();
6819 cx.executor().run_until_parked();
6820
6821 // Submit a second format request.
6822 let format_2 = cx
6823 .update_editor(|editor, cx| editor.format(&Format, cx))
6824 .unwrap();
6825 cx.executor().run_until_parked();
6826
6827 // Wait for both format requests to complete
6828 cx.executor().advance_clock(Duration::from_millis(200));
6829 cx.executor().start_waiting();
6830 format_1.await.unwrap();
6831 cx.executor().start_waiting();
6832 format_2.await.unwrap();
6833
6834 // The formatting edits only happens once.
6835 cx.assert_editor_state(indoc! {"
6836 one
6837 .twoˇ
6838 "});
6839}
6840
6841#[gpui::test]
6842async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
6843 init_test(cx, |settings| {
6844 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
6845 });
6846
6847 let mut cx = EditorLspTestContext::new_rust(
6848 lsp::ServerCapabilities {
6849 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6850 ..Default::default()
6851 },
6852 cx,
6853 )
6854 .await;
6855
6856 // Set up a buffer white some trailing whitespace and no trailing newline.
6857 cx.set_state(
6858 &[
6859 "one ", //
6860 "twoˇ", //
6861 "three ", //
6862 "four", //
6863 ]
6864 .join("\n"),
6865 );
6866
6867 // Submit a format request.
6868 let format = cx
6869 .update_editor(|editor, cx| editor.format(&Format, cx))
6870 .unwrap();
6871
6872 // Record which buffer changes have been sent to the language server
6873 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
6874 cx.lsp
6875 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
6876 let buffer_changes = buffer_changes.clone();
6877 move |params, _| {
6878 buffer_changes.lock().extend(
6879 params
6880 .content_changes
6881 .into_iter()
6882 .map(|e| (e.range.unwrap(), e.text)),
6883 );
6884 }
6885 });
6886
6887 // Handle formatting requests to the language server.
6888 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
6889 let buffer_changes = buffer_changes.clone();
6890 move |_, _| {
6891 // When formatting is requested, trailing whitespace has already been stripped,
6892 // and the trailing newline has already been added.
6893 assert_eq!(
6894 &buffer_changes.lock()[1..],
6895 &[
6896 (
6897 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
6898 "".into()
6899 ),
6900 (
6901 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
6902 "".into()
6903 ),
6904 (
6905 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
6906 "\n".into()
6907 ),
6908 ]
6909 );
6910
6911 // Insert blank lines between each line of the buffer.
6912 async move {
6913 Ok(Some(vec![
6914 lsp::TextEdit {
6915 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
6916 new_text: "\n".into(),
6917 },
6918 lsp::TextEdit {
6919 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
6920 new_text: "\n".into(),
6921 },
6922 ]))
6923 }
6924 }
6925 });
6926
6927 // After formatting the buffer, the trailing whitespace is stripped,
6928 // a newline is appended, and the edits provided by the language server
6929 // have been applied.
6930 format.await.unwrap();
6931 cx.assert_editor_state(
6932 &[
6933 "one", //
6934 "", //
6935 "twoˇ", //
6936 "", //
6937 "three", //
6938 "four", //
6939 "", //
6940 ]
6941 .join("\n"),
6942 );
6943
6944 // Undoing the formatting undoes the trailing whitespace removal, the
6945 // trailing newline, and the LSP edits.
6946 cx.update_buffer(|buffer, cx| buffer.undo(cx));
6947 cx.assert_editor_state(
6948 &[
6949 "one ", //
6950 "twoˇ", //
6951 "three ", //
6952 "four", //
6953 ]
6954 .join("\n"),
6955 );
6956}
6957
6958#[gpui::test]
6959async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
6960 cx: &mut gpui::TestAppContext,
6961) {
6962 init_test(cx, |_| {});
6963
6964 cx.update(|cx| {
6965 cx.update_global::<SettingsStore, _>(|settings, cx| {
6966 settings.update_user_settings::<EditorSettings>(cx, |settings| {
6967 settings.auto_signature_help = Some(true);
6968 });
6969 });
6970 });
6971
6972 let mut cx = EditorLspTestContext::new_rust(
6973 lsp::ServerCapabilities {
6974 signature_help_provider: Some(lsp::SignatureHelpOptions {
6975 ..Default::default()
6976 }),
6977 ..Default::default()
6978 },
6979 cx,
6980 )
6981 .await;
6982
6983 let language = Language::new(
6984 LanguageConfig {
6985 name: "Rust".into(),
6986 brackets: BracketPairConfig {
6987 pairs: vec![
6988 BracketPair {
6989 start: "{".to_string(),
6990 end: "}".to_string(),
6991 close: true,
6992 surround: true,
6993 newline: true,
6994 },
6995 BracketPair {
6996 start: "(".to_string(),
6997 end: ")".to_string(),
6998 close: true,
6999 surround: true,
7000 newline: true,
7001 },
7002 BracketPair {
7003 start: "/*".to_string(),
7004 end: " */".to_string(),
7005 close: true,
7006 surround: true,
7007 newline: true,
7008 },
7009 BracketPair {
7010 start: "[".to_string(),
7011 end: "]".to_string(),
7012 close: false,
7013 surround: false,
7014 newline: true,
7015 },
7016 BracketPair {
7017 start: "\"".to_string(),
7018 end: "\"".to_string(),
7019 close: true,
7020 surround: true,
7021 newline: false,
7022 },
7023 BracketPair {
7024 start: "<".to_string(),
7025 end: ">".to_string(),
7026 close: false,
7027 surround: true,
7028 newline: true,
7029 },
7030 ],
7031 ..Default::default()
7032 },
7033 autoclose_before: "})]".to_string(),
7034 ..Default::default()
7035 },
7036 Some(tree_sitter_rust::language()),
7037 );
7038 let language = Arc::new(language);
7039
7040 cx.language_registry().add(language.clone());
7041 cx.update_buffer(|buffer, cx| {
7042 buffer.set_language(Some(language), cx);
7043 });
7044
7045 cx.set_state(
7046 &r#"
7047 fn main() {
7048 sampleˇ
7049 }
7050 "#
7051 .unindent(),
7052 );
7053
7054 cx.update_editor(|view, cx| {
7055 view.handle_input("(", cx);
7056 });
7057 cx.assert_editor_state(
7058 &"
7059 fn main() {
7060 sample(ˇ)
7061 }
7062 "
7063 .unindent(),
7064 );
7065
7066 let mocked_response = lsp::SignatureHelp {
7067 signatures: vec![lsp::SignatureInformation {
7068 label: "fn sample(param1: u8, param2: u8)".to_string(),
7069 documentation: None,
7070 parameters: Some(vec![
7071 lsp::ParameterInformation {
7072 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7073 documentation: None,
7074 },
7075 lsp::ParameterInformation {
7076 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7077 documentation: None,
7078 },
7079 ]),
7080 active_parameter: None,
7081 }],
7082 active_signature: Some(0),
7083 active_parameter: Some(0),
7084 };
7085 handle_signature_help_request(&mut cx, mocked_response).await;
7086
7087 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7088 .await;
7089
7090 cx.editor(|editor, _| {
7091 let signature_help_state = editor.signature_help_state.popover().cloned();
7092 assert!(signature_help_state.is_some());
7093 let ParsedMarkdown {
7094 text, highlights, ..
7095 } = signature_help_state.unwrap().parsed_content;
7096 assert_eq!(text, "param1: u8, param2: u8");
7097 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7098 });
7099}
7100
7101#[gpui::test]
7102async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7103 init_test(cx, |_| {});
7104
7105 cx.update(|cx| {
7106 cx.update_global::<SettingsStore, _>(|settings, cx| {
7107 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7108 settings.auto_signature_help = Some(false);
7109 settings.show_signature_help_after_edits = Some(false);
7110 });
7111 });
7112 });
7113
7114 let mut cx = EditorLspTestContext::new_rust(
7115 lsp::ServerCapabilities {
7116 signature_help_provider: Some(lsp::SignatureHelpOptions {
7117 ..Default::default()
7118 }),
7119 ..Default::default()
7120 },
7121 cx,
7122 )
7123 .await;
7124
7125 let language = Language::new(
7126 LanguageConfig {
7127 name: "Rust".into(),
7128 brackets: BracketPairConfig {
7129 pairs: vec![
7130 BracketPair {
7131 start: "{".to_string(),
7132 end: "}".to_string(),
7133 close: true,
7134 surround: true,
7135 newline: true,
7136 },
7137 BracketPair {
7138 start: "(".to_string(),
7139 end: ")".to_string(),
7140 close: true,
7141 surround: true,
7142 newline: true,
7143 },
7144 BracketPair {
7145 start: "/*".to_string(),
7146 end: " */".to_string(),
7147 close: true,
7148 surround: true,
7149 newline: true,
7150 },
7151 BracketPair {
7152 start: "[".to_string(),
7153 end: "]".to_string(),
7154 close: false,
7155 surround: false,
7156 newline: true,
7157 },
7158 BracketPair {
7159 start: "\"".to_string(),
7160 end: "\"".to_string(),
7161 close: true,
7162 surround: true,
7163 newline: false,
7164 },
7165 BracketPair {
7166 start: "<".to_string(),
7167 end: ">".to_string(),
7168 close: false,
7169 surround: true,
7170 newline: true,
7171 },
7172 ],
7173 ..Default::default()
7174 },
7175 autoclose_before: "})]".to_string(),
7176 ..Default::default()
7177 },
7178 Some(tree_sitter_rust::language()),
7179 );
7180 let language = Arc::new(language);
7181
7182 cx.language_registry().add(language.clone());
7183 cx.update_buffer(|buffer, cx| {
7184 buffer.set_language(Some(language), cx);
7185 });
7186
7187 // Ensure that signature_help is not called when no signature help is enabled.
7188 cx.set_state(
7189 &r#"
7190 fn main() {
7191 sampleˇ
7192 }
7193 "#
7194 .unindent(),
7195 );
7196 cx.update_editor(|view, cx| {
7197 view.handle_input("(", cx);
7198 });
7199 cx.assert_editor_state(
7200 &"
7201 fn main() {
7202 sample(ˇ)
7203 }
7204 "
7205 .unindent(),
7206 );
7207 cx.editor(|editor, _| {
7208 assert!(editor.signature_help_state.task().is_none());
7209 });
7210
7211 let mocked_response = lsp::SignatureHelp {
7212 signatures: vec![lsp::SignatureInformation {
7213 label: "fn sample(param1: u8, param2: u8)".to_string(),
7214 documentation: None,
7215 parameters: Some(vec![
7216 lsp::ParameterInformation {
7217 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7218 documentation: None,
7219 },
7220 lsp::ParameterInformation {
7221 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7222 documentation: None,
7223 },
7224 ]),
7225 active_parameter: None,
7226 }],
7227 active_signature: Some(0),
7228 active_parameter: Some(0),
7229 };
7230
7231 // Ensure that signature_help is called when enabled afte edits
7232 cx.update(|cx| {
7233 cx.update_global::<SettingsStore, _>(|settings, cx| {
7234 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7235 settings.auto_signature_help = Some(false);
7236 settings.show_signature_help_after_edits = Some(true);
7237 });
7238 });
7239 });
7240 cx.set_state(
7241 &r#"
7242 fn main() {
7243 sampleˇ
7244 }
7245 "#
7246 .unindent(),
7247 );
7248 cx.update_editor(|view, cx| {
7249 view.handle_input("(", cx);
7250 });
7251 cx.assert_editor_state(
7252 &"
7253 fn main() {
7254 sample(ˇ)
7255 }
7256 "
7257 .unindent(),
7258 );
7259 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7260 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7261 .await;
7262 cx.update_editor(|editor, _| {
7263 let signature_help_state = editor.signature_help_state.popover().cloned();
7264 assert!(signature_help_state.is_some());
7265 let ParsedMarkdown {
7266 text, highlights, ..
7267 } = signature_help_state.unwrap().parsed_content;
7268 assert_eq!(text, "param1: u8, param2: u8");
7269 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7270 editor.signature_help_state = SignatureHelpState::default();
7271 });
7272
7273 // Ensure that signature_help is called when auto signature help override is enabled
7274 cx.update(|cx| {
7275 cx.update_global::<SettingsStore, _>(|settings, cx| {
7276 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7277 settings.auto_signature_help = Some(true);
7278 settings.show_signature_help_after_edits = Some(false);
7279 });
7280 });
7281 });
7282 cx.set_state(
7283 &r#"
7284 fn main() {
7285 sampleˇ
7286 }
7287 "#
7288 .unindent(),
7289 );
7290 cx.update_editor(|view, cx| {
7291 view.handle_input("(", cx);
7292 });
7293 cx.assert_editor_state(
7294 &"
7295 fn main() {
7296 sample(ˇ)
7297 }
7298 "
7299 .unindent(),
7300 );
7301 handle_signature_help_request(&mut cx, mocked_response).await;
7302 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7303 .await;
7304 cx.editor(|editor, _| {
7305 let signature_help_state = editor.signature_help_state.popover().cloned();
7306 assert!(signature_help_state.is_some());
7307 let ParsedMarkdown {
7308 text, highlights, ..
7309 } = signature_help_state.unwrap().parsed_content;
7310 assert_eq!(text, "param1: u8, param2: u8");
7311 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7312 });
7313}
7314
7315#[gpui::test]
7316async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7317 init_test(cx, |_| {});
7318 cx.update(|cx| {
7319 cx.update_global::<SettingsStore, _>(|settings, cx| {
7320 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7321 settings.auto_signature_help = Some(true);
7322 });
7323 });
7324 });
7325
7326 let mut cx = EditorLspTestContext::new_rust(
7327 lsp::ServerCapabilities {
7328 signature_help_provider: Some(lsp::SignatureHelpOptions {
7329 ..Default::default()
7330 }),
7331 ..Default::default()
7332 },
7333 cx,
7334 )
7335 .await;
7336
7337 // A test that directly calls `show_signature_help`
7338 cx.update_editor(|editor, cx| {
7339 editor.show_signature_help(&ShowSignatureHelp, cx);
7340 });
7341
7342 let mocked_response = lsp::SignatureHelp {
7343 signatures: vec![lsp::SignatureInformation {
7344 label: "fn sample(param1: u8, param2: u8)".to_string(),
7345 documentation: None,
7346 parameters: Some(vec![
7347 lsp::ParameterInformation {
7348 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7349 documentation: None,
7350 },
7351 lsp::ParameterInformation {
7352 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7353 documentation: None,
7354 },
7355 ]),
7356 active_parameter: None,
7357 }],
7358 active_signature: Some(0),
7359 active_parameter: Some(0),
7360 };
7361 handle_signature_help_request(&mut cx, mocked_response).await;
7362
7363 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7364 .await;
7365
7366 cx.editor(|editor, _| {
7367 let signature_help_state = editor.signature_help_state.popover().cloned();
7368 assert!(signature_help_state.is_some());
7369 let ParsedMarkdown {
7370 text, highlights, ..
7371 } = signature_help_state.unwrap().parsed_content;
7372 assert_eq!(text, "param1: u8, param2: u8");
7373 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7374 });
7375
7376 // When exiting outside from inside the brackets, `signature_help` is closed.
7377 cx.set_state(indoc! {"
7378 fn main() {
7379 sample(ˇ);
7380 }
7381
7382 fn sample(param1: u8, param2: u8) {}
7383 "});
7384
7385 cx.update_editor(|editor, cx| {
7386 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
7387 });
7388
7389 let mocked_response = lsp::SignatureHelp {
7390 signatures: Vec::new(),
7391 active_signature: None,
7392 active_parameter: None,
7393 };
7394 handle_signature_help_request(&mut cx, mocked_response).await;
7395
7396 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7397 .await;
7398
7399 cx.editor(|editor, _| {
7400 assert!(!editor.signature_help_state.is_shown());
7401 });
7402
7403 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
7404 cx.set_state(indoc! {"
7405 fn main() {
7406 sample(ˇ);
7407 }
7408
7409 fn sample(param1: u8, param2: u8) {}
7410 "});
7411
7412 let mocked_response = lsp::SignatureHelp {
7413 signatures: vec![lsp::SignatureInformation {
7414 label: "fn sample(param1: u8, param2: u8)".to_string(),
7415 documentation: None,
7416 parameters: Some(vec![
7417 lsp::ParameterInformation {
7418 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7419 documentation: None,
7420 },
7421 lsp::ParameterInformation {
7422 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7423 documentation: None,
7424 },
7425 ]),
7426 active_parameter: None,
7427 }],
7428 active_signature: Some(0),
7429 active_parameter: Some(0),
7430 };
7431 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7432 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7433 .await;
7434 cx.editor(|editor, _| {
7435 assert!(editor.signature_help_state.is_shown());
7436 });
7437
7438 // Restore the popover with more parameter input
7439 cx.set_state(indoc! {"
7440 fn main() {
7441 sample(param1, param2ˇ);
7442 }
7443
7444 fn sample(param1: u8, param2: u8) {}
7445 "});
7446
7447 let mocked_response = lsp::SignatureHelp {
7448 signatures: vec![lsp::SignatureInformation {
7449 label: "fn sample(param1: u8, param2: u8)".to_string(),
7450 documentation: None,
7451 parameters: Some(vec![
7452 lsp::ParameterInformation {
7453 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7454 documentation: None,
7455 },
7456 lsp::ParameterInformation {
7457 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7458 documentation: None,
7459 },
7460 ]),
7461 active_parameter: None,
7462 }],
7463 active_signature: Some(0),
7464 active_parameter: Some(1),
7465 };
7466 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7467 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7468 .await;
7469
7470 // When selecting a range, the popover is gone.
7471 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
7472 cx.update_editor(|editor, cx| {
7473 editor.change_selections(None, cx, |s| {
7474 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7475 })
7476 });
7477 cx.assert_editor_state(indoc! {"
7478 fn main() {
7479 sample(param1, «ˇparam2»);
7480 }
7481
7482 fn sample(param1: u8, param2: u8) {}
7483 "});
7484 cx.editor(|editor, _| {
7485 assert!(!editor.signature_help_state.is_shown());
7486 });
7487
7488 // When unselecting again, the popover is back if within the brackets.
7489 cx.update_editor(|editor, cx| {
7490 editor.change_selections(None, cx, |s| {
7491 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7492 })
7493 });
7494 cx.assert_editor_state(indoc! {"
7495 fn main() {
7496 sample(param1, ˇparam2);
7497 }
7498
7499 fn sample(param1: u8, param2: u8) {}
7500 "});
7501 handle_signature_help_request(&mut cx, mocked_response).await;
7502 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7503 .await;
7504 cx.editor(|editor, _| {
7505 assert!(editor.signature_help_state.is_shown());
7506 });
7507
7508 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
7509 cx.update_editor(|editor, cx| {
7510 editor.change_selections(None, cx, |s| {
7511 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
7512 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7513 })
7514 });
7515 cx.assert_editor_state(indoc! {"
7516 fn main() {
7517 sample(param1, ˇparam2);
7518 }
7519
7520 fn sample(param1: u8, param2: u8) {}
7521 "});
7522
7523 let mocked_response = lsp::SignatureHelp {
7524 signatures: vec![lsp::SignatureInformation {
7525 label: "fn sample(param1: u8, param2: u8)".to_string(),
7526 documentation: None,
7527 parameters: Some(vec![
7528 lsp::ParameterInformation {
7529 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7530 documentation: None,
7531 },
7532 lsp::ParameterInformation {
7533 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7534 documentation: None,
7535 },
7536 ]),
7537 active_parameter: None,
7538 }],
7539 active_signature: Some(0),
7540 active_parameter: Some(1),
7541 };
7542 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7543 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7544 .await;
7545 cx.update_editor(|editor, cx| {
7546 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
7547 });
7548 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7549 .await;
7550 cx.update_editor(|editor, cx| {
7551 editor.change_selections(None, cx, |s| {
7552 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7553 })
7554 });
7555 cx.assert_editor_state(indoc! {"
7556 fn main() {
7557 sample(param1, «ˇparam2»);
7558 }
7559
7560 fn sample(param1: u8, param2: u8) {}
7561 "});
7562 cx.update_editor(|editor, cx| {
7563 editor.change_selections(None, cx, |s| {
7564 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7565 })
7566 });
7567 cx.assert_editor_state(indoc! {"
7568 fn main() {
7569 sample(param1, ˇparam2);
7570 }
7571
7572 fn sample(param1: u8, param2: u8) {}
7573 "});
7574 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
7575 .await;
7576}
7577
7578#[gpui::test]
7579async fn test_completion(cx: &mut gpui::TestAppContext) {
7580 init_test(cx, |_| {});
7581
7582 let mut cx = EditorLspTestContext::new_rust(
7583 lsp::ServerCapabilities {
7584 completion_provider: Some(lsp::CompletionOptions {
7585 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7586 resolve_provider: Some(true),
7587 ..Default::default()
7588 }),
7589 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
7590 ..Default::default()
7591 },
7592 cx,
7593 )
7594 .await;
7595 let counter = Arc::new(AtomicUsize::new(0));
7596
7597 cx.set_state(indoc! {"
7598 oneˇ
7599 two
7600 three
7601 "});
7602 cx.simulate_keystroke(".");
7603 handle_completion_request(
7604 &mut cx,
7605 indoc! {"
7606 one.|<>
7607 two
7608 three
7609 "},
7610 vec!["first_completion", "second_completion"],
7611 counter.clone(),
7612 )
7613 .await;
7614 cx.condition(|editor, _| editor.context_menu_visible())
7615 .await;
7616 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
7617
7618 let _handler = handle_signature_help_request(
7619 &mut cx,
7620 lsp::SignatureHelp {
7621 signatures: vec![lsp::SignatureInformation {
7622 label: "test signature".to_string(),
7623 documentation: None,
7624 parameters: Some(vec![lsp::ParameterInformation {
7625 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
7626 documentation: None,
7627 }]),
7628 active_parameter: None,
7629 }],
7630 active_signature: None,
7631 active_parameter: None,
7632 },
7633 );
7634 cx.update_editor(|editor, cx| {
7635 assert!(
7636 !editor.signature_help_state.is_shown(),
7637 "No signature help was called for"
7638 );
7639 editor.show_signature_help(&ShowSignatureHelp, cx);
7640 });
7641 cx.run_until_parked();
7642 cx.update_editor(|editor, _| {
7643 assert!(
7644 !editor.signature_help_state.is_shown(),
7645 "No signature help should be shown when completions menu is open"
7646 );
7647 });
7648
7649 let apply_additional_edits = cx.update_editor(|editor, cx| {
7650 editor.context_menu_next(&Default::default(), cx);
7651 editor
7652 .confirm_completion(&ConfirmCompletion::default(), cx)
7653 .unwrap()
7654 });
7655 cx.assert_editor_state(indoc! {"
7656 one.second_completionˇ
7657 two
7658 three
7659 "});
7660
7661 handle_resolve_completion_request(
7662 &mut cx,
7663 Some(vec![
7664 (
7665 //This overlaps with the primary completion edit which is
7666 //misbehavior from the LSP spec, test that we filter it out
7667 indoc! {"
7668 one.second_ˇcompletion
7669 two
7670 threeˇ
7671 "},
7672 "overlapping additional edit",
7673 ),
7674 (
7675 indoc! {"
7676 one.second_completion
7677 two
7678 threeˇ
7679 "},
7680 "\nadditional edit",
7681 ),
7682 ]),
7683 )
7684 .await;
7685 apply_additional_edits.await.unwrap();
7686 cx.assert_editor_state(indoc! {"
7687 one.second_completionˇ
7688 two
7689 three
7690 additional edit
7691 "});
7692
7693 cx.set_state(indoc! {"
7694 one.second_completion
7695 twoˇ
7696 threeˇ
7697 additional edit
7698 "});
7699 cx.simulate_keystroke(" ");
7700 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7701 cx.simulate_keystroke("s");
7702 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7703
7704 cx.assert_editor_state(indoc! {"
7705 one.second_completion
7706 two sˇ
7707 three sˇ
7708 additional edit
7709 "});
7710 handle_completion_request(
7711 &mut cx,
7712 indoc! {"
7713 one.second_completion
7714 two s
7715 three <s|>
7716 additional edit
7717 "},
7718 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
7719 counter.clone(),
7720 )
7721 .await;
7722 cx.condition(|editor, _| editor.context_menu_visible())
7723 .await;
7724 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
7725
7726 cx.simulate_keystroke("i");
7727
7728 handle_completion_request(
7729 &mut cx,
7730 indoc! {"
7731 one.second_completion
7732 two si
7733 three <si|>
7734 additional edit
7735 "},
7736 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
7737 counter.clone(),
7738 )
7739 .await;
7740 cx.condition(|editor, _| editor.context_menu_visible())
7741 .await;
7742 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
7743
7744 let apply_additional_edits = cx.update_editor(|editor, cx| {
7745 editor
7746 .confirm_completion(&ConfirmCompletion::default(), cx)
7747 .unwrap()
7748 });
7749 cx.assert_editor_state(indoc! {"
7750 one.second_completion
7751 two sixth_completionˇ
7752 three sixth_completionˇ
7753 additional edit
7754 "});
7755
7756 handle_resolve_completion_request(&mut cx, None).await;
7757 apply_additional_edits.await.unwrap();
7758
7759 cx.update(|cx| {
7760 cx.update_global::<SettingsStore, _>(|settings, cx| {
7761 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7762 settings.show_completions_on_input = Some(false);
7763 });
7764 })
7765 });
7766 cx.set_state("editorˇ");
7767 cx.simulate_keystroke(".");
7768 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7769 cx.simulate_keystroke("c");
7770 cx.simulate_keystroke("l");
7771 cx.simulate_keystroke("o");
7772 cx.assert_editor_state("editor.cloˇ");
7773 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7774 cx.update_editor(|editor, cx| {
7775 editor.show_completions(&ShowCompletions { trigger: None }, cx);
7776 });
7777 handle_completion_request(
7778 &mut cx,
7779 "editor.<clo|>",
7780 vec!["close", "clobber"],
7781 counter.clone(),
7782 )
7783 .await;
7784 cx.condition(|editor, _| editor.context_menu_visible())
7785 .await;
7786 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
7787
7788 let apply_additional_edits = cx.update_editor(|editor, cx| {
7789 editor
7790 .confirm_completion(&ConfirmCompletion::default(), cx)
7791 .unwrap()
7792 });
7793 cx.assert_editor_state("editor.closeˇ");
7794 handle_resolve_completion_request(&mut cx, None).await;
7795 apply_additional_edits.await.unwrap();
7796}
7797
7798#[gpui::test]
7799async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
7800 init_test(cx, |_| {});
7801 let mut cx = EditorLspTestContext::new_rust(
7802 lsp::ServerCapabilities {
7803 completion_provider: Some(lsp::CompletionOptions {
7804 trigger_characters: Some(vec![".".to_string()]),
7805 ..Default::default()
7806 }),
7807 ..Default::default()
7808 },
7809 cx,
7810 )
7811 .await;
7812 cx.lsp
7813 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
7814 Ok(Some(lsp::CompletionResponse::Array(vec![
7815 lsp::CompletionItem {
7816 label: "first".into(),
7817 ..Default::default()
7818 },
7819 lsp::CompletionItem {
7820 label: "last".into(),
7821 ..Default::default()
7822 },
7823 ])))
7824 });
7825 cx.set_state("variableˇ");
7826 cx.simulate_keystroke(".");
7827 cx.executor().run_until_parked();
7828
7829 cx.update_editor(|editor, _| {
7830 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7831 assert_eq!(
7832 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
7833 &["first", "last"]
7834 );
7835 } else {
7836 panic!("expected completion menu to be open");
7837 }
7838 });
7839
7840 cx.update_editor(|editor, cx| {
7841 editor.move_page_down(&MovePageDown::default(), cx);
7842 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7843 assert!(
7844 menu.selected_item == 1,
7845 "expected PageDown to select the last item from the context menu"
7846 );
7847 } else {
7848 panic!("expected completion menu to stay open after PageDown");
7849 }
7850 });
7851
7852 cx.update_editor(|editor, cx| {
7853 editor.move_page_up(&MovePageUp::default(), cx);
7854 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7855 assert!(
7856 menu.selected_item == 0,
7857 "expected PageUp to select the first item from the context menu"
7858 );
7859 } else {
7860 panic!("expected completion menu to stay open after PageUp");
7861 }
7862 });
7863}
7864
7865#[gpui::test]
7866async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
7867 init_test(cx, |_| {});
7868
7869 let mut cx = EditorLspTestContext::new_rust(
7870 lsp::ServerCapabilities {
7871 completion_provider: Some(lsp::CompletionOptions {
7872 trigger_characters: Some(vec![".".to_string()]),
7873 resolve_provider: Some(true),
7874 ..Default::default()
7875 }),
7876 ..Default::default()
7877 },
7878 cx,
7879 )
7880 .await;
7881
7882 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
7883 cx.simulate_keystroke(".");
7884 let completion_item = lsp::CompletionItem {
7885 label: "Some".into(),
7886 kind: Some(lsp::CompletionItemKind::SNIPPET),
7887 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
7888 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
7889 kind: lsp::MarkupKind::Markdown,
7890 value: "```rust\nSome(2)\n```".to_string(),
7891 })),
7892 deprecated: Some(false),
7893 sort_text: Some("Some".to_string()),
7894 filter_text: Some("Some".to_string()),
7895 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
7896 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7897 range: lsp::Range {
7898 start: lsp::Position {
7899 line: 0,
7900 character: 22,
7901 },
7902 end: lsp::Position {
7903 line: 0,
7904 character: 22,
7905 },
7906 },
7907 new_text: "Some(2)".to_string(),
7908 })),
7909 additional_text_edits: Some(vec![lsp::TextEdit {
7910 range: lsp::Range {
7911 start: lsp::Position {
7912 line: 0,
7913 character: 20,
7914 },
7915 end: lsp::Position {
7916 line: 0,
7917 character: 22,
7918 },
7919 },
7920 new_text: "".to_string(),
7921 }]),
7922 ..Default::default()
7923 };
7924
7925 let closure_completion_item = completion_item.clone();
7926 let counter = Arc::new(AtomicUsize::new(0));
7927 let counter_clone = counter.clone();
7928 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
7929 let task_completion_item = closure_completion_item.clone();
7930 counter_clone.fetch_add(1, atomic::Ordering::Release);
7931 async move {
7932 Ok(Some(lsp::CompletionResponse::Array(vec![
7933 task_completion_item,
7934 ])))
7935 }
7936 });
7937
7938 cx.condition(|editor, _| editor.context_menu_visible())
7939 .await;
7940 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
7941 assert!(request.next().await.is_some());
7942 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
7943
7944 cx.simulate_keystroke("S");
7945 cx.simulate_keystroke("o");
7946 cx.simulate_keystroke("m");
7947 cx.condition(|editor, _| editor.context_menu_visible())
7948 .await;
7949 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
7950 assert!(request.next().await.is_some());
7951 assert!(request.next().await.is_some());
7952 assert!(request.next().await.is_some());
7953 request.close();
7954 assert!(request.next().await.is_none());
7955 assert_eq!(
7956 counter.load(atomic::Ordering::Acquire),
7957 4,
7958 "With the completions menu open, only one LSP request should happen per input"
7959 );
7960}
7961
7962#[gpui::test]
7963async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
7964 init_test(cx, |_| {});
7965 let mut cx = EditorTestContext::new(cx).await;
7966 let language = Arc::new(Language::new(
7967 LanguageConfig {
7968 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
7969 ..Default::default()
7970 },
7971 Some(tree_sitter_rust::language()),
7972 ));
7973 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
7974
7975 // If multiple selections intersect a line, the line is only toggled once.
7976 cx.set_state(indoc! {"
7977 fn a() {
7978 «//b();
7979 ˇ»// «c();
7980 //ˇ» d();
7981 }
7982 "});
7983
7984 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7985
7986 cx.assert_editor_state(indoc! {"
7987 fn a() {
7988 «b();
7989 c();
7990 ˇ» d();
7991 }
7992 "});
7993
7994 // The comment prefix is inserted at the same column for every line in a
7995 // selection.
7996 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7997
7998 cx.assert_editor_state(indoc! {"
7999 fn a() {
8000 // «b();
8001 // c();
8002 ˇ»// d();
8003 }
8004 "});
8005
8006 // If a selection ends at the beginning of a line, that line is not toggled.
8007 cx.set_selections_state(indoc! {"
8008 fn a() {
8009 // b();
8010 «// c();
8011 ˇ» // d();
8012 }
8013 "});
8014
8015 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8016
8017 cx.assert_editor_state(indoc! {"
8018 fn a() {
8019 // b();
8020 «c();
8021 ˇ» // d();
8022 }
8023 "});
8024
8025 // If a selection span a single line and is empty, the line is toggled.
8026 cx.set_state(indoc! {"
8027 fn a() {
8028 a();
8029 b();
8030 ˇ
8031 }
8032 "});
8033
8034 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8035
8036 cx.assert_editor_state(indoc! {"
8037 fn a() {
8038 a();
8039 b();
8040 //•ˇ
8041 }
8042 "});
8043
8044 // If a selection span multiple lines, empty lines are not toggled.
8045 cx.set_state(indoc! {"
8046 fn a() {
8047 «a();
8048
8049 c();ˇ»
8050 }
8051 "});
8052
8053 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8054
8055 cx.assert_editor_state(indoc! {"
8056 fn a() {
8057 // «a();
8058
8059 // c();ˇ»
8060 }
8061 "});
8062
8063 // If a selection includes multiple comment prefixes, all lines are uncommented.
8064 cx.set_state(indoc! {"
8065 fn a() {
8066 «// a();
8067 /// b();
8068 //! c();ˇ»
8069 }
8070 "});
8071
8072 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8073
8074 cx.assert_editor_state(indoc! {"
8075 fn a() {
8076 «a();
8077 b();
8078 c();ˇ»
8079 }
8080 "});
8081}
8082
8083#[gpui::test]
8084async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8085 init_test(cx, |_| {});
8086
8087 let language = Arc::new(Language::new(
8088 LanguageConfig {
8089 line_comments: vec!["// ".into()],
8090 ..Default::default()
8091 },
8092 Some(tree_sitter_rust::language()),
8093 ));
8094
8095 let mut cx = EditorTestContext::new(cx).await;
8096
8097 cx.language_registry().add(language.clone());
8098 cx.update_buffer(|buffer, cx| {
8099 buffer.set_language(Some(language), cx);
8100 });
8101
8102 let toggle_comments = &ToggleComments {
8103 advance_downwards: true,
8104 };
8105
8106 // Single cursor on one line -> advance
8107 // Cursor moves horizontally 3 characters as well on non-blank line
8108 cx.set_state(indoc!(
8109 "fn a() {
8110 ˇdog();
8111 cat();
8112 }"
8113 ));
8114 cx.update_editor(|editor, cx| {
8115 editor.toggle_comments(toggle_comments, cx);
8116 });
8117 cx.assert_editor_state(indoc!(
8118 "fn a() {
8119 // dog();
8120 catˇ();
8121 }"
8122 ));
8123
8124 // Single selection on one line -> don't advance
8125 cx.set_state(indoc!(
8126 "fn a() {
8127 «dog()ˇ»;
8128 cat();
8129 }"
8130 ));
8131 cx.update_editor(|editor, cx| {
8132 editor.toggle_comments(toggle_comments, cx);
8133 });
8134 cx.assert_editor_state(indoc!(
8135 "fn a() {
8136 // «dog()ˇ»;
8137 cat();
8138 }"
8139 ));
8140
8141 // Multiple cursors on one line -> advance
8142 cx.set_state(indoc!(
8143 "fn a() {
8144 ˇdˇog();
8145 cat();
8146 }"
8147 ));
8148 cx.update_editor(|editor, cx| {
8149 editor.toggle_comments(toggle_comments, cx);
8150 });
8151 cx.assert_editor_state(indoc!(
8152 "fn a() {
8153 // dog();
8154 catˇ(ˇ);
8155 }"
8156 ));
8157
8158 // Multiple cursors on one line, with selection -> don't advance
8159 cx.set_state(indoc!(
8160 "fn a() {
8161 ˇdˇog«()ˇ»;
8162 cat();
8163 }"
8164 ));
8165 cx.update_editor(|editor, cx| {
8166 editor.toggle_comments(toggle_comments, cx);
8167 });
8168 cx.assert_editor_state(indoc!(
8169 "fn a() {
8170 // ˇdˇog«()ˇ»;
8171 cat();
8172 }"
8173 ));
8174
8175 // Single cursor on one line -> advance
8176 // Cursor moves to column 0 on blank line
8177 cx.set_state(indoc!(
8178 "fn a() {
8179 ˇdog();
8180
8181 cat();
8182 }"
8183 ));
8184 cx.update_editor(|editor, cx| {
8185 editor.toggle_comments(toggle_comments, cx);
8186 });
8187 cx.assert_editor_state(indoc!(
8188 "fn a() {
8189 // dog();
8190 ˇ
8191 cat();
8192 }"
8193 ));
8194
8195 // Single cursor on one line -> advance
8196 // Cursor starts and ends at column 0
8197 cx.set_state(indoc!(
8198 "fn a() {
8199 ˇ dog();
8200 cat();
8201 }"
8202 ));
8203 cx.update_editor(|editor, cx| {
8204 editor.toggle_comments(toggle_comments, cx);
8205 });
8206 cx.assert_editor_state(indoc!(
8207 "fn a() {
8208 // dog();
8209 ˇ cat();
8210 }"
8211 ));
8212}
8213
8214#[gpui::test]
8215async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
8216 init_test(cx, |_| {});
8217
8218 let mut cx = EditorTestContext::new(cx).await;
8219
8220 let html_language = Arc::new(
8221 Language::new(
8222 LanguageConfig {
8223 name: "HTML".into(),
8224 block_comment: Some(("<!-- ".into(), " -->".into())),
8225 ..Default::default()
8226 },
8227 Some(tree_sitter_html::language()),
8228 )
8229 .with_injection_query(
8230 r#"
8231 (script_element
8232 (raw_text) @content
8233 (#set! "language" "javascript"))
8234 "#,
8235 )
8236 .unwrap(),
8237 );
8238
8239 let javascript_language = Arc::new(Language::new(
8240 LanguageConfig {
8241 name: "JavaScript".into(),
8242 line_comments: vec!["// ".into()],
8243 ..Default::default()
8244 },
8245 Some(tree_sitter_typescript::language_tsx()),
8246 ));
8247
8248 cx.language_registry().add(html_language.clone());
8249 cx.language_registry().add(javascript_language.clone());
8250 cx.update_buffer(|buffer, cx| {
8251 buffer.set_language(Some(html_language), cx);
8252 });
8253
8254 // Toggle comments for empty selections
8255 cx.set_state(
8256 &r#"
8257 <p>A</p>ˇ
8258 <p>B</p>ˇ
8259 <p>C</p>ˇ
8260 "#
8261 .unindent(),
8262 );
8263 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8264 cx.assert_editor_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
8282 // Toggle comments for mixture of empty and non-empty selections, where
8283 // multiple selections occupy a given line.
8284 cx.set_state(
8285 &r#"
8286 <p>A«</p>
8287 <p>ˇ»B</p>ˇ
8288 <p>C«</p>
8289 <p>ˇ»D</p>ˇ
8290 "#
8291 .unindent(),
8292 );
8293
8294 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8295 cx.assert_editor_state(
8296 &r#"
8297 <!-- <p>A«</p>
8298 <p>ˇ»B</p>ˇ -->
8299 <!-- <p>C«</p>
8300 <p>ˇ»D</p>ˇ -->
8301 "#
8302 .unindent(),
8303 );
8304 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8305 cx.assert_editor_state(
8306 &r#"
8307 <p>A«</p>
8308 <p>ˇ»B</p>ˇ
8309 <p>C«</p>
8310 <p>ˇ»D</p>ˇ
8311 "#
8312 .unindent(),
8313 );
8314
8315 // Toggle comments when different languages are active for different
8316 // selections.
8317 cx.set_state(
8318 &r#"
8319 ˇ<script>
8320 ˇvar x = new Y();
8321 ˇ</script>
8322 "#
8323 .unindent(),
8324 );
8325 cx.executor().run_until_parked();
8326 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8327 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
8328 // Uncommenting and commenting from this position brings in even more wrong artifacts.
8329 cx.assert_editor_state(
8330 &r#"
8331 <!-- ˇ<script> -->
8332 // ˇvar x = new Y();
8333 // ˇ</script>
8334 "#
8335 .unindent(),
8336 );
8337}
8338
8339#[gpui::test]
8340fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
8341 init_test(cx, |_| {});
8342
8343 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8344 let multibuffer = cx.new_model(|cx| {
8345 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8346 multibuffer.push_excerpts(
8347 buffer.clone(),
8348 [
8349 ExcerptRange {
8350 context: Point::new(0, 0)..Point::new(0, 4),
8351 primary: None,
8352 },
8353 ExcerptRange {
8354 context: Point::new(1, 0)..Point::new(1, 4),
8355 primary: None,
8356 },
8357 ],
8358 cx,
8359 );
8360 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
8361 multibuffer
8362 });
8363
8364 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8365 view.update(cx, |view, cx| {
8366 assert_eq!(view.text(cx), "aaaa\nbbbb");
8367 view.change_selections(None, cx, |s| {
8368 s.select_ranges([
8369 Point::new(0, 0)..Point::new(0, 0),
8370 Point::new(1, 0)..Point::new(1, 0),
8371 ])
8372 });
8373
8374 view.handle_input("X", cx);
8375 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
8376 assert_eq!(
8377 view.selections.ranges(cx),
8378 [
8379 Point::new(0, 1)..Point::new(0, 1),
8380 Point::new(1, 1)..Point::new(1, 1),
8381 ]
8382 );
8383
8384 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
8385 view.change_selections(None, cx, |s| {
8386 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
8387 });
8388 view.backspace(&Default::default(), cx);
8389 assert_eq!(view.text(cx), "Xa\nbbb");
8390 assert_eq!(
8391 view.selections.ranges(cx),
8392 [Point::new(1, 0)..Point::new(1, 0)]
8393 );
8394
8395 view.change_selections(None, cx, |s| {
8396 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
8397 });
8398 view.backspace(&Default::default(), cx);
8399 assert_eq!(view.text(cx), "X\nbb");
8400 assert_eq!(
8401 view.selections.ranges(cx),
8402 [Point::new(0, 1)..Point::new(0, 1)]
8403 );
8404 });
8405}
8406
8407#[gpui::test]
8408fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
8409 init_test(cx, |_| {});
8410
8411 let markers = vec![('[', ']').into(), ('(', ')').into()];
8412 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
8413 indoc! {"
8414 [aaaa
8415 (bbbb]
8416 cccc)",
8417 },
8418 markers.clone(),
8419 );
8420 let excerpt_ranges = markers.into_iter().map(|marker| {
8421 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
8422 ExcerptRange {
8423 context,
8424 primary: None,
8425 }
8426 });
8427 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
8428 let multibuffer = cx.new_model(|cx| {
8429 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8430 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
8431 multibuffer
8432 });
8433
8434 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8435 view.update(cx, |view, cx| {
8436 let (expected_text, selection_ranges) = marked_text_ranges(
8437 indoc! {"
8438 aaaa
8439 bˇbbb
8440 bˇbbˇb
8441 cccc"
8442 },
8443 true,
8444 );
8445 assert_eq!(view.text(cx), expected_text);
8446 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
8447
8448 view.handle_input("X", cx);
8449
8450 let (expected_text, expected_selections) = marked_text_ranges(
8451 indoc! {"
8452 aaaa
8453 bXˇbbXb
8454 bXˇbbXˇb
8455 cccc"
8456 },
8457 false,
8458 );
8459 assert_eq!(view.text(cx), expected_text);
8460 assert_eq!(view.selections.ranges(cx), expected_selections);
8461
8462 view.newline(&Newline, cx);
8463 let (expected_text, expected_selections) = marked_text_ranges(
8464 indoc! {"
8465 aaaa
8466 bX
8467 ˇbbX
8468 b
8469 bX
8470 ˇbbX
8471 ˇb
8472 cccc"
8473 },
8474 false,
8475 );
8476 assert_eq!(view.text(cx), expected_text);
8477 assert_eq!(view.selections.ranges(cx), expected_selections);
8478 });
8479}
8480
8481#[gpui::test]
8482fn test_refresh_selections(cx: &mut TestAppContext) {
8483 init_test(cx, |_| {});
8484
8485 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8486 let mut excerpt1_id = None;
8487 let multibuffer = cx.new_model(|cx| {
8488 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8489 excerpt1_id = multibuffer
8490 .push_excerpts(
8491 buffer.clone(),
8492 [
8493 ExcerptRange {
8494 context: Point::new(0, 0)..Point::new(1, 4),
8495 primary: None,
8496 },
8497 ExcerptRange {
8498 context: Point::new(1, 0)..Point::new(2, 4),
8499 primary: None,
8500 },
8501 ],
8502 cx,
8503 )
8504 .into_iter()
8505 .next();
8506 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8507 multibuffer
8508 });
8509
8510 let editor = cx.add_window(|cx| {
8511 let mut editor = build_editor(multibuffer.clone(), cx);
8512 let snapshot = editor.snapshot(cx);
8513 editor.change_selections(None, cx, |s| {
8514 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
8515 });
8516 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
8517 assert_eq!(
8518 editor.selections.ranges(cx),
8519 [
8520 Point::new(1, 3)..Point::new(1, 3),
8521 Point::new(2, 1)..Point::new(2, 1),
8522 ]
8523 );
8524 editor
8525 });
8526
8527 // Refreshing selections is a no-op when excerpts haven't changed.
8528 _ = editor.update(cx, |editor, cx| {
8529 editor.change_selections(None, cx, |s| s.refresh());
8530 assert_eq!(
8531 editor.selections.ranges(cx),
8532 [
8533 Point::new(1, 3)..Point::new(1, 3),
8534 Point::new(2, 1)..Point::new(2, 1),
8535 ]
8536 );
8537 });
8538
8539 multibuffer.update(cx, |multibuffer, cx| {
8540 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8541 });
8542 _ = editor.update(cx, |editor, cx| {
8543 // Removing an excerpt causes the first selection to become degenerate.
8544 assert_eq!(
8545 editor.selections.ranges(cx),
8546 [
8547 Point::new(0, 0)..Point::new(0, 0),
8548 Point::new(0, 1)..Point::new(0, 1)
8549 ]
8550 );
8551
8552 // Refreshing selections will relocate the first selection to the original buffer
8553 // location.
8554 editor.change_selections(None, cx, |s| s.refresh());
8555 assert_eq!(
8556 editor.selections.ranges(cx),
8557 [
8558 Point::new(0, 1)..Point::new(0, 1),
8559 Point::new(0, 3)..Point::new(0, 3)
8560 ]
8561 );
8562 assert!(editor.selections.pending_anchor().is_some());
8563 });
8564}
8565
8566#[gpui::test]
8567fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
8568 init_test(cx, |_| {});
8569
8570 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8571 let mut excerpt1_id = None;
8572 let multibuffer = cx.new_model(|cx| {
8573 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8574 excerpt1_id = multibuffer
8575 .push_excerpts(
8576 buffer.clone(),
8577 [
8578 ExcerptRange {
8579 context: Point::new(0, 0)..Point::new(1, 4),
8580 primary: None,
8581 },
8582 ExcerptRange {
8583 context: Point::new(1, 0)..Point::new(2, 4),
8584 primary: None,
8585 },
8586 ],
8587 cx,
8588 )
8589 .into_iter()
8590 .next();
8591 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8592 multibuffer
8593 });
8594
8595 let editor = cx.add_window(|cx| {
8596 let mut editor = build_editor(multibuffer.clone(), cx);
8597 let snapshot = editor.snapshot(cx);
8598 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
8599 assert_eq!(
8600 editor.selections.ranges(cx),
8601 [Point::new(1, 3)..Point::new(1, 3)]
8602 );
8603 editor
8604 });
8605
8606 multibuffer.update(cx, |multibuffer, cx| {
8607 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8608 });
8609 _ = editor.update(cx, |editor, cx| {
8610 assert_eq!(
8611 editor.selections.ranges(cx),
8612 [Point::new(0, 0)..Point::new(0, 0)]
8613 );
8614
8615 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
8616 editor.change_selections(None, cx, |s| s.refresh());
8617 assert_eq!(
8618 editor.selections.ranges(cx),
8619 [Point::new(0, 3)..Point::new(0, 3)]
8620 );
8621 assert!(editor.selections.pending_anchor().is_some());
8622 });
8623}
8624
8625#[gpui::test]
8626async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
8627 init_test(cx, |_| {});
8628
8629 let language = Arc::new(
8630 Language::new(
8631 LanguageConfig {
8632 brackets: BracketPairConfig {
8633 pairs: vec![
8634 BracketPair {
8635 start: "{".to_string(),
8636 end: "}".to_string(),
8637 close: true,
8638 surround: true,
8639 newline: true,
8640 },
8641 BracketPair {
8642 start: "/* ".to_string(),
8643 end: " */".to_string(),
8644 close: true,
8645 surround: true,
8646 newline: true,
8647 },
8648 ],
8649 ..Default::default()
8650 },
8651 ..Default::default()
8652 },
8653 Some(tree_sitter_rust::language()),
8654 )
8655 .with_indents_query("")
8656 .unwrap(),
8657 );
8658
8659 let text = concat!(
8660 "{ }\n", //
8661 " x\n", //
8662 " /* */\n", //
8663 "x\n", //
8664 "{{} }\n", //
8665 );
8666
8667 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
8668 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
8669 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
8670 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
8671 .await;
8672
8673 view.update(cx, |view, cx| {
8674 view.change_selections(None, cx, |s| {
8675 s.select_display_ranges([
8676 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
8677 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
8678 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
8679 ])
8680 });
8681 view.newline(&Newline, cx);
8682
8683 assert_eq!(
8684 view.buffer().read(cx).read(cx).text(),
8685 concat!(
8686 "{ \n", // Suppress rustfmt
8687 "\n", //
8688 "}\n", //
8689 " x\n", //
8690 " /* \n", //
8691 " \n", //
8692 " */\n", //
8693 "x\n", //
8694 "{{} \n", //
8695 "}\n", //
8696 )
8697 );
8698 });
8699}
8700
8701#[gpui::test]
8702fn test_highlighted_ranges(cx: &mut TestAppContext) {
8703 init_test(cx, |_| {});
8704
8705 let editor = cx.add_window(|cx| {
8706 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
8707 build_editor(buffer.clone(), cx)
8708 });
8709
8710 _ = editor.update(cx, |editor, cx| {
8711 struct Type1;
8712 struct Type2;
8713
8714 let buffer = editor.buffer.read(cx).snapshot(cx);
8715
8716 let anchor_range =
8717 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
8718
8719 editor.highlight_background::<Type1>(
8720 &[
8721 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
8722 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
8723 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
8724 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
8725 ],
8726 |_| Hsla::red(),
8727 cx,
8728 );
8729 editor.highlight_background::<Type2>(
8730 &[
8731 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
8732 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
8733 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
8734 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
8735 ],
8736 |_| Hsla::green(),
8737 cx,
8738 );
8739
8740 let snapshot = editor.snapshot(cx);
8741 let mut highlighted_ranges = editor.background_highlights_in_range(
8742 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
8743 &snapshot,
8744 cx.theme().colors(),
8745 );
8746 // Enforce a consistent ordering based on color without relying on the ordering of the
8747 // highlight's `TypeId` which is non-executor.
8748 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
8749 assert_eq!(
8750 highlighted_ranges,
8751 &[
8752 (
8753 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
8754 Hsla::red(),
8755 ),
8756 (
8757 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
8758 Hsla::red(),
8759 ),
8760 (
8761 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
8762 Hsla::green(),
8763 ),
8764 (
8765 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
8766 Hsla::green(),
8767 ),
8768 ]
8769 );
8770 assert_eq!(
8771 editor.background_highlights_in_range(
8772 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
8773 &snapshot,
8774 cx.theme().colors(),
8775 ),
8776 &[(
8777 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
8778 Hsla::red(),
8779 )]
8780 );
8781 });
8782}
8783
8784#[gpui::test]
8785async fn test_following(cx: &mut gpui::TestAppContext) {
8786 init_test(cx, |_| {});
8787
8788 let fs = FakeFs::new(cx.executor());
8789 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8790
8791 let buffer = project.update(cx, |project, cx| {
8792 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
8793 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
8794 });
8795 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
8796 let follower = cx.update(|cx| {
8797 cx.open_window(
8798 WindowOptions {
8799 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
8800 gpui::Point::new(px(0.), px(0.)),
8801 gpui::Point::new(px(10.), px(80.)),
8802 ))),
8803 ..Default::default()
8804 },
8805 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
8806 )
8807 .unwrap()
8808 });
8809
8810 let is_still_following = Rc::new(RefCell::new(true));
8811 let follower_edit_event_count = Rc::new(RefCell::new(0));
8812 let pending_update = Rc::new(RefCell::new(None));
8813 _ = follower.update(cx, {
8814 let update = pending_update.clone();
8815 let is_still_following = is_still_following.clone();
8816 let follower_edit_event_count = follower_edit_event_count.clone();
8817 |_, cx| {
8818 cx.subscribe(
8819 &leader.root_view(cx).unwrap(),
8820 move |_, leader, event, cx| {
8821 leader
8822 .read(cx)
8823 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
8824 },
8825 )
8826 .detach();
8827
8828 cx.subscribe(
8829 &follower.root_view(cx).unwrap(),
8830 move |_, _, event: &EditorEvent, _cx| {
8831 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
8832 *is_still_following.borrow_mut() = false;
8833 }
8834
8835 if let EditorEvent::BufferEdited = event {
8836 *follower_edit_event_count.borrow_mut() += 1;
8837 }
8838 },
8839 )
8840 .detach();
8841 }
8842 });
8843
8844 // Update the selections only
8845 _ = leader.update(cx, |leader, cx| {
8846 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
8847 });
8848 follower
8849 .update(cx, |follower, cx| {
8850 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8851 })
8852 .unwrap()
8853 .await
8854 .unwrap();
8855 _ = follower.update(cx, |follower, cx| {
8856 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
8857 });
8858 assert!(*is_still_following.borrow());
8859 assert_eq!(*follower_edit_event_count.borrow(), 0);
8860
8861 // Update the scroll position only
8862 _ = leader.update(cx, |leader, cx| {
8863 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
8864 });
8865 follower
8866 .update(cx, |follower, cx| {
8867 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8868 })
8869 .unwrap()
8870 .await
8871 .unwrap();
8872 assert_eq!(
8873 follower
8874 .update(cx, |follower, cx| follower.scroll_position(cx))
8875 .unwrap(),
8876 gpui::Point::new(1.5, 3.5)
8877 );
8878 assert!(*is_still_following.borrow());
8879 assert_eq!(*follower_edit_event_count.borrow(), 0);
8880
8881 // Update the selections and scroll position. The follower's scroll position is updated
8882 // via autoscroll, not via the leader's exact scroll position.
8883 _ = leader.update(cx, |leader, cx| {
8884 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
8885 leader.request_autoscroll(Autoscroll::newest(), cx);
8886 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
8887 });
8888 follower
8889 .update(cx, |follower, cx| {
8890 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8891 })
8892 .unwrap()
8893 .await
8894 .unwrap();
8895 _ = follower.update(cx, |follower, cx| {
8896 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
8897 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
8898 });
8899 assert!(*is_still_following.borrow());
8900
8901 // Creating a pending selection that precedes another selection
8902 _ = leader.update(cx, |leader, cx| {
8903 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
8904 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
8905 });
8906 follower
8907 .update(cx, |follower, cx| {
8908 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8909 })
8910 .unwrap()
8911 .await
8912 .unwrap();
8913 _ = follower.update(cx, |follower, cx| {
8914 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
8915 });
8916 assert!(*is_still_following.borrow());
8917
8918 // Extend the pending selection so that it surrounds another selection
8919 _ = leader.update(cx, |leader, cx| {
8920 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
8921 });
8922 follower
8923 .update(cx, |follower, cx| {
8924 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8925 })
8926 .unwrap()
8927 .await
8928 .unwrap();
8929 _ = follower.update(cx, |follower, cx| {
8930 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
8931 });
8932
8933 // Scrolling locally breaks the follow
8934 _ = follower.update(cx, |follower, cx| {
8935 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
8936 follower.set_scroll_anchor(
8937 ScrollAnchor {
8938 anchor: top_anchor,
8939 offset: gpui::Point::new(0.0, 0.5),
8940 },
8941 cx,
8942 );
8943 });
8944 assert!(!(*is_still_following.borrow()));
8945}
8946
8947#[gpui::test]
8948async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
8949 init_test(cx, |_| {});
8950
8951 let fs = FakeFs::new(cx.executor());
8952 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8953 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8954 let pane = workspace
8955 .update(cx, |workspace, _| workspace.active_pane().clone())
8956 .unwrap();
8957
8958 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
8959
8960 let leader = pane.update(cx, |_, cx| {
8961 let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
8962 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
8963 });
8964
8965 // Start following the editor when it has no excerpts.
8966 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
8967 let follower_1 = cx
8968 .update_window(*workspace.deref(), |_, cx| {
8969 Editor::from_state_proto(
8970 workspace.root_view(cx).unwrap(),
8971 ViewId {
8972 creator: Default::default(),
8973 id: 0,
8974 },
8975 &mut state_message,
8976 cx,
8977 )
8978 })
8979 .unwrap()
8980 .unwrap()
8981 .await
8982 .unwrap();
8983
8984 let update_message = Rc::new(RefCell::new(None));
8985 follower_1.update(cx, {
8986 let update = update_message.clone();
8987 |_, cx| {
8988 cx.subscribe(&leader, move |_, leader, event, cx| {
8989 leader
8990 .read(cx)
8991 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
8992 })
8993 .detach();
8994 }
8995 });
8996
8997 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
8998 (
8999 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9000 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9001 )
9002 });
9003
9004 // Insert some excerpts.
9005 leader.update(cx, |leader, cx| {
9006 leader.buffer.update(cx, |multibuffer, cx| {
9007 let excerpt_ids = multibuffer.push_excerpts(
9008 buffer_1.clone(),
9009 [
9010 ExcerptRange {
9011 context: 1..6,
9012 primary: None,
9013 },
9014 ExcerptRange {
9015 context: 12..15,
9016 primary: None,
9017 },
9018 ExcerptRange {
9019 context: 0..3,
9020 primary: None,
9021 },
9022 ],
9023 cx,
9024 );
9025 multibuffer.insert_excerpts_after(
9026 excerpt_ids[0],
9027 buffer_2.clone(),
9028 [
9029 ExcerptRange {
9030 context: 8..12,
9031 primary: None,
9032 },
9033 ExcerptRange {
9034 context: 0..6,
9035 primary: None,
9036 },
9037 ],
9038 cx,
9039 );
9040 });
9041 });
9042
9043 // Apply the update of adding the excerpts.
9044 follower_1
9045 .update(cx, |follower, cx| {
9046 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9047 })
9048 .await
9049 .unwrap();
9050 assert_eq!(
9051 follower_1.update(cx, |editor, cx| editor.text(cx)),
9052 leader.update(cx, |editor, cx| editor.text(cx))
9053 );
9054 update_message.borrow_mut().take();
9055
9056 // Start following separately after it already has excerpts.
9057 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9058 let follower_2 = cx
9059 .update_window(*workspace.deref(), |_, cx| {
9060 Editor::from_state_proto(
9061 workspace.root_view(cx).unwrap().clone(),
9062 ViewId {
9063 creator: Default::default(),
9064 id: 0,
9065 },
9066 &mut state_message,
9067 cx,
9068 )
9069 })
9070 .unwrap()
9071 .unwrap()
9072 .await
9073 .unwrap();
9074 assert_eq!(
9075 follower_2.update(cx, |editor, cx| editor.text(cx)),
9076 leader.update(cx, |editor, cx| editor.text(cx))
9077 );
9078
9079 // Remove some excerpts.
9080 leader.update(cx, |leader, cx| {
9081 leader.buffer.update(cx, |multibuffer, cx| {
9082 let excerpt_ids = multibuffer.excerpt_ids();
9083 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9084 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9085 });
9086 });
9087
9088 // Apply the update of removing the excerpts.
9089 follower_1
9090 .update(cx, |follower, cx| {
9091 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9092 })
9093 .await
9094 .unwrap();
9095 follower_2
9096 .update(cx, |follower, cx| {
9097 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9098 })
9099 .await
9100 .unwrap();
9101 update_message.borrow_mut().take();
9102 assert_eq!(
9103 follower_1.update(cx, |editor, cx| editor.text(cx)),
9104 leader.update(cx, |editor, cx| editor.text(cx))
9105 );
9106}
9107
9108#[gpui::test]
9109async fn go_to_prev_overlapping_diagnostic(
9110 executor: BackgroundExecutor,
9111 cx: &mut gpui::TestAppContext,
9112) {
9113 init_test(cx, |_| {});
9114
9115 let mut cx = EditorTestContext::new(cx).await;
9116 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9117
9118 cx.set_state(indoc! {"
9119 ˇfn func(abc def: i32) -> u32 {
9120 }
9121 "});
9122
9123 cx.update(|cx| {
9124 project.update(cx, |project, cx| {
9125 project
9126 .update_diagnostics(
9127 LanguageServerId(0),
9128 lsp::PublishDiagnosticsParams {
9129 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9130 version: None,
9131 diagnostics: vec![
9132 lsp::Diagnostic {
9133 range: lsp::Range::new(
9134 lsp::Position::new(0, 11),
9135 lsp::Position::new(0, 12),
9136 ),
9137 severity: Some(lsp::DiagnosticSeverity::ERROR),
9138 ..Default::default()
9139 },
9140 lsp::Diagnostic {
9141 range: lsp::Range::new(
9142 lsp::Position::new(0, 12),
9143 lsp::Position::new(0, 15),
9144 ),
9145 severity: Some(lsp::DiagnosticSeverity::ERROR),
9146 ..Default::default()
9147 },
9148 lsp::Diagnostic {
9149 range: lsp::Range::new(
9150 lsp::Position::new(0, 25),
9151 lsp::Position::new(0, 28),
9152 ),
9153 severity: Some(lsp::DiagnosticSeverity::ERROR),
9154 ..Default::default()
9155 },
9156 ],
9157 },
9158 &[],
9159 cx,
9160 )
9161 .unwrap()
9162 });
9163 });
9164
9165 executor.run_until_parked();
9166
9167 cx.update_editor(|editor, cx| {
9168 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9169 });
9170
9171 cx.assert_editor_state(indoc! {"
9172 fn func(abc def: i32) -> ˇu32 {
9173 }
9174 "});
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
9204#[gpui::test]
9205async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
9206 init_test(cx, |_| {});
9207
9208 let mut cx = EditorTestContext::new(cx).await;
9209
9210 cx.set_state(indoc! {"
9211 fn func(abˇc def: i32) -> u32 {
9212 }
9213 "});
9214 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9215
9216 cx.update(|cx| {
9217 project.update(cx, |project, cx| {
9218 project.update_diagnostics(
9219 LanguageServerId(0),
9220 lsp::PublishDiagnosticsParams {
9221 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9222 version: None,
9223 diagnostics: vec![lsp::Diagnostic {
9224 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
9225 severity: Some(lsp::DiagnosticSeverity::ERROR),
9226 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
9227 ..Default::default()
9228 }],
9229 },
9230 &[],
9231 cx,
9232 )
9233 })
9234 }).unwrap();
9235 cx.run_until_parked();
9236 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
9237 cx.run_until_parked();
9238 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
9239}
9240
9241#[gpui::test]
9242async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9243 init_test(cx, |_| {});
9244
9245 let mut cx = EditorTestContext::new(cx).await;
9246
9247 let diff_base = r#"
9248 use some::mod;
9249
9250 const A: u32 = 42;
9251
9252 fn main() {
9253 println!("hello");
9254
9255 println!("world");
9256 }
9257 "#
9258 .unindent();
9259
9260 // Edits are modified, removed, modified, added
9261 cx.set_state(
9262 &r#"
9263 use some::modified;
9264
9265 ˇ
9266 fn main() {
9267 println!("hello there");
9268
9269 println!("around the");
9270 println!("world");
9271 }
9272 "#
9273 .unindent(),
9274 );
9275
9276 cx.set_diff_base(Some(&diff_base));
9277 executor.run_until_parked();
9278
9279 cx.update_editor(|editor, cx| {
9280 //Wrap around the bottom of the buffer
9281 for _ in 0..3 {
9282 editor.go_to_hunk(&GoToHunk, cx);
9283 }
9284 });
9285
9286 cx.assert_editor_state(
9287 &r#"
9288 ˇuse some::modified;
9289
9290
9291 fn main() {
9292 println!("hello there");
9293
9294 println!("around the");
9295 println!("world");
9296 }
9297 "#
9298 .unindent(),
9299 );
9300
9301 cx.update_editor(|editor, cx| {
9302 //Wrap around the top of the buffer
9303 for _ in 0..2 {
9304 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9305 }
9306 });
9307
9308 cx.assert_editor_state(
9309 &r#"
9310 use some::modified;
9311
9312
9313 fn main() {
9314 ˇ println!("hello there");
9315
9316 println!("around the");
9317 println!("world");
9318 }
9319 "#
9320 .unindent(),
9321 );
9322
9323 cx.update_editor(|editor, cx| {
9324 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9325 });
9326
9327 cx.assert_editor_state(
9328 &r#"
9329 use some::modified;
9330
9331 ˇ
9332 fn main() {
9333 println!("hello there");
9334
9335 println!("around the");
9336 println!("world");
9337 }
9338 "#
9339 .unindent(),
9340 );
9341
9342 cx.update_editor(|editor, cx| {
9343 for _ in 0..3 {
9344 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9345 }
9346 });
9347
9348 cx.assert_editor_state(
9349 &r#"
9350 use some::modified;
9351
9352
9353 fn main() {
9354 ˇ println!("hello there");
9355
9356 println!("around the");
9357 println!("world");
9358 }
9359 "#
9360 .unindent(),
9361 );
9362
9363 cx.update_editor(|editor, cx| {
9364 editor.fold(&Fold, cx);
9365
9366 //Make sure that the fold only gets one hunk
9367 for _ in 0..4 {
9368 editor.go_to_hunk(&GoToHunk, cx);
9369 }
9370 });
9371
9372 cx.assert_editor_state(
9373 &r#"
9374 ˇuse some::modified;
9375
9376
9377 fn main() {
9378 println!("hello there");
9379
9380 println!("around the");
9381 println!("world");
9382 }
9383 "#
9384 .unindent(),
9385 );
9386}
9387
9388#[test]
9389fn test_split_words() {
9390 fn split(text: &str) -> Vec<&str> {
9391 split_words(text).collect()
9392 }
9393
9394 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
9395 assert_eq!(split("hello_world"), &["hello_", "world"]);
9396 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
9397 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
9398 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
9399 assert_eq!(split("helloworld"), &["helloworld"]);
9400
9401 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
9402}
9403
9404#[gpui::test]
9405async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
9406 init_test(cx, |_| {});
9407
9408 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
9409 let mut assert = |before, after| {
9410 let _state_context = cx.set_state(before);
9411 cx.update_editor(|editor, cx| {
9412 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
9413 });
9414 cx.assert_editor_state(after);
9415 };
9416
9417 // Outside bracket jumps to outside of matching bracket
9418 assert("console.logˇ(var);", "console.log(var)ˇ;");
9419 assert("console.log(var)ˇ;", "console.logˇ(var);");
9420
9421 // Inside bracket jumps to inside of matching bracket
9422 assert("console.log(ˇvar);", "console.log(varˇ);");
9423 assert("console.log(varˇ);", "console.log(ˇvar);");
9424
9425 // When outside a bracket and inside, favor jumping to the inside bracket
9426 assert(
9427 "console.log('foo', [1, 2, 3]ˇ);",
9428 "console.log(ˇ'foo', [1, 2, 3]);",
9429 );
9430 assert(
9431 "console.log(ˇ'foo', [1, 2, 3]);",
9432 "console.log('foo', [1, 2, 3]ˇ);",
9433 );
9434
9435 // Bias forward if two options are equally likely
9436 assert(
9437 "let result = curried_fun()ˇ();",
9438 "let result = curried_fun()()ˇ;",
9439 );
9440
9441 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
9442 assert(
9443 indoc! {"
9444 function test() {
9445 console.log('test')ˇ
9446 }"},
9447 indoc! {"
9448 function test() {
9449 console.logˇ('test')
9450 }"},
9451 );
9452}
9453
9454#[gpui::test]
9455async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
9456 init_test(cx, |_| {});
9457
9458 let fs = FakeFs::new(cx.executor());
9459 fs.insert_tree(
9460 "/a",
9461 json!({
9462 "main.rs": "fn main() { let a = 5; }",
9463 "other.rs": "// Test file",
9464 }),
9465 )
9466 .await;
9467 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9468
9469 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9470 language_registry.add(Arc::new(Language::new(
9471 LanguageConfig {
9472 name: "Rust".into(),
9473 matcher: LanguageMatcher {
9474 path_suffixes: vec!["rs".to_string()],
9475 ..Default::default()
9476 },
9477 brackets: BracketPairConfig {
9478 pairs: vec![BracketPair {
9479 start: "{".to_string(),
9480 end: "}".to_string(),
9481 close: true,
9482 surround: true,
9483 newline: true,
9484 }],
9485 disabled_scopes_by_bracket_ix: Vec::new(),
9486 },
9487 ..Default::default()
9488 },
9489 Some(tree_sitter_rust::language()),
9490 )));
9491 let mut fake_servers = language_registry.register_fake_lsp_adapter(
9492 "Rust",
9493 FakeLspAdapter {
9494 capabilities: lsp::ServerCapabilities {
9495 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
9496 first_trigger_character: "{".to_string(),
9497 more_trigger_character: None,
9498 }),
9499 ..Default::default()
9500 },
9501 ..Default::default()
9502 },
9503 );
9504
9505 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9506
9507 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9508
9509 let worktree_id = workspace
9510 .update(cx, |workspace, cx| {
9511 workspace.project().update(cx, |project, cx| {
9512 project.worktrees(cx).next().unwrap().read(cx).id()
9513 })
9514 })
9515 .unwrap();
9516
9517 let buffer = project
9518 .update(cx, |project, cx| {
9519 project.open_local_buffer("/a/main.rs", cx)
9520 })
9521 .await
9522 .unwrap();
9523 cx.executor().run_until_parked();
9524 cx.executor().start_waiting();
9525 let fake_server = fake_servers.next().await.unwrap();
9526 let editor_handle = workspace
9527 .update(cx, |workspace, cx| {
9528 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
9529 })
9530 .unwrap()
9531 .await
9532 .unwrap()
9533 .downcast::<Editor>()
9534 .unwrap();
9535
9536 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
9537 assert_eq!(
9538 params.text_document_position.text_document.uri,
9539 lsp::Url::from_file_path("/a/main.rs").unwrap(),
9540 );
9541 assert_eq!(
9542 params.text_document_position.position,
9543 lsp::Position::new(0, 21),
9544 );
9545
9546 Ok(Some(vec![lsp::TextEdit {
9547 new_text: "]".to_string(),
9548 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
9549 }]))
9550 });
9551
9552 editor_handle.update(cx, |editor, cx| {
9553 editor.focus(cx);
9554 editor.change_selections(None, cx, |s| {
9555 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
9556 });
9557 editor.handle_input("{", cx);
9558 });
9559
9560 cx.executor().run_until_parked();
9561
9562 buffer.update(cx, |buffer, _| {
9563 assert_eq!(
9564 buffer.text(),
9565 "fn main() { let a = {5}; }",
9566 "No extra braces from on type formatting should appear in the buffer"
9567 )
9568 });
9569}
9570
9571#[gpui::test]
9572async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
9573 init_test(cx, |_| {});
9574
9575 let fs = FakeFs::new(cx.executor());
9576 fs.insert_tree(
9577 "/a",
9578 json!({
9579 "main.rs": "fn main() { let a = 5; }",
9580 "other.rs": "// Test file",
9581 }),
9582 )
9583 .await;
9584
9585 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9586
9587 let server_restarts = Arc::new(AtomicUsize::new(0));
9588 let closure_restarts = Arc::clone(&server_restarts);
9589 let language_server_name = "test language server";
9590 let language_name: LanguageName = "Rust".into();
9591
9592 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9593 language_registry.add(Arc::new(Language::new(
9594 LanguageConfig {
9595 name: language_name.clone(),
9596 matcher: LanguageMatcher {
9597 path_suffixes: vec!["rs".to_string()],
9598 ..Default::default()
9599 },
9600 ..Default::default()
9601 },
9602 Some(tree_sitter_rust::language()),
9603 )));
9604 let mut fake_servers = language_registry.register_fake_lsp_adapter(
9605 "Rust",
9606 FakeLspAdapter {
9607 name: language_server_name,
9608 initialization_options: Some(json!({
9609 "testOptionValue": true
9610 })),
9611 initializer: Some(Box::new(move |fake_server| {
9612 let task_restarts = Arc::clone(&closure_restarts);
9613 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
9614 task_restarts.fetch_add(1, atomic::Ordering::Release);
9615 futures::future::ready(Ok(()))
9616 });
9617 })),
9618 ..Default::default()
9619 },
9620 );
9621
9622 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9623 let _buffer = project
9624 .update(cx, |project, cx| {
9625 project.open_local_buffer("/a/main.rs", cx)
9626 })
9627 .await
9628 .unwrap();
9629 let _fake_server = fake_servers.next().await.unwrap();
9630 update_test_language_settings(cx, |language_settings| {
9631 language_settings.languages.insert(
9632 language_name.clone(),
9633 LanguageSettingsContent {
9634 tab_size: NonZeroU32::new(8),
9635 ..Default::default()
9636 },
9637 );
9638 });
9639 cx.executor().run_until_parked();
9640 assert_eq!(
9641 server_restarts.load(atomic::Ordering::Acquire),
9642 0,
9643 "Should not restart LSP server on an unrelated change"
9644 );
9645
9646 update_test_project_settings(cx, |project_settings| {
9647 project_settings.lsp.insert(
9648 "Some other server name".into(),
9649 LspSettings {
9650 binary: None,
9651 settings: None,
9652 initialization_options: Some(json!({
9653 "some other init value": false
9654 })),
9655 },
9656 );
9657 });
9658 cx.executor().run_until_parked();
9659 assert_eq!(
9660 server_restarts.load(atomic::Ordering::Acquire),
9661 0,
9662 "Should not restart LSP server on an unrelated LSP settings change"
9663 );
9664
9665 update_test_project_settings(cx, |project_settings| {
9666 project_settings.lsp.insert(
9667 language_server_name.into(),
9668 LspSettings {
9669 binary: None,
9670 settings: None,
9671 initialization_options: Some(json!({
9672 "anotherInitValue": false
9673 })),
9674 },
9675 );
9676 });
9677 cx.executor().run_until_parked();
9678 assert_eq!(
9679 server_restarts.load(atomic::Ordering::Acquire),
9680 1,
9681 "Should restart LSP server on a related LSP settings change"
9682 );
9683
9684 update_test_project_settings(cx, |project_settings| {
9685 project_settings.lsp.insert(
9686 language_server_name.into(),
9687 LspSettings {
9688 binary: None,
9689 settings: None,
9690 initialization_options: Some(json!({
9691 "anotherInitValue": false
9692 })),
9693 },
9694 );
9695 });
9696 cx.executor().run_until_parked();
9697 assert_eq!(
9698 server_restarts.load(atomic::Ordering::Acquire),
9699 1,
9700 "Should not restart LSP server on a related LSP settings change that is the same"
9701 );
9702
9703 update_test_project_settings(cx, |project_settings| {
9704 project_settings.lsp.insert(
9705 language_server_name.into(),
9706 LspSettings {
9707 binary: None,
9708 settings: None,
9709 initialization_options: None,
9710 },
9711 );
9712 });
9713 cx.executor().run_until_parked();
9714 assert_eq!(
9715 server_restarts.load(atomic::Ordering::Acquire),
9716 2,
9717 "Should restart LSP server on another related LSP settings change"
9718 );
9719}
9720
9721#[gpui::test]
9722async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
9723 init_test(cx, |_| {});
9724
9725 let mut cx = EditorLspTestContext::new_rust(
9726 lsp::ServerCapabilities {
9727 completion_provider: Some(lsp::CompletionOptions {
9728 trigger_characters: Some(vec![".".to_string()]),
9729 resolve_provider: Some(true),
9730 ..Default::default()
9731 }),
9732 ..Default::default()
9733 },
9734 cx,
9735 )
9736 .await;
9737
9738 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9739 cx.simulate_keystroke(".");
9740 let completion_item = lsp::CompletionItem {
9741 label: "some".into(),
9742 kind: Some(lsp::CompletionItemKind::SNIPPET),
9743 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9744 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9745 kind: lsp::MarkupKind::Markdown,
9746 value: "```rust\nSome(2)\n```".to_string(),
9747 })),
9748 deprecated: Some(false),
9749 sort_text: Some("fffffff2".to_string()),
9750 filter_text: Some("some".to_string()),
9751 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9752 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9753 range: lsp::Range {
9754 start: lsp::Position {
9755 line: 0,
9756 character: 22,
9757 },
9758 end: lsp::Position {
9759 line: 0,
9760 character: 22,
9761 },
9762 },
9763 new_text: "Some(2)".to_string(),
9764 })),
9765 additional_text_edits: Some(vec![lsp::TextEdit {
9766 range: lsp::Range {
9767 start: lsp::Position {
9768 line: 0,
9769 character: 20,
9770 },
9771 end: lsp::Position {
9772 line: 0,
9773 character: 22,
9774 },
9775 },
9776 new_text: "".to_string(),
9777 }]),
9778 ..Default::default()
9779 };
9780
9781 let closure_completion_item = completion_item.clone();
9782 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9783 let task_completion_item = closure_completion_item.clone();
9784 async move {
9785 Ok(Some(lsp::CompletionResponse::Array(vec![
9786 task_completion_item,
9787 ])))
9788 }
9789 });
9790
9791 request.next().await;
9792
9793 cx.condition(|editor, _| editor.context_menu_visible())
9794 .await;
9795 let apply_additional_edits = cx.update_editor(|editor, cx| {
9796 editor
9797 .confirm_completion(&ConfirmCompletion::default(), cx)
9798 .unwrap()
9799 });
9800 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
9801
9802 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
9803 let task_completion_item = completion_item.clone();
9804 async move { Ok(task_completion_item) }
9805 })
9806 .next()
9807 .await
9808 .unwrap();
9809 apply_additional_edits.await.unwrap();
9810 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
9811}
9812
9813#[gpui::test]
9814async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
9815 init_test(cx, |_| {});
9816
9817 let mut cx = EditorLspTestContext::new(
9818 Language::new(
9819 LanguageConfig {
9820 matcher: LanguageMatcher {
9821 path_suffixes: vec!["jsx".into()],
9822 ..Default::default()
9823 },
9824 overrides: [(
9825 "element".into(),
9826 LanguageConfigOverride {
9827 word_characters: Override::Set(['-'].into_iter().collect()),
9828 ..Default::default()
9829 },
9830 )]
9831 .into_iter()
9832 .collect(),
9833 ..Default::default()
9834 },
9835 Some(tree_sitter_typescript::language_tsx()),
9836 )
9837 .with_override_query("(jsx_self_closing_element) @element")
9838 .unwrap(),
9839 lsp::ServerCapabilities {
9840 completion_provider: Some(lsp::CompletionOptions {
9841 trigger_characters: Some(vec![":".to_string()]),
9842 ..Default::default()
9843 }),
9844 ..Default::default()
9845 },
9846 cx,
9847 )
9848 .await;
9849
9850 cx.lsp
9851 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9852 Ok(Some(lsp::CompletionResponse::Array(vec![
9853 lsp::CompletionItem {
9854 label: "bg-blue".into(),
9855 ..Default::default()
9856 },
9857 lsp::CompletionItem {
9858 label: "bg-red".into(),
9859 ..Default::default()
9860 },
9861 lsp::CompletionItem {
9862 label: "bg-yellow".into(),
9863 ..Default::default()
9864 },
9865 ])))
9866 });
9867
9868 cx.set_state(r#"<p class="bgˇ" />"#);
9869
9870 // Trigger completion when typing a dash, because the dash is an extra
9871 // word character in the 'element' scope, which contains the cursor.
9872 cx.simulate_keystroke("-");
9873 cx.executor().run_until_parked();
9874 cx.update_editor(|editor, _| {
9875 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9876 assert_eq!(
9877 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9878 &["bg-red", "bg-blue", "bg-yellow"]
9879 );
9880 } else {
9881 panic!("expected completion menu to be open");
9882 }
9883 });
9884
9885 cx.simulate_keystroke("l");
9886 cx.executor().run_until_parked();
9887 cx.update_editor(|editor, _| {
9888 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9889 assert_eq!(
9890 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9891 &["bg-blue", "bg-yellow"]
9892 );
9893 } else {
9894 panic!("expected completion menu to be open");
9895 }
9896 });
9897
9898 // When filtering completions, consider the character after the '-' to
9899 // be the start of a subword.
9900 cx.set_state(r#"<p class="yelˇ" />"#);
9901 cx.simulate_keystroke("l");
9902 cx.executor().run_until_parked();
9903 cx.update_editor(|editor, _| {
9904 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9905 assert_eq!(
9906 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9907 &["bg-yellow"]
9908 );
9909 } else {
9910 panic!("expected completion menu to be open");
9911 }
9912 });
9913}
9914
9915#[gpui::test]
9916async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
9917 init_test(cx, |settings| {
9918 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
9919 FormatterList(vec![Formatter::Prettier].into()),
9920 ))
9921 });
9922
9923 let fs = FakeFs::new(cx.executor());
9924 fs.insert_file("/file.ts", Default::default()).await;
9925
9926 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
9927 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9928
9929 language_registry.add(Arc::new(Language::new(
9930 LanguageConfig {
9931 name: "TypeScript".into(),
9932 matcher: LanguageMatcher {
9933 path_suffixes: vec!["ts".to_string()],
9934 ..Default::default()
9935 },
9936 ..Default::default()
9937 },
9938 Some(tree_sitter_rust::language()),
9939 )));
9940 update_test_language_settings(cx, |settings| {
9941 settings.defaults.prettier = Some(PrettierSettings {
9942 allowed: true,
9943 ..PrettierSettings::default()
9944 });
9945 });
9946
9947 let test_plugin = "test_plugin";
9948 let _ = language_registry.register_fake_lsp_adapter(
9949 "TypeScript",
9950 FakeLspAdapter {
9951 prettier_plugins: vec![test_plugin],
9952 ..Default::default()
9953 },
9954 );
9955
9956 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
9957 let buffer = project
9958 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
9959 .await
9960 .unwrap();
9961
9962 let buffer_text = "one\ntwo\nthree\n";
9963 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9964 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9965 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
9966
9967 editor
9968 .update(cx, |editor, cx| {
9969 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
9970 })
9971 .unwrap()
9972 .await;
9973 assert_eq!(
9974 editor.update(cx, |editor, cx| editor.text(cx)),
9975 buffer_text.to_string() + prettier_format_suffix,
9976 "Test prettier formatting was not applied to the original buffer text",
9977 );
9978
9979 update_test_language_settings(cx, |settings| {
9980 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
9981 });
9982 let format = editor.update(cx, |editor, cx| {
9983 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
9984 });
9985 format.await.unwrap();
9986 assert_eq!(
9987 editor.update(cx, |editor, cx| editor.text(cx)),
9988 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
9989 "Autoformatting (via test prettier) was not applied to the original buffer text",
9990 );
9991}
9992
9993#[gpui::test]
9994async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
9995 init_test(cx, |_| {});
9996 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9997 let base_text = indoc! {r#"struct Row;
9998struct Row1;
9999struct Row2;
10000
10001struct Row4;
10002struct Row5;
10003struct Row6;
10004
10005struct Row8;
10006struct Row9;
10007struct Row10;"#};
10008
10009 // When addition hunks are not adjacent to carets, no hunk revert is performed
10010 assert_hunk_revert(
10011 indoc! {r#"struct Row;
10012 struct Row1;
10013 struct Row1.1;
10014 struct Row1.2;
10015 struct Row2;ˇ
10016
10017 struct Row4;
10018 struct Row5;
10019 struct Row6;
10020
10021 struct Row8;
10022 ˇstruct Row9;
10023 struct Row9.1;
10024 struct Row9.2;
10025 struct Row9.3;
10026 struct Row10;"#},
10027 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10028 indoc! {r#"struct Row;
10029 struct Row1;
10030 struct Row1.1;
10031 struct Row1.2;
10032 struct Row2;ˇ
10033
10034 struct Row4;
10035 struct Row5;
10036 struct Row6;
10037
10038 struct Row8;
10039 ˇstruct Row9;
10040 struct Row9.1;
10041 struct Row9.2;
10042 struct Row9.3;
10043 struct Row10;"#},
10044 base_text,
10045 &mut cx,
10046 );
10047 // Same for selections
10048 assert_hunk_revert(
10049 indoc! {r#"struct Row;
10050 struct Row1;
10051 struct Row2;
10052 struct Row2.1;
10053 struct Row2.2;
10054 «ˇ
10055 struct Row4;
10056 struct» Row5;
10057 «struct Row6;
10058 ˇ»
10059 struct Row9.1;
10060 struct Row9.2;
10061 struct Row9.3;
10062 struct Row8;
10063 struct Row9;
10064 struct Row10;"#},
10065 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10066 indoc! {r#"struct Row;
10067 struct Row1;
10068 struct Row2;
10069 struct Row2.1;
10070 struct Row2.2;
10071 «ˇ
10072 struct Row4;
10073 struct» Row5;
10074 «struct Row6;
10075 ˇ»
10076 struct Row9.1;
10077 struct Row9.2;
10078 struct Row9.3;
10079 struct Row8;
10080 struct Row9;
10081 struct Row10;"#},
10082 base_text,
10083 &mut cx,
10084 );
10085
10086 // When carets and selections intersect the addition hunks, those are reverted.
10087 // Adjacent carets got merged.
10088 assert_hunk_revert(
10089 indoc! {r#"struct Row;
10090 ˇ// something on the top
10091 struct Row1;
10092 struct Row2;
10093 struct Roˇw3.1;
10094 struct Row2.2;
10095 struct Row2.3;ˇ
10096
10097 struct Row4;
10098 struct ˇRow5.1;
10099 struct Row5.2;
10100 struct «Rowˇ»5.3;
10101 struct Row5;
10102 struct Row6;
10103 ˇ
10104 struct Row9.1;
10105 struct «Rowˇ»9.2;
10106 struct «ˇRow»9.3;
10107 struct Row8;
10108 struct Row9;
10109 «ˇ// something on bottom»
10110 struct Row10;"#},
10111 vec![
10112 DiffHunkStatus::Added,
10113 DiffHunkStatus::Added,
10114 DiffHunkStatus::Added,
10115 DiffHunkStatus::Added,
10116 DiffHunkStatus::Added,
10117 ],
10118 indoc! {r#"struct Row;
10119 ˇstruct Row1;
10120 struct Row2;
10121 ˇ
10122 struct Row4;
10123 ˇstruct Row5;
10124 struct Row6;
10125 ˇ
10126 ˇstruct Row8;
10127 struct Row9;
10128 ˇstruct Row10;"#},
10129 base_text,
10130 &mut cx,
10131 );
10132}
10133
10134#[gpui::test]
10135async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
10136 init_test(cx, |_| {});
10137 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10138 let base_text = indoc! {r#"struct Row;
10139struct Row1;
10140struct Row2;
10141
10142struct Row4;
10143struct Row5;
10144struct Row6;
10145
10146struct Row8;
10147struct Row9;
10148struct Row10;"#};
10149
10150 // Modification hunks behave the same as the addition ones.
10151 assert_hunk_revert(
10152 indoc! {r#"struct Row;
10153 struct Row1;
10154 struct Row33;
10155 ˇ
10156 struct Row4;
10157 struct Row5;
10158 struct Row6;
10159 ˇ
10160 struct Row99;
10161 struct Row9;
10162 struct Row10;"#},
10163 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10164 indoc! {r#"struct Row;
10165 struct Row1;
10166 struct Row33;
10167 ˇ
10168 struct Row4;
10169 struct Row5;
10170 struct Row6;
10171 ˇ
10172 struct Row99;
10173 struct Row9;
10174 struct Row10;"#},
10175 base_text,
10176 &mut cx,
10177 );
10178 assert_hunk_revert(
10179 indoc! {r#"struct Row;
10180 struct Row1;
10181 struct Row33;
10182 «ˇ
10183 struct Row4;
10184 struct» Row5;
10185 «struct Row6;
10186 ˇ»
10187 struct Row99;
10188 struct Row9;
10189 struct Row10;"#},
10190 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10191 indoc! {r#"struct Row;
10192 struct Row1;
10193 struct Row33;
10194 «ˇ
10195 struct Row4;
10196 struct» Row5;
10197 «struct Row6;
10198 ˇ»
10199 struct Row99;
10200 struct Row9;
10201 struct Row10;"#},
10202 base_text,
10203 &mut cx,
10204 );
10205
10206 assert_hunk_revert(
10207 indoc! {r#"ˇstruct Row1.1;
10208 struct Row1;
10209 «ˇstr»uct Row22;
10210
10211 struct ˇRow44;
10212 struct Row5;
10213 struct «Rˇ»ow66;ˇ
10214
10215 «struˇ»ct Row88;
10216 struct Row9;
10217 struct Row1011;ˇ"#},
10218 vec![
10219 DiffHunkStatus::Modified,
10220 DiffHunkStatus::Modified,
10221 DiffHunkStatus::Modified,
10222 DiffHunkStatus::Modified,
10223 DiffHunkStatus::Modified,
10224 DiffHunkStatus::Modified,
10225 ],
10226 indoc! {r#"struct Row;
10227 ˇstruct Row1;
10228 struct Row2;
10229 ˇ
10230 struct Row4;
10231 ˇstruct Row5;
10232 struct Row6;
10233 ˇ
10234 struct Row8;
10235 ˇstruct Row9;
10236 struct Row10;ˇ"#},
10237 base_text,
10238 &mut cx,
10239 );
10240}
10241
10242#[gpui::test]
10243async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
10244 init_test(cx, |_| {});
10245 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10246 let base_text = indoc! {r#"struct Row;
10247struct Row1;
10248struct Row2;
10249
10250struct Row4;
10251struct Row5;
10252struct Row6;
10253
10254struct Row8;
10255struct Row9;
10256struct Row10;"#};
10257
10258 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
10259 assert_hunk_revert(
10260 indoc! {r#"struct Row;
10261 struct Row2;
10262
10263 ˇstruct Row4;
10264 struct Row5;
10265 struct Row6;
10266 ˇ
10267 struct Row8;
10268 struct Row10;"#},
10269 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10270 indoc! {r#"struct Row;
10271 struct Row2;
10272
10273 ˇstruct Row4;
10274 struct Row5;
10275 struct Row6;
10276 ˇ
10277 struct Row8;
10278 struct Row10;"#},
10279 base_text,
10280 &mut cx,
10281 );
10282 assert_hunk_revert(
10283 indoc! {r#"struct Row;
10284 struct Row2;
10285
10286 «ˇstruct Row4;
10287 struct» Row5;
10288 «struct Row6;
10289 ˇ»
10290 struct Row8;
10291 struct Row10;"#},
10292 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10293 indoc! {r#"struct Row;
10294 struct Row2;
10295
10296 «ˇstruct Row4;
10297 struct» Row5;
10298 «struct Row6;
10299 ˇ»
10300 struct Row8;
10301 struct Row10;"#},
10302 base_text,
10303 &mut cx,
10304 );
10305
10306 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
10307 assert_hunk_revert(
10308 indoc! {r#"struct Row;
10309 ˇstruct Row2;
10310
10311 struct Row4;
10312 struct Row5;
10313 struct Row6;
10314
10315 struct Row8;ˇ
10316 struct Row10;"#},
10317 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10318 indoc! {r#"struct Row;
10319 struct Row1;
10320 ˇstruct Row2;
10321
10322 struct Row4;
10323 struct Row5;
10324 struct Row6;
10325
10326 struct Row8;ˇ
10327 struct Row9;
10328 struct Row10;"#},
10329 base_text,
10330 &mut cx,
10331 );
10332 assert_hunk_revert(
10333 indoc! {r#"struct Row;
10334 struct Row2«ˇ;
10335 struct Row4;
10336 struct» Row5;
10337 «struct Row6;
10338
10339 struct Row8;ˇ»
10340 struct Row10;"#},
10341 vec![
10342 DiffHunkStatus::Removed,
10343 DiffHunkStatus::Removed,
10344 DiffHunkStatus::Removed,
10345 ],
10346 indoc! {r#"struct Row;
10347 struct Row1;
10348 struct Row2«ˇ;
10349
10350 struct Row4;
10351 struct» Row5;
10352 «struct Row6;
10353
10354 struct Row8;ˇ»
10355 struct Row9;
10356 struct Row10;"#},
10357 base_text,
10358 &mut cx,
10359 );
10360}
10361
10362#[gpui::test]
10363async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
10364 init_test(cx, |_| {});
10365
10366 let cols = 4;
10367 let rows = 10;
10368 let sample_text_1 = sample_text(rows, cols, 'a');
10369 assert_eq!(
10370 sample_text_1,
10371 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10372 );
10373 let sample_text_2 = sample_text(rows, cols, 'l');
10374 assert_eq!(
10375 sample_text_2,
10376 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10377 );
10378 let sample_text_3 = sample_text(rows, cols, 'v');
10379 assert_eq!(
10380 sample_text_3,
10381 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10382 );
10383
10384 fn diff_every_buffer_row(
10385 buffer: &Model<Buffer>,
10386 sample_text: String,
10387 cols: usize,
10388 cx: &mut gpui::TestAppContext,
10389 ) {
10390 // revert first character in each row, creating one large diff hunk per buffer
10391 let is_first_char = |offset: usize| offset % cols == 0;
10392 buffer.update(cx, |buffer, cx| {
10393 buffer.set_text(
10394 sample_text
10395 .chars()
10396 .enumerate()
10397 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
10398 .collect::<String>(),
10399 cx,
10400 );
10401 buffer.set_diff_base(Some(sample_text), cx);
10402 });
10403 cx.executor().run_until_parked();
10404 }
10405
10406 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10407 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10408
10409 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10410 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10411
10412 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10413 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10414
10415 let multibuffer = cx.new_model(|cx| {
10416 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10417 multibuffer.push_excerpts(
10418 buffer_1.clone(),
10419 [
10420 ExcerptRange {
10421 context: Point::new(0, 0)..Point::new(3, 0),
10422 primary: None,
10423 },
10424 ExcerptRange {
10425 context: Point::new(5, 0)..Point::new(7, 0),
10426 primary: None,
10427 },
10428 ExcerptRange {
10429 context: Point::new(9, 0)..Point::new(10, 4),
10430 primary: None,
10431 },
10432 ],
10433 cx,
10434 );
10435 multibuffer.push_excerpts(
10436 buffer_2.clone(),
10437 [
10438 ExcerptRange {
10439 context: Point::new(0, 0)..Point::new(3, 0),
10440 primary: None,
10441 },
10442 ExcerptRange {
10443 context: Point::new(5, 0)..Point::new(7, 0),
10444 primary: None,
10445 },
10446 ExcerptRange {
10447 context: Point::new(9, 0)..Point::new(10, 4),
10448 primary: None,
10449 },
10450 ],
10451 cx,
10452 );
10453 multibuffer.push_excerpts(
10454 buffer_3.clone(),
10455 [
10456 ExcerptRange {
10457 context: Point::new(0, 0)..Point::new(3, 0),
10458 primary: None,
10459 },
10460 ExcerptRange {
10461 context: Point::new(5, 0)..Point::new(7, 0),
10462 primary: None,
10463 },
10464 ExcerptRange {
10465 context: Point::new(9, 0)..Point::new(10, 4),
10466 primary: None,
10467 },
10468 ],
10469 cx,
10470 );
10471 multibuffer
10472 });
10473
10474 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
10475 editor.update(cx, |editor, cx| {
10476 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");
10477 editor.select_all(&SelectAll, cx);
10478 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10479 });
10480 cx.executor().run_until_parked();
10481 // When all ranges are selected, all buffer hunks are reverted.
10482 editor.update(cx, |editor, cx| {
10483 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");
10484 });
10485 buffer_1.update(cx, |buffer, _| {
10486 assert_eq!(buffer.text(), sample_text_1);
10487 });
10488 buffer_2.update(cx, |buffer, _| {
10489 assert_eq!(buffer.text(), sample_text_2);
10490 });
10491 buffer_3.update(cx, |buffer, _| {
10492 assert_eq!(buffer.text(), sample_text_3);
10493 });
10494
10495 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10496 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10497 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10498 editor.update(cx, |editor, cx| {
10499 editor.change_selections(None, cx, |s| {
10500 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
10501 });
10502 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10503 });
10504 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
10505 // but not affect buffer_2 and its related excerpts.
10506 editor.update(cx, |editor, cx| {
10507 assert_eq!(
10508 editor.text(cx),
10509 "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"
10510 );
10511 });
10512 buffer_1.update(cx, |buffer, _| {
10513 assert_eq!(buffer.text(), sample_text_1);
10514 });
10515 buffer_2.update(cx, |buffer, _| {
10516 assert_eq!(
10517 buffer.text(),
10518 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
10519 );
10520 });
10521 buffer_3.update(cx, |buffer, _| {
10522 assert_eq!(
10523 buffer.text(),
10524 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
10525 );
10526 });
10527}
10528
10529#[gpui::test]
10530async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
10531 init_test(cx, |_| {});
10532
10533 let cols = 4;
10534 let rows = 10;
10535 let sample_text_1 = sample_text(rows, cols, 'a');
10536 assert_eq!(
10537 sample_text_1,
10538 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10539 );
10540 let sample_text_2 = sample_text(rows, cols, 'l');
10541 assert_eq!(
10542 sample_text_2,
10543 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10544 );
10545 let sample_text_3 = sample_text(rows, cols, 'v');
10546 assert_eq!(
10547 sample_text_3,
10548 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10549 );
10550
10551 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10552 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10553 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10554
10555 let multi_buffer = cx.new_model(|cx| {
10556 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10557 multibuffer.push_excerpts(
10558 buffer_1.clone(),
10559 [
10560 ExcerptRange {
10561 context: Point::new(0, 0)..Point::new(3, 0),
10562 primary: None,
10563 },
10564 ExcerptRange {
10565 context: Point::new(5, 0)..Point::new(7, 0),
10566 primary: None,
10567 },
10568 ExcerptRange {
10569 context: Point::new(9, 0)..Point::new(10, 4),
10570 primary: None,
10571 },
10572 ],
10573 cx,
10574 );
10575 multibuffer.push_excerpts(
10576 buffer_2.clone(),
10577 [
10578 ExcerptRange {
10579 context: Point::new(0, 0)..Point::new(3, 0),
10580 primary: None,
10581 },
10582 ExcerptRange {
10583 context: Point::new(5, 0)..Point::new(7, 0),
10584 primary: None,
10585 },
10586 ExcerptRange {
10587 context: Point::new(9, 0)..Point::new(10, 4),
10588 primary: None,
10589 },
10590 ],
10591 cx,
10592 );
10593 multibuffer.push_excerpts(
10594 buffer_3.clone(),
10595 [
10596 ExcerptRange {
10597 context: Point::new(0, 0)..Point::new(3, 0),
10598 primary: None,
10599 },
10600 ExcerptRange {
10601 context: Point::new(5, 0)..Point::new(7, 0),
10602 primary: None,
10603 },
10604 ExcerptRange {
10605 context: Point::new(9, 0)..Point::new(10, 4),
10606 primary: None,
10607 },
10608 ],
10609 cx,
10610 );
10611 multibuffer
10612 });
10613
10614 let fs = FakeFs::new(cx.executor());
10615 fs.insert_tree(
10616 "/a",
10617 json!({
10618 "main.rs": sample_text_1,
10619 "other.rs": sample_text_2,
10620 "lib.rs": sample_text_3,
10621 }),
10622 )
10623 .await;
10624 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10625 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10626 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10627 let multi_buffer_editor = cx.new_view(|cx| {
10628 Editor::new(
10629 EditorMode::Full,
10630 multi_buffer,
10631 Some(project.clone()),
10632 true,
10633 cx,
10634 )
10635 });
10636 let multibuffer_item_id = workspace
10637 .update(cx, |workspace, cx| {
10638 assert!(
10639 workspace.active_item(cx).is_none(),
10640 "active item should be None before the first item is added"
10641 );
10642 workspace.add_item_to_active_pane(
10643 Box::new(multi_buffer_editor.clone()),
10644 None,
10645 true,
10646 cx,
10647 );
10648 let active_item = workspace
10649 .active_item(cx)
10650 .expect("should have an active item after adding the multi buffer");
10651 assert!(
10652 !active_item.is_singleton(cx),
10653 "A multi buffer was expected to active after adding"
10654 );
10655 active_item.item_id()
10656 })
10657 .unwrap();
10658 cx.executor().run_until_parked();
10659
10660 multi_buffer_editor.update(cx, |editor, cx| {
10661 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
10662 editor.open_excerpts(&OpenExcerpts, cx);
10663 });
10664 cx.executor().run_until_parked();
10665 let first_item_id = workspace
10666 .update(cx, |workspace, cx| {
10667 let active_item = workspace
10668 .active_item(cx)
10669 .expect("should have an active item after navigating into the 1st buffer");
10670 let first_item_id = active_item.item_id();
10671 assert_ne!(
10672 first_item_id, multibuffer_item_id,
10673 "Should navigate into the 1st buffer and activate it"
10674 );
10675 assert!(
10676 active_item.is_singleton(cx),
10677 "New active item should be a singleton buffer"
10678 );
10679 assert_eq!(
10680 active_item
10681 .act_as::<Editor>(cx)
10682 .expect("should have navigated into an editor for the 1st buffer")
10683 .read(cx)
10684 .text(cx),
10685 sample_text_1
10686 );
10687
10688 workspace
10689 .go_back(workspace.active_pane().downgrade(), cx)
10690 .detach_and_log_err(cx);
10691
10692 first_item_id
10693 })
10694 .unwrap();
10695 cx.executor().run_until_parked();
10696 workspace
10697 .update(cx, |workspace, cx| {
10698 let active_item = workspace
10699 .active_item(cx)
10700 .expect("should have an active item after navigating back");
10701 assert_eq!(
10702 active_item.item_id(),
10703 multibuffer_item_id,
10704 "Should navigate back to the multi buffer"
10705 );
10706 assert!(!active_item.is_singleton(cx));
10707 })
10708 .unwrap();
10709
10710 multi_buffer_editor.update(cx, |editor, cx| {
10711 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
10712 s.select_ranges(Some(39..40))
10713 });
10714 editor.open_excerpts(&OpenExcerpts, cx);
10715 });
10716 cx.executor().run_until_parked();
10717 let second_item_id = workspace
10718 .update(cx, |workspace, cx| {
10719 let active_item = workspace
10720 .active_item(cx)
10721 .expect("should have an active item after navigating into the 2nd buffer");
10722 let second_item_id = active_item.item_id();
10723 assert_ne!(
10724 second_item_id, multibuffer_item_id,
10725 "Should navigate away from the multibuffer"
10726 );
10727 assert_ne!(
10728 second_item_id, first_item_id,
10729 "Should navigate into the 2nd buffer and activate it"
10730 );
10731 assert!(
10732 active_item.is_singleton(cx),
10733 "New active item should be a singleton buffer"
10734 );
10735 assert_eq!(
10736 active_item
10737 .act_as::<Editor>(cx)
10738 .expect("should have navigated into an editor")
10739 .read(cx)
10740 .text(cx),
10741 sample_text_2
10742 );
10743
10744 workspace
10745 .go_back(workspace.active_pane().downgrade(), cx)
10746 .detach_and_log_err(cx);
10747
10748 second_item_id
10749 })
10750 .unwrap();
10751 cx.executor().run_until_parked();
10752 workspace
10753 .update(cx, |workspace, cx| {
10754 let active_item = workspace
10755 .active_item(cx)
10756 .expect("should have an active item after navigating back from the 2nd buffer");
10757 assert_eq!(
10758 active_item.item_id(),
10759 multibuffer_item_id,
10760 "Should navigate back from the 2nd buffer to the multi buffer"
10761 );
10762 assert!(!active_item.is_singleton(cx));
10763 })
10764 .unwrap();
10765
10766 multi_buffer_editor.update(cx, |editor, cx| {
10767 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
10768 s.select_ranges(Some(60..70))
10769 });
10770 editor.open_excerpts(&OpenExcerpts, cx);
10771 });
10772 cx.executor().run_until_parked();
10773 workspace
10774 .update(cx, |workspace, cx| {
10775 let active_item = workspace
10776 .active_item(cx)
10777 .expect("should have an active item after navigating into the 3rd buffer");
10778 let third_item_id = active_item.item_id();
10779 assert_ne!(
10780 third_item_id, multibuffer_item_id,
10781 "Should navigate into the 3rd buffer and activate it"
10782 );
10783 assert_ne!(third_item_id, first_item_id);
10784 assert_ne!(third_item_id, second_item_id);
10785 assert!(
10786 active_item.is_singleton(cx),
10787 "New active item should be a singleton buffer"
10788 );
10789 assert_eq!(
10790 active_item
10791 .act_as::<Editor>(cx)
10792 .expect("should have navigated into an editor")
10793 .read(cx)
10794 .text(cx),
10795 sample_text_3
10796 );
10797
10798 workspace
10799 .go_back(workspace.active_pane().downgrade(), cx)
10800 .detach_and_log_err(cx);
10801 })
10802 .unwrap();
10803 cx.executor().run_until_parked();
10804 workspace
10805 .update(cx, |workspace, cx| {
10806 let active_item = workspace
10807 .active_item(cx)
10808 .expect("should have an active item after navigating back from the 3rd buffer");
10809 assert_eq!(
10810 active_item.item_id(),
10811 multibuffer_item_id,
10812 "Should navigate back from the 3rd buffer to the multi buffer"
10813 );
10814 assert!(!active_item.is_singleton(cx));
10815 })
10816 .unwrap();
10817}
10818
10819#[gpui::test]
10820async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10821 init_test(cx, |_| {});
10822
10823 let mut cx = EditorTestContext::new(cx).await;
10824
10825 let diff_base = r#"
10826 use some::mod;
10827
10828 const A: u32 = 42;
10829
10830 fn main() {
10831 println!("hello");
10832
10833 println!("world");
10834 }
10835 "#
10836 .unindent();
10837
10838 cx.set_state(
10839 &r#"
10840 use some::modified;
10841
10842 ˇ
10843 fn main() {
10844 println!("hello there");
10845
10846 println!("around the");
10847 println!("world");
10848 }
10849 "#
10850 .unindent(),
10851 );
10852
10853 cx.set_diff_base(Some(&diff_base));
10854 executor.run_until_parked();
10855 let unexpanded_hunks = vec![
10856 (
10857 "use some::mod;\n".to_string(),
10858 DiffHunkStatus::Modified,
10859 DisplayRow(0)..DisplayRow(1),
10860 ),
10861 (
10862 "const A: u32 = 42;\n".to_string(),
10863 DiffHunkStatus::Removed,
10864 DisplayRow(2)..DisplayRow(2),
10865 ),
10866 (
10867 " println!(\"hello\");\n".to_string(),
10868 DiffHunkStatus::Modified,
10869 DisplayRow(4)..DisplayRow(5),
10870 ),
10871 (
10872 "".to_string(),
10873 DiffHunkStatus::Added,
10874 DisplayRow(6)..DisplayRow(7),
10875 ),
10876 ];
10877 cx.update_editor(|editor, cx| {
10878 let snapshot = editor.snapshot(cx);
10879 let all_hunks = editor_hunks(editor, &snapshot, cx);
10880 assert_eq!(all_hunks, unexpanded_hunks);
10881 });
10882
10883 cx.update_editor(|editor, cx| {
10884 for _ in 0..4 {
10885 editor.go_to_hunk(&GoToHunk, cx);
10886 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10887 }
10888 });
10889 executor.run_until_parked();
10890 cx.assert_editor_state(
10891 &r#"
10892 use some::modified;
10893
10894 ˇ
10895 fn main() {
10896 println!("hello there");
10897
10898 println!("around the");
10899 println!("world");
10900 }
10901 "#
10902 .unindent(),
10903 );
10904 cx.update_editor(|editor, cx| {
10905 let snapshot = editor.snapshot(cx);
10906 let all_hunks = editor_hunks(editor, &snapshot, cx);
10907 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
10908 assert_eq!(
10909 expanded_hunks_background_highlights(editor, cx),
10910 vec![DisplayRow(1)..=DisplayRow(1), DisplayRow(7)..=DisplayRow(7), DisplayRow(9)..=DisplayRow(9)],
10911 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
10912 );
10913 assert_eq!(
10914 all_hunks,
10915 vec![
10916 ("use some::mod;\n".to_string(), DiffHunkStatus::Modified, DisplayRow(1)..DisplayRow(2)),
10917 ("const A: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(4)..DisplayRow(4)),
10918 (" println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(7)..DisplayRow(8)),
10919 ("".to_string(), DiffHunkStatus::Added, DisplayRow(9)..DisplayRow(10)),
10920 ],
10921 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
10922 (from modified and removed hunks)"
10923 );
10924 assert_eq!(
10925 all_hunks, all_expanded_hunks,
10926 "Editor hunks should not change and all be expanded"
10927 );
10928 });
10929
10930 cx.update_editor(|editor, cx| {
10931 editor.cancel(&Cancel, cx);
10932
10933 let snapshot = editor.snapshot(cx);
10934 let all_hunks = editor_hunks(editor, &snapshot, cx);
10935 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
10936 assert_eq!(
10937 expanded_hunks_background_highlights(editor, cx),
10938 Vec::new(),
10939 "After cancelling in editor, no git highlights should be left"
10940 );
10941 assert_eq!(
10942 all_expanded_hunks,
10943 Vec::new(),
10944 "After cancelling in editor, no hunks should be expanded"
10945 );
10946 assert_eq!(
10947 all_hunks, unexpanded_hunks,
10948 "After cancelling in editor, regular hunks' coordinates should get back to normal"
10949 );
10950 });
10951}
10952
10953#[gpui::test]
10954async fn test_toggled_diff_base_change(
10955 executor: BackgroundExecutor,
10956 cx: &mut gpui::TestAppContext,
10957) {
10958 init_test(cx, |_| {});
10959
10960 let mut cx = EditorTestContext::new(cx).await;
10961
10962 let diff_base = r#"
10963 use some::mod1;
10964 use some::mod2;
10965
10966 const A: u32 = 42;
10967 const B: u32 = 42;
10968 const C: u32 = 42;
10969
10970 fn main(ˇ) {
10971 println!("hello");
10972
10973 println!("world");
10974 }
10975 "#
10976 .unindent();
10977
10978 cx.set_state(
10979 &r#"
10980 use some::mod2;
10981
10982 const A: u32 = 42;
10983 const C: u32 = 42;
10984
10985 fn main(ˇ) {
10986 //println!("hello");
10987
10988 println!("world");
10989 //
10990 //
10991 }
10992 "#
10993 .unindent(),
10994 );
10995
10996 cx.set_diff_base(Some(&diff_base));
10997 executor.run_until_parked();
10998 cx.update_editor(|editor, cx| {
10999 let snapshot = editor.snapshot(cx);
11000 let all_hunks = editor_hunks(editor, &snapshot, cx);
11001 assert_eq!(
11002 all_hunks,
11003 vec![
11004 (
11005 "use some::mod1;\n".to_string(),
11006 DiffHunkStatus::Removed,
11007 DisplayRow(0)..DisplayRow(0)
11008 ),
11009 (
11010 "const B: u32 = 42;\n".to_string(),
11011 DiffHunkStatus::Removed,
11012 DisplayRow(3)..DisplayRow(3)
11013 ),
11014 (
11015 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11016 DiffHunkStatus::Modified,
11017 DisplayRow(5)..DisplayRow(7)
11018 ),
11019 (
11020 "".to_string(),
11021 DiffHunkStatus::Added,
11022 DisplayRow(9)..DisplayRow(11)
11023 ),
11024 ]
11025 );
11026 });
11027
11028 cx.update_editor(|editor, cx| {
11029 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11030 });
11031 executor.run_until_parked();
11032 cx.assert_editor_state(
11033 &r#"
11034 use some::mod2;
11035
11036 const A: u32 = 42;
11037 const C: u32 = 42;
11038
11039 fn main(ˇ) {
11040 //println!("hello");
11041
11042 println!("world");
11043 //
11044 //
11045 }
11046 "#
11047 .unindent(),
11048 );
11049 cx.update_editor(|editor, cx| {
11050 let snapshot = editor.snapshot(cx);
11051 let all_hunks = editor_hunks(editor, &snapshot, cx);
11052 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11053 assert_eq!(
11054 expanded_hunks_background_highlights(editor, cx),
11055 vec![DisplayRow(9)..=DisplayRow(10), DisplayRow(13)..=DisplayRow(14)],
11056 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
11057 );
11058 assert_eq!(
11059 all_hunks,
11060 vec![
11061 ("use some::mod1;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(1)..DisplayRow(1)),
11062 ("const B: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(5)..DisplayRow(5)),
11063 ("fn main(ˇ) {\n println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(9)..DisplayRow(11)),
11064 ("".to_string(), DiffHunkStatus::Added, DisplayRow(13)..DisplayRow(15)),
11065 ],
11066 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
11067 (from modified and removed hunks)"
11068 );
11069 assert_eq!(
11070 all_hunks, all_expanded_hunks,
11071 "Editor hunks should not change and all be expanded"
11072 );
11073 });
11074
11075 cx.set_diff_base(Some("new diff base!"));
11076 executor.run_until_parked();
11077
11078 cx.update_editor(|editor, cx| {
11079 let snapshot = editor.snapshot(cx);
11080 let all_hunks = editor_hunks(editor, &snapshot, cx);
11081 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11082 assert_eq!(
11083 expanded_hunks_background_highlights(editor, cx),
11084 Vec::new(),
11085 "After diff base is changed, old git highlights should be removed"
11086 );
11087 assert_eq!(
11088 all_expanded_hunks,
11089 Vec::new(),
11090 "After diff base is changed, old git hunk expansions should be removed"
11091 );
11092 assert_eq!(
11093 all_hunks,
11094 vec![(
11095 "new diff base!".to_string(),
11096 DiffHunkStatus::Modified,
11097 DisplayRow(0)..snapshot.display_snapshot.max_point().row()
11098 )],
11099 "After diff base is changed, hunks should update"
11100 );
11101 });
11102}
11103
11104#[gpui::test]
11105async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11106 init_test(cx, |_| {});
11107
11108 let mut cx = EditorTestContext::new(cx).await;
11109
11110 let diff_base = r#"
11111 use some::mod1;
11112 use some::mod2;
11113
11114 const A: u32 = 42;
11115 const B: u32 = 42;
11116 const C: u32 = 42;
11117
11118 fn main(ˇ) {
11119 println!("hello");
11120
11121 println!("world");
11122 }
11123
11124 fn another() {
11125 println!("another");
11126 }
11127
11128 fn another2() {
11129 println!("another2");
11130 }
11131 "#
11132 .unindent();
11133
11134 cx.set_state(
11135 &r#"
11136 «use some::mod2;
11137
11138 const A: u32 = 42;
11139 const C: u32 = 42;
11140
11141 fn main() {
11142 //println!("hello");
11143
11144 println!("world");
11145 //
11146 //ˇ»
11147 }
11148
11149 fn another() {
11150 println!("another");
11151 println!("another");
11152 }
11153
11154 println!("another2");
11155 }
11156 "#
11157 .unindent(),
11158 );
11159
11160 cx.set_diff_base(Some(&diff_base));
11161 executor.run_until_parked();
11162 cx.update_editor(|editor, cx| {
11163 let snapshot = editor.snapshot(cx);
11164 let all_hunks = editor_hunks(editor, &snapshot, cx);
11165 assert_eq!(
11166 all_hunks,
11167 vec![
11168 (
11169 "use some::mod1;\n".to_string(),
11170 DiffHunkStatus::Removed,
11171 DisplayRow(0)..DisplayRow(0)
11172 ),
11173 (
11174 "const B: u32 = 42;\n".to_string(),
11175 DiffHunkStatus::Removed,
11176 DisplayRow(3)..DisplayRow(3)
11177 ),
11178 (
11179 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11180 DiffHunkStatus::Modified,
11181 DisplayRow(5)..DisplayRow(7)
11182 ),
11183 (
11184 "".to_string(),
11185 DiffHunkStatus::Added,
11186 DisplayRow(9)..DisplayRow(11)
11187 ),
11188 (
11189 "".to_string(),
11190 DiffHunkStatus::Added,
11191 DisplayRow(15)..DisplayRow(16)
11192 ),
11193 (
11194 "fn another2() {\n".to_string(),
11195 DiffHunkStatus::Removed,
11196 DisplayRow(18)..DisplayRow(18)
11197 ),
11198 ]
11199 );
11200 });
11201
11202 cx.update_editor(|editor, cx| {
11203 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11204 });
11205 executor.run_until_parked();
11206 cx.assert_editor_state(
11207 &r#"
11208 «use some::mod2;
11209
11210 const A: u32 = 42;
11211 const C: u32 = 42;
11212
11213 fn main() {
11214 //println!("hello");
11215
11216 println!("world");
11217 //
11218 //ˇ»
11219 }
11220
11221 fn another() {
11222 println!("another");
11223 println!("another");
11224 }
11225
11226 println!("another2");
11227 }
11228 "#
11229 .unindent(),
11230 );
11231 cx.update_editor(|editor, cx| {
11232 let snapshot = editor.snapshot(cx);
11233 let all_hunks = editor_hunks(editor, &snapshot, cx);
11234 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11235 assert_eq!(
11236 expanded_hunks_background_highlights(editor, cx),
11237 vec![
11238 DisplayRow(9)..=DisplayRow(10),
11239 DisplayRow(13)..=DisplayRow(14),
11240 DisplayRow(19)..=DisplayRow(19)
11241 ]
11242 );
11243 assert_eq!(
11244 all_hunks,
11245 vec![
11246 (
11247 "use some::mod1;\n".to_string(),
11248 DiffHunkStatus::Removed,
11249 DisplayRow(1)..DisplayRow(1)
11250 ),
11251 (
11252 "const B: u32 = 42;\n".to_string(),
11253 DiffHunkStatus::Removed,
11254 DisplayRow(5)..DisplayRow(5)
11255 ),
11256 (
11257 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11258 DiffHunkStatus::Modified,
11259 DisplayRow(9)..DisplayRow(11)
11260 ),
11261 (
11262 "".to_string(),
11263 DiffHunkStatus::Added,
11264 DisplayRow(13)..DisplayRow(15)
11265 ),
11266 (
11267 "".to_string(),
11268 DiffHunkStatus::Added,
11269 DisplayRow(19)..DisplayRow(20)
11270 ),
11271 (
11272 "fn another2() {\n".to_string(),
11273 DiffHunkStatus::Removed,
11274 DisplayRow(23)..DisplayRow(23)
11275 ),
11276 ],
11277 );
11278 assert_eq!(all_hunks, all_expanded_hunks);
11279 });
11280
11281 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
11282 cx.executor().run_until_parked();
11283 cx.assert_editor_state(
11284 &r#"
11285 «use some::mod2;
11286
11287 const A: u32 = 42;
11288 const C: u32 = 42;
11289
11290 fn main() {
11291 //println!("hello");
11292
11293 println!("world");
11294 //
11295 //ˇ»
11296 }
11297
11298 fn another() {
11299 println!("another");
11300 println!("another");
11301 }
11302
11303 println!("another2");
11304 }
11305 "#
11306 .unindent(),
11307 );
11308 cx.update_editor(|editor, cx| {
11309 let snapshot = editor.snapshot(cx);
11310 let all_hunks = editor_hunks(editor, &snapshot, cx);
11311 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11312 assert_eq!(
11313 expanded_hunks_background_highlights(editor, cx),
11314 vec![DisplayRow(0)..=DisplayRow(0), DisplayRow(5)..=DisplayRow(5)],
11315 "Only one hunk is left not folded, its highlight should be visible"
11316 );
11317 assert_eq!(
11318 all_hunks,
11319 vec![
11320 (
11321 "use some::mod1;\n".to_string(),
11322 DiffHunkStatus::Removed,
11323 DisplayRow(0)..DisplayRow(0)
11324 ),
11325 (
11326 "const B: u32 = 42;\n".to_string(),
11327 DiffHunkStatus::Removed,
11328 DisplayRow(0)..DisplayRow(0)
11329 ),
11330 (
11331 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11332 DiffHunkStatus::Modified,
11333 DisplayRow(0)..DisplayRow(0)
11334 ),
11335 (
11336 "".to_string(),
11337 DiffHunkStatus::Added,
11338 DisplayRow(0)..DisplayRow(1)
11339 ),
11340 (
11341 "".to_string(),
11342 DiffHunkStatus::Added,
11343 DisplayRow(5)..DisplayRow(6)
11344 ),
11345 (
11346 "fn another2() {\n".to_string(),
11347 DiffHunkStatus::Removed,
11348 DisplayRow(9)..DisplayRow(9)
11349 ),
11350 ],
11351 "Hunk list should still return shifted folded hunks"
11352 );
11353 assert_eq!(
11354 all_expanded_hunks,
11355 vec![
11356 (
11357 "".to_string(),
11358 DiffHunkStatus::Added,
11359 DisplayRow(5)..DisplayRow(6)
11360 ),
11361 (
11362 "fn another2() {\n".to_string(),
11363 DiffHunkStatus::Removed,
11364 DisplayRow(9)..DisplayRow(9)
11365 ),
11366 ],
11367 "Only non-folded hunks should be left expanded"
11368 );
11369 });
11370
11371 cx.update_editor(|editor, cx| {
11372 editor.select_all(&SelectAll, cx);
11373 editor.unfold_lines(&UnfoldLines, cx);
11374 });
11375 cx.executor().run_until_parked();
11376 cx.assert_editor_state(
11377 &r#"
11378 «use some::mod2;
11379
11380 const A: u32 = 42;
11381 const C: u32 = 42;
11382
11383 fn main() {
11384 //println!("hello");
11385
11386 println!("world");
11387 //
11388 //
11389 }
11390
11391 fn another() {
11392 println!("another");
11393 println!("another");
11394 }
11395
11396 println!("another2");
11397 }
11398 ˇ»"#
11399 .unindent(),
11400 );
11401 cx.update_editor(|editor, cx| {
11402 let snapshot = editor.snapshot(cx);
11403 let all_hunks = editor_hunks(editor, &snapshot, cx);
11404 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11405 assert_eq!(
11406 expanded_hunks_background_highlights(editor, cx),
11407 vec![
11408 DisplayRow(9)..=DisplayRow(10),
11409 DisplayRow(13)..=DisplayRow(14),
11410 DisplayRow(19)..=DisplayRow(19)
11411 ],
11412 "After unfolding, all hunk diffs should be visible again"
11413 );
11414 assert_eq!(
11415 all_hunks,
11416 vec![
11417 (
11418 "use some::mod1;\n".to_string(),
11419 DiffHunkStatus::Removed,
11420 DisplayRow(1)..DisplayRow(1)
11421 ),
11422 (
11423 "const B: u32 = 42;\n".to_string(),
11424 DiffHunkStatus::Removed,
11425 DisplayRow(5)..DisplayRow(5)
11426 ),
11427 (
11428 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11429 DiffHunkStatus::Modified,
11430 DisplayRow(9)..DisplayRow(11)
11431 ),
11432 (
11433 "".to_string(),
11434 DiffHunkStatus::Added,
11435 DisplayRow(13)..DisplayRow(15)
11436 ),
11437 (
11438 "".to_string(),
11439 DiffHunkStatus::Added,
11440 DisplayRow(19)..DisplayRow(20)
11441 ),
11442 (
11443 "fn another2() {\n".to_string(),
11444 DiffHunkStatus::Removed,
11445 DisplayRow(23)..DisplayRow(23)
11446 ),
11447 ],
11448 );
11449 assert_eq!(all_hunks, all_expanded_hunks);
11450 });
11451}
11452
11453#[gpui::test]
11454async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
11455 init_test(cx, |_| {});
11456
11457 let cols = 4;
11458 let rows = 10;
11459 let sample_text_1 = sample_text(rows, cols, 'a');
11460 assert_eq!(
11461 sample_text_1,
11462 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11463 );
11464 let modified_sample_text_1 = "aaaa\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
11465 let sample_text_2 = sample_text(rows, cols, 'l');
11466 assert_eq!(
11467 sample_text_2,
11468 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11469 );
11470 let modified_sample_text_2 = "llll\nmmmm\n1n1n1n1n1\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
11471 let sample_text_3 = sample_text(rows, cols, 'v');
11472 assert_eq!(
11473 sample_text_3,
11474 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11475 );
11476 let modified_sample_text_3 =
11477 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n@@@@\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
11478 let buffer_1 = cx.new_model(|cx| {
11479 let mut buffer = Buffer::local(modified_sample_text_1.to_string(), cx);
11480 buffer.set_diff_base(Some(sample_text_1.clone()), cx);
11481 buffer
11482 });
11483 let buffer_2 = cx.new_model(|cx| {
11484 let mut buffer = Buffer::local(modified_sample_text_2.to_string(), cx);
11485 buffer.set_diff_base(Some(sample_text_2.clone()), cx);
11486 buffer
11487 });
11488 let buffer_3 = cx.new_model(|cx| {
11489 let mut buffer = Buffer::local(modified_sample_text_3.to_string(), cx);
11490 buffer.set_diff_base(Some(sample_text_3.clone()), cx);
11491 buffer
11492 });
11493
11494 let multi_buffer = cx.new_model(|cx| {
11495 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
11496 multibuffer.push_excerpts(
11497 buffer_1.clone(),
11498 [
11499 ExcerptRange {
11500 context: Point::new(0, 0)..Point::new(3, 0),
11501 primary: None,
11502 },
11503 ExcerptRange {
11504 context: Point::new(5, 0)..Point::new(7, 0),
11505 primary: None,
11506 },
11507 ExcerptRange {
11508 context: Point::new(9, 0)..Point::new(10, 4),
11509 primary: None,
11510 },
11511 ],
11512 cx,
11513 );
11514 multibuffer.push_excerpts(
11515 buffer_2.clone(),
11516 [
11517 ExcerptRange {
11518 context: Point::new(0, 0)..Point::new(3, 0),
11519 primary: None,
11520 },
11521 ExcerptRange {
11522 context: Point::new(5, 0)..Point::new(7, 0),
11523 primary: None,
11524 },
11525 ExcerptRange {
11526 context: Point::new(9, 0)..Point::new(10, 4),
11527 primary: None,
11528 },
11529 ],
11530 cx,
11531 );
11532 multibuffer.push_excerpts(
11533 buffer_3.clone(),
11534 [
11535 ExcerptRange {
11536 context: Point::new(0, 0)..Point::new(3, 0),
11537 primary: None,
11538 },
11539 ExcerptRange {
11540 context: Point::new(5, 0)..Point::new(7, 0),
11541 primary: None,
11542 },
11543 ExcerptRange {
11544 context: Point::new(9, 0)..Point::new(10, 4),
11545 primary: None,
11546 },
11547 ],
11548 cx,
11549 );
11550 multibuffer
11551 });
11552
11553 let fs = FakeFs::new(cx.executor());
11554 fs.insert_tree(
11555 "/a",
11556 json!({
11557 "main.rs": modified_sample_text_1,
11558 "other.rs": modified_sample_text_2,
11559 "lib.rs": modified_sample_text_3,
11560 }),
11561 )
11562 .await;
11563
11564 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11565 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11566 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11567 let multi_buffer_editor = cx.new_view(|cx| {
11568 Editor::new(
11569 EditorMode::Full,
11570 multi_buffer,
11571 Some(project.clone()),
11572 true,
11573 cx,
11574 )
11575 });
11576 cx.executor().run_until_parked();
11577
11578 let expected_all_hunks = vec![
11579 (
11580 "bbbb\n".to_string(),
11581 DiffHunkStatus::Removed,
11582 DisplayRow(4)..DisplayRow(4),
11583 ),
11584 (
11585 "nnnn\n".to_string(),
11586 DiffHunkStatus::Modified,
11587 DisplayRow(21)..DisplayRow(22),
11588 ),
11589 (
11590 "".to_string(),
11591 DiffHunkStatus::Added,
11592 DisplayRow(41)..DisplayRow(42),
11593 ),
11594 ];
11595 let expected_all_hunks_shifted = vec![
11596 (
11597 "bbbb\n".to_string(),
11598 DiffHunkStatus::Removed,
11599 DisplayRow(5)..DisplayRow(5),
11600 ),
11601 (
11602 "nnnn\n".to_string(),
11603 DiffHunkStatus::Modified,
11604 DisplayRow(23)..DisplayRow(24),
11605 ),
11606 (
11607 "".to_string(),
11608 DiffHunkStatus::Added,
11609 DisplayRow(43)..DisplayRow(44),
11610 ),
11611 ];
11612
11613 multi_buffer_editor.update(cx, |editor, cx| {
11614 let snapshot = editor.snapshot(cx);
11615 let all_hunks = editor_hunks(editor, &snapshot, cx);
11616 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11617 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11618 assert_eq!(all_hunks, expected_all_hunks);
11619 assert_eq!(all_expanded_hunks, Vec::new());
11620 });
11621
11622 multi_buffer_editor.update(cx, |editor, cx| {
11623 editor.select_all(&SelectAll, cx);
11624 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11625 });
11626 cx.executor().run_until_parked();
11627 multi_buffer_editor.update(cx, |editor, cx| {
11628 let snapshot = editor.snapshot(cx);
11629 let all_hunks = editor_hunks(editor, &snapshot, cx);
11630 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11631 assert_eq!(
11632 expanded_hunks_background_highlights(editor, cx),
11633 vec![
11634 DisplayRow(23)..=DisplayRow(23),
11635 DisplayRow(43)..=DisplayRow(43)
11636 ],
11637 );
11638 assert_eq!(all_hunks, expected_all_hunks_shifted);
11639 assert_eq!(all_hunks, all_expanded_hunks);
11640 });
11641
11642 multi_buffer_editor.update(cx, |editor, cx| {
11643 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11644 });
11645 cx.executor().run_until_parked();
11646 multi_buffer_editor.update(cx, |editor, cx| {
11647 let snapshot = editor.snapshot(cx);
11648 let all_hunks = editor_hunks(editor, &snapshot, cx);
11649 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11650 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11651 assert_eq!(all_hunks, expected_all_hunks);
11652 assert_eq!(all_expanded_hunks, Vec::new());
11653 });
11654
11655 multi_buffer_editor.update(cx, |editor, cx| {
11656 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11657 });
11658 cx.executor().run_until_parked();
11659 multi_buffer_editor.update(cx, |editor, cx| {
11660 let snapshot = editor.snapshot(cx);
11661 let all_hunks = editor_hunks(editor, &snapshot, cx);
11662 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11663 assert_eq!(
11664 expanded_hunks_background_highlights(editor, cx),
11665 vec![
11666 DisplayRow(23)..=DisplayRow(23),
11667 DisplayRow(43)..=DisplayRow(43)
11668 ],
11669 );
11670 assert_eq!(all_hunks, expected_all_hunks_shifted);
11671 assert_eq!(all_hunks, all_expanded_hunks);
11672 });
11673
11674 multi_buffer_editor.update(cx, |editor, cx| {
11675 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11676 });
11677 cx.executor().run_until_parked();
11678 multi_buffer_editor.update(cx, |editor, cx| {
11679 let snapshot = editor.snapshot(cx);
11680 let all_hunks = editor_hunks(editor, &snapshot, cx);
11681 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11682 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11683 assert_eq!(all_hunks, expected_all_hunks);
11684 assert_eq!(all_expanded_hunks, Vec::new());
11685 });
11686}
11687
11688#[gpui::test]
11689async fn test_edits_around_toggled_additions(
11690 executor: BackgroundExecutor,
11691 cx: &mut gpui::TestAppContext,
11692) {
11693 init_test(cx, |_| {});
11694
11695 let mut cx = EditorTestContext::new(cx).await;
11696
11697 let diff_base = r#"
11698 use some::mod1;
11699 use some::mod2;
11700
11701 const A: u32 = 42;
11702
11703 fn main() {
11704 println!("hello");
11705
11706 println!("world");
11707 }
11708 "#
11709 .unindent();
11710 executor.run_until_parked();
11711 cx.set_state(
11712 &r#"
11713 use some::mod1;
11714 use some::mod2;
11715
11716 const A: u32 = 42;
11717 const B: u32 = 42;
11718 const C: u32 = 42;
11719 ˇ
11720
11721 fn main() {
11722 println!("hello");
11723
11724 println!("world");
11725 }
11726 "#
11727 .unindent(),
11728 );
11729
11730 cx.set_diff_base(Some(&diff_base));
11731 executor.run_until_parked();
11732 cx.update_editor(|editor, cx| {
11733 let snapshot = editor.snapshot(cx);
11734 let all_hunks = editor_hunks(editor, &snapshot, cx);
11735 assert_eq!(
11736 all_hunks,
11737 vec![(
11738 "".to_string(),
11739 DiffHunkStatus::Added,
11740 DisplayRow(4)..DisplayRow(7)
11741 )]
11742 );
11743 });
11744 cx.update_editor(|editor, cx| {
11745 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11746 });
11747 executor.run_until_parked();
11748 cx.assert_editor_state(
11749 &r#"
11750 use some::mod1;
11751 use some::mod2;
11752
11753 const A: u32 = 42;
11754 const B: u32 = 42;
11755 const C: u32 = 42;
11756 ˇ
11757
11758 fn main() {
11759 println!("hello");
11760
11761 println!("world");
11762 }
11763 "#
11764 .unindent(),
11765 );
11766 cx.update_editor(|editor, cx| {
11767 let snapshot = editor.snapshot(cx);
11768 let all_hunks = editor_hunks(editor, &snapshot, cx);
11769 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11770 assert_eq!(
11771 all_hunks,
11772 vec![(
11773 "".to_string(),
11774 DiffHunkStatus::Added,
11775 DisplayRow(4)..DisplayRow(7)
11776 )]
11777 );
11778 assert_eq!(
11779 expanded_hunks_background_highlights(editor, cx),
11780 vec![DisplayRow(4)..=DisplayRow(6)]
11781 );
11782 assert_eq!(all_hunks, all_expanded_hunks);
11783 });
11784
11785 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
11786 executor.run_until_parked();
11787 cx.assert_editor_state(
11788 &r#"
11789 use some::mod1;
11790 use some::mod2;
11791
11792 const A: u32 = 42;
11793 const B: u32 = 42;
11794 const C: u32 = 42;
11795 const D: u32 = 42;
11796 ˇ
11797
11798 fn main() {
11799 println!("hello");
11800
11801 println!("world");
11802 }
11803 "#
11804 .unindent(),
11805 );
11806 cx.update_editor(|editor, cx| {
11807 let snapshot = editor.snapshot(cx);
11808 let all_hunks = editor_hunks(editor, &snapshot, cx);
11809 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11810 assert_eq!(
11811 all_hunks,
11812 vec![(
11813 "".to_string(),
11814 DiffHunkStatus::Added,
11815 DisplayRow(4)..DisplayRow(8)
11816 )]
11817 );
11818 assert_eq!(
11819 expanded_hunks_background_highlights(editor, cx),
11820 vec![DisplayRow(4)..=DisplayRow(6)],
11821 "Edited hunk should have one more line added"
11822 );
11823 assert_eq!(
11824 all_hunks, all_expanded_hunks,
11825 "Expanded hunk should also grow with the addition"
11826 );
11827 });
11828
11829 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
11830 executor.run_until_parked();
11831 cx.assert_editor_state(
11832 &r#"
11833 use some::mod1;
11834 use some::mod2;
11835
11836 const A: u32 = 42;
11837 const B: u32 = 42;
11838 const C: u32 = 42;
11839 const D: u32 = 42;
11840 const E: u32 = 42;
11841 ˇ
11842
11843 fn main() {
11844 println!("hello");
11845
11846 println!("world");
11847 }
11848 "#
11849 .unindent(),
11850 );
11851 cx.update_editor(|editor, cx| {
11852 let snapshot = editor.snapshot(cx);
11853 let all_hunks = editor_hunks(editor, &snapshot, cx);
11854 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11855 assert_eq!(
11856 all_hunks,
11857 vec![(
11858 "".to_string(),
11859 DiffHunkStatus::Added,
11860 DisplayRow(4)..DisplayRow(9)
11861 )]
11862 );
11863 assert_eq!(
11864 expanded_hunks_background_highlights(editor, cx),
11865 vec![DisplayRow(4)..=DisplayRow(6)],
11866 "Edited hunk should have one more line added"
11867 );
11868 assert_eq!(all_hunks, all_expanded_hunks);
11869 });
11870
11871 cx.update_editor(|editor, cx| {
11872 editor.move_up(&MoveUp, cx);
11873 editor.delete_line(&DeleteLine, cx);
11874 });
11875 executor.run_until_parked();
11876 cx.assert_editor_state(
11877 &r#"
11878 use some::mod1;
11879 use some::mod2;
11880
11881 const A: u32 = 42;
11882 const B: u32 = 42;
11883 const C: u32 = 42;
11884 const D: u32 = 42;
11885 ˇ
11886
11887 fn main() {
11888 println!("hello");
11889
11890 println!("world");
11891 }
11892 "#
11893 .unindent(),
11894 );
11895 cx.update_editor(|editor, cx| {
11896 let snapshot = editor.snapshot(cx);
11897 let all_hunks = editor_hunks(editor, &snapshot, cx);
11898 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11899 assert_eq!(
11900 all_hunks,
11901 vec![(
11902 "".to_string(),
11903 DiffHunkStatus::Added,
11904 DisplayRow(4)..DisplayRow(8)
11905 )]
11906 );
11907 assert_eq!(
11908 expanded_hunks_background_highlights(editor, cx),
11909 vec![DisplayRow(4)..=DisplayRow(6)],
11910 "Deleting a line should shrint the hunk"
11911 );
11912 assert_eq!(
11913 all_hunks, all_expanded_hunks,
11914 "Expanded hunk should also shrink with the addition"
11915 );
11916 });
11917
11918 cx.update_editor(|editor, cx| {
11919 editor.move_up(&MoveUp, cx);
11920 editor.delete_line(&DeleteLine, cx);
11921 editor.move_up(&MoveUp, cx);
11922 editor.delete_line(&DeleteLine, cx);
11923 editor.move_up(&MoveUp, cx);
11924 editor.delete_line(&DeleteLine, cx);
11925 });
11926 executor.run_until_parked();
11927 cx.assert_editor_state(
11928 &r#"
11929 use some::mod1;
11930 use some::mod2;
11931
11932 const A: u32 = 42;
11933 ˇ
11934
11935 fn main() {
11936 println!("hello");
11937
11938 println!("world");
11939 }
11940 "#
11941 .unindent(),
11942 );
11943 cx.update_editor(|editor, cx| {
11944 let snapshot = editor.snapshot(cx);
11945 let all_hunks = editor_hunks(editor, &snapshot, cx);
11946 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11947 assert_eq!(
11948 all_hunks,
11949 vec![(
11950 "".to_string(),
11951 DiffHunkStatus::Added,
11952 DisplayRow(5)..DisplayRow(6)
11953 )]
11954 );
11955 assert_eq!(
11956 expanded_hunks_background_highlights(editor, cx),
11957 vec![DisplayRow(5)..=DisplayRow(5)]
11958 );
11959 assert_eq!(all_hunks, all_expanded_hunks);
11960 });
11961
11962 cx.update_editor(|editor, cx| {
11963 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
11964 editor.delete_line(&DeleteLine, cx);
11965 });
11966 executor.run_until_parked();
11967 cx.assert_editor_state(
11968 &r#"
11969 ˇ
11970
11971 fn main() {
11972 println!("hello");
11973
11974 println!("world");
11975 }
11976 "#
11977 .unindent(),
11978 );
11979 cx.update_editor(|editor, cx| {
11980 let snapshot = editor.snapshot(cx);
11981 let all_hunks = editor_hunks(editor, &snapshot, cx);
11982 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11983 assert_eq!(
11984 all_hunks,
11985 vec![
11986 (
11987 "use some::mod1;\nuse some::mod2;\n".to_string(),
11988 DiffHunkStatus::Removed,
11989 DisplayRow(0)..DisplayRow(0)
11990 ),
11991 (
11992 "const A: u32 = 42;\n".to_string(),
11993 DiffHunkStatus::Removed,
11994 DisplayRow(2)..DisplayRow(2)
11995 )
11996 ]
11997 );
11998 assert_eq!(
11999 expanded_hunks_background_highlights(editor, cx),
12000 Vec::new(),
12001 "Should close all stale expanded addition hunks"
12002 );
12003 assert_eq!(
12004 all_expanded_hunks,
12005 vec![(
12006 "const A: u32 = 42;\n".to_string(),
12007 DiffHunkStatus::Removed,
12008 DisplayRow(2)..DisplayRow(2)
12009 )],
12010 "Should open hunks that were adjacent to the stale addition one"
12011 );
12012 });
12013}
12014
12015#[gpui::test]
12016async fn test_edits_around_toggled_deletions(
12017 executor: BackgroundExecutor,
12018 cx: &mut gpui::TestAppContext,
12019) {
12020 init_test(cx, |_| {});
12021
12022 let mut cx = EditorTestContext::new(cx).await;
12023
12024 let diff_base = r#"
12025 use some::mod1;
12026 use some::mod2;
12027
12028 const A: u32 = 42;
12029 const B: u32 = 42;
12030 const C: u32 = 42;
12031
12032
12033 fn main() {
12034 println!("hello");
12035
12036 println!("world");
12037 }
12038 "#
12039 .unindent();
12040 executor.run_until_parked();
12041 cx.set_state(
12042 &r#"
12043 use some::mod1;
12044 use some::mod2;
12045
12046 ˇconst B: u32 = 42;
12047 const C: u32 = 42;
12048
12049
12050 fn main() {
12051 println!("hello");
12052
12053 println!("world");
12054 }
12055 "#
12056 .unindent(),
12057 );
12058
12059 cx.set_diff_base(Some(&diff_base));
12060 executor.run_until_parked();
12061 cx.update_editor(|editor, cx| {
12062 let snapshot = editor.snapshot(cx);
12063 let all_hunks = editor_hunks(editor, &snapshot, cx);
12064 assert_eq!(
12065 all_hunks,
12066 vec![(
12067 "const A: u32 = 42;\n".to_string(),
12068 DiffHunkStatus::Removed,
12069 DisplayRow(3)..DisplayRow(3)
12070 )]
12071 );
12072 });
12073 cx.update_editor(|editor, cx| {
12074 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12075 });
12076 executor.run_until_parked();
12077 cx.assert_editor_state(
12078 &r#"
12079 use some::mod1;
12080 use some::mod2;
12081
12082 ˇconst B: u32 = 42;
12083 const C: u32 = 42;
12084
12085
12086 fn main() {
12087 println!("hello");
12088
12089 println!("world");
12090 }
12091 "#
12092 .unindent(),
12093 );
12094 cx.update_editor(|editor, cx| {
12095 let snapshot = editor.snapshot(cx);
12096 let all_hunks = editor_hunks(editor, &snapshot, cx);
12097 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12098 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
12099 assert_eq!(
12100 all_hunks,
12101 vec![(
12102 "const A: u32 = 42;\n".to_string(),
12103 DiffHunkStatus::Removed,
12104 DisplayRow(4)..DisplayRow(4)
12105 )]
12106 );
12107 assert_eq!(all_hunks, all_expanded_hunks);
12108 });
12109
12110 cx.update_editor(|editor, cx| {
12111 editor.delete_line(&DeleteLine, cx);
12112 });
12113 executor.run_until_parked();
12114 cx.assert_editor_state(
12115 &r#"
12116 use some::mod1;
12117 use some::mod2;
12118
12119 ˇconst C: u32 = 42;
12120
12121
12122 fn main() {
12123 println!("hello");
12124
12125 println!("world");
12126 }
12127 "#
12128 .unindent(),
12129 );
12130 cx.update_editor(|editor, cx| {
12131 let snapshot = editor.snapshot(cx);
12132 let all_hunks = editor_hunks(editor, &snapshot, cx);
12133 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12134 assert_eq!(
12135 expanded_hunks_background_highlights(editor, cx),
12136 Vec::new(),
12137 "Deleted hunks do not highlight current editor's background"
12138 );
12139 assert_eq!(
12140 all_hunks,
12141 vec![(
12142 "const A: u32 = 42;\nconst B: u32 = 42;\n".to_string(),
12143 DiffHunkStatus::Removed,
12144 DisplayRow(5)..DisplayRow(5)
12145 )]
12146 );
12147 assert_eq!(all_hunks, all_expanded_hunks);
12148 });
12149
12150 cx.update_editor(|editor, cx| {
12151 editor.delete_line(&DeleteLine, cx);
12152 });
12153 executor.run_until_parked();
12154 cx.assert_editor_state(
12155 &r#"
12156 use some::mod1;
12157 use some::mod2;
12158
12159 ˇ
12160
12161 fn main() {
12162 println!("hello");
12163
12164 println!("world");
12165 }
12166 "#
12167 .unindent(),
12168 );
12169 cx.update_editor(|editor, cx| {
12170 let snapshot = editor.snapshot(cx);
12171 let all_hunks = editor_hunks(editor, &snapshot, cx);
12172 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12173 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
12174 assert_eq!(
12175 all_hunks,
12176 vec![(
12177 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12178 DiffHunkStatus::Removed,
12179 DisplayRow(6)..DisplayRow(6)
12180 )]
12181 );
12182 assert_eq!(all_hunks, all_expanded_hunks);
12183 });
12184
12185 cx.update_editor(|editor, cx| {
12186 editor.handle_input("replacement", cx);
12187 });
12188 executor.run_until_parked();
12189 cx.assert_editor_state(
12190 &r#"
12191 use some::mod1;
12192 use some::mod2;
12193
12194 replacementˇ
12195
12196 fn main() {
12197 println!("hello");
12198
12199 println!("world");
12200 }
12201 "#
12202 .unindent(),
12203 );
12204 cx.update_editor(|editor, cx| {
12205 let snapshot = editor.snapshot(cx);
12206 let all_hunks = editor_hunks(editor, &snapshot, cx);
12207 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12208 assert_eq!(
12209 all_hunks,
12210 vec![(
12211 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n\n".to_string(),
12212 DiffHunkStatus::Modified,
12213 DisplayRow(7)..DisplayRow(8)
12214 )]
12215 );
12216 assert_eq!(
12217 expanded_hunks_background_highlights(editor, cx),
12218 vec![DisplayRow(7)..=DisplayRow(7)],
12219 "Modified expanded hunks should display additions and highlight their background"
12220 );
12221 assert_eq!(all_hunks, all_expanded_hunks);
12222 });
12223}
12224
12225#[gpui::test]
12226async fn test_edits_around_toggled_modifications(
12227 executor: BackgroundExecutor,
12228 cx: &mut gpui::TestAppContext,
12229) {
12230 init_test(cx, |_| {});
12231
12232 let mut cx = EditorTestContext::new(cx).await;
12233
12234 let diff_base = r#"
12235 use some::mod1;
12236 use some::mod2;
12237
12238 const A: u32 = 42;
12239 const B: u32 = 42;
12240 const C: u32 = 42;
12241 const D: u32 = 42;
12242
12243
12244 fn main() {
12245 println!("hello");
12246
12247 println!("world");
12248 }"#
12249 .unindent();
12250 executor.run_until_parked();
12251 cx.set_state(
12252 &r#"
12253 use some::mod1;
12254 use some::mod2;
12255
12256 const A: u32 = 42;
12257 const B: u32 = 42;
12258 const C: u32 = 43ˇ
12259 const D: u32 = 42;
12260
12261
12262 fn main() {
12263 println!("hello");
12264
12265 println!("world");
12266 }"#
12267 .unindent(),
12268 );
12269
12270 cx.set_diff_base(Some(&diff_base));
12271 executor.run_until_parked();
12272 cx.update_editor(|editor, cx| {
12273 let snapshot = editor.snapshot(cx);
12274 let all_hunks = editor_hunks(editor, &snapshot, cx);
12275 assert_eq!(
12276 all_hunks,
12277 vec![(
12278 "const C: u32 = 42;\n".to_string(),
12279 DiffHunkStatus::Modified,
12280 DisplayRow(5)..DisplayRow(6)
12281 )]
12282 );
12283 });
12284 cx.update_editor(|editor, cx| {
12285 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12286 });
12287 executor.run_until_parked();
12288 cx.assert_editor_state(
12289 &r#"
12290 use some::mod1;
12291 use some::mod2;
12292
12293 const A: u32 = 42;
12294 const B: u32 = 42;
12295 const C: u32 = 43ˇ
12296 const D: u32 = 42;
12297
12298
12299 fn main() {
12300 println!("hello");
12301
12302 println!("world");
12303 }"#
12304 .unindent(),
12305 );
12306 cx.update_editor(|editor, cx| {
12307 let snapshot = editor.snapshot(cx);
12308 let all_hunks = editor_hunks(editor, &snapshot, cx);
12309 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12310 assert_eq!(
12311 expanded_hunks_background_highlights(editor, cx),
12312 vec![DisplayRow(6)..=DisplayRow(6)],
12313 );
12314 assert_eq!(
12315 all_hunks,
12316 vec![(
12317 "const C: u32 = 42;\n".to_string(),
12318 DiffHunkStatus::Modified,
12319 DisplayRow(6)..DisplayRow(7)
12320 )]
12321 );
12322 assert_eq!(all_hunks, all_expanded_hunks);
12323 });
12324
12325 cx.update_editor(|editor, cx| {
12326 editor.handle_input("\nnew_line\n", cx);
12327 });
12328 executor.run_until_parked();
12329 cx.assert_editor_state(
12330 &r#"
12331 use some::mod1;
12332 use some::mod2;
12333
12334 const A: u32 = 42;
12335 const B: u32 = 42;
12336 const C: u32 = 43
12337 new_line
12338 ˇ
12339 const D: u32 = 42;
12340
12341
12342 fn main() {
12343 println!("hello");
12344
12345 println!("world");
12346 }"#
12347 .unindent(),
12348 );
12349 cx.update_editor(|editor, cx| {
12350 let snapshot = editor.snapshot(cx);
12351 let all_hunks = editor_hunks(editor, &snapshot, cx);
12352 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12353 assert_eq!(
12354 expanded_hunks_background_highlights(editor, cx),
12355 vec![DisplayRow(6)..=DisplayRow(6)],
12356 "Modified hunk should grow highlighted lines on more text additions"
12357 );
12358 assert_eq!(
12359 all_hunks,
12360 vec![(
12361 "const C: u32 = 42;\n".to_string(),
12362 DiffHunkStatus::Modified,
12363 DisplayRow(6)..DisplayRow(9)
12364 )]
12365 );
12366 assert_eq!(all_hunks, all_expanded_hunks);
12367 });
12368
12369 cx.update_editor(|editor, cx| {
12370 editor.move_up(&MoveUp, cx);
12371 editor.move_up(&MoveUp, cx);
12372 editor.move_up(&MoveUp, cx);
12373 editor.delete_line(&DeleteLine, cx);
12374 });
12375 executor.run_until_parked();
12376 cx.assert_editor_state(
12377 &r#"
12378 use some::mod1;
12379 use some::mod2;
12380
12381 const A: u32 = 42;
12382 ˇconst C: u32 = 43
12383 new_line
12384
12385 const D: u32 = 42;
12386
12387
12388 fn main() {
12389 println!("hello");
12390
12391 println!("world");
12392 }"#
12393 .unindent(),
12394 );
12395 cx.update_editor(|editor, cx| {
12396 let snapshot = editor.snapshot(cx);
12397 let all_hunks = editor_hunks(editor, &snapshot, cx);
12398 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12399 assert_eq!(
12400 expanded_hunks_background_highlights(editor, cx),
12401 vec![DisplayRow(6)..=DisplayRow(8)],
12402 );
12403 assert_eq!(
12404 all_hunks,
12405 vec![(
12406 "const B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12407 DiffHunkStatus::Modified,
12408 DisplayRow(6)..DisplayRow(9)
12409 )],
12410 "Modified hunk should grow deleted lines on text deletions above"
12411 );
12412 assert_eq!(all_hunks, all_expanded_hunks);
12413 });
12414
12415 cx.update_editor(|editor, cx| {
12416 editor.move_up(&MoveUp, cx);
12417 editor.handle_input("v", cx);
12418 });
12419 executor.run_until_parked();
12420 cx.assert_editor_state(
12421 &r#"
12422 use some::mod1;
12423 use some::mod2;
12424
12425 vˇconst A: u32 = 42;
12426 const C: u32 = 43
12427 new_line
12428
12429 const D: u32 = 42;
12430
12431
12432 fn main() {
12433 println!("hello");
12434
12435 println!("world");
12436 }"#
12437 .unindent(),
12438 );
12439 cx.update_editor(|editor, cx| {
12440 let snapshot = editor.snapshot(cx);
12441 let all_hunks = editor_hunks(editor, &snapshot, cx);
12442 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12443 assert_eq!(
12444 expanded_hunks_background_highlights(editor, cx),
12445 vec![DisplayRow(6)..=DisplayRow(9)],
12446 "Modified hunk should grow deleted lines on text modifications above"
12447 );
12448 assert_eq!(
12449 all_hunks,
12450 vec![(
12451 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12452 DiffHunkStatus::Modified,
12453 DisplayRow(6)..DisplayRow(10)
12454 )]
12455 );
12456 assert_eq!(all_hunks, all_expanded_hunks);
12457 });
12458
12459 cx.update_editor(|editor, cx| {
12460 editor.move_down(&MoveDown, cx);
12461 editor.move_down(&MoveDown, cx);
12462 editor.delete_line(&DeleteLine, cx)
12463 });
12464 executor.run_until_parked();
12465 cx.assert_editor_state(
12466 &r#"
12467 use some::mod1;
12468 use some::mod2;
12469
12470 vconst A: u32 = 42;
12471 const C: u32 = 43
12472 ˇ
12473 const D: u32 = 42;
12474
12475
12476 fn main() {
12477 println!("hello");
12478
12479 println!("world");
12480 }"#
12481 .unindent(),
12482 );
12483 cx.update_editor(|editor, cx| {
12484 let snapshot = editor.snapshot(cx);
12485 let all_hunks = editor_hunks(editor, &snapshot, cx);
12486 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12487 assert_eq!(
12488 expanded_hunks_background_highlights(editor, cx),
12489 vec![DisplayRow(6)..=DisplayRow(8)],
12490 "Modified hunk should grow shrink lines on modification lines removal"
12491 );
12492 assert_eq!(
12493 all_hunks,
12494 vec![(
12495 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12496 DiffHunkStatus::Modified,
12497 DisplayRow(6)..DisplayRow(9)
12498 )]
12499 );
12500 assert_eq!(all_hunks, all_expanded_hunks);
12501 });
12502
12503 cx.update_editor(|editor, cx| {
12504 editor.move_up(&MoveUp, cx);
12505 editor.move_up(&MoveUp, cx);
12506 editor.select_down_by_lines(&SelectDownByLines { lines: 4 }, cx);
12507 editor.delete_line(&DeleteLine, cx)
12508 });
12509 executor.run_until_parked();
12510 cx.assert_editor_state(
12511 &r#"
12512 use some::mod1;
12513 use some::mod2;
12514
12515 ˇ
12516
12517 fn main() {
12518 println!("hello");
12519
12520 println!("world");
12521 }"#
12522 .unindent(),
12523 );
12524 cx.update_editor(|editor, cx| {
12525 let snapshot = editor.snapshot(cx);
12526 let all_hunks = editor_hunks(editor, &snapshot, cx);
12527 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12528 assert_eq!(
12529 expanded_hunks_background_highlights(editor, cx),
12530 Vec::new(),
12531 "Modified hunk should turn into a removed one on all modified lines removal"
12532 );
12533 assert_eq!(
12534 all_hunks,
12535 vec![(
12536 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\nconst D: u32 = 42;\n"
12537 .to_string(),
12538 DiffHunkStatus::Removed,
12539 DisplayRow(7)..DisplayRow(7)
12540 )]
12541 );
12542 assert_eq!(all_hunks, all_expanded_hunks);
12543 });
12544}
12545
12546#[gpui::test]
12547async fn test_multiple_expanded_hunks_merge(
12548 executor: BackgroundExecutor,
12549 cx: &mut gpui::TestAppContext,
12550) {
12551 init_test(cx, |_| {});
12552
12553 let mut cx = EditorTestContext::new(cx).await;
12554
12555 let diff_base = r#"
12556 use some::mod1;
12557 use some::mod2;
12558
12559 const A: u32 = 42;
12560 const B: u32 = 42;
12561 const C: u32 = 42;
12562 const D: u32 = 42;
12563
12564
12565 fn main() {
12566 println!("hello");
12567
12568 println!("world");
12569 }"#
12570 .unindent();
12571 executor.run_until_parked();
12572 cx.set_state(
12573 &r#"
12574 use some::mod1;
12575 use some::mod2;
12576
12577 const A: u32 = 42;
12578 const B: u32 = 42;
12579 const C: u32 = 43ˇ
12580 const D: u32 = 42;
12581
12582
12583 fn main() {
12584 println!("hello");
12585
12586 println!("world");
12587 }"#
12588 .unindent(),
12589 );
12590
12591 cx.set_diff_base(Some(&diff_base));
12592 executor.run_until_parked();
12593 cx.update_editor(|editor, cx| {
12594 let snapshot = editor.snapshot(cx);
12595 let all_hunks = editor_hunks(editor, &snapshot, cx);
12596 assert_eq!(
12597 all_hunks,
12598 vec![(
12599 "const C: u32 = 42;\n".to_string(),
12600 DiffHunkStatus::Modified,
12601 DisplayRow(5)..DisplayRow(6)
12602 )]
12603 );
12604 });
12605 cx.update_editor(|editor, cx| {
12606 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12607 });
12608 executor.run_until_parked();
12609 cx.assert_editor_state(
12610 &r#"
12611 use some::mod1;
12612 use some::mod2;
12613
12614 const A: u32 = 42;
12615 const B: u32 = 42;
12616 const C: u32 = 43ˇ
12617 const D: u32 = 42;
12618
12619
12620 fn main() {
12621 println!("hello");
12622
12623 println!("world");
12624 }"#
12625 .unindent(),
12626 );
12627 cx.update_editor(|editor, cx| {
12628 let snapshot = editor.snapshot(cx);
12629 let all_hunks = editor_hunks(editor, &snapshot, cx);
12630 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
12631 assert_eq!(
12632 expanded_hunks_background_highlights(editor, cx),
12633 vec![DisplayRow(6)..=DisplayRow(6)],
12634 );
12635 assert_eq!(
12636 all_hunks,
12637 vec![(
12638 "const C: u32 = 42;\n".to_string(),
12639 DiffHunkStatus::Modified,
12640 DisplayRow(6)..DisplayRow(7)
12641 )]
12642 );
12643 assert_eq!(all_hunks, all_expanded_hunks);
12644 });
12645
12646 cx.update_editor(|editor, cx| {
12647 editor.handle_input("\nnew_line\n", cx);
12648 });
12649 executor.run_until_parked();
12650 cx.assert_editor_state(
12651 &r#"
12652 use some::mod1;
12653 use some::mod2;
12654
12655 const A: u32 = 42;
12656 const B: u32 = 42;
12657 const C: u32 = 43
12658 new_line
12659 ˇ
12660 const D: u32 = 42;
12661
12662
12663 fn main() {
12664 println!("hello");
12665
12666 println!("world");
12667 }"#
12668 .unindent(),
12669 );
12670}
12671
12672async fn setup_indent_guides_editor(
12673 text: &str,
12674 cx: &mut gpui::TestAppContext,
12675) -> (BufferId, EditorTestContext) {
12676 init_test(cx, |_| {});
12677
12678 let mut cx = EditorTestContext::new(cx).await;
12679
12680 let buffer_id = cx.update_editor(|editor, cx| {
12681 editor.set_text(text, cx);
12682 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12683
12684 buffer_ids[0]
12685 });
12686
12687 (buffer_id, cx)
12688}
12689
12690fn assert_indent_guides(
12691 range: Range<u32>,
12692 expected: Vec<IndentGuide>,
12693 active_indices: Option<Vec<usize>>,
12694 cx: &mut EditorTestContext,
12695) {
12696 let indent_guides = cx.update_editor(|editor, cx| {
12697 let snapshot = editor.snapshot(cx).display_snapshot;
12698 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12699 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12700 true,
12701 &snapshot,
12702 cx,
12703 );
12704
12705 indent_guides.sort_by(|a, b| {
12706 a.depth.cmp(&b.depth).then(
12707 a.start_row
12708 .cmp(&b.start_row)
12709 .then(a.end_row.cmp(&b.end_row)),
12710 )
12711 });
12712 indent_guides
12713 });
12714
12715 if let Some(expected) = active_indices {
12716 let active_indices = cx.update_editor(|editor, cx| {
12717 let snapshot = editor.snapshot(cx).display_snapshot;
12718 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
12719 });
12720
12721 assert_eq!(
12722 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
12723 expected,
12724 "Active indent guide indices do not match"
12725 );
12726 }
12727
12728 let expected: Vec<_> = expected
12729 .into_iter()
12730 .map(|guide| MultiBufferIndentGuide {
12731 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
12732 buffer: guide,
12733 })
12734 .collect();
12735
12736 assert_eq!(indent_guides, expected, "Indent guides do not match");
12737}
12738
12739fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
12740 IndentGuide {
12741 buffer_id,
12742 start_row,
12743 end_row,
12744 depth,
12745 tab_size: 4,
12746 settings: IndentGuideSettings {
12747 enabled: true,
12748 line_width: 1,
12749 active_line_width: 1,
12750 ..Default::default()
12751 },
12752 }
12753}
12754
12755#[gpui::test]
12756async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12757 let (buffer_id, mut cx) = setup_indent_guides_editor(
12758 &"
12759 fn main() {
12760 let a = 1;
12761 }"
12762 .unindent(),
12763 cx,
12764 )
12765 .await;
12766
12767 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12768}
12769
12770#[gpui::test]
12771async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
12772 let (buffer_id, mut cx) = setup_indent_guides_editor(
12773 &"
12774 fn main() {
12775 let a = 1;
12776 let b = 2;
12777 }"
12778 .unindent(),
12779 cx,
12780 )
12781 .await;
12782
12783 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
12784}
12785
12786#[gpui::test]
12787async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
12788 let (buffer_id, mut cx) = setup_indent_guides_editor(
12789 &"
12790 fn main() {
12791 let a = 1;
12792 if a == 3 {
12793 let b = 2;
12794 } else {
12795 let c = 3;
12796 }
12797 }"
12798 .unindent(),
12799 cx,
12800 )
12801 .await;
12802
12803 assert_indent_guides(
12804 0..8,
12805 vec![
12806 indent_guide(buffer_id, 1, 6, 0),
12807 indent_guide(buffer_id, 3, 3, 1),
12808 indent_guide(buffer_id, 5, 5, 1),
12809 ],
12810 None,
12811 &mut cx,
12812 );
12813}
12814
12815#[gpui::test]
12816async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
12817 let (buffer_id, mut cx) = setup_indent_guides_editor(
12818 &"
12819 fn main() {
12820 let a = 1;
12821 let b = 2;
12822 let c = 3;
12823 }"
12824 .unindent(),
12825 cx,
12826 )
12827 .await;
12828
12829 assert_indent_guides(
12830 0..5,
12831 vec![
12832 indent_guide(buffer_id, 1, 3, 0),
12833 indent_guide(buffer_id, 2, 2, 1),
12834 ],
12835 None,
12836 &mut cx,
12837 );
12838}
12839
12840#[gpui::test]
12841async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
12842 let (buffer_id, mut cx) = setup_indent_guides_editor(
12843 &"
12844 fn main() {
12845 let a = 1;
12846
12847 let c = 3;
12848 }"
12849 .unindent(),
12850 cx,
12851 )
12852 .await;
12853
12854 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
12855}
12856
12857#[gpui::test]
12858async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
12859 let (buffer_id, mut cx) = setup_indent_guides_editor(
12860 &"
12861 fn main() {
12862 let a = 1;
12863
12864 let c = 3;
12865
12866 if a == 3 {
12867 let b = 2;
12868 } else {
12869 let c = 3;
12870 }
12871 }"
12872 .unindent(),
12873 cx,
12874 )
12875 .await;
12876
12877 assert_indent_guides(
12878 0..11,
12879 vec![
12880 indent_guide(buffer_id, 1, 9, 0),
12881 indent_guide(buffer_id, 6, 6, 1),
12882 indent_guide(buffer_id, 8, 8, 1),
12883 ],
12884 None,
12885 &mut cx,
12886 );
12887}
12888
12889#[gpui::test]
12890async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
12891 let (buffer_id, mut cx) = setup_indent_guides_editor(
12892 &"
12893 fn main() {
12894 let a = 1;
12895
12896 let c = 3;
12897
12898 if a == 3 {
12899 let b = 2;
12900 } else {
12901 let c = 3;
12902 }
12903 }"
12904 .unindent(),
12905 cx,
12906 )
12907 .await;
12908
12909 assert_indent_guides(
12910 1..11,
12911 vec![
12912 indent_guide(buffer_id, 1, 9, 0),
12913 indent_guide(buffer_id, 6, 6, 1),
12914 indent_guide(buffer_id, 8, 8, 1),
12915 ],
12916 None,
12917 &mut cx,
12918 );
12919}
12920
12921#[gpui::test]
12922async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
12923 let (buffer_id, mut cx) = setup_indent_guides_editor(
12924 &"
12925 fn main() {
12926 let a = 1;
12927
12928 let c = 3;
12929
12930 if a == 3 {
12931 let b = 2;
12932 } else {
12933 let c = 3;
12934 }
12935 }"
12936 .unindent(),
12937 cx,
12938 )
12939 .await;
12940
12941 assert_indent_guides(
12942 1..10,
12943 vec![
12944 indent_guide(buffer_id, 1, 9, 0),
12945 indent_guide(buffer_id, 6, 6, 1),
12946 indent_guide(buffer_id, 8, 8, 1),
12947 ],
12948 None,
12949 &mut cx,
12950 );
12951}
12952
12953#[gpui::test]
12954async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
12955 let (buffer_id, mut cx) = setup_indent_guides_editor(
12956 &"
12957 block1
12958 block2
12959 block3
12960 block4
12961 block2
12962 block1
12963 block1"
12964 .unindent(),
12965 cx,
12966 )
12967 .await;
12968
12969 assert_indent_guides(
12970 1..10,
12971 vec![
12972 indent_guide(buffer_id, 1, 4, 0),
12973 indent_guide(buffer_id, 2, 3, 1),
12974 indent_guide(buffer_id, 3, 3, 2),
12975 ],
12976 None,
12977 &mut cx,
12978 );
12979}
12980
12981#[gpui::test]
12982async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
12983 let (buffer_id, mut cx) = setup_indent_guides_editor(
12984 &"
12985 block1
12986 block2
12987 block3
12988
12989 block1
12990 block1"
12991 .unindent(),
12992 cx,
12993 )
12994 .await;
12995
12996 assert_indent_guides(
12997 0..6,
12998 vec![
12999 indent_guide(buffer_id, 1, 2, 0),
13000 indent_guide(buffer_id, 2, 2, 1),
13001 ],
13002 None,
13003 &mut cx,
13004 );
13005}
13006
13007#[gpui::test]
13008async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
13009 let (buffer_id, mut cx) = setup_indent_guides_editor(
13010 &"
13011 block1
13012
13013
13014
13015 block2
13016 "
13017 .unindent(),
13018 cx,
13019 )
13020 .await;
13021
13022 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13023}
13024
13025#[gpui::test]
13026async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
13027 let (buffer_id, mut cx) = setup_indent_guides_editor(
13028 &"
13029 def a:
13030 \tb = 3
13031 \tif True:
13032 \t\tc = 4
13033 \t\td = 5
13034 \tprint(b)
13035 "
13036 .unindent(),
13037 cx,
13038 )
13039 .await;
13040
13041 assert_indent_guides(
13042 0..6,
13043 vec![
13044 indent_guide(buffer_id, 1, 6, 0),
13045 indent_guide(buffer_id, 3, 4, 1),
13046 ],
13047 None,
13048 &mut cx,
13049 );
13050}
13051
13052#[gpui::test]
13053async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13054 let (buffer_id, mut cx) = setup_indent_guides_editor(
13055 &"
13056 fn main() {
13057 let a = 1;
13058 }"
13059 .unindent(),
13060 cx,
13061 )
13062 .await;
13063
13064 cx.update_editor(|editor, cx| {
13065 editor.change_selections(None, cx, |s| {
13066 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13067 });
13068 });
13069
13070 assert_indent_guides(
13071 0..3,
13072 vec![indent_guide(buffer_id, 1, 1, 0)],
13073 Some(vec![0]),
13074 &mut cx,
13075 );
13076}
13077
13078#[gpui::test]
13079async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13080 let (buffer_id, mut cx) = setup_indent_guides_editor(
13081 &"
13082 fn main() {
13083 if 1 == 2 {
13084 let a = 1;
13085 }
13086 }"
13087 .unindent(),
13088 cx,
13089 )
13090 .await;
13091
13092 cx.update_editor(|editor, cx| {
13093 editor.change_selections(None, cx, |s| {
13094 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13095 });
13096 });
13097
13098 assert_indent_guides(
13099 0..4,
13100 vec![
13101 indent_guide(buffer_id, 1, 3, 0),
13102 indent_guide(buffer_id, 2, 2, 1),
13103 ],
13104 Some(vec![1]),
13105 &mut cx,
13106 );
13107
13108 cx.update_editor(|editor, cx| {
13109 editor.change_selections(None, cx, |s| {
13110 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13111 });
13112 });
13113
13114 assert_indent_guides(
13115 0..4,
13116 vec![
13117 indent_guide(buffer_id, 1, 3, 0),
13118 indent_guide(buffer_id, 2, 2, 1),
13119 ],
13120 Some(vec![1]),
13121 &mut cx,
13122 );
13123
13124 cx.update_editor(|editor, cx| {
13125 editor.change_selections(None, cx, |s| {
13126 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13127 });
13128 });
13129
13130 assert_indent_guides(
13131 0..4,
13132 vec![
13133 indent_guide(buffer_id, 1, 3, 0),
13134 indent_guide(buffer_id, 2, 2, 1),
13135 ],
13136 Some(vec![0]),
13137 &mut cx,
13138 );
13139}
13140
13141#[gpui::test]
13142async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13143 let (buffer_id, mut cx) = setup_indent_guides_editor(
13144 &"
13145 fn main() {
13146 let a = 1;
13147
13148 let b = 2;
13149 }"
13150 .unindent(),
13151 cx,
13152 )
13153 .await;
13154
13155 cx.update_editor(|editor, cx| {
13156 editor.change_selections(None, cx, |s| {
13157 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13158 });
13159 });
13160
13161 assert_indent_guides(
13162 0..5,
13163 vec![indent_guide(buffer_id, 1, 3, 0)],
13164 Some(vec![0]),
13165 &mut cx,
13166 );
13167}
13168
13169#[gpui::test]
13170async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13171 let (buffer_id, mut cx) = setup_indent_guides_editor(
13172 &"
13173 def m:
13174 a = 1
13175 pass"
13176 .unindent(),
13177 cx,
13178 )
13179 .await;
13180
13181 cx.update_editor(|editor, cx| {
13182 editor.change_selections(None, cx, |s| {
13183 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13184 });
13185 });
13186
13187 assert_indent_guides(
13188 0..3,
13189 vec![indent_guide(buffer_id, 1, 2, 0)],
13190 Some(vec![0]),
13191 &mut cx,
13192 );
13193}
13194
13195#[gpui::test]
13196fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13197 init_test(cx, |_| {});
13198
13199 let editor = cx.add_window(|cx| {
13200 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13201 build_editor(buffer, cx)
13202 });
13203
13204 let render_args = Arc::new(Mutex::new(None));
13205 let snapshot = editor
13206 .update(cx, |editor, cx| {
13207 let snapshot = editor.buffer().read(cx).snapshot(cx);
13208 let range =
13209 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13210
13211 struct RenderArgs {
13212 row: MultiBufferRow,
13213 folded: bool,
13214 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13215 }
13216
13217 let crease = Crease::new(
13218 range,
13219 FoldPlaceholder::test(),
13220 {
13221 let toggle_callback = render_args.clone();
13222 move |row, folded, callback, _cx| {
13223 *toggle_callback.lock() = Some(RenderArgs {
13224 row,
13225 folded,
13226 callback,
13227 });
13228 div()
13229 }
13230 },
13231 |_row, _folded, _cx| div(),
13232 );
13233
13234 editor.insert_creases(Some(crease), cx);
13235 let snapshot = editor.snapshot(cx);
13236 let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13237 snapshot
13238 })
13239 .unwrap();
13240
13241 let render_args = render_args.lock().take().unwrap();
13242 assert_eq!(render_args.row, MultiBufferRow(1));
13243 assert!(!render_args.folded);
13244 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13245
13246 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13247 .unwrap();
13248 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13249 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13250
13251 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13252 .unwrap();
13253 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13254 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13255}
13256
13257#[gpui::test]
13258async fn test_input_text(cx: &mut gpui::TestAppContext) {
13259 init_test(cx, |_| {});
13260 let mut cx = EditorTestContext::new(cx).await;
13261
13262 cx.set_state(
13263 &r#"ˇone
13264 two
13265
13266 three
13267 fourˇ
13268 five
13269
13270 siˇx"#
13271 .unindent(),
13272 );
13273
13274 cx.dispatch_action(HandleInput(String::new()));
13275 cx.assert_editor_state(
13276 &r#"ˇone
13277 two
13278
13279 three
13280 fourˇ
13281 five
13282
13283 siˇx"#
13284 .unindent(),
13285 );
13286
13287 cx.dispatch_action(HandleInput("AAAA".to_string()));
13288 cx.assert_editor_state(
13289 &r#"AAAAˇone
13290 two
13291
13292 three
13293 fourAAAAˇ
13294 five
13295
13296 siAAAAˇx"#
13297 .unindent(),
13298 );
13299}
13300
13301#[gpui::test]
13302async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13303 init_test(cx, |_| {});
13304
13305 let mut cx = EditorTestContext::new(cx).await;
13306 cx.set_state(
13307 r#"let foo = 1;
13308let foo = 2;
13309let foo = 3;
13310let fooˇ = 4;
13311let foo = 5;
13312let foo = 6;
13313let foo = 7;
13314let foo = 8;
13315let foo = 9;
13316let foo = 10;
13317let foo = 11;
13318let foo = 12;
13319let foo = 13;
13320let foo = 14;
13321let foo = 15;"#,
13322 );
13323
13324 cx.update_editor(|e, cx| {
13325 assert_eq!(
13326 e.next_scroll_position,
13327 NextScrollCursorCenterTopBottom::Center,
13328 "Default next scroll direction is center",
13329 );
13330
13331 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13332 assert_eq!(
13333 e.next_scroll_position,
13334 NextScrollCursorCenterTopBottom::Top,
13335 "After center, next scroll direction should be top",
13336 );
13337
13338 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13339 assert_eq!(
13340 e.next_scroll_position,
13341 NextScrollCursorCenterTopBottom::Bottom,
13342 "After top, next scroll direction should be bottom",
13343 );
13344
13345 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13346 assert_eq!(
13347 e.next_scroll_position,
13348 NextScrollCursorCenterTopBottom::Center,
13349 "After bottom, scrolling should start over",
13350 );
13351
13352 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13353 assert_eq!(
13354 e.next_scroll_position,
13355 NextScrollCursorCenterTopBottom::Top,
13356 "Scrolling continues if retriggered fast enough"
13357 );
13358 });
13359
13360 cx.executor()
13361 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13362 cx.executor().run_until_parked();
13363 cx.update_editor(|e, _| {
13364 assert_eq!(
13365 e.next_scroll_position,
13366 NextScrollCursorCenterTopBottom::Center,
13367 "If scrolling is not triggered fast enough, it should reset"
13368 );
13369 });
13370}
13371
13372#[gpui::test]
13373async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13374 init_test(cx, |_| {});
13375 let mut cx = EditorLspTestContext::new_rust(
13376 lsp::ServerCapabilities {
13377 definition_provider: Some(lsp::OneOf::Left(true)),
13378 references_provider: Some(lsp::OneOf::Left(true)),
13379 ..lsp::ServerCapabilities::default()
13380 },
13381 cx,
13382 )
13383 .await;
13384
13385 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13386 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13387 move |params, _| async move {
13388 if empty_go_to_definition {
13389 Ok(None)
13390 } else {
13391 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13392 uri: params.text_document_position_params.text_document.uri,
13393 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13394 })))
13395 }
13396 },
13397 );
13398 let references =
13399 cx.lsp
13400 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13401 Ok(Some(vec![lsp::Location {
13402 uri: params.text_document_position.text_document.uri,
13403 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13404 }]))
13405 });
13406 (go_to_definition, references)
13407 };
13408
13409 cx.set_state(
13410 &r#"fn one() {
13411 let mut a = ˇtwo();
13412 }
13413
13414 fn two() {}"#
13415 .unindent(),
13416 );
13417 set_up_lsp_handlers(false, &mut cx);
13418 let navigated = cx
13419 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13420 .await
13421 .expect("Failed to navigate to definition");
13422 assert_eq!(
13423 navigated,
13424 Navigated::Yes,
13425 "Should have navigated to definition from the GetDefinition response"
13426 );
13427 cx.assert_editor_state(
13428 &r#"fn one() {
13429 let mut a = two();
13430 }
13431
13432 fn «twoˇ»() {}"#
13433 .unindent(),
13434 );
13435
13436 let editors = cx.update_workspace(|workspace, cx| {
13437 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13438 });
13439 cx.update_editor(|_, test_editor_cx| {
13440 assert_eq!(
13441 editors.len(),
13442 1,
13443 "Initially, only one, test, editor should be open in the workspace"
13444 );
13445 assert_eq!(
13446 test_editor_cx.view(),
13447 editors.last().expect("Asserted len is 1")
13448 );
13449 });
13450
13451 set_up_lsp_handlers(true, &mut cx);
13452 let navigated = cx
13453 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13454 .await
13455 .expect("Failed to navigate to lookup references");
13456 assert_eq!(
13457 navigated,
13458 Navigated::Yes,
13459 "Should have navigated to references as a fallback after empty GoToDefinition response"
13460 );
13461 // We should not change the selections in the existing file,
13462 // if opening another milti buffer with the references
13463 cx.assert_editor_state(
13464 &r#"fn one() {
13465 let mut a = two();
13466 }
13467
13468 fn «twoˇ»() {}"#
13469 .unindent(),
13470 );
13471 let editors = cx.update_workspace(|workspace, cx| {
13472 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13473 });
13474 cx.update_editor(|_, test_editor_cx| {
13475 assert_eq!(
13476 editors.len(),
13477 2,
13478 "After falling back to references search, we open a new editor with the results"
13479 );
13480 let references_fallback_text = editors
13481 .into_iter()
13482 .find(|new_editor| new_editor != test_editor_cx.view())
13483 .expect("Should have one non-test editor now")
13484 .read(test_editor_cx)
13485 .text(test_editor_cx);
13486 assert_eq!(
13487 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13488 "Should use the range from the references response and not the GoToDefinition one"
13489 );
13490 });
13491}
13492
13493fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13494 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13495 point..point
13496}
13497
13498fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13499 let (text, ranges) = marked_text_ranges(marked_text, true);
13500 assert_eq!(view.text(cx), text);
13501 assert_eq!(
13502 view.selections.ranges(cx),
13503 ranges,
13504 "Assert selections are {}",
13505 marked_text
13506 );
13507}
13508
13509pub fn handle_signature_help_request(
13510 cx: &mut EditorLspTestContext,
13511 mocked_response: lsp::SignatureHelp,
13512) -> impl Future<Output = ()> {
13513 let mut request =
13514 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13515 let mocked_response = mocked_response.clone();
13516 async move { Ok(Some(mocked_response)) }
13517 });
13518
13519 async move {
13520 request.next().await;
13521 }
13522}
13523
13524/// Handle completion request passing a marked string specifying where the completion
13525/// should be triggered from using '|' character, what range should be replaced, and what completions
13526/// should be returned using '<' and '>' to delimit the range
13527pub fn handle_completion_request(
13528 cx: &mut EditorLspTestContext,
13529 marked_string: &str,
13530 completions: Vec<&'static str>,
13531 counter: Arc<AtomicUsize>,
13532) -> impl Future<Output = ()> {
13533 let complete_from_marker: TextRangeMarker = '|'.into();
13534 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13535 let (_, mut marked_ranges) = marked_text_ranges_by(
13536 marked_string,
13537 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13538 );
13539
13540 let complete_from_position =
13541 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13542 let replace_range =
13543 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13544
13545 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13546 let completions = completions.clone();
13547 counter.fetch_add(1, atomic::Ordering::Release);
13548 async move {
13549 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13550 assert_eq!(
13551 params.text_document_position.position,
13552 complete_from_position
13553 );
13554 Ok(Some(lsp::CompletionResponse::Array(
13555 completions
13556 .iter()
13557 .map(|completion_text| lsp::CompletionItem {
13558 label: completion_text.to_string(),
13559 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13560 range: replace_range,
13561 new_text: completion_text.to_string(),
13562 })),
13563 ..Default::default()
13564 })
13565 .collect(),
13566 )))
13567 }
13568 });
13569
13570 async move {
13571 request.next().await;
13572 }
13573}
13574
13575fn handle_resolve_completion_request(
13576 cx: &mut EditorLspTestContext,
13577 edits: Option<Vec<(&'static str, &'static str)>>,
13578) -> impl Future<Output = ()> {
13579 let edits = edits.map(|edits| {
13580 edits
13581 .iter()
13582 .map(|(marked_string, new_text)| {
13583 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13584 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13585 lsp::TextEdit::new(replace_range, new_text.to_string())
13586 })
13587 .collect::<Vec<_>>()
13588 });
13589
13590 let mut request =
13591 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13592 let edits = edits.clone();
13593 async move {
13594 Ok(lsp::CompletionItem {
13595 additional_text_edits: edits,
13596 ..Default::default()
13597 })
13598 }
13599 });
13600
13601 async move {
13602 request.next().await;
13603 }
13604}
13605
13606pub(crate) fn update_test_language_settings(
13607 cx: &mut TestAppContext,
13608 f: impl Fn(&mut AllLanguageSettingsContent),
13609) {
13610 cx.update(|cx| {
13611 SettingsStore::update_global(cx, |store, cx| {
13612 store.update_user_settings::<AllLanguageSettings>(cx, f);
13613 });
13614 });
13615}
13616
13617pub(crate) fn update_test_project_settings(
13618 cx: &mut TestAppContext,
13619 f: impl Fn(&mut ProjectSettings),
13620) {
13621 cx.update(|cx| {
13622 SettingsStore::update_global(cx, |store, cx| {
13623 store.update_user_settings::<ProjectSettings>(cx, f);
13624 });
13625 });
13626}
13627
13628pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
13629 cx.update(|cx| {
13630 assets::Assets.load_test_fonts(cx);
13631 let store = SettingsStore::test(cx);
13632 cx.set_global(store);
13633 theme::init(theme::LoadThemes::JustBase, cx);
13634 release_channel::init(SemanticVersion::default(), cx);
13635 client::init_settings(cx);
13636 language::init(cx);
13637 Project::init_settings(cx);
13638 workspace::init_settings(cx);
13639 crate::init(cx);
13640 });
13641
13642 update_test_language_settings(cx, f);
13643}
13644
13645pub(crate) fn rust_lang() -> Arc<Language> {
13646 Arc::new(Language::new(
13647 LanguageConfig {
13648 name: "Rust".into(),
13649 matcher: LanguageMatcher {
13650 path_suffixes: vec!["rs".to_string()],
13651 ..Default::default()
13652 },
13653 ..Default::default()
13654 },
13655 Some(tree_sitter_rust::language()),
13656 ))
13657}
13658
13659#[track_caller]
13660fn assert_hunk_revert(
13661 not_reverted_text_with_selections: &str,
13662 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
13663 expected_reverted_text_with_selections: &str,
13664 base_text: &str,
13665 cx: &mut EditorLspTestContext,
13666) {
13667 cx.set_state(not_reverted_text_with_selections);
13668 cx.update_editor(|editor, cx| {
13669 editor
13670 .buffer()
13671 .read(cx)
13672 .as_singleton()
13673 .unwrap()
13674 .update(cx, |buffer, cx| {
13675 buffer.set_diff_base(Some(base_text.into()), cx);
13676 });
13677 });
13678 cx.executor().run_until_parked();
13679
13680 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
13681 let snapshot = editor.buffer().read(cx).snapshot(cx);
13682 let reverted_hunk_statuses = snapshot
13683 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
13684 .map(|hunk| hunk_status(&hunk))
13685 .collect::<Vec<_>>();
13686
13687 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
13688 reverted_hunk_statuses
13689 });
13690 cx.executor().run_until_parked();
13691 cx.assert_editor_state(expected_reverted_text_with_selections);
13692 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
13693}