1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_hunks,
6 editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext,
7 expanded_hunks, expanded_hunks_background_highlights, select_ranges,
8 },
9 JoinLines,
10};
11use futures::StreamExt;
12use gpui::{
13 div, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds,
14 WindowOptions,
15};
16use indoc::indoc;
17use language::{
18 language_settings::{
19 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
20 },
21 BracketPairConfig,
22 Capability::ReadWrite,
23 FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher, Override,
24 ParsedMarkdown, Point,
25};
26use language_settings::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(&DeleteToPreviousWordStart, cx);
2106 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2107 });
2108
2109 _ = view.update(cx, |view, cx| {
2110 view.change_selections(None, cx, |s| {
2111 s.select_display_ranges([
2112 // an empty selection - the following word fragment is deleted
2113 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2114 // characters selected - they are deleted
2115 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2116 ])
2117 });
2118 view.delete_to_next_word_end(&DeleteToNextWordEnd, cx);
2119 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2120 });
2121}
2122
2123#[gpui::test]
2124fn test_newline(cx: &mut TestAppContext) {
2125 init_test(cx, |_| {});
2126
2127 let view = cx.add_window(|cx| {
2128 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2129 build_editor(buffer.clone(), cx)
2130 });
2131
2132 _ = view.update(cx, |view, cx| {
2133 view.change_selections(None, cx, |s| {
2134 s.select_display_ranges([
2135 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2136 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2137 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2138 ])
2139 });
2140
2141 view.newline(&Newline, cx);
2142 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2143 });
2144}
2145
2146#[gpui::test]
2147fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2148 init_test(cx, |_| {});
2149
2150 let editor = cx.add_window(|cx| {
2151 let buffer = MultiBuffer::build_simple(
2152 "
2153 a
2154 b(
2155 X
2156 )
2157 c(
2158 X
2159 )
2160 "
2161 .unindent()
2162 .as_str(),
2163 cx,
2164 );
2165 let mut editor = build_editor(buffer.clone(), cx);
2166 editor.change_selections(None, cx, |s| {
2167 s.select_ranges([
2168 Point::new(2, 4)..Point::new(2, 5),
2169 Point::new(5, 4)..Point::new(5, 5),
2170 ])
2171 });
2172 editor
2173 });
2174
2175 _ = editor.update(cx, |editor, cx| {
2176 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2177 editor.buffer.update(cx, |buffer, cx| {
2178 buffer.edit(
2179 [
2180 (Point::new(1, 2)..Point::new(3, 0), ""),
2181 (Point::new(4, 2)..Point::new(6, 0), ""),
2182 ],
2183 None,
2184 cx,
2185 );
2186 assert_eq!(
2187 buffer.read(cx).text(),
2188 "
2189 a
2190 b()
2191 c()
2192 "
2193 .unindent()
2194 );
2195 });
2196 assert_eq!(
2197 editor.selections.ranges(cx),
2198 &[
2199 Point::new(1, 2)..Point::new(1, 2),
2200 Point::new(2, 2)..Point::new(2, 2),
2201 ],
2202 );
2203
2204 editor.newline(&Newline, cx);
2205 assert_eq!(
2206 editor.text(cx),
2207 "
2208 a
2209 b(
2210 )
2211 c(
2212 )
2213 "
2214 .unindent()
2215 );
2216
2217 // The selections are moved after the inserted newlines
2218 assert_eq!(
2219 editor.selections.ranges(cx),
2220 &[
2221 Point::new(2, 0)..Point::new(2, 0),
2222 Point::new(4, 0)..Point::new(4, 0),
2223 ],
2224 );
2225 });
2226}
2227
2228#[gpui::test]
2229async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2230 init_test(cx, |settings| {
2231 settings.defaults.tab_size = NonZeroU32::new(4)
2232 });
2233
2234 let language = Arc::new(
2235 Language::new(
2236 LanguageConfig::default(),
2237 Some(tree_sitter_rust::language()),
2238 )
2239 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2240 .unwrap(),
2241 );
2242
2243 let mut cx = EditorTestContext::new(cx).await;
2244 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2245 cx.set_state(indoc! {"
2246 const a: ˇA = (
2247 (ˇ
2248 «const_functionˇ»(ˇ),
2249 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2250 )ˇ
2251 ˇ);ˇ
2252 "});
2253
2254 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2255 cx.assert_editor_state(indoc! {"
2256 ˇ
2257 const a: A = (
2258 ˇ
2259 (
2260 ˇ
2261 ˇ
2262 const_function(),
2263 ˇ
2264 ˇ
2265 ˇ
2266 ˇ
2267 something_else,
2268 ˇ
2269 )
2270 ˇ
2271 ˇ
2272 );
2273 "});
2274}
2275
2276#[gpui::test]
2277async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2278 init_test(cx, |settings| {
2279 settings.defaults.tab_size = NonZeroU32::new(4)
2280 });
2281
2282 let language = Arc::new(
2283 Language::new(
2284 LanguageConfig::default(),
2285 Some(tree_sitter_rust::language()),
2286 )
2287 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2288 .unwrap(),
2289 );
2290
2291 let mut cx = EditorTestContext::new(cx).await;
2292 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2293 cx.set_state(indoc! {"
2294 const a: ˇA = (
2295 (ˇ
2296 «const_functionˇ»(ˇ),
2297 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2298 )ˇ
2299 ˇ);ˇ
2300 "});
2301
2302 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2303 cx.assert_editor_state(indoc! {"
2304 const a: A = (
2305 ˇ
2306 (
2307 ˇ
2308 const_function(),
2309 ˇ
2310 ˇ
2311 something_else,
2312 ˇ
2313 ˇ
2314 ˇ
2315 ˇ
2316 )
2317 ˇ
2318 );
2319 ˇ
2320 ˇ
2321 "});
2322}
2323
2324#[gpui::test]
2325async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2326 init_test(cx, |settings| {
2327 settings.defaults.tab_size = NonZeroU32::new(4)
2328 });
2329
2330 let language = Arc::new(Language::new(
2331 LanguageConfig {
2332 line_comments: vec!["//".into()],
2333 ..LanguageConfig::default()
2334 },
2335 None,
2336 ));
2337 {
2338 let mut cx = EditorTestContext::new(cx).await;
2339 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2340 cx.set_state(indoc! {"
2341 // Fooˇ
2342 "});
2343
2344 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2345 cx.assert_editor_state(indoc! {"
2346 // Foo
2347 //ˇ
2348 "});
2349 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2350 cx.set_state(indoc! {"
2351 ˇ// Foo
2352 "});
2353 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2354 cx.assert_editor_state(indoc! {"
2355
2356 ˇ// Foo
2357 "});
2358 }
2359 // Ensure that comment continuations can be disabled.
2360 update_test_language_settings(cx, |settings| {
2361 settings.defaults.extend_comment_on_newline = Some(false);
2362 });
2363 let mut cx = EditorTestContext::new(cx).await;
2364 cx.set_state(indoc! {"
2365 // Fooˇ
2366 "});
2367 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2368 cx.assert_editor_state(indoc! {"
2369 // Foo
2370 ˇ
2371 "});
2372}
2373
2374#[gpui::test]
2375fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2376 init_test(cx, |_| {});
2377
2378 let editor = cx.add_window(|cx| {
2379 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2380 let mut editor = build_editor(buffer.clone(), cx);
2381 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2382 editor
2383 });
2384
2385 _ = editor.update(cx, |editor, cx| {
2386 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2387 editor.buffer.update(cx, |buffer, cx| {
2388 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2389 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2390 });
2391 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2392
2393 editor.insert("Z", cx);
2394 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2395
2396 // The selections are moved after the inserted characters
2397 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2398 });
2399}
2400
2401#[gpui::test]
2402async fn test_tab(cx: &mut gpui::TestAppContext) {
2403 init_test(cx, |settings| {
2404 settings.defaults.tab_size = NonZeroU32::new(3)
2405 });
2406
2407 let mut cx = EditorTestContext::new(cx).await;
2408 cx.set_state(indoc! {"
2409 ˇabˇc
2410 ˇ🏀ˇ🏀ˇefg
2411 dˇ
2412 "});
2413 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2414 cx.assert_editor_state(indoc! {"
2415 ˇab ˇc
2416 ˇ🏀 ˇ🏀 ˇefg
2417 d ˇ
2418 "});
2419
2420 cx.set_state(indoc! {"
2421 a
2422 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2423 "});
2424 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2425 cx.assert_editor_state(indoc! {"
2426 a
2427 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2428 "});
2429}
2430
2431#[gpui::test]
2432async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2433 init_test(cx, |_| {});
2434
2435 let mut cx = EditorTestContext::new(cx).await;
2436 let language = Arc::new(
2437 Language::new(
2438 LanguageConfig::default(),
2439 Some(tree_sitter_rust::language()),
2440 )
2441 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2442 .unwrap(),
2443 );
2444 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2445
2446 // cursors that are already at the suggested indent level insert
2447 // a soft tab. cursors that are to the left of the suggested indent
2448 // auto-indent their line.
2449 cx.set_state(indoc! {"
2450 ˇ
2451 const a: B = (
2452 c(
2453 d(
2454 ˇ
2455 )
2456 ˇ
2457 ˇ )
2458 );
2459 "});
2460 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2461 cx.assert_editor_state(indoc! {"
2462 ˇ
2463 const a: B = (
2464 c(
2465 d(
2466 ˇ
2467 )
2468 ˇ
2469 ˇ)
2470 );
2471 "});
2472
2473 // handle auto-indent when there are multiple cursors on the same line
2474 cx.set_state(indoc! {"
2475 const a: B = (
2476 c(
2477 ˇ ˇ
2478 ˇ )
2479 );
2480 "});
2481 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2482 cx.assert_editor_state(indoc! {"
2483 const a: B = (
2484 c(
2485 ˇ
2486 ˇ)
2487 );
2488 "});
2489}
2490
2491#[gpui::test]
2492async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2493 init_test(cx, |settings| {
2494 settings.defaults.tab_size = NonZeroU32::new(4)
2495 });
2496
2497 let language = Arc::new(
2498 Language::new(
2499 LanguageConfig::default(),
2500 Some(tree_sitter_rust::language()),
2501 )
2502 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2503 .unwrap(),
2504 );
2505
2506 let mut cx = EditorTestContext::new(cx).await;
2507 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2508 cx.set_state(indoc! {"
2509 fn a() {
2510 if b {
2511 \t ˇc
2512 }
2513 }
2514 "});
2515
2516 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2517 cx.assert_editor_state(indoc! {"
2518 fn a() {
2519 if b {
2520 ˇc
2521 }
2522 }
2523 "});
2524}
2525
2526#[gpui::test]
2527async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2528 init_test(cx, |settings| {
2529 settings.defaults.tab_size = NonZeroU32::new(4);
2530 });
2531
2532 let mut cx = EditorTestContext::new(cx).await;
2533
2534 cx.set_state(indoc! {"
2535 «oneˇ» «twoˇ»
2536 three
2537 four
2538 "});
2539 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2540 cx.assert_editor_state(indoc! {"
2541 «oneˇ» «twoˇ»
2542 three
2543 four
2544 "});
2545
2546 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2547 cx.assert_editor_state(indoc! {"
2548 «oneˇ» «twoˇ»
2549 three
2550 four
2551 "});
2552
2553 // select across line ending
2554 cx.set_state(indoc! {"
2555 one two
2556 t«hree
2557 ˇ» four
2558 "});
2559 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2560 cx.assert_editor_state(indoc! {"
2561 one two
2562 t«hree
2563 ˇ» four
2564 "});
2565
2566 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2567 cx.assert_editor_state(indoc! {"
2568 one two
2569 t«hree
2570 ˇ» four
2571 "});
2572
2573 // Ensure that indenting/outdenting works when the cursor is at column 0.
2574 cx.set_state(indoc! {"
2575 one two
2576 ˇthree
2577 four
2578 "});
2579 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2580 cx.assert_editor_state(indoc! {"
2581 one two
2582 ˇthree
2583 four
2584 "});
2585
2586 cx.set_state(indoc! {"
2587 one two
2588 ˇ three
2589 four
2590 "});
2591 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2592 cx.assert_editor_state(indoc! {"
2593 one two
2594 ˇthree
2595 four
2596 "});
2597}
2598
2599#[gpui::test]
2600async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2601 init_test(cx, |settings| {
2602 settings.defaults.hard_tabs = Some(true);
2603 });
2604
2605 let mut cx = EditorTestContext::new(cx).await;
2606
2607 // select two ranges on one line
2608 cx.set_state(indoc! {"
2609 «oneˇ» «twoˇ»
2610 three
2611 four
2612 "});
2613 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2614 cx.assert_editor_state(indoc! {"
2615 \t«oneˇ» «twoˇ»
2616 three
2617 four
2618 "});
2619 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2620 cx.assert_editor_state(indoc! {"
2621 \t\t«oneˇ» «twoˇ»
2622 three
2623 four
2624 "});
2625 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2626 cx.assert_editor_state(indoc! {"
2627 \t«oneˇ» «twoˇ»
2628 three
2629 four
2630 "});
2631 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2632 cx.assert_editor_state(indoc! {"
2633 «oneˇ» «twoˇ»
2634 three
2635 four
2636 "});
2637
2638 // select across a line ending
2639 cx.set_state(indoc! {"
2640 one two
2641 t«hree
2642 ˇ»four
2643 "});
2644 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2645 cx.assert_editor_state(indoc! {"
2646 one two
2647 \tt«hree
2648 ˇ»four
2649 "});
2650 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2651 cx.assert_editor_state(indoc! {"
2652 one two
2653 \t\tt«hree
2654 ˇ»four
2655 "});
2656 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2657 cx.assert_editor_state(indoc! {"
2658 one two
2659 \tt«hree
2660 ˇ»four
2661 "});
2662 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2663 cx.assert_editor_state(indoc! {"
2664 one two
2665 t«hree
2666 ˇ»four
2667 "});
2668
2669 // Ensure that indenting/outdenting works when the cursor is at column 0.
2670 cx.set_state(indoc! {"
2671 one two
2672 ˇthree
2673 four
2674 "});
2675 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2676 cx.assert_editor_state(indoc! {"
2677 one two
2678 ˇthree
2679 four
2680 "});
2681 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2682 cx.assert_editor_state(indoc! {"
2683 one two
2684 \tˇthree
2685 four
2686 "});
2687 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2688 cx.assert_editor_state(indoc! {"
2689 one two
2690 ˇthree
2691 four
2692 "});
2693}
2694
2695#[gpui::test]
2696fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2697 init_test(cx, |settings| {
2698 settings.languages.extend([
2699 (
2700 "TOML".into(),
2701 LanguageSettingsContent {
2702 tab_size: NonZeroU32::new(2),
2703 ..Default::default()
2704 },
2705 ),
2706 (
2707 "Rust".into(),
2708 LanguageSettingsContent {
2709 tab_size: NonZeroU32::new(4),
2710 ..Default::default()
2711 },
2712 ),
2713 ]);
2714 });
2715
2716 let toml_language = Arc::new(Language::new(
2717 LanguageConfig {
2718 name: "TOML".into(),
2719 ..Default::default()
2720 },
2721 None,
2722 ));
2723 let rust_language = Arc::new(Language::new(
2724 LanguageConfig {
2725 name: "Rust".into(),
2726 ..Default::default()
2727 },
2728 None,
2729 ));
2730
2731 let toml_buffer =
2732 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2733 let rust_buffer = cx.new_model(|cx| {
2734 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2735 });
2736 let multibuffer = cx.new_model(|cx| {
2737 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
2738 multibuffer.push_excerpts(
2739 toml_buffer.clone(),
2740 [ExcerptRange {
2741 context: Point::new(0, 0)..Point::new(2, 0),
2742 primary: None,
2743 }],
2744 cx,
2745 );
2746 multibuffer.push_excerpts(
2747 rust_buffer.clone(),
2748 [ExcerptRange {
2749 context: Point::new(0, 0)..Point::new(1, 0),
2750 primary: None,
2751 }],
2752 cx,
2753 );
2754 multibuffer
2755 });
2756
2757 cx.add_window(|cx| {
2758 let mut editor = build_editor(multibuffer, cx);
2759
2760 assert_eq!(
2761 editor.text(cx),
2762 indoc! {"
2763 a = 1
2764 b = 2
2765
2766 const c: usize = 3;
2767 "}
2768 );
2769
2770 select_ranges(
2771 &mut editor,
2772 indoc! {"
2773 «aˇ» = 1
2774 b = 2
2775
2776 «const c:ˇ» usize = 3;
2777 "},
2778 cx,
2779 );
2780
2781 editor.tab(&Tab, cx);
2782 assert_text_with_selections(
2783 &mut editor,
2784 indoc! {"
2785 «aˇ» = 1
2786 b = 2
2787
2788 «const c:ˇ» usize = 3;
2789 "},
2790 cx,
2791 );
2792 editor.tab_prev(&TabPrev, cx);
2793 assert_text_with_selections(
2794 &mut editor,
2795 indoc! {"
2796 «aˇ» = 1
2797 b = 2
2798
2799 «const c:ˇ» usize = 3;
2800 "},
2801 cx,
2802 );
2803
2804 editor
2805 });
2806}
2807
2808#[gpui::test]
2809async fn test_backspace(cx: &mut gpui::TestAppContext) {
2810 init_test(cx, |_| {});
2811
2812 let mut cx = EditorTestContext::new(cx).await;
2813
2814 // Basic backspace
2815 cx.set_state(indoc! {"
2816 onˇe two three
2817 fou«rˇ» five six
2818 seven «ˇeight nine
2819 »ten
2820 "});
2821 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2822 cx.assert_editor_state(indoc! {"
2823 oˇe two three
2824 fouˇ five six
2825 seven ˇten
2826 "});
2827
2828 // Test backspace inside and around indents
2829 cx.set_state(indoc! {"
2830 zero
2831 ˇone
2832 ˇtwo
2833 ˇ ˇ ˇ three
2834 ˇ ˇ four
2835 "});
2836 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2837 cx.assert_editor_state(indoc! {"
2838 zero
2839 ˇone
2840 ˇtwo
2841 ˇ threeˇ four
2842 "});
2843
2844 // Test backspace with line_mode set to true
2845 cx.update_editor(|e, _| e.selections.line_mode = true);
2846 cx.set_state(indoc! {"
2847 The ˇquick ˇbrown
2848 fox jumps over
2849 the lazy dog
2850 ˇThe qu«ick bˇ»rown"});
2851 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2852 cx.assert_editor_state(indoc! {"
2853 ˇfox jumps over
2854 the lazy dogˇ"});
2855}
2856
2857#[gpui::test]
2858async fn test_delete(cx: &mut gpui::TestAppContext) {
2859 init_test(cx, |_| {});
2860
2861 let mut cx = EditorTestContext::new(cx).await;
2862 cx.set_state(indoc! {"
2863 onˇe two three
2864 fou«rˇ» five six
2865 seven «ˇeight nine
2866 »ten
2867 "});
2868 cx.update_editor(|e, cx| e.delete(&Delete, cx));
2869 cx.assert_editor_state(indoc! {"
2870 onˇ two three
2871 fouˇ five six
2872 seven ˇten
2873 "});
2874
2875 // Test backspace with line_mode set to true
2876 cx.update_editor(|e, _| e.selections.line_mode = true);
2877 cx.set_state(indoc! {"
2878 The ˇquick ˇbrown
2879 fox «ˇjum»ps over
2880 the lazy dog
2881 ˇThe qu«ick bˇ»rown"});
2882 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2883 cx.assert_editor_state("ˇthe lazy dogˇ");
2884}
2885
2886#[gpui::test]
2887fn test_delete_line(cx: &mut TestAppContext) {
2888 init_test(cx, |_| {});
2889
2890 let view = cx.add_window(|cx| {
2891 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2892 build_editor(buffer, cx)
2893 });
2894 _ = view.update(cx, |view, cx| {
2895 view.change_selections(None, cx, |s| {
2896 s.select_display_ranges([
2897 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
2898 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
2899 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
2900 ])
2901 });
2902 view.delete_line(&DeleteLine, cx);
2903 assert_eq!(view.display_text(cx), "ghi");
2904 assert_eq!(
2905 view.selections.display_ranges(cx),
2906 vec![
2907 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
2908 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
2909 ]
2910 );
2911 });
2912
2913 let view = cx.add_window(|cx| {
2914 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2915 build_editor(buffer, cx)
2916 });
2917 _ = view.update(cx, |view, cx| {
2918 view.change_selections(None, cx, |s| {
2919 s.select_display_ranges([
2920 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
2921 ])
2922 });
2923 view.delete_line(&DeleteLine, cx);
2924 assert_eq!(view.display_text(cx), "ghi\n");
2925 assert_eq!(
2926 view.selections.display_ranges(cx),
2927 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
2928 );
2929 });
2930}
2931
2932#[gpui::test]
2933fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
2934 init_test(cx, |_| {});
2935
2936 cx.add_window(|cx| {
2937 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
2938 let mut editor = build_editor(buffer.clone(), cx);
2939 let buffer = buffer.read(cx).as_singleton().unwrap();
2940
2941 assert_eq!(
2942 editor.selections.ranges::<Point>(cx),
2943 &[Point::new(0, 0)..Point::new(0, 0)]
2944 );
2945
2946 // When on single line, replace newline at end by space
2947 editor.join_lines(&JoinLines, cx);
2948 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
2949 assert_eq!(
2950 editor.selections.ranges::<Point>(cx),
2951 &[Point::new(0, 3)..Point::new(0, 3)]
2952 );
2953
2954 // When multiple lines are selected, remove newlines that are spanned by the selection
2955 editor.change_selections(None, cx, |s| {
2956 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
2957 });
2958 editor.join_lines(&JoinLines, cx);
2959 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
2960 assert_eq!(
2961 editor.selections.ranges::<Point>(cx),
2962 &[Point::new(0, 11)..Point::new(0, 11)]
2963 );
2964
2965 // Undo should be transactional
2966 editor.undo(&Undo, cx);
2967 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
2968 assert_eq!(
2969 editor.selections.ranges::<Point>(cx),
2970 &[Point::new(0, 5)..Point::new(2, 2)]
2971 );
2972
2973 // When joining an empty line don't insert a space
2974 editor.change_selections(None, cx, |s| {
2975 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
2976 });
2977 editor.join_lines(&JoinLines, cx);
2978 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
2979 assert_eq!(
2980 editor.selections.ranges::<Point>(cx),
2981 [Point::new(2, 3)..Point::new(2, 3)]
2982 );
2983
2984 // We can remove trailing newlines
2985 editor.join_lines(&JoinLines, cx);
2986 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
2987 assert_eq!(
2988 editor.selections.ranges::<Point>(cx),
2989 [Point::new(2, 3)..Point::new(2, 3)]
2990 );
2991
2992 // We don't blow up on the last line
2993 editor.join_lines(&JoinLines, cx);
2994 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
2995 assert_eq!(
2996 editor.selections.ranges::<Point>(cx),
2997 [Point::new(2, 3)..Point::new(2, 3)]
2998 );
2999
3000 // reset to test indentation
3001 editor.buffer.update(cx, |buffer, cx| {
3002 buffer.edit(
3003 [
3004 (Point::new(1, 0)..Point::new(1, 2), " "),
3005 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3006 ],
3007 None,
3008 cx,
3009 )
3010 });
3011
3012 // We remove any leading spaces
3013 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3014 editor.change_selections(None, cx, |s| {
3015 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3016 });
3017 editor.join_lines(&JoinLines, cx);
3018 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3019
3020 // We don't insert a space for a line containing only spaces
3021 editor.join_lines(&JoinLines, cx);
3022 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3023
3024 // We ignore any leading tabs
3025 editor.join_lines(&JoinLines, cx);
3026 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3027
3028 editor
3029 });
3030}
3031
3032#[gpui::test]
3033fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3034 init_test(cx, |_| {});
3035
3036 cx.add_window(|cx| {
3037 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3038 let mut editor = build_editor(buffer.clone(), cx);
3039 let buffer = buffer.read(cx).as_singleton().unwrap();
3040
3041 editor.change_selections(None, cx, |s| {
3042 s.select_ranges([
3043 Point::new(0, 2)..Point::new(1, 1),
3044 Point::new(1, 2)..Point::new(1, 2),
3045 Point::new(3, 1)..Point::new(3, 2),
3046 ])
3047 });
3048
3049 editor.join_lines(&JoinLines, cx);
3050 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3051
3052 assert_eq!(
3053 editor.selections.ranges::<Point>(cx),
3054 [
3055 Point::new(0, 7)..Point::new(0, 7),
3056 Point::new(1, 3)..Point::new(1, 3)
3057 ]
3058 );
3059 editor
3060 });
3061}
3062
3063#[gpui::test]
3064async fn test_join_lines_with_git_diff_base(
3065 executor: BackgroundExecutor,
3066 cx: &mut gpui::TestAppContext,
3067) {
3068 init_test(cx, |_| {});
3069
3070 let mut cx = EditorTestContext::new(cx).await;
3071
3072 let diff_base = r#"
3073 Line 0
3074 Line 1
3075 Line 2
3076 Line 3
3077 "#
3078 .unindent();
3079
3080 cx.set_state(
3081 &r#"
3082 ˇLine 0
3083 Line 1
3084 Line 2
3085 Line 3
3086 "#
3087 .unindent(),
3088 );
3089
3090 cx.set_diff_base(Some(&diff_base));
3091 executor.run_until_parked();
3092
3093 // Join lines
3094 cx.update_editor(|editor, cx| {
3095 editor.join_lines(&JoinLines, cx);
3096 });
3097 executor.run_until_parked();
3098
3099 cx.assert_editor_state(
3100 &r#"
3101 Line 0ˇ Line 1
3102 Line 2
3103 Line 3
3104 "#
3105 .unindent(),
3106 );
3107 // Join again
3108 cx.update_editor(|editor, cx| {
3109 editor.join_lines(&JoinLines, cx);
3110 });
3111 executor.run_until_parked();
3112
3113 cx.assert_editor_state(
3114 &r#"
3115 Line 0 Line 1ˇ Line 2
3116 Line 3
3117 "#
3118 .unindent(),
3119 );
3120}
3121
3122#[gpui::test]
3123async fn test_custom_newlines_cause_no_false_positive_diffs(
3124 executor: BackgroundExecutor,
3125 cx: &mut gpui::TestAppContext,
3126) {
3127 init_test(cx, |_| {});
3128 let mut cx = EditorTestContext::new(cx).await;
3129 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3130 cx.set_diff_base(Some("Line 0\r\nLine 1\r\nLine 2\r\nLine 3"));
3131 executor.run_until_parked();
3132
3133 cx.update_editor(|editor, cx| {
3134 assert_eq!(
3135 editor
3136 .buffer()
3137 .read(cx)
3138 .snapshot(cx)
3139 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
3140 .collect::<Vec<_>>(),
3141 Vec::new(),
3142 "Should not have any diffs for files with custom newlines"
3143 );
3144 });
3145}
3146
3147#[gpui::test]
3148async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3149 init_test(cx, |_| {});
3150
3151 let mut cx = EditorTestContext::new(cx).await;
3152
3153 // Test sort_lines_case_insensitive()
3154 cx.set_state(indoc! {"
3155 «z
3156 y
3157 x
3158 Z
3159 Y
3160 Xˇ»
3161 "});
3162 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3163 cx.assert_editor_state(indoc! {"
3164 «x
3165 X
3166 y
3167 Y
3168 z
3169 Zˇ»
3170 "});
3171
3172 // Test reverse_lines()
3173 cx.set_state(indoc! {"
3174 «5
3175 4
3176 3
3177 2
3178 1ˇ»
3179 "});
3180 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3181 cx.assert_editor_state(indoc! {"
3182 «1
3183 2
3184 3
3185 4
3186 5ˇ»
3187 "});
3188
3189 // Skip testing shuffle_line()
3190
3191 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3192 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3193
3194 // Don't manipulate when cursor is on single line, but expand the selection
3195 cx.set_state(indoc! {"
3196 ddˇdd
3197 ccc
3198 bb
3199 a
3200 "});
3201 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3202 cx.assert_editor_state(indoc! {"
3203 «ddddˇ»
3204 ccc
3205 bb
3206 a
3207 "});
3208
3209 // Basic manipulate case
3210 // Start selection moves to column 0
3211 // End of selection shrinks to fit shorter line
3212 cx.set_state(indoc! {"
3213 dd«d
3214 ccc
3215 bb
3216 aaaaaˇ»
3217 "});
3218 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3219 cx.assert_editor_state(indoc! {"
3220 «aaaaa
3221 bb
3222 ccc
3223 dddˇ»
3224 "});
3225
3226 // Manipulate case with newlines
3227 cx.set_state(indoc! {"
3228 dd«d
3229 ccc
3230
3231 bb
3232 aaaaa
3233
3234 ˇ»
3235 "});
3236 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3237 cx.assert_editor_state(indoc! {"
3238 «
3239
3240 aaaaa
3241 bb
3242 ccc
3243 dddˇ»
3244
3245 "});
3246
3247 // Adding new line
3248 cx.set_state(indoc! {"
3249 aa«a
3250 bbˇ»b
3251 "});
3252 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3253 cx.assert_editor_state(indoc! {"
3254 «aaa
3255 bbb
3256 added_lineˇ»
3257 "});
3258
3259 // Removing line
3260 cx.set_state(indoc! {"
3261 aa«a
3262 bbbˇ»
3263 "});
3264 cx.update_editor(|e, cx| {
3265 e.manipulate_lines(cx, |lines| {
3266 lines.pop();
3267 })
3268 });
3269 cx.assert_editor_state(indoc! {"
3270 «aaaˇ»
3271 "});
3272
3273 // Removing all lines
3274 cx.set_state(indoc! {"
3275 aa«a
3276 bbbˇ»
3277 "});
3278 cx.update_editor(|e, cx| {
3279 e.manipulate_lines(cx, |lines| {
3280 lines.drain(..);
3281 })
3282 });
3283 cx.assert_editor_state(indoc! {"
3284 ˇ
3285 "});
3286}
3287
3288#[gpui::test]
3289async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3290 init_test(cx, |_| {});
3291
3292 let mut cx = EditorTestContext::new(cx).await;
3293
3294 // Consider continuous selection as single selection
3295 cx.set_state(indoc! {"
3296 Aaa«aa
3297 cˇ»c«c
3298 bb
3299 aaaˇ»aa
3300 "});
3301 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3302 cx.assert_editor_state(indoc! {"
3303 «Aaaaa
3304 ccc
3305 bb
3306 aaaaaˇ»
3307 "});
3308
3309 cx.set_state(indoc! {"
3310 Aaa«aa
3311 cˇ»c«c
3312 bb
3313 aaaˇ»aa
3314 "});
3315 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3316 cx.assert_editor_state(indoc! {"
3317 «Aaaaa
3318 ccc
3319 bbˇ»
3320 "});
3321
3322 // Consider non continuous selection as distinct dedup operations
3323 cx.set_state(indoc! {"
3324 «aaaaa
3325 bb
3326 aaaaa
3327 aaaaaˇ»
3328
3329 aaa«aaˇ»
3330 "});
3331 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3332 cx.assert_editor_state(indoc! {"
3333 «aaaaa
3334 bbˇ»
3335
3336 «aaaaaˇ»
3337 "});
3338}
3339
3340#[gpui::test]
3341async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3342 init_test(cx, |_| {});
3343
3344 let mut cx = EditorTestContext::new(cx).await;
3345
3346 cx.set_state(indoc! {"
3347 «Aaa
3348 aAa
3349 Aaaˇ»
3350 "});
3351 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3352 cx.assert_editor_state(indoc! {"
3353 «Aaa
3354 aAaˇ»
3355 "});
3356
3357 cx.set_state(indoc! {"
3358 «Aaa
3359 aAa
3360 aaAˇ»
3361 "});
3362 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3363 cx.assert_editor_state(indoc! {"
3364 «Aaaˇ»
3365 "});
3366}
3367
3368#[gpui::test]
3369async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3370 init_test(cx, |_| {});
3371
3372 let mut cx = EditorTestContext::new(cx).await;
3373
3374 // Manipulate with multiple selections on a single line
3375 cx.set_state(indoc! {"
3376 dd«dd
3377 cˇ»c«c
3378 bb
3379 aaaˇ»aa
3380 "});
3381 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3382 cx.assert_editor_state(indoc! {"
3383 «aaaaa
3384 bb
3385 ccc
3386 ddddˇ»
3387 "});
3388
3389 // Manipulate with multiple disjoin selections
3390 cx.set_state(indoc! {"
3391 5«
3392 4
3393 3
3394 2
3395 1ˇ»
3396
3397 dd«dd
3398 ccc
3399 bb
3400 aaaˇ»aa
3401 "});
3402 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3403 cx.assert_editor_state(indoc! {"
3404 «1
3405 2
3406 3
3407 4
3408 5ˇ»
3409
3410 «aaaaa
3411 bb
3412 ccc
3413 ddddˇ»
3414 "});
3415
3416 // Adding lines on each selection
3417 cx.set_state(indoc! {"
3418 2«
3419 1ˇ»
3420
3421 bb«bb
3422 aaaˇ»aa
3423 "});
3424 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3425 cx.assert_editor_state(indoc! {"
3426 «2
3427 1
3428 added lineˇ»
3429
3430 «bbbb
3431 aaaaa
3432 added lineˇ»
3433 "});
3434
3435 // Removing lines on each selection
3436 cx.set_state(indoc! {"
3437 2«
3438 1ˇ»
3439
3440 bb«bb
3441 aaaˇ»aa
3442 "});
3443 cx.update_editor(|e, cx| {
3444 e.manipulate_lines(cx, |lines| {
3445 lines.pop();
3446 })
3447 });
3448 cx.assert_editor_state(indoc! {"
3449 «2ˇ»
3450
3451 «bbbbˇ»
3452 "});
3453}
3454
3455#[gpui::test]
3456async fn test_manipulate_text(cx: &mut TestAppContext) {
3457 init_test(cx, |_| {});
3458
3459 let mut cx = EditorTestContext::new(cx).await;
3460
3461 // Test convert_to_upper_case()
3462 cx.set_state(indoc! {"
3463 «hello worldˇ»
3464 "});
3465 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3466 cx.assert_editor_state(indoc! {"
3467 «HELLO WORLDˇ»
3468 "});
3469
3470 // Test convert_to_lower_case()
3471 cx.set_state(indoc! {"
3472 «HELLO WORLDˇ»
3473 "});
3474 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3475 cx.assert_editor_state(indoc! {"
3476 «hello worldˇ»
3477 "});
3478
3479 // Test multiple line, single selection case
3480 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3481 cx.set_state(indoc! {"
3482 «The quick brown
3483 fox jumps over
3484 the lazy dogˇ»
3485 "});
3486 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3487 cx.assert_editor_state(indoc! {"
3488 «The Quick Brown
3489 Fox Jumps Over
3490 The Lazy Dogˇ»
3491 "});
3492
3493 // Test multiple line, single selection case
3494 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3495 cx.set_state(indoc! {"
3496 «The quick brown
3497 fox jumps over
3498 the lazy dogˇ»
3499 "});
3500 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3501 cx.assert_editor_state(indoc! {"
3502 «TheQuickBrown
3503 FoxJumpsOver
3504 TheLazyDogˇ»
3505 "});
3506
3507 // From here on out, test more complex cases of manipulate_text()
3508
3509 // Test no selection case - should affect words cursors are in
3510 // Cursor at beginning, middle, and end of word
3511 cx.set_state(indoc! {"
3512 ˇhello big beauˇtiful worldˇ
3513 "});
3514 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3515 cx.assert_editor_state(indoc! {"
3516 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3517 "});
3518
3519 // Test multiple selections on a single line and across multiple lines
3520 cx.set_state(indoc! {"
3521 «Theˇ» quick «brown
3522 foxˇ» jumps «overˇ»
3523 the «lazyˇ» dog
3524 "});
3525 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3526 cx.assert_editor_state(indoc! {"
3527 «THEˇ» quick «BROWN
3528 FOXˇ» jumps «OVERˇ»
3529 the «LAZYˇ» dog
3530 "});
3531
3532 // Test case where text length grows
3533 cx.set_state(indoc! {"
3534 «tschüߡ»
3535 "});
3536 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3537 cx.assert_editor_state(indoc! {"
3538 «TSCHÜSSˇ»
3539 "});
3540
3541 // Test to make sure we don't crash when text shrinks
3542 cx.set_state(indoc! {"
3543 aaa_bbbˇ
3544 "});
3545 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3546 cx.assert_editor_state(indoc! {"
3547 «aaaBbbˇ»
3548 "});
3549
3550 // Test to make sure we all aware of the fact that each word can grow and shrink
3551 // Final selections should be aware of this fact
3552 cx.set_state(indoc! {"
3553 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3554 "});
3555 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3556 cx.assert_editor_state(indoc! {"
3557 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3558 "});
3559
3560 cx.set_state(indoc! {"
3561 «hElLo, WoRld!ˇ»
3562 "});
3563 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3564 cx.assert_editor_state(indoc! {"
3565 «HeLlO, wOrLD!ˇ»
3566 "});
3567}
3568
3569#[gpui::test]
3570fn test_duplicate_line(cx: &mut TestAppContext) {
3571 init_test(cx, |_| {});
3572
3573 let view = cx.add_window(|cx| {
3574 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3575 build_editor(buffer, cx)
3576 });
3577 _ = view.update(cx, |view, cx| {
3578 view.change_selections(None, cx, |s| {
3579 s.select_display_ranges([
3580 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3581 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3582 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3583 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3584 ])
3585 });
3586 view.duplicate_line_down(&DuplicateLineDown, cx);
3587 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3588 assert_eq!(
3589 view.selections.display_ranges(cx),
3590 vec![
3591 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3592 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3593 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3594 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3595 ]
3596 );
3597 });
3598
3599 let view = cx.add_window(|cx| {
3600 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3601 build_editor(buffer, cx)
3602 });
3603 _ = view.update(cx, |view, cx| {
3604 view.change_selections(None, cx, |s| {
3605 s.select_display_ranges([
3606 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3607 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3608 ])
3609 });
3610 view.duplicate_line_down(&DuplicateLineDown, cx);
3611 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3612 assert_eq!(
3613 view.selections.display_ranges(cx),
3614 vec![
3615 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3616 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3617 ]
3618 );
3619 });
3620
3621 // With `move_upwards` the selections stay in place, except for
3622 // the lines inserted above them
3623 let view = cx.add_window(|cx| {
3624 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3625 build_editor(buffer, cx)
3626 });
3627 _ = view.update(cx, |view, cx| {
3628 view.change_selections(None, cx, |s| {
3629 s.select_display_ranges([
3630 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3631 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3632 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3633 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3634 ])
3635 });
3636 view.duplicate_line_up(&DuplicateLineUp, cx);
3637 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3638 assert_eq!(
3639 view.selections.display_ranges(cx),
3640 vec![
3641 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3642 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3643 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3644 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3645 ]
3646 );
3647 });
3648
3649 let view = cx.add_window(|cx| {
3650 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3651 build_editor(buffer, cx)
3652 });
3653 _ = view.update(cx, |view, cx| {
3654 view.change_selections(None, cx, |s| {
3655 s.select_display_ranges([
3656 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3657 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3658 ])
3659 });
3660 view.duplicate_line_up(&DuplicateLineUp, cx);
3661 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3662 assert_eq!(
3663 view.selections.display_ranges(cx),
3664 vec![
3665 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3666 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3667 ]
3668 );
3669 });
3670}
3671
3672#[gpui::test]
3673fn test_move_line_up_down(cx: &mut TestAppContext) {
3674 init_test(cx, |_| {});
3675
3676 let view = cx.add_window(|cx| {
3677 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3678 build_editor(buffer, cx)
3679 });
3680 _ = view.update(cx, |view, cx| {
3681 view.fold_ranges(
3682 vec![
3683 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3684 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3685 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3686 ],
3687 true,
3688 cx,
3689 );
3690 view.change_selections(None, cx, |s| {
3691 s.select_display_ranges([
3692 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3693 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3694 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3695 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3696 ])
3697 });
3698 assert_eq!(
3699 view.display_text(cx),
3700 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3701 );
3702
3703 view.move_line_up(&MoveLineUp, cx);
3704 assert_eq!(
3705 view.display_text(cx),
3706 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3707 );
3708 assert_eq!(
3709 view.selections.display_ranges(cx),
3710 vec![
3711 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3712 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3713 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3714 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3715 ]
3716 );
3717 });
3718
3719 _ = view.update(cx, |view, cx| {
3720 view.move_line_down(&MoveLineDown, cx);
3721 assert_eq!(
3722 view.display_text(cx),
3723 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3724 );
3725 assert_eq!(
3726 view.selections.display_ranges(cx),
3727 vec![
3728 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3729 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3730 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3731 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3732 ]
3733 );
3734 });
3735
3736 _ = view.update(cx, |view, cx| {
3737 view.move_line_down(&MoveLineDown, cx);
3738 assert_eq!(
3739 view.display_text(cx),
3740 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3741 );
3742 assert_eq!(
3743 view.selections.display_ranges(cx),
3744 vec![
3745 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3746 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3747 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3748 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3749 ]
3750 );
3751 });
3752
3753 _ = view.update(cx, |view, cx| {
3754 view.move_line_up(&MoveLineUp, cx);
3755 assert_eq!(
3756 view.display_text(cx),
3757 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3758 );
3759 assert_eq!(
3760 view.selections.display_ranges(cx),
3761 vec![
3762 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3763 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3764 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3765 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3766 ]
3767 );
3768 });
3769}
3770
3771#[gpui::test]
3772fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3773 init_test(cx, |_| {});
3774
3775 let editor = cx.add_window(|cx| {
3776 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3777 build_editor(buffer, cx)
3778 });
3779 _ = editor.update(cx, |editor, cx| {
3780 let snapshot = editor.buffer.read(cx).snapshot(cx);
3781 editor.insert_blocks(
3782 [BlockProperties {
3783 style: BlockStyle::Fixed,
3784 position: snapshot.anchor_after(Point::new(2, 0)),
3785 disposition: BlockDisposition::Below,
3786 height: 1,
3787 render: Box::new(|_| div().into_any()),
3788 }],
3789 Some(Autoscroll::fit()),
3790 cx,
3791 );
3792 editor.change_selections(None, cx, |s| {
3793 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
3794 });
3795 editor.move_line_down(&MoveLineDown, cx);
3796 });
3797}
3798
3799#[gpui::test]
3800fn test_transpose(cx: &mut TestAppContext) {
3801 init_test(cx, |_| {});
3802
3803 _ = cx.add_window(|cx| {
3804 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
3805 editor.set_style(EditorStyle::default(), cx);
3806 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
3807 editor.transpose(&Default::default(), cx);
3808 assert_eq!(editor.text(cx), "bac");
3809 assert_eq!(editor.selections.ranges(cx), [2..2]);
3810
3811 editor.transpose(&Default::default(), cx);
3812 assert_eq!(editor.text(cx), "bca");
3813 assert_eq!(editor.selections.ranges(cx), [3..3]);
3814
3815 editor.transpose(&Default::default(), cx);
3816 assert_eq!(editor.text(cx), "bac");
3817 assert_eq!(editor.selections.ranges(cx), [3..3]);
3818
3819 editor
3820 });
3821
3822 _ = cx.add_window(|cx| {
3823 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3824 editor.set_style(EditorStyle::default(), cx);
3825 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
3826 editor.transpose(&Default::default(), cx);
3827 assert_eq!(editor.text(cx), "acb\nde");
3828 assert_eq!(editor.selections.ranges(cx), [3..3]);
3829
3830 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3831 editor.transpose(&Default::default(), cx);
3832 assert_eq!(editor.text(cx), "acbd\ne");
3833 assert_eq!(editor.selections.ranges(cx), [5..5]);
3834
3835 editor.transpose(&Default::default(), cx);
3836 assert_eq!(editor.text(cx), "acbde\n");
3837 assert_eq!(editor.selections.ranges(cx), [6..6]);
3838
3839 editor.transpose(&Default::default(), cx);
3840 assert_eq!(editor.text(cx), "acbd\ne");
3841 assert_eq!(editor.selections.ranges(cx), [6..6]);
3842
3843 editor
3844 });
3845
3846 _ = cx.add_window(|cx| {
3847 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3848 editor.set_style(EditorStyle::default(), cx);
3849 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
3850 editor.transpose(&Default::default(), cx);
3851 assert_eq!(editor.text(cx), "bacd\ne");
3852 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
3853
3854 editor.transpose(&Default::default(), cx);
3855 assert_eq!(editor.text(cx), "bcade\n");
3856 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
3857
3858 editor.transpose(&Default::default(), cx);
3859 assert_eq!(editor.text(cx), "bcda\ne");
3860 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3861
3862 editor.transpose(&Default::default(), cx);
3863 assert_eq!(editor.text(cx), "bcade\n");
3864 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3865
3866 editor.transpose(&Default::default(), cx);
3867 assert_eq!(editor.text(cx), "bcaed\n");
3868 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
3869
3870 editor
3871 });
3872
3873 _ = cx.add_window(|cx| {
3874 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
3875 editor.set_style(EditorStyle::default(), cx);
3876 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3877 editor.transpose(&Default::default(), cx);
3878 assert_eq!(editor.text(cx), "🏀🍐✋");
3879 assert_eq!(editor.selections.ranges(cx), [8..8]);
3880
3881 editor.transpose(&Default::default(), cx);
3882 assert_eq!(editor.text(cx), "🏀✋🍐");
3883 assert_eq!(editor.selections.ranges(cx), [11..11]);
3884
3885 editor.transpose(&Default::default(), cx);
3886 assert_eq!(editor.text(cx), "🏀🍐✋");
3887 assert_eq!(editor.selections.ranges(cx), [11..11]);
3888
3889 editor
3890 });
3891}
3892
3893#[gpui::test]
3894async fn test_clipboard(cx: &mut gpui::TestAppContext) {
3895 init_test(cx, |_| {});
3896
3897 let mut cx = EditorTestContext::new(cx).await;
3898
3899 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
3900 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3901 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
3902
3903 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
3904 cx.set_state("two ˇfour ˇsix ˇ");
3905 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3906 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
3907
3908 // Paste again but with only two cursors. Since the number of cursors doesn't
3909 // match the number of slices in the clipboard, the entire clipboard text
3910 // is pasted at each cursor.
3911 cx.set_state("ˇtwo one✅ four three six five ˇ");
3912 cx.update_editor(|e, cx| {
3913 e.handle_input("( ", cx);
3914 e.paste(&Paste, cx);
3915 e.handle_input(") ", cx);
3916 });
3917 cx.assert_editor_state(
3918 &([
3919 "( one✅ ",
3920 "three ",
3921 "five ) ˇtwo one✅ four three six five ( one✅ ",
3922 "three ",
3923 "five ) ˇ",
3924 ]
3925 .join("\n")),
3926 );
3927
3928 // Cut with three selections, one of which is full-line.
3929 cx.set_state(indoc! {"
3930 1«2ˇ»3
3931 4ˇ567
3932 «8ˇ»9"});
3933 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3934 cx.assert_editor_state(indoc! {"
3935 1ˇ3
3936 ˇ9"});
3937
3938 // Paste with three selections, noticing how the copied selection that was full-line
3939 // gets inserted before the second cursor.
3940 cx.set_state(indoc! {"
3941 1ˇ3
3942 9ˇ
3943 «oˇ»ne"});
3944 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3945 cx.assert_editor_state(indoc! {"
3946 12ˇ3
3947 4567
3948 9ˇ
3949 8ˇne"});
3950
3951 // Copy with a single cursor only, which writes the whole line into the clipboard.
3952 cx.set_state(indoc! {"
3953 The quick brown
3954 fox juˇmps over
3955 the lazy dog"});
3956 cx.update_editor(|e, cx| e.copy(&Copy, cx));
3957 assert_eq!(
3958 cx.read_from_clipboard().map(|item| item.text().to_owned()),
3959 Some("fox jumps over\n".to_owned())
3960 );
3961
3962 // Paste with three selections, noticing how the copied full-line selection is inserted
3963 // before the empty selections but replaces the selection that is non-empty.
3964 cx.set_state(indoc! {"
3965 Tˇhe quick brown
3966 «foˇ»x jumps over
3967 tˇhe lazy dog"});
3968 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3969 cx.assert_editor_state(indoc! {"
3970 fox jumps over
3971 Tˇhe quick brown
3972 fox jumps over
3973 ˇx jumps over
3974 fox jumps over
3975 tˇhe lazy dog"});
3976}
3977
3978#[gpui::test]
3979async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
3980 init_test(cx, |_| {});
3981
3982 let mut cx = EditorTestContext::new(cx).await;
3983 let language = Arc::new(Language::new(
3984 LanguageConfig::default(),
3985 Some(tree_sitter_rust::language()),
3986 ));
3987 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
3988
3989 // Cut an indented block, without the leading whitespace.
3990 cx.set_state(indoc! {"
3991 const a: B = (
3992 c(),
3993 «d(
3994 e,
3995 f
3996 )ˇ»
3997 );
3998 "});
3999 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4000 cx.assert_editor_state(indoc! {"
4001 const a: B = (
4002 c(),
4003 ˇ
4004 );
4005 "});
4006
4007 // Paste it at the same position.
4008 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4009 cx.assert_editor_state(indoc! {"
4010 const a: B = (
4011 c(),
4012 d(
4013 e,
4014 f
4015 )ˇ
4016 );
4017 "});
4018
4019 // Paste it at a line with a lower indent level.
4020 cx.set_state(indoc! {"
4021 ˇ
4022 const a: B = (
4023 c(),
4024 );
4025 "});
4026 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4027 cx.assert_editor_state(indoc! {"
4028 d(
4029 e,
4030 f
4031 )ˇ
4032 const a: B = (
4033 c(),
4034 );
4035 "});
4036
4037 // Cut an indented block, with the leading whitespace.
4038 cx.set_state(indoc! {"
4039 const a: B = (
4040 c(),
4041 « d(
4042 e,
4043 f
4044 )
4045 ˇ»);
4046 "});
4047 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4048 cx.assert_editor_state(indoc! {"
4049 const a: B = (
4050 c(),
4051 ˇ);
4052 "});
4053
4054 // Paste it at the same position.
4055 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4056 cx.assert_editor_state(indoc! {"
4057 const a: B = (
4058 c(),
4059 d(
4060 e,
4061 f
4062 )
4063 ˇ);
4064 "});
4065
4066 // Paste it at a line with a higher indent level.
4067 cx.set_state(indoc! {"
4068 const a: B = (
4069 c(),
4070 d(
4071 e,
4072 fˇ
4073 )
4074 );
4075 "});
4076 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4077 cx.assert_editor_state(indoc! {"
4078 const a: B = (
4079 c(),
4080 d(
4081 e,
4082 f d(
4083 e,
4084 f
4085 )
4086 ˇ
4087 )
4088 );
4089 "});
4090}
4091
4092#[gpui::test]
4093fn test_select_all(cx: &mut TestAppContext) {
4094 init_test(cx, |_| {});
4095
4096 let view = cx.add_window(|cx| {
4097 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4098 build_editor(buffer, cx)
4099 });
4100 _ = view.update(cx, |view, cx| {
4101 view.select_all(&SelectAll, cx);
4102 assert_eq!(
4103 view.selections.display_ranges(cx),
4104 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4105 );
4106 });
4107}
4108
4109#[gpui::test]
4110fn test_select_line(cx: &mut TestAppContext) {
4111 init_test(cx, |_| {});
4112
4113 let view = cx.add_window(|cx| {
4114 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4115 build_editor(buffer, cx)
4116 });
4117 _ = view.update(cx, |view, cx| {
4118 view.change_selections(None, cx, |s| {
4119 s.select_display_ranges([
4120 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4121 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4122 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4123 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4124 ])
4125 });
4126 view.select_line(&SelectLine, cx);
4127 assert_eq!(
4128 view.selections.display_ranges(cx),
4129 vec![
4130 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4131 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4132 ]
4133 );
4134 });
4135
4136 _ = view.update(cx, |view, cx| {
4137 view.select_line(&SelectLine, cx);
4138 assert_eq!(
4139 view.selections.display_ranges(cx),
4140 vec![
4141 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4142 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4143 ]
4144 );
4145 });
4146
4147 _ = view.update(cx, |view, cx| {
4148 view.select_line(&SelectLine, cx);
4149 assert_eq!(
4150 view.selections.display_ranges(cx),
4151 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4152 );
4153 });
4154}
4155
4156#[gpui::test]
4157fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4158 init_test(cx, |_| {});
4159
4160 let view = cx.add_window(|cx| {
4161 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4162 build_editor(buffer, cx)
4163 });
4164 _ = view.update(cx, |view, cx| {
4165 view.fold_ranges(
4166 vec![
4167 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4168 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4169 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4170 ],
4171 true,
4172 cx,
4173 );
4174 view.change_selections(None, cx, |s| {
4175 s.select_display_ranges([
4176 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4177 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4178 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4179 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4180 ])
4181 });
4182 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4183 });
4184
4185 _ = view.update(cx, |view, cx| {
4186 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4187 assert_eq!(
4188 view.display_text(cx),
4189 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4190 );
4191 assert_eq!(
4192 view.selections.display_ranges(cx),
4193 [
4194 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4195 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4196 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4197 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4198 ]
4199 );
4200 });
4201
4202 _ = view.update(cx, |view, cx| {
4203 view.change_selections(None, cx, |s| {
4204 s.select_display_ranges([
4205 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4206 ])
4207 });
4208 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4209 assert_eq!(
4210 view.display_text(cx),
4211 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4212 );
4213 assert_eq!(
4214 view.selections.display_ranges(cx),
4215 [
4216 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4217 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4218 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4219 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4220 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4221 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4222 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4223 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4224 ]
4225 );
4226 });
4227}
4228
4229#[gpui::test]
4230async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4231 init_test(cx, |_| {});
4232
4233 let mut cx = EditorTestContext::new(cx).await;
4234
4235 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4236 cx.set_state(indoc!(
4237 r#"abc
4238 defˇghi
4239
4240 jk
4241 nlmo
4242 "#
4243 ));
4244
4245 cx.update_editor(|editor, cx| {
4246 editor.add_selection_above(&Default::default(), cx);
4247 });
4248
4249 cx.assert_editor_state(indoc!(
4250 r#"abcˇ
4251 defˇghi
4252
4253 jk
4254 nlmo
4255 "#
4256 ));
4257
4258 cx.update_editor(|editor, cx| {
4259 editor.add_selection_above(&Default::default(), cx);
4260 });
4261
4262 cx.assert_editor_state(indoc!(
4263 r#"abcˇ
4264 defˇghi
4265
4266 jk
4267 nlmo
4268 "#
4269 ));
4270
4271 cx.update_editor(|view, cx| {
4272 view.add_selection_below(&Default::default(), cx);
4273 });
4274
4275 cx.assert_editor_state(indoc!(
4276 r#"abc
4277 defˇghi
4278
4279 jk
4280 nlmo
4281 "#
4282 ));
4283
4284 cx.update_editor(|view, cx| {
4285 view.undo_selection(&Default::default(), cx);
4286 });
4287
4288 cx.assert_editor_state(indoc!(
4289 r#"abcˇ
4290 defˇghi
4291
4292 jk
4293 nlmo
4294 "#
4295 ));
4296
4297 cx.update_editor(|view, cx| {
4298 view.redo_selection(&Default::default(), cx);
4299 });
4300
4301 cx.assert_editor_state(indoc!(
4302 r#"abc
4303 defˇghi
4304
4305 jk
4306 nlmo
4307 "#
4308 ));
4309
4310 cx.update_editor(|view, cx| {
4311 view.add_selection_below(&Default::default(), cx);
4312 });
4313
4314 cx.assert_editor_state(indoc!(
4315 r#"abc
4316 defˇghi
4317
4318 jk
4319 nlmˇo
4320 "#
4321 ));
4322
4323 cx.update_editor(|view, cx| {
4324 view.add_selection_below(&Default::default(), cx);
4325 });
4326
4327 cx.assert_editor_state(indoc!(
4328 r#"abc
4329 defˇghi
4330
4331 jk
4332 nlmˇo
4333 "#
4334 ));
4335
4336 // change selections
4337 cx.set_state(indoc!(
4338 r#"abc
4339 def«ˇg»hi
4340
4341 jk
4342 nlmo
4343 "#
4344 ));
4345
4346 cx.update_editor(|view, cx| {
4347 view.add_selection_below(&Default::default(), cx);
4348 });
4349
4350 cx.assert_editor_state(indoc!(
4351 r#"abc
4352 def«ˇg»hi
4353
4354 jk
4355 nlm«ˇo»
4356 "#
4357 ));
4358
4359 cx.update_editor(|view, cx| {
4360 view.add_selection_below(&Default::default(), cx);
4361 });
4362
4363 cx.assert_editor_state(indoc!(
4364 r#"abc
4365 def«ˇg»hi
4366
4367 jk
4368 nlm«ˇo»
4369 "#
4370 ));
4371
4372 cx.update_editor(|view, cx| {
4373 view.add_selection_above(&Default::default(), cx);
4374 });
4375
4376 cx.assert_editor_state(indoc!(
4377 r#"abc
4378 def«ˇg»hi
4379
4380 jk
4381 nlmo
4382 "#
4383 ));
4384
4385 cx.update_editor(|view, cx| {
4386 view.add_selection_above(&Default::default(), cx);
4387 });
4388
4389 cx.assert_editor_state(indoc!(
4390 r#"abc
4391 def«ˇg»hi
4392
4393 jk
4394 nlmo
4395 "#
4396 ));
4397
4398 // Change selections again
4399 cx.set_state(indoc!(
4400 r#"a«bc
4401 defgˇ»hi
4402
4403 jk
4404 nlmo
4405 "#
4406 ));
4407
4408 cx.update_editor(|view, cx| {
4409 view.add_selection_below(&Default::default(), cx);
4410 });
4411
4412 cx.assert_editor_state(indoc!(
4413 r#"a«bcˇ»
4414 d«efgˇ»hi
4415
4416 j«kˇ»
4417 nlmo
4418 "#
4419 ));
4420
4421 cx.update_editor(|view, cx| {
4422 view.add_selection_below(&Default::default(), cx);
4423 });
4424 cx.assert_editor_state(indoc!(
4425 r#"a«bcˇ»
4426 d«efgˇ»hi
4427
4428 j«kˇ»
4429 n«lmoˇ»
4430 "#
4431 ));
4432 cx.update_editor(|view, cx| {
4433 view.add_selection_above(&Default::default(), cx);
4434 });
4435
4436 cx.assert_editor_state(indoc!(
4437 r#"a«bcˇ»
4438 d«efgˇ»hi
4439
4440 j«kˇ»
4441 nlmo
4442 "#
4443 ));
4444
4445 // Change selections again
4446 cx.set_state(indoc!(
4447 r#"abc
4448 d«ˇefghi
4449
4450 jk
4451 nlm»o
4452 "#
4453 ));
4454
4455 cx.update_editor(|view, cx| {
4456 view.add_selection_above(&Default::default(), cx);
4457 });
4458
4459 cx.assert_editor_state(indoc!(
4460 r#"a«ˇbc»
4461 d«ˇef»ghi
4462
4463 j«ˇk»
4464 n«ˇlm»o
4465 "#
4466 ));
4467
4468 cx.update_editor(|view, cx| {
4469 view.add_selection_below(&Default::default(), cx);
4470 });
4471
4472 cx.assert_editor_state(indoc!(
4473 r#"abc
4474 d«ˇef»ghi
4475
4476 j«ˇk»
4477 n«ˇlm»o
4478 "#
4479 ));
4480}
4481
4482#[gpui::test]
4483async fn test_select_next(cx: &mut gpui::TestAppContext) {
4484 init_test(cx, |_| {});
4485
4486 let mut cx = EditorTestContext::new(cx).await;
4487 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4488
4489 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4490 .unwrap();
4491 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4492
4493 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4494 .unwrap();
4495 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4496
4497 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4498 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4499
4500 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4501 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4502
4503 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4504 .unwrap();
4505 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4506
4507 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4508 .unwrap();
4509 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4510}
4511
4512#[gpui::test]
4513async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
4514 init_test(cx, |_| {});
4515
4516 let mut cx = EditorTestContext::new(cx).await;
4517 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4518
4519 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
4520 .unwrap();
4521 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4522}
4523
4524#[gpui::test]
4525async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4526 init_test(cx, |_| {});
4527
4528 let mut cx = EditorTestContext::new(cx).await;
4529 cx.set_state(
4530 r#"let foo = 2;
4531lˇet foo = 2;
4532let fooˇ = 2;
4533let foo = 2;
4534let foo = ˇ2;"#,
4535 );
4536
4537 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4538 .unwrap();
4539 cx.assert_editor_state(
4540 r#"let foo = 2;
4541«letˇ» foo = 2;
4542let «fooˇ» = 2;
4543let foo = 2;
4544let foo = «2ˇ»;"#,
4545 );
4546
4547 // noop for multiple selections with different contents
4548 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4549 .unwrap();
4550 cx.assert_editor_state(
4551 r#"let foo = 2;
4552«letˇ» foo = 2;
4553let «fooˇ» = 2;
4554let foo = 2;
4555let foo = «2ˇ»;"#,
4556 );
4557}
4558
4559#[gpui::test]
4560async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
4561 init_test(cx, |_| {});
4562
4563 let mut cx = EditorTestContext::new_multibuffer(
4564 cx,
4565 [
4566 &indoc! {
4567 "aaa\n«bbb\nccc\n»ddd"
4568 },
4569 &indoc! {
4570 "aaa\n«bbb\nccc\n»ddd"
4571 },
4572 ],
4573 );
4574
4575 cx.assert_editor_state(indoc! {"
4576 ˇbbb
4577 ccc
4578
4579 bbb
4580 ccc
4581 "});
4582 cx.dispatch_action(SelectPrevious::default());
4583 cx.assert_editor_state(indoc! {"
4584 «bbbˇ»
4585 ccc
4586
4587 bbb
4588 ccc
4589 "});
4590 cx.dispatch_action(SelectPrevious::default());
4591 cx.assert_editor_state(indoc! {"
4592 «bbbˇ»
4593 ccc
4594
4595 «bbbˇ»
4596 ccc
4597 "});
4598}
4599
4600#[gpui::test]
4601async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
4602 init_test(cx, |_| {});
4603
4604 let mut cx = EditorTestContext::new(cx).await;
4605 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4606
4607 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4608 .unwrap();
4609 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4610
4611 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4612 .unwrap();
4613 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4614
4615 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4616 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4617
4618 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4619 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4620
4621 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4622 .unwrap();
4623 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
4624
4625 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4626 .unwrap();
4627 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
4628
4629 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4630 .unwrap();
4631 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
4632}
4633
4634#[gpui::test]
4635async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4636 init_test(cx, |_| {});
4637
4638 let mut cx = EditorTestContext::new(cx).await;
4639 cx.set_state(
4640 r#"let foo = 2;
4641lˇet foo = 2;
4642let fooˇ = 2;
4643let foo = 2;
4644let foo = ˇ2;"#,
4645 );
4646
4647 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4648 .unwrap();
4649 cx.assert_editor_state(
4650 r#"let foo = 2;
4651«letˇ» foo = 2;
4652let «fooˇ» = 2;
4653let foo = 2;
4654let foo = «2ˇ»;"#,
4655 );
4656
4657 // noop for multiple selections with different contents
4658 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4659 .unwrap();
4660 cx.assert_editor_state(
4661 r#"let foo = 2;
4662«letˇ» foo = 2;
4663let «fooˇ» = 2;
4664let foo = 2;
4665let foo = «2ˇ»;"#,
4666 );
4667}
4668
4669#[gpui::test]
4670async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
4671 init_test(cx, |_| {});
4672
4673 let mut cx = EditorTestContext::new(cx).await;
4674 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
4675
4676 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4677 .unwrap();
4678 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
4679
4680 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4681 .unwrap();
4682 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
4683
4684 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4685 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
4686
4687 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4688 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
4689
4690 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4691 .unwrap();
4692 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
4693
4694 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4695 .unwrap();
4696 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
4697}
4698
4699#[gpui::test]
4700async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
4701 init_test(cx, |_| {});
4702
4703 let language = Arc::new(Language::new(
4704 LanguageConfig::default(),
4705 Some(tree_sitter_rust::language()),
4706 ));
4707
4708 let text = r#"
4709 use mod1::mod2::{mod3, mod4};
4710
4711 fn fn_1(param1: bool, param2: &str) {
4712 let var1 = "text";
4713 }
4714 "#
4715 .unindent();
4716
4717 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
4718 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4719 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4720
4721 view.condition::<crate::EditorEvent>(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4722 .await;
4723
4724 _ = view.update(cx, |view, cx| {
4725 view.change_selections(None, cx, |s| {
4726 s.select_display_ranges([
4727 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
4728 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
4729 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
4730 ]);
4731 });
4732 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4733 });
4734 assert_eq!(
4735 view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
4736 &[
4737 DisplayPoint::new(DisplayRow(0), 23)..DisplayPoint::new(DisplayRow(0), 27),
4738 DisplayPoint::new(DisplayRow(2), 35)..DisplayPoint::new(DisplayRow(2), 7),
4739 DisplayPoint::new(DisplayRow(3), 15)..DisplayPoint::new(DisplayRow(3), 21),
4740 ]
4741 );
4742
4743 _ = view.update(cx, |view, cx| {
4744 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4745 });
4746 assert_eq!(
4747 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4748 &[
4749 DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 28),
4750 DisplayPoint::new(DisplayRow(4), 1)..DisplayPoint::new(DisplayRow(2), 0),
4751 ]
4752 );
4753
4754 _ = view.update(cx, |view, cx| {
4755 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4756 });
4757 assert_eq!(
4758 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4759 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
4760 );
4761
4762 // Trying to expand the selected syntax node one more time has no effect.
4763 _ = view.update(cx, |view, cx| {
4764 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4765 });
4766 assert_eq!(
4767 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4768 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
4769 );
4770
4771 _ = view.update(cx, |view, cx| {
4772 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4773 });
4774 assert_eq!(
4775 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4776 &[
4777 DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 28),
4778 DisplayPoint::new(DisplayRow(4), 1)..DisplayPoint::new(DisplayRow(2), 0),
4779 ]
4780 );
4781
4782 _ = view.update(cx, |view, cx| {
4783 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4784 });
4785 assert_eq!(
4786 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4787 &[
4788 DisplayPoint::new(DisplayRow(0), 23)..DisplayPoint::new(DisplayRow(0), 27),
4789 DisplayPoint::new(DisplayRow(2), 35)..DisplayPoint::new(DisplayRow(2), 7),
4790 DisplayPoint::new(DisplayRow(3), 15)..DisplayPoint::new(DisplayRow(3), 21),
4791 ]
4792 );
4793
4794 _ = view.update(cx, |view, cx| {
4795 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4796 });
4797 assert_eq!(
4798 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4799 &[
4800 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
4801 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
4802 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
4803 ]
4804 );
4805
4806 // Trying to shrink the selected syntax node one more time has no effect.
4807 _ = view.update(cx, |view, cx| {
4808 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4809 });
4810 assert_eq!(
4811 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4812 &[
4813 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
4814 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
4815 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
4816 ]
4817 );
4818
4819 // Ensure that we keep expanding the selection if the larger selection starts or ends within
4820 // a fold.
4821 _ = view.update(cx, |view, cx| {
4822 view.fold_ranges(
4823 vec![
4824 (
4825 Point::new(0, 21)..Point::new(0, 24),
4826 FoldPlaceholder::test(),
4827 ),
4828 (
4829 Point::new(3, 20)..Point::new(3, 22),
4830 FoldPlaceholder::test(),
4831 ),
4832 ],
4833 true,
4834 cx,
4835 );
4836 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4837 });
4838 assert_eq!(
4839 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4840 &[
4841 DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 28),
4842 DisplayPoint::new(DisplayRow(2), 35)..DisplayPoint::new(DisplayRow(2), 7),
4843 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(3), 23),
4844 ]
4845 );
4846}
4847
4848#[gpui::test]
4849async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
4850 init_test(cx, |_| {});
4851
4852 let language = Arc::new(
4853 Language::new(
4854 LanguageConfig {
4855 brackets: BracketPairConfig {
4856 pairs: vec![
4857 BracketPair {
4858 start: "{".to_string(),
4859 end: "}".to_string(),
4860 close: false,
4861 surround: false,
4862 newline: true,
4863 },
4864 BracketPair {
4865 start: "(".to_string(),
4866 end: ")".to_string(),
4867 close: false,
4868 surround: false,
4869 newline: true,
4870 },
4871 ],
4872 ..Default::default()
4873 },
4874 ..Default::default()
4875 },
4876 Some(tree_sitter_rust::language()),
4877 )
4878 .with_indents_query(
4879 r#"
4880 (_ "(" ")" @end) @indent
4881 (_ "{" "}" @end) @indent
4882 "#,
4883 )
4884 .unwrap(),
4885 );
4886
4887 let text = "fn a() {}";
4888
4889 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
4890 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4891 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4892 editor
4893 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
4894 .await;
4895
4896 _ = editor.update(cx, |editor, cx| {
4897 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
4898 editor.newline(&Newline, cx);
4899 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
4900 assert_eq!(
4901 editor.selections.ranges(cx),
4902 &[
4903 Point::new(1, 4)..Point::new(1, 4),
4904 Point::new(3, 4)..Point::new(3, 4),
4905 Point::new(5, 0)..Point::new(5, 0)
4906 ]
4907 );
4908 });
4909}
4910
4911#[gpui::test]
4912async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
4913 init_test(cx, |_| {});
4914
4915 let mut cx = EditorTestContext::new(cx).await;
4916
4917 let language = Arc::new(Language::new(
4918 LanguageConfig {
4919 brackets: BracketPairConfig {
4920 pairs: vec![
4921 BracketPair {
4922 start: "{".to_string(),
4923 end: "}".to_string(),
4924 close: true,
4925 surround: true,
4926 newline: true,
4927 },
4928 BracketPair {
4929 start: "(".to_string(),
4930 end: ")".to_string(),
4931 close: true,
4932 surround: true,
4933 newline: true,
4934 },
4935 BracketPair {
4936 start: "/*".to_string(),
4937 end: " */".to_string(),
4938 close: true,
4939 surround: true,
4940 newline: true,
4941 },
4942 BracketPair {
4943 start: "[".to_string(),
4944 end: "]".to_string(),
4945 close: false,
4946 surround: false,
4947 newline: true,
4948 },
4949 BracketPair {
4950 start: "\"".to_string(),
4951 end: "\"".to_string(),
4952 close: true,
4953 surround: true,
4954 newline: false,
4955 },
4956 BracketPair {
4957 start: "<".to_string(),
4958 end: ">".to_string(),
4959 close: false,
4960 surround: true,
4961 newline: true,
4962 },
4963 ],
4964 ..Default::default()
4965 },
4966 autoclose_before: "})]".to_string(),
4967 ..Default::default()
4968 },
4969 Some(tree_sitter_rust::language()),
4970 ));
4971
4972 cx.language_registry().add(language.clone());
4973 cx.update_buffer(|buffer, cx| {
4974 buffer.set_language(Some(language), cx);
4975 });
4976
4977 cx.set_state(
4978 &r#"
4979 🏀ˇ
4980 εˇ
4981 ❤️ˇ
4982 "#
4983 .unindent(),
4984 );
4985
4986 // autoclose multiple nested brackets at multiple cursors
4987 cx.update_editor(|view, cx| {
4988 view.handle_input("{", cx);
4989 view.handle_input("{", cx);
4990 view.handle_input("{", cx);
4991 });
4992 cx.assert_editor_state(
4993 &"
4994 🏀{{{ˇ}}}
4995 ε{{{ˇ}}}
4996 ❤️{{{ˇ}}}
4997 "
4998 .unindent(),
4999 );
5000
5001 // insert a different closing bracket
5002 cx.update_editor(|view, cx| {
5003 view.handle_input(")", cx);
5004 });
5005 cx.assert_editor_state(
5006 &"
5007 🏀{{{)ˇ}}}
5008 ε{{{)ˇ}}}
5009 ❤️{{{)ˇ}}}
5010 "
5011 .unindent(),
5012 );
5013
5014 // skip over the auto-closed brackets when typing a closing bracket
5015 cx.update_editor(|view, cx| {
5016 view.move_right(&MoveRight, cx);
5017 view.handle_input("}", cx);
5018 view.handle_input("}", cx);
5019 view.handle_input("}", cx);
5020 });
5021 cx.assert_editor_state(
5022 &"
5023 🏀{{{)}}}}ˇ
5024 ε{{{)}}}}ˇ
5025 ❤️{{{)}}}}ˇ
5026 "
5027 .unindent(),
5028 );
5029
5030 // autoclose multi-character pairs
5031 cx.set_state(
5032 &"
5033 ˇ
5034 ˇ
5035 "
5036 .unindent(),
5037 );
5038 cx.update_editor(|view, cx| {
5039 view.handle_input("/", cx);
5040 view.handle_input("*", cx);
5041 });
5042 cx.assert_editor_state(
5043 &"
5044 /*ˇ */
5045 /*ˇ */
5046 "
5047 .unindent(),
5048 );
5049
5050 // one cursor autocloses a multi-character pair, one cursor
5051 // does not autoclose.
5052 cx.set_state(
5053 &"
5054 /ˇ
5055 ˇ
5056 "
5057 .unindent(),
5058 );
5059 cx.update_editor(|view, cx| view.handle_input("*", cx));
5060 cx.assert_editor_state(
5061 &"
5062 /*ˇ */
5063 *ˇ
5064 "
5065 .unindent(),
5066 );
5067
5068 // Don't autoclose if the next character isn't whitespace and isn't
5069 // listed in the language's "autoclose_before" section.
5070 cx.set_state("ˇa b");
5071 cx.update_editor(|view, cx| view.handle_input("{", cx));
5072 cx.assert_editor_state("{ˇa b");
5073
5074 // Don't autoclose if `close` is false for the bracket pair
5075 cx.set_state("ˇ");
5076 cx.update_editor(|view, cx| view.handle_input("[", cx));
5077 cx.assert_editor_state("[ˇ");
5078
5079 // Surround with brackets if text is selected
5080 cx.set_state("«aˇ» b");
5081 cx.update_editor(|view, cx| view.handle_input("{", cx));
5082 cx.assert_editor_state("{«aˇ»} b");
5083
5084 // Autclose pair where the start and end characters are the same
5085 cx.set_state("aˇ");
5086 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5087 cx.assert_editor_state("a\"ˇ\"");
5088 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5089 cx.assert_editor_state("a\"\"ˇ");
5090
5091 // Don't autoclose pair if autoclose is disabled
5092 cx.set_state("ˇ");
5093 cx.update_editor(|view, cx| view.handle_input("<", cx));
5094 cx.assert_editor_state("<ˇ");
5095
5096 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5097 cx.set_state("«aˇ» b");
5098 cx.update_editor(|view, cx| view.handle_input("<", cx));
5099 cx.assert_editor_state("<«aˇ»> b");
5100}
5101
5102#[gpui::test]
5103async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5104 init_test(cx, |settings| {
5105 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5106 });
5107
5108 let mut cx = EditorTestContext::new(cx).await;
5109
5110 let language = Arc::new(Language::new(
5111 LanguageConfig {
5112 brackets: BracketPairConfig {
5113 pairs: vec![
5114 BracketPair {
5115 start: "{".to_string(),
5116 end: "}".to_string(),
5117 close: true,
5118 surround: true,
5119 newline: true,
5120 },
5121 BracketPair {
5122 start: "(".to_string(),
5123 end: ")".to_string(),
5124 close: true,
5125 surround: true,
5126 newline: true,
5127 },
5128 BracketPair {
5129 start: "[".to_string(),
5130 end: "]".to_string(),
5131 close: false,
5132 surround: false,
5133 newline: true,
5134 },
5135 ],
5136 ..Default::default()
5137 },
5138 autoclose_before: "})]".to_string(),
5139 ..Default::default()
5140 },
5141 Some(tree_sitter_rust::language()),
5142 ));
5143
5144 cx.language_registry().add(language.clone());
5145 cx.update_buffer(|buffer, cx| {
5146 buffer.set_language(Some(language), cx);
5147 });
5148
5149 cx.set_state(
5150 &"
5151 ˇ
5152 ˇ
5153 ˇ
5154 "
5155 .unindent(),
5156 );
5157
5158 // ensure only matching closing brackets are skipped over
5159 cx.update_editor(|view, cx| {
5160 view.handle_input("}", cx);
5161 view.move_left(&MoveLeft, cx);
5162 view.handle_input(")", cx);
5163 view.move_left(&MoveLeft, cx);
5164 });
5165 cx.assert_editor_state(
5166 &"
5167 ˇ)}
5168 ˇ)}
5169 ˇ)}
5170 "
5171 .unindent(),
5172 );
5173
5174 // skip-over closing brackets at multiple cursors
5175 cx.update_editor(|view, cx| {
5176 view.handle_input(")", cx);
5177 view.handle_input("}", cx);
5178 });
5179 cx.assert_editor_state(
5180 &"
5181 )}ˇ
5182 )}ˇ
5183 )}ˇ
5184 "
5185 .unindent(),
5186 );
5187
5188 // ignore non-close brackets
5189 cx.update_editor(|view, cx| {
5190 view.handle_input("]", cx);
5191 view.move_left(&MoveLeft, cx);
5192 view.handle_input("]", cx);
5193 });
5194 cx.assert_editor_state(
5195 &"
5196 )}]ˇ]
5197 )}]ˇ]
5198 )}]ˇ]
5199 "
5200 .unindent(),
5201 );
5202}
5203
5204#[gpui::test]
5205async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5206 init_test(cx, |_| {});
5207
5208 let mut cx = EditorTestContext::new(cx).await;
5209
5210 let html_language = Arc::new(
5211 Language::new(
5212 LanguageConfig {
5213 name: "HTML".into(),
5214 brackets: BracketPairConfig {
5215 pairs: vec![
5216 BracketPair {
5217 start: "<".into(),
5218 end: ">".into(),
5219 close: true,
5220 ..Default::default()
5221 },
5222 BracketPair {
5223 start: "{".into(),
5224 end: "}".into(),
5225 close: true,
5226 ..Default::default()
5227 },
5228 BracketPair {
5229 start: "(".into(),
5230 end: ")".into(),
5231 close: true,
5232 ..Default::default()
5233 },
5234 ],
5235 ..Default::default()
5236 },
5237 autoclose_before: "})]>".into(),
5238 ..Default::default()
5239 },
5240 Some(tree_sitter_html::language()),
5241 )
5242 .with_injection_query(
5243 r#"
5244 (script_element
5245 (raw_text) @content
5246 (#set! "language" "javascript"))
5247 "#,
5248 )
5249 .unwrap(),
5250 );
5251
5252 let javascript_language = Arc::new(Language::new(
5253 LanguageConfig {
5254 name: "JavaScript".into(),
5255 brackets: BracketPairConfig {
5256 pairs: vec![
5257 BracketPair {
5258 start: "/*".into(),
5259 end: " */".into(),
5260 close: true,
5261 ..Default::default()
5262 },
5263 BracketPair {
5264 start: "{".into(),
5265 end: "}".into(),
5266 close: true,
5267 ..Default::default()
5268 },
5269 BracketPair {
5270 start: "(".into(),
5271 end: ")".into(),
5272 close: true,
5273 ..Default::default()
5274 },
5275 ],
5276 ..Default::default()
5277 },
5278 autoclose_before: "})]>".into(),
5279 ..Default::default()
5280 },
5281 Some(tree_sitter_typescript::language_tsx()),
5282 ));
5283
5284 cx.language_registry().add(html_language.clone());
5285 cx.language_registry().add(javascript_language.clone());
5286
5287 cx.update_buffer(|buffer, cx| {
5288 buffer.set_language(Some(html_language), cx);
5289 });
5290
5291 cx.set_state(
5292 &r#"
5293 <body>ˇ
5294 <script>
5295 var x = 1;ˇ
5296 </script>
5297 </body>ˇ
5298 "#
5299 .unindent(),
5300 );
5301
5302 // Precondition: different languages are active at different locations.
5303 cx.update_editor(|editor, cx| {
5304 let snapshot = editor.snapshot(cx);
5305 let cursors = editor.selections.ranges::<usize>(cx);
5306 let languages = cursors
5307 .iter()
5308 .map(|c| snapshot.language_at(c.start).unwrap().name())
5309 .collect::<Vec<_>>();
5310 assert_eq!(
5311 languages,
5312 &["HTML".into(), "JavaScript".into(), "HTML".into()]
5313 );
5314 });
5315
5316 // Angle brackets autoclose in HTML, but not JavaScript.
5317 cx.update_editor(|editor, cx| {
5318 editor.handle_input("<", cx);
5319 editor.handle_input("a", cx);
5320 });
5321 cx.assert_editor_state(
5322 &r#"
5323 <body><aˇ>
5324 <script>
5325 var x = 1;<aˇ
5326 </script>
5327 </body><aˇ>
5328 "#
5329 .unindent(),
5330 );
5331
5332 // Curly braces and parens autoclose in both HTML and JavaScript.
5333 cx.update_editor(|editor, cx| {
5334 editor.handle_input(" b=", cx);
5335 editor.handle_input("{", cx);
5336 editor.handle_input("c", cx);
5337 editor.handle_input("(", cx);
5338 });
5339 cx.assert_editor_state(
5340 &r#"
5341 <body><a b={c(ˇ)}>
5342 <script>
5343 var x = 1;<a b={c(ˇ)}
5344 </script>
5345 </body><a b={c(ˇ)}>
5346 "#
5347 .unindent(),
5348 );
5349
5350 // Brackets that were already autoclosed are skipped.
5351 cx.update_editor(|editor, cx| {
5352 editor.handle_input(")", cx);
5353 editor.handle_input("d", cx);
5354 editor.handle_input("}", cx);
5355 });
5356 cx.assert_editor_state(
5357 &r#"
5358 <body><a b={c()d}ˇ>
5359 <script>
5360 var x = 1;<a b={c()d}ˇ
5361 </script>
5362 </body><a b={c()d}ˇ>
5363 "#
5364 .unindent(),
5365 );
5366 cx.update_editor(|editor, cx| {
5367 editor.handle_input(">", cx);
5368 });
5369 cx.assert_editor_state(
5370 &r#"
5371 <body><a b={c()d}>ˇ
5372 <script>
5373 var x = 1;<a b={c()d}>ˇ
5374 </script>
5375 </body><a b={c()d}>ˇ
5376 "#
5377 .unindent(),
5378 );
5379
5380 // Reset
5381 cx.set_state(
5382 &r#"
5383 <body>ˇ
5384 <script>
5385 var x = 1;ˇ
5386 </script>
5387 </body>ˇ
5388 "#
5389 .unindent(),
5390 );
5391
5392 cx.update_editor(|editor, cx| {
5393 editor.handle_input("<", cx);
5394 });
5395 cx.assert_editor_state(
5396 &r#"
5397 <body><ˇ>
5398 <script>
5399 var x = 1;<ˇ
5400 </script>
5401 </body><ˇ>
5402 "#
5403 .unindent(),
5404 );
5405
5406 // When backspacing, the closing angle brackets are removed.
5407 cx.update_editor(|editor, cx| {
5408 editor.backspace(&Backspace, cx);
5409 });
5410 cx.assert_editor_state(
5411 &r#"
5412 <body>ˇ
5413 <script>
5414 var x = 1;ˇ
5415 </script>
5416 </body>ˇ
5417 "#
5418 .unindent(),
5419 );
5420
5421 // Block comments autoclose in JavaScript, but not HTML.
5422 cx.update_editor(|editor, cx| {
5423 editor.handle_input("/", cx);
5424 editor.handle_input("*", cx);
5425 });
5426 cx.assert_editor_state(
5427 &r#"
5428 <body>/*ˇ
5429 <script>
5430 var x = 1;/*ˇ */
5431 </script>
5432 </body>/*ˇ
5433 "#
5434 .unindent(),
5435 );
5436}
5437
5438#[gpui::test]
5439async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
5440 init_test(cx, |_| {});
5441
5442 let mut cx = EditorTestContext::new(cx).await;
5443
5444 let rust_language = Arc::new(
5445 Language::new(
5446 LanguageConfig {
5447 name: "Rust".into(),
5448 brackets: serde_json::from_value(json!([
5449 { "start": "{", "end": "}", "close": true, "newline": true },
5450 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
5451 ]))
5452 .unwrap(),
5453 autoclose_before: "})]>".into(),
5454 ..Default::default()
5455 },
5456 Some(tree_sitter_rust::language()),
5457 )
5458 .with_override_query("(string_literal) @string")
5459 .unwrap(),
5460 );
5461
5462 cx.language_registry().add(rust_language.clone());
5463 cx.update_buffer(|buffer, cx| {
5464 buffer.set_language(Some(rust_language), cx);
5465 });
5466
5467 cx.set_state(
5468 &r#"
5469 let x = ˇ
5470 "#
5471 .unindent(),
5472 );
5473
5474 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
5475 cx.update_editor(|editor, cx| {
5476 editor.handle_input("\"", cx);
5477 });
5478 cx.assert_editor_state(
5479 &r#"
5480 let x = "ˇ"
5481 "#
5482 .unindent(),
5483 );
5484
5485 // Inserting another quotation mark. The cursor moves across the existing
5486 // automatically-inserted quotation mark.
5487 cx.update_editor(|editor, cx| {
5488 editor.handle_input("\"", cx);
5489 });
5490 cx.assert_editor_state(
5491 &r#"
5492 let x = ""ˇ
5493 "#
5494 .unindent(),
5495 );
5496
5497 // Reset
5498 cx.set_state(
5499 &r#"
5500 let x = ˇ
5501 "#
5502 .unindent(),
5503 );
5504
5505 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
5506 cx.update_editor(|editor, cx| {
5507 editor.handle_input("\"", cx);
5508 editor.handle_input(" ", cx);
5509 editor.move_left(&Default::default(), cx);
5510 editor.handle_input("\\", cx);
5511 editor.handle_input("\"", cx);
5512 });
5513 cx.assert_editor_state(
5514 &r#"
5515 let x = "\"ˇ "
5516 "#
5517 .unindent(),
5518 );
5519
5520 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
5521 // mark. Nothing is inserted.
5522 cx.update_editor(|editor, cx| {
5523 editor.move_right(&Default::default(), cx);
5524 editor.handle_input("\"", cx);
5525 });
5526 cx.assert_editor_state(
5527 &r#"
5528 let x = "\" "ˇ
5529 "#
5530 .unindent(),
5531 );
5532}
5533
5534#[gpui::test]
5535async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
5536 init_test(cx, |_| {});
5537
5538 let language = Arc::new(Language::new(
5539 LanguageConfig {
5540 brackets: BracketPairConfig {
5541 pairs: vec![
5542 BracketPair {
5543 start: "{".to_string(),
5544 end: "}".to_string(),
5545 close: true,
5546 surround: true,
5547 newline: true,
5548 },
5549 BracketPair {
5550 start: "/* ".to_string(),
5551 end: "*/".to_string(),
5552 close: true,
5553 surround: true,
5554 ..Default::default()
5555 },
5556 ],
5557 ..Default::default()
5558 },
5559 ..Default::default()
5560 },
5561 Some(tree_sitter_rust::language()),
5562 ));
5563
5564 let text = r#"
5565 a
5566 b
5567 c
5568 "#
5569 .unindent();
5570
5571 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5572 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5573 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5574 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5575 .await;
5576
5577 _ = view.update(cx, |view, cx| {
5578 view.change_selections(None, cx, |s| {
5579 s.select_display_ranges([
5580 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5581 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5582 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
5583 ])
5584 });
5585
5586 view.handle_input("{", cx);
5587 view.handle_input("{", cx);
5588 view.handle_input("{", cx);
5589 assert_eq!(
5590 view.text(cx),
5591 "
5592 {{{a}}}
5593 {{{b}}}
5594 {{{c}}}
5595 "
5596 .unindent()
5597 );
5598 assert_eq!(
5599 view.selections.display_ranges(cx),
5600 [
5601 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
5602 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
5603 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
5604 ]
5605 );
5606
5607 view.undo(&Undo, cx);
5608 view.undo(&Undo, cx);
5609 view.undo(&Undo, cx);
5610 assert_eq!(
5611 view.text(cx),
5612 "
5613 a
5614 b
5615 c
5616 "
5617 .unindent()
5618 );
5619 assert_eq!(
5620 view.selections.display_ranges(cx),
5621 [
5622 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5623 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5624 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
5625 ]
5626 );
5627
5628 // Ensure inserting the first character of a multi-byte bracket pair
5629 // doesn't surround the selections with the bracket.
5630 view.handle_input("/", cx);
5631 assert_eq!(
5632 view.text(cx),
5633 "
5634 /
5635 /
5636 /
5637 "
5638 .unindent()
5639 );
5640 assert_eq!(
5641 view.selections.display_ranges(cx),
5642 [
5643 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
5644 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
5645 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
5646 ]
5647 );
5648
5649 view.undo(&Undo, cx);
5650 assert_eq!(
5651 view.text(cx),
5652 "
5653 a
5654 b
5655 c
5656 "
5657 .unindent()
5658 );
5659 assert_eq!(
5660 view.selections.display_ranges(cx),
5661 [
5662 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5663 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5664 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
5665 ]
5666 );
5667
5668 // Ensure inserting the last character of a multi-byte bracket pair
5669 // doesn't surround the selections with the bracket.
5670 view.handle_input("*", cx);
5671 assert_eq!(
5672 view.text(cx),
5673 "
5674 *
5675 *
5676 *
5677 "
5678 .unindent()
5679 );
5680 assert_eq!(
5681 view.selections.display_ranges(cx),
5682 [
5683 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
5684 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
5685 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
5686 ]
5687 );
5688 });
5689}
5690
5691#[gpui::test]
5692async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
5693 init_test(cx, |_| {});
5694
5695 let language = Arc::new(Language::new(
5696 LanguageConfig {
5697 brackets: BracketPairConfig {
5698 pairs: vec![BracketPair {
5699 start: "{".to_string(),
5700 end: "}".to_string(),
5701 close: true,
5702 surround: true,
5703 newline: true,
5704 }],
5705 ..Default::default()
5706 },
5707 autoclose_before: "}".to_string(),
5708 ..Default::default()
5709 },
5710 Some(tree_sitter_rust::language()),
5711 ));
5712
5713 let text = r#"
5714 a
5715 b
5716 c
5717 "#
5718 .unindent();
5719
5720 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5721 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5722 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5723 editor
5724 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5725 .await;
5726
5727 _ = editor.update(cx, |editor, cx| {
5728 editor.change_selections(None, cx, |s| {
5729 s.select_ranges([
5730 Point::new(0, 1)..Point::new(0, 1),
5731 Point::new(1, 1)..Point::new(1, 1),
5732 Point::new(2, 1)..Point::new(2, 1),
5733 ])
5734 });
5735
5736 editor.handle_input("{", cx);
5737 editor.handle_input("{", cx);
5738 editor.handle_input("_", cx);
5739 assert_eq!(
5740 editor.text(cx),
5741 "
5742 a{{_}}
5743 b{{_}}
5744 c{{_}}
5745 "
5746 .unindent()
5747 );
5748 assert_eq!(
5749 editor.selections.ranges::<Point>(cx),
5750 [
5751 Point::new(0, 4)..Point::new(0, 4),
5752 Point::new(1, 4)..Point::new(1, 4),
5753 Point::new(2, 4)..Point::new(2, 4)
5754 ]
5755 );
5756
5757 editor.backspace(&Default::default(), cx);
5758 editor.backspace(&Default::default(), cx);
5759 assert_eq!(
5760 editor.text(cx),
5761 "
5762 a{}
5763 b{}
5764 c{}
5765 "
5766 .unindent()
5767 );
5768 assert_eq!(
5769 editor.selections.ranges::<Point>(cx),
5770 [
5771 Point::new(0, 2)..Point::new(0, 2),
5772 Point::new(1, 2)..Point::new(1, 2),
5773 Point::new(2, 2)..Point::new(2, 2)
5774 ]
5775 );
5776
5777 editor.delete_to_previous_word_start(&Default::default(), cx);
5778 assert_eq!(
5779 editor.text(cx),
5780 "
5781 a
5782 b
5783 c
5784 "
5785 .unindent()
5786 );
5787 assert_eq!(
5788 editor.selections.ranges::<Point>(cx),
5789 [
5790 Point::new(0, 1)..Point::new(0, 1),
5791 Point::new(1, 1)..Point::new(1, 1),
5792 Point::new(2, 1)..Point::new(2, 1)
5793 ]
5794 );
5795 });
5796}
5797
5798#[gpui::test]
5799async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
5800 init_test(cx, |settings| {
5801 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5802 });
5803
5804 let mut cx = EditorTestContext::new(cx).await;
5805
5806 let language = Arc::new(Language::new(
5807 LanguageConfig {
5808 brackets: BracketPairConfig {
5809 pairs: vec![
5810 BracketPair {
5811 start: "{".to_string(),
5812 end: "}".to_string(),
5813 close: true,
5814 surround: true,
5815 newline: true,
5816 },
5817 BracketPair {
5818 start: "(".to_string(),
5819 end: ")".to_string(),
5820 close: true,
5821 surround: true,
5822 newline: true,
5823 },
5824 BracketPair {
5825 start: "[".to_string(),
5826 end: "]".to_string(),
5827 close: false,
5828 surround: true,
5829 newline: true,
5830 },
5831 ],
5832 ..Default::default()
5833 },
5834 autoclose_before: "})]".to_string(),
5835 ..Default::default()
5836 },
5837 Some(tree_sitter_rust::language()),
5838 ));
5839
5840 cx.language_registry().add(language.clone());
5841 cx.update_buffer(|buffer, cx| {
5842 buffer.set_language(Some(language), cx);
5843 });
5844
5845 cx.set_state(
5846 &"
5847 {(ˇ)}
5848 [[ˇ]]
5849 {(ˇ)}
5850 "
5851 .unindent(),
5852 );
5853
5854 cx.update_editor(|view, cx| {
5855 view.backspace(&Default::default(), cx);
5856 view.backspace(&Default::default(), cx);
5857 });
5858
5859 cx.assert_editor_state(
5860 &"
5861 ˇ
5862 ˇ]]
5863 ˇ
5864 "
5865 .unindent(),
5866 );
5867
5868 cx.update_editor(|view, cx| {
5869 view.handle_input("{", cx);
5870 view.handle_input("{", cx);
5871 view.move_right(&MoveRight, cx);
5872 view.move_right(&MoveRight, cx);
5873 view.move_left(&MoveLeft, cx);
5874 view.move_left(&MoveLeft, cx);
5875 view.backspace(&Default::default(), cx);
5876 });
5877
5878 cx.assert_editor_state(
5879 &"
5880 {ˇ}
5881 {ˇ}]]
5882 {ˇ}
5883 "
5884 .unindent(),
5885 );
5886
5887 cx.update_editor(|view, cx| {
5888 view.backspace(&Default::default(), cx);
5889 });
5890
5891 cx.assert_editor_state(
5892 &"
5893 ˇ
5894 ˇ]]
5895 ˇ
5896 "
5897 .unindent(),
5898 );
5899}
5900
5901#[gpui::test]
5902async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
5903 init_test(cx, |_| {});
5904
5905 let language = Arc::new(Language::new(
5906 LanguageConfig::default(),
5907 Some(tree_sitter_rust::language()),
5908 ));
5909
5910 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
5911 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5912 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5913 editor
5914 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5915 .await;
5916
5917 _ = editor.update(cx, |editor, cx| {
5918 editor.set_auto_replace_emoji_shortcode(true);
5919
5920 editor.handle_input("Hello ", cx);
5921 editor.handle_input(":wave", cx);
5922 assert_eq!(editor.text(cx), "Hello :wave".unindent());
5923
5924 editor.handle_input(":", cx);
5925 assert_eq!(editor.text(cx), "Hello 👋".unindent());
5926
5927 editor.handle_input(" :smile", cx);
5928 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
5929
5930 editor.handle_input(":", cx);
5931 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
5932
5933 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
5934 editor.handle_input(":wave", cx);
5935 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
5936
5937 editor.handle_input(":", cx);
5938 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
5939
5940 editor.handle_input(":1", cx);
5941 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
5942
5943 editor.handle_input(":", cx);
5944 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
5945
5946 // Ensure shortcode does not get replaced when it is part of a word
5947 editor.handle_input(" Test:wave", cx);
5948 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
5949
5950 editor.handle_input(":", cx);
5951 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
5952
5953 editor.set_auto_replace_emoji_shortcode(false);
5954
5955 // Ensure shortcode does not get replaced when auto replace is off
5956 editor.handle_input(" :wave", cx);
5957 assert_eq!(
5958 editor.text(cx),
5959 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
5960 );
5961
5962 editor.handle_input(":", cx);
5963 assert_eq!(
5964 editor.text(cx),
5965 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
5966 );
5967 });
5968}
5969
5970#[gpui::test]
5971async fn test_snippets(cx: &mut gpui::TestAppContext) {
5972 init_test(cx, |_| {});
5973
5974 let (text, insertion_ranges) = marked_text_ranges(
5975 indoc! {"
5976 a.ˇ b
5977 a.ˇ b
5978 a.ˇ b
5979 "},
5980 false,
5981 );
5982
5983 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
5984 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5985
5986 _ = editor.update(cx, |editor, cx| {
5987 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
5988
5989 editor
5990 .insert_snippet(&insertion_ranges, snippet, cx)
5991 .unwrap();
5992
5993 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
5994 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
5995 assert_eq!(editor.text(cx), expected_text);
5996 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
5997 }
5998
5999 assert(
6000 editor,
6001 cx,
6002 indoc! {"
6003 a.f(«one», two, «three») b
6004 a.f(«one», two, «three») b
6005 a.f(«one», two, «three») b
6006 "},
6007 );
6008
6009 // Can't move earlier than the first tab stop
6010 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6011 assert(
6012 editor,
6013 cx,
6014 indoc! {"
6015 a.f(«one», two, «three») b
6016 a.f(«one», two, «three») b
6017 a.f(«one», two, «three») b
6018 "},
6019 );
6020
6021 assert!(editor.move_to_next_snippet_tabstop(cx));
6022 assert(
6023 editor,
6024 cx,
6025 indoc! {"
6026 a.f(one, «two», three) b
6027 a.f(one, «two», three) b
6028 a.f(one, «two», three) b
6029 "},
6030 );
6031
6032 editor.move_to_prev_snippet_tabstop(cx);
6033 assert(
6034 editor,
6035 cx,
6036 indoc! {"
6037 a.f(«one», two, «three») b
6038 a.f(«one», two, «three») b
6039 a.f(«one», two, «three») b
6040 "},
6041 );
6042
6043 assert!(editor.move_to_next_snippet_tabstop(cx));
6044 assert(
6045 editor,
6046 cx,
6047 indoc! {"
6048 a.f(one, «two», three) b
6049 a.f(one, «two», three) b
6050 a.f(one, «two», three) b
6051 "},
6052 );
6053 assert!(editor.move_to_next_snippet_tabstop(cx));
6054 assert(
6055 editor,
6056 cx,
6057 indoc! {"
6058 a.f(one, two, three)ˇ b
6059 a.f(one, two, three)ˇ b
6060 a.f(one, two, three)ˇ b
6061 "},
6062 );
6063
6064 // As soon as the last tab stop is reached, snippet state is gone
6065 editor.move_to_prev_snippet_tabstop(cx);
6066 assert(
6067 editor,
6068 cx,
6069 indoc! {"
6070 a.f(one, two, three)ˇ b
6071 a.f(one, two, three)ˇ b
6072 a.f(one, two, three)ˇ b
6073 "},
6074 );
6075 });
6076}
6077
6078#[gpui::test]
6079async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6080 init_test(cx, |_| {});
6081
6082 let fs = FakeFs::new(cx.executor());
6083 fs.insert_file("/file.rs", Default::default()).await;
6084
6085 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6086
6087 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6088 language_registry.add(rust_lang());
6089 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6090 "Rust",
6091 FakeLspAdapter {
6092 capabilities: lsp::ServerCapabilities {
6093 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6094 ..Default::default()
6095 },
6096 ..Default::default()
6097 },
6098 );
6099
6100 let buffer = project
6101 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6102 .await
6103 .unwrap();
6104
6105 cx.executor().start_waiting();
6106 let fake_server = fake_servers.next().await.unwrap();
6107
6108 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6109 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6110 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6111 assert!(cx.read(|cx| editor.is_dirty(cx)));
6112
6113 let save = editor
6114 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6115 .unwrap();
6116 fake_server
6117 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6118 assert_eq!(
6119 params.text_document.uri,
6120 lsp::Url::from_file_path("/file.rs").unwrap()
6121 );
6122 assert_eq!(params.options.tab_size, 4);
6123 Ok(Some(vec![lsp::TextEdit::new(
6124 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6125 ", ".to_string(),
6126 )]))
6127 })
6128 .next()
6129 .await;
6130 cx.executor().start_waiting();
6131 save.await;
6132
6133 assert_eq!(
6134 editor.update(cx, |editor, cx| editor.text(cx)),
6135 "one, two\nthree\n"
6136 );
6137 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6138
6139 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6140 assert!(cx.read(|cx| editor.is_dirty(cx)));
6141
6142 // Ensure we can still save even if formatting hangs.
6143 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6144 assert_eq!(
6145 params.text_document.uri,
6146 lsp::Url::from_file_path("/file.rs").unwrap()
6147 );
6148 futures::future::pending::<()>().await;
6149 unreachable!()
6150 });
6151 let save = editor
6152 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6153 .unwrap();
6154 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6155 cx.executor().start_waiting();
6156 save.await;
6157 assert_eq!(
6158 editor.update(cx, |editor, cx| editor.text(cx)),
6159 "one\ntwo\nthree\n"
6160 );
6161 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6162
6163 // For non-dirty buffer, no formatting request should be sent
6164 let save = editor
6165 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6166 .unwrap();
6167 let _pending_format_request = fake_server
6168 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6169 panic!("Should not be invoked on non-dirty buffer");
6170 })
6171 .next();
6172 cx.executor().start_waiting();
6173 save.await;
6174
6175 // Set rust language override and assert overridden tabsize is sent to language server
6176 update_test_language_settings(cx, |settings| {
6177 settings.languages.insert(
6178 "Rust".into(),
6179 LanguageSettingsContent {
6180 tab_size: NonZeroU32::new(8),
6181 ..Default::default()
6182 },
6183 );
6184 });
6185
6186 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6187 assert!(cx.read(|cx| editor.is_dirty(cx)));
6188 let save = editor
6189 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6190 .unwrap();
6191 fake_server
6192 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6193 assert_eq!(
6194 params.text_document.uri,
6195 lsp::Url::from_file_path("/file.rs").unwrap()
6196 );
6197 assert_eq!(params.options.tab_size, 8);
6198 Ok(Some(vec![]))
6199 })
6200 .next()
6201 .await;
6202 cx.executor().start_waiting();
6203 save.await;
6204}
6205
6206#[gpui::test]
6207async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6208 init_test(cx, |_| {});
6209
6210 let cols = 4;
6211 let rows = 10;
6212 let sample_text_1 = sample_text(rows, cols, 'a');
6213 assert_eq!(
6214 sample_text_1,
6215 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6216 );
6217 let sample_text_2 = sample_text(rows, cols, 'l');
6218 assert_eq!(
6219 sample_text_2,
6220 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6221 );
6222 let sample_text_3 = sample_text(rows, cols, 'v');
6223 assert_eq!(
6224 sample_text_3,
6225 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6226 );
6227
6228 let fs = FakeFs::new(cx.executor());
6229 fs.insert_tree(
6230 "/a",
6231 json!({
6232 "main.rs": sample_text_1,
6233 "other.rs": sample_text_2,
6234 "lib.rs": sample_text_3,
6235 }),
6236 )
6237 .await;
6238
6239 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6240 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6241 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6242
6243 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6244 language_registry.add(rust_lang());
6245 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6246 "Rust",
6247 FakeLspAdapter {
6248 capabilities: lsp::ServerCapabilities {
6249 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6250 ..Default::default()
6251 },
6252 ..Default::default()
6253 },
6254 );
6255
6256 let worktree = project.update(cx, |project, _| {
6257 let mut worktrees = project.worktrees().collect::<Vec<_>>();
6258 assert_eq!(worktrees.len(), 1);
6259 worktrees.pop().unwrap()
6260 });
6261 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6262
6263 let buffer_1 = project
6264 .update(cx, |project, cx| {
6265 project.open_buffer((worktree_id, "main.rs"), cx)
6266 })
6267 .await
6268 .unwrap();
6269 let buffer_2 = project
6270 .update(cx, |project, cx| {
6271 project.open_buffer((worktree_id, "other.rs"), cx)
6272 })
6273 .await
6274 .unwrap();
6275 let buffer_3 = project
6276 .update(cx, |project, cx| {
6277 project.open_buffer((worktree_id, "lib.rs"), cx)
6278 })
6279 .await
6280 .unwrap();
6281
6282 let multi_buffer = cx.new_model(|cx| {
6283 let mut multi_buffer = MultiBuffer::new(0, ReadWrite);
6284 multi_buffer.push_excerpts(
6285 buffer_1.clone(),
6286 [
6287 ExcerptRange {
6288 context: Point::new(0, 0)..Point::new(3, 0),
6289 primary: None,
6290 },
6291 ExcerptRange {
6292 context: Point::new(5, 0)..Point::new(7, 0),
6293 primary: None,
6294 },
6295 ExcerptRange {
6296 context: Point::new(9, 0)..Point::new(10, 4),
6297 primary: None,
6298 },
6299 ],
6300 cx,
6301 );
6302 multi_buffer.push_excerpts(
6303 buffer_2.clone(),
6304 [
6305 ExcerptRange {
6306 context: Point::new(0, 0)..Point::new(3, 0),
6307 primary: None,
6308 },
6309 ExcerptRange {
6310 context: Point::new(5, 0)..Point::new(7, 0),
6311 primary: None,
6312 },
6313 ExcerptRange {
6314 context: Point::new(9, 0)..Point::new(10, 4),
6315 primary: None,
6316 },
6317 ],
6318 cx,
6319 );
6320 multi_buffer.push_excerpts(
6321 buffer_3.clone(),
6322 [
6323 ExcerptRange {
6324 context: Point::new(0, 0)..Point::new(3, 0),
6325 primary: None,
6326 },
6327 ExcerptRange {
6328 context: Point::new(5, 0)..Point::new(7, 0),
6329 primary: None,
6330 },
6331 ExcerptRange {
6332 context: Point::new(9, 0)..Point::new(10, 4),
6333 primary: None,
6334 },
6335 ],
6336 cx,
6337 );
6338 multi_buffer
6339 });
6340 let multi_buffer_editor = cx.new_view(|cx| {
6341 Editor::new(
6342 EditorMode::Full,
6343 multi_buffer,
6344 Some(project.clone()),
6345 true,
6346 cx,
6347 )
6348 });
6349
6350 multi_buffer_editor.update(cx, |editor, cx| {
6351 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
6352 editor.insert("|one|two|three|", cx);
6353 });
6354 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6355 multi_buffer_editor.update(cx, |editor, cx| {
6356 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
6357 s.select_ranges(Some(60..70))
6358 });
6359 editor.insert("|four|five|six|", cx);
6360 });
6361 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6362
6363 // First two buffers should be edited, but not the third one.
6364 assert_eq!(
6365 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6366 "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}",
6367 );
6368 buffer_1.update(cx, |buffer, _| {
6369 assert!(buffer.is_dirty());
6370 assert_eq!(
6371 buffer.text(),
6372 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
6373 )
6374 });
6375 buffer_2.update(cx, |buffer, _| {
6376 assert!(buffer.is_dirty());
6377 assert_eq!(
6378 buffer.text(),
6379 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
6380 )
6381 });
6382 buffer_3.update(cx, |buffer, _| {
6383 assert!(!buffer.is_dirty());
6384 assert_eq!(buffer.text(), sample_text_3,)
6385 });
6386
6387 cx.executor().start_waiting();
6388 let save = multi_buffer_editor
6389 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6390 .unwrap();
6391
6392 let fake_server = fake_servers.next().await.unwrap();
6393 fake_server
6394 .server
6395 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6396 Ok(Some(vec![lsp::TextEdit::new(
6397 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6398 format!("[{} formatted]", params.text_document.uri),
6399 )]))
6400 })
6401 .detach();
6402 save.await;
6403
6404 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
6405 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
6406 assert_eq!(
6407 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6408 "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}",
6409 );
6410 buffer_1.update(cx, |buffer, _| {
6411 assert!(!buffer.is_dirty());
6412 assert_eq!(
6413 buffer.text(),
6414 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
6415 )
6416 });
6417 buffer_2.update(cx, |buffer, _| {
6418 assert!(!buffer.is_dirty());
6419 assert_eq!(
6420 buffer.text(),
6421 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
6422 )
6423 });
6424 buffer_3.update(cx, |buffer, _| {
6425 assert!(!buffer.is_dirty());
6426 assert_eq!(buffer.text(), sample_text_3,)
6427 });
6428}
6429
6430#[gpui::test]
6431async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
6432 init_test(cx, |_| {});
6433
6434 let fs = FakeFs::new(cx.executor());
6435 fs.insert_file("/file.rs", Default::default()).await;
6436
6437 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6438
6439 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6440 language_registry.add(rust_lang());
6441 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6442 "Rust",
6443 FakeLspAdapter {
6444 capabilities: lsp::ServerCapabilities {
6445 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
6446 ..Default::default()
6447 },
6448 ..Default::default()
6449 },
6450 );
6451
6452 let buffer = project
6453 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6454 .await
6455 .unwrap();
6456
6457 cx.executor().start_waiting();
6458 let fake_server = fake_servers.next().await.unwrap();
6459
6460 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6461 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6462 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6463 assert!(cx.read(|cx| editor.is_dirty(cx)));
6464
6465 let save = editor
6466 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6467 .unwrap();
6468 fake_server
6469 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6470 assert_eq!(
6471 params.text_document.uri,
6472 lsp::Url::from_file_path("/file.rs").unwrap()
6473 );
6474 assert_eq!(params.options.tab_size, 4);
6475 Ok(Some(vec![lsp::TextEdit::new(
6476 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6477 ", ".to_string(),
6478 )]))
6479 })
6480 .next()
6481 .await;
6482 cx.executor().start_waiting();
6483 save.await;
6484 assert_eq!(
6485 editor.update(cx, |editor, cx| editor.text(cx)),
6486 "one, two\nthree\n"
6487 );
6488 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6489
6490 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6491 assert!(cx.read(|cx| editor.is_dirty(cx)));
6492
6493 // Ensure we can still save even if formatting hangs.
6494 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
6495 move |params, _| async move {
6496 assert_eq!(
6497 params.text_document.uri,
6498 lsp::Url::from_file_path("/file.rs").unwrap()
6499 );
6500 futures::future::pending::<()>().await;
6501 unreachable!()
6502 },
6503 );
6504 let save = editor
6505 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6506 .unwrap();
6507 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6508 cx.executor().start_waiting();
6509 save.await;
6510 assert_eq!(
6511 editor.update(cx, |editor, cx| editor.text(cx)),
6512 "one\ntwo\nthree\n"
6513 );
6514 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6515
6516 // For non-dirty buffer, no formatting request should be sent
6517 let save = editor
6518 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6519 .unwrap();
6520 let _pending_format_request = fake_server
6521 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6522 panic!("Should not be invoked on non-dirty buffer");
6523 })
6524 .next();
6525 cx.executor().start_waiting();
6526 save.await;
6527
6528 // Set Rust language override and assert overridden tabsize is sent to language server
6529 update_test_language_settings(cx, |settings| {
6530 settings.languages.insert(
6531 "Rust".into(),
6532 LanguageSettingsContent {
6533 tab_size: NonZeroU32::new(8),
6534 ..Default::default()
6535 },
6536 );
6537 });
6538
6539 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6540 assert!(cx.read(|cx| editor.is_dirty(cx)));
6541 let save = editor
6542 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6543 .unwrap();
6544 fake_server
6545 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6546 assert_eq!(
6547 params.text_document.uri,
6548 lsp::Url::from_file_path("/file.rs").unwrap()
6549 );
6550 assert_eq!(params.options.tab_size, 8);
6551 Ok(Some(vec![]))
6552 })
6553 .next()
6554 .await;
6555 cx.executor().start_waiting();
6556 save.await;
6557}
6558
6559#[gpui::test]
6560async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
6561 init_test(cx, |settings| {
6562 settings.defaults.formatter = Some(language_settings::Formatter::LanguageServer)
6563 });
6564
6565 let fs = FakeFs::new(cx.executor());
6566 fs.insert_file("/file.rs", Default::default()).await;
6567
6568 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6569
6570 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6571 language_registry.add(Arc::new(Language::new(
6572 LanguageConfig {
6573 name: "Rust".into(),
6574 matcher: LanguageMatcher {
6575 path_suffixes: vec!["rs".to_string()],
6576 ..Default::default()
6577 },
6578 ..LanguageConfig::default()
6579 },
6580 Some(tree_sitter_rust::language()),
6581 )));
6582 update_test_language_settings(cx, |settings| {
6583 // Enable Prettier formatting for the same buffer, and ensure
6584 // LSP is called instead of Prettier.
6585 settings.defaults.prettier = Some(PrettierSettings {
6586 allowed: true,
6587 ..PrettierSettings::default()
6588 });
6589 });
6590 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6591 "Rust",
6592 FakeLspAdapter {
6593 capabilities: lsp::ServerCapabilities {
6594 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6595 ..Default::default()
6596 },
6597 ..Default::default()
6598 },
6599 );
6600
6601 let buffer = project
6602 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6603 .await
6604 .unwrap();
6605
6606 cx.executor().start_waiting();
6607 let fake_server = fake_servers.next().await.unwrap();
6608
6609 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6610 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6611 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6612
6613 let format = editor
6614 .update(cx, |editor, cx| {
6615 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
6616 })
6617 .unwrap();
6618 fake_server
6619 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6620 assert_eq!(
6621 params.text_document.uri,
6622 lsp::Url::from_file_path("/file.rs").unwrap()
6623 );
6624 assert_eq!(params.options.tab_size, 4);
6625 Ok(Some(vec![lsp::TextEdit::new(
6626 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6627 ", ".to_string(),
6628 )]))
6629 })
6630 .next()
6631 .await;
6632 cx.executor().start_waiting();
6633 format.await;
6634 assert_eq!(
6635 editor.update(cx, |editor, cx| editor.text(cx)),
6636 "one, two\nthree\n"
6637 );
6638
6639 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6640 // Ensure we don't lock if formatting hangs.
6641 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6642 assert_eq!(
6643 params.text_document.uri,
6644 lsp::Url::from_file_path("/file.rs").unwrap()
6645 );
6646 futures::future::pending::<()>().await;
6647 unreachable!()
6648 });
6649 let format = editor
6650 .update(cx, |editor, cx| {
6651 editor.perform_format(project, FormatTrigger::Manual, cx)
6652 })
6653 .unwrap();
6654 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6655 cx.executor().start_waiting();
6656 format.await;
6657 assert_eq!(
6658 editor.update(cx, |editor, cx| editor.text(cx)),
6659 "one\ntwo\nthree\n"
6660 );
6661}
6662
6663#[gpui::test]
6664async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
6665 init_test(cx, |_| {});
6666
6667 let mut cx = EditorLspTestContext::new_rust(
6668 lsp::ServerCapabilities {
6669 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6670 ..Default::default()
6671 },
6672 cx,
6673 )
6674 .await;
6675
6676 cx.set_state(indoc! {"
6677 one.twoˇ
6678 "});
6679
6680 // The format request takes a long time. When it completes, it inserts
6681 // a newline and an indent before the `.`
6682 cx.lsp
6683 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
6684 let executor = cx.background_executor().clone();
6685 async move {
6686 executor.timer(Duration::from_millis(100)).await;
6687 Ok(Some(vec![lsp::TextEdit {
6688 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
6689 new_text: "\n ".into(),
6690 }]))
6691 }
6692 });
6693
6694 // Submit a format request.
6695 let format_1 = cx
6696 .update_editor(|editor, cx| editor.format(&Format, cx))
6697 .unwrap();
6698 cx.executor().run_until_parked();
6699
6700 // Submit a second format request.
6701 let format_2 = cx
6702 .update_editor(|editor, cx| editor.format(&Format, cx))
6703 .unwrap();
6704 cx.executor().run_until_parked();
6705
6706 // Wait for both format requests to complete
6707 cx.executor().advance_clock(Duration::from_millis(200));
6708 cx.executor().start_waiting();
6709 format_1.await.unwrap();
6710 cx.executor().start_waiting();
6711 format_2.await.unwrap();
6712
6713 // The formatting edits only happens once.
6714 cx.assert_editor_state(indoc! {"
6715 one
6716 .twoˇ
6717 "});
6718}
6719
6720#[gpui::test]
6721async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
6722 init_test(cx, |settings| {
6723 settings.defaults.formatter = Some(language_settings::Formatter::Auto)
6724 });
6725
6726 let mut cx = EditorLspTestContext::new_rust(
6727 lsp::ServerCapabilities {
6728 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6729 ..Default::default()
6730 },
6731 cx,
6732 )
6733 .await;
6734
6735 // Set up a buffer white some trailing whitespace and no trailing newline.
6736 cx.set_state(
6737 &[
6738 "one ", //
6739 "twoˇ", //
6740 "three ", //
6741 "four", //
6742 ]
6743 .join("\n"),
6744 );
6745
6746 // Submit a format request.
6747 let format = cx
6748 .update_editor(|editor, cx| editor.format(&Format, cx))
6749 .unwrap();
6750
6751 // Record which buffer changes have been sent to the language server
6752 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
6753 cx.lsp
6754 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
6755 let buffer_changes = buffer_changes.clone();
6756 move |params, _| {
6757 buffer_changes.lock().extend(
6758 params
6759 .content_changes
6760 .into_iter()
6761 .map(|e| (e.range.unwrap(), e.text)),
6762 );
6763 }
6764 });
6765
6766 // Handle formatting requests to the language server.
6767 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
6768 let buffer_changes = buffer_changes.clone();
6769 move |_, _| {
6770 // When formatting is requested, trailing whitespace has already been stripped,
6771 // and the trailing newline has already been added.
6772 assert_eq!(
6773 &buffer_changes.lock()[1..],
6774 &[
6775 (
6776 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
6777 "".into()
6778 ),
6779 (
6780 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
6781 "".into()
6782 ),
6783 (
6784 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
6785 "\n".into()
6786 ),
6787 ]
6788 );
6789
6790 // Insert blank lines between each line of the buffer.
6791 async move {
6792 Ok(Some(vec![
6793 lsp::TextEdit {
6794 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
6795 new_text: "\n".into(),
6796 },
6797 lsp::TextEdit {
6798 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
6799 new_text: "\n".into(),
6800 },
6801 ]))
6802 }
6803 }
6804 });
6805
6806 // After formatting the buffer, the trailing whitespace is stripped,
6807 // a newline is appended, and the edits provided by the language server
6808 // have been applied.
6809 format.await.unwrap();
6810 cx.assert_editor_state(
6811 &[
6812 "one", //
6813 "", //
6814 "twoˇ", //
6815 "", //
6816 "three", //
6817 "four", //
6818 "", //
6819 ]
6820 .join("\n"),
6821 );
6822
6823 // Undoing the formatting undoes the trailing whitespace removal, the
6824 // trailing newline, and the LSP edits.
6825 cx.update_buffer(|buffer, cx| buffer.undo(cx));
6826 cx.assert_editor_state(
6827 &[
6828 "one ", //
6829 "twoˇ", //
6830 "three ", //
6831 "four", //
6832 ]
6833 .join("\n"),
6834 );
6835}
6836
6837#[gpui::test]
6838async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
6839 cx: &mut gpui::TestAppContext,
6840) {
6841 init_test(cx, |_| {});
6842
6843 cx.update(|cx| {
6844 cx.update_global::<SettingsStore, _>(|settings, cx| {
6845 settings.update_user_settings::<EditorSettings>(cx, |settings| {
6846 settings.auto_signature_help = Some(true);
6847 });
6848 });
6849 });
6850
6851 let mut cx = EditorLspTestContext::new_rust(
6852 lsp::ServerCapabilities {
6853 signature_help_provider: Some(lsp::SignatureHelpOptions {
6854 ..Default::default()
6855 }),
6856 ..Default::default()
6857 },
6858 cx,
6859 )
6860 .await;
6861
6862 let language = Language::new(
6863 LanguageConfig {
6864 name: "Rust".into(),
6865 brackets: BracketPairConfig {
6866 pairs: vec![
6867 BracketPair {
6868 start: "{".to_string(),
6869 end: "}".to_string(),
6870 close: true,
6871 surround: true,
6872 newline: true,
6873 },
6874 BracketPair {
6875 start: "(".to_string(),
6876 end: ")".to_string(),
6877 close: true,
6878 surround: true,
6879 newline: true,
6880 },
6881 BracketPair {
6882 start: "/*".to_string(),
6883 end: " */".to_string(),
6884 close: true,
6885 surround: true,
6886 newline: true,
6887 },
6888 BracketPair {
6889 start: "[".to_string(),
6890 end: "]".to_string(),
6891 close: false,
6892 surround: false,
6893 newline: true,
6894 },
6895 BracketPair {
6896 start: "\"".to_string(),
6897 end: "\"".to_string(),
6898 close: true,
6899 surround: true,
6900 newline: false,
6901 },
6902 BracketPair {
6903 start: "<".to_string(),
6904 end: ">".to_string(),
6905 close: false,
6906 surround: true,
6907 newline: true,
6908 },
6909 ],
6910 ..Default::default()
6911 },
6912 autoclose_before: "})]".to_string(),
6913 ..Default::default()
6914 },
6915 Some(tree_sitter_rust::language()),
6916 );
6917 let language = Arc::new(language);
6918
6919 cx.language_registry().add(language.clone());
6920 cx.update_buffer(|buffer, cx| {
6921 buffer.set_language(Some(language), cx);
6922 });
6923
6924 cx.set_state(
6925 &r#"
6926 fn main() {
6927 sampleˇ
6928 }
6929 "#
6930 .unindent(),
6931 );
6932
6933 cx.update_editor(|view, cx| {
6934 view.handle_input("(", cx);
6935 });
6936 cx.assert_editor_state(
6937 &"
6938 fn main() {
6939 sample(ˇ)
6940 }
6941 "
6942 .unindent(),
6943 );
6944
6945 let mocked_response = lsp::SignatureHelp {
6946 signatures: vec![lsp::SignatureInformation {
6947 label: "fn sample(param1: u8, param2: u8)".to_string(),
6948 documentation: None,
6949 parameters: Some(vec![
6950 lsp::ParameterInformation {
6951 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
6952 documentation: None,
6953 },
6954 lsp::ParameterInformation {
6955 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
6956 documentation: None,
6957 },
6958 ]),
6959 active_parameter: None,
6960 }],
6961 active_signature: Some(0),
6962 active_parameter: Some(0),
6963 };
6964 handle_signature_help_request(&mut cx, mocked_response).await;
6965
6966 cx.condition(|editor, _| editor.signature_help_state.is_shown())
6967 .await;
6968
6969 cx.editor(|editor, _| {
6970 let signature_help_state = editor.signature_help_state.popover().cloned();
6971 assert!(signature_help_state.is_some());
6972 let ParsedMarkdown {
6973 text, highlights, ..
6974 } = signature_help_state.unwrap().parsed_content;
6975 assert_eq!(text, "param1: u8, param2: u8");
6976 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
6977 });
6978}
6979
6980#[gpui::test]
6981async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
6982 init_test(cx, |_| {});
6983
6984 cx.update(|cx| {
6985 cx.update_global::<SettingsStore, _>(|settings, cx| {
6986 settings.update_user_settings::<EditorSettings>(cx, |settings| {
6987 settings.auto_signature_help = Some(false);
6988 settings.show_signature_help_after_edits = Some(false);
6989 });
6990 });
6991 });
6992
6993 let mut cx = EditorLspTestContext::new_rust(
6994 lsp::ServerCapabilities {
6995 signature_help_provider: Some(lsp::SignatureHelpOptions {
6996 ..Default::default()
6997 }),
6998 ..Default::default()
6999 },
7000 cx,
7001 )
7002 .await;
7003
7004 let language = Language::new(
7005 LanguageConfig {
7006 name: "Rust".into(),
7007 brackets: BracketPairConfig {
7008 pairs: vec![
7009 BracketPair {
7010 start: "{".to_string(),
7011 end: "}".to_string(),
7012 close: true,
7013 surround: true,
7014 newline: true,
7015 },
7016 BracketPair {
7017 start: "(".to_string(),
7018 end: ")".to_string(),
7019 close: true,
7020 surround: true,
7021 newline: true,
7022 },
7023 BracketPair {
7024 start: "/*".to_string(),
7025 end: " */".to_string(),
7026 close: true,
7027 surround: true,
7028 newline: true,
7029 },
7030 BracketPair {
7031 start: "[".to_string(),
7032 end: "]".to_string(),
7033 close: false,
7034 surround: false,
7035 newline: true,
7036 },
7037 BracketPair {
7038 start: "\"".to_string(),
7039 end: "\"".to_string(),
7040 close: true,
7041 surround: true,
7042 newline: false,
7043 },
7044 BracketPair {
7045 start: "<".to_string(),
7046 end: ">".to_string(),
7047 close: false,
7048 surround: true,
7049 newline: true,
7050 },
7051 ],
7052 ..Default::default()
7053 },
7054 autoclose_before: "})]".to_string(),
7055 ..Default::default()
7056 },
7057 Some(tree_sitter_rust::language()),
7058 );
7059 let language = Arc::new(language);
7060
7061 cx.language_registry().add(language.clone());
7062 cx.update_buffer(|buffer, cx| {
7063 buffer.set_language(Some(language), cx);
7064 });
7065
7066 // Ensure that signature_help is not called when no signature help is enabled.
7067 cx.set_state(
7068 &r#"
7069 fn main() {
7070 sampleˇ
7071 }
7072 "#
7073 .unindent(),
7074 );
7075 cx.update_editor(|view, cx| {
7076 view.handle_input("(", cx);
7077 });
7078 cx.assert_editor_state(
7079 &"
7080 fn main() {
7081 sample(ˇ)
7082 }
7083 "
7084 .unindent(),
7085 );
7086 cx.editor(|editor, _| {
7087 assert!(editor.signature_help_state.task().is_none());
7088 });
7089
7090 let mocked_response = lsp::SignatureHelp {
7091 signatures: vec![lsp::SignatureInformation {
7092 label: "fn sample(param1: u8, param2: u8)".to_string(),
7093 documentation: None,
7094 parameters: Some(vec![
7095 lsp::ParameterInformation {
7096 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7097 documentation: None,
7098 },
7099 lsp::ParameterInformation {
7100 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7101 documentation: None,
7102 },
7103 ]),
7104 active_parameter: None,
7105 }],
7106 active_signature: Some(0),
7107 active_parameter: Some(0),
7108 };
7109
7110 // Ensure that signature_help is called when enabled afte edits
7111 cx.update(|cx| {
7112 cx.update_global::<SettingsStore, _>(|settings, cx| {
7113 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7114 settings.auto_signature_help = Some(false);
7115 settings.show_signature_help_after_edits = Some(true);
7116 });
7117 });
7118 });
7119 cx.set_state(
7120 &r#"
7121 fn main() {
7122 sampleˇ
7123 }
7124 "#
7125 .unindent(),
7126 );
7127 cx.update_editor(|view, cx| {
7128 view.handle_input("(", cx);
7129 });
7130 cx.assert_editor_state(
7131 &"
7132 fn main() {
7133 sample(ˇ)
7134 }
7135 "
7136 .unindent(),
7137 );
7138 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7139 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7140 .await;
7141 cx.update_editor(|editor, _| {
7142 let signature_help_state = editor.signature_help_state.popover().cloned();
7143 assert!(signature_help_state.is_some());
7144 let ParsedMarkdown {
7145 text, highlights, ..
7146 } = signature_help_state.unwrap().parsed_content;
7147 assert_eq!(text, "param1: u8, param2: u8");
7148 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7149 editor.signature_help_state = SignatureHelpState::default();
7150 });
7151
7152 // Ensure that signature_help is called when auto signature help override is enabled
7153 cx.update(|cx| {
7154 cx.update_global::<SettingsStore, _>(|settings, cx| {
7155 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7156 settings.auto_signature_help = Some(true);
7157 settings.show_signature_help_after_edits = Some(false);
7158 });
7159 });
7160 });
7161 cx.set_state(
7162 &r#"
7163 fn main() {
7164 sampleˇ
7165 }
7166 "#
7167 .unindent(),
7168 );
7169 cx.update_editor(|view, cx| {
7170 view.handle_input("(", cx);
7171 });
7172 cx.assert_editor_state(
7173 &"
7174 fn main() {
7175 sample(ˇ)
7176 }
7177 "
7178 .unindent(),
7179 );
7180 handle_signature_help_request(&mut cx, mocked_response).await;
7181 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7182 .await;
7183 cx.editor(|editor, _| {
7184 let signature_help_state = editor.signature_help_state.popover().cloned();
7185 assert!(signature_help_state.is_some());
7186 let ParsedMarkdown {
7187 text, highlights, ..
7188 } = signature_help_state.unwrap().parsed_content;
7189 assert_eq!(text, "param1: u8, param2: u8");
7190 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7191 });
7192}
7193
7194#[gpui::test]
7195async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7196 init_test(cx, |_| {});
7197 cx.update(|cx| {
7198 cx.update_global::<SettingsStore, _>(|settings, cx| {
7199 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7200 settings.auto_signature_help = Some(true);
7201 });
7202 });
7203 });
7204
7205 let mut cx = EditorLspTestContext::new_rust(
7206 lsp::ServerCapabilities {
7207 signature_help_provider: Some(lsp::SignatureHelpOptions {
7208 ..Default::default()
7209 }),
7210 ..Default::default()
7211 },
7212 cx,
7213 )
7214 .await;
7215
7216 // A test that directly calls `show_signature_help`
7217 cx.update_editor(|editor, cx| {
7218 editor.show_signature_help(&ShowSignatureHelp, cx);
7219 });
7220
7221 let mocked_response = lsp::SignatureHelp {
7222 signatures: vec![lsp::SignatureInformation {
7223 label: "fn sample(param1: u8, param2: u8)".to_string(),
7224 documentation: None,
7225 parameters: Some(vec![
7226 lsp::ParameterInformation {
7227 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7228 documentation: None,
7229 },
7230 lsp::ParameterInformation {
7231 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7232 documentation: None,
7233 },
7234 ]),
7235 active_parameter: None,
7236 }],
7237 active_signature: Some(0),
7238 active_parameter: Some(0),
7239 };
7240 handle_signature_help_request(&mut cx, mocked_response).await;
7241
7242 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7243 .await;
7244
7245 cx.editor(|editor, _| {
7246 let signature_help_state = editor.signature_help_state.popover().cloned();
7247 assert!(signature_help_state.is_some());
7248 let ParsedMarkdown {
7249 text, highlights, ..
7250 } = signature_help_state.unwrap().parsed_content;
7251 assert_eq!(text, "param1: u8, param2: u8");
7252 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7253 });
7254
7255 // When exiting outside from inside the brackets, `signature_help` is closed.
7256 cx.set_state(indoc! {"
7257 fn main() {
7258 sample(ˇ);
7259 }
7260
7261 fn sample(param1: u8, param2: u8) {}
7262 "});
7263
7264 cx.update_editor(|editor, cx| {
7265 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
7266 });
7267
7268 let mocked_response = lsp::SignatureHelp {
7269 signatures: Vec::new(),
7270 active_signature: None,
7271 active_parameter: None,
7272 };
7273 handle_signature_help_request(&mut cx, mocked_response).await;
7274
7275 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7276 .await;
7277
7278 cx.editor(|editor, _| {
7279 assert!(!editor.signature_help_state.is_shown());
7280 });
7281
7282 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
7283 cx.set_state(indoc! {"
7284 fn main() {
7285 sample(ˇ);
7286 }
7287
7288 fn sample(param1: u8, param2: u8) {}
7289 "});
7290
7291 let mocked_response = lsp::SignatureHelp {
7292 signatures: vec![lsp::SignatureInformation {
7293 label: "fn sample(param1: u8, param2: u8)".to_string(),
7294 documentation: None,
7295 parameters: Some(vec![
7296 lsp::ParameterInformation {
7297 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7298 documentation: None,
7299 },
7300 lsp::ParameterInformation {
7301 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7302 documentation: None,
7303 },
7304 ]),
7305 active_parameter: None,
7306 }],
7307 active_signature: Some(0),
7308 active_parameter: Some(0),
7309 };
7310 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7311 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7312 .await;
7313 cx.editor(|editor, _| {
7314 assert!(editor.signature_help_state.is_shown());
7315 });
7316
7317 // Restore the popover with more parameter input
7318 cx.set_state(indoc! {"
7319 fn main() {
7320 sample(param1, param2ˇ);
7321 }
7322
7323 fn sample(param1: u8, param2: u8) {}
7324 "});
7325
7326 let mocked_response = lsp::SignatureHelp {
7327 signatures: vec![lsp::SignatureInformation {
7328 label: "fn sample(param1: u8, param2: u8)".to_string(),
7329 documentation: None,
7330 parameters: Some(vec![
7331 lsp::ParameterInformation {
7332 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7333 documentation: None,
7334 },
7335 lsp::ParameterInformation {
7336 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7337 documentation: None,
7338 },
7339 ]),
7340 active_parameter: None,
7341 }],
7342 active_signature: Some(0),
7343 active_parameter: Some(1),
7344 };
7345 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7346 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7347 .await;
7348
7349 // When selecting a range, the popover is gone.
7350 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
7351 cx.update_editor(|editor, cx| {
7352 editor.change_selections(None, cx, |s| {
7353 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7354 })
7355 });
7356 cx.assert_editor_state(indoc! {"
7357 fn main() {
7358 sample(param1, «ˇparam2»);
7359 }
7360
7361 fn sample(param1: u8, param2: u8) {}
7362 "});
7363 cx.editor(|editor, _| {
7364 assert!(!editor.signature_help_state.is_shown());
7365 });
7366
7367 // When unselecting again, the popover is back if within the brackets.
7368 cx.update_editor(|editor, cx| {
7369 editor.change_selections(None, cx, |s| {
7370 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7371 })
7372 });
7373 cx.assert_editor_state(indoc! {"
7374 fn main() {
7375 sample(param1, ˇparam2);
7376 }
7377
7378 fn sample(param1: u8, param2: u8) {}
7379 "});
7380 handle_signature_help_request(&mut cx, mocked_response).await;
7381 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7382 .await;
7383 cx.editor(|editor, _| {
7384 assert!(editor.signature_help_state.is_shown());
7385 });
7386
7387 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
7388 cx.update_editor(|editor, cx| {
7389 editor.change_selections(None, cx, |s| {
7390 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
7391 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7392 })
7393 });
7394 cx.assert_editor_state(indoc! {"
7395 fn main() {
7396 sample(param1, ˇparam2);
7397 }
7398
7399 fn sample(param1: u8, param2: u8) {}
7400 "});
7401
7402 let mocked_response = lsp::SignatureHelp {
7403 signatures: vec![lsp::SignatureInformation {
7404 label: "fn sample(param1: u8, param2: u8)".to_string(),
7405 documentation: None,
7406 parameters: Some(vec![
7407 lsp::ParameterInformation {
7408 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7409 documentation: None,
7410 },
7411 lsp::ParameterInformation {
7412 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7413 documentation: None,
7414 },
7415 ]),
7416 active_parameter: None,
7417 }],
7418 active_signature: Some(0),
7419 active_parameter: Some(1),
7420 };
7421 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7422 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7423 .await;
7424 cx.update_editor(|editor, cx| {
7425 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
7426 });
7427 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7428 .await;
7429 cx.update_editor(|editor, cx| {
7430 editor.change_selections(None, cx, |s| {
7431 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7432 })
7433 });
7434 cx.assert_editor_state(indoc! {"
7435 fn main() {
7436 sample(param1, «ˇparam2»);
7437 }
7438
7439 fn sample(param1: u8, param2: u8) {}
7440 "});
7441 cx.update_editor(|editor, cx| {
7442 editor.change_selections(None, cx, |s| {
7443 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7444 })
7445 });
7446 cx.assert_editor_state(indoc! {"
7447 fn main() {
7448 sample(param1, ˇparam2);
7449 }
7450
7451 fn sample(param1: u8, param2: u8) {}
7452 "});
7453 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
7454 .await;
7455}
7456
7457#[gpui::test]
7458async fn test_completion(cx: &mut gpui::TestAppContext) {
7459 init_test(cx, |_| {});
7460
7461 let mut cx = EditorLspTestContext::new_rust(
7462 lsp::ServerCapabilities {
7463 completion_provider: Some(lsp::CompletionOptions {
7464 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7465 resolve_provider: Some(true),
7466 ..Default::default()
7467 }),
7468 ..Default::default()
7469 },
7470 cx,
7471 )
7472 .await;
7473 let counter = Arc::new(AtomicUsize::new(0));
7474
7475 cx.set_state(indoc! {"
7476 oneˇ
7477 two
7478 three
7479 "});
7480 cx.simulate_keystroke(".");
7481 handle_completion_request(
7482 &mut cx,
7483 indoc! {"
7484 one.|<>
7485 two
7486 three
7487 "},
7488 vec!["first_completion", "second_completion"],
7489 counter.clone(),
7490 )
7491 .await;
7492 cx.condition(|editor, _| editor.context_menu_visible())
7493 .await;
7494 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
7495
7496 let apply_additional_edits = cx.update_editor(|editor, cx| {
7497 editor.context_menu_next(&Default::default(), cx);
7498 editor
7499 .confirm_completion(&ConfirmCompletion::default(), cx)
7500 .unwrap()
7501 });
7502 cx.assert_editor_state(indoc! {"
7503 one.second_completionˇ
7504 two
7505 three
7506 "});
7507
7508 handle_resolve_completion_request(
7509 &mut cx,
7510 Some(vec![
7511 (
7512 //This overlaps with the primary completion edit which is
7513 //misbehavior from the LSP spec, test that we filter it out
7514 indoc! {"
7515 one.second_ˇcompletion
7516 two
7517 threeˇ
7518 "},
7519 "overlapping additional edit",
7520 ),
7521 (
7522 indoc! {"
7523 one.second_completion
7524 two
7525 threeˇ
7526 "},
7527 "\nadditional edit",
7528 ),
7529 ]),
7530 )
7531 .await;
7532 apply_additional_edits.await.unwrap();
7533 cx.assert_editor_state(indoc! {"
7534 one.second_completionˇ
7535 two
7536 three
7537 additional edit
7538 "});
7539
7540 cx.set_state(indoc! {"
7541 one.second_completion
7542 twoˇ
7543 threeˇ
7544 additional edit
7545 "});
7546 cx.simulate_keystroke(" ");
7547 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7548 cx.simulate_keystroke("s");
7549 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7550
7551 cx.assert_editor_state(indoc! {"
7552 one.second_completion
7553 two sˇ
7554 three sˇ
7555 additional edit
7556 "});
7557 handle_completion_request(
7558 &mut cx,
7559 indoc! {"
7560 one.second_completion
7561 two s
7562 three <s|>
7563 additional edit
7564 "},
7565 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
7566 counter.clone(),
7567 )
7568 .await;
7569 cx.condition(|editor, _| editor.context_menu_visible())
7570 .await;
7571 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
7572
7573 cx.simulate_keystroke("i");
7574
7575 handle_completion_request(
7576 &mut cx,
7577 indoc! {"
7578 one.second_completion
7579 two si
7580 three <si|>
7581 additional edit
7582 "},
7583 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
7584 counter.clone(),
7585 )
7586 .await;
7587 cx.condition(|editor, _| editor.context_menu_visible())
7588 .await;
7589 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
7590
7591 let apply_additional_edits = cx.update_editor(|editor, cx| {
7592 editor
7593 .confirm_completion(&ConfirmCompletion::default(), cx)
7594 .unwrap()
7595 });
7596 cx.assert_editor_state(indoc! {"
7597 one.second_completion
7598 two sixth_completionˇ
7599 three sixth_completionˇ
7600 additional edit
7601 "});
7602
7603 handle_resolve_completion_request(&mut cx, None).await;
7604 apply_additional_edits.await.unwrap();
7605
7606 _ = cx.update(|cx| {
7607 cx.update_global::<SettingsStore, _>(|settings, cx| {
7608 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7609 settings.show_completions_on_input = Some(false);
7610 });
7611 })
7612 });
7613 cx.set_state("editorˇ");
7614 cx.simulate_keystroke(".");
7615 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7616 cx.simulate_keystroke("c");
7617 cx.simulate_keystroke("l");
7618 cx.simulate_keystroke("o");
7619 cx.assert_editor_state("editor.cloˇ");
7620 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7621 cx.update_editor(|editor, cx| {
7622 editor.show_completions(&ShowCompletions { trigger: None }, cx);
7623 });
7624 handle_completion_request(
7625 &mut cx,
7626 "editor.<clo|>",
7627 vec!["close", "clobber"],
7628 counter.clone(),
7629 )
7630 .await;
7631 cx.condition(|editor, _| editor.context_menu_visible())
7632 .await;
7633 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
7634
7635 let apply_additional_edits = cx.update_editor(|editor, cx| {
7636 editor
7637 .confirm_completion(&ConfirmCompletion::default(), cx)
7638 .unwrap()
7639 });
7640 cx.assert_editor_state("editor.closeˇ");
7641 handle_resolve_completion_request(&mut cx, None).await;
7642 apply_additional_edits.await.unwrap();
7643}
7644
7645#[gpui::test]
7646async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
7647 init_test(cx, |_| {});
7648 let mut cx = EditorLspTestContext::new_rust(
7649 lsp::ServerCapabilities {
7650 completion_provider: Some(lsp::CompletionOptions {
7651 trigger_characters: Some(vec![".".to_string()]),
7652 ..Default::default()
7653 }),
7654 ..Default::default()
7655 },
7656 cx,
7657 )
7658 .await;
7659 cx.lsp
7660 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
7661 Ok(Some(lsp::CompletionResponse::Array(vec![
7662 lsp::CompletionItem {
7663 label: "first".into(),
7664 ..Default::default()
7665 },
7666 lsp::CompletionItem {
7667 label: "last".into(),
7668 ..Default::default()
7669 },
7670 ])))
7671 });
7672 cx.set_state("variableˇ");
7673 cx.simulate_keystroke(".");
7674 cx.executor().run_until_parked();
7675
7676 cx.update_editor(|editor, _| {
7677 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7678 assert_eq!(
7679 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
7680 &["first", "last"]
7681 );
7682 } else {
7683 panic!("expected completion menu to be open");
7684 }
7685 });
7686
7687 cx.update_editor(|editor, cx| {
7688 editor.move_page_down(&MovePageDown::default(), cx);
7689 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7690 assert!(
7691 menu.selected_item == 1,
7692 "expected PageDown to select the last item from the context menu"
7693 );
7694 } else {
7695 panic!("expected completion menu to stay open after PageDown");
7696 }
7697 });
7698
7699 cx.update_editor(|editor, cx| {
7700 editor.move_page_up(&MovePageUp::default(), cx);
7701 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7702 assert!(
7703 menu.selected_item == 0,
7704 "expected PageUp to select the first item from the context menu"
7705 );
7706 } else {
7707 panic!("expected completion menu to stay open after PageUp");
7708 }
7709 });
7710}
7711
7712#[gpui::test]
7713async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
7714 init_test(cx, |_| {});
7715
7716 let mut cx = EditorLspTestContext::new_rust(
7717 lsp::ServerCapabilities {
7718 completion_provider: Some(lsp::CompletionOptions {
7719 trigger_characters: Some(vec![".".to_string()]),
7720 resolve_provider: Some(true),
7721 ..Default::default()
7722 }),
7723 ..Default::default()
7724 },
7725 cx,
7726 )
7727 .await;
7728
7729 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
7730 cx.simulate_keystroke(".");
7731 let completion_item = lsp::CompletionItem {
7732 label: "Some".into(),
7733 kind: Some(lsp::CompletionItemKind::SNIPPET),
7734 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
7735 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
7736 kind: lsp::MarkupKind::Markdown,
7737 value: "```rust\nSome(2)\n```".to_string(),
7738 })),
7739 deprecated: Some(false),
7740 sort_text: Some("Some".to_string()),
7741 filter_text: Some("Some".to_string()),
7742 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
7743 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7744 range: lsp::Range {
7745 start: lsp::Position {
7746 line: 0,
7747 character: 22,
7748 },
7749 end: lsp::Position {
7750 line: 0,
7751 character: 22,
7752 },
7753 },
7754 new_text: "Some(2)".to_string(),
7755 })),
7756 additional_text_edits: Some(vec![lsp::TextEdit {
7757 range: lsp::Range {
7758 start: lsp::Position {
7759 line: 0,
7760 character: 20,
7761 },
7762 end: lsp::Position {
7763 line: 0,
7764 character: 22,
7765 },
7766 },
7767 new_text: "".to_string(),
7768 }]),
7769 ..Default::default()
7770 };
7771
7772 let closure_completion_item = completion_item.clone();
7773 let counter = Arc::new(AtomicUsize::new(0));
7774 let counter_clone = counter.clone();
7775 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
7776 let task_completion_item = closure_completion_item.clone();
7777 counter_clone.fetch_add(1, atomic::Ordering::Release);
7778 async move {
7779 Ok(Some(lsp::CompletionResponse::Array(vec![
7780 task_completion_item,
7781 ])))
7782 }
7783 });
7784
7785 cx.condition(|editor, _| editor.context_menu_visible())
7786 .await;
7787 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
7788 assert!(request.next().await.is_some());
7789 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
7790
7791 cx.simulate_keystroke("S");
7792 cx.simulate_keystroke("o");
7793 cx.simulate_keystroke("m");
7794 cx.condition(|editor, _| editor.context_menu_visible())
7795 .await;
7796 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
7797 assert!(request.next().await.is_some());
7798 assert!(request.next().await.is_some());
7799 assert!(request.next().await.is_some());
7800 request.close();
7801 assert!(request.next().await.is_none());
7802 assert_eq!(
7803 counter.load(atomic::Ordering::Acquire),
7804 4,
7805 "With the completions menu open, only one LSP request should happen per input"
7806 );
7807}
7808
7809#[gpui::test]
7810async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
7811 init_test(cx, |_| {});
7812 let mut cx = EditorTestContext::new(cx).await;
7813 let language = Arc::new(Language::new(
7814 LanguageConfig {
7815 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
7816 ..Default::default()
7817 },
7818 Some(tree_sitter_rust::language()),
7819 ));
7820 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
7821
7822 // If multiple selections intersect a line, the line is only toggled once.
7823 cx.set_state(indoc! {"
7824 fn a() {
7825 «//b();
7826 ˇ»// «c();
7827 //ˇ» d();
7828 }
7829 "});
7830
7831 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7832
7833 cx.assert_editor_state(indoc! {"
7834 fn a() {
7835 «b();
7836 c();
7837 ˇ» d();
7838 }
7839 "});
7840
7841 // The comment prefix is inserted at the same column for every line in a
7842 // selection.
7843 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7844
7845 cx.assert_editor_state(indoc! {"
7846 fn a() {
7847 // «b();
7848 // c();
7849 ˇ»// d();
7850 }
7851 "});
7852
7853 // If a selection ends at the beginning of a line, that line is not toggled.
7854 cx.set_selections_state(indoc! {"
7855 fn a() {
7856 // b();
7857 «// c();
7858 ˇ» // d();
7859 }
7860 "});
7861
7862 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7863
7864 cx.assert_editor_state(indoc! {"
7865 fn a() {
7866 // b();
7867 «c();
7868 ˇ» // d();
7869 }
7870 "});
7871
7872 // If a selection span a single line and is empty, the line is toggled.
7873 cx.set_state(indoc! {"
7874 fn a() {
7875 a();
7876 b();
7877 ˇ
7878 }
7879 "});
7880
7881 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7882
7883 cx.assert_editor_state(indoc! {"
7884 fn a() {
7885 a();
7886 b();
7887 //•ˇ
7888 }
7889 "});
7890
7891 // If a selection span multiple lines, empty lines are not toggled.
7892 cx.set_state(indoc! {"
7893 fn a() {
7894 «a();
7895
7896 c();ˇ»
7897 }
7898 "});
7899
7900 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7901
7902 cx.assert_editor_state(indoc! {"
7903 fn a() {
7904 // «a();
7905
7906 // c();ˇ»
7907 }
7908 "});
7909
7910 // If a selection includes multiple comment prefixes, all lines are uncommented.
7911 cx.set_state(indoc! {"
7912 fn a() {
7913 «// a();
7914 /// b();
7915 //! c();ˇ»
7916 }
7917 "});
7918
7919 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7920
7921 cx.assert_editor_state(indoc! {"
7922 fn a() {
7923 «a();
7924 b();
7925 c();ˇ»
7926 }
7927 "});
7928}
7929
7930#[gpui::test]
7931async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
7932 init_test(cx, |_| {});
7933
7934 let language = Arc::new(Language::new(
7935 LanguageConfig {
7936 line_comments: vec!["// ".into()],
7937 ..Default::default()
7938 },
7939 Some(tree_sitter_rust::language()),
7940 ));
7941
7942 let mut cx = EditorTestContext::new(cx).await;
7943
7944 cx.language_registry().add(language.clone());
7945 cx.update_buffer(|buffer, cx| {
7946 buffer.set_language(Some(language), cx);
7947 });
7948
7949 let toggle_comments = &ToggleComments {
7950 advance_downwards: true,
7951 };
7952
7953 // Single cursor on one line -> advance
7954 // Cursor moves horizontally 3 characters as well on non-blank line
7955 cx.set_state(indoc!(
7956 "fn a() {
7957 ˇdog();
7958 cat();
7959 }"
7960 ));
7961 cx.update_editor(|editor, cx| {
7962 editor.toggle_comments(toggle_comments, cx);
7963 });
7964 cx.assert_editor_state(indoc!(
7965 "fn a() {
7966 // dog();
7967 catˇ();
7968 }"
7969 ));
7970
7971 // Single selection on one line -> don't advance
7972 cx.set_state(indoc!(
7973 "fn a() {
7974 «dog()ˇ»;
7975 cat();
7976 }"
7977 ));
7978 cx.update_editor(|editor, cx| {
7979 editor.toggle_comments(toggle_comments, cx);
7980 });
7981 cx.assert_editor_state(indoc!(
7982 "fn a() {
7983 // «dog()ˇ»;
7984 cat();
7985 }"
7986 ));
7987
7988 // Multiple cursors on one line -> advance
7989 cx.set_state(indoc!(
7990 "fn a() {
7991 ˇdˇog();
7992 cat();
7993 }"
7994 ));
7995 cx.update_editor(|editor, cx| {
7996 editor.toggle_comments(toggle_comments, cx);
7997 });
7998 cx.assert_editor_state(indoc!(
7999 "fn a() {
8000 // dog();
8001 catˇ(ˇ);
8002 }"
8003 ));
8004
8005 // Multiple cursors on one line, with selection -> don't advance
8006 cx.set_state(indoc!(
8007 "fn a() {
8008 ˇdˇog«()ˇ»;
8009 cat();
8010 }"
8011 ));
8012 cx.update_editor(|editor, cx| {
8013 editor.toggle_comments(toggle_comments, cx);
8014 });
8015 cx.assert_editor_state(indoc!(
8016 "fn a() {
8017 // ˇdˇog«()ˇ»;
8018 cat();
8019 }"
8020 ));
8021
8022 // Single cursor on one line -> advance
8023 // Cursor moves to column 0 on blank line
8024 cx.set_state(indoc!(
8025 "fn a() {
8026 ˇdog();
8027
8028 cat();
8029 }"
8030 ));
8031 cx.update_editor(|editor, cx| {
8032 editor.toggle_comments(toggle_comments, cx);
8033 });
8034 cx.assert_editor_state(indoc!(
8035 "fn a() {
8036 // dog();
8037 ˇ
8038 cat();
8039 }"
8040 ));
8041
8042 // Single cursor on one line -> advance
8043 // Cursor starts and ends at column 0
8044 cx.set_state(indoc!(
8045 "fn a() {
8046 ˇ dog();
8047 cat();
8048 }"
8049 ));
8050 cx.update_editor(|editor, cx| {
8051 editor.toggle_comments(toggle_comments, cx);
8052 });
8053 cx.assert_editor_state(indoc!(
8054 "fn a() {
8055 // dog();
8056 ˇ cat();
8057 }"
8058 ));
8059}
8060
8061#[gpui::test]
8062async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
8063 init_test(cx, |_| {});
8064
8065 let mut cx = EditorTestContext::new(cx).await;
8066
8067 let html_language = Arc::new(
8068 Language::new(
8069 LanguageConfig {
8070 name: "HTML".into(),
8071 block_comment: Some(("<!-- ".into(), " -->".into())),
8072 ..Default::default()
8073 },
8074 Some(tree_sitter_html::language()),
8075 )
8076 .with_injection_query(
8077 r#"
8078 (script_element
8079 (raw_text) @content
8080 (#set! "language" "javascript"))
8081 "#,
8082 )
8083 .unwrap(),
8084 );
8085
8086 let javascript_language = Arc::new(Language::new(
8087 LanguageConfig {
8088 name: "JavaScript".into(),
8089 line_comments: vec!["// ".into()],
8090 ..Default::default()
8091 },
8092 Some(tree_sitter_typescript::language_tsx()),
8093 ));
8094
8095 cx.language_registry().add(html_language.clone());
8096 cx.language_registry().add(javascript_language.clone());
8097 cx.update_buffer(|buffer, cx| {
8098 buffer.set_language(Some(html_language), cx);
8099 });
8100
8101 // Toggle comments for empty selections
8102 cx.set_state(
8103 &r#"
8104 <p>A</p>ˇ
8105 <p>B</p>ˇ
8106 <p>C</p>ˇ
8107 "#
8108 .unindent(),
8109 );
8110 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8111 cx.assert_editor_state(
8112 &r#"
8113 <!-- <p>A</p>ˇ -->
8114 <!-- <p>B</p>ˇ -->
8115 <!-- <p>C</p>ˇ -->
8116 "#
8117 .unindent(),
8118 );
8119 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8120 cx.assert_editor_state(
8121 &r#"
8122 <p>A</p>ˇ
8123 <p>B</p>ˇ
8124 <p>C</p>ˇ
8125 "#
8126 .unindent(),
8127 );
8128
8129 // Toggle comments for mixture of empty and non-empty selections, where
8130 // multiple selections occupy a given line.
8131 cx.set_state(
8132 &r#"
8133 <p>A«</p>
8134 <p>ˇ»B</p>ˇ
8135 <p>C«</p>
8136 <p>ˇ»D</p>ˇ
8137 "#
8138 .unindent(),
8139 );
8140
8141 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8142 cx.assert_editor_state(
8143 &r#"
8144 <!-- <p>A«</p>
8145 <p>ˇ»B</p>ˇ -->
8146 <!-- <p>C«</p>
8147 <p>ˇ»D</p>ˇ -->
8148 "#
8149 .unindent(),
8150 );
8151 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8152 cx.assert_editor_state(
8153 &r#"
8154 <p>A«</p>
8155 <p>ˇ»B</p>ˇ
8156 <p>C«</p>
8157 <p>ˇ»D</p>ˇ
8158 "#
8159 .unindent(),
8160 );
8161
8162 // Toggle comments when different languages are active for different
8163 // selections.
8164 cx.set_state(
8165 &r#"
8166 ˇ<script>
8167 ˇvar x = new Y();
8168 ˇ</script>
8169 "#
8170 .unindent(),
8171 );
8172 cx.executor().run_until_parked();
8173 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8174 cx.assert_editor_state(
8175 &r#"
8176 <!-- ˇ<script> -->
8177 // ˇvar x = new Y();
8178 <!-- ˇ</script> -->
8179 "#
8180 .unindent(),
8181 );
8182}
8183
8184#[gpui::test]
8185fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
8186 init_test(cx, |_| {});
8187
8188 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8189 let multibuffer = cx.new_model(|cx| {
8190 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8191 multibuffer.push_excerpts(
8192 buffer.clone(),
8193 [
8194 ExcerptRange {
8195 context: Point::new(0, 0)..Point::new(0, 4),
8196 primary: None,
8197 },
8198 ExcerptRange {
8199 context: Point::new(1, 0)..Point::new(1, 4),
8200 primary: None,
8201 },
8202 ],
8203 cx,
8204 );
8205 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
8206 multibuffer
8207 });
8208
8209 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8210 _ = view.update(cx, |view, cx| {
8211 assert_eq!(view.text(cx), "aaaa\nbbbb");
8212 view.change_selections(None, cx, |s| {
8213 s.select_ranges([
8214 Point::new(0, 0)..Point::new(0, 0),
8215 Point::new(1, 0)..Point::new(1, 0),
8216 ])
8217 });
8218
8219 view.handle_input("X", cx);
8220 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
8221 assert_eq!(
8222 view.selections.ranges(cx),
8223 [
8224 Point::new(0, 1)..Point::new(0, 1),
8225 Point::new(1, 1)..Point::new(1, 1),
8226 ]
8227 );
8228
8229 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
8230 view.change_selections(None, cx, |s| {
8231 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
8232 });
8233 view.backspace(&Default::default(), cx);
8234 assert_eq!(view.text(cx), "Xa\nbbb");
8235 assert_eq!(
8236 view.selections.ranges(cx),
8237 [Point::new(1, 0)..Point::new(1, 0)]
8238 );
8239
8240 view.change_selections(None, cx, |s| {
8241 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
8242 });
8243 view.backspace(&Default::default(), cx);
8244 assert_eq!(view.text(cx), "X\nbb");
8245 assert_eq!(
8246 view.selections.ranges(cx),
8247 [Point::new(0, 1)..Point::new(0, 1)]
8248 );
8249 });
8250}
8251
8252#[gpui::test]
8253fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
8254 init_test(cx, |_| {});
8255
8256 let markers = vec![('[', ']').into(), ('(', ')').into()];
8257 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
8258 indoc! {"
8259 [aaaa
8260 (bbbb]
8261 cccc)",
8262 },
8263 markers.clone(),
8264 );
8265 let excerpt_ranges = markers.into_iter().map(|marker| {
8266 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
8267 ExcerptRange {
8268 context,
8269 primary: None,
8270 }
8271 });
8272 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
8273 let multibuffer = cx.new_model(|cx| {
8274 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8275 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
8276 multibuffer
8277 });
8278
8279 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8280 _ = view.update(cx, |view, cx| {
8281 let (expected_text, selection_ranges) = marked_text_ranges(
8282 indoc! {"
8283 aaaa
8284 bˇbbb
8285 bˇbbˇb
8286 cccc"
8287 },
8288 true,
8289 );
8290 assert_eq!(view.text(cx), expected_text);
8291 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
8292
8293 view.handle_input("X", cx);
8294
8295 let (expected_text, expected_selections) = marked_text_ranges(
8296 indoc! {"
8297 aaaa
8298 bXˇbbXb
8299 bXˇbbXˇb
8300 cccc"
8301 },
8302 false,
8303 );
8304 assert_eq!(view.text(cx), expected_text);
8305 assert_eq!(view.selections.ranges(cx), expected_selections);
8306
8307 view.newline(&Newline, cx);
8308 let (expected_text, expected_selections) = marked_text_ranges(
8309 indoc! {"
8310 aaaa
8311 bX
8312 ˇbbX
8313 b
8314 bX
8315 ˇbbX
8316 ˇb
8317 cccc"
8318 },
8319 false,
8320 );
8321 assert_eq!(view.text(cx), expected_text);
8322 assert_eq!(view.selections.ranges(cx), expected_selections);
8323 });
8324}
8325
8326#[gpui::test]
8327fn test_refresh_selections(cx: &mut TestAppContext) {
8328 init_test(cx, |_| {});
8329
8330 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8331 let mut excerpt1_id = None;
8332 let multibuffer = cx.new_model(|cx| {
8333 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8334 excerpt1_id = multibuffer
8335 .push_excerpts(
8336 buffer.clone(),
8337 [
8338 ExcerptRange {
8339 context: Point::new(0, 0)..Point::new(1, 4),
8340 primary: None,
8341 },
8342 ExcerptRange {
8343 context: Point::new(1, 0)..Point::new(2, 4),
8344 primary: None,
8345 },
8346 ],
8347 cx,
8348 )
8349 .into_iter()
8350 .next();
8351 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8352 multibuffer
8353 });
8354
8355 let editor = cx.add_window(|cx| {
8356 let mut editor = build_editor(multibuffer.clone(), cx);
8357 let snapshot = editor.snapshot(cx);
8358 editor.change_selections(None, cx, |s| {
8359 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
8360 });
8361 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
8362 assert_eq!(
8363 editor.selections.ranges(cx),
8364 [
8365 Point::new(1, 3)..Point::new(1, 3),
8366 Point::new(2, 1)..Point::new(2, 1),
8367 ]
8368 );
8369 editor
8370 });
8371
8372 // Refreshing selections is a no-op when excerpts haven't changed.
8373 _ = editor.update(cx, |editor, cx| {
8374 editor.change_selections(None, cx, |s| s.refresh());
8375 assert_eq!(
8376 editor.selections.ranges(cx),
8377 [
8378 Point::new(1, 3)..Point::new(1, 3),
8379 Point::new(2, 1)..Point::new(2, 1),
8380 ]
8381 );
8382 });
8383
8384 _ = multibuffer.update(cx, |multibuffer, cx| {
8385 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8386 });
8387 _ = editor.update(cx, |editor, cx| {
8388 // Removing an excerpt causes the first selection to become degenerate.
8389 assert_eq!(
8390 editor.selections.ranges(cx),
8391 [
8392 Point::new(0, 0)..Point::new(0, 0),
8393 Point::new(0, 1)..Point::new(0, 1)
8394 ]
8395 );
8396
8397 // Refreshing selections will relocate the first selection to the original buffer
8398 // location.
8399 editor.change_selections(None, cx, |s| s.refresh());
8400 assert_eq!(
8401 editor.selections.ranges(cx),
8402 [
8403 Point::new(0, 1)..Point::new(0, 1),
8404 Point::new(0, 3)..Point::new(0, 3)
8405 ]
8406 );
8407 assert!(editor.selections.pending_anchor().is_some());
8408 });
8409}
8410
8411#[gpui::test]
8412fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
8413 init_test(cx, |_| {});
8414
8415 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8416 let mut excerpt1_id = None;
8417 let multibuffer = cx.new_model(|cx| {
8418 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8419 excerpt1_id = multibuffer
8420 .push_excerpts(
8421 buffer.clone(),
8422 [
8423 ExcerptRange {
8424 context: Point::new(0, 0)..Point::new(1, 4),
8425 primary: None,
8426 },
8427 ExcerptRange {
8428 context: Point::new(1, 0)..Point::new(2, 4),
8429 primary: None,
8430 },
8431 ],
8432 cx,
8433 )
8434 .into_iter()
8435 .next();
8436 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8437 multibuffer
8438 });
8439
8440 let editor = cx.add_window(|cx| {
8441 let mut editor = build_editor(multibuffer.clone(), cx);
8442 let snapshot = editor.snapshot(cx);
8443 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
8444 assert_eq!(
8445 editor.selections.ranges(cx),
8446 [Point::new(1, 3)..Point::new(1, 3)]
8447 );
8448 editor
8449 });
8450
8451 _ = multibuffer.update(cx, |multibuffer, cx| {
8452 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8453 });
8454 _ = editor.update(cx, |editor, cx| {
8455 assert_eq!(
8456 editor.selections.ranges(cx),
8457 [Point::new(0, 0)..Point::new(0, 0)]
8458 );
8459
8460 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
8461 editor.change_selections(None, cx, |s| s.refresh());
8462 assert_eq!(
8463 editor.selections.ranges(cx),
8464 [Point::new(0, 3)..Point::new(0, 3)]
8465 );
8466 assert!(editor.selections.pending_anchor().is_some());
8467 });
8468}
8469
8470#[gpui::test]
8471async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
8472 init_test(cx, |_| {});
8473
8474 let language = Arc::new(
8475 Language::new(
8476 LanguageConfig {
8477 brackets: BracketPairConfig {
8478 pairs: vec![
8479 BracketPair {
8480 start: "{".to_string(),
8481 end: "}".to_string(),
8482 close: true,
8483 surround: true,
8484 newline: true,
8485 },
8486 BracketPair {
8487 start: "/* ".to_string(),
8488 end: " */".to_string(),
8489 close: true,
8490 surround: true,
8491 newline: true,
8492 },
8493 ],
8494 ..Default::default()
8495 },
8496 ..Default::default()
8497 },
8498 Some(tree_sitter_rust::language()),
8499 )
8500 .with_indents_query("")
8501 .unwrap(),
8502 );
8503
8504 let text = concat!(
8505 "{ }\n", //
8506 " x\n", //
8507 " /* */\n", //
8508 "x\n", //
8509 "{{} }\n", //
8510 );
8511
8512 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
8513 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
8514 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
8515 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
8516 .await;
8517
8518 _ = view.update(cx, |view, cx| {
8519 view.change_selections(None, cx, |s| {
8520 s.select_display_ranges([
8521 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
8522 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
8523 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
8524 ])
8525 });
8526 view.newline(&Newline, cx);
8527
8528 assert_eq!(
8529 view.buffer().read(cx).read(cx).text(),
8530 concat!(
8531 "{ \n", // Suppress rustfmt
8532 "\n", //
8533 "}\n", //
8534 " x\n", //
8535 " /* \n", //
8536 " \n", //
8537 " */\n", //
8538 "x\n", //
8539 "{{} \n", //
8540 "}\n", //
8541 )
8542 );
8543 });
8544}
8545
8546#[gpui::test]
8547fn test_highlighted_ranges(cx: &mut TestAppContext) {
8548 init_test(cx, |_| {});
8549
8550 let editor = cx.add_window(|cx| {
8551 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
8552 build_editor(buffer.clone(), cx)
8553 });
8554
8555 _ = editor.update(cx, |editor, cx| {
8556 struct Type1;
8557 struct Type2;
8558
8559 let buffer = editor.buffer.read(cx).snapshot(cx);
8560
8561 let anchor_range =
8562 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
8563
8564 editor.highlight_background::<Type1>(
8565 &[
8566 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
8567 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
8568 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
8569 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
8570 ],
8571 |_| Hsla::red(),
8572 cx,
8573 );
8574 editor.highlight_background::<Type2>(
8575 &[
8576 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
8577 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
8578 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
8579 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
8580 ],
8581 |_| Hsla::green(),
8582 cx,
8583 );
8584
8585 let snapshot = editor.snapshot(cx);
8586 let mut highlighted_ranges = editor.background_highlights_in_range(
8587 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
8588 &snapshot,
8589 cx.theme().colors(),
8590 );
8591 // Enforce a consistent ordering based on color without relying on the ordering of the
8592 // highlight's `TypeId` which is non-executor.
8593 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
8594 assert_eq!(
8595 highlighted_ranges,
8596 &[
8597 (
8598 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
8599 Hsla::red(),
8600 ),
8601 (
8602 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
8603 Hsla::red(),
8604 ),
8605 (
8606 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
8607 Hsla::green(),
8608 ),
8609 (
8610 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
8611 Hsla::green(),
8612 ),
8613 ]
8614 );
8615 assert_eq!(
8616 editor.background_highlights_in_range(
8617 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
8618 &snapshot,
8619 cx.theme().colors(),
8620 ),
8621 &[(
8622 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
8623 Hsla::red(),
8624 )]
8625 );
8626 });
8627}
8628
8629#[gpui::test]
8630async fn test_following(cx: &mut gpui::TestAppContext) {
8631 init_test(cx, |_| {});
8632
8633 let fs = FakeFs::new(cx.executor());
8634 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8635
8636 let buffer = project.update(cx, |project, cx| {
8637 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
8638 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
8639 });
8640 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
8641 let follower = cx.update(|cx| {
8642 cx.open_window(
8643 WindowOptions {
8644 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
8645 gpui::Point::new(px(0.), px(0.)),
8646 gpui::Point::new(px(10.), px(80.)),
8647 ))),
8648 ..Default::default()
8649 },
8650 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
8651 )
8652 .unwrap()
8653 });
8654
8655 let is_still_following = Rc::new(RefCell::new(true));
8656 let follower_edit_event_count = Rc::new(RefCell::new(0));
8657 let pending_update = Rc::new(RefCell::new(None));
8658 _ = follower.update(cx, {
8659 let update = pending_update.clone();
8660 let is_still_following = is_still_following.clone();
8661 let follower_edit_event_count = follower_edit_event_count.clone();
8662 |_, cx| {
8663 cx.subscribe(
8664 &leader.root_view(cx).unwrap(),
8665 move |_, leader, event, cx| {
8666 leader
8667 .read(cx)
8668 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
8669 },
8670 )
8671 .detach();
8672
8673 cx.subscribe(
8674 &follower.root_view(cx).unwrap(),
8675 move |_, _, event: &EditorEvent, _cx| {
8676 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
8677 *is_still_following.borrow_mut() = false;
8678 }
8679
8680 if let EditorEvent::BufferEdited = event {
8681 *follower_edit_event_count.borrow_mut() += 1;
8682 }
8683 },
8684 )
8685 .detach();
8686 }
8687 });
8688
8689 // Update the selections only
8690 _ = leader.update(cx, |leader, cx| {
8691 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
8692 });
8693 follower
8694 .update(cx, |follower, cx| {
8695 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8696 })
8697 .unwrap()
8698 .await
8699 .unwrap();
8700 _ = follower.update(cx, |follower, cx| {
8701 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
8702 });
8703 assert_eq!(*is_still_following.borrow(), true);
8704 assert_eq!(*follower_edit_event_count.borrow(), 0);
8705
8706 // Update the scroll position only
8707 _ = leader.update(cx, |leader, cx| {
8708 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
8709 });
8710 follower
8711 .update(cx, |follower, cx| {
8712 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8713 })
8714 .unwrap()
8715 .await
8716 .unwrap();
8717 assert_eq!(
8718 follower
8719 .update(cx, |follower, cx| follower.scroll_position(cx))
8720 .unwrap(),
8721 gpui::Point::new(1.5, 3.5)
8722 );
8723 assert_eq!(*is_still_following.borrow(), true);
8724 assert_eq!(*follower_edit_event_count.borrow(), 0);
8725
8726 // Update the selections and scroll position. The follower's scroll position is updated
8727 // via autoscroll, not via the leader's exact scroll position.
8728 _ = leader.update(cx, |leader, cx| {
8729 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
8730 leader.request_autoscroll(Autoscroll::newest(), cx);
8731 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
8732 });
8733 follower
8734 .update(cx, |follower, cx| {
8735 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8736 })
8737 .unwrap()
8738 .await
8739 .unwrap();
8740 _ = follower.update(cx, |follower, cx| {
8741 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
8742 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
8743 });
8744 assert_eq!(*is_still_following.borrow(), true);
8745
8746 // Creating a pending selection that precedes another selection
8747 _ = leader.update(cx, |leader, cx| {
8748 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
8749 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
8750 });
8751 follower
8752 .update(cx, |follower, cx| {
8753 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8754 })
8755 .unwrap()
8756 .await
8757 .unwrap();
8758 _ = follower.update(cx, |follower, cx| {
8759 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
8760 });
8761 assert_eq!(*is_still_following.borrow(), true);
8762
8763 // Extend the pending selection so that it surrounds another selection
8764 _ = leader.update(cx, |leader, cx| {
8765 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
8766 });
8767 follower
8768 .update(cx, |follower, cx| {
8769 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8770 })
8771 .unwrap()
8772 .await
8773 .unwrap();
8774 _ = follower.update(cx, |follower, cx| {
8775 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
8776 });
8777
8778 // Scrolling locally breaks the follow
8779 _ = follower.update(cx, |follower, cx| {
8780 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
8781 follower.set_scroll_anchor(
8782 ScrollAnchor {
8783 anchor: top_anchor,
8784 offset: gpui::Point::new(0.0, 0.5),
8785 },
8786 cx,
8787 );
8788 });
8789 assert_eq!(*is_still_following.borrow(), false);
8790}
8791
8792#[gpui::test]
8793async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
8794 init_test(cx, |_| {});
8795
8796 let fs = FakeFs::new(cx.executor());
8797 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8798 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8799 let pane = workspace
8800 .update(cx, |workspace, _| workspace.active_pane().clone())
8801 .unwrap();
8802
8803 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
8804
8805 let leader = pane.update(cx, |_, cx| {
8806 let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
8807 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
8808 });
8809
8810 // Start following the editor when it has no excerpts.
8811 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
8812 let follower_1 = cx
8813 .update_window(*workspace.deref(), |_, cx| {
8814 Editor::from_state_proto(
8815 workspace.root_view(cx).unwrap(),
8816 ViewId {
8817 creator: Default::default(),
8818 id: 0,
8819 },
8820 &mut state_message,
8821 cx,
8822 )
8823 })
8824 .unwrap()
8825 .unwrap()
8826 .await
8827 .unwrap();
8828
8829 let update_message = Rc::new(RefCell::new(None));
8830 follower_1.update(cx, {
8831 let update = update_message.clone();
8832 |_, cx| {
8833 cx.subscribe(&leader, move |_, leader, event, cx| {
8834 leader
8835 .read(cx)
8836 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
8837 })
8838 .detach();
8839 }
8840 });
8841
8842 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
8843 (
8844 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
8845 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
8846 )
8847 });
8848
8849 // Insert some excerpts.
8850 _ = leader.update(cx, |leader, cx| {
8851 leader.buffer.update(cx, |multibuffer, cx| {
8852 let excerpt_ids = multibuffer.push_excerpts(
8853 buffer_1.clone(),
8854 [
8855 ExcerptRange {
8856 context: 1..6,
8857 primary: None,
8858 },
8859 ExcerptRange {
8860 context: 12..15,
8861 primary: None,
8862 },
8863 ExcerptRange {
8864 context: 0..3,
8865 primary: None,
8866 },
8867 ],
8868 cx,
8869 );
8870 multibuffer.insert_excerpts_after(
8871 excerpt_ids[0],
8872 buffer_2.clone(),
8873 [
8874 ExcerptRange {
8875 context: 8..12,
8876 primary: None,
8877 },
8878 ExcerptRange {
8879 context: 0..6,
8880 primary: None,
8881 },
8882 ],
8883 cx,
8884 );
8885 });
8886 });
8887
8888 // Apply the update of adding the excerpts.
8889 follower_1
8890 .update(cx, |follower, cx| {
8891 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
8892 })
8893 .await
8894 .unwrap();
8895 assert_eq!(
8896 follower_1.update(cx, |editor, cx| editor.text(cx)),
8897 leader.update(cx, |editor, cx| editor.text(cx))
8898 );
8899 update_message.borrow_mut().take();
8900
8901 // Start following separately after it already has excerpts.
8902 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
8903 let follower_2 = cx
8904 .update_window(*workspace.deref(), |_, cx| {
8905 Editor::from_state_proto(
8906 workspace.root_view(cx).unwrap().clone(),
8907 ViewId {
8908 creator: Default::default(),
8909 id: 0,
8910 },
8911 &mut state_message,
8912 cx,
8913 )
8914 })
8915 .unwrap()
8916 .unwrap()
8917 .await
8918 .unwrap();
8919 assert_eq!(
8920 follower_2.update(cx, |editor, cx| editor.text(cx)),
8921 leader.update(cx, |editor, cx| editor.text(cx))
8922 );
8923
8924 // Remove some excerpts.
8925 _ = leader.update(cx, |leader, cx| {
8926 leader.buffer.update(cx, |multibuffer, cx| {
8927 let excerpt_ids = multibuffer.excerpt_ids();
8928 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
8929 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
8930 });
8931 });
8932
8933 // Apply the update of removing the excerpts.
8934 follower_1
8935 .update(cx, |follower, cx| {
8936 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
8937 })
8938 .await
8939 .unwrap();
8940 follower_2
8941 .update(cx, |follower, cx| {
8942 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
8943 })
8944 .await
8945 .unwrap();
8946 update_message.borrow_mut().take();
8947 assert_eq!(
8948 follower_1.update(cx, |editor, cx| editor.text(cx)),
8949 leader.update(cx, |editor, cx| editor.text(cx))
8950 );
8951}
8952
8953#[gpui::test]
8954async fn go_to_prev_overlapping_diagnostic(
8955 executor: BackgroundExecutor,
8956 cx: &mut gpui::TestAppContext,
8957) {
8958 init_test(cx, |_| {});
8959
8960 let mut cx = EditorTestContext::new(cx).await;
8961 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
8962
8963 cx.set_state(indoc! {"
8964 ˇfn func(abc def: i32) -> u32 {
8965 }
8966 "});
8967
8968 _ = cx.update(|cx| {
8969 _ = project.update(cx, |project, cx| {
8970 project
8971 .update_diagnostics(
8972 LanguageServerId(0),
8973 lsp::PublishDiagnosticsParams {
8974 uri: lsp::Url::from_file_path("/root/file").unwrap(),
8975 version: None,
8976 diagnostics: vec![
8977 lsp::Diagnostic {
8978 range: lsp::Range::new(
8979 lsp::Position::new(0, 11),
8980 lsp::Position::new(0, 12),
8981 ),
8982 severity: Some(lsp::DiagnosticSeverity::ERROR),
8983 ..Default::default()
8984 },
8985 lsp::Diagnostic {
8986 range: lsp::Range::new(
8987 lsp::Position::new(0, 12),
8988 lsp::Position::new(0, 15),
8989 ),
8990 severity: Some(lsp::DiagnosticSeverity::ERROR),
8991 ..Default::default()
8992 },
8993 lsp::Diagnostic {
8994 range: lsp::Range::new(
8995 lsp::Position::new(0, 25),
8996 lsp::Position::new(0, 28),
8997 ),
8998 severity: Some(lsp::DiagnosticSeverity::ERROR),
8999 ..Default::default()
9000 },
9001 ],
9002 },
9003 &[],
9004 cx,
9005 )
9006 .unwrap()
9007 });
9008 });
9009
9010 executor.run_until_parked();
9011
9012 cx.update_editor(|editor, cx| {
9013 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9014 });
9015
9016 cx.assert_editor_state(indoc! {"
9017 fn func(abc def: i32) -> ˇu32 {
9018 }
9019 "});
9020
9021 cx.update_editor(|editor, cx| {
9022 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9023 });
9024
9025 cx.assert_editor_state(indoc! {"
9026 fn func(abc ˇdef: i32) -> u32 {
9027 }
9028 "});
9029
9030 cx.update_editor(|editor, cx| {
9031 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9032 });
9033
9034 cx.assert_editor_state(indoc! {"
9035 fn func(abcˇ def: i32) -> u32 {
9036 }
9037 "});
9038
9039 cx.update_editor(|editor, cx| {
9040 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9041 });
9042
9043 cx.assert_editor_state(indoc! {"
9044 fn func(abc def: i32) -> ˇu32 {
9045 }
9046 "});
9047}
9048
9049#[gpui::test]
9050async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9051 init_test(cx, |_| {});
9052
9053 let mut cx = EditorTestContext::new(cx).await;
9054
9055 let diff_base = r#"
9056 use some::mod;
9057
9058 const A: u32 = 42;
9059
9060 fn main() {
9061 println!("hello");
9062
9063 println!("world");
9064 }
9065 "#
9066 .unindent();
9067
9068 // Edits are modified, removed, modified, added
9069 cx.set_state(
9070 &r#"
9071 use some::modified;
9072
9073 ˇ
9074 fn main() {
9075 println!("hello there");
9076
9077 println!("around the");
9078 println!("world");
9079 }
9080 "#
9081 .unindent(),
9082 );
9083
9084 cx.set_diff_base(Some(&diff_base));
9085 executor.run_until_parked();
9086
9087 cx.update_editor(|editor, cx| {
9088 //Wrap around the bottom of the buffer
9089 for _ in 0..3 {
9090 editor.go_to_hunk(&GoToHunk, cx);
9091 }
9092 });
9093
9094 cx.assert_editor_state(
9095 &r#"
9096 ˇuse some::modified;
9097
9098
9099 fn main() {
9100 println!("hello there");
9101
9102 println!("around the");
9103 println!("world");
9104 }
9105 "#
9106 .unindent(),
9107 );
9108
9109 cx.update_editor(|editor, cx| {
9110 //Wrap around the top of the buffer
9111 for _ in 0..2 {
9112 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9113 }
9114 });
9115
9116 cx.assert_editor_state(
9117 &r#"
9118 use some::modified;
9119
9120
9121 fn main() {
9122 ˇ println!("hello there");
9123
9124 println!("around the");
9125 println!("world");
9126 }
9127 "#
9128 .unindent(),
9129 );
9130
9131 cx.update_editor(|editor, cx| {
9132 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9133 });
9134
9135 cx.assert_editor_state(
9136 &r#"
9137 use some::modified;
9138
9139 ˇ
9140 fn main() {
9141 println!("hello there");
9142
9143 println!("around the");
9144 println!("world");
9145 }
9146 "#
9147 .unindent(),
9148 );
9149
9150 cx.update_editor(|editor, cx| {
9151 for _ in 0..3 {
9152 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9153 }
9154 });
9155
9156 cx.assert_editor_state(
9157 &r#"
9158 use some::modified;
9159
9160
9161 fn main() {
9162 ˇ println!("hello there");
9163
9164 println!("around the");
9165 println!("world");
9166 }
9167 "#
9168 .unindent(),
9169 );
9170
9171 cx.update_editor(|editor, cx| {
9172 editor.fold(&Fold, cx);
9173
9174 //Make sure that the fold only gets one hunk
9175 for _ in 0..4 {
9176 editor.go_to_hunk(&GoToHunk, cx);
9177 }
9178 });
9179
9180 cx.assert_editor_state(
9181 &r#"
9182 ˇuse some::modified;
9183
9184
9185 fn main() {
9186 println!("hello there");
9187
9188 println!("around the");
9189 println!("world");
9190 }
9191 "#
9192 .unindent(),
9193 );
9194}
9195
9196#[test]
9197fn test_split_words() {
9198 fn split(text: &str) -> Vec<&str> {
9199 split_words(text).collect()
9200 }
9201
9202 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
9203 assert_eq!(split("hello_world"), &["hello_", "world"]);
9204 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
9205 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
9206 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
9207 assert_eq!(split("helloworld"), &["helloworld"]);
9208
9209 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
9210}
9211
9212#[gpui::test]
9213async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
9214 init_test(cx, |_| {});
9215
9216 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
9217 let mut assert = |before, after| {
9218 let _state_context = cx.set_state(before);
9219 cx.update_editor(|editor, cx| {
9220 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
9221 });
9222 cx.assert_editor_state(after);
9223 };
9224
9225 // Outside bracket jumps to outside of matching bracket
9226 assert("console.logˇ(var);", "console.log(var)ˇ;");
9227 assert("console.log(var)ˇ;", "console.logˇ(var);");
9228
9229 // Inside bracket jumps to inside of matching bracket
9230 assert("console.log(ˇvar);", "console.log(varˇ);");
9231 assert("console.log(varˇ);", "console.log(ˇvar);");
9232
9233 // When outside a bracket and inside, favor jumping to the inside bracket
9234 assert(
9235 "console.log('foo', [1, 2, 3]ˇ);",
9236 "console.log(ˇ'foo', [1, 2, 3]);",
9237 );
9238 assert(
9239 "console.log(ˇ'foo', [1, 2, 3]);",
9240 "console.log('foo', [1, 2, 3]ˇ);",
9241 );
9242
9243 // Bias forward if two options are equally likely
9244 assert(
9245 "let result = curried_fun()ˇ();",
9246 "let result = curried_fun()()ˇ;",
9247 );
9248
9249 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
9250 assert(
9251 indoc! {"
9252 function test() {
9253 console.log('test')ˇ
9254 }"},
9255 indoc! {"
9256 function test() {
9257 console.logˇ('test')
9258 }"},
9259 );
9260}
9261
9262#[gpui::test]
9263async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
9264 init_test(cx, |_| {});
9265
9266 let fs = FakeFs::new(cx.executor());
9267 fs.insert_tree(
9268 "/a",
9269 json!({
9270 "main.rs": "fn main() { let a = 5; }",
9271 "other.rs": "// Test file",
9272 }),
9273 )
9274 .await;
9275 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9276
9277 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9278 language_registry.add(Arc::new(Language::new(
9279 LanguageConfig {
9280 name: "Rust".into(),
9281 matcher: LanguageMatcher {
9282 path_suffixes: vec!["rs".to_string()],
9283 ..Default::default()
9284 },
9285 brackets: BracketPairConfig {
9286 pairs: vec![BracketPair {
9287 start: "{".to_string(),
9288 end: "}".to_string(),
9289 close: true,
9290 surround: true,
9291 newline: true,
9292 }],
9293 disabled_scopes_by_bracket_ix: Vec::new(),
9294 },
9295 ..Default::default()
9296 },
9297 Some(tree_sitter_rust::language()),
9298 )));
9299 let mut fake_servers = language_registry.register_fake_lsp_adapter(
9300 "Rust",
9301 FakeLspAdapter {
9302 capabilities: lsp::ServerCapabilities {
9303 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
9304 first_trigger_character: "{".to_string(),
9305 more_trigger_character: None,
9306 }),
9307 ..Default::default()
9308 },
9309 ..Default::default()
9310 },
9311 );
9312
9313 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9314
9315 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9316
9317 let worktree_id = workspace
9318 .update(cx, |workspace, cx| {
9319 workspace.project().update(cx, |project, cx| {
9320 project.worktrees().next().unwrap().read(cx).id()
9321 })
9322 })
9323 .unwrap();
9324
9325 let buffer = project
9326 .update(cx, |project, cx| {
9327 project.open_local_buffer("/a/main.rs", cx)
9328 })
9329 .await
9330 .unwrap();
9331 cx.executor().run_until_parked();
9332 cx.executor().start_waiting();
9333 let fake_server = fake_servers.next().await.unwrap();
9334 let editor_handle = workspace
9335 .update(cx, |workspace, cx| {
9336 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
9337 })
9338 .unwrap()
9339 .await
9340 .unwrap()
9341 .downcast::<Editor>()
9342 .unwrap();
9343
9344 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
9345 assert_eq!(
9346 params.text_document_position.text_document.uri,
9347 lsp::Url::from_file_path("/a/main.rs").unwrap(),
9348 );
9349 assert_eq!(
9350 params.text_document_position.position,
9351 lsp::Position::new(0, 21),
9352 );
9353
9354 Ok(Some(vec![lsp::TextEdit {
9355 new_text: "]".to_string(),
9356 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
9357 }]))
9358 });
9359
9360 editor_handle.update(cx, |editor, cx| {
9361 editor.focus(cx);
9362 editor.change_selections(None, cx, |s| {
9363 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
9364 });
9365 editor.handle_input("{", cx);
9366 });
9367
9368 cx.executor().run_until_parked();
9369
9370 _ = buffer.update(cx, |buffer, _| {
9371 assert_eq!(
9372 buffer.text(),
9373 "fn main() { let a = {5}; }",
9374 "No extra braces from on type formatting should appear in the buffer"
9375 )
9376 });
9377}
9378
9379#[gpui::test]
9380async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
9381 init_test(cx, |_| {});
9382
9383 let fs = FakeFs::new(cx.executor());
9384 fs.insert_tree(
9385 "/a",
9386 json!({
9387 "main.rs": "fn main() { let a = 5; }",
9388 "other.rs": "// Test file",
9389 }),
9390 )
9391 .await;
9392
9393 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9394
9395 let server_restarts = Arc::new(AtomicUsize::new(0));
9396 let closure_restarts = Arc::clone(&server_restarts);
9397 let language_server_name = "test language server";
9398 let language_name: Arc<str> = "Rust".into();
9399
9400 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9401 language_registry.add(Arc::new(Language::new(
9402 LanguageConfig {
9403 name: Arc::clone(&language_name),
9404 matcher: LanguageMatcher {
9405 path_suffixes: vec!["rs".to_string()],
9406 ..Default::default()
9407 },
9408 ..Default::default()
9409 },
9410 Some(tree_sitter_rust::language()),
9411 )));
9412 let mut fake_servers = language_registry.register_fake_lsp_adapter(
9413 "Rust",
9414 FakeLspAdapter {
9415 name: language_server_name,
9416 initialization_options: Some(json!({
9417 "testOptionValue": true
9418 })),
9419 initializer: Some(Box::new(move |fake_server| {
9420 let task_restarts = Arc::clone(&closure_restarts);
9421 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
9422 task_restarts.fetch_add(1, atomic::Ordering::Release);
9423 futures::future::ready(Ok(()))
9424 });
9425 })),
9426 ..Default::default()
9427 },
9428 );
9429
9430 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9431 let _buffer = project
9432 .update(cx, |project, cx| {
9433 project.open_local_buffer("/a/main.rs", cx)
9434 })
9435 .await
9436 .unwrap();
9437 let _fake_server = fake_servers.next().await.unwrap();
9438 update_test_language_settings(cx, |language_settings| {
9439 language_settings.languages.insert(
9440 Arc::clone(&language_name),
9441 LanguageSettingsContent {
9442 tab_size: NonZeroU32::new(8),
9443 ..Default::default()
9444 },
9445 );
9446 });
9447 cx.executor().run_until_parked();
9448 assert_eq!(
9449 server_restarts.load(atomic::Ordering::Acquire),
9450 0,
9451 "Should not restart LSP server on an unrelated change"
9452 );
9453
9454 update_test_project_settings(cx, |project_settings| {
9455 project_settings.lsp.insert(
9456 "Some other server name".into(),
9457 LspSettings {
9458 binary: None,
9459 settings: None,
9460 initialization_options: Some(json!({
9461 "some other init value": false
9462 })),
9463 },
9464 );
9465 });
9466 cx.executor().run_until_parked();
9467 assert_eq!(
9468 server_restarts.load(atomic::Ordering::Acquire),
9469 0,
9470 "Should not restart LSP server on an unrelated LSP settings change"
9471 );
9472
9473 update_test_project_settings(cx, |project_settings| {
9474 project_settings.lsp.insert(
9475 language_server_name.into(),
9476 LspSettings {
9477 binary: None,
9478 settings: None,
9479 initialization_options: Some(json!({
9480 "anotherInitValue": false
9481 })),
9482 },
9483 );
9484 });
9485 cx.executor().run_until_parked();
9486 assert_eq!(
9487 server_restarts.load(atomic::Ordering::Acquire),
9488 1,
9489 "Should restart LSP server on a related LSP settings change"
9490 );
9491
9492 update_test_project_settings(cx, |project_settings| {
9493 project_settings.lsp.insert(
9494 language_server_name.into(),
9495 LspSettings {
9496 binary: None,
9497 settings: None,
9498 initialization_options: Some(json!({
9499 "anotherInitValue": false
9500 })),
9501 },
9502 );
9503 });
9504 cx.executor().run_until_parked();
9505 assert_eq!(
9506 server_restarts.load(atomic::Ordering::Acquire),
9507 1,
9508 "Should not restart LSP server on a related LSP settings change that is the same"
9509 );
9510
9511 update_test_project_settings(cx, |project_settings| {
9512 project_settings.lsp.insert(
9513 language_server_name.into(),
9514 LspSettings {
9515 binary: None,
9516 settings: None,
9517 initialization_options: None,
9518 },
9519 );
9520 });
9521 cx.executor().run_until_parked();
9522 assert_eq!(
9523 server_restarts.load(atomic::Ordering::Acquire),
9524 2,
9525 "Should restart LSP server on another related LSP settings change"
9526 );
9527}
9528
9529#[gpui::test]
9530async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
9531 init_test(cx, |_| {});
9532
9533 let mut cx = EditorLspTestContext::new_rust(
9534 lsp::ServerCapabilities {
9535 completion_provider: Some(lsp::CompletionOptions {
9536 trigger_characters: Some(vec![".".to_string()]),
9537 resolve_provider: Some(true),
9538 ..Default::default()
9539 }),
9540 ..Default::default()
9541 },
9542 cx,
9543 )
9544 .await;
9545
9546 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9547 cx.simulate_keystroke(".");
9548 let completion_item = lsp::CompletionItem {
9549 label: "some".into(),
9550 kind: Some(lsp::CompletionItemKind::SNIPPET),
9551 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9552 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9553 kind: lsp::MarkupKind::Markdown,
9554 value: "```rust\nSome(2)\n```".to_string(),
9555 })),
9556 deprecated: Some(false),
9557 sort_text: Some("fffffff2".to_string()),
9558 filter_text: Some("some".to_string()),
9559 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9560 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9561 range: lsp::Range {
9562 start: lsp::Position {
9563 line: 0,
9564 character: 22,
9565 },
9566 end: lsp::Position {
9567 line: 0,
9568 character: 22,
9569 },
9570 },
9571 new_text: "Some(2)".to_string(),
9572 })),
9573 additional_text_edits: Some(vec![lsp::TextEdit {
9574 range: lsp::Range {
9575 start: lsp::Position {
9576 line: 0,
9577 character: 20,
9578 },
9579 end: lsp::Position {
9580 line: 0,
9581 character: 22,
9582 },
9583 },
9584 new_text: "".to_string(),
9585 }]),
9586 ..Default::default()
9587 };
9588
9589 let closure_completion_item = completion_item.clone();
9590 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9591 let task_completion_item = closure_completion_item.clone();
9592 async move {
9593 Ok(Some(lsp::CompletionResponse::Array(vec![
9594 task_completion_item,
9595 ])))
9596 }
9597 });
9598
9599 request.next().await;
9600
9601 cx.condition(|editor, _| editor.context_menu_visible())
9602 .await;
9603 let apply_additional_edits = cx.update_editor(|editor, cx| {
9604 editor
9605 .confirm_completion(&ConfirmCompletion::default(), cx)
9606 .unwrap()
9607 });
9608 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
9609
9610 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
9611 let task_completion_item = completion_item.clone();
9612 async move { Ok(task_completion_item) }
9613 })
9614 .next()
9615 .await
9616 .unwrap();
9617 apply_additional_edits.await.unwrap();
9618 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
9619}
9620
9621#[gpui::test]
9622async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
9623 init_test(cx, |_| {});
9624
9625 let mut cx = EditorLspTestContext::new(
9626 Language::new(
9627 LanguageConfig {
9628 matcher: LanguageMatcher {
9629 path_suffixes: vec!["jsx".into()],
9630 ..Default::default()
9631 },
9632 overrides: [(
9633 "element".into(),
9634 LanguageConfigOverride {
9635 word_characters: Override::Set(['-'].into_iter().collect()),
9636 ..Default::default()
9637 },
9638 )]
9639 .into_iter()
9640 .collect(),
9641 ..Default::default()
9642 },
9643 Some(tree_sitter_typescript::language_tsx()),
9644 )
9645 .with_override_query("(jsx_self_closing_element) @element")
9646 .unwrap(),
9647 lsp::ServerCapabilities {
9648 completion_provider: Some(lsp::CompletionOptions {
9649 trigger_characters: Some(vec![":".to_string()]),
9650 ..Default::default()
9651 }),
9652 ..Default::default()
9653 },
9654 cx,
9655 )
9656 .await;
9657
9658 cx.lsp
9659 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9660 Ok(Some(lsp::CompletionResponse::Array(vec![
9661 lsp::CompletionItem {
9662 label: "bg-blue".into(),
9663 ..Default::default()
9664 },
9665 lsp::CompletionItem {
9666 label: "bg-red".into(),
9667 ..Default::default()
9668 },
9669 lsp::CompletionItem {
9670 label: "bg-yellow".into(),
9671 ..Default::default()
9672 },
9673 ])))
9674 });
9675
9676 cx.set_state(r#"<p class="bgˇ" />"#);
9677
9678 // Trigger completion when typing a dash, because the dash is an extra
9679 // word character in the 'element' scope, which contains the cursor.
9680 cx.simulate_keystroke("-");
9681 cx.executor().run_until_parked();
9682 cx.update_editor(|editor, _| {
9683 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9684 assert_eq!(
9685 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9686 &["bg-red", "bg-blue", "bg-yellow"]
9687 );
9688 } else {
9689 panic!("expected completion menu to be open");
9690 }
9691 });
9692
9693 cx.simulate_keystroke("l");
9694 cx.executor().run_until_parked();
9695 cx.update_editor(|editor, _| {
9696 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9697 assert_eq!(
9698 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9699 &["bg-blue", "bg-yellow"]
9700 );
9701 } else {
9702 panic!("expected completion menu to be open");
9703 }
9704 });
9705
9706 // When filtering completions, consider the character after the '-' to
9707 // be the start of a subword.
9708 cx.set_state(r#"<p class="yelˇ" />"#);
9709 cx.simulate_keystroke("l");
9710 cx.executor().run_until_parked();
9711 cx.update_editor(|editor, _| {
9712 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9713 assert_eq!(
9714 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9715 &["bg-yellow"]
9716 );
9717 } else {
9718 panic!("expected completion menu to be open");
9719 }
9720 });
9721}
9722
9723#[gpui::test]
9724async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
9725 init_test(cx, |settings| {
9726 settings.defaults.formatter = Some(language_settings::Formatter::Prettier)
9727 });
9728
9729 let fs = FakeFs::new(cx.executor());
9730 fs.insert_file("/file.ts", Default::default()).await;
9731
9732 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
9733 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9734
9735 language_registry.add(Arc::new(Language::new(
9736 LanguageConfig {
9737 name: "TypeScript".into(),
9738 matcher: LanguageMatcher {
9739 path_suffixes: vec!["ts".to_string()],
9740 ..Default::default()
9741 },
9742 ..Default::default()
9743 },
9744 Some(tree_sitter_rust::language()),
9745 )));
9746 update_test_language_settings(cx, |settings| {
9747 settings.defaults.prettier = Some(PrettierSettings {
9748 allowed: true,
9749 ..PrettierSettings::default()
9750 });
9751 });
9752
9753 let test_plugin = "test_plugin";
9754 let _ = language_registry.register_fake_lsp_adapter(
9755 "TypeScript",
9756 FakeLspAdapter {
9757 prettier_plugins: vec![test_plugin],
9758 ..Default::default()
9759 },
9760 );
9761
9762 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
9763 let buffer = project
9764 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
9765 .await
9766 .unwrap();
9767
9768 let buffer_text = "one\ntwo\nthree\n";
9769 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9770 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9771 _ = editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
9772
9773 editor
9774 .update(cx, |editor, cx| {
9775 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
9776 })
9777 .unwrap()
9778 .await;
9779 assert_eq!(
9780 editor.update(cx, |editor, cx| editor.text(cx)),
9781 buffer_text.to_string() + prettier_format_suffix,
9782 "Test prettier formatting was not applied to the original buffer text",
9783 );
9784
9785 update_test_language_settings(cx, |settings| {
9786 settings.defaults.formatter = Some(language_settings::Formatter::Auto)
9787 });
9788 let format = editor.update(cx, |editor, cx| {
9789 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
9790 });
9791 format.await.unwrap();
9792 assert_eq!(
9793 editor.update(cx, |editor, cx| editor.text(cx)),
9794 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
9795 "Autoformatting (via test prettier) was not applied to the original buffer text",
9796 );
9797}
9798
9799#[gpui::test]
9800async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
9801 init_test(cx, |_| {});
9802 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9803 let base_text = indoc! {r#"struct Row;
9804struct Row1;
9805struct Row2;
9806
9807struct Row4;
9808struct Row5;
9809struct Row6;
9810
9811struct Row8;
9812struct Row9;
9813struct Row10;"#};
9814
9815 // When addition hunks are not adjacent to carets, no hunk revert is performed
9816 assert_hunk_revert(
9817 indoc! {r#"struct Row;
9818 struct Row1;
9819 struct Row1.1;
9820 struct Row1.2;
9821 struct Row2;ˇ
9822
9823 struct Row4;
9824 struct Row5;
9825 struct Row6;
9826
9827 struct Row8;
9828 ˇstruct Row9;
9829 struct Row9.1;
9830 struct Row9.2;
9831 struct Row9.3;
9832 struct Row10;"#},
9833 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
9834 indoc! {r#"struct Row;
9835 struct Row1;
9836 struct Row1.1;
9837 struct Row1.2;
9838 struct Row2;ˇ
9839
9840 struct Row4;
9841 struct Row5;
9842 struct Row6;
9843
9844 struct Row8;
9845 ˇstruct Row9;
9846 struct Row9.1;
9847 struct Row9.2;
9848 struct Row9.3;
9849 struct Row10;"#},
9850 base_text,
9851 &mut cx,
9852 );
9853 // Same for selections
9854 assert_hunk_revert(
9855 indoc! {r#"struct Row;
9856 struct Row1;
9857 struct Row2;
9858 struct Row2.1;
9859 struct Row2.2;
9860 «ˇ
9861 struct Row4;
9862 struct» Row5;
9863 «struct Row6;
9864 ˇ»
9865 struct Row9.1;
9866 struct Row9.2;
9867 struct Row9.3;
9868 struct Row8;
9869 struct Row9;
9870 struct Row10;"#},
9871 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
9872 indoc! {r#"struct Row;
9873 struct Row1;
9874 struct Row2;
9875 struct Row2.1;
9876 struct Row2.2;
9877 «ˇ
9878 struct Row4;
9879 struct» Row5;
9880 «struct Row6;
9881 ˇ»
9882 struct Row9.1;
9883 struct Row9.2;
9884 struct Row9.3;
9885 struct Row8;
9886 struct Row9;
9887 struct Row10;"#},
9888 base_text,
9889 &mut cx,
9890 );
9891
9892 // When carets and selections intersect the addition hunks, those are reverted.
9893 // Adjacent carets got merged.
9894 assert_hunk_revert(
9895 indoc! {r#"struct Row;
9896 ˇ// something on the top
9897 struct Row1;
9898 struct Row2;
9899 struct Roˇw3.1;
9900 struct Row2.2;
9901 struct Row2.3;ˇ
9902
9903 struct Row4;
9904 struct ˇRow5.1;
9905 struct Row5.2;
9906 struct «Rowˇ»5.3;
9907 struct Row5;
9908 struct Row6;
9909 ˇ
9910 struct Row9.1;
9911 struct «Rowˇ»9.2;
9912 struct «ˇRow»9.3;
9913 struct Row8;
9914 struct Row9;
9915 «ˇ// something on bottom»
9916 struct Row10;"#},
9917 vec![
9918 DiffHunkStatus::Added,
9919 DiffHunkStatus::Added,
9920 DiffHunkStatus::Added,
9921 DiffHunkStatus::Added,
9922 DiffHunkStatus::Added,
9923 ],
9924 indoc! {r#"struct Row;
9925 ˇstruct Row1;
9926 struct Row2;
9927 ˇ
9928 struct Row4;
9929 ˇstruct Row5;
9930 struct Row6;
9931 ˇ
9932 ˇstruct Row8;
9933 struct Row9;
9934 ˇstruct Row10;"#},
9935 base_text,
9936 &mut cx,
9937 );
9938}
9939
9940#[gpui::test]
9941async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
9942 init_test(cx, |_| {});
9943 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9944 let base_text = indoc! {r#"struct Row;
9945struct Row1;
9946struct Row2;
9947
9948struct Row4;
9949struct Row5;
9950struct Row6;
9951
9952struct Row8;
9953struct Row9;
9954struct Row10;"#};
9955
9956 // Modification hunks behave the same as the addition ones.
9957 assert_hunk_revert(
9958 indoc! {r#"struct Row;
9959 struct Row1;
9960 struct Row33;
9961 ˇ
9962 struct Row4;
9963 struct Row5;
9964 struct Row6;
9965 ˇ
9966 struct Row99;
9967 struct Row9;
9968 struct Row10;"#},
9969 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
9970 indoc! {r#"struct Row;
9971 struct Row1;
9972 struct Row33;
9973 ˇ
9974 struct Row4;
9975 struct Row5;
9976 struct Row6;
9977 ˇ
9978 struct Row99;
9979 struct Row9;
9980 struct Row10;"#},
9981 base_text,
9982 &mut cx,
9983 );
9984 assert_hunk_revert(
9985 indoc! {r#"struct Row;
9986 struct Row1;
9987 struct Row33;
9988 «ˇ
9989 struct Row4;
9990 struct» Row5;
9991 «struct Row6;
9992 ˇ»
9993 struct Row99;
9994 struct Row9;
9995 struct Row10;"#},
9996 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
9997 indoc! {r#"struct Row;
9998 struct Row1;
9999 struct Row33;
10000 «ˇ
10001 struct Row4;
10002 struct» Row5;
10003 «struct Row6;
10004 ˇ»
10005 struct Row99;
10006 struct Row9;
10007 struct Row10;"#},
10008 base_text,
10009 &mut cx,
10010 );
10011
10012 assert_hunk_revert(
10013 indoc! {r#"ˇstruct Row1.1;
10014 struct Row1;
10015 «ˇstr»uct Row22;
10016
10017 struct ˇRow44;
10018 struct Row5;
10019 struct «Rˇ»ow66;ˇ
10020
10021 «struˇ»ct Row88;
10022 struct Row9;
10023 struct Row1011;ˇ"#},
10024 vec![
10025 DiffHunkStatus::Modified,
10026 DiffHunkStatus::Modified,
10027 DiffHunkStatus::Modified,
10028 DiffHunkStatus::Modified,
10029 DiffHunkStatus::Modified,
10030 DiffHunkStatus::Modified,
10031 ],
10032 indoc! {r#"struct Row;
10033 ˇstruct Row1;
10034 struct Row2;
10035 ˇ
10036 struct Row4;
10037 ˇstruct Row5;
10038 struct Row6;
10039 ˇ
10040 struct Row8;
10041 ˇstruct Row9;
10042 struct Row10;ˇ"#},
10043 base_text,
10044 &mut cx,
10045 );
10046}
10047
10048#[gpui::test]
10049async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
10050 init_test(cx, |_| {});
10051 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10052 let base_text = indoc! {r#"struct Row;
10053struct Row1;
10054struct Row2;
10055
10056struct Row4;
10057struct Row5;
10058struct Row6;
10059
10060struct Row8;
10061struct Row9;
10062struct Row10;"#};
10063
10064 // Deletion hunks trigger with carets on ajacent rows, so carets and selections have to stay farther to avoid the revert
10065 assert_hunk_revert(
10066 indoc! {r#"struct Row;
10067 struct Row2;
10068
10069 ˇstruct Row4;
10070 struct Row5;
10071 struct Row6;
10072 ˇ
10073 struct Row8;
10074 struct Row10;"#},
10075 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10076 indoc! {r#"struct Row;
10077 struct Row2;
10078
10079 ˇstruct Row4;
10080 struct Row5;
10081 struct Row6;
10082 ˇ
10083 struct Row8;
10084 struct Row10;"#},
10085 base_text,
10086 &mut cx,
10087 );
10088 assert_hunk_revert(
10089 indoc! {r#"struct Row;
10090 struct Row2;
10091
10092 «ˇstruct Row4;
10093 struct» Row5;
10094 «struct Row6;
10095 ˇ»
10096 struct Row8;
10097 struct Row10;"#},
10098 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10099 indoc! {r#"struct Row;
10100 struct Row2;
10101
10102 «ˇstruct Row4;
10103 struct» Row5;
10104 «struct Row6;
10105 ˇ»
10106 struct Row8;
10107 struct Row10;"#},
10108 base_text,
10109 &mut cx,
10110 );
10111
10112 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
10113 assert_hunk_revert(
10114 indoc! {r#"struct Row;
10115 ˇstruct Row2;
10116
10117 struct Row4;
10118 struct Row5;
10119 struct Row6;
10120
10121 struct Row8;ˇ
10122 struct Row10;"#},
10123 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10124 indoc! {r#"struct Row;
10125 struct Row1;
10126 ˇstruct Row2;
10127
10128 struct Row4;
10129 struct Row5;
10130 struct Row6;
10131
10132 struct Row8;ˇ
10133 struct Row9;
10134 struct Row10;"#},
10135 base_text,
10136 &mut cx,
10137 );
10138 assert_hunk_revert(
10139 indoc! {r#"struct Row;
10140 struct Row2«ˇ;
10141 struct Row4;
10142 struct» Row5;
10143 «struct Row6;
10144
10145 struct Row8;ˇ»
10146 struct Row10;"#},
10147 vec![
10148 DiffHunkStatus::Removed,
10149 DiffHunkStatus::Removed,
10150 DiffHunkStatus::Removed,
10151 ],
10152 indoc! {r#"struct Row;
10153 struct Row1;
10154 struct Row2«ˇ;
10155
10156 struct Row4;
10157 struct» Row5;
10158 «struct Row6;
10159
10160 struct Row8;ˇ»
10161 struct Row9;
10162 struct Row10;"#},
10163 base_text,
10164 &mut cx,
10165 );
10166}
10167
10168#[gpui::test]
10169async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
10170 init_test(cx, |_| {});
10171
10172 let cols = 4;
10173 let rows = 10;
10174 let sample_text_1 = sample_text(rows, cols, 'a');
10175 assert_eq!(
10176 sample_text_1,
10177 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10178 );
10179 let sample_text_2 = sample_text(rows, cols, 'l');
10180 assert_eq!(
10181 sample_text_2,
10182 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10183 );
10184 let sample_text_3 = sample_text(rows, cols, 'v');
10185 assert_eq!(
10186 sample_text_3,
10187 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10188 );
10189
10190 fn diff_every_buffer_row(
10191 buffer: &Model<Buffer>,
10192 sample_text: String,
10193 cols: usize,
10194 cx: &mut gpui::TestAppContext,
10195 ) {
10196 // revert first character in each row, creating one large diff hunk per buffer
10197 let is_first_char = |offset: usize| offset % cols == 0;
10198 buffer.update(cx, |buffer, cx| {
10199 buffer.set_text(
10200 sample_text
10201 .chars()
10202 .enumerate()
10203 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
10204 .collect::<String>(),
10205 cx,
10206 );
10207 buffer.set_diff_base(Some(sample_text), cx);
10208 });
10209 cx.executor().run_until_parked();
10210 }
10211
10212 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10213 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10214
10215 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10216 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10217
10218 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10219 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10220
10221 let multibuffer = cx.new_model(|cx| {
10222 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10223 multibuffer.push_excerpts(
10224 buffer_1.clone(),
10225 [
10226 ExcerptRange {
10227 context: Point::new(0, 0)..Point::new(3, 0),
10228 primary: None,
10229 },
10230 ExcerptRange {
10231 context: Point::new(5, 0)..Point::new(7, 0),
10232 primary: None,
10233 },
10234 ExcerptRange {
10235 context: Point::new(9, 0)..Point::new(10, 4),
10236 primary: None,
10237 },
10238 ],
10239 cx,
10240 );
10241 multibuffer.push_excerpts(
10242 buffer_2.clone(),
10243 [
10244 ExcerptRange {
10245 context: Point::new(0, 0)..Point::new(3, 0),
10246 primary: None,
10247 },
10248 ExcerptRange {
10249 context: Point::new(5, 0)..Point::new(7, 0),
10250 primary: None,
10251 },
10252 ExcerptRange {
10253 context: Point::new(9, 0)..Point::new(10, 4),
10254 primary: None,
10255 },
10256 ],
10257 cx,
10258 );
10259 multibuffer.push_excerpts(
10260 buffer_3.clone(),
10261 [
10262 ExcerptRange {
10263 context: Point::new(0, 0)..Point::new(3, 0),
10264 primary: None,
10265 },
10266 ExcerptRange {
10267 context: Point::new(5, 0)..Point::new(7, 0),
10268 primary: None,
10269 },
10270 ExcerptRange {
10271 context: Point::new(9, 0)..Point::new(10, 4),
10272 primary: None,
10273 },
10274 ],
10275 cx,
10276 );
10277 multibuffer
10278 });
10279
10280 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
10281 editor.update(cx, |editor, cx| {
10282 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");
10283 editor.select_all(&SelectAll, cx);
10284 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10285 });
10286 cx.executor().run_until_parked();
10287 // When all ranges are selected, all buffer hunks are reverted.
10288 editor.update(cx, |editor, cx| {
10289 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");
10290 });
10291 buffer_1.update(cx, |buffer, _| {
10292 assert_eq!(buffer.text(), sample_text_1);
10293 });
10294 buffer_2.update(cx, |buffer, _| {
10295 assert_eq!(buffer.text(), sample_text_2);
10296 });
10297 buffer_3.update(cx, |buffer, _| {
10298 assert_eq!(buffer.text(), sample_text_3);
10299 });
10300
10301 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10302 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10303 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10304 editor.update(cx, |editor, cx| {
10305 editor.change_selections(None, cx, |s| {
10306 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
10307 });
10308 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10309 });
10310 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
10311 // but not affect buffer_2 and its related excerpts.
10312 editor.update(cx, |editor, cx| {
10313 assert_eq!(
10314 editor.text(cx),
10315 "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"
10316 );
10317 });
10318 buffer_1.update(cx, |buffer, _| {
10319 assert_eq!(buffer.text(), sample_text_1);
10320 });
10321 buffer_2.update(cx, |buffer, _| {
10322 assert_eq!(
10323 buffer.text(),
10324 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
10325 );
10326 });
10327 buffer_3.update(cx, |buffer, _| {
10328 assert_eq!(
10329 buffer.text(),
10330 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
10331 );
10332 });
10333}
10334
10335#[gpui::test]
10336async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
10337 init_test(cx, |_| {});
10338
10339 let cols = 4;
10340 let rows = 10;
10341 let sample_text_1 = sample_text(rows, cols, 'a');
10342 assert_eq!(
10343 sample_text_1,
10344 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10345 );
10346 let sample_text_2 = sample_text(rows, cols, 'l');
10347 assert_eq!(
10348 sample_text_2,
10349 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10350 );
10351 let sample_text_3 = sample_text(rows, cols, 'v');
10352 assert_eq!(
10353 sample_text_3,
10354 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10355 );
10356
10357 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10358 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10359 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10360
10361 let multi_buffer = cx.new_model(|cx| {
10362 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10363 multibuffer.push_excerpts(
10364 buffer_1.clone(),
10365 [
10366 ExcerptRange {
10367 context: Point::new(0, 0)..Point::new(3, 0),
10368 primary: None,
10369 },
10370 ExcerptRange {
10371 context: Point::new(5, 0)..Point::new(7, 0),
10372 primary: None,
10373 },
10374 ExcerptRange {
10375 context: Point::new(9, 0)..Point::new(10, 4),
10376 primary: None,
10377 },
10378 ],
10379 cx,
10380 );
10381 multibuffer.push_excerpts(
10382 buffer_2.clone(),
10383 [
10384 ExcerptRange {
10385 context: Point::new(0, 0)..Point::new(3, 0),
10386 primary: None,
10387 },
10388 ExcerptRange {
10389 context: Point::new(5, 0)..Point::new(7, 0),
10390 primary: None,
10391 },
10392 ExcerptRange {
10393 context: Point::new(9, 0)..Point::new(10, 4),
10394 primary: None,
10395 },
10396 ],
10397 cx,
10398 );
10399 multibuffer.push_excerpts(
10400 buffer_3.clone(),
10401 [
10402 ExcerptRange {
10403 context: Point::new(0, 0)..Point::new(3, 0),
10404 primary: None,
10405 },
10406 ExcerptRange {
10407 context: Point::new(5, 0)..Point::new(7, 0),
10408 primary: None,
10409 },
10410 ExcerptRange {
10411 context: Point::new(9, 0)..Point::new(10, 4),
10412 primary: None,
10413 },
10414 ],
10415 cx,
10416 );
10417 multibuffer
10418 });
10419
10420 let fs = FakeFs::new(cx.executor());
10421 fs.insert_tree(
10422 "/a",
10423 json!({
10424 "main.rs": sample_text_1,
10425 "other.rs": sample_text_2,
10426 "lib.rs": sample_text_3,
10427 }),
10428 )
10429 .await;
10430 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10431 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10432 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10433 let multi_buffer_editor = cx.new_view(|cx| {
10434 Editor::new(
10435 EditorMode::Full,
10436 multi_buffer,
10437 Some(project.clone()),
10438 true,
10439 cx,
10440 )
10441 });
10442 let multibuffer_item_id = workspace
10443 .update(cx, |workspace, cx| {
10444 assert!(
10445 workspace.active_item(cx).is_none(),
10446 "active item should be None before the first item is added"
10447 );
10448 workspace.add_item_to_active_pane(Box::new(multi_buffer_editor.clone()), None, cx);
10449 let active_item = workspace
10450 .active_item(cx)
10451 .expect("should have an active item after adding the multi buffer");
10452 assert!(
10453 !active_item.is_singleton(cx),
10454 "A multi buffer was expected to active after adding"
10455 );
10456 active_item.item_id()
10457 })
10458 .unwrap();
10459 cx.executor().run_until_parked();
10460
10461 multi_buffer_editor.update(cx, |editor, cx| {
10462 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
10463 editor.open_excerpts(&OpenExcerpts, cx);
10464 });
10465 cx.executor().run_until_parked();
10466 let first_item_id = workspace
10467 .update(cx, |workspace, cx| {
10468 let active_item = workspace
10469 .active_item(cx)
10470 .expect("should have an active item after navigating into the 1st buffer");
10471 let first_item_id = active_item.item_id();
10472 assert_ne!(
10473 first_item_id, multibuffer_item_id,
10474 "Should navigate into the 1st buffer and activate it"
10475 );
10476 assert!(
10477 active_item.is_singleton(cx),
10478 "New active item should be a singleton buffer"
10479 );
10480 assert_eq!(
10481 active_item
10482 .act_as::<Editor>(cx)
10483 .expect("should have navigated into an editor for the 1st buffer")
10484 .read(cx)
10485 .text(cx),
10486 sample_text_1
10487 );
10488
10489 workspace
10490 .go_back(workspace.active_pane().downgrade(), cx)
10491 .detach_and_log_err(cx);
10492
10493 first_item_id
10494 })
10495 .unwrap();
10496 cx.executor().run_until_parked();
10497 workspace
10498 .update(cx, |workspace, cx| {
10499 let active_item = workspace
10500 .active_item(cx)
10501 .expect("should have an active item after navigating back");
10502 assert_eq!(
10503 active_item.item_id(),
10504 multibuffer_item_id,
10505 "Should navigate back to the multi buffer"
10506 );
10507 assert!(!active_item.is_singleton(cx));
10508 })
10509 .unwrap();
10510
10511 multi_buffer_editor.update(cx, |editor, cx| {
10512 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
10513 s.select_ranges(Some(39..40))
10514 });
10515 editor.open_excerpts(&OpenExcerpts, cx);
10516 });
10517 cx.executor().run_until_parked();
10518 let second_item_id = workspace
10519 .update(cx, |workspace, cx| {
10520 let active_item = workspace
10521 .active_item(cx)
10522 .expect("should have an active item after navigating into the 2nd buffer");
10523 let second_item_id = active_item.item_id();
10524 assert_ne!(
10525 second_item_id, multibuffer_item_id,
10526 "Should navigate away from the multibuffer"
10527 );
10528 assert_ne!(
10529 second_item_id, first_item_id,
10530 "Should navigate into the 2nd buffer and activate it"
10531 );
10532 assert!(
10533 active_item.is_singleton(cx),
10534 "New active item should be a singleton buffer"
10535 );
10536 assert_eq!(
10537 active_item
10538 .act_as::<Editor>(cx)
10539 .expect("should have navigated into an editor")
10540 .read(cx)
10541 .text(cx),
10542 sample_text_2
10543 );
10544
10545 workspace
10546 .go_back(workspace.active_pane().downgrade(), cx)
10547 .detach_and_log_err(cx);
10548
10549 second_item_id
10550 })
10551 .unwrap();
10552 cx.executor().run_until_parked();
10553 workspace
10554 .update(cx, |workspace, cx| {
10555 let active_item = workspace
10556 .active_item(cx)
10557 .expect("should have an active item after navigating back from the 2nd buffer");
10558 assert_eq!(
10559 active_item.item_id(),
10560 multibuffer_item_id,
10561 "Should navigate back from the 2nd buffer to the multi buffer"
10562 );
10563 assert!(!active_item.is_singleton(cx));
10564 })
10565 .unwrap();
10566
10567 multi_buffer_editor.update(cx, |editor, cx| {
10568 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
10569 s.select_ranges(Some(60..70))
10570 });
10571 editor.open_excerpts(&OpenExcerpts, cx);
10572 });
10573 cx.executor().run_until_parked();
10574 workspace
10575 .update(cx, |workspace, cx| {
10576 let active_item = workspace
10577 .active_item(cx)
10578 .expect("should have an active item after navigating into the 3rd buffer");
10579 let third_item_id = active_item.item_id();
10580 assert_ne!(
10581 third_item_id, multibuffer_item_id,
10582 "Should navigate into the 3rd buffer and activate it"
10583 );
10584 assert_ne!(third_item_id, first_item_id);
10585 assert_ne!(third_item_id, second_item_id);
10586 assert!(
10587 active_item.is_singleton(cx),
10588 "New active item should be a singleton buffer"
10589 );
10590 assert_eq!(
10591 active_item
10592 .act_as::<Editor>(cx)
10593 .expect("should have navigated into an editor")
10594 .read(cx)
10595 .text(cx),
10596 sample_text_3
10597 );
10598
10599 workspace
10600 .go_back(workspace.active_pane().downgrade(), cx)
10601 .detach_and_log_err(cx);
10602 })
10603 .unwrap();
10604 cx.executor().run_until_parked();
10605 workspace
10606 .update(cx, |workspace, cx| {
10607 let active_item = workspace
10608 .active_item(cx)
10609 .expect("should have an active item after navigating back from the 3rd buffer");
10610 assert_eq!(
10611 active_item.item_id(),
10612 multibuffer_item_id,
10613 "Should navigate back from the 3rd buffer to the multi buffer"
10614 );
10615 assert!(!active_item.is_singleton(cx));
10616 })
10617 .unwrap();
10618}
10619
10620#[gpui::test]
10621async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10622 init_test(cx, |_| {});
10623
10624 let mut cx = EditorTestContext::new(cx).await;
10625
10626 let diff_base = r#"
10627 use some::mod;
10628
10629 const A: u32 = 42;
10630
10631 fn main() {
10632 println!("hello");
10633
10634 println!("world");
10635 }
10636 "#
10637 .unindent();
10638
10639 cx.set_state(
10640 &r#"
10641 use some::modified;
10642
10643 ˇ
10644 fn main() {
10645 println!("hello there");
10646
10647 println!("around the");
10648 println!("world");
10649 }
10650 "#
10651 .unindent(),
10652 );
10653
10654 cx.set_diff_base(Some(&diff_base));
10655 executor.run_until_parked();
10656 let unexpanded_hunks = vec![
10657 (
10658 "use some::mod;\n".to_string(),
10659 DiffHunkStatus::Modified,
10660 DisplayRow(0)..DisplayRow(1),
10661 ),
10662 (
10663 "const A: u32 = 42;\n".to_string(),
10664 DiffHunkStatus::Removed,
10665 DisplayRow(2)..DisplayRow(2),
10666 ),
10667 (
10668 " println!(\"hello\");\n".to_string(),
10669 DiffHunkStatus::Modified,
10670 DisplayRow(4)..DisplayRow(5),
10671 ),
10672 (
10673 "".to_string(),
10674 DiffHunkStatus::Added,
10675 DisplayRow(6)..DisplayRow(7),
10676 ),
10677 ];
10678 cx.update_editor(|editor, cx| {
10679 let snapshot = editor.snapshot(cx);
10680 let all_hunks = editor_hunks(editor, &snapshot, cx);
10681 assert_eq!(all_hunks, unexpanded_hunks);
10682 });
10683
10684 cx.update_editor(|editor, cx| {
10685 for _ in 0..4 {
10686 editor.go_to_hunk(&GoToHunk, cx);
10687 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10688 }
10689 });
10690 executor.run_until_parked();
10691 cx.assert_editor_state(
10692 &r#"
10693 use some::modified;
10694
10695 ˇ
10696 fn main() {
10697 println!("hello there");
10698
10699 println!("around the");
10700 println!("world");
10701 }
10702 "#
10703 .unindent(),
10704 );
10705 cx.update_editor(|editor, cx| {
10706 let snapshot = editor.snapshot(cx);
10707 let all_hunks = editor_hunks(editor, &snapshot, cx);
10708 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10709 assert_eq!(
10710 expanded_hunks_background_highlights(editor, cx),
10711 vec![DisplayRow(1)..=DisplayRow(1), DisplayRow(7)..=DisplayRow(7), DisplayRow(9)..=DisplayRow(9)],
10712 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
10713 );
10714 assert_eq!(
10715 all_hunks,
10716 vec![
10717 ("use some::mod;\n".to_string(), DiffHunkStatus::Modified, DisplayRow(1)..DisplayRow(2)),
10718 ("const A: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(4)..DisplayRow(4)),
10719 (" println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(7)..DisplayRow(8)),
10720 ("".to_string(), DiffHunkStatus::Added, DisplayRow(9)..DisplayRow(10)),
10721 ],
10722 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
10723 (from modified and removed hunks)"
10724 );
10725 assert_eq!(
10726 all_hunks, all_expanded_hunks,
10727 "Editor hunks should not change and all be expanded"
10728 );
10729 });
10730
10731 cx.update_editor(|editor, cx| {
10732 editor.cancel(&Cancel, cx);
10733
10734 let snapshot = editor.snapshot(cx);
10735 let all_hunks = editor_hunks(editor, &snapshot, cx);
10736 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
10737 assert_eq!(
10738 expanded_hunks_background_highlights(editor, cx),
10739 Vec::new(),
10740 "After cancelling in editor, no git highlights should be left"
10741 );
10742 assert_eq!(
10743 all_expanded_hunks,
10744 Vec::new(),
10745 "After cancelling in editor, no hunks should be expanded"
10746 );
10747 assert_eq!(
10748 all_hunks, unexpanded_hunks,
10749 "After cancelling in editor, regular hunks' coordinates should get back to normal"
10750 );
10751 });
10752}
10753
10754#[gpui::test]
10755async fn test_toggled_diff_base_change(
10756 executor: BackgroundExecutor,
10757 cx: &mut gpui::TestAppContext,
10758) {
10759 init_test(cx, |_| {});
10760
10761 let mut cx = EditorTestContext::new(cx).await;
10762
10763 let diff_base = r#"
10764 use some::mod1;
10765 use some::mod2;
10766
10767 const A: u32 = 42;
10768 const B: u32 = 42;
10769 const C: u32 = 42;
10770
10771 fn main(ˇ) {
10772 println!("hello");
10773
10774 println!("world");
10775 }
10776 "#
10777 .unindent();
10778
10779 cx.set_state(
10780 &r#"
10781 use some::mod2;
10782
10783 const A: u32 = 42;
10784 const C: u32 = 42;
10785
10786 fn main(ˇ) {
10787 //println!("hello");
10788
10789 println!("world");
10790 //
10791 //
10792 }
10793 "#
10794 .unindent(),
10795 );
10796
10797 cx.set_diff_base(Some(&diff_base));
10798 executor.run_until_parked();
10799 cx.update_editor(|editor, cx| {
10800 let snapshot = editor.snapshot(cx);
10801 let all_hunks = editor_hunks(editor, &snapshot, cx);
10802 assert_eq!(
10803 all_hunks,
10804 vec![
10805 (
10806 "use some::mod1;\n".to_string(),
10807 DiffHunkStatus::Removed,
10808 DisplayRow(0)..DisplayRow(0)
10809 ),
10810 (
10811 "const B: u32 = 42;\n".to_string(),
10812 DiffHunkStatus::Removed,
10813 DisplayRow(3)..DisplayRow(3)
10814 ),
10815 (
10816 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
10817 DiffHunkStatus::Modified,
10818 DisplayRow(5)..DisplayRow(7)
10819 ),
10820 (
10821 "".to_string(),
10822 DiffHunkStatus::Added,
10823 DisplayRow(9)..DisplayRow(11)
10824 ),
10825 ]
10826 );
10827 });
10828
10829 cx.update_editor(|editor, cx| {
10830 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10831 });
10832 executor.run_until_parked();
10833 cx.assert_editor_state(
10834 &r#"
10835 use some::mod2;
10836
10837 const A: u32 = 42;
10838 const C: u32 = 42;
10839
10840 fn main(ˇ) {
10841 //println!("hello");
10842
10843 println!("world");
10844 //
10845 //
10846 }
10847 "#
10848 .unindent(),
10849 );
10850 cx.update_editor(|editor, cx| {
10851 let snapshot = editor.snapshot(cx);
10852 let all_hunks = editor_hunks(editor, &snapshot, cx);
10853 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10854 assert_eq!(
10855 expanded_hunks_background_highlights(editor, cx),
10856 vec![DisplayRow(9)..=DisplayRow(10), DisplayRow(13)..=DisplayRow(14)],
10857 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
10858 );
10859 assert_eq!(
10860 all_hunks,
10861 vec![
10862 ("use some::mod1;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(1)..DisplayRow(1)),
10863 ("const B: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(5)..DisplayRow(5)),
10864 ("fn main(ˇ) {\n println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(9)..DisplayRow(11)),
10865 ("".to_string(), DiffHunkStatus::Added, DisplayRow(13)..DisplayRow(15)),
10866 ],
10867 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
10868 (from modified and removed hunks)"
10869 );
10870 assert_eq!(
10871 all_hunks, all_expanded_hunks,
10872 "Editor hunks should not change and all be expanded"
10873 );
10874 });
10875
10876 cx.set_diff_base(Some("new diff base!"));
10877 executor.run_until_parked();
10878
10879 cx.update_editor(|editor, cx| {
10880 let snapshot = editor.snapshot(cx);
10881 let all_hunks = editor_hunks(editor, &snapshot, cx);
10882 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
10883 assert_eq!(
10884 expanded_hunks_background_highlights(editor, cx),
10885 Vec::new(),
10886 "After diff base is changed, old git highlights should be removed"
10887 );
10888 assert_eq!(
10889 all_expanded_hunks,
10890 Vec::new(),
10891 "After diff base is changed, old git hunk expansions should be removed"
10892 );
10893 assert_eq!(
10894 all_hunks,
10895 vec![(
10896 "new diff base!".to_string(),
10897 DiffHunkStatus::Modified,
10898 DisplayRow(0)..snapshot.display_snapshot.max_point().row()
10899 )],
10900 "After diff base is changed, hunks should update"
10901 );
10902 });
10903}
10904
10905#[gpui::test]
10906async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10907 init_test(cx, |_| {});
10908
10909 let mut cx = EditorTestContext::new(cx).await;
10910
10911 let diff_base = r#"
10912 use some::mod1;
10913 use some::mod2;
10914
10915 const A: u32 = 42;
10916 const B: u32 = 42;
10917 const C: u32 = 42;
10918
10919 fn main(ˇ) {
10920 println!("hello");
10921
10922 println!("world");
10923 }
10924
10925 fn another() {
10926 println!("another");
10927 }
10928
10929 fn another2() {
10930 println!("another2");
10931 }
10932 "#
10933 .unindent();
10934
10935 cx.set_state(
10936 &r#"
10937 «use some::mod2;
10938
10939 const A: u32 = 42;
10940 const C: u32 = 42;
10941
10942 fn main() {
10943 //println!("hello");
10944
10945 println!("world");
10946 //
10947 //ˇ»
10948 }
10949
10950 fn another() {
10951 println!("another");
10952 println!("another");
10953 }
10954
10955 println!("another2");
10956 }
10957 "#
10958 .unindent(),
10959 );
10960
10961 cx.set_diff_base(Some(&diff_base));
10962 executor.run_until_parked();
10963 cx.update_editor(|editor, cx| {
10964 let snapshot = editor.snapshot(cx);
10965 let all_hunks = editor_hunks(editor, &snapshot, cx);
10966 assert_eq!(
10967 all_hunks,
10968 vec![
10969 (
10970 "use some::mod1;\n".to_string(),
10971 DiffHunkStatus::Removed,
10972 DisplayRow(0)..DisplayRow(0)
10973 ),
10974 (
10975 "const B: u32 = 42;\n".to_string(),
10976 DiffHunkStatus::Removed,
10977 DisplayRow(3)..DisplayRow(3)
10978 ),
10979 (
10980 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
10981 DiffHunkStatus::Modified,
10982 DisplayRow(5)..DisplayRow(7)
10983 ),
10984 (
10985 "".to_string(),
10986 DiffHunkStatus::Added,
10987 DisplayRow(9)..DisplayRow(11)
10988 ),
10989 (
10990 "".to_string(),
10991 DiffHunkStatus::Added,
10992 DisplayRow(15)..DisplayRow(16)
10993 ),
10994 (
10995 "fn another2() {\n".to_string(),
10996 DiffHunkStatus::Removed,
10997 DisplayRow(18)..DisplayRow(18)
10998 ),
10999 ]
11000 );
11001 });
11002
11003 cx.update_editor(|editor, cx| {
11004 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11005 });
11006 executor.run_until_parked();
11007 cx.assert_editor_state(
11008 &r#"
11009 «use some::mod2;
11010
11011 const A: u32 = 42;
11012 const C: u32 = 42;
11013
11014 fn main() {
11015 //println!("hello");
11016
11017 println!("world");
11018 //
11019 //ˇ»
11020 }
11021
11022 fn another() {
11023 println!("another");
11024 println!("another");
11025 }
11026
11027 println!("another2");
11028 }
11029 "#
11030 .unindent(),
11031 );
11032 cx.update_editor(|editor, cx| {
11033 let snapshot = editor.snapshot(cx);
11034 let all_hunks = editor_hunks(editor, &snapshot, cx);
11035 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11036 assert_eq!(
11037 expanded_hunks_background_highlights(editor, cx),
11038 vec![
11039 DisplayRow(9)..=DisplayRow(10),
11040 DisplayRow(13)..=DisplayRow(14),
11041 DisplayRow(19)..=DisplayRow(19)
11042 ]
11043 );
11044 assert_eq!(
11045 all_hunks,
11046 vec![
11047 (
11048 "use some::mod1;\n".to_string(),
11049 DiffHunkStatus::Removed,
11050 DisplayRow(1)..DisplayRow(1)
11051 ),
11052 (
11053 "const B: u32 = 42;\n".to_string(),
11054 DiffHunkStatus::Removed,
11055 DisplayRow(5)..DisplayRow(5)
11056 ),
11057 (
11058 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11059 DiffHunkStatus::Modified,
11060 DisplayRow(9)..DisplayRow(11)
11061 ),
11062 (
11063 "".to_string(),
11064 DiffHunkStatus::Added,
11065 DisplayRow(13)..DisplayRow(15)
11066 ),
11067 (
11068 "".to_string(),
11069 DiffHunkStatus::Added,
11070 DisplayRow(19)..DisplayRow(20)
11071 ),
11072 (
11073 "fn another2() {\n".to_string(),
11074 DiffHunkStatus::Removed,
11075 DisplayRow(23)..DisplayRow(23)
11076 ),
11077 ],
11078 );
11079 assert_eq!(all_hunks, all_expanded_hunks);
11080 });
11081
11082 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
11083 cx.executor().run_until_parked();
11084 cx.assert_editor_state(
11085 &r#"
11086 «use some::mod2;
11087
11088 const A: u32 = 42;
11089 const C: u32 = 42;
11090
11091 fn main() {
11092 //println!("hello");
11093
11094 println!("world");
11095 //
11096 //ˇ»
11097 }
11098
11099 fn another() {
11100 println!("another");
11101 println!("another");
11102 }
11103
11104 println!("another2");
11105 }
11106 "#
11107 .unindent(),
11108 );
11109 cx.update_editor(|editor, cx| {
11110 let snapshot = editor.snapshot(cx);
11111 let all_hunks = editor_hunks(editor, &snapshot, cx);
11112 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11113 assert_eq!(
11114 expanded_hunks_background_highlights(editor, cx),
11115 vec![DisplayRow(0)..=DisplayRow(0), DisplayRow(5)..=DisplayRow(5)],
11116 "Only one hunk is left not folded, its highlight should be visible"
11117 );
11118 assert_eq!(
11119 all_hunks,
11120 vec![
11121 (
11122 "use some::mod1;\n".to_string(),
11123 DiffHunkStatus::Removed,
11124 DisplayRow(0)..DisplayRow(0)
11125 ),
11126 (
11127 "const B: u32 = 42;\n".to_string(),
11128 DiffHunkStatus::Removed,
11129 DisplayRow(0)..DisplayRow(0)
11130 ),
11131 (
11132 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11133 DiffHunkStatus::Modified,
11134 DisplayRow(0)..DisplayRow(0)
11135 ),
11136 (
11137 "".to_string(),
11138 DiffHunkStatus::Added,
11139 DisplayRow(0)..DisplayRow(1)
11140 ),
11141 (
11142 "".to_string(),
11143 DiffHunkStatus::Added,
11144 DisplayRow(5)..DisplayRow(6)
11145 ),
11146 (
11147 "fn another2() {\n".to_string(),
11148 DiffHunkStatus::Removed,
11149 DisplayRow(9)..DisplayRow(9)
11150 ),
11151 ],
11152 "Hunk list should still return shifted folded hunks"
11153 );
11154 assert_eq!(
11155 all_expanded_hunks,
11156 vec![
11157 (
11158 "".to_string(),
11159 DiffHunkStatus::Added,
11160 DisplayRow(5)..DisplayRow(6)
11161 ),
11162 (
11163 "fn another2() {\n".to_string(),
11164 DiffHunkStatus::Removed,
11165 DisplayRow(9)..DisplayRow(9)
11166 ),
11167 ],
11168 "Only non-folded hunks should be left expanded"
11169 );
11170 });
11171
11172 cx.update_editor(|editor, cx| {
11173 editor.select_all(&SelectAll, cx);
11174 editor.unfold_lines(&UnfoldLines, cx);
11175 });
11176 cx.executor().run_until_parked();
11177 cx.assert_editor_state(
11178 &r#"
11179 «use some::mod2;
11180
11181 const A: u32 = 42;
11182 const C: u32 = 42;
11183
11184 fn main() {
11185 //println!("hello");
11186
11187 println!("world");
11188 //
11189 //
11190 }
11191
11192 fn another() {
11193 println!("another");
11194 println!("another");
11195 }
11196
11197 println!("another2");
11198 }
11199 ˇ»"#
11200 .unindent(),
11201 );
11202 cx.update_editor(|editor, cx| {
11203 let snapshot = editor.snapshot(cx);
11204 let all_hunks = editor_hunks(editor, &snapshot, cx);
11205 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11206 assert_eq!(
11207 expanded_hunks_background_highlights(editor, cx),
11208 vec![
11209 DisplayRow(9)..=DisplayRow(10),
11210 DisplayRow(13)..=DisplayRow(14),
11211 DisplayRow(19)..=DisplayRow(19)
11212 ],
11213 "After unfolding, all hunk diffs should be visible again"
11214 );
11215 assert_eq!(
11216 all_hunks,
11217 vec![
11218 (
11219 "use some::mod1;\n".to_string(),
11220 DiffHunkStatus::Removed,
11221 DisplayRow(1)..DisplayRow(1)
11222 ),
11223 (
11224 "const B: u32 = 42;\n".to_string(),
11225 DiffHunkStatus::Removed,
11226 DisplayRow(5)..DisplayRow(5)
11227 ),
11228 (
11229 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11230 DiffHunkStatus::Modified,
11231 DisplayRow(9)..DisplayRow(11)
11232 ),
11233 (
11234 "".to_string(),
11235 DiffHunkStatus::Added,
11236 DisplayRow(13)..DisplayRow(15)
11237 ),
11238 (
11239 "".to_string(),
11240 DiffHunkStatus::Added,
11241 DisplayRow(19)..DisplayRow(20)
11242 ),
11243 (
11244 "fn another2() {\n".to_string(),
11245 DiffHunkStatus::Removed,
11246 DisplayRow(23)..DisplayRow(23)
11247 ),
11248 ],
11249 );
11250 assert_eq!(all_hunks, all_expanded_hunks);
11251 });
11252}
11253
11254#[gpui::test]
11255async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
11256 init_test(cx, |_| {});
11257
11258 let cols = 4;
11259 let rows = 10;
11260 let sample_text_1 = sample_text(rows, cols, 'a');
11261 assert_eq!(
11262 sample_text_1,
11263 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11264 );
11265 let modified_sample_text_1 = "aaaa\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
11266 let sample_text_2 = sample_text(rows, cols, 'l');
11267 assert_eq!(
11268 sample_text_2,
11269 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11270 );
11271 let modified_sample_text_2 = "llll\nmmmm\n1n1n1n1n1\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
11272 let sample_text_3 = sample_text(rows, cols, 'v');
11273 assert_eq!(
11274 sample_text_3,
11275 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11276 );
11277 let modified_sample_text_3 =
11278 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n@@@@\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
11279 let buffer_1 = cx.new_model(|cx| {
11280 let mut buffer = Buffer::local(modified_sample_text_1.to_string(), cx);
11281 buffer.set_diff_base(Some(sample_text_1.clone()), cx);
11282 buffer
11283 });
11284 let buffer_2 = cx.new_model(|cx| {
11285 let mut buffer = Buffer::local(modified_sample_text_2.to_string(), cx);
11286 buffer.set_diff_base(Some(sample_text_2.clone()), cx);
11287 buffer
11288 });
11289 let buffer_3 = cx.new_model(|cx| {
11290 let mut buffer = Buffer::local(modified_sample_text_3.to_string(), cx);
11291 buffer.set_diff_base(Some(sample_text_3.clone()), cx);
11292 buffer
11293 });
11294
11295 let multi_buffer = cx.new_model(|cx| {
11296 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
11297 multibuffer.push_excerpts(
11298 buffer_1.clone(),
11299 [
11300 ExcerptRange {
11301 context: Point::new(0, 0)..Point::new(3, 0),
11302 primary: None,
11303 },
11304 ExcerptRange {
11305 context: Point::new(5, 0)..Point::new(7, 0),
11306 primary: None,
11307 },
11308 ExcerptRange {
11309 context: Point::new(9, 0)..Point::new(10, 4),
11310 primary: None,
11311 },
11312 ],
11313 cx,
11314 );
11315 multibuffer.push_excerpts(
11316 buffer_2.clone(),
11317 [
11318 ExcerptRange {
11319 context: Point::new(0, 0)..Point::new(3, 0),
11320 primary: None,
11321 },
11322 ExcerptRange {
11323 context: Point::new(5, 0)..Point::new(7, 0),
11324 primary: None,
11325 },
11326 ExcerptRange {
11327 context: Point::new(9, 0)..Point::new(10, 4),
11328 primary: None,
11329 },
11330 ],
11331 cx,
11332 );
11333 multibuffer.push_excerpts(
11334 buffer_3.clone(),
11335 [
11336 ExcerptRange {
11337 context: Point::new(0, 0)..Point::new(3, 0),
11338 primary: None,
11339 },
11340 ExcerptRange {
11341 context: Point::new(5, 0)..Point::new(7, 0),
11342 primary: None,
11343 },
11344 ExcerptRange {
11345 context: Point::new(9, 0)..Point::new(10, 4),
11346 primary: None,
11347 },
11348 ],
11349 cx,
11350 );
11351 multibuffer
11352 });
11353
11354 let fs = FakeFs::new(cx.executor());
11355 fs.insert_tree(
11356 "/a",
11357 json!({
11358 "main.rs": modified_sample_text_1,
11359 "other.rs": modified_sample_text_2,
11360 "lib.rs": modified_sample_text_3,
11361 }),
11362 )
11363 .await;
11364
11365 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11366 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11367 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11368 let multi_buffer_editor = cx.new_view(|cx| {
11369 Editor::new(
11370 EditorMode::Full,
11371 multi_buffer,
11372 Some(project.clone()),
11373 true,
11374 cx,
11375 )
11376 });
11377 cx.executor().run_until_parked();
11378
11379 let expected_all_hunks = vec![
11380 (
11381 "bbbb\n".to_string(),
11382 DiffHunkStatus::Removed,
11383 DisplayRow(4)..DisplayRow(4),
11384 ),
11385 (
11386 "nnnn\n".to_string(),
11387 DiffHunkStatus::Modified,
11388 DisplayRow(21)..DisplayRow(22),
11389 ),
11390 (
11391 "".to_string(),
11392 DiffHunkStatus::Added,
11393 DisplayRow(41)..DisplayRow(42),
11394 ),
11395 ];
11396 let expected_all_hunks_shifted = vec![
11397 (
11398 "bbbb\n".to_string(),
11399 DiffHunkStatus::Removed,
11400 DisplayRow(5)..DisplayRow(5),
11401 ),
11402 (
11403 "nnnn\n".to_string(),
11404 DiffHunkStatus::Modified,
11405 DisplayRow(23)..DisplayRow(24),
11406 ),
11407 (
11408 "".to_string(),
11409 DiffHunkStatus::Added,
11410 DisplayRow(43)..DisplayRow(44),
11411 ),
11412 ];
11413
11414 multi_buffer_editor.update(cx, |editor, cx| {
11415 let snapshot = editor.snapshot(cx);
11416 let all_hunks = editor_hunks(editor, &snapshot, cx);
11417 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11418 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11419 assert_eq!(all_hunks, expected_all_hunks);
11420 assert_eq!(all_expanded_hunks, Vec::new());
11421 });
11422
11423 multi_buffer_editor.update(cx, |editor, cx| {
11424 editor.select_all(&SelectAll, cx);
11425 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11426 });
11427 cx.executor().run_until_parked();
11428 multi_buffer_editor.update(cx, |editor, cx| {
11429 let snapshot = editor.snapshot(cx);
11430 let all_hunks = editor_hunks(editor, &snapshot, cx);
11431 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11432 assert_eq!(
11433 expanded_hunks_background_highlights(editor, cx),
11434 vec![
11435 DisplayRow(23)..=DisplayRow(23),
11436 DisplayRow(43)..=DisplayRow(43)
11437 ],
11438 );
11439 assert_eq!(all_hunks, expected_all_hunks_shifted);
11440 assert_eq!(all_hunks, all_expanded_hunks);
11441 });
11442
11443 multi_buffer_editor.update(cx, |editor, cx| {
11444 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11445 });
11446 cx.executor().run_until_parked();
11447 multi_buffer_editor.update(cx, |editor, cx| {
11448 let snapshot = editor.snapshot(cx);
11449 let all_hunks = editor_hunks(editor, &snapshot, cx);
11450 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11451 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11452 assert_eq!(all_hunks, expected_all_hunks);
11453 assert_eq!(all_expanded_hunks, Vec::new());
11454 });
11455
11456 multi_buffer_editor.update(cx, |editor, cx| {
11457 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11458 });
11459 cx.executor().run_until_parked();
11460 multi_buffer_editor.update(cx, |editor, cx| {
11461 let snapshot = editor.snapshot(cx);
11462 let all_hunks = editor_hunks(editor, &snapshot, cx);
11463 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11464 assert_eq!(
11465 expanded_hunks_background_highlights(editor, cx),
11466 vec![
11467 DisplayRow(23)..=DisplayRow(23),
11468 DisplayRow(43)..=DisplayRow(43)
11469 ],
11470 );
11471 assert_eq!(all_hunks, expected_all_hunks_shifted);
11472 assert_eq!(all_hunks, all_expanded_hunks);
11473 });
11474
11475 multi_buffer_editor.update(cx, |editor, cx| {
11476 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11477 });
11478 cx.executor().run_until_parked();
11479 multi_buffer_editor.update(cx, |editor, cx| {
11480 let snapshot = editor.snapshot(cx);
11481 let all_hunks = editor_hunks(editor, &snapshot, cx);
11482 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11483 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11484 assert_eq!(all_hunks, expected_all_hunks);
11485 assert_eq!(all_expanded_hunks, Vec::new());
11486 });
11487}
11488
11489#[gpui::test]
11490async fn test_edits_around_toggled_additions(
11491 executor: BackgroundExecutor,
11492 cx: &mut gpui::TestAppContext,
11493) {
11494 init_test(cx, |_| {});
11495
11496 let mut cx = EditorTestContext::new(cx).await;
11497
11498 let diff_base = r#"
11499 use some::mod1;
11500 use some::mod2;
11501
11502 const A: u32 = 42;
11503
11504 fn main() {
11505 println!("hello");
11506
11507 println!("world");
11508 }
11509 "#
11510 .unindent();
11511 executor.run_until_parked();
11512 cx.set_state(
11513 &r#"
11514 use some::mod1;
11515 use some::mod2;
11516
11517 const A: u32 = 42;
11518 const B: u32 = 42;
11519 const C: u32 = 42;
11520 ˇ
11521
11522 fn main() {
11523 println!("hello");
11524
11525 println!("world");
11526 }
11527 "#
11528 .unindent(),
11529 );
11530
11531 cx.set_diff_base(Some(&diff_base));
11532 executor.run_until_parked();
11533 cx.update_editor(|editor, cx| {
11534 let snapshot = editor.snapshot(cx);
11535 let all_hunks = editor_hunks(editor, &snapshot, cx);
11536 assert_eq!(
11537 all_hunks,
11538 vec![(
11539 "".to_string(),
11540 DiffHunkStatus::Added,
11541 DisplayRow(4)..DisplayRow(7)
11542 )]
11543 );
11544 });
11545 cx.update_editor(|editor, cx| {
11546 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11547 });
11548 executor.run_until_parked();
11549 cx.assert_editor_state(
11550 &r#"
11551 use some::mod1;
11552 use some::mod2;
11553
11554 const A: u32 = 42;
11555 const B: u32 = 42;
11556 const C: u32 = 42;
11557 ˇ
11558
11559 fn main() {
11560 println!("hello");
11561
11562 println!("world");
11563 }
11564 "#
11565 .unindent(),
11566 );
11567 cx.update_editor(|editor, cx| {
11568 let snapshot = editor.snapshot(cx);
11569 let all_hunks = editor_hunks(editor, &snapshot, cx);
11570 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11571 assert_eq!(
11572 all_hunks,
11573 vec![(
11574 "".to_string(),
11575 DiffHunkStatus::Added,
11576 DisplayRow(4)..DisplayRow(7)
11577 )]
11578 );
11579 assert_eq!(
11580 expanded_hunks_background_highlights(editor, cx),
11581 vec![DisplayRow(4)..=DisplayRow(6)]
11582 );
11583 assert_eq!(all_hunks, all_expanded_hunks);
11584 });
11585
11586 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
11587 executor.run_until_parked();
11588 cx.assert_editor_state(
11589 &r#"
11590 use some::mod1;
11591 use some::mod2;
11592
11593 const A: u32 = 42;
11594 const B: u32 = 42;
11595 const C: u32 = 42;
11596 const D: u32 = 42;
11597 ˇ
11598
11599 fn main() {
11600 println!("hello");
11601
11602 println!("world");
11603 }
11604 "#
11605 .unindent(),
11606 );
11607 cx.update_editor(|editor, cx| {
11608 let snapshot = editor.snapshot(cx);
11609 let all_hunks = editor_hunks(editor, &snapshot, cx);
11610 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11611 assert_eq!(
11612 all_hunks,
11613 vec![(
11614 "".to_string(),
11615 DiffHunkStatus::Added,
11616 DisplayRow(4)..DisplayRow(8)
11617 )]
11618 );
11619 assert_eq!(
11620 expanded_hunks_background_highlights(editor, cx),
11621 vec![DisplayRow(4)..=DisplayRow(6)],
11622 "Edited hunk should have one more line added"
11623 );
11624 assert_eq!(
11625 all_hunks, all_expanded_hunks,
11626 "Expanded hunk should also grow with the addition"
11627 );
11628 });
11629
11630 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
11631 executor.run_until_parked();
11632 cx.assert_editor_state(
11633 &r#"
11634 use some::mod1;
11635 use some::mod2;
11636
11637 const A: u32 = 42;
11638 const B: u32 = 42;
11639 const C: u32 = 42;
11640 const D: u32 = 42;
11641 const E: u32 = 42;
11642 ˇ
11643
11644 fn main() {
11645 println!("hello");
11646
11647 println!("world");
11648 }
11649 "#
11650 .unindent(),
11651 );
11652 cx.update_editor(|editor, cx| {
11653 let snapshot = editor.snapshot(cx);
11654 let all_hunks = editor_hunks(editor, &snapshot, cx);
11655 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11656 assert_eq!(
11657 all_hunks,
11658 vec![(
11659 "".to_string(),
11660 DiffHunkStatus::Added,
11661 DisplayRow(4)..DisplayRow(9)
11662 )]
11663 );
11664 assert_eq!(
11665 expanded_hunks_background_highlights(editor, cx),
11666 vec![DisplayRow(4)..=DisplayRow(6)],
11667 "Edited hunk should have one more line added"
11668 );
11669 assert_eq!(all_hunks, all_expanded_hunks);
11670 });
11671
11672 cx.update_editor(|editor, cx| {
11673 editor.move_up(&MoveUp, cx);
11674 editor.delete_line(&DeleteLine, cx);
11675 });
11676 executor.run_until_parked();
11677 cx.assert_editor_state(
11678 &r#"
11679 use some::mod1;
11680 use some::mod2;
11681
11682 const A: u32 = 42;
11683 const B: u32 = 42;
11684 const C: u32 = 42;
11685 const D: u32 = 42;
11686 ˇ
11687
11688 fn main() {
11689 println!("hello");
11690
11691 println!("world");
11692 }
11693 "#
11694 .unindent(),
11695 );
11696 cx.update_editor(|editor, cx| {
11697 let snapshot = editor.snapshot(cx);
11698 let all_hunks = editor_hunks(editor, &snapshot, cx);
11699 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11700 assert_eq!(
11701 all_hunks,
11702 vec![(
11703 "".to_string(),
11704 DiffHunkStatus::Added,
11705 DisplayRow(4)..DisplayRow(8)
11706 )]
11707 );
11708 assert_eq!(
11709 expanded_hunks_background_highlights(editor, cx),
11710 vec![DisplayRow(4)..=DisplayRow(6)],
11711 "Deleting a line should shrint the hunk"
11712 );
11713 assert_eq!(
11714 all_hunks, all_expanded_hunks,
11715 "Expanded hunk should also shrink with the addition"
11716 );
11717 });
11718
11719 cx.update_editor(|editor, cx| {
11720 editor.move_up(&MoveUp, cx);
11721 editor.delete_line(&DeleteLine, cx);
11722 editor.move_up(&MoveUp, cx);
11723 editor.delete_line(&DeleteLine, cx);
11724 editor.move_up(&MoveUp, cx);
11725 editor.delete_line(&DeleteLine, cx);
11726 });
11727 executor.run_until_parked();
11728 cx.assert_editor_state(
11729 &r#"
11730 use some::mod1;
11731 use some::mod2;
11732
11733 const A: u32 = 42;
11734 ˇ
11735
11736 fn main() {
11737 println!("hello");
11738
11739 println!("world");
11740 }
11741 "#
11742 .unindent(),
11743 );
11744 cx.update_editor(|editor, cx| {
11745 let snapshot = editor.snapshot(cx);
11746 let all_hunks = editor_hunks(editor, &snapshot, cx);
11747 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11748 assert_eq!(
11749 all_hunks,
11750 vec![(
11751 "".to_string(),
11752 DiffHunkStatus::Added,
11753 DisplayRow(5)..DisplayRow(6)
11754 )]
11755 );
11756 assert_eq!(
11757 expanded_hunks_background_highlights(editor, cx),
11758 vec![DisplayRow(5)..=DisplayRow(5)]
11759 );
11760 assert_eq!(all_hunks, all_expanded_hunks);
11761 });
11762
11763 cx.update_editor(|editor, cx| {
11764 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
11765 editor.delete_line(&DeleteLine, cx);
11766 });
11767 executor.run_until_parked();
11768 cx.assert_editor_state(
11769 &r#"
11770 ˇ
11771
11772 fn main() {
11773 println!("hello");
11774
11775 println!("world");
11776 }
11777 "#
11778 .unindent(),
11779 );
11780 cx.update_editor(|editor, cx| {
11781 let snapshot = editor.snapshot(cx);
11782 let all_hunks = editor_hunks(editor, &snapshot, cx);
11783 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11784 assert_eq!(
11785 all_hunks,
11786 vec![
11787 (
11788 "use some::mod1;\nuse some::mod2;\n".to_string(),
11789 DiffHunkStatus::Removed,
11790 DisplayRow(0)..DisplayRow(0)
11791 ),
11792 (
11793 "const A: u32 = 42;\n".to_string(),
11794 DiffHunkStatus::Removed,
11795 DisplayRow(2)..DisplayRow(2)
11796 )
11797 ]
11798 );
11799 assert_eq!(
11800 expanded_hunks_background_highlights(editor, cx),
11801 Vec::new(),
11802 "Should close all stale expanded addition hunks"
11803 );
11804 assert_eq!(
11805 all_expanded_hunks,
11806 vec![(
11807 "const A: u32 = 42;\n".to_string(),
11808 DiffHunkStatus::Removed,
11809 DisplayRow(2)..DisplayRow(2)
11810 )],
11811 "Should open hunks that were adjacent to the stale addition one"
11812 );
11813 });
11814}
11815
11816#[gpui::test]
11817async fn test_edits_around_toggled_deletions(
11818 executor: BackgroundExecutor,
11819 cx: &mut gpui::TestAppContext,
11820) {
11821 init_test(cx, |_| {});
11822
11823 let mut cx = EditorTestContext::new(cx).await;
11824
11825 let diff_base = r#"
11826 use some::mod1;
11827 use some::mod2;
11828
11829 const A: u32 = 42;
11830 const B: u32 = 42;
11831 const C: u32 = 42;
11832
11833
11834 fn main() {
11835 println!("hello");
11836
11837 println!("world");
11838 }
11839 "#
11840 .unindent();
11841 executor.run_until_parked();
11842 cx.set_state(
11843 &r#"
11844 use some::mod1;
11845 use some::mod2;
11846
11847 ˇconst B: u32 = 42;
11848 const C: u32 = 42;
11849
11850
11851 fn main() {
11852 println!("hello");
11853
11854 println!("world");
11855 }
11856 "#
11857 .unindent(),
11858 );
11859
11860 cx.set_diff_base(Some(&diff_base));
11861 executor.run_until_parked();
11862 cx.update_editor(|editor, cx| {
11863 let snapshot = editor.snapshot(cx);
11864 let all_hunks = editor_hunks(editor, &snapshot, cx);
11865 assert_eq!(
11866 all_hunks,
11867 vec![(
11868 "const A: u32 = 42;\n".to_string(),
11869 DiffHunkStatus::Removed,
11870 DisplayRow(3)..DisplayRow(3)
11871 )]
11872 );
11873 });
11874 cx.update_editor(|editor, cx| {
11875 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11876 });
11877 executor.run_until_parked();
11878 cx.assert_editor_state(
11879 &r#"
11880 use some::mod1;
11881 use some::mod2;
11882
11883 ˇconst B: u32 = 42;
11884 const C: 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!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11900 assert_eq!(
11901 all_hunks,
11902 vec![(
11903 "const A: u32 = 42;\n".to_string(),
11904 DiffHunkStatus::Removed,
11905 DisplayRow(4)..DisplayRow(4)
11906 )]
11907 );
11908 assert_eq!(all_hunks, all_expanded_hunks);
11909 });
11910
11911 cx.update_editor(|editor, cx| {
11912 editor.delete_line(&DeleteLine, cx);
11913 });
11914 executor.run_until_parked();
11915 cx.assert_editor_state(
11916 &r#"
11917 use some::mod1;
11918 use some::mod2;
11919
11920 ˇconst C: u32 = 42;
11921
11922
11923 fn main() {
11924 println!("hello");
11925
11926 println!("world");
11927 }
11928 "#
11929 .unindent(),
11930 );
11931 cx.update_editor(|editor, cx| {
11932 let snapshot = editor.snapshot(cx);
11933 let all_hunks = editor_hunks(editor, &snapshot, cx);
11934 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11935 assert_eq!(
11936 expanded_hunks_background_highlights(editor, cx),
11937 Vec::new(),
11938 "Deleted hunks do not highlight current editor's background"
11939 );
11940 assert_eq!(
11941 all_hunks,
11942 vec![(
11943 "const A: u32 = 42;\nconst B: u32 = 42;\n".to_string(),
11944 DiffHunkStatus::Removed,
11945 DisplayRow(5)..DisplayRow(5)
11946 )]
11947 );
11948 assert_eq!(all_hunks, all_expanded_hunks);
11949 });
11950
11951 cx.update_editor(|editor, cx| {
11952 editor.delete_line(&DeleteLine, cx);
11953 });
11954 executor.run_until_parked();
11955 cx.assert_editor_state(
11956 &r#"
11957 use some::mod1;
11958 use some::mod2;
11959
11960 ˇ
11961
11962 fn main() {
11963 println!("hello");
11964
11965 println!("world");
11966 }
11967 "#
11968 .unindent(),
11969 );
11970 cx.update_editor(|editor, cx| {
11971 let snapshot = editor.snapshot(cx);
11972 let all_hunks = editor_hunks(editor, &snapshot, cx);
11973 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11974 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11975 assert_eq!(
11976 all_hunks,
11977 vec![(
11978 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
11979 DiffHunkStatus::Removed,
11980 DisplayRow(6)..DisplayRow(6)
11981 )]
11982 );
11983 assert_eq!(all_hunks, all_expanded_hunks);
11984 });
11985
11986 cx.update_editor(|editor, cx| {
11987 editor.handle_input("replacement", cx);
11988 });
11989 executor.run_until_parked();
11990 cx.assert_editor_state(
11991 &r#"
11992 use some::mod1;
11993 use some::mod2;
11994
11995 replacementˇ
11996
11997 fn main() {
11998 println!("hello");
11999
12000 println!("world");
12001 }
12002 "#
12003 .unindent(),
12004 );
12005 cx.update_editor(|editor, cx| {
12006 let snapshot = editor.snapshot(cx);
12007 let all_hunks = editor_hunks(editor, &snapshot, cx);
12008 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12009 assert_eq!(
12010 all_hunks,
12011 vec![(
12012 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n\n".to_string(),
12013 DiffHunkStatus::Modified,
12014 DisplayRow(7)..DisplayRow(8)
12015 )]
12016 );
12017 assert_eq!(
12018 expanded_hunks_background_highlights(editor, cx),
12019 vec![DisplayRow(7)..=DisplayRow(7)],
12020 "Modified expanded hunks should display additions and highlight their background"
12021 );
12022 assert_eq!(all_hunks, all_expanded_hunks);
12023 });
12024}
12025
12026#[gpui::test]
12027async fn test_edits_around_toggled_modifications(
12028 executor: BackgroundExecutor,
12029 cx: &mut gpui::TestAppContext,
12030) {
12031 init_test(cx, |_| {});
12032
12033 let mut cx = EditorTestContext::new(cx).await;
12034
12035 let diff_base = r#"
12036 use some::mod1;
12037 use some::mod2;
12038
12039 const A: u32 = 42;
12040 const B: u32 = 42;
12041 const C: u32 = 42;
12042 const D: u32 = 42;
12043
12044
12045 fn main() {
12046 println!("hello");
12047
12048 println!("world");
12049 }"#
12050 .unindent();
12051 executor.run_until_parked();
12052 cx.set_state(
12053 &r#"
12054 use some::mod1;
12055 use some::mod2;
12056
12057 const A: u32 = 42;
12058 const B: u32 = 42;
12059 const C: u32 = 43ˇ
12060 const D: u32 = 42;
12061
12062
12063 fn main() {
12064 println!("hello");
12065
12066 println!("world");
12067 }"#
12068 .unindent(),
12069 );
12070
12071 cx.set_diff_base(Some(&diff_base));
12072 executor.run_until_parked();
12073 cx.update_editor(|editor, cx| {
12074 let snapshot = editor.snapshot(cx);
12075 let all_hunks = editor_hunks(editor, &snapshot, cx);
12076 assert_eq!(
12077 all_hunks,
12078 vec![(
12079 "const C: u32 = 42;\n".to_string(),
12080 DiffHunkStatus::Modified,
12081 DisplayRow(5)..DisplayRow(6)
12082 )]
12083 );
12084 });
12085 cx.update_editor(|editor, cx| {
12086 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12087 });
12088 executor.run_until_parked();
12089 cx.assert_editor_state(
12090 &r#"
12091 use some::mod1;
12092 use some::mod2;
12093
12094 const A: u32 = 42;
12095 const B: u32 = 42;
12096 const C: u32 = 43ˇ
12097 const D: u32 = 42;
12098
12099
12100 fn main() {
12101 println!("hello");
12102
12103 println!("world");
12104 }"#
12105 .unindent(),
12106 );
12107 cx.update_editor(|editor, cx| {
12108 let snapshot = editor.snapshot(cx);
12109 let all_hunks = editor_hunks(editor, &snapshot, cx);
12110 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12111 assert_eq!(
12112 expanded_hunks_background_highlights(editor, cx),
12113 vec![DisplayRow(6)..=DisplayRow(6)],
12114 );
12115 assert_eq!(
12116 all_hunks,
12117 vec![(
12118 "const C: u32 = 42;\n".to_string(),
12119 DiffHunkStatus::Modified,
12120 DisplayRow(6)..DisplayRow(7)
12121 )]
12122 );
12123 assert_eq!(all_hunks, all_expanded_hunks);
12124 });
12125
12126 cx.update_editor(|editor, cx| {
12127 editor.handle_input("\nnew_line\n", cx);
12128 });
12129 executor.run_until_parked();
12130 cx.assert_editor_state(
12131 &r#"
12132 use some::mod1;
12133 use some::mod2;
12134
12135 const A: u32 = 42;
12136 const B: u32 = 42;
12137 const C: u32 = 43
12138 new_line
12139 ˇ
12140 const D: u32 = 42;
12141
12142
12143 fn main() {
12144 println!("hello");
12145
12146 println!("world");
12147 }"#
12148 .unindent(),
12149 );
12150 cx.update_editor(|editor, cx| {
12151 let snapshot = editor.snapshot(cx);
12152 let all_hunks = editor_hunks(editor, &snapshot, cx);
12153 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12154 assert_eq!(
12155 expanded_hunks_background_highlights(editor, cx),
12156 vec![DisplayRow(6)..=DisplayRow(6)],
12157 "Modified hunk should grow highlighted lines on more text additions"
12158 );
12159 assert_eq!(
12160 all_hunks,
12161 vec![(
12162 "const C: u32 = 42;\n".to_string(),
12163 DiffHunkStatus::Modified,
12164 DisplayRow(6)..DisplayRow(9)
12165 )]
12166 );
12167 assert_eq!(all_hunks, all_expanded_hunks);
12168 });
12169
12170 cx.update_editor(|editor, cx| {
12171 editor.move_up(&MoveUp, cx);
12172 editor.move_up(&MoveUp, cx);
12173 editor.move_up(&MoveUp, cx);
12174 editor.delete_line(&DeleteLine, cx);
12175 });
12176 executor.run_until_parked();
12177 cx.assert_editor_state(
12178 &r#"
12179 use some::mod1;
12180 use some::mod2;
12181
12182 const A: u32 = 42;
12183 ˇconst C: u32 = 43
12184 new_line
12185
12186 const D: u32 = 42;
12187
12188
12189 fn main() {
12190 println!("hello");
12191
12192 println!("world");
12193 }"#
12194 .unindent(),
12195 );
12196 cx.update_editor(|editor, cx| {
12197 let snapshot = editor.snapshot(cx);
12198 let all_hunks = editor_hunks(editor, &snapshot, cx);
12199 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12200 assert_eq!(
12201 expanded_hunks_background_highlights(editor, cx),
12202 vec![DisplayRow(6)..=DisplayRow(8)],
12203 );
12204 assert_eq!(
12205 all_hunks,
12206 vec![(
12207 "const B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12208 DiffHunkStatus::Modified,
12209 DisplayRow(6)..DisplayRow(9)
12210 )],
12211 "Modified hunk should grow deleted lines on text deletions above"
12212 );
12213 assert_eq!(all_hunks, all_expanded_hunks);
12214 });
12215
12216 cx.update_editor(|editor, cx| {
12217 editor.move_up(&MoveUp, cx);
12218 editor.handle_input("v", cx);
12219 });
12220 executor.run_until_parked();
12221 cx.assert_editor_state(
12222 &r#"
12223 use some::mod1;
12224 use some::mod2;
12225
12226 vˇconst A: u32 = 42;
12227 const C: u32 = 43
12228 new_line
12229
12230 const D: u32 = 42;
12231
12232
12233 fn main() {
12234 println!("hello");
12235
12236 println!("world");
12237 }"#
12238 .unindent(),
12239 );
12240 cx.update_editor(|editor, cx| {
12241 let snapshot = editor.snapshot(cx);
12242 let all_hunks = editor_hunks(editor, &snapshot, cx);
12243 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12244 assert_eq!(
12245 expanded_hunks_background_highlights(editor, cx),
12246 vec![DisplayRow(6)..=DisplayRow(9)],
12247 "Modified hunk should grow deleted lines on text modifications above"
12248 );
12249 assert_eq!(
12250 all_hunks,
12251 vec![(
12252 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12253 DiffHunkStatus::Modified,
12254 DisplayRow(6)..DisplayRow(10)
12255 )]
12256 );
12257 assert_eq!(all_hunks, all_expanded_hunks);
12258 });
12259
12260 cx.update_editor(|editor, cx| {
12261 editor.move_down(&MoveDown, cx);
12262 editor.move_down(&MoveDown, cx);
12263 editor.delete_line(&DeleteLine, cx)
12264 });
12265 executor.run_until_parked();
12266 cx.assert_editor_state(
12267 &r#"
12268 use some::mod1;
12269 use some::mod2;
12270
12271 vconst A: u32 = 42;
12272 const C: u32 = 43
12273 ˇ
12274 const D: u32 = 42;
12275
12276
12277 fn main() {
12278 println!("hello");
12279
12280 println!("world");
12281 }"#
12282 .unindent(),
12283 );
12284 cx.update_editor(|editor, cx| {
12285 let snapshot = editor.snapshot(cx);
12286 let all_hunks = editor_hunks(editor, &snapshot, cx);
12287 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12288 assert_eq!(
12289 expanded_hunks_background_highlights(editor, cx),
12290 vec![DisplayRow(6)..=DisplayRow(8)],
12291 "Modified hunk should grow shrink lines on modification lines removal"
12292 );
12293 assert_eq!(
12294 all_hunks,
12295 vec![(
12296 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12297 DiffHunkStatus::Modified,
12298 DisplayRow(6)..DisplayRow(9)
12299 )]
12300 );
12301 assert_eq!(all_hunks, all_expanded_hunks);
12302 });
12303
12304 cx.update_editor(|editor, cx| {
12305 editor.move_up(&MoveUp, cx);
12306 editor.move_up(&MoveUp, cx);
12307 editor.select_down_by_lines(&SelectDownByLines { lines: 4 }, cx);
12308 editor.delete_line(&DeleteLine, cx)
12309 });
12310 executor.run_until_parked();
12311 cx.assert_editor_state(
12312 &r#"
12313 use some::mod1;
12314 use some::mod2;
12315
12316 ˇ
12317
12318 fn main() {
12319 println!("hello");
12320
12321 println!("world");
12322 }"#
12323 .unindent(),
12324 );
12325 cx.update_editor(|editor, cx| {
12326 let snapshot = editor.snapshot(cx);
12327 let all_hunks = editor_hunks(editor, &snapshot, cx);
12328 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12329 assert_eq!(
12330 expanded_hunks_background_highlights(editor, cx),
12331 Vec::new(),
12332 "Modified hunk should turn into a removed one on all modified lines removal"
12333 );
12334 assert_eq!(
12335 all_hunks,
12336 vec![(
12337 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\nconst D: u32 = 42;\n"
12338 .to_string(),
12339 DiffHunkStatus::Removed,
12340 DisplayRow(7)..DisplayRow(7)
12341 )]
12342 );
12343 assert_eq!(all_hunks, all_expanded_hunks);
12344 });
12345}
12346
12347#[gpui::test]
12348async fn test_multiple_expanded_hunks_merge(
12349 executor: BackgroundExecutor,
12350 cx: &mut gpui::TestAppContext,
12351) {
12352 init_test(cx, |_| {});
12353
12354 let mut cx = EditorTestContext::new(cx).await;
12355
12356 let diff_base = r#"
12357 use some::mod1;
12358 use some::mod2;
12359
12360 const A: u32 = 42;
12361 const B: u32 = 42;
12362 const C: u32 = 42;
12363 const D: u32 = 42;
12364
12365
12366 fn main() {
12367 println!("hello");
12368
12369 println!("world");
12370 }"#
12371 .unindent();
12372 executor.run_until_parked();
12373 cx.set_state(
12374 &r#"
12375 use some::mod1;
12376 use some::mod2;
12377
12378 const A: u32 = 42;
12379 const B: u32 = 42;
12380 const C: u32 = 43ˇ
12381 const D: u32 = 42;
12382
12383
12384 fn main() {
12385 println!("hello");
12386
12387 println!("world");
12388 }"#
12389 .unindent(),
12390 );
12391
12392 cx.set_diff_base(Some(&diff_base));
12393 executor.run_until_parked();
12394 cx.update_editor(|editor, cx| {
12395 let snapshot = editor.snapshot(cx);
12396 let all_hunks = editor_hunks(editor, &snapshot, cx);
12397 assert_eq!(
12398 all_hunks,
12399 vec![(
12400 "const C: u32 = 42;\n".to_string(),
12401 DiffHunkStatus::Modified,
12402 DisplayRow(5)..DisplayRow(6)
12403 )]
12404 );
12405 });
12406 cx.update_editor(|editor, cx| {
12407 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12408 });
12409 executor.run_until_parked();
12410 cx.assert_editor_state(
12411 &r#"
12412 use some::mod1;
12413 use some::mod2;
12414
12415 const A: u32 = 42;
12416 const B: u32 = 42;
12417 const C: u32 = 43ˇ
12418 const D: u32 = 42;
12419
12420
12421 fn main() {
12422 println!("hello");
12423
12424 println!("world");
12425 }"#
12426 .unindent(),
12427 );
12428 cx.update_editor(|editor, cx| {
12429 let snapshot = editor.snapshot(cx);
12430 let all_hunks = editor_hunks(editor, &snapshot, cx);
12431 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12432 assert_eq!(
12433 expanded_hunks_background_highlights(editor, cx),
12434 vec![DisplayRow(6)..=DisplayRow(6)],
12435 );
12436 assert_eq!(
12437 all_hunks,
12438 vec![(
12439 "const C: u32 = 42;\n".to_string(),
12440 DiffHunkStatus::Modified,
12441 DisplayRow(6)..DisplayRow(7)
12442 )]
12443 );
12444 assert_eq!(all_hunks, all_expanded_hunks);
12445 });
12446
12447 cx.update_editor(|editor, cx| {
12448 editor.handle_input("\nnew_line\n", cx);
12449 });
12450 executor.run_until_parked();
12451 cx.assert_editor_state(
12452 &r#"
12453 use some::mod1;
12454 use some::mod2;
12455
12456 const A: u32 = 42;
12457 const B: u32 = 42;
12458 const C: u32 = 43
12459 new_line
12460 ˇ
12461 const D: u32 = 42;
12462
12463
12464 fn main() {
12465 println!("hello");
12466
12467 println!("world");
12468 }"#
12469 .unindent(),
12470 );
12471}
12472
12473async fn setup_indent_guides_editor(
12474 text: &str,
12475 cx: &mut gpui::TestAppContext,
12476) -> (BufferId, EditorTestContext) {
12477 init_test(cx, |_| {});
12478
12479 let mut cx = EditorTestContext::new(cx).await;
12480
12481 let buffer_id = cx.update_editor(|editor, cx| {
12482 editor.set_text(text, cx);
12483 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12484 let buffer_id = buffer_ids[0];
12485 buffer_id
12486 });
12487
12488 (buffer_id, cx)
12489}
12490
12491fn assert_indent_guides(
12492 range: Range<u32>,
12493 expected: Vec<IndentGuide>,
12494 active_indices: Option<Vec<usize>>,
12495 cx: &mut EditorTestContext,
12496) {
12497 let indent_guides = cx.update_editor(|editor, cx| {
12498 let snapshot = editor.snapshot(cx).display_snapshot;
12499 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12500 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12501 true,
12502 &snapshot,
12503 cx,
12504 );
12505
12506 indent_guides.sort_by(|a, b| {
12507 a.depth.cmp(&b.depth).then(
12508 a.start_row
12509 .cmp(&b.start_row)
12510 .then(a.end_row.cmp(&b.end_row)),
12511 )
12512 });
12513 indent_guides
12514 });
12515
12516 if let Some(expected) = active_indices {
12517 let active_indices = cx.update_editor(|editor, cx| {
12518 let snapshot = editor.snapshot(cx).display_snapshot;
12519 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
12520 });
12521
12522 assert_eq!(
12523 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
12524 expected,
12525 "Active indent guide indices do not match"
12526 );
12527 }
12528
12529 let expected: Vec<_> = expected
12530 .into_iter()
12531 .map(|guide| MultiBufferIndentGuide {
12532 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
12533 buffer: guide,
12534 })
12535 .collect();
12536
12537 assert_eq!(indent_guides, expected, "Indent guides do not match");
12538}
12539
12540fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
12541 IndentGuide {
12542 buffer_id,
12543 start_row,
12544 end_row,
12545 depth,
12546 tab_size: 4,
12547 settings: IndentGuideSettings {
12548 enabled: true,
12549 line_width: 1,
12550 active_line_width: 1,
12551 ..Default::default()
12552 },
12553 }
12554}
12555
12556#[gpui::test]
12557async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12558 let (buffer_id, mut cx) = setup_indent_guides_editor(
12559 &"
12560 fn main() {
12561 let a = 1;
12562 }"
12563 .unindent(),
12564 cx,
12565 )
12566 .await;
12567
12568 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12569}
12570
12571#[gpui::test]
12572async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
12573 let (buffer_id, mut cx) = setup_indent_guides_editor(
12574 &"
12575 fn main() {
12576 let a = 1;
12577 let b = 2;
12578 }"
12579 .unindent(),
12580 cx,
12581 )
12582 .await;
12583
12584 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
12585}
12586
12587#[gpui::test]
12588async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
12589 let (buffer_id, mut cx) = setup_indent_guides_editor(
12590 &"
12591 fn main() {
12592 let a = 1;
12593 if a == 3 {
12594 let b = 2;
12595 } else {
12596 let c = 3;
12597 }
12598 }"
12599 .unindent(),
12600 cx,
12601 )
12602 .await;
12603
12604 assert_indent_guides(
12605 0..8,
12606 vec![
12607 indent_guide(buffer_id, 1, 6, 0),
12608 indent_guide(buffer_id, 3, 3, 1),
12609 indent_guide(buffer_id, 5, 5, 1),
12610 ],
12611 None,
12612 &mut cx,
12613 );
12614}
12615
12616#[gpui::test]
12617async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
12618 let (buffer_id, mut cx) = setup_indent_guides_editor(
12619 &"
12620 fn main() {
12621 let a = 1;
12622 let b = 2;
12623 let c = 3;
12624 }"
12625 .unindent(),
12626 cx,
12627 )
12628 .await;
12629
12630 assert_indent_guides(
12631 0..5,
12632 vec![
12633 indent_guide(buffer_id, 1, 3, 0),
12634 indent_guide(buffer_id, 2, 2, 1),
12635 ],
12636 None,
12637 &mut cx,
12638 );
12639}
12640
12641#[gpui::test]
12642async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
12643 let (buffer_id, mut cx) = setup_indent_guides_editor(
12644 &"
12645 fn main() {
12646 let a = 1;
12647
12648 let c = 3;
12649 }"
12650 .unindent(),
12651 cx,
12652 )
12653 .await;
12654
12655 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
12656}
12657
12658#[gpui::test]
12659async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
12660 let (buffer_id, mut cx) = setup_indent_guides_editor(
12661 &"
12662 fn main() {
12663 let a = 1;
12664
12665 let c = 3;
12666
12667 if a == 3 {
12668 let b = 2;
12669 } else {
12670 let c = 3;
12671 }
12672 }"
12673 .unindent(),
12674 cx,
12675 )
12676 .await;
12677
12678 assert_indent_guides(
12679 0..11,
12680 vec![
12681 indent_guide(buffer_id, 1, 9, 0),
12682 indent_guide(buffer_id, 6, 6, 1),
12683 indent_guide(buffer_id, 8, 8, 1),
12684 ],
12685 None,
12686 &mut cx,
12687 );
12688}
12689
12690#[gpui::test]
12691async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
12692 let (buffer_id, mut cx) = setup_indent_guides_editor(
12693 &"
12694 fn main() {
12695 let a = 1;
12696
12697 let c = 3;
12698
12699 if a == 3 {
12700 let b = 2;
12701 } else {
12702 let c = 3;
12703 }
12704 }"
12705 .unindent(),
12706 cx,
12707 )
12708 .await;
12709
12710 assert_indent_guides(
12711 1..11,
12712 vec![
12713 indent_guide(buffer_id, 1, 9, 0),
12714 indent_guide(buffer_id, 6, 6, 1),
12715 indent_guide(buffer_id, 8, 8, 1),
12716 ],
12717 None,
12718 &mut cx,
12719 );
12720}
12721
12722#[gpui::test]
12723async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
12724 let (buffer_id, mut cx) = setup_indent_guides_editor(
12725 &"
12726 fn main() {
12727 let a = 1;
12728
12729 let c = 3;
12730
12731 if a == 3 {
12732 let b = 2;
12733 } else {
12734 let c = 3;
12735 }
12736 }"
12737 .unindent(),
12738 cx,
12739 )
12740 .await;
12741
12742 assert_indent_guides(
12743 1..10,
12744 vec![
12745 indent_guide(buffer_id, 1, 9, 0),
12746 indent_guide(buffer_id, 6, 6, 1),
12747 indent_guide(buffer_id, 8, 8, 1),
12748 ],
12749 None,
12750 &mut cx,
12751 );
12752}
12753
12754#[gpui::test]
12755async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
12756 let (buffer_id, mut cx) = setup_indent_guides_editor(
12757 &"
12758 block1
12759 block2
12760 block3
12761 block4
12762 block2
12763 block1
12764 block1"
12765 .unindent(),
12766 cx,
12767 )
12768 .await;
12769
12770 assert_indent_guides(
12771 1..10,
12772 vec![
12773 indent_guide(buffer_id, 1, 4, 0),
12774 indent_guide(buffer_id, 2, 3, 1),
12775 indent_guide(buffer_id, 3, 3, 2),
12776 ],
12777 None,
12778 &mut cx,
12779 );
12780}
12781
12782#[gpui::test]
12783async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
12784 let (buffer_id, mut cx) = setup_indent_guides_editor(
12785 &"
12786 block1
12787 block2
12788 block3
12789
12790 block1
12791 block1"
12792 .unindent(),
12793 cx,
12794 )
12795 .await;
12796
12797 assert_indent_guides(
12798 0..6,
12799 vec![
12800 indent_guide(buffer_id, 1, 2, 0),
12801 indent_guide(buffer_id, 2, 2, 1),
12802 ],
12803 None,
12804 &mut cx,
12805 );
12806}
12807
12808#[gpui::test]
12809async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
12810 let (buffer_id, mut cx) = setup_indent_guides_editor(
12811 &"
12812 block1
12813
12814
12815
12816 block2
12817 "
12818 .unindent(),
12819 cx,
12820 )
12821 .await;
12822
12823 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12824}
12825
12826#[gpui::test]
12827async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
12828 let (buffer_id, mut cx) = setup_indent_guides_editor(
12829 &"
12830 def a:
12831 \tb = 3
12832 \tif True:
12833 \t\tc = 4
12834 \t\td = 5
12835 \tprint(b)
12836 "
12837 .unindent(),
12838 cx,
12839 )
12840 .await;
12841
12842 assert_indent_guides(
12843 0..6,
12844 vec![
12845 indent_guide(buffer_id, 1, 6, 0),
12846 indent_guide(buffer_id, 3, 4, 1),
12847 ],
12848 None,
12849 &mut cx,
12850 );
12851}
12852
12853#[gpui::test]
12854async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12855 let (buffer_id, mut cx) = setup_indent_guides_editor(
12856 &"
12857 fn main() {
12858 let a = 1;
12859 }"
12860 .unindent(),
12861 cx,
12862 )
12863 .await;
12864
12865 cx.update_editor(|editor, cx| {
12866 editor.change_selections(None, cx, |s| {
12867 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12868 });
12869 });
12870
12871 assert_indent_guides(
12872 0..3,
12873 vec![indent_guide(buffer_id, 1, 1, 0)],
12874 Some(vec![0]),
12875 &mut cx,
12876 );
12877}
12878
12879#[gpui::test]
12880async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
12881 let (buffer_id, mut cx) = setup_indent_guides_editor(
12882 &"
12883 fn main() {
12884 if 1 == 2 {
12885 let a = 1;
12886 }
12887 }"
12888 .unindent(),
12889 cx,
12890 )
12891 .await;
12892
12893 cx.update_editor(|editor, cx| {
12894 editor.change_selections(None, cx, |s| {
12895 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12896 });
12897 });
12898
12899 assert_indent_guides(
12900 0..4,
12901 vec![
12902 indent_guide(buffer_id, 1, 3, 0),
12903 indent_guide(buffer_id, 2, 2, 1),
12904 ],
12905 Some(vec![1]),
12906 &mut cx,
12907 );
12908
12909 cx.update_editor(|editor, cx| {
12910 editor.change_selections(None, cx, |s| {
12911 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12912 });
12913 });
12914
12915 assert_indent_guides(
12916 0..4,
12917 vec![
12918 indent_guide(buffer_id, 1, 3, 0),
12919 indent_guide(buffer_id, 2, 2, 1),
12920 ],
12921 Some(vec![1]),
12922 &mut cx,
12923 );
12924
12925 cx.update_editor(|editor, cx| {
12926 editor.change_selections(None, cx, |s| {
12927 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
12928 });
12929 });
12930
12931 assert_indent_guides(
12932 0..4,
12933 vec![
12934 indent_guide(buffer_id, 1, 3, 0),
12935 indent_guide(buffer_id, 2, 2, 1),
12936 ],
12937 Some(vec![0]),
12938 &mut cx,
12939 );
12940}
12941
12942#[gpui::test]
12943async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
12944 let (buffer_id, mut cx) = setup_indent_guides_editor(
12945 &"
12946 fn main() {
12947 let a = 1;
12948
12949 let b = 2;
12950 }"
12951 .unindent(),
12952 cx,
12953 )
12954 .await;
12955
12956 cx.update_editor(|editor, cx| {
12957 editor.change_selections(None, cx, |s| {
12958 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12959 });
12960 });
12961
12962 assert_indent_guides(
12963 0..5,
12964 vec![indent_guide(buffer_id, 1, 3, 0)],
12965 Some(vec![0]),
12966 &mut cx,
12967 );
12968}
12969
12970#[gpui::test]
12971async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
12972 let (buffer_id, mut cx) = setup_indent_guides_editor(
12973 &"
12974 def m:
12975 a = 1
12976 pass"
12977 .unindent(),
12978 cx,
12979 )
12980 .await;
12981
12982 cx.update_editor(|editor, cx| {
12983 editor.change_selections(None, cx, |s| {
12984 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12985 });
12986 });
12987
12988 assert_indent_guides(
12989 0..3,
12990 vec![indent_guide(buffer_id, 1, 2, 0)],
12991 Some(vec![0]),
12992 &mut cx,
12993 );
12994}
12995
12996#[gpui::test]
12997fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
12998 init_test(cx, |_| {});
12999
13000 let editor = cx.add_window(|cx| {
13001 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13002 build_editor(buffer, cx)
13003 });
13004
13005 let render_args = Arc::new(Mutex::new(None));
13006 let snapshot = editor
13007 .update(cx, |editor, cx| {
13008 let snapshot = editor.buffer().read(cx).snapshot(cx);
13009 let range =
13010 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13011
13012 struct RenderArgs {
13013 row: MultiBufferRow,
13014 folded: bool,
13015 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13016 }
13017
13018 let crease = Crease::new(
13019 range,
13020 FoldPlaceholder::test(),
13021 {
13022 let toggle_callback = render_args.clone();
13023 move |row, folded, callback, _cx| {
13024 *toggle_callback.lock() = Some(RenderArgs {
13025 row,
13026 folded,
13027 callback,
13028 });
13029 div()
13030 }
13031 },
13032 |_row, _folded, _cx| div(),
13033 );
13034
13035 editor.insert_creases(Some(crease), cx);
13036 let snapshot = editor.snapshot(cx);
13037 let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13038 snapshot
13039 })
13040 .unwrap();
13041
13042 let render_args = render_args.lock().take().unwrap();
13043 assert_eq!(render_args.row, MultiBufferRow(1));
13044 assert_eq!(render_args.folded, false);
13045 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13046
13047 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13048 .unwrap();
13049 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13050 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13051
13052 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13053 .unwrap();
13054 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13055 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13056}
13057
13058fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13059 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13060 point..point
13061}
13062
13063fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13064 let (text, ranges) = marked_text_ranges(marked_text, true);
13065 assert_eq!(view.text(cx), text);
13066 assert_eq!(
13067 view.selections.ranges(cx),
13068 ranges,
13069 "Assert selections are {}",
13070 marked_text
13071 );
13072}
13073
13074pub fn handle_signature_help_request(
13075 cx: &mut EditorLspTestContext,
13076 mocked_response: lsp::SignatureHelp,
13077) -> impl Future<Output = ()> {
13078 let mut request =
13079 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13080 let mocked_response = mocked_response.clone();
13081 async move { Ok(Some(mocked_response)) }
13082 });
13083
13084 async move {
13085 request.next().await;
13086 }
13087}
13088
13089/// Handle completion request passing a marked string specifying where the completion
13090/// should be triggered from using '|' character, what range should be replaced, and what completions
13091/// should be returned using '<' and '>' to delimit the range
13092pub fn handle_completion_request(
13093 cx: &mut EditorLspTestContext,
13094 marked_string: &str,
13095 completions: Vec<&'static str>,
13096 counter: Arc<AtomicUsize>,
13097) -> impl Future<Output = ()> {
13098 let complete_from_marker: TextRangeMarker = '|'.into();
13099 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13100 let (_, mut marked_ranges) = marked_text_ranges_by(
13101 marked_string,
13102 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13103 );
13104
13105 let complete_from_position =
13106 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13107 let replace_range =
13108 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13109
13110 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13111 let completions = completions.clone();
13112 counter.fetch_add(1, atomic::Ordering::Release);
13113 async move {
13114 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13115 assert_eq!(
13116 params.text_document_position.position,
13117 complete_from_position
13118 );
13119 Ok(Some(lsp::CompletionResponse::Array(
13120 completions
13121 .iter()
13122 .map(|completion_text| lsp::CompletionItem {
13123 label: completion_text.to_string(),
13124 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13125 range: replace_range,
13126 new_text: completion_text.to_string(),
13127 })),
13128 ..Default::default()
13129 })
13130 .collect(),
13131 )))
13132 }
13133 });
13134
13135 async move {
13136 request.next().await;
13137 }
13138}
13139
13140fn handle_resolve_completion_request(
13141 cx: &mut EditorLspTestContext,
13142 edits: Option<Vec<(&'static str, &'static str)>>,
13143) -> impl Future<Output = ()> {
13144 let edits = edits.map(|edits| {
13145 edits
13146 .iter()
13147 .map(|(marked_string, new_text)| {
13148 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13149 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13150 lsp::TextEdit::new(replace_range, new_text.to_string())
13151 })
13152 .collect::<Vec<_>>()
13153 });
13154
13155 let mut request =
13156 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13157 let edits = edits.clone();
13158 async move {
13159 Ok(lsp::CompletionItem {
13160 additional_text_edits: edits,
13161 ..Default::default()
13162 })
13163 }
13164 });
13165
13166 async move {
13167 request.next().await;
13168 }
13169}
13170
13171pub(crate) fn update_test_language_settings(
13172 cx: &mut TestAppContext,
13173 f: impl Fn(&mut AllLanguageSettingsContent),
13174) {
13175 _ = cx.update(|cx| {
13176 SettingsStore::update_global(cx, |store, cx| {
13177 store.update_user_settings::<AllLanguageSettings>(cx, f);
13178 });
13179 });
13180}
13181
13182pub(crate) fn update_test_project_settings(
13183 cx: &mut TestAppContext,
13184 f: impl Fn(&mut ProjectSettings),
13185) {
13186 _ = cx.update(|cx| {
13187 SettingsStore::update_global(cx, |store, cx| {
13188 store.update_user_settings::<ProjectSettings>(cx, f);
13189 });
13190 });
13191}
13192
13193pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
13194 _ = cx.update(|cx| {
13195 assets::Assets.load_test_fonts(cx);
13196 let store = SettingsStore::test(cx);
13197 cx.set_global(store);
13198 theme::init(theme::LoadThemes::JustBase, cx);
13199 release_channel::init(SemanticVersion::default(), cx);
13200 client::init_settings(cx);
13201 language::init(cx);
13202 Project::init_settings(cx);
13203 workspace::init_settings(cx);
13204 crate::init(cx);
13205 });
13206
13207 update_test_language_settings(cx, f);
13208}
13209
13210pub(crate) fn rust_lang() -> Arc<Language> {
13211 Arc::new(Language::new(
13212 LanguageConfig {
13213 name: "Rust".into(),
13214 matcher: LanguageMatcher {
13215 path_suffixes: vec!["rs".to_string()],
13216 ..Default::default()
13217 },
13218 ..Default::default()
13219 },
13220 Some(tree_sitter_rust::language()),
13221 ))
13222}
13223
13224#[track_caller]
13225fn assert_hunk_revert(
13226 not_reverted_text_with_selections: &str,
13227 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
13228 expected_reverted_text_with_selections: &str,
13229 base_text: &str,
13230 cx: &mut EditorLspTestContext,
13231) {
13232 cx.set_state(not_reverted_text_with_selections);
13233 cx.update_editor(|editor, cx| {
13234 editor
13235 .buffer()
13236 .read(cx)
13237 .as_singleton()
13238 .unwrap()
13239 .update(cx, |buffer, cx| {
13240 buffer.set_diff_base(Some(base_text.into()), cx);
13241 });
13242 });
13243 cx.executor().run_until_parked();
13244
13245 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
13246 let snapshot = editor.buffer().read(cx).snapshot(cx);
13247 let reverted_hunk_statuses = snapshot
13248 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
13249 .map(|hunk| hunk_status(&hunk))
13250 .collect::<Vec<_>>();
13251
13252 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
13253 reverted_hunk_statuses
13254 });
13255 cx.executor().run_until_parked();
13256 cx.assert_editor_state(expected_reverted_text_with_selections);
13257 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
13258}