1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_hunks,
6 editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext,
7 expanded_hunks, expanded_hunks_background_highlights, select_ranges,
8 },
9 JoinLines,
10};
11use futures::StreamExt;
12use gpui::{
13 div, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds,
14 WindowOptions,
15};
16use indoc::indoc;
17use language::{
18 language_settings::{
19 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
20 },
21 BracketPairConfig,
22 Capability::ReadWrite,
23 FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher, Override,
24 ParsedMarkdown, Point,
25};
26use language_settings::{Formatter, FormatterList, IndentGuideSettings};
27use multi_buffer::MultiBufferIndentGuide;
28use parking_lot::Mutex;
29use project::FakeFs;
30use project::{
31 lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
32 project_settings::{LspSettings, ProjectSettings},
33};
34use serde_json::{self, json};
35use std::sync::atomic;
36use std::sync::atomic::AtomicUsize;
37use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
38use unindent::Unindent;
39use util::{
40 assert_set_eq,
41 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
42};
43use workspace::{
44 item::{FollowEvent, FollowableItem, Item, ItemHandle},
45 NavigationEntry, ViewId,
46};
47
48#[gpui::test]
49fn test_edit_events(cx: &mut TestAppContext) {
50 init_test(cx, |_| {});
51
52 let buffer = cx.new_model(|cx| {
53 let mut buffer = language::Buffer::local("123456", cx);
54 buffer.set_group_interval(Duration::from_secs(1));
55 buffer
56 });
57
58 let events = Rc::new(RefCell::new(Vec::new()));
59 let editor1 = cx.add_window({
60 let events = events.clone();
61 |cx| {
62 let view = cx.view().clone();
63 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
64 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
65 EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
66 _ => {}
67 })
68 .detach();
69 Editor::for_buffer(buffer.clone(), None, cx)
70 }
71 });
72
73 let editor2 = cx.add_window({
74 let events = events.clone();
75 |cx| {
76 cx.subscribe(
77 &cx.view().clone(),
78 move |_, _, event: &EditorEvent, _| match event {
79 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
80 EditorEvent::BufferEdited => {
81 events.borrow_mut().push(("editor2", "buffer edited"))
82 }
83 _ => {}
84 },
85 )
86 .detach();
87 Editor::for_buffer(buffer.clone(), None, cx)
88 }
89 });
90
91 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
92
93 // Mutating editor 1 will emit an `Edited` event only for that editor.
94 _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
95 assert_eq!(
96 mem::take(&mut *events.borrow_mut()),
97 [
98 ("editor1", "edited"),
99 ("editor1", "buffer edited"),
100 ("editor2", "buffer edited"),
101 ]
102 );
103
104 // Mutating editor 2 will emit an `Edited` event only for that editor.
105 _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
106 assert_eq!(
107 mem::take(&mut *events.borrow_mut()),
108 [
109 ("editor2", "edited"),
110 ("editor1", "buffer edited"),
111 ("editor2", "buffer edited"),
112 ]
113 );
114
115 // Undoing on editor 1 will emit an `Edited` event only for that editor.
116 _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
117 assert_eq!(
118 mem::take(&mut *events.borrow_mut()),
119 [
120 ("editor1", "edited"),
121 ("editor1", "buffer edited"),
122 ("editor2", "buffer edited"),
123 ]
124 );
125
126 // Redoing on editor 1 will emit an `Edited` event only for that editor.
127 _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
128 assert_eq!(
129 mem::take(&mut *events.borrow_mut()),
130 [
131 ("editor1", "edited"),
132 ("editor1", "buffer edited"),
133 ("editor2", "buffer edited"),
134 ]
135 );
136
137 // Undoing on editor 2 will emit an `Edited` event only for that editor.
138 _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
139 assert_eq!(
140 mem::take(&mut *events.borrow_mut()),
141 [
142 ("editor2", "edited"),
143 ("editor1", "buffer edited"),
144 ("editor2", "buffer edited"),
145 ]
146 );
147
148 // Redoing on editor 2 will emit an `Edited` event only for that editor.
149 _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
150 assert_eq!(
151 mem::take(&mut *events.borrow_mut()),
152 [
153 ("editor2", "edited"),
154 ("editor1", "buffer edited"),
155 ("editor2", "buffer edited"),
156 ]
157 );
158
159 // No event is emitted when the mutation is a no-op.
160 _ = editor2.update(cx, |editor, cx| {
161 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
162
163 editor.backspace(&Backspace, cx);
164 });
165 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
166}
167
168#[gpui::test]
169fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
170 init_test(cx, |_| {});
171
172 let mut now = Instant::now();
173 let buffer = cx.new_model(|cx| language::Buffer::local("123456", cx));
174 let group_interval = buffer.update(cx, |buffer, _| buffer.transaction_group_interval());
175 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
176 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
177
178 _ = editor.update(cx, |editor, cx| {
179 editor.start_transaction_at(now, cx);
180 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
181
182 editor.insert("cd", cx);
183 editor.end_transaction_at(now, cx);
184 assert_eq!(editor.text(cx), "12cd56");
185 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
186
187 editor.start_transaction_at(now, cx);
188 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
189 editor.insert("e", cx);
190 editor.end_transaction_at(now, cx);
191 assert_eq!(editor.text(cx), "12cde6");
192 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
193
194 now += group_interval + Duration::from_millis(1);
195 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
196
197 // Simulate an edit in another editor
198 _ = buffer.update(cx, |buffer, cx| {
199 buffer.start_transaction_at(now, cx);
200 buffer.edit([(0..1, "a")], None, cx);
201 buffer.edit([(1..1, "b")], None, cx);
202 buffer.end_transaction_at(now, cx);
203 });
204
205 assert_eq!(editor.text(cx), "ab2cde6");
206 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
207
208 // Last transaction happened past the group interval in a different editor.
209 // Undo it individually and don't restore selections.
210 editor.undo(&Undo, cx);
211 assert_eq!(editor.text(cx), "12cde6");
212 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
213
214 // First two transactions happened within the group interval in this editor.
215 // Undo them together and restore selections.
216 editor.undo(&Undo, cx);
217 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
218 assert_eq!(editor.text(cx), "123456");
219 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
220
221 // Redo the first two transactions together.
222 editor.redo(&Redo, cx);
223 assert_eq!(editor.text(cx), "12cde6");
224 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
225
226 // Redo the last transaction on its own.
227 editor.redo(&Redo, cx);
228 assert_eq!(editor.text(cx), "ab2cde6");
229 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
230
231 // Test empty transactions.
232 editor.start_transaction_at(now, cx);
233 editor.end_transaction_at(now, cx);
234 editor.undo(&Undo, cx);
235 assert_eq!(editor.text(cx), "12cde6");
236 });
237}
238
239#[gpui::test]
240fn test_ime_composition(cx: &mut TestAppContext) {
241 init_test(cx, |_| {});
242
243 let buffer = cx.new_model(|cx| {
244 let mut buffer = language::Buffer::local("abcde", cx);
245 // Ensure automatic grouping doesn't occur.
246 buffer.set_group_interval(Duration::ZERO);
247 buffer
248 });
249
250 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
251 cx.add_window(|cx| {
252 let mut editor = build_editor(buffer.clone(), cx);
253
254 // Start a new IME composition.
255 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
256 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
257 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
258 assert_eq!(editor.text(cx), "äbcde");
259 assert_eq!(
260 editor.marked_text_ranges(cx),
261 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
262 );
263
264 // Finalize IME composition.
265 editor.replace_text_in_range(None, "ā", cx);
266 assert_eq!(editor.text(cx), "ābcde");
267 assert_eq!(editor.marked_text_ranges(cx), None);
268
269 // IME composition edits are grouped and are undone/redone at once.
270 editor.undo(&Default::default(), cx);
271 assert_eq!(editor.text(cx), "abcde");
272 assert_eq!(editor.marked_text_ranges(cx), None);
273 editor.redo(&Default::default(), cx);
274 assert_eq!(editor.text(cx), "ābcde");
275 assert_eq!(editor.marked_text_ranges(cx), None);
276
277 // Start a new IME composition.
278 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
279 assert_eq!(
280 editor.marked_text_ranges(cx),
281 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
282 );
283
284 // Undoing during an IME composition cancels it.
285 editor.undo(&Default::default(), cx);
286 assert_eq!(editor.text(cx), "ābcde");
287 assert_eq!(editor.marked_text_ranges(cx), None);
288
289 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
290 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
291 assert_eq!(editor.text(cx), "ābcdè");
292 assert_eq!(
293 editor.marked_text_ranges(cx),
294 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
295 );
296
297 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
298 editor.replace_text_in_range(Some(4..999), "ę", cx);
299 assert_eq!(editor.text(cx), "ābcdę");
300 assert_eq!(editor.marked_text_ranges(cx), None);
301
302 // Start a new IME composition with multiple cursors.
303 editor.change_selections(None, cx, |s| {
304 s.select_ranges([
305 OffsetUtf16(1)..OffsetUtf16(1),
306 OffsetUtf16(3)..OffsetUtf16(3),
307 OffsetUtf16(5)..OffsetUtf16(5),
308 ])
309 });
310 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
311 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
312 assert_eq!(
313 editor.marked_text_ranges(cx),
314 Some(vec![
315 OffsetUtf16(0)..OffsetUtf16(3),
316 OffsetUtf16(4)..OffsetUtf16(7),
317 OffsetUtf16(8)..OffsetUtf16(11)
318 ])
319 );
320
321 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
322 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
323 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
324 assert_eq!(
325 editor.marked_text_ranges(cx),
326 Some(vec![
327 OffsetUtf16(1)..OffsetUtf16(2),
328 OffsetUtf16(5)..OffsetUtf16(6),
329 OffsetUtf16(9)..OffsetUtf16(10)
330 ])
331 );
332
333 // Finalize IME composition with multiple cursors.
334 editor.replace_text_in_range(Some(9..10), "2", cx);
335 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
336 assert_eq!(editor.marked_text_ranges(cx), None);
337
338 editor
339 });
340}
341
342#[gpui::test]
343fn test_selection_with_mouse(cx: &mut TestAppContext) {
344 init_test(cx, |_| {});
345
346 let editor = cx.add_window(|cx| {
347 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
348 build_editor(buffer, cx)
349 });
350
351 _ = editor.update(cx, |view, cx| {
352 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
353 });
354 assert_eq!(
355 editor
356 .update(cx, |view, cx| view.selections.display_ranges(cx))
357 .unwrap(),
358 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
359 );
360
361 _ = editor.update(cx, |view, cx| {
362 view.update_selection(
363 DisplayPoint::new(DisplayRow(3), 3),
364 0,
365 gpui::Point::<f32>::default(),
366 cx,
367 );
368 });
369
370 assert_eq!(
371 editor
372 .update(cx, |view, cx| view.selections.display_ranges(cx))
373 .unwrap(),
374 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
375 );
376
377 _ = editor.update(cx, |view, cx| {
378 view.update_selection(
379 DisplayPoint::new(DisplayRow(1), 1),
380 0,
381 gpui::Point::<f32>::default(),
382 cx,
383 );
384 });
385
386 assert_eq!(
387 editor
388 .update(cx, |view, cx| view.selections.display_ranges(cx))
389 .unwrap(),
390 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
391 );
392
393 _ = editor.update(cx, |view, cx| {
394 view.end_selection(cx);
395 view.update_selection(
396 DisplayPoint::new(DisplayRow(3), 3),
397 0,
398 gpui::Point::<f32>::default(),
399 cx,
400 );
401 });
402
403 assert_eq!(
404 editor
405 .update(cx, |view, cx| view.selections.display_ranges(cx))
406 .unwrap(),
407 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
408 );
409
410 _ = editor.update(cx, |view, cx| {
411 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
412 view.update_selection(
413 DisplayPoint::new(DisplayRow(0), 0),
414 0,
415 gpui::Point::<f32>::default(),
416 cx,
417 );
418 });
419
420 assert_eq!(
421 editor
422 .update(cx, |view, cx| view.selections.display_ranges(cx))
423 .unwrap(),
424 [
425 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
426 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
427 ]
428 );
429
430 _ = editor.update(cx, |view, cx| {
431 view.end_selection(cx);
432 });
433
434 assert_eq!(
435 editor
436 .update(cx, |view, cx| view.selections.display_ranges(cx))
437 .unwrap(),
438 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
439 );
440}
441
442#[gpui::test]
443fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
444 init_test(cx, |_| {});
445
446 let editor = cx.add_window(|cx| {
447 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
448 build_editor(buffer, cx)
449 });
450
451 _ = editor.update(cx, |view, cx| {
452 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
453 });
454
455 _ = editor.update(cx, |view, cx| {
456 view.end_selection(cx);
457 });
458
459 _ = editor.update(cx, |view, cx| {
460 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
461 });
462
463 _ = editor.update(cx, |view, cx| {
464 view.end_selection(cx);
465 });
466
467 assert_eq!(
468 editor
469 .update(cx, |view, cx| view.selections.display_ranges(cx))
470 .unwrap(),
471 [
472 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
473 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
474 ]
475 );
476
477 _ = editor.update(cx, |view, cx| {
478 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
479 });
480
481 _ = editor.update(cx, |view, cx| {
482 view.end_selection(cx);
483 });
484
485 assert_eq!(
486 editor
487 .update(cx, |view, cx| view.selections.display_ranges(cx))
488 .unwrap(),
489 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
490 );
491}
492
493#[gpui::test]
494fn test_canceling_pending_selection(cx: &mut TestAppContext) {
495 init_test(cx, |_| {});
496
497 let view = cx.add_window(|cx| {
498 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
499 build_editor(buffer, cx)
500 });
501
502 _ = view.update(cx, |view, cx| {
503 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
504 assert_eq!(
505 view.selections.display_ranges(cx),
506 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
507 );
508 });
509
510 _ = view.update(cx, |view, cx| {
511 view.update_selection(
512 DisplayPoint::new(DisplayRow(3), 3),
513 0,
514 gpui::Point::<f32>::default(),
515 cx,
516 );
517 assert_eq!(
518 view.selections.display_ranges(cx),
519 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
520 );
521 });
522
523 _ = view.update(cx, |view, cx| {
524 view.cancel(&Cancel, cx);
525 view.update_selection(
526 DisplayPoint::new(DisplayRow(1), 1),
527 0,
528 gpui::Point::<f32>::default(),
529 cx,
530 );
531 assert_eq!(
532 view.selections.display_ranges(cx),
533 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
534 );
535 });
536}
537
538#[gpui::test]
539fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
540 init_test(cx, |_| {});
541
542 let view = cx.add_window(|cx| {
543 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
544 build_editor(buffer, cx)
545 });
546
547 _ = view.update(cx, |view, cx| {
548 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
549 assert_eq!(
550 view.selections.display_ranges(cx),
551 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
552 );
553
554 view.move_down(&Default::default(), cx);
555 assert_eq!(
556 view.selections.display_ranges(cx),
557 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
558 );
559
560 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
561 assert_eq!(
562 view.selections.display_ranges(cx),
563 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
564 );
565
566 view.move_up(&Default::default(), cx);
567 assert_eq!(
568 view.selections.display_ranges(cx),
569 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
570 );
571 });
572}
573
574#[gpui::test]
575fn test_clone(cx: &mut TestAppContext) {
576 init_test(cx, |_| {});
577
578 let (text, selection_ranges) = marked_text_ranges(
579 indoc! {"
580 one
581 two
582 threeˇ
583 four
584 fiveˇ
585 "},
586 true,
587 );
588
589 let editor = cx.add_window(|cx| {
590 let buffer = MultiBuffer::build_simple(&text, cx);
591 build_editor(buffer, cx)
592 });
593
594 _ = editor.update(cx, |editor, cx| {
595 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
596 editor.fold_ranges(
597 [
598 (Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
599 (Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
600 ],
601 true,
602 cx,
603 );
604 });
605
606 let cloned_editor = editor
607 .update(cx, |editor, cx| {
608 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
609 })
610 .unwrap()
611 .unwrap();
612
613 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
614 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
615
616 assert_eq!(
617 cloned_editor
618 .update(cx, |e, cx| e.display_text(cx))
619 .unwrap(),
620 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
621 );
622 assert_eq!(
623 cloned_snapshot
624 .folds_in_range(0..text.len())
625 .collect::<Vec<_>>(),
626 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
627 );
628 assert_set_eq!(
629 cloned_editor
630 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
631 .unwrap(),
632 editor
633 .update(cx, |editor, cx| editor.selections.ranges(cx))
634 .unwrap()
635 );
636 assert_set_eq!(
637 cloned_editor
638 .update(cx, |e, cx| e.selections.display_ranges(cx))
639 .unwrap(),
640 editor
641 .update(cx, |e, cx| e.selections.display_ranges(cx))
642 .unwrap()
643 );
644}
645
646#[gpui::test]
647async fn test_navigation_history(cx: &mut TestAppContext) {
648 init_test(cx, |_| {});
649
650 use workspace::item::Item;
651
652 let fs = FakeFs::new(cx.executor());
653 let project = Project::test(fs, [], cx).await;
654 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
655 let pane = workspace
656 .update(cx, |workspace, _| workspace.active_pane().clone())
657 .unwrap();
658
659 _ = workspace.update(cx, |_v, cx| {
660 cx.new_view(|cx| {
661 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
662 let mut editor = build_editor(buffer.clone(), cx);
663 let handle = cx.view();
664 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
665
666 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
667 editor.nav_history.as_mut().unwrap().pop_backward(cx)
668 }
669
670 // Move the cursor a small distance.
671 // Nothing is added to the navigation history.
672 editor.change_selections(None, cx, |s| {
673 s.select_display_ranges([
674 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
675 ])
676 });
677 editor.change_selections(None, cx, |s| {
678 s.select_display_ranges([
679 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
680 ])
681 });
682 assert!(pop_history(&mut editor, cx).is_none());
683
684 // Move the cursor a large distance.
685 // The history can jump back to the previous position.
686 editor.change_selections(None, cx, |s| {
687 s.select_display_ranges([
688 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
689 ])
690 });
691 let nav_entry = pop_history(&mut editor, cx).unwrap();
692 editor.navigate(nav_entry.data.unwrap(), cx);
693 assert_eq!(nav_entry.item.id(), cx.entity_id());
694 assert_eq!(
695 editor.selections.display_ranges(cx),
696 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
697 );
698 assert!(pop_history(&mut editor, cx).is_none());
699
700 // Move the cursor a small distance via the mouse.
701 // Nothing is added to the navigation history.
702 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
703 editor.end_selection(cx);
704 assert_eq!(
705 editor.selections.display_ranges(cx),
706 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
707 );
708 assert!(pop_history(&mut editor, cx).is_none());
709
710 // Move the cursor a large distance via the mouse.
711 // The history can jump back to the previous position.
712 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
713 editor.end_selection(cx);
714 assert_eq!(
715 editor.selections.display_ranges(cx),
716 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
717 );
718 let nav_entry = pop_history(&mut editor, cx).unwrap();
719 editor.navigate(nav_entry.data.unwrap(), cx);
720 assert_eq!(nav_entry.item.id(), cx.entity_id());
721 assert_eq!(
722 editor.selections.display_ranges(cx),
723 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
724 );
725 assert!(pop_history(&mut editor, cx).is_none());
726
727 // Set scroll position to check later
728 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
729 let original_scroll_position = editor.scroll_manager.anchor();
730
731 // Jump to the end of the document and adjust scroll
732 editor.move_to_end(&MoveToEnd, cx);
733 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
734 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
735
736 let nav_entry = pop_history(&mut editor, cx).unwrap();
737 editor.navigate(nav_entry.data.unwrap(), cx);
738 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
739
740 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
741 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
742 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
743 let invalid_point = Point::new(9999, 0);
744 editor.navigate(
745 Box::new(NavigationData {
746 cursor_anchor: invalid_anchor,
747 cursor_position: invalid_point,
748 scroll_anchor: ScrollAnchor {
749 anchor: invalid_anchor,
750 offset: Default::default(),
751 },
752 scroll_top_row: invalid_point.row,
753 }),
754 cx,
755 );
756 assert_eq!(
757 editor.selections.display_ranges(cx),
758 &[editor.max_point(cx)..editor.max_point(cx)]
759 );
760 assert_eq!(
761 editor.scroll_position(cx),
762 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
763 );
764
765 editor
766 })
767 });
768}
769
770#[gpui::test]
771fn test_cancel(cx: &mut TestAppContext) {
772 init_test(cx, |_| {});
773
774 let view = cx.add_window(|cx| {
775 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
776 build_editor(buffer, cx)
777 });
778
779 _ = view.update(cx, |view, cx| {
780 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
781 view.update_selection(
782 DisplayPoint::new(DisplayRow(1), 1),
783 0,
784 gpui::Point::<f32>::default(),
785 cx,
786 );
787 view.end_selection(cx);
788
789 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
790 view.update_selection(
791 DisplayPoint::new(DisplayRow(0), 3),
792 0,
793 gpui::Point::<f32>::default(),
794 cx,
795 );
796 view.end_selection(cx);
797 assert_eq!(
798 view.selections.display_ranges(cx),
799 [
800 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
801 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
802 ]
803 );
804 });
805
806 _ = view.update(cx, |view, cx| {
807 view.cancel(&Cancel, cx);
808 assert_eq!(
809 view.selections.display_ranges(cx),
810 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
811 );
812 });
813
814 _ = view.update(cx, |view, cx| {
815 view.cancel(&Cancel, cx);
816 assert_eq!(
817 view.selections.display_ranges(cx),
818 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
819 );
820 });
821}
822
823#[gpui::test]
824fn test_fold_action(cx: &mut TestAppContext) {
825 init_test(cx, |_| {});
826
827 let view = cx.add_window(|cx| {
828 let buffer = MultiBuffer::build_simple(
829 &"
830 impl Foo {
831 // Hello!
832
833 fn a() {
834 1
835 }
836
837 fn b() {
838 2
839 }
840
841 fn c() {
842 3
843 }
844 }
845 "
846 .unindent(),
847 cx,
848 );
849 build_editor(buffer.clone(), cx)
850 });
851
852 _ = view.update(cx, |view, cx| {
853 view.change_selections(None, cx, |s| {
854 s.select_display_ranges([
855 DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(12), 0)
856 ]);
857 });
858 view.fold(&Fold, cx);
859 assert_eq!(
860 view.display_text(cx),
861 "
862 impl Foo {
863 // Hello!
864
865 fn a() {
866 1
867 }
868
869 fn b() {⋯
870 }
871
872 fn c() {⋯
873 }
874 }
875 "
876 .unindent(),
877 );
878
879 view.fold(&Fold, cx);
880 assert_eq!(
881 view.display_text(cx),
882 "
883 impl Foo {⋯
884 }
885 "
886 .unindent(),
887 );
888
889 view.unfold_lines(&UnfoldLines, cx);
890 assert_eq!(
891 view.display_text(cx),
892 "
893 impl Foo {
894 // Hello!
895
896 fn a() {
897 1
898 }
899
900 fn b() {⋯
901 }
902
903 fn c() {⋯
904 }
905 }
906 "
907 .unindent(),
908 );
909
910 view.unfold_lines(&UnfoldLines, cx);
911 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
912 });
913}
914
915#[gpui::test]
916fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
917 init_test(cx, |_| {});
918
919 let view = cx.add_window(|cx| {
920 let buffer = MultiBuffer::build_simple(
921 &"
922 class Foo:
923 # Hello!
924
925 def a():
926 print(1)
927
928 def b():
929 print(2)
930
931 def c():
932 print(3)
933 "
934 .unindent(),
935 cx,
936 );
937 build_editor(buffer.clone(), cx)
938 });
939
940 _ = view.update(cx, |view, cx| {
941 view.change_selections(None, cx, |s| {
942 s.select_display_ranges([
943 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(10), 0)
944 ]);
945 });
946 view.fold(&Fold, cx);
947 assert_eq!(
948 view.display_text(cx),
949 "
950 class Foo:
951 # Hello!
952
953 def a():
954 print(1)
955
956 def b():⋯
957
958 def c():⋯
959 "
960 .unindent(),
961 );
962
963 view.fold(&Fold, cx);
964 assert_eq!(
965 view.display_text(cx),
966 "
967 class Foo:⋯
968 "
969 .unindent(),
970 );
971
972 view.unfold_lines(&UnfoldLines, cx);
973 assert_eq!(
974 view.display_text(cx),
975 "
976 class Foo:
977 # Hello!
978
979 def a():
980 print(1)
981
982 def b():⋯
983
984 def c():⋯
985 "
986 .unindent(),
987 );
988
989 view.unfold_lines(&UnfoldLines, cx);
990 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
991 });
992}
993
994#[gpui::test]
995fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
996 init_test(cx, |_| {});
997
998 let view = cx.add_window(|cx| {
999 let buffer = MultiBuffer::build_simple(
1000 &"
1001 class Foo:
1002 # Hello!
1003
1004 def a():
1005 print(1)
1006
1007 def b():
1008 print(2)
1009
1010
1011 def c():
1012 print(3)
1013
1014
1015 "
1016 .unindent(),
1017 cx,
1018 );
1019 build_editor(buffer.clone(), cx)
1020 });
1021
1022 _ = view.update(cx, |view, cx| {
1023 view.change_selections(None, cx, |s| {
1024 s.select_display_ranges([
1025 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(11), 0)
1026 ]);
1027 });
1028 view.fold(&Fold, cx);
1029 assert_eq!(
1030 view.display_text(cx),
1031 "
1032 class Foo:
1033 # Hello!
1034
1035 def a():
1036 print(1)
1037
1038 def b():⋯
1039
1040
1041 def c():⋯
1042
1043
1044 "
1045 .unindent(),
1046 );
1047
1048 view.fold(&Fold, cx);
1049 assert_eq!(
1050 view.display_text(cx),
1051 "
1052 class Foo:⋯
1053
1054
1055 "
1056 .unindent(),
1057 );
1058
1059 view.unfold_lines(&UnfoldLines, cx);
1060 assert_eq!(
1061 view.display_text(cx),
1062 "
1063 class Foo:
1064 # Hello!
1065
1066 def a():
1067 print(1)
1068
1069 def b():⋯
1070
1071
1072 def c():⋯
1073
1074
1075 "
1076 .unindent(),
1077 );
1078
1079 view.unfold_lines(&UnfoldLines, cx);
1080 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1081 });
1082}
1083
1084#[gpui::test]
1085fn test_move_cursor(cx: &mut TestAppContext) {
1086 init_test(cx, |_| {});
1087
1088 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1089 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1090
1091 _ = buffer.update(cx, |buffer, cx| {
1092 buffer.edit(
1093 vec![
1094 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1095 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1096 ],
1097 None,
1098 cx,
1099 );
1100 });
1101 _ = view.update(cx, |view, cx| {
1102 assert_eq!(
1103 view.selections.display_ranges(cx),
1104 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1105 );
1106
1107 view.move_down(&MoveDown, cx);
1108 assert_eq!(
1109 view.selections.display_ranges(cx),
1110 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1111 );
1112
1113 view.move_right(&MoveRight, cx);
1114 assert_eq!(
1115 view.selections.display_ranges(cx),
1116 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1117 );
1118
1119 view.move_left(&MoveLeft, cx);
1120 assert_eq!(
1121 view.selections.display_ranges(cx),
1122 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1123 );
1124
1125 view.move_up(&MoveUp, cx);
1126 assert_eq!(
1127 view.selections.display_ranges(cx),
1128 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1129 );
1130
1131 view.move_to_end(&MoveToEnd, cx);
1132 assert_eq!(
1133 view.selections.display_ranges(cx),
1134 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1135 );
1136
1137 view.move_to_beginning(&MoveToBeginning, cx);
1138 assert_eq!(
1139 view.selections.display_ranges(cx),
1140 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1141 );
1142
1143 view.change_selections(None, cx, |s| {
1144 s.select_display_ranges([
1145 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1146 ]);
1147 });
1148 view.select_to_beginning(&SelectToBeginning, cx);
1149 assert_eq!(
1150 view.selections.display_ranges(cx),
1151 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1152 );
1153
1154 view.select_to_end(&SelectToEnd, cx);
1155 assert_eq!(
1156 view.selections.display_ranges(cx),
1157 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1158 );
1159 });
1160}
1161
1162// TODO: Re-enable this test
1163#[cfg(target_os = "macos")]
1164#[gpui::test]
1165fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1166 init_test(cx, |_| {});
1167
1168 let view = cx.add_window(|cx| {
1169 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1170 build_editor(buffer.clone(), cx)
1171 });
1172
1173 assert_eq!('ⓐ'.len_utf8(), 3);
1174 assert_eq!('α'.len_utf8(), 2);
1175
1176 _ = view.update(cx, |view, cx| {
1177 view.fold_ranges(
1178 vec![
1179 (Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1180 (Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1181 (Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1182 ],
1183 true,
1184 cx,
1185 );
1186 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1187
1188 view.move_right(&MoveRight, cx);
1189 assert_eq!(
1190 view.selections.display_ranges(cx),
1191 &[empty_range(0, "ⓐ".len())]
1192 );
1193 view.move_right(&MoveRight, cx);
1194 assert_eq!(
1195 view.selections.display_ranges(cx),
1196 &[empty_range(0, "ⓐⓑ".len())]
1197 );
1198 view.move_right(&MoveRight, cx);
1199 assert_eq!(
1200 view.selections.display_ranges(cx),
1201 &[empty_range(0, "ⓐⓑ⋯".len())]
1202 );
1203
1204 view.move_down(&MoveDown, cx);
1205 assert_eq!(
1206 view.selections.display_ranges(cx),
1207 &[empty_range(1, "ab⋯e".len())]
1208 );
1209 view.move_left(&MoveLeft, cx);
1210 assert_eq!(
1211 view.selections.display_ranges(cx),
1212 &[empty_range(1, "ab⋯".len())]
1213 );
1214 view.move_left(&MoveLeft, cx);
1215 assert_eq!(
1216 view.selections.display_ranges(cx),
1217 &[empty_range(1, "ab".len())]
1218 );
1219 view.move_left(&MoveLeft, cx);
1220 assert_eq!(
1221 view.selections.display_ranges(cx),
1222 &[empty_range(1, "a".len())]
1223 );
1224
1225 view.move_down(&MoveDown, cx);
1226 assert_eq!(
1227 view.selections.display_ranges(cx),
1228 &[empty_range(2, "α".len())]
1229 );
1230 view.move_right(&MoveRight, cx);
1231 assert_eq!(
1232 view.selections.display_ranges(cx),
1233 &[empty_range(2, "αβ".len())]
1234 );
1235 view.move_right(&MoveRight, cx);
1236 assert_eq!(
1237 view.selections.display_ranges(cx),
1238 &[empty_range(2, "αβ⋯".len())]
1239 );
1240 view.move_right(&MoveRight, cx);
1241 assert_eq!(
1242 view.selections.display_ranges(cx),
1243 &[empty_range(2, "αβ⋯ε".len())]
1244 );
1245
1246 view.move_up(&MoveUp, cx);
1247 assert_eq!(
1248 view.selections.display_ranges(cx),
1249 &[empty_range(1, "ab⋯e".len())]
1250 );
1251 view.move_down(&MoveDown, cx);
1252 assert_eq!(
1253 view.selections.display_ranges(cx),
1254 &[empty_range(2, "αβ⋯ε".len())]
1255 );
1256 view.move_up(&MoveUp, cx);
1257 assert_eq!(
1258 view.selections.display_ranges(cx),
1259 &[empty_range(1, "ab⋯e".len())]
1260 );
1261
1262 view.move_up(&MoveUp, cx);
1263 assert_eq!(
1264 view.selections.display_ranges(cx),
1265 &[empty_range(0, "ⓐⓑ".len())]
1266 );
1267 view.move_left(&MoveLeft, cx);
1268 assert_eq!(
1269 view.selections.display_ranges(cx),
1270 &[empty_range(0, "ⓐ".len())]
1271 );
1272 view.move_left(&MoveLeft, cx);
1273 assert_eq!(
1274 view.selections.display_ranges(cx),
1275 &[empty_range(0, "".len())]
1276 );
1277 });
1278}
1279
1280#[gpui::test]
1281fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1282 init_test(cx, |_| {});
1283
1284 let view = cx.add_window(|cx| {
1285 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1286 build_editor(buffer.clone(), cx)
1287 });
1288 _ = view.update(cx, |view, cx| {
1289 view.change_selections(None, cx, |s| {
1290 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1291 });
1292 view.move_down(&MoveDown, cx);
1293 assert_eq!(
1294 view.selections.display_ranges(cx),
1295 &[empty_range(1, "abcd".len())]
1296 );
1297
1298 view.move_down(&MoveDown, cx);
1299 assert_eq!(
1300 view.selections.display_ranges(cx),
1301 &[empty_range(2, "αβγ".len())]
1302 );
1303
1304 view.move_down(&MoveDown, cx);
1305 assert_eq!(
1306 view.selections.display_ranges(cx),
1307 &[empty_range(3, "abcd".len())]
1308 );
1309
1310 view.move_down(&MoveDown, cx);
1311 assert_eq!(
1312 view.selections.display_ranges(cx),
1313 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1314 );
1315
1316 view.move_up(&MoveUp, cx);
1317 assert_eq!(
1318 view.selections.display_ranges(cx),
1319 &[empty_range(3, "abcd".len())]
1320 );
1321
1322 view.move_up(&MoveUp, cx);
1323 assert_eq!(
1324 view.selections.display_ranges(cx),
1325 &[empty_range(2, "αβγ".len())]
1326 );
1327 });
1328}
1329
1330#[gpui::test]
1331fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1332 init_test(cx, |_| {});
1333 let move_to_beg = MoveToBeginningOfLine {
1334 stop_at_soft_wraps: true,
1335 };
1336
1337 let move_to_end = MoveToEndOfLine {
1338 stop_at_soft_wraps: true,
1339 };
1340
1341 let view = cx.add_window(|cx| {
1342 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1343 build_editor(buffer, cx)
1344 });
1345 _ = view.update(cx, |view, cx| {
1346 view.change_selections(None, cx, |s| {
1347 s.select_display_ranges([
1348 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1349 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1350 ]);
1351 });
1352 });
1353
1354 _ = view.update(cx, |view, cx| {
1355 view.move_to_beginning_of_line(&move_to_beg, cx);
1356 assert_eq!(
1357 view.selections.display_ranges(cx),
1358 &[
1359 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1360 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1361 ]
1362 );
1363 });
1364
1365 _ = view.update(cx, |view, cx| {
1366 view.move_to_beginning_of_line(&move_to_beg, cx);
1367 assert_eq!(
1368 view.selections.display_ranges(cx),
1369 &[
1370 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1371 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1372 ]
1373 );
1374 });
1375
1376 _ = view.update(cx, |view, cx| {
1377 view.move_to_beginning_of_line(&move_to_beg, cx);
1378 assert_eq!(
1379 view.selections.display_ranges(cx),
1380 &[
1381 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1382 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1383 ]
1384 );
1385 });
1386
1387 _ = view.update(cx, |view, cx| {
1388 view.move_to_end_of_line(&move_to_end, cx);
1389 assert_eq!(
1390 view.selections.display_ranges(cx),
1391 &[
1392 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1393 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1394 ]
1395 );
1396 });
1397
1398 // Moving to the end of line again is a no-op.
1399 _ = view.update(cx, |view, cx| {
1400 view.move_to_end_of_line(&move_to_end, cx);
1401 assert_eq!(
1402 view.selections.display_ranges(cx),
1403 &[
1404 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1405 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1406 ]
1407 );
1408 });
1409
1410 _ = view.update(cx, |view, cx| {
1411 view.move_left(&MoveLeft, cx);
1412 view.select_to_beginning_of_line(
1413 &SelectToBeginningOfLine {
1414 stop_at_soft_wraps: true,
1415 },
1416 cx,
1417 );
1418 assert_eq!(
1419 view.selections.display_ranges(cx),
1420 &[
1421 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1422 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1423 ]
1424 );
1425 });
1426
1427 _ = view.update(cx, |view, cx| {
1428 view.select_to_beginning_of_line(
1429 &SelectToBeginningOfLine {
1430 stop_at_soft_wraps: true,
1431 },
1432 cx,
1433 );
1434 assert_eq!(
1435 view.selections.display_ranges(cx),
1436 &[
1437 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1438 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1439 ]
1440 );
1441 });
1442
1443 _ = view.update(cx, |view, cx| {
1444 view.select_to_beginning_of_line(
1445 &SelectToBeginningOfLine {
1446 stop_at_soft_wraps: true,
1447 },
1448 cx,
1449 );
1450 assert_eq!(
1451 view.selections.display_ranges(cx),
1452 &[
1453 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1454 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1455 ]
1456 );
1457 });
1458
1459 _ = view.update(cx, |view, cx| {
1460 view.select_to_end_of_line(
1461 &SelectToEndOfLine {
1462 stop_at_soft_wraps: true,
1463 },
1464 cx,
1465 );
1466 assert_eq!(
1467 view.selections.display_ranges(cx),
1468 &[
1469 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1470 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1471 ]
1472 );
1473 });
1474
1475 _ = view.update(cx, |view, cx| {
1476 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1477 assert_eq!(view.display_text(cx), "ab\n de");
1478 assert_eq!(
1479 view.selections.display_ranges(cx),
1480 &[
1481 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1482 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1483 ]
1484 );
1485 });
1486
1487 _ = view.update(cx, |view, cx| {
1488 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1489 assert_eq!(view.display_text(cx), "\n");
1490 assert_eq!(
1491 view.selections.display_ranges(cx),
1492 &[
1493 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1494 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1495 ]
1496 );
1497 });
1498}
1499
1500#[gpui::test]
1501fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1502 init_test(cx, |_| {});
1503 let move_to_beg = MoveToBeginningOfLine {
1504 stop_at_soft_wraps: false,
1505 };
1506
1507 let move_to_end = MoveToEndOfLine {
1508 stop_at_soft_wraps: false,
1509 };
1510
1511 let view = cx.add_window(|cx| {
1512 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1513 build_editor(buffer, cx)
1514 });
1515
1516 _ = view.update(cx, |view, cx| {
1517 view.set_wrap_width(Some(140.0.into()), cx);
1518
1519 // We expect the following lines after wrapping
1520 // ```
1521 // thequickbrownfox
1522 // jumpedoverthelazydo
1523 // gs
1524 // ```
1525 // The final `gs` was soft-wrapped onto a new line.
1526 assert_eq!(
1527 "thequickbrownfox\njumpedoverthelaz\nydogs",
1528 view.display_text(cx),
1529 );
1530
1531 // First, let's assert behavior on the first line, that was not soft-wrapped.
1532 // Start the cursor at the `k` on the first line
1533 view.change_selections(None, cx, |s| {
1534 s.select_display_ranges([
1535 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1536 ]);
1537 });
1538
1539 // Moving to the beginning of the line should put us at the beginning of the line.
1540 view.move_to_beginning_of_line(&move_to_beg, cx);
1541 assert_eq!(
1542 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1543 view.selections.display_ranges(cx)
1544 );
1545
1546 // Moving to the end of the line should put us at the end of the line.
1547 view.move_to_end_of_line(&move_to_end, cx);
1548 assert_eq!(
1549 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1550 view.selections.display_ranges(cx)
1551 );
1552
1553 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1554 // Start the cursor at the last line (`y` that was wrapped to a new line)
1555 view.change_selections(None, cx, |s| {
1556 s.select_display_ranges([
1557 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1558 ]);
1559 });
1560
1561 // Moving to the beginning of the line should put us at the start of the second line of
1562 // display text, i.e., the `j`.
1563 view.move_to_beginning_of_line(&move_to_beg, cx);
1564 assert_eq!(
1565 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1566 view.selections.display_ranges(cx)
1567 );
1568
1569 // Moving to the beginning of the line again should be a no-op.
1570 view.move_to_beginning_of_line(&move_to_beg, cx);
1571 assert_eq!(
1572 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1573 view.selections.display_ranges(cx)
1574 );
1575
1576 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1577 // next display line.
1578 view.move_to_end_of_line(&move_to_end, cx);
1579 assert_eq!(
1580 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1581 view.selections.display_ranges(cx)
1582 );
1583
1584 // Moving to the end of the line again should be a no-op.
1585 view.move_to_end_of_line(&move_to_end, cx);
1586 assert_eq!(
1587 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1588 view.selections.display_ranges(cx)
1589 );
1590 });
1591}
1592
1593#[gpui::test]
1594fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1595 init_test(cx, |_| {});
1596
1597 let view = cx.add_window(|cx| {
1598 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1599 build_editor(buffer, cx)
1600 });
1601 _ = view.update(cx, |view, cx| {
1602 view.change_selections(None, cx, |s| {
1603 s.select_display_ranges([
1604 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1605 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1606 ])
1607 });
1608
1609 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1610 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1611
1612 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1613 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1614
1615 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1616 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1617
1618 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1619 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1620
1621 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1622 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1623
1624 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1625 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1626
1627 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1628 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1629
1630 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1631 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1632
1633 view.move_right(&MoveRight, cx);
1634 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1635 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1636
1637 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1638 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1639
1640 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1641 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1642 });
1643}
1644
1645#[gpui::test]
1646fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1647 init_test(cx, |_| {});
1648
1649 let view = cx.add_window(|cx| {
1650 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1651 build_editor(buffer, cx)
1652 });
1653
1654 _ = view.update(cx, |view, cx| {
1655 view.set_wrap_width(Some(140.0.into()), cx);
1656 assert_eq!(
1657 view.display_text(cx),
1658 "use one::{\n two::three::\n four::five\n};"
1659 );
1660
1661 view.change_selections(None, cx, |s| {
1662 s.select_display_ranges([
1663 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1664 ]);
1665 });
1666
1667 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1668 assert_eq!(
1669 view.selections.display_ranges(cx),
1670 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1671 );
1672
1673 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1674 assert_eq!(
1675 view.selections.display_ranges(cx),
1676 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1677 );
1678
1679 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1680 assert_eq!(
1681 view.selections.display_ranges(cx),
1682 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1683 );
1684
1685 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1686 assert_eq!(
1687 view.selections.display_ranges(cx),
1688 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1689 );
1690
1691 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1692 assert_eq!(
1693 view.selections.display_ranges(cx),
1694 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1695 );
1696
1697 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1698 assert_eq!(
1699 view.selections.display_ranges(cx),
1700 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1701 );
1702 });
1703}
1704
1705#[gpui::test]
1706async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1707 init_test(cx, |_| {});
1708 let mut cx = EditorTestContext::new(cx).await;
1709
1710 let line_height = cx.editor(|editor, cx| {
1711 editor
1712 .style()
1713 .unwrap()
1714 .text
1715 .line_height_in_pixels(cx.rem_size())
1716 });
1717 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1718
1719 cx.set_state(
1720 &r#"ˇone
1721 two
1722
1723 three
1724 fourˇ
1725 five
1726
1727 six"#
1728 .unindent(),
1729 );
1730
1731 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1732 cx.assert_editor_state(
1733 &r#"one
1734 two
1735 ˇ
1736 three
1737 four
1738 five
1739 ˇ
1740 six"#
1741 .unindent(),
1742 );
1743
1744 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1745 cx.assert_editor_state(
1746 &r#"one
1747 two
1748
1749 three
1750 four
1751 five
1752 ˇ
1753 sixˇ"#
1754 .unindent(),
1755 );
1756
1757 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1758 cx.assert_editor_state(
1759 &r#"one
1760 two
1761
1762 three
1763 four
1764 five
1765
1766 sixˇ"#
1767 .unindent(),
1768 );
1769
1770 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1771 cx.assert_editor_state(
1772 &r#"one
1773 two
1774
1775 three
1776 four
1777 five
1778 ˇ
1779 six"#
1780 .unindent(),
1781 );
1782
1783 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1784 cx.assert_editor_state(
1785 &r#"one
1786 two
1787 ˇ
1788 three
1789 four
1790 five
1791
1792 six"#
1793 .unindent(),
1794 );
1795
1796 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1797 cx.assert_editor_state(
1798 &r#"ˇone
1799 two
1800
1801 three
1802 four
1803 five
1804
1805 six"#
1806 .unindent(),
1807 );
1808}
1809
1810#[gpui::test]
1811async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1812 init_test(cx, |_| {});
1813 let mut cx = EditorTestContext::new(cx).await;
1814 let line_height = cx.editor(|editor, cx| {
1815 editor
1816 .style()
1817 .unwrap()
1818 .text
1819 .line_height_in_pixels(cx.rem_size())
1820 });
1821 let window = cx.window;
1822 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1823
1824 cx.set_state(
1825 &r#"ˇone
1826 two
1827 three
1828 four
1829 five
1830 six
1831 seven
1832 eight
1833 nine
1834 ten
1835 "#,
1836 );
1837
1838 cx.update_editor(|editor, cx| {
1839 assert_eq!(
1840 editor.snapshot(cx).scroll_position(),
1841 gpui::Point::new(0., 0.)
1842 );
1843 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1844 assert_eq!(
1845 editor.snapshot(cx).scroll_position(),
1846 gpui::Point::new(0., 3.)
1847 );
1848 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1849 assert_eq!(
1850 editor.snapshot(cx).scroll_position(),
1851 gpui::Point::new(0., 6.)
1852 );
1853 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1854 assert_eq!(
1855 editor.snapshot(cx).scroll_position(),
1856 gpui::Point::new(0., 3.)
1857 );
1858
1859 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1860 assert_eq!(
1861 editor.snapshot(cx).scroll_position(),
1862 gpui::Point::new(0., 1.)
1863 );
1864 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
1865 assert_eq!(
1866 editor.snapshot(cx).scroll_position(),
1867 gpui::Point::new(0., 3.)
1868 );
1869 });
1870}
1871
1872#[gpui::test]
1873async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
1874 init_test(cx, |_| {});
1875 let mut cx = EditorTestContext::new(cx).await;
1876
1877 let line_height = cx.update_editor(|editor, cx| {
1878 editor.set_vertical_scroll_margin(2, cx);
1879 editor
1880 .style()
1881 .unwrap()
1882 .text
1883 .line_height_in_pixels(cx.rem_size())
1884 });
1885 let window = cx.window;
1886 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
1887
1888 cx.set_state(
1889 &r#"ˇone
1890 two
1891 three
1892 four
1893 five
1894 six
1895 seven
1896 eight
1897 nine
1898 ten
1899 "#,
1900 );
1901 cx.update_editor(|editor, cx| {
1902 assert_eq!(
1903 editor.snapshot(cx).scroll_position(),
1904 gpui::Point::new(0., 0.0)
1905 );
1906 });
1907
1908 // Add a cursor below the visible area. Since both cursors cannot fit
1909 // on screen, the editor autoscrolls to reveal the newest cursor, and
1910 // allows the vertical scroll margin below that cursor.
1911 cx.update_editor(|editor, cx| {
1912 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1913 selections.select_ranges([
1914 Point::new(0, 0)..Point::new(0, 0),
1915 Point::new(6, 0)..Point::new(6, 0),
1916 ]);
1917 })
1918 });
1919 cx.update_editor(|editor, cx| {
1920 assert_eq!(
1921 editor.snapshot(cx).scroll_position(),
1922 gpui::Point::new(0., 3.0)
1923 );
1924 });
1925
1926 // Move down. The editor cursor scrolls down to track the newest cursor.
1927 cx.update_editor(|editor, cx| {
1928 editor.move_down(&Default::default(), cx);
1929 });
1930 cx.update_editor(|editor, cx| {
1931 assert_eq!(
1932 editor.snapshot(cx).scroll_position(),
1933 gpui::Point::new(0., 4.0)
1934 );
1935 });
1936
1937 // Add a cursor above the visible area. Since both cursors fit on screen,
1938 // the editor scrolls to show both.
1939 cx.update_editor(|editor, cx| {
1940 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1941 selections.select_ranges([
1942 Point::new(1, 0)..Point::new(1, 0),
1943 Point::new(6, 0)..Point::new(6, 0),
1944 ]);
1945 })
1946 });
1947 cx.update_editor(|editor, cx| {
1948 assert_eq!(
1949 editor.snapshot(cx).scroll_position(),
1950 gpui::Point::new(0., 1.0)
1951 );
1952 });
1953}
1954
1955#[gpui::test]
1956async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
1957 init_test(cx, |_| {});
1958 let mut cx = EditorTestContext::new(cx).await;
1959
1960 let line_height = cx.editor(|editor, cx| {
1961 editor
1962 .style()
1963 .unwrap()
1964 .text
1965 .line_height_in_pixels(cx.rem_size())
1966 });
1967 let window = cx.window;
1968 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
1969 cx.set_state(
1970 &r#"
1971 ˇone
1972 two
1973 threeˇ
1974 four
1975 five
1976 six
1977 seven
1978 eight
1979 nine
1980 ten
1981 "#
1982 .unindent(),
1983 );
1984
1985 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1986 cx.assert_editor_state(
1987 &r#"
1988 one
1989 two
1990 three
1991 ˇfour
1992 five
1993 sixˇ
1994 seven
1995 eight
1996 nine
1997 ten
1998 "#
1999 .unindent(),
2000 );
2001
2002 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2003 cx.assert_editor_state(
2004 &r#"
2005 one
2006 two
2007 three
2008 four
2009 five
2010 six
2011 ˇseven
2012 eight
2013 nineˇ
2014 ten
2015 "#
2016 .unindent(),
2017 );
2018
2019 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2020 cx.assert_editor_state(
2021 &r#"
2022 one
2023 two
2024 three
2025 ˇfour
2026 five
2027 sixˇ
2028 seven
2029 eight
2030 nine
2031 ten
2032 "#
2033 .unindent(),
2034 );
2035
2036 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2037 cx.assert_editor_state(
2038 &r#"
2039 ˇone
2040 two
2041 threeˇ
2042 four
2043 five
2044 six
2045 seven
2046 eight
2047 nine
2048 ten
2049 "#
2050 .unindent(),
2051 );
2052
2053 // Test select collapsing
2054 cx.update_editor(|editor, cx| {
2055 editor.move_page_down(&MovePageDown::default(), cx);
2056 editor.move_page_down(&MovePageDown::default(), cx);
2057 editor.move_page_down(&MovePageDown::default(), cx);
2058 });
2059 cx.assert_editor_state(
2060 &r#"
2061 one
2062 two
2063 three
2064 four
2065 five
2066 six
2067 seven
2068 eight
2069 nine
2070 ˇten
2071 ˇ"#
2072 .unindent(),
2073 );
2074}
2075
2076#[gpui::test]
2077async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2078 init_test(cx, |_| {});
2079 let mut cx = EditorTestContext::new(cx).await;
2080 cx.set_state("one «two threeˇ» four");
2081 cx.update_editor(|editor, cx| {
2082 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2083 assert_eq!(editor.text(cx), " four");
2084 });
2085}
2086
2087#[gpui::test]
2088fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2089 init_test(cx, |_| {});
2090
2091 let view = cx.add_window(|cx| {
2092 let buffer = MultiBuffer::build_simple("one two three four", cx);
2093 build_editor(buffer.clone(), cx)
2094 });
2095
2096 _ = view.update(cx, |view, cx| {
2097 view.change_selections(None, cx, |s| {
2098 s.select_display_ranges([
2099 // an empty selection - the preceding word fragment is deleted
2100 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2101 // characters selected - they are deleted
2102 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2103 ])
2104 });
2105 view.delete_to_previous_word_start(&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 priority: 0,
3789 }],
3790 Some(Autoscroll::fit()),
3791 cx,
3792 );
3793 editor.change_selections(None, cx, |s| {
3794 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
3795 });
3796 editor.move_line_down(&MoveLineDown, cx);
3797 });
3798}
3799
3800#[gpui::test]
3801fn test_transpose(cx: &mut TestAppContext) {
3802 init_test(cx, |_| {});
3803
3804 _ = cx.add_window(|cx| {
3805 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
3806 editor.set_style(EditorStyle::default(), cx);
3807 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
3808 editor.transpose(&Default::default(), cx);
3809 assert_eq!(editor.text(cx), "bac");
3810 assert_eq!(editor.selections.ranges(cx), [2..2]);
3811
3812 editor.transpose(&Default::default(), cx);
3813 assert_eq!(editor.text(cx), "bca");
3814 assert_eq!(editor.selections.ranges(cx), [3..3]);
3815
3816 editor.transpose(&Default::default(), cx);
3817 assert_eq!(editor.text(cx), "bac");
3818 assert_eq!(editor.selections.ranges(cx), [3..3]);
3819
3820 editor
3821 });
3822
3823 _ = cx.add_window(|cx| {
3824 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3825 editor.set_style(EditorStyle::default(), cx);
3826 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
3827 editor.transpose(&Default::default(), cx);
3828 assert_eq!(editor.text(cx), "acb\nde");
3829 assert_eq!(editor.selections.ranges(cx), [3..3]);
3830
3831 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3832 editor.transpose(&Default::default(), cx);
3833 assert_eq!(editor.text(cx), "acbd\ne");
3834 assert_eq!(editor.selections.ranges(cx), [5..5]);
3835
3836 editor.transpose(&Default::default(), cx);
3837 assert_eq!(editor.text(cx), "acbde\n");
3838 assert_eq!(editor.selections.ranges(cx), [6..6]);
3839
3840 editor.transpose(&Default::default(), cx);
3841 assert_eq!(editor.text(cx), "acbd\ne");
3842 assert_eq!(editor.selections.ranges(cx), [6..6]);
3843
3844 editor
3845 });
3846
3847 _ = cx.add_window(|cx| {
3848 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3849 editor.set_style(EditorStyle::default(), cx);
3850 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
3851 editor.transpose(&Default::default(), cx);
3852 assert_eq!(editor.text(cx), "bacd\ne");
3853 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
3854
3855 editor.transpose(&Default::default(), cx);
3856 assert_eq!(editor.text(cx), "bcade\n");
3857 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
3858
3859 editor.transpose(&Default::default(), cx);
3860 assert_eq!(editor.text(cx), "bcda\ne");
3861 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3862
3863 editor.transpose(&Default::default(), cx);
3864 assert_eq!(editor.text(cx), "bcade\n");
3865 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3866
3867 editor.transpose(&Default::default(), cx);
3868 assert_eq!(editor.text(cx), "bcaed\n");
3869 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
3870
3871 editor
3872 });
3873
3874 _ = cx.add_window(|cx| {
3875 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
3876 editor.set_style(EditorStyle::default(), cx);
3877 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3878 editor.transpose(&Default::default(), cx);
3879 assert_eq!(editor.text(cx), "🏀🍐✋");
3880 assert_eq!(editor.selections.ranges(cx), [8..8]);
3881
3882 editor.transpose(&Default::default(), cx);
3883 assert_eq!(editor.text(cx), "🏀✋🍐");
3884 assert_eq!(editor.selections.ranges(cx), [11..11]);
3885
3886 editor.transpose(&Default::default(), cx);
3887 assert_eq!(editor.text(cx), "🏀🍐✋");
3888 assert_eq!(editor.selections.ranges(cx), [11..11]);
3889
3890 editor
3891 });
3892}
3893
3894#[gpui::test]
3895async fn test_clipboard(cx: &mut gpui::TestAppContext) {
3896 init_test(cx, |_| {});
3897
3898 let mut cx = EditorTestContext::new(cx).await;
3899
3900 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
3901 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3902 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
3903
3904 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
3905 cx.set_state("two ˇfour ˇsix ˇ");
3906 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3907 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
3908
3909 // Paste again but with only two cursors. Since the number of cursors doesn't
3910 // match the number of slices in the clipboard, the entire clipboard text
3911 // is pasted at each cursor.
3912 cx.set_state("ˇtwo one✅ four three six five ˇ");
3913 cx.update_editor(|e, cx| {
3914 e.handle_input("( ", cx);
3915 e.paste(&Paste, cx);
3916 e.handle_input(") ", cx);
3917 });
3918 cx.assert_editor_state(
3919 &([
3920 "( one✅ ",
3921 "three ",
3922 "five ) ˇtwo one✅ four three six five ( one✅ ",
3923 "three ",
3924 "five ) ˇ",
3925 ]
3926 .join("\n")),
3927 );
3928
3929 // Cut with three selections, one of which is full-line.
3930 cx.set_state(indoc! {"
3931 1«2ˇ»3
3932 4ˇ567
3933 «8ˇ»9"});
3934 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3935 cx.assert_editor_state(indoc! {"
3936 1ˇ3
3937 ˇ9"});
3938
3939 // Paste with three selections, noticing how the copied selection that was full-line
3940 // gets inserted before the second cursor.
3941 cx.set_state(indoc! {"
3942 1ˇ3
3943 9ˇ
3944 «oˇ»ne"});
3945 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3946 cx.assert_editor_state(indoc! {"
3947 12ˇ3
3948 4567
3949 9ˇ
3950 8ˇne"});
3951
3952 // Copy with a single cursor only, which writes the whole line into the clipboard.
3953 cx.set_state(indoc! {"
3954 The quick brown
3955 fox juˇmps over
3956 the lazy dog"});
3957 cx.update_editor(|e, cx| e.copy(&Copy, cx));
3958 assert_eq!(
3959 cx.read_from_clipboard()
3960 .and_then(|item| item.text().as_deref().map(str::to_string)),
3961 Some("fox jumps over\n".to_string())
3962 );
3963
3964 // Paste with three selections, noticing how the copied full-line selection is inserted
3965 // before the empty selections but replaces the selection that is non-empty.
3966 cx.set_state(indoc! {"
3967 Tˇhe quick brown
3968 «foˇ»x jumps over
3969 tˇhe lazy dog"});
3970 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3971 cx.assert_editor_state(indoc! {"
3972 fox jumps over
3973 Tˇhe quick brown
3974 fox jumps over
3975 ˇx jumps over
3976 fox jumps over
3977 tˇhe lazy dog"});
3978}
3979
3980#[gpui::test]
3981async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
3982 init_test(cx, |_| {});
3983
3984 let mut cx = EditorTestContext::new(cx).await;
3985 let language = Arc::new(Language::new(
3986 LanguageConfig::default(),
3987 Some(tree_sitter_rust::language()),
3988 ));
3989 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
3990
3991 // Cut an indented block, without the leading whitespace.
3992 cx.set_state(indoc! {"
3993 const a: B = (
3994 c(),
3995 «d(
3996 e,
3997 f
3998 )ˇ»
3999 );
4000 "});
4001 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4002 cx.assert_editor_state(indoc! {"
4003 const a: B = (
4004 c(),
4005 ˇ
4006 );
4007 "});
4008
4009 // Paste it at the same position.
4010 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4011 cx.assert_editor_state(indoc! {"
4012 const a: B = (
4013 c(),
4014 d(
4015 e,
4016 f
4017 )ˇ
4018 );
4019 "});
4020
4021 // Paste it at a line with a lower indent level.
4022 cx.set_state(indoc! {"
4023 ˇ
4024 const a: B = (
4025 c(),
4026 );
4027 "});
4028 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4029 cx.assert_editor_state(indoc! {"
4030 d(
4031 e,
4032 f
4033 )ˇ
4034 const a: B = (
4035 c(),
4036 );
4037 "});
4038
4039 // Cut an indented block, with the leading whitespace.
4040 cx.set_state(indoc! {"
4041 const a: B = (
4042 c(),
4043 « d(
4044 e,
4045 f
4046 )
4047 ˇ»);
4048 "});
4049 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4050 cx.assert_editor_state(indoc! {"
4051 const a: B = (
4052 c(),
4053 ˇ);
4054 "});
4055
4056 // Paste it at the same position.
4057 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4058 cx.assert_editor_state(indoc! {"
4059 const a: B = (
4060 c(),
4061 d(
4062 e,
4063 f
4064 )
4065 ˇ);
4066 "});
4067
4068 // Paste it at a line with a higher indent level.
4069 cx.set_state(indoc! {"
4070 const a: B = (
4071 c(),
4072 d(
4073 e,
4074 fˇ
4075 )
4076 );
4077 "});
4078 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4079 cx.assert_editor_state(indoc! {"
4080 const a: B = (
4081 c(),
4082 d(
4083 e,
4084 f d(
4085 e,
4086 f
4087 )
4088 ˇ
4089 )
4090 );
4091 "});
4092}
4093
4094#[gpui::test]
4095fn test_select_all(cx: &mut TestAppContext) {
4096 init_test(cx, |_| {});
4097
4098 let view = cx.add_window(|cx| {
4099 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4100 build_editor(buffer, cx)
4101 });
4102 _ = view.update(cx, |view, cx| {
4103 view.select_all(&SelectAll, cx);
4104 assert_eq!(
4105 view.selections.display_ranges(cx),
4106 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4107 );
4108 });
4109}
4110
4111#[gpui::test]
4112fn test_select_line(cx: &mut TestAppContext) {
4113 init_test(cx, |_| {});
4114
4115 let view = cx.add_window(|cx| {
4116 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4117 build_editor(buffer, cx)
4118 });
4119 _ = view.update(cx, |view, cx| {
4120 view.change_selections(None, cx, |s| {
4121 s.select_display_ranges([
4122 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4123 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4124 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4125 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4126 ])
4127 });
4128 view.select_line(&SelectLine, cx);
4129 assert_eq!(
4130 view.selections.display_ranges(cx),
4131 vec![
4132 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4133 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4134 ]
4135 );
4136 });
4137
4138 _ = view.update(cx, |view, cx| {
4139 view.select_line(&SelectLine, cx);
4140 assert_eq!(
4141 view.selections.display_ranges(cx),
4142 vec![
4143 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4144 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4145 ]
4146 );
4147 });
4148
4149 _ = view.update(cx, |view, cx| {
4150 view.select_line(&SelectLine, cx);
4151 assert_eq!(
4152 view.selections.display_ranges(cx),
4153 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4154 );
4155 });
4156}
4157
4158#[gpui::test]
4159fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4160 init_test(cx, |_| {});
4161
4162 let view = cx.add_window(|cx| {
4163 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4164 build_editor(buffer, cx)
4165 });
4166 _ = view.update(cx, |view, cx| {
4167 view.fold_ranges(
4168 vec![
4169 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4170 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4171 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4172 ],
4173 true,
4174 cx,
4175 );
4176 view.change_selections(None, cx, |s| {
4177 s.select_display_ranges([
4178 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4179 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4180 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4181 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4182 ])
4183 });
4184 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4185 });
4186
4187 _ = view.update(cx, |view, cx| {
4188 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4189 assert_eq!(
4190 view.display_text(cx),
4191 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4192 );
4193 assert_eq!(
4194 view.selections.display_ranges(cx),
4195 [
4196 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4197 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4198 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4199 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4200 ]
4201 );
4202 });
4203
4204 _ = view.update(cx, |view, cx| {
4205 view.change_selections(None, cx, |s| {
4206 s.select_display_ranges([
4207 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4208 ])
4209 });
4210 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4211 assert_eq!(
4212 view.display_text(cx),
4213 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4214 );
4215 assert_eq!(
4216 view.selections.display_ranges(cx),
4217 [
4218 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4219 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4220 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4221 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4222 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4223 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4224 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4225 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4226 ]
4227 );
4228 });
4229}
4230
4231#[gpui::test]
4232async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4233 init_test(cx, |_| {});
4234
4235 let mut cx = EditorTestContext::new(cx).await;
4236
4237 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4238 cx.set_state(indoc!(
4239 r#"abc
4240 defˇghi
4241
4242 jk
4243 nlmo
4244 "#
4245 ));
4246
4247 cx.update_editor(|editor, cx| {
4248 editor.add_selection_above(&Default::default(), cx);
4249 });
4250
4251 cx.assert_editor_state(indoc!(
4252 r#"abcˇ
4253 defˇghi
4254
4255 jk
4256 nlmo
4257 "#
4258 ));
4259
4260 cx.update_editor(|editor, cx| {
4261 editor.add_selection_above(&Default::default(), cx);
4262 });
4263
4264 cx.assert_editor_state(indoc!(
4265 r#"abcˇ
4266 defˇghi
4267
4268 jk
4269 nlmo
4270 "#
4271 ));
4272
4273 cx.update_editor(|view, cx| {
4274 view.add_selection_below(&Default::default(), cx);
4275 });
4276
4277 cx.assert_editor_state(indoc!(
4278 r#"abc
4279 defˇghi
4280
4281 jk
4282 nlmo
4283 "#
4284 ));
4285
4286 cx.update_editor(|view, cx| {
4287 view.undo_selection(&Default::default(), cx);
4288 });
4289
4290 cx.assert_editor_state(indoc!(
4291 r#"abcˇ
4292 defˇghi
4293
4294 jk
4295 nlmo
4296 "#
4297 ));
4298
4299 cx.update_editor(|view, cx| {
4300 view.redo_selection(&Default::default(), cx);
4301 });
4302
4303 cx.assert_editor_state(indoc!(
4304 r#"abc
4305 defˇghi
4306
4307 jk
4308 nlmo
4309 "#
4310 ));
4311
4312 cx.update_editor(|view, cx| {
4313 view.add_selection_below(&Default::default(), cx);
4314 });
4315
4316 cx.assert_editor_state(indoc!(
4317 r#"abc
4318 defˇghi
4319
4320 jk
4321 nlmˇo
4322 "#
4323 ));
4324
4325 cx.update_editor(|view, cx| {
4326 view.add_selection_below(&Default::default(), cx);
4327 });
4328
4329 cx.assert_editor_state(indoc!(
4330 r#"abc
4331 defˇghi
4332
4333 jk
4334 nlmˇo
4335 "#
4336 ));
4337
4338 // change selections
4339 cx.set_state(indoc!(
4340 r#"abc
4341 def«ˇg»hi
4342
4343 jk
4344 nlmo
4345 "#
4346 ));
4347
4348 cx.update_editor(|view, cx| {
4349 view.add_selection_below(&Default::default(), cx);
4350 });
4351
4352 cx.assert_editor_state(indoc!(
4353 r#"abc
4354 def«ˇg»hi
4355
4356 jk
4357 nlm«ˇo»
4358 "#
4359 ));
4360
4361 cx.update_editor(|view, cx| {
4362 view.add_selection_below(&Default::default(), cx);
4363 });
4364
4365 cx.assert_editor_state(indoc!(
4366 r#"abc
4367 def«ˇg»hi
4368
4369 jk
4370 nlm«ˇo»
4371 "#
4372 ));
4373
4374 cx.update_editor(|view, cx| {
4375 view.add_selection_above(&Default::default(), cx);
4376 });
4377
4378 cx.assert_editor_state(indoc!(
4379 r#"abc
4380 def«ˇg»hi
4381
4382 jk
4383 nlmo
4384 "#
4385 ));
4386
4387 cx.update_editor(|view, cx| {
4388 view.add_selection_above(&Default::default(), cx);
4389 });
4390
4391 cx.assert_editor_state(indoc!(
4392 r#"abc
4393 def«ˇg»hi
4394
4395 jk
4396 nlmo
4397 "#
4398 ));
4399
4400 // Change selections again
4401 cx.set_state(indoc!(
4402 r#"a«bc
4403 defgˇ»hi
4404
4405 jk
4406 nlmo
4407 "#
4408 ));
4409
4410 cx.update_editor(|view, cx| {
4411 view.add_selection_below(&Default::default(), cx);
4412 });
4413
4414 cx.assert_editor_state(indoc!(
4415 r#"a«bcˇ»
4416 d«efgˇ»hi
4417
4418 j«kˇ»
4419 nlmo
4420 "#
4421 ));
4422
4423 cx.update_editor(|view, cx| {
4424 view.add_selection_below(&Default::default(), cx);
4425 });
4426 cx.assert_editor_state(indoc!(
4427 r#"a«bcˇ»
4428 d«efgˇ»hi
4429
4430 j«kˇ»
4431 n«lmoˇ»
4432 "#
4433 ));
4434 cx.update_editor(|view, cx| {
4435 view.add_selection_above(&Default::default(), cx);
4436 });
4437
4438 cx.assert_editor_state(indoc!(
4439 r#"a«bcˇ»
4440 d«efgˇ»hi
4441
4442 j«kˇ»
4443 nlmo
4444 "#
4445 ));
4446
4447 // Change selections again
4448 cx.set_state(indoc!(
4449 r#"abc
4450 d«ˇefghi
4451
4452 jk
4453 nlm»o
4454 "#
4455 ));
4456
4457 cx.update_editor(|view, cx| {
4458 view.add_selection_above(&Default::default(), cx);
4459 });
4460
4461 cx.assert_editor_state(indoc!(
4462 r#"a«ˇbc»
4463 d«ˇef»ghi
4464
4465 j«ˇk»
4466 n«ˇlm»o
4467 "#
4468 ));
4469
4470 cx.update_editor(|view, cx| {
4471 view.add_selection_below(&Default::default(), cx);
4472 });
4473
4474 cx.assert_editor_state(indoc!(
4475 r#"abc
4476 d«ˇef»ghi
4477
4478 j«ˇk»
4479 n«ˇlm»o
4480 "#
4481 ));
4482}
4483
4484#[gpui::test]
4485async fn test_select_next(cx: &mut gpui::TestAppContext) {
4486 init_test(cx, |_| {});
4487
4488 let mut cx = EditorTestContext::new(cx).await;
4489 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4490
4491 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4492 .unwrap();
4493 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4494
4495 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4496 .unwrap();
4497 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4498
4499 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4500 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4501
4502 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4503 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4504
4505 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4506 .unwrap();
4507 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4508
4509 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4510 .unwrap();
4511 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4512}
4513
4514#[gpui::test]
4515async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
4516 init_test(cx, |_| {});
4517
4518 let mut cx = EditorTestContext::new(cx).await;
4519 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4520
4521 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
4522 .unwrap();
4523 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4524}
4525
4526#[gpui::test]
4527async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4528 init_test(cx, |_| {});
4529
4530 let mut cx = EditorTestContext::new(cx).await;
4531 cx.set_state(
4532 r#"let foo = 2;
4533lˇet foo = 2;
4534let fooˇ = 2;
4535let foo = 2;
4536let foo = ˇ2;"#,
4537 );
4538
4539 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4540 .unwrap();
4541 cx.assert_editor_state(
4542 r#"let foo = 2;
4543«letˇ» foo = 2;
4544let «fooˇ» = 2;
4545let foo = 2;
4546let foo = «2ˇ»;"#,
4547 );
4548
4549 // noop for multiple selections with different contents
4550 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4551 .unwrap();
4552 cx.assert_editor_state(
4553 r#"let foo = 2;
4554«letˇ» foo = 2;
4555let «fooˇ» = 2;
4556let foo = 2;
4557let foo = «2ˇ»;"#,
4558 );
4559}
4560
4561#[gpui::test]
4562async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
4563 init_test(cx, |_| {});
4564
4565 let mut cx = EditorTestContext::new_multibuffer(
4566 cx,
4567 [
4568 &indoc! {
4569 "aaa\n«bbb\nccc\n»ddd"
4570 },
4571 &indoc! {
4572 "aaa\n«bbb\nccc\n»ddd"
4573 },
4574 ],
4575 );
4576
4577 cx.assert_editor_state(indoc! {"
4578 ˇbbb
4579 ccc
4580
4581 bbb
4582 ccc
4583 "});
4584 cx.dispatch_action(SelectPrevious::default());
4585 cx.assert_editor_state(indoc! {"
4586 «bbbˇ»
4587 ccc
4588
4589 bbb
4590 ccc
4591 "});
4592 cx.dispatch_action(SelectPrevious::default());
4593 cx.assert_editor_state(indoc! {"
4594 «bbbˇ»
4595 ccc
4596
4597 «bbbˇ»
4598 ccc
4599 "});
4600}
4601
4602#[gpui::test]
4603async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
4604 init_test(cx, |_| {});
4605
4606 let mut cx = EditorTestContext::new(cx).await;
4607 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4608
4609 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4610 .unwrap();
4611 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4612
4613 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4614 .unwrap();
4615 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4616
4617 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4618 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4619
4620 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4621 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4622
4623 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4624 .unwrap();
4625 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
4626
4627 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4628 .unwrap();
4629 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
4630
4631 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4632 .unwrap();
4633 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
4634}
4635
4636#[gpui::test]
4637async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4638 init_test(cx, |_| {});
4639
4640 let mut cx = EditorTestContext::new(cx).await;
4641 cx.set_state(
4642 r#"let foo = 2;
4643lˇet foo = 2;
4644let fooˇ = 2;
4645let foo = 2;
4646let foo = ˇ2;"#,
4647 );
4648
4649 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4650 .unwrap();
4651 cx.assert_editor_state(
4652 r#"let foo = 2;
4653«letˇ» foo = 2;
4654let «fooˇ» = 2;
4655let foo = 2;
4656let foo = «2ˇ»;"#,
4657 );
4658
4659 // noop for multiple selections with different contents
4660 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4661 .unwrap();
4662 cx.assert_editor_state(
4663 r#"let foo = 2;
4664«letˇ» foo = 2;
4665let «fooˇ» = 2;
4666let foo = 2;
4667let foo = «2ˇ»;"#,
4668 );
4669}
4670
4671#[gpui::test]
4672async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
4673 init_test(cx, |_| {});
4674
4675 let mut cx = EditorTestContext::new(cx).await;
4676 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
4677
4678 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4679 .unwrap();
4680 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
4681
4682 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4683 .unwrap();
4684 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
4685
4686 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4687 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
4688
4689 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4690 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
4691
4692 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4693 .unwrap();
4694 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
4695
4696 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4697 .unwrap();
4698 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
4699}
4700
4701#[gpui::test]
4702async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
4703 init_test(cx, |_| {});
4704
4705 let language = Arc::new(Language::new(
4706 LanguageConfig::default(),
4707 Some(tree_sitter_rust::language()),
4708 ));
4709
4710 let text = r#"
4711 use mod1::mod2::{mod3, mod4};
4712
4713 fn fn_1(param1: bool, param2: &str) {
4714 let var1 = "text";
4715 }
4716 "#
4717 .unindent();
4718
4719 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
4720 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4721 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4722
4723 editor
4724 .condition::<crate::EditorEvent>(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4725 .await;
4726
4727 editor.update(cx, |view, cx| {
4728 view.change_selections(None, cx, |s| {
4729 s.select_display_ranges([
4730 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
4731 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
4732 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
4733 ]);
4734 });
4735 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4736 });
4737 editor.update(cx, |editor, cx| {
4738 assert_text_with_selections(
4739 editor,
4740 indoc! {r#"
4741 use mod1::mod2::{mod3, «mod4ˇ»};
4742
4743 fn fn_1«ˇ(param1: bool, param2: &str)» {
4744 let var1 = "«textˇ»";
4745 }
4746 "#},
4747 cx,
4748 );
4749 });
4750
4751 editor.update(cx, |view, cx| {
4752 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4753 });
4754 editor.update(cx, |editor, cx| {
4755 assert_text_with_selections(
4756 editor,
4757 indoc! {r#"
4758 use mod1::mod2::«{mod3, mod4}ˇ»;
4759
4760 «ˇfn fn_1(param1: bool, param2: &str) {
4761 let var1 = "text";
4762 }»
4763 "#},
4764 cx,
4765 );
4766 });
4767
4768 editor.update(cx, |view, cx| {
4769 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4770 });
4771 assert_eq!(
4772 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
4773 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
4774 );
4775
4776 // Trying to expand the selected syntax node one more time has no effect.
4777 editor.update(cx, |view, cx| {
4778 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4779 });
4780 assert_eq!(
4781 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
4782 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
4783 );
4784
4785 editor.update(cx, |view, cx| {
4786 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4787 });
4788 editor.update(cx, |editor, cx| {
4789 assert_text_with_selections(
4790 editor,
4791 indoc! {r#"
4792 use mod1::mod2::«{mod3, mod4}ˇ»;
4793
4794 «ˇfn fn_1(param1: bool, param2: &str) {
4795 let var1 = "text";
4796 }»
4797 "#},
4798 cx,
4799 );
4800 });
4801
4802 editor.update(cx, |view, cx| {
4803 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4804 });
4805 editor.update(cx, |editor, cx| {
4806 assert_text_with_selections(
4807 editor,
4808 indoc! {r#"
4809 use mod1::mod2::{mod3, «mod4ˇ»};
4810
4811 fn fn_1«ˇ(param1: bool, param2: &str)» {
4812 let var1 = "«textˇ»";
4813 }
4814 "#},
4815 cx,
4816 );
4817 });
4818
4819 editor.update(cx, |view, cx| {
4820 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4821 });
4822 editor.update(cx, |editor, cx| {
4823 assert_text_with_selections(
4824 editor,
4825 indoc! {r#"
4826 use mod1::mod2::{mod3, mo«ˇ»d4};
4827
4828 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
4829 let var1 = "te«ˇ»xt";
4830 }
4831 "#},
4832 cx,
4833 );
4834 });
4835
4836 // Trying to shrink the selected syntax node one more time has no effect.
4837 editor.update(cx, |view, cx| {
4838 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4839 });
4840 editor.update(cx, |editor, cx| {
4841 assert_text_with_selections(
4842 editor,
4843 indoc! {r#"
4844 use mod1::mod2::{mod3, mo«ˇ»d4};
4845
4846 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
4847 let var1 = "te«ˇ»xt";
4848 }
4849 "#},
4850 cx,
4851 );
4852 });
4853
4854 // Ensure that we keep expanding the selection if the larger selection starts or ends within
4855 // a fold.
4856 editor.update(cx, |view, cx| {
4857 view.fold_ranges(
4858 vec![
4859 (
4860 Point::new(0, 21)..Point::new(0, 24),
4861 FoldPlaceholder::test(),
4862 ),
4863 (
4864 Point::new(3, 20)..Point::new(3, 22),
4865 FoldPlaceholder::test(),
4866 ),
4867 ],
4868 true,
4869 cx,
4870 );
4871 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4872 });
4873 editor.update(cx, |editor, cx| {
4874 assert_text_with_selections(
4875 editor,
4876 indoc! {r#"
4877 use mod1::mod2::«{mod3, mod4}ˇ»;
4878
4879 fn fn_1«ˇ(param1: bool, param2: &str)» {
4880 «let var1 = "text";ˇ»
4881 }
4882 "#},
4883 cx,
4884 );
4885 });
4886}
4887
4888#[gpui::test]
4889async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
4890 init_test(cx, |_| {});
4891
4892 let language = Arc::new(
4893 Language::new(
4894 LanguageConfig {
4895 brackets: BracketPairConfig {
4896 pairs: vec![
4897 BracketPair {
4898 start: "{".to_string(),
4899 end: "}".to_string(),
4900 close: false,
4901 surround: false,
4902 newline: true,
4903 },
4904 BracketPair {
4905 start: "(".to_string(),
4906 end: ")".to_string(),
4907 close: false,
4908 surround: false,
4909 newline: true,
4910 },
4911 ],
4912 ..Default::default()
4913 },
4914 ..Default::default()
4915 },
4916 Some(tree_sitter_rust::language()),
4917 )
4918 .with_indents_query(
4919 r#"
4920 (_ "(" ")" @end) @indent
4921 (_ "{" "}" @end) @indent
4922 "#,
4923 )
4924 .unwrap(),
4925 );
4926
4927 let text = "fn a() {}";
4928
4929 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
4930 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4931 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4932 editor
4933 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
4934 .await;
4935
4936 _ = editor.update(cx, |editor, cx| {
4937 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
4938 editor.newline(&Newline, cx);
4939 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
4940 assert_eq!(
4941 editor.selections.ranges(cx),
4942 &[
4943 Point::new(1, 4)..Point::new(1, 4),
4944 Point::new(3, 4)..Point::new(3, 4),
4945 Point::new(5, 0)..Point::new(5, 0)
4946 ]
4947 );
4948 });
4949}
4950
4951#[gpui::test]
4952async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
4953 init_test(cx, |_| {});
4954
4955 let mut cx = EditorTestContext::new(cx).await;
4956
4957 let language = Arc::new(Language::new(
4958 LanguageConfig {
4959 brackets: BracketPairConfig {
4960 pairs: vec![
4961 BracketPair {
4962 start: "{".to_string(),
4963 end: "}".to_string(),
4964 close: true,
4965 surround: true,
4966 newline: true,
4967 },
4968 BracketPair {
4969 start: "(".to_string(),
4970 end: ")".to_string(),
4971 close: true,
4972 surround: true,
4973 newline: true,
4974 },
4975 BracketPair {
4976 start: "/*".to_string(),
4977 end: " */".to_string(),
4978 close: true,
4979 surround: true,
4980 newline: true,
4981 },
4982 BracketPair {
4983 start: "[".to_string(),
4984 end: "]".to_string(),
4985 close: false,
4986 surround: false,
4987 newline: true,
4988 },
4989 BracketPair {
4990 start: "\"".to_string(),
4991 end: "\"".to_string(),
4992 close: true,
4993 surround: true,
4994 newline: false,
4995 },
4996 BracketPair {
4997 start: "<".to_string(),
4998 end: ">".to_string(),
4999 close: false,
5000 surround: true,
5001 newline: true,
5002 },
5003 ],
5004 ..Default::default()
5005 },
5006 autoclose_before: "})]".to_string(),
5007 ..Default::default()
5008 },
5009 Some(tree_sitter_rust::language()),
5010 ));
5011
5012 cx.language_registry().add(language.clone());
5013 cx.update_buffer(|buffer, cx| {
5014 buffer.set_language(Some(language), cx);
5015 });
5016
5017 cx.set_state(
5018 &r#"
5019 🏀ˇ
5020 εˇ
5021 ❤️ˇ
5022 "#
5023 .unindent(),
5024 );
5025
5026 // autoclose multiple nested brackets at multiple cursors
5027 cx.update_editor(|view, cx| {
5028 view.handle_input("{", cx);
5029 view.handle_input("{", cx);
5030 view.handle_input("{", cx);
5031 });
5032 cx.assert_editor_state(
5033 &"
5034 🏀{{{ˇ}}}
5035 ε{{{ˇ}}}
5036 ❤️{{{ˇ}}}
5037 "
5038 .unindent(),
5039 );
5040
5041 // insert a different closing bracket
5042 cx.update_editor(|view, cx| {
5043 view.handle_input(")", cx);
5044 });
5045 cx.assert_editor_state(
5046 &"
5047 🏀{{{)ˇ}}}
5048 ε{{{)ˇ}}}
5049 ❤️{{{)ˇ}}}
5050 "
5051 .unindent(),
5052 );
5053
5054 // skip over the auto-closed brackets when typing a closing bracket
5055 cx.update_editor(|view, cx| {
5056 view.move_right(&MoveRight, cx);
5057 view.handle_input("}", cx);
5058 view.handle_input("}", cx);
5059 view.handle_input("}", cx);
5060 });
5061 cx.assert_editor_state(
5062 &"
5063 🏀{{{)}}}}ˇ
5064 ε{{{)}}}}ˇ
5065 ❤️{{{)}}}}ˇ
5066 "
5067 .unindent(),
5068 );
5069
5070 // autoclose multi-character pairs
5071 cx.set_state(
5072 &"
5073 ˇ
5074 ˇ
5075 "
5076 .unindent(),
5077 );
5078 cx.update_editor(|view, cx| {
5079 view.handle_input("/", cx);
5080 view.handle_input("*", cx);
5081 });
5082 cx.assert_editor_state(
5083 &"
5084 /*ˇ */
5085 /*ˇ */
5086 "
5087 .unindent(),
5088 );
5089
5090 // one cursor autocloses a multi-character pair, one cursor
5091 // does not autoclose.
5092 cx.set_state(
5093 &"
5094 /ˇ
5095 ˇ
5096 "
5097 .unindent(),
5098 );
5099 cx.update_editor(|view, cx| view.handle_input("*", cx));
5100 cx.assert_editor_state(
5101 &"
5102 /*ˇ */
5103 *ˇ
5104 "
5105 .unindent(),
5106 );
5107
5108 // Don't autoclose if the next character isn't whitespace and isn't
5109 // listed in the language's "autoclose_before" section.
5110 cx.set_state("ˇa b");
5111 cx.update_editor(|view, cx| view.handle_input("{", cx));
5112 cx.assert_editor_state("{ˇa b");
5113
5114 // Don't autoclose if `close` is false for the bracket pair
5115 cx.set_state("ˇ");
5116 cx.update_editor(|view, cx| view.handle_input("[", cx));
5117 cx.assert_editor_state("[ˇ");
5118
5119 // Surround with brackets if text is selected
5120 cx.set_state("«aˇ» b");
5121 cx.update_editor(|view, cx| view.handle_input("{", cx));
5122 cx.assert_editor_state("{«aˇ»} b");
5123
5124 // Autclose pair where the start and end characters are the same
5125 cx.set_state("aˇ");
5126 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5127 cx.assert_editor_state("a\"ˇ\"");
5128 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5129 cx.assert_editor_state("a\"\"ˇ");
5130
5131 // Don't autoclose pair if autoclose is disabled
5132 cx.set_state("ˇ");
5133 cx.update_editor(|view, cx| view.handle_input("<", cx));
5134 cx.assert_editor_state("<ˇ");
5135
5136 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5137 cx.set_state("«aˇ» b");
5138 cx.update_editor(|view, cx| view.handle_input("<", cx));
5139 cx.assert_editor_state("<«aˇ»> b");
5140}
5141
5142#[gpui::test]
5143async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5144 init_test(cx, |settings| {
5145 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5146 });
5147
5148 let mut cx = EditorTestContext::new(cx).await;
5149
5150 let language = Arc::new(Language::new(
5151 LanguageConfig {
5152 brackets: BracketPairConfig {
5153 pairs: vec![
5154 BracketPair {
5155 start: "{".to_string(),
5156 end: "}".to_string(),
5157 close: true,
5158 surround: true,
5159 newline: true,
5160 },
5161 BracketPair {
5162 start: "(".to_string(),
5163 end: ")".to_string(),
5164 close: true,
5165 surround: true,
5166 newline: true,
5167 },
5168 BracketPair {
5169 start: "[".to_string(),
5170 end: "]".to_string(),
5171 close: false,
5172 surround: false,
5173 newline: true,
5174 },
5175 ],
5176 ..Default::default()
5177 },
5178 autoclose_before: "})]".to_string(),
5179 ..Default::default()
5180 },
5181 Some(tree_sitter_rust::language()),
5182 ));
5183
5184 cx.language_registry().add(language.clone());
5185 cx.update_buffer(|buffer, cx| {
5186 buffer.set_language(Some(language), cx);
5187 });
5188
5189 cx.set_state(
5190 &"
5191 ˇ
5192 ˇ
5193 ˇ
5194 "
5195 .unindent(),
5196 );
5197
5198 // ensure only matching closing brackets are skipped over
5199 cx.update_editor(|view, cx| {
5200 view.handle_input("}", cx);
5201 view.move_left(&MoveLeft, cx);
5202 view.handle_input(")", cx);
5203 view.move_left(&MoveLeft, cx);
5204 });
5205 cx.assert_editor_state(
5206 &"
5207 ˇ)}
5208 ˇ)}
5209 ˇ)}
5210 "
5211 .unindent(),
5212 );
5213
5214 // skip-over closing brackets at multiple cursors
5215 cx.update_editor(|view, cx| {
5216 view.handle_input(")", cx);
5217 view.handle_input("}", cx);
5218 });
5219 cx.assert_editor_state(
5220 &"
5221 )}ˇ
5222 )}ˇ
5223 )}ˇ
5224 "
5225 .unindent(),
5226 );
5227
5228 // ignore non-close brackets
5229 cx.update_editor(|view, cx| {
5230 view.handle_input("]", cx);
5231 view.move_left(&MoveLeft, cx);
5232 view.handle_input("]", cx);
5233 });
5234 cx.assert_editor_state(
5235 &"
5236 )}]ˇ]
5237 )}]ˇ]
5238 )}]ˇ]
5239 "
5240 .unindent(),
5241 );
5242}
5243
5244#[gpui::test]
5245async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5246 init_test(cx, |_| {});
5247
5248 let mut cx = EditorTestContext::new(cx).await;
5249
5250 let html_language = Arc::new(
5251 Language::new(
5252 LanguageConfig {
5253 name: "HTML".into(),
5254 brackets: BracketPairConfig {
5255 pairs: vec![
5256 BracketPair {
5257 start: "<".into(),
5258 end: ">".into(),
5259 close: true,
5260 ..Default::default()
5261 },
5262 BracketPair {
5263 start: "{".into(),
5264 end: "}".into(),
5265 close: true,
5266 ..Default::default()
5267 },
5268 BracketPair {
5269 start: "(".into(),
5270 end: ")".into(),
5271 close: true,
5272 ..Default::default()
5273 },
5274 ],
5275 ..Default::default()
5276 },
5277 autoclose_before: "})]>".into(),
5278 ..Default::default()
5279 },
5280 Some(tree_sitter_html::language()),
5281 )
5282 .with_injection_query(
5283 r#"
5284 (script_element
5285 (raw_text) @content
5286 (#set! "language" "javascript"))
5287 "#,
5288 )
5289 .unwrap(),
5290 );
5291
5292 let javascript_language = Arc::new(Language::new(
5293 LanguageConfig {
5294 name: "JavaScript".into(),
5295 brackets: BracketPairConfig {
5296 pairs: vec![
5297 BracketPair {
5298 start: "/*".into(),
5299 end: " */".into(),
5300 close: true,
5301 ..Default::default()
5302 },
5303 BracketPair {
5304 start: "{".into(),
5305 end: "}".into(),
5306 close: true,
5307 ..Default::default()
5308 },
5309 BracketPair {
5310 start: "(".into(),
5311 end: ")".into(),
5312 close: true,
5313 ..Default::default()
5314 },
5315 ],
5316 ..Default::default()
5317 },
5318 autoclose_before: "})]>".into(),
5319 ..Default::default()
5320 },
5321 Some(tree_sitter_typescript::language_tsx()),
5322 ));
5323
5324 cx.language_registry().add(html_language.clone());
5325 cx.language_registry().add(javascript_language.clone());
5326
5327 cx.update_buffer(|buffer, cx| {
5328 buffer.set_language(Some(html_language), cx);
5329 });
5330
5331 cx.set_state(
5332 &r#"
5333 <body>ˇ
5334 <script>
5335 var x = 1;ˇ
5336 </script>
5337 </body>ˇ
5338 "#
5339 .unindent(),
5340 );
5341
5342 // Precondition: different languages are active at different locations.
5343 cx.update_editor(|editor, cx| {
5344 let snapshot = editor.snapshot(cx);
5345 let cursors = editor.selections.ranges::<usize>(cx);
5346 let languages = cursors
5347 .iter()
5348 .map(|c| snapshot.language_at(c.start).unwrap().name())
5349 .collect::<Vec<_>>();
5350 assert_eq!(
5351 languages,
5352 &["HTML".into(), "JavaScript".into(), "HTML".into()]
5353 );
5354 });
5355
5356 // Angle brackets autoclose in HTML, but not JavaScript.
5357 cx.update_editor(|editor, cx| {
5358 editor.handle_input("<", cx);
5359 editor.handle_input("a", cx);
5360 });
5361 cx.assert_editor_state(
5362 &r#"
5363 <body><aˇ>
5364 <script>
5365 var x = 1;<aˇ
5366 </script>
5367 </body><aˇ>
5368 "#
5369 .unindent(),
5370 );
5371
5372 // Curly braces and parens autoclose in both HTML and JavaScript.
5373 cx.update_editor(|editor, cx| {
5374 editor.handle_input(" b=", cx);
5375 editor.handle_input("{", cx);
5376 editor.handle_input("c", cx);
5377 editor.handle_input("(", cx);
5378 });
5379 cx.assert_editor_state(
5380 &r#"
5381 <body><a b={c(ˇ)}>
5382 <script>
5383 var x = 1;<a b={c(ˇ)}
5384 </script>
5385 </body><a b={c(ˇ)}>
5386 "#
5387 .unindent(),
5388 );
5389
5390 // Brackets that were already autoclosed are skipped.
5391 cx.update_editor(|editor, cx| {
5392 editor.handle_input(")", cx);
5393 editor.handle_input("d", cx);
5394 editor.handle_input("}", cx);
5395 });
5396 cx.assert_editor_state(
5397 &r#"
5398 <body><a b={c()d}ˇ>
5399 <script>
5400 var x = 1;<a b={c()d}ˇ
5401 </script>
5402 </body><a b={c()d}ˇ>
5403 "#
5404 .unindent(),
5405 );
5406 cx.update_editor(|editor, cx| {
5407 editor.handle_input(">", cx);
5408 });
5409 cx.assert_editor_state(
5410 &r#"
5411 <body><a b={c()d}>ˇ
5412 <script>
5413 var x = 1;<a b={c()d}>ˇ
5414 </script>
5415 </body><a b={c()d}>ˇ
5416 "#
5417 .unindent(),
5418 );
5419
5420 // Reset
5421 cx.set_state(
5422 &r#"
5423 <body>ˇ
5424 <script>
5425 var x = 1;ˇ
5426 </script>
5427 </body>ˇ
5428 "#
5429 .unindent(),
5430 );
5431
5432 cx.update_editor(|editor, cx| {
5433 editor.handle_input("<", cx);
5434 });
5435 cx.assert_editor_state(
5436 &r#"
5437 <body><ˇ>
5438 <script>
5439 var x = 1;<ˇ
5440 </script>
5441 </body><ˇ>
5442 "#
5443 .unindent(),
5444 );
5445
5446 // When backspacing, the closing angle brackets are removed.
5447 cx.update_editor(|editor, cx| {
5448 editor.backspace(&Backspace, cx);
5449 });
5450 cx.assert_editor_state(
5451 &r#"
5452 <body>ˇ
5453 <script>
5454 var x = 1;ˇ
5455 </script>
5456 </body>ˇ
5457 "#
5458 .unindent(),
5459 );
5460
5461 // Block comments autoclose in JavaScript, but not HTML.
5462 cx.update_editor(|editor, cx| {
5463 editor.handle_input("/", cx);
5464 editor.handle_input("*", cx);
5465 });
5466 cx.assert_editor_state(
5467 &r#"
5468 <body>/*ˇ
5469 <script>
5470 var x = 1;/*ˇ */
5471 </script>
5472 </body>/*ˇ
5473 "#
5474 .unindent(),
5475 );
5476}
5477
5478#[gpui::test]
5479async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
5480 init_test(cx, |_| {});
5481
5482 let mut cx = EditorTestContext::new(cx).await;
5483
5484 let rust_language = Arc::new(
5485 Language::new(
5486 LanguageConfig {
5487 name: "Rust".into(),
5488 brackets: serde_json::from_value(json!([
5489 { "start": "{", "end": "}", "close": true, "newline": true },
5490 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
5491 ]))
5492 .unwrap(),
5493 autoclose_before: "})]>".into(),
5494 ..Default::default()
5495 },
5496 Some(tree_sitter_rust::language()),
5497 )
5498 .with_override_query("(string_literal) @string")
5499 .unwrap(),
5500 );
5501
5502 cx.language_registry().add(rust_language.clone());
5503 cx.update_buffer(|buffer, cx| {
5504 buffer.set_language(Some(rust_language), cx);
5505 });
5506
5507 cx.set_state(
5508 &r#"
5509 let x = ˇ
5510 "#
5511 .unindent(),
5512 );
5513
5514 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
5515 cx.update_editor(|editor, cx| {
5516 editor.handle_input("\"", cx);
5517 });
5518 cx.assert_editor_state(
5519 &r#"
5520 let x = "ˇ"
5521 "#
5522 .unindent(),
5523 );
5524
5525 // Inserting another quotation mark. The cursor moves across the existing
5526 // automatically-inserted quotation mark.
5527 cx.update_editor(|editor, cx| {
5528 editor.handle_input("\"", cx);
5529 });
5530 cx.assert_editor_state(
5531 &r#"
5532 let x = ""ˇ
5533 "#
5534 .unindent(),
5535 );
5536
5537 // Reset
5538 cx.set_state(
5539 &r#"
5540 let x = ˇ
5541 "#
5542 .unindent(),
5543 );
5544
5545 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
5546 cx.update_editor(|editor, cx| {
5547 editor.handle_input("\"", cx);
5548 editor.handle_input(" ", cx);
5549 editor.move_left(&Default::default(), cx);
5550 editor.handle_input("\\", cx);
5551 editor.handle_input("\"", cx);
5552 });
5553 cx.assert_editor_state(
5554 &r#"
5555 let x = "\"ˇ "
5556 "#
5557 .unindent(),
5558 );
5559
5560 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
5561 // mark. Nothing is inserted.
5562 cx.update_editor(|editor, cx| {
5563 editor.move_right(&Default::default(), cx);
5564 editor.handle_input("\"", cx);
5565 });
5566 cx.assert_editor_state(
5567 &r#"
5568 let x = "\" "ˇ
5569 "#
5570 .unindent(),
5571 );
5572}
5573
5574#[gpui::test]
5575async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
5576 init_test(cx, |_| {});
5577
5578 let language = Arc::new(Language::new(
5579 LanguageConfig {
5580 brackets: BracketPairConfig {
5581 pairs: vec![
5582 BracketPair {
5583 start: "{".to_string(),
5584 end: "}".to_string(),
5585 close: true,
5586 surround: true,
5587 newline: true,
5588 },
5589 BracketPair {
5590 start: "/* ".to_string(),
5591 end: "*/".to_string(),
5592 close: true,
5593 surround: true,
5594 ..Default::default()
5595 },
5596 ],
5597 ..Default::default()
5598 },
5599 ..Default::default()
5600 },
5601 Some(tree_sitter_rust::language()),
5602 ));
5603
5604 let text = r#"
5605 a
5606 b
5607 c
5608 "#
5609 .unindent();
5610
5611 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5612 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5613 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5614 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5615 .await;
5616
5617 _ = view.update(cx, |view, cx| {
5618 view.change_selections(None, cx, |s| {
5619 s.select_display_ranges([
5620 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5621 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5622 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
5623 ])
5624 });
5625
5626 view.handle_input("{", cx);
5627 view.handle_input("{", cx);
5628 view.handle_input("{", cx);
5629 assert_eq!(
5630 view.text(cx),
5631 "
5632 {{{a}}}
5633 {{{b}}}
5634 {{{c}}}
5635 "
5636 .unindent()
5637 );
5638 assert_eq!(
5639 view.selections.display_ranges(cx),
5640 [
5641 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
5642 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
5643 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
5644 ]
5645 );
5646
5647 view.undo(&Undo, cx);
5648 view.undo(&Undo, cx);
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 first 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 view.undo(&Undo, cx);
5690 assert_eq!(
5691 view.text(cx),
5692 "
5693 a
5694 b
5695 c
5696 "
5697 .unindent()
5698 );
5699 assert_eq!(
5700 view.selections.display_ranges(cx),
5701 [
5702 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5703 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5704 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
5705 ]
5706 );
5707
5708 // Ensure inserting the last character of a multi-byte bracket pair
5709 // doesn't surround the selections with the bracket.
5710 view.handle_input("*", cx);
5711 assert_eq!(
5712 view.text(cx),
5713 "
5714 *
5715 *
5716 *
5717 "
5718 .unindent()
5719 );
5720 assert_eq!(
5721 view.selections.display_ranges(cx),
5722 [
5723 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
5724 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
5725 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
5726 ]
5727 );
5728 });
5729}
5730
5731#[gpui::test]
5732async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
5733 init_test(cx, |_| {});
5734
5735 let language = Arc::new(Language::new(
5736 LanguageConfig {
5737 brackets: BracketPairConfig {
5738 pairs: vec![BracketPair {
5739 start: "{".to_string(),
5740 end: "}".to_string(),
5741 close: true,
5742 surround: true,
5743 newline: true,
5744 }],
5745 ..Default::default()
5746 },
5747 autoclose_before: "}".to_string(),
5748 ..Default::default()
5749 },
5750 Some(tree_sitter_rust::language()),
5751 ));
5752
5753 let text = r#"
5754 a
5755 b
5756 c
5757 "#
5758 .unindent();
5759
5760 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5761 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5762 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5763 editor
5764 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5765 .await;
5766
5767 _ = editor.update(cx, |editor, cx| {
5768 editor.change_selections(None, cx, |s| {
5769 s.select_ranges([
5770 Point::new(0, 1)..Point::new(0, 1),
5771 Point::new(1, 1)..Point::new(1, 1),
5772 Point::new(2, 1)..Point::new(2, 1),
5773 ])
5774 });
5775
5776 editor.handle_input("{", cx);
5777 editor.handle_input("{", cx);
5778 editor.handle_input("_", cx);
5779 assert_eq!(
5780 editor.text(cx),
5781 "
5782 a{{_}}
5783 b{{_}}
5784 c{{_}}
5785 "
5786 .unindent()
5787 );
5788 assert_eq!(
5789 editor.selections.ranges::<Point>(cx),
5790 [
5791 Point::new(0, 4)..Point::new(0, 4),
5792 Point::new(1, 4)..Point::new(1, 4),
5793 Point::new(2, 4)..Point::new(2, 4)
5794 ]
5795 );
5796
5797 editor.backspace(&Default::default(), cx);
5798 editor.backspace(&Default::default(), cx);
5799 assert_eq!(
5800 editor.text(cx),
5801 "
5802 a{}
5803 b{}
5804 c{}
5805 "
5806 .unindent()
5807 );
5808 assert_eq!(
5809 editor.selections.ranges::<Point>(cx),
5810 [
5811 Point::new(0, 2)..Point::new(0, 2),
5812 Point::new(1, 2)..Point::new(1, 2),
5813 Point::new(2, 2)..Point::new(2, 2)
5814 ]
5815 );
5816
5817 editor.delete_to_previous_word_start(&Default::default(), cx);
5818 assert_eq!(
5819 editor.text(cx),
5820 "
5821 a
5822 b
5823 c
5824 "
5825 .unindent()
5826 );
5827 assert_eq!(
5828 editor.selections.ranges::<Point>(cx),
5829 [
5830 Point::new(0, 1)..Point::new(0, 1),
5831 Point::new(1, 1)..Point::new(1, 1),
5832 Point::new(2, 1)..Point::new(2, 1)
5833 ]
5834 );
5835 });
5836}
5837
5838#[gpui::test]
5839async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
5840 init_test(cx, |settings| {
5841 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5842 });
5843
5844 let mut cx = EditorTestContext::new(cx).await;
5845
5846 let language = Arc::new(Language::new(
5847 LanguageConfig {
5848 brackets: BracketPairConfig {
5849 pairs: vec![
5850 BracketPair {
5851 start: "{".to_string(),
5852 end: "}".to_string(),
5853 close: true,
5854 surround: true,
5855 newline: true,
5856 },
5857 BracketPair {
5858 start: "(".to_string(),
5859 end: ")".to_string(),
5860 close: true,
5861 surround: true,
5862 newline: true,
5863 },
5864 BracketPair {
5865 start: "[".to_string(),
5866 end: "]".to_string(),
5867 close: false,
5868 surround: true,
5869 newline: true,
5870 },
5871 ],
5872 ..Default::default()
5873 },
5874 autoclose_before: "})]".to_string(),
5875 ..Default::default()
5876 },
5877 Some(tree_sitter_rust::language()),
5878 ));
5879
5880 cx.language_registry().add(language.clone());
5881 cx.update_buffer(|buffer, cx| {
5882 buffer.set_language(Some(language), cx);
5883 });
5884
5885 cx.set_state(
5886 &"
5887 {(ˇ)}
5888 [[ˇ]]
5889 {(ˇ)}
5890 "
5891 .unindent(),
5892 );
5893
5894 cx.update_editor(|view, cx| {
5895 view.backspace(&Default::default(), cx);
5896 view.backspace(&Default::default(), cx);
5897 });
5898
5899 cx.assert_editor_state(
5900 &"
5901 ˇ
5902 ˇ]]
5903 ˇ
5904 "
5905 .unindent(),
5906 );
5907
5908 cx.update_editor(|view, cx| {
5909 view.handle_input("{", cx);
5910 view.handle_input("{", cx);
5911 view.move_right(&MoveRight, cx);
5912 view.move_right(&MoveRight, cx);
5913 view.move_left(&MoveLeft, cx);
5914 view.move_left(&MoveLeft, cx);
5915 view.backspace(&Default::default(), cx);
5916 });
5917
5918 cx.assert_editor_state(
5919 &"
5920 {ˇ}
5921 {ˇ}]]
5922 {ˇ}
5923 "
5924 .unindent(),
5925 );
5926
5927 cx.update_editor(|view, cx| {
5928 view.backspace(&Default::default(), cx);
5929 });
5930
5931 cx.assert_editor_state(
5932 &"
5933 ˇ
5934 ˇ]]
5935 ˇ
5936 "
5937 .unindent(),
5938 );
5939}
5940
5941#[gpui::test]
5942async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
5943 init_test(cx, |_| {});
5944
5945 let language = Arc::new(Language::new(
5946 LanguageConfig::default(),
5947 Some(tree_sitter_rust::language()),
5948 ));
5949
5950 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
5951 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5952 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5953 editor
5954 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5955 .await;
5956
5957 _ = editor.update(cx, |editor, cx| {
5958 editor.set_auto_replace_emoji_shortcode(true);
5959
5960 editor.handle_input("Hello ", cx);
5961 editor.handle_input(":wave", cx);
5962 assert_eq!(editor.text(cx), "Hello :wave".unindent());
5963
5964 editor.handle_input(":", cx);
5965 assert_eq!(editor.text(cx), "Hello 👋".unindent());
5966
5967 editor.handle_input(" :smile", cx);
5968 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
5969
5970 editor.handle_input(":", cx);
5971 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
5972
5973 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
5974 editor.handle_input(":wave", cx);
5975 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
5976
5977 editor.handle_input(":", cx);
5978 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
5979
5980 editor.handle_input(":1", cx);
5981 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
5982
5983 editor.handle_input(":", cx);
5984 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
5985
5986 // Ensure shortcode does not get replaced when it is part of a word
5987 editor.handle_input(" Test:wave", cx);
5988 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
5989
5990 editor.handle_input(":", cx);
5991 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
5992
5993 editor.set_auto_replace_emoji_shortcode(false);
5994
5995 // Ensure shortcode does not get replaced when auto replace is off
5996 editor.handle_input(" :wave", cx);
5997 assert_eq!(
5998 editor.text(cx),
5999 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6000 );
6001
6002 editor.handle_input(":", cx);
6003 assert_eq!(
6004 editor.text(cx),
6005 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6006 );
6007 });
6008}
6009
6010#[gpui::test]
6011async fn test_snippets(cx: &mut gpui::TestAppContext) {
6012 init_test(cx, |_| {});
6013
6014 let (text, insertion_ranges) = marked_text_ranges(
6015 indoc! {"
6016 a.ˇ b
6017 a.ˇ b
6018 a.ˇ b
6019 "},
6020 false,
6021 );
6022
6023 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6024 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6025
6026 _ = editor.update(cx, |editor, cx| {
6027 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6028
6029 editor
6030 .insert_snippet(&insertion_ranges, snippet, cx)
6031 .unwrap();
6032
6033 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6034 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6035 assert_eq!(editor.text(cx), expected_text);
6036 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6037 }
6038
6039 assert(
6040 editor,
6041 cx,
6042 indoc! {"
6043 a.f(«one», two, «three») b
6044 a.f(«one», two, «three») b
6045 a.f(«one», two, «three») b
6046 "},
6047 );
6048
6049 // Can't move earlier than the first tab stop
6050 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6051 assert(
6052 editor,
6053 cx,
6054 indoc! {"
6055 a.f(«one», two, «three») b
6056 a.f(«one», two, «three») b
6057 a.f(«one», two, «three») b
6058 "},
6059 );
6060
6061 assert!(editor.move_to_next_snippet_tabstop(cx));
6062 assert(
6063 editor,
6064 cx,
6065 indoc! {"
6066 a.f(one, «two», three) b
6067 a.f(one, «two», three) b
6068 a.f(one, «two», three) b
6069 "},
6070 );
6071
6072 editor.move_to_prev_snippet_tabstop(cx);
6073 assert(
6074 editor,
6075 cx,
6076 indoc! {"
6077 a.f(«one», two, «three») b
6078 a.f(«one», two, «three») b
6079 a.f(«one», two, «three») b
6080 "},
6081 );
6082
6083 assert!(editor.move_to_next_snippet_tabstop(cx));
6084 assert(
6085 editor,
6086 cx,
6087 indoc! {"
6088 a.f(one, «two», three) b
6089 a.f(one, «two», three) b
6090 a.f(one, «two», three) b
6091 "},
6092 );
6093 assert!(editor.move_to_next_snippet_tabstop(cx));
6094 assert(
6095 editor,
6096 cx,
6097 indoc! {"
6098 a.f(one, two, three)ˇ b
6099 a.f(one, two, three)ˇ b
6100 a.f(one, two, three)ˇ b
6101 "},
6102 );
6103
6104 // As soon as the last tab stop is reached, snippet state is gone
6105 editor.move_to_prev_snippet_tabstop(cx);
6106 assert(
6107 editor,
6108 cx,
6109 indoc! {"
6110 a.f(one, two, three)ˇ b
6111 a.f(one, two, three)ˇ b
6112 a.f(one, two, three)ˇ b
6113 "},
6114 );
6115 });
6116}
6117
6118#[gpui::test]
6119async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6120 init_test(cx, |_| {});
6121
6122 let fs = FakeFs::new(cx.executor());
6123 fs.insert_file("/file.rs", Default::default()).await;
6124
6125 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6126
6127 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6128 language_registry.add(rust_lang());
6129 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6130 "Rust",
6131 FakeLspAdapter {
6132 capabilities: lsp::ServerCapabilities {
6133 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6134 ..Default::default()
6135 },
6136 ..Default::default()
6137 },
6138 );
6139
6140 let buffer = project
6141 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6142 .await
6143 .unwrap();
6144
6145 cx.executor().start_waiting();
6146 let fake_server = fake_servers.next().await.unwrap();
6147
6148 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6149 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6150 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6151 assert!(cx.read(|cx| editor.is_dirty(cx)));
6152
6153 let save = editor
6154 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6155 .unwrap();
6156 fake_server
6157 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6158 assert_eq!(
6159 params.text_document.uri,
6160 lsp::Url::from_file_path("/file.rs").unwrap()
6161 );
6162 assert_eq!(params.options.tab_size, 4);
6163 Ok(Some(vec![lsp::TextEdit::new(
6164 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6165 ", ".to_string(),
6166 )]))
6167 })
6168 .next()
6169 .await;
6170 cx.executor().start_waiting();
6171 save.await;
6172
6173 assert_eq!(
6174 editor.update(cx, |editor, cx| editor.text(cx)),
6175 "one, two\nthree\n"
6176 );
6177 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6178
6179 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6180 assert!(cx.read(|cx| editor.is_dirty(cx)));
6181
6182 // Ensure we can still save even if formatting hangs.
6183 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6184 assert_eq!(
6185 params.text_document.uri,
6186 lsp::Url::from_file_path("/file.rs").unwrap()
6187 );
6188 futures::future::pending::<()>().await;
6189 unreachable!()
6190 });
6191 let save = editor
6192 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6193 .unwrap();
6194 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6195 cx.executor().start_waiting();
6196 save.await;
6197 assert_eq!(
6198 editor.update(cx, |editor, cx| editor.text(cx)),
6199 "one\ntwo\nthree\n"
6200 );
6201 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6202
6203 // For non-dirty buffer, no formatting request should be sent
6204 let save = editor
6205 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6206 .unwrap();
6207 let _pending_format_request = fake_server
6208 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6209 panic!("Should not be invoked on non-dirty buffer");
6210 })
6211 .next();
6212 cx.executor().start_waiting();
6213 save.await;
6214
6215 // Set rust language override and assert overridden tabsize is sent to language server
6216 update_test_language_settings(cx, |settings| {
6217 settings.languages.insert(
6218 "Rust".into(),
6219 LanguageSettingsContent {
6220 tab_size: NonZeroU32::new(8),
6221 ..Default::default()
6222 },
6223 );
6224 });
6225
6226 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6227 assert!(cx.read(|cx| editor.is_dirty(cx)));
6228 let save = editor
6229 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6230 .unwrap();
6231 fake_server
6232 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6233 assert_eq!(
6234 params.text_document.uri,
6235 lsp::Url::from_file_path("/file.rs").unwrap()
6236 );
6237 assert_eq!(params.options.tab_size, 8);
6238 Ok(Some(vec![]))
6239 })
6240 .next()
6241 .await;
6242 cx.executor().start_waiting();
6243 save.await;
6244}
6245
6246#[gpui::test]
6247async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6248 init_test(cx, |_| {});
6249
6250 let cols = 4;
6251 let rows = 10;
6252 let sample_text_1 = sample_text(rows, cols, 'a');
6253 assert_eq!(
6254 sample_text_1,
6255 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6256 );
6257 let sample_text_2 = sample_text(rows, cols, 'l');
6258 assert_eq!(
6259 sample_text_2,
6260 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6261 );
6262 let sample_text_3 = sample_text(rows, cols, 'v');
6263 assert_eq!(
6264 sample_text_3,
6265 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6266 );
6267
6268 let fs = FakeFs::new(cx.executor());
6269 fs.insert_tree(
6270 "/a",
6271 json!({
6272 "main.rs": sample_text_1,
6273 "other.rs": sample_text_2,
6274 "lib.rs": sample_text_3,
6275 }),
6276 )
6277 .await;
6278
6279 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6280 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6281 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6282
6283 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6284 language_registry.add(rust_lang());
6285 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6286 "Rust",
6287 FakeLspAdapter {
6288 capabilities: lsp::ServerCapabilities {
6289 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6290 ..Default::default()
6291 },
6292 ..Default::default()
6293 },
6294 );
6295
6296 let worktree = project.update(cx, |project, cx| {
6297 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
6298 assert_eq!(worktrees.len(), 1);
6299 worktrees.pop().unwrap()
6300 });
6301 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6302
6303 let buffer_1 = project
6304 .update(cx, |project, cx| {
6305 project.open_buffer((worktree_id, "main.rs"), cx)
6306 })
6307 .await
6308 .unwrap();
6309 let buffer_2 = project
6310 .update(cx, |project, cx| {
6311 project.open_buffer((worktree_id, "other.rs"), cx)
6312 })
6313 .await
6314 .unwrap();
6315 let buffer_3 = project
6316 .update(cx, |project, cx| {
6317 project.open_buffer((worktree_id, "lib.rs"), cx)
6318 })
6319 .await
6320 .unwrap();
6321
6322 let multi_buffer = cx.new_model(|cx| {
6323 let mut multi_buffer = MultiBuffer::new(0, ReadWrite);
6324 multi_buffer.push_excerpts(
6325 buffer_1.clone(),
6326 [
6327 ExcerptRange {
6328 context: Point::new(0, 0)..Point::new(3, 0),
6329 primary: None,
6330 },
6331 ExcerptRange {
6332 context: Point::new(5, 0)..Point::new(7, 0),
6333 primary: None,
6334 },
6335 ExcerptRange {
6336 context: Point::new(9, 0)..Point::new(10, 4),
6337 primary: None,
6338 },
6339 ],
6340 cx,
6341 );
6342 multi_buffer.push_excerpts(
6343 buffer_2.clone(),
6344 [
6345 ExcerptRange {
6346 context: Point::new(0, 0)..Point::new(3, 0),
6347 primary: None,
6348 },
6349 ExcerptRange {
6350 context: Point::new(5, 0)..Point::new(7, 0),
6351 primary: None,
6352 },
6353 ExcerptRange {
6354 context: Point::new(9, 0)..Point::new(10, 4),
6355 primary: None,
6356 },
6357 ],
6358 cx,
6359 );
6360 multi_buffer.push_excerpts(
6361 buffer_3.clone(),
6362 [
6363 ExcerptRange {
6364 context: Point::new(0, 0)..Point::new(3, 0),
6365 primary: None,
6366 },
6367 ExcerptRange {
6368 context: Point::new(5, 0)..Point::new(7, 0),
6369 primary: None,
6370 },
6371 ExcerptRange {
6372 context: Point::new(9, 0)..Point::new(10, 4),
6373 primary: None,
6374 },
6375 ],
6376 cx,
6377 );
6378 multi_buffer
6379 });
6380 let multi_buffer_editor = cx.new_view(|cx| {
6381 Editor::new(
6382 EditorMode::Full,
6383 multi_buffer,
6384 Some(project.clone()),
6385 true,
6386 cx,
6387 )
6388 });
6389
6390 multi_buffer_editor.update(cx, |editor, cx| {
6391 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
6392 editor.insert("|one|two|three|", cx);
6393 });
6394 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6395 multi_buffer_editor.update(cx, |editor, cx| {
6396 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
6397 s.select_ranges(Some(60..70))
6398 });
6399 editor.insert("|four|five|six|", cx);
6400 });
6401 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6402
6403 // First two buffers should be edited, but not the third one.
6404 assert_eq!(
6405 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6406 "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}",
6407 );
6408 buffer_1.update(cx, |buffer, _| {
6409 assert!(buffer.is_dirty());
6410 assert_eq!(
6411 buffer.text(),
6412 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
6413 )
6414 });
6415 buffer_2.update(cx, |buffer, _| {
6416 assert!(buffer.is_dirty());
6417 assert_eq!(
6418 buffer.text(),
6419 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
6420 )
6421 });
6422 buffer_3.update(cx, |buffer, _| {
6423 assert!(!buffer.is_dirty());
6424 assert_eq!(buffer.text(), sample_text_3,)
6425 });
6426
6427 cx.executor().start_waiting();
6428 let save = multi_buffer_editor
6429 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6430 .unwrap();
6431
6432 let fake_server = fake_servers.next().await.unwrap();
6433 fake_server
6434 .server
6435 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6436 Ok(Some(vec![lsp::TextEdit::new(
6437 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6438 format!("[{} formatted]", params.text_document.uri),
6439 )]))
6440 })
6441 .detach();
6442 save.await;
6443
6444 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
6445 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
6446 assert_eq!(
6447 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6448 "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}",
6449 );
6450 buffer_1.update(cx, |buffer, _| {
6451 assert!(!buffer.is_dirty());
6452 assert_eq!(
6453 buffer.text(),
6454 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
6455 )
6456 });
6457 buffer_2.update(cx, |buffer, _| {
6458 assert!(!buffer.is_dirty());
6459 assert_eq!(
6460 buffer.text(),
6461 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
6462 )
6463 });
6464 buffer_3.update(cx, |buffer, _| {
6465 assert!(!buffer.is_dirty());
6466 assert_eq!(buffer.text(), sample_text_3,)
6467 });
6468}
6469
6470#[gpui::test]
6471async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
6472 init_test(cx, |_| {});
6473
6474 let fs = FakeFs::new(cx.executor());
6475 fs.insert_file("/file.rs", Default::default()).await;
6476
6477 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6478
6479 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6480 language_registry.add(rust_lang());
6481 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6482 "Rust",
6483 FakeLspAdapter {
6484 capabilities: lsp::ServerCapabilities {
6485 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
6486 ..Default::default()
6487 },
6488 ..Default::default()
6489 },
6490 );
6491
6492 let buffer = project
6493 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6494 .await
6495 .unwrap();
6496
6497 cx.executor().start_waiting();
6498 let fake_server = fake_servers.next().await.unwrap();
6499
6500 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6501 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6502 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6503 assert!(cx.read(|cx| editor.is_dirty(cx)));
6504
6505 let save = editor
6506 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6507 .unwrap();
6508 fake_server
6509 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6510 assert_eq!(
6511 params.text_document.uri,
6512 lsp::Url::from_file_path("/file.rs").unwrap()
6513 );
6514 assert_eq!(params.options.tab_size, 4);
6515 Ok(Some(vec![lsp::TextEdit::new(
6516 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6517 ", ".to_string(),
6518 )]))
6519 })
6520 .next()
6521 .await;
6522 cx.executor().start_waiting();
6523 save.await;
6524 assert_eq!(
6525 editor.update(cx, |editor, cx| editor.text(cx)),
6526 "one, two\nthree\n"
6527 );
6528 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6529
6530 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6531 assert!(cx.read(|cx| editor.is_dirty(cx)));
6532
6533 // Ensure we can still save even if formatting hangs.
6534 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
6535 move |params, _| async move {
6536 assert_eq!(
6537 params.text_document.uri,
6538 lsp::Url::from_file_path("/file.rs").unwrap()
6539 );
6540 futures::future::pending::<()>().await;
6541 unreachable!()
6542 },
6543 );
6544 let save = editor
6545 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6546 .unwrap();
6547 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6548 cx.executor().start_waiting();
6549 save.await;
6550 assert_eq!(
6551 editor.update(cx, |editor, cx| editor.text(cx)),
6552 "one\ntwo\nthree\n"
6553 );
6554 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6555
6556 // For non-dirty buffer, no formatting request should be sent
6557 let save = editor
6558 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6559 .unwrap();
6560 let _pending_format_request = fake_server
6561 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6562 panic!("Should not be invoked on non-dirty buffer");
6563 })
6564 .next();
6565 cx.executor().start_waiting();
6566 save.await;
6567
6568 // Set Rust language override and assert overridden tabsize is sent to language server
6569 update_test_language_settings(cx, |settings| {
6570 settings.languages.insert(
6571 "Rust".into(),
6572 LanguageSettingsContent {
6573 tab_size: NonZeroU32::new(8),
6574 ..Default::default()
6575 },
6576 );
6577 });
6578
6579 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6580 assert!(cx.read(|cx| editor.is_dirty(cx)));
6581 let save = editor
6582 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6583 .unwrap();
6584 fake_server
6585 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6586 assert_eq!(
6587 params.text_document.uri,
6588 lsp::Url::from_file_path("/file.rs").unwrap()
6589 );
6590 assert_eq!(params.options.tab_size, 8);
6591 Ok(Some(vec![]))
6592 })
6593 .next()
6594 .await;
6595 cx.executor().start_waiting();
6596 save.await;
6597}
6598
6599#[gpui::test]
6600async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
6601 init_test(cx, |settings| {
6602 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
6603 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
6604 ))
6605 });
6606
6607 let fs = FakeFs::new(cx.executor());
6608 fs.insert_file("/file.rs", Default::default()).await;
6609
6610 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6611
6612 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6613 language_registry.add(Arc::new(Language::new(
6614 LanguageConfig {
6615 name: "Rust".into(),
6616 matcher: LanguageMatcher {
6617 path_suffixes: vec!["rs".to_string()],
6618 ..Default::default()
6619 },
6620 ..LanguageConfig::default()
6621 },
6622 Some(tree_sitter_rust::language()),
6623 )));
6624 update_test_language_settings(cx, |settings| {
6625 // Enable Prettier formatting for the same buffer, and ensure
6626 // LSP is called instead of Prettier.
6627 settings.defaults.prettier = Some(PrettierSettings {
6628 allowed: true,
6629 ..PrettierSettings::default()
6630 });
6631 });
6632 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6633 "Rust",
6634 FakeLspAdapter {
6635 capabilities: lsp::ServerCapabilities {
6636 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6637 ..Default::default()
6638 },
6639 ..Default::default()
6640 },
6641 );
6642
6643 let buffer = project
6644 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6645 .await
6646 .unwrap();
6647
6648 cx.executor().start_waiting();
6649 let fake_server = fake_servers.next().await.unwrap();
6650
6651 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6652 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6653 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6654
6655 let format = editor
6656 .update(cx, |editor, cx| {
6657 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
6658 })
6659 .unwrap();
6660 fake_server
6661 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6662 assert_eq!(
6663 params.text_document.uri,
6664 lsp::Url::from_file_path("/file.rs").unwrap()
6665 );
6666 assert_eq!(params.options.tab_size, 4);
6667 Ok(Some(vec![lsp::TextEdit::new(
6668 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6669 ", ".to_string(),
6670 )]))
6671 })
6672 .next()
6673 .await;
6674 cx.executor().start_waiting();
6675 format.await;
6676 assert_eq!(
6677 editor.update(cx, |editor, cx| editor.text(cx)),
6678 "one, two\nthree\n"
6679 );
6680
6681 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6682 // Ensure we don't lock if formatting hangs.
6683 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6684 assert_eq!(
6685 params.text_document.uri,
6686 lsp::Url::from_file_path("/file.rs").unwrap()
6687 );
6688 futures::future::pending::<()>().await;
6689 unreachable!()
6690 });
6691 let format = editor
6692 .update(cx, |editor, cx| {
6693 editor.perform_format(project, FormatTrigger::Manual, cx)
6694 })
6695 .unwrap();
6696 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6697 cx.executor().start_waiting();
6698 format.await;
6699 assert_eq!(
6700 editor.update(cx, |editor, cx| editor.text(cx)),
6701 "one\ntwo\nthree\n"
6702 );
6703}
6704
6705#[gpui::test]
6706async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
6707 init_test(cx, |_| {});
6708
6709 let mut cx = EditorLspTestContext::new_rust(
6710 lsp::ServerCapabilities {
6711 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6712 ..Default::default()
6713 },
6714 cx,
6715 )
6716 .await;
6717
6718 cx.set_state(indoc! {"
6719 one.twoˇ
6720 "});
6721
6722 // The format request takes a long time. When it completes, it inserts
6723 // a newline and an indent before the `.`
6724 cx.lsp
6725 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
6726 let executor = cx.background_executor().clone();
6727 async move {
6728 executor.timer(Duration::from_millis(100)).await;
6729 Ok(Some(vec![lsp::TextEdit {
6730 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
6731 new_text: "\n ".into(),
6732 }]))
6733 }
6734 });
6735
6736 // Submit a format request.
6737 let format_1 = cx
6738 .update_editor(|editor, cx| editor.format(&Format, cx))
6739 .unwrap();
6740 cx.executor().run_until_parked();
6741
6742 // Submit a second format request.
6743 let format_2 = cx
6744 .update_editor(|editor, cx| editor.format(&Format, cx))
6745 .unwrap();
6746 cx.executor().run_until_parked();
6747
6748 // Wait for both format requests to complete
6749 cx.executor().advance_clock(Duration::from_millis(200));
6750 cx.executor().start_waiting();
6751 format_1.await.unwrap();
6752 cx.executor().start_waiting();
6753 format_2.await.unwrap();
6754
6755 // The formatting edits only happens once.
6756 cx.assert_editor_state(indoc! {"
6757 one
6758 .twoˇ
6759 "});
6760}
6761
6762#[gpui::test]
6763async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
6764 init_test(cx, |settings| {
6765 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
6766 });
6767
6768 let mut cx = EditorLspTestContext::new_rust(
6769 lsp::ServerCapabilities {
6770 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6771 ..Default::default()
6772 },
6773 cx,
6774 )
6775 .await;
6776
6777 // Set up a buffer white some trailing whitespace and no trailing newline.
6778 cx.set_state(
6779 &[
6780 "one ", //
6781 "twoˇ", //
6782 "three ", //
6783 "four", //
6784 ]
6785 .join("\n"),
6786 );
6787
6788 // Submit a format request.
6789 let format = cx
6790 .update_editor(|editor, cx| editor.format(&Format, cx))
6791 .unwrap();
6792
6793 // Record which buffer changes have been sent to the language server
6794 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
6795 cx.lsp
6796 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
6797 let buffer_changes = buffer_changes.clone();
6798 move |params, _| {
6799 buffer_changes.lock().extend(
6800 params
6801 .content_changes
6802 .into_iter()
6803 .map(|e| (e.range.unwrap(), e.text)),
6804 );
6805 }
6806 });
6807
6808 // Handle formatting requests to the language server.
6809 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
6810 let buffer_changes = buffer_changes.clone();
6811 move |_, _| {
6812 // When formatting is requested, trailing whitespace has already been stripped,
6813 // and the trailing newline has already been added.
6814 assert_eq!(
6815 &buffer_changes.lock()[1..],
6816 &[
6817 (
6818 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
6819 "".into()
6820 ),
6821 (
6822 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
6823 "".into()
6824 ),
6825 (
6826 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
6827 "\n".into()
6828 ),
6829 ]
6830 );
6831
6832 // Insert blank lines between each line of the buffer.
6833 async move {
6834 Ok(Some(vec![
6835 lsp::TextEdit {
6836 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
6837 new_text: "\n".into(),
6838 },
6839 lsp::TextEdit {
6840 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
6841 new_text: "\n".into(),
6842 },
6843 ]))
6844 }
6845 }
6846 });
6847
6848 // After formatting the buffer, the trailing whitespace is stripped,
6849 // a newline is appended, and the edits provided by the language server
6850 // have been applied.
6851 format.await.unwrap();
6852 cx.assert_editor_state(
6853 &[
6854 "one", //
6855 "", //
6856 "twoˇ", //
6857 "", //
6858 "three", //
6859 "four", //
6860 "", //
6861 ]
6862 .join("\n"),
6863 );
6864
6865 // Undoing the formatting undoes the trailing whitespace removal, the
6866 // trailing newline, and the LSP edits.
6867 cx.update_buffer(|buffer, cx| buffer.undo(cx));
6868 cx.assert_editor_state(
6869 &[
6870 "one ", //
6871 "twoˇ", //
6872 "three ", //
6873 "four", //
6874 ]
6875 .join("\n"),
6876 );
6877}
6878
6879#[gpui::test]
6880async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
6881 cx: &mut gpui::TestAppContext,
6882) {
6883 init_test(cx, |_| {});
6884
6885 cx.update(|cx| {
6886 cx.update_global::<SettingsStore, _>(|settings, cx| {
6887 settings.update_user_settings::<EditorSettings>(cx, |settings| {
6888 settings.auto_signature_help = Some(true);
6889 });
6890 });
6891 });
6892
6893 let mut cx = EditorLspTestContext::new_rust(
6894 lsp::ServerCapabilities {
6895 signature_help_provider: Some(lsp::SignatureHelpOptions {
6896 ..Default::default()
6897 }),
6898 ..Default::default()
6899 },
6900 cx,
6901 )
6902 .await;
6903
6904 let language = Language::new(
6905 LanguageConfig {
6906 name: "Rust".into(),
6907 brackets: BracketPairConfig {
6908 pairs: vec![
6909 BracketPair {
6910 start: "{".to_string(),
6911 end: "}".to_string(),
6912 close: true,
6913 surround: true,
6914 newline: true,
6915 },
6916 BracketPair {
6917 start: "(".to_string(),
6918 end: ")".to_string(),
6919 close: true,
6920 surround: true,
6921 newline: true,
6922 },
6923 BracketPair {
6924 start: "/*".to_string(),
6925 end: " */".to_string(),
6926 close: true,
6927 surround: true,
6928 newline: true,
6929 },
6930 BracketPair {
6931 start: "[".to_string(),
6932 end: "]".to_string(),
6933 close: false,
6934 surround: false,
6935 newline: true,
6936 },
6937 BracketPair {
6938 start: "\"".to_string(),
6939 end: "\"".to_string(),
6940 close: true,
6941 surround: true,
6942 newline: false,
6943 },
6944 BracketPair {
6945 start: "<".to_string(),
6946 end: ">".to_string(),
6947 close: false,
6948 surround: true,
6949 newline: true,
6950 },
6951 ],
6952 ..Default::default()
6953 },
6954 autoclose_before: "})]".to_string(),
6955 ..Default::default()
6956 },
6957 Some(tree_sitter_rust::language()),
6958 );
6959 let language = Arc::new(language);
6960
6961 cx.language_registry().add(language.clone());
6962 cx.update_buffer(|buffer, cx| {
6963 buffer.set_language(Some(language), cx);
6964 });
6965
6966 cx.set_state(
6967 &r#"
6968 fn main() {
6969 sampleˇ
6970 }
6971 "#
6972 .unindent(),
6973 );
6974
6975 cx.update_editor(|view, cx| {
6976 view.handle_input("(", cx);
6977 });
6978 cx.assert_editor_state(
6979 &"
6980 fn main() {
6981 sample(ˇ)
6982 }
6983 "
6984 .unindent(),
6985 );
6986
6987 let mocked_response = lsp::SignatureHelp {
6988 signatures: vec![lsp::SignatureInformation {
6989 label: "fn sample(param1: u8, param2: u8)".to_string(),
6990 documentation: None,
6991 parameters: Some(vec![
6992 lsp::ParameterInformation {
6993 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
6994 documentation: None,
6995 },
6996 lsp::ParameterInformation {
6997 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
6998 documentation: None,
6999 },
7000 ]),
7001 active_parameter: None,
7002 }],
7003 active_signature: Some(0),
7004 active_parameter: Some(0),
7005 };
7006 handle_signature_help_request(&mut cx, mocked_response).await;
7007
7008 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7009 .await;
7010
7011 cx.editor(|editor, _| {
7012 let signature_help_state = editor.signature_help_state.popover().cloned();
7013 assert!(signature_help_state.is_some());
7014 let ParsedMarkdown {
7015 text, highlights, ..
7016 } = signature_help_state.unwrap().parsed_content;
7017 assert_eq!(text, "param1: u8, param2: u8");
7018 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7019 });
7020}
7021
7022#[gpui::test]
7023async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7024 init_test(cx, |_| {});
7025
7026 cx.update(|cx| {
7027 cx.update_global::<SettingsStore, _>(|settings, cx| {
7028 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7029 settings.auto_signature_help = Some(false);
7030 settings.show_signature_help_after_edits = Some(false);
7031 });
7032 });
7033 });
7034
7035 let mut cx = EditorLspTestContext::new_rust(
7036 lsp::ServerCapabilities {
7037 signature_help_provider: Some(lsp::SignatureHelpOptions {
7038 ..Default::default()
7039 }),
7040 ..Default::default()
7041 },
7042 cx,
7043 )
7044 .await;
7045
7046 let language = Language::new(
7047 LanguageConfig {
7048 name: "Rust".into(),
7049 brackets: BracketPairConfig {
7050 pairs: vec![
7051 BracketPair {
7052 start: "{".to_string(),
7053 end: "}".to_string(),
7054 close: true,
7055 surround: true,
7056 newline: true,
7057 },
7058 BracketPair {
7059 start: "(".to_string(),
7060 end: ")".to_string(),
7061 close: true,
7062 surround: true,
7063 newline: true,
7064 },
7065 BracketPair {
7066 start: "/*".to_string(),
7067 end: " */".to_string(),
7068 close: true,
7069 surround: true,
7070 newline: true,
7071 },
7072 BracketPair {
7073 start: "[".to_string(),
7074 end: "]".to_string(),
7075 close: false,
7076 surround: false,
7077 newline: true,
7078 },
7079 BracketPair {
7080 start: "\"".to_string(),
7081 end: "\"".to_string(),
7082 close: true,
7083 surround: true,
7084 newline: false,
7085 },
7086 BracketPair {
7087 start: "<".to_string(),
7088 end: ">".to_string(),
7089 close: false,
7090 surround: true,
7091 newline: true,
7092 },
7093 ],
7094 ..Default::default()
7095 },
7096 autoclose_before: "})]".to_string(),
7097 ..Default::default()
7098 },
7099 Some(tree_sitter_rust::language()),
7100 );
7101 let language = Arc::new(language);
7102
7103 cx.language_registry().add(language.clone());
7104 cx.update_buffer(|buffer, cx| {
7105 buffer.set_language(Some(language), cx);
7106 });
7107
7108 // Ensure that signature_help is not called when no signature help is enabled.
7109 cx.set_state(
7110 &r#"
7111 fn main() {
7112 sampleˇ
7113 }
7114 "#
7115 .unindent(),
7116 );
7117 cx.update_editor(|view, cx| {
7118 view.handle_input("(", cx);
7119 });
7120 cx.assert_editor_state(
7121 &"
7122 fn main() {
7123 sample(ˇ)
7124 }
7125 "
7126 .unindent(),
7127 );
7128 cx.editor(|editor, _| {
7129 assert!(editor.signature_help_state.task().is_none());
7130 });
7131
7132 let mocked_response = lsp::SignatureHelp {
7133 signatures: vec![lsp::SignatureInformation {
7134 label: "fn sample(param1: u8, param2: u8)".to_string(),
7135 documentation: None,
7136 parameters: Some(vec![
7137 lsp::ParameterInformation {
7138 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7139 documentation: None,
7140 },
7141 lsp::ParameterInformation {
7142 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7143 documentation: None,
7144 },
7145 ]),
7146 active_parameter: None,
7147 }],
7148 active_signature: Some(0),
7149 active_parameter: Some(0),
7150 };
7151
7152 // Ensure that signature_help is called when enabled afte edits
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(false);
7157 settings.show_signature_help_after_edits = Some(true);
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.clone()).await;
7181 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7182 .await;
7183 cx.update_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 editor.signature_help_state = SignatureHelpState::default();
7192 });
7193
7194 // Ensure that signature_help is called when auto signature help override is enabled
7195 cx.update(|cx| {
7196 cx.update_global::<SettingsStore, _>(|settings, cx| {
7197 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7198 settings.auto_signature_help = Some(true);
7199 settings.show_signature_help_after_edits = Some(false);
7200 });
7201 });
7202 });
7203 cx.set_state(
7204 &r#"
7205 fn main() {
7206 sampleˇ
7207 }
7208 "#
7209 .unindent(),
7210 );
7211 cx.update_editor(|view, cx| {
7212 view.handle_input("(", cx);
7213 });
7214 cx.assert_editor_state(
7215 &"
7216 fn main() {
7217 sample(ˇ)
7218 }
7219 "
7220 .unindent(),
7221 );
7222 handle_signature_help_request(&mut cx, mocked_response).await;
7223 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7224 .await;
7225 cx.editor(|editor, _| {
7226 let signature_help_state = editor.signature_help_state.popover().cloned();
7227 assert!(signature_help_state.is_some());
7228 let ParsedMarkdown {
7229 text, highlights, ..
7230 } = signature_help_state.unwrap().parsed_content;
7231 assert_eq!(text, "param1: u8, param2: u8");
7232 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7233 });
7234}
7235
7236#[gpui::test]
7237async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7238 init_test(cx, |_| {});
7239 cx.update(|cx| {
7240 cx.update_global::<SettingsStore, _>(|settings, cx| {
7241 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7242 settings.auto_signature_help = Some(true);
7243 });
7244 });
7245 });
7246
7247 let mut cx = EditorLspTestContext::new_rust(
7248 lsp::ServerCapabilities {
7249 signature_help_provider: Some(lsp::SignatureHelpOptions {
7250 ..Default::default()
7251 }),
7252 ..Default::default()
7253 },
7254 cx,
7255 )
7256 .await;
7257
7258 // A test that directly calls `show_signature_help`
7259 cx.update_editor(|editor, cx| {
7260 editor.show_signature_help(&ShowSignatureHelp, cx);
7261 });
7262
7263 let mocked_response = lsp::SignatureHelp {
7264 signatures: vec![lsp::SignatureInformation {
7265 label: "fn sample(param1: u8, param2: u8)".to_string(),
7266 documentation: None,
7267 parameters: Some(vec![
7268 lsp::ParameterInformation {
7269 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7270 documentation: None,
7271 },
7272 lsp::ParameterInformation {
7273 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7274 documentation: None,
7275 },
7276 ]),
7277 active_parameter: None,
7278 }],
7279 active_signature: Some(0),
7280 active_parameter: Some(0),
7281 };
7282 handle_signature_help_request(&mut cx, mocked_response).await;
7283
7284 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7285 .await;
7286
7287 cx.editor(|editor, _| {
7288 let signature_help_state = editor.signature_help_state.popover().cloned();
7289 assert!(signature_help_state.is_some());
7290 let ParsedMarkdown {
7291 text, highlights, ..
7292 } = signature_help_state.unwrap().parsed_content;
7293 assert_eq!(text, "param1: u8, param2: u8");
7294 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7295 });
7296
7297 // When exiting outside from inside the brackets, `signature_help` is closed.
7298 cx.set_state(indoc! {"
7299 fn main() {
7300 sample(ˇ);
7301 }
7302
7303 fn sample(param1: u8, param2: u8) {}
7304 "});
7305
7306 cx.update_editor(|editor, cx| {
7307 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
7308 });
7309
7310 let mocked_response = lsp::SignatureHelp {
7311 signatures: Vec::new(),
7312 active_signature: None,
7313 active_parameter: None,
7314 };
7315 handle_signature_help_request(&mut cx, mocked_response).await;
7316
7317 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7318 .await;
7319
7320 cx.editor(|editor, _| {
7321 assert!(!editor.signature_help_state.is_shown());
7322 });
7323
7324 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
7325 cx.set_state(indoc! {"
7326 fn main() {
7327 sample(ˇ);
7328 }
7329
7330 fn sample(param1: u8, param2: u8) {}
7331 "});
7332
7333 let mocked_response = lsp::SignatureHelp {
7334 signatures: vec![lsp::SignatureInformation {
7335 label: "fn sample(param1: u8, param2: u8)".to_string(),
7336 documentation: None,
7337 parameters: Some(vec![
7338 lsp::ParameterInformation {
7339 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7340 documentation: None,
7341 },
7342 lsp::ParameterInformation {
7343 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7344 documentation: None,
7345 },
7346 ]),
7347 active_parameter: None,
7348 }],
7349 active_signature: Some(0),
7350 active_parameter: Some(0),
7351 };
7352 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7353 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7354 .await;
7355 cx.editor(|editor, _| {
7356 assert!(editor.signature_help_state.is_shown());
7357 });
7358
7359 // Restore the popover with more parameter input
7360 cx.set_state(indoc! {"
7361 fn main() {
7362 sample(param1, param2ˇ);
7363 }
7364
7365 fn sample(param1: u8, param2: u8) {}
7366 "});
7367
7368 let mocked_response = lsp::SignatureHelp {
7369 signatures: vec![lsp::SignatureInformation {
7370 label: "fn sample(param1: u8, param2: u8)".to_string(),
7371 documentation: None,
7372 parameters: Some(vec![
7373 lsp::ParameterInformation {
7374 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7375 documentation: None,
7376 },
7377 lsp::ParameterInformation {
7378 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7379 documentation: None,
7380 },
7381 ]),
7382 active_parameter: None,
7383 }],
7384 active_signature: Some(0),
7385 active_parameter: Some(1),
7386 };
7387 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7388 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7389 .await;
7390
7391 // When selecting a range, the popover is gone.
7392 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
7393 cx.update_editor(|editor, cx| {
7394 editor.change_selections(None, cx, |s| {
7395 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7396 })
7397 });
7398 cx.assert_editor_state(indoc! {"
7399 fn main() {
7400 sample(param1, «ˇparam2»);
7401 }
7402
7403 fn sample(param1: u8, param2: u8) {}
7404 "});
7405 cx.editor(|editor, _| {
7406 assert!(!editor.signature_help_state.is_shown());
7407 });
7408
7409 // When unselecting again, the popover is back if within the brackets.
7410 cx.update_editor(|editor, cx| {
7411 editor.change_selections(None, cx, |s| {
7412 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7413 })
7414 });
7415 cx.assert_editor_state(indoc! {"
7416 fn main() {
7417 sample(param1, ˇparam2);
7418 }
7419
7420 fn sample(param1: u8, param2: u8) {}
7421 "});
7422 handle_signature_help_request(&mut cx, mocked_response).await;
7423 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7424 .await;
7425 cx.editor(|editor, _| {
7426 assert!(editor.signature_help_state.is_shown());
7427 });
7428
7429 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
7430 cx.update_editor(|editor, cx| {
7431 editor.change_selections(None, cx, |s| {
7432 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
7433 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7434 })
7435 });
7436 cx.assert_editor_state(indoc! {"
7437 fn main() {
7438 sample(param1, ˇparam2);
7439 }
7440
7441 fn sample(param1: u8, param2: u8) {}
7442 "});
7443
7444 let mocked_response = lsp::SignatureHelp {
7445 signatures: vec![lsp::SignatureInformation {
7446 label: "fn sample(param1: u8, param2: u8)".to_string(),
7447 documentation: None,
7448 parameters: Some(vec![
7449 lsp::ParameterInformation {
7450 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7451 documentation: None,
7452 },
7453 lsp::ParameterInformation {
7454 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7455 documentation: None,
7456 },
7457 ]),
7458 active_parameter: None,
7459 }],
7460 active_signature: Some(0),
7461 active_parameter: Some(1),
7462 };
7463 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7464 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7465 .await;
7466 cx.update_editor(|editor, cx| {
7467 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
7468 });
7469 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7470 .await;
7471 cx.update_editor(|editor, cx| {
7472 editor.change_selections(None, cx, |s| {
7473 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7474 })
7475 });
7476 cx.assert_editor_state(indoc! {"
7477 fn main() {
7478 sample(param1, «ˇparam2»);
7479 }
7480
7481 fn sample(param1: u8, param2: u8) {}
7482 "});
7483 cx.update_editor(|editor, cx| {
7484 editor.change_selections(None, cx, |s| {
7485 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7486 })
7487 });
7488 cx.assert_editor_state(indoc! {"
7489 fn main() {
7490 sample(param1, ˇparam2);
7491 }
7492
7493 fn sample(param1: u8, param2: u8) {}
7494 "});
7495 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
7496 .await;
7497}
7498
7499#[gpui::test]
7500async fn test_completion(cx: &mut gpui::TestAppContext) {
7501 init_test(cx, |_| {});
7502
7503 let mut cx = EditorLspTestContext::new_rust(
7504 lsp::ServerCapabilities {
7505 completion_provider: Some(lsp::CompletionOptions {
7506 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7507 resolve_provider: Some(true),
7508 ..Default::default()
7509 }),
7510 ..Default::default()
7511 },
7512 cx,
7513 )
7514 .await;
7515 let counter = Arc::new(AtomicUsize::new(0));
7516
7517 cx.set_state(indoc! {"
7518 oneˇ
7519 two
7520 three
7521 "});
7522 cx.simulate_keystroke(".");
7523 handle_completion_request(
7524 &mut cx,
7525 indoc! {"
7526 one.|<>
7527 two
7528 three
7529 "},
7530 vec!["first_completion", "second_completion"],
7531 counter.clone(),
7532 )
7533 .await;
7534 cx.condition(|editor, _| editor.context_menu_visible())
7535 .await;
7536 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
7537
7538 let apply_additional_edits = cx.update_editor(|editor, cx| {
7539 editor.context_menu_next(&Default::default(), cx);
7540 editor
7541 .confirm_completion(&ConfirmCompletion::default(), cx)
7542 .unwrap()
7543 });
7544 cx.assert_editor_state(indoc! {"
7545 one.second_completionˇ
7546 two
7547 three
7548 "});
7549
7550 handle_resolve_completion_request(
7551 &mut cx,
7552 Some(vec![
7553 (
7554 //This overlaps with the primary completion edit which is
7555 //misbehavior from the LSP spec, test that we filter it out
7556 indoc! {"
7557 one.second_ˇcompletion
7558 two
7559 threeˇ
7560 "},
7561 "overlapping additional edit",
7562 ),
7563 (
7564 indoc! {"
7565 one.second_completion
7566 two
7567 threeˇ
7568 "},
7569 "\nadditional edit",
7570 ),
7571 ]),
7572 )
7573 .await;
7574 apply_additional_edits.await.unwrap();
7575 cx.assert_editor_state(indoc! {"
7576 one.second_completionˇ
7577 two
7578 three
7579 additional edit
7580 "});
7581
7582 cx.set_state(indoc! {"
7583 one.second_completion
7584 twoˇ
7585 threeˇ
7586 additional edit
7587 "});
7588 cx.simulate_keystroke(" ");
7589 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7590 cx.simulate_keystroke("s");
7591 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7592
7593 cx.assert_editor_state(indoc! {"
7594 one.second_completion
7595 two sˇ
7596 three sˇ
7597 additional edit
7598 "});
7599 handle_completion_request(
7600 &mut cx,
7601 indoc! {"
7602 one.second_completion
7603 two s
7604 three <s|>
7605 additional edit
7606 "},
7607 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
7608 counter.clone(),
7609 )
7610 .await;
7611 cx.condition(|editor, _| editor.context_menu_visible())
7612 .await;
7613 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
7614
7615 cx.simulate_keystroke("i");
7616
7617 handle_completion_request(
7618 &mut cx,
7619 indoc! {"
7620 one.second_completion
7621 two si
7622 three <si|>
7623 additional edit
7624 "},
7625 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
7626 counter.clone(),
7627 )
7628 .await;
7629 cx.condition(|editor, _| editor.context_menu_visible())
7630 .await;
7631 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
7632
7633 let apply_additional_edits = cx.update_editor(|editor, cx| {
7634 editor
7635 .confirm_completion(&ConfirmCompletion::default(), cx)
7636 .unwrap()
7637 });
7638 cx.assert_editor_state(indoc! {"
7639 one.second_completion
7640 two sixth_completionˇ
7641 three sixth_completionˇ
7642 additional edit
7643 "});
7644
7645 handle_resolve_completion_request(&mut cx, None).await;
7646 apply_additional_edits.await.unwrap();
7647
7648 _ = cx.update(|cx| {
7649 cx.update_global::<SettingsStore, _>(|settings, cx| {
7650 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7651 settings.show_completions_on_input = Some(false);
7652 });
7653 })
7654 });
7655 cx.set_state("editorˇ");
7656 cx.simulate_keystroke(".");
7657 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7658 cx.simulate_keystroke("c");
7659 cx.simulate_keystroke("l");
7660 cx.simulate_keystroke("o");
7661 cx.assert_editor_state("editor.cloˇ");
7662 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7663 cx.update_editor(|editor, cx| {
7664 editor.show_completions(&ShowCompletions { trigger: None }, cx);
7665 });
7666 handle_completion_request(
7667 &mut cx,
7668 "editor.<clo|>",
7669 vec!["close", "clobber"],
7670 counter.clone(),
7671 )
7672 .await;
7673 cx.condition(|editor, _| editor.context_menu_visible())
7674 .await;
7675 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
7676
7677 let apply_additional_edits = cx.update_editor(|editor, cx| {
7678 editor
7679 .confirm_completion(&ConfirmCompletion::default(), cx)
7680 .unwrap()
7681 });
7682 cx.assert_editor_state("editor.closeˇ");
7683 handle_resolve_completion_request(&mut cx, None).await;
7684 apply_additional_edits.await.unwrap();
7685}
7686
7687#[gpui::test]
7688async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
7689 init_test(cx, |_| {});
7690 let mut cx = EditorLspTestContext::new_rust(
7691 lsp::ServerCapabilities {
7692 completion_provider: Some(lsp::CompletionOptions {
7693 trigger_characters: Some(vec![".".to_string()]),
7694 ..Default::default()
7695 }),
7696 ..Default::default()
7697 },
7698 cx,
7699 )
7700 .await;
7701 cx.lsp
7702 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
7703 Ok(Some(lsp::CompletionResponse::Array(vec![
7704 lsp::CompletionItem {
7705 label: "first".into(),
7706 ..Default::default()
7707 },
7708 lsp::CompletionItem {
7709 label: "last".into(),
7710 ..Default::default()
7711 },
7712 ])))
7713 });
7714 cx.set_state("variableˇ");
7715 cx.simulate_keystroke(".");
7716 cx.executor().run_until_parked();
7717
7718 cx.update_editor(|editor, _| {
7719 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7720 assert_eq!(
7721 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
7722 &["first", "last"]
7723 );
7724 } else {
7725 panic!("expected completion menu to be open");
7726 }
7727 });
7728
7729 cx.update_editor(|editor, cx| {
7730 editor.move_page_down(&MovePageDown::default(), cx);
7731 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7732 assert!(
7733 menu.selected_item == 1,
7734 "expected PageDown to select the last item from the context menu"
7735 );
7736 } else {
7737 panic!("expected completion menu to stay open after PageDown");
7738 }
7739 });
7740
7741 cx.update_editor(|editor, cx| {
7742 editor.move_page_up(&MovePageUp::default(), cx);
7743 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7744 assert!(
7745 menu.selected_item == 0,
7746 "expected PageUp to select the first item from the context menu"
7747 );
7748 } else {
7749 panic!("expected completion menu to stay open after PageUp");
7750 }
7751 });
7752}
7753
7754#[gpui::test]
7755async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
7756 init_test(cx, |_| {});
7757
7758 let mut cx = EditorLspTestContext::new_rust(
7759 lsp::ServerCapabilities {
7760 completion_provider: Some(lsp::CompletionOptions {
7761 trigger_characters: Some(vec![".".to_string()]),
7762 resolve_provider: Some(true),
7763 ..Default::default()
7764 }),
7765 ..Default::default()
7766 },
7767 cx,
7768 )
7769 .await;
7770
7771 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
7772 cx.simulate_keystroke(".");
7773 let completion_item = lsp::CompletionItem {
7774 label: "Some".into(),
7775 kind: Some(lsp::CompletionItemKind::SNIPPET),
7776 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
7777 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
7778 kind: lsp::MarkupKind::Markdown,
7779 value: "```rust\nSome(2)\n```".to_string(),
7780 })),
7781 deprecated: Some(false),
7782 sort_text: Some("Some".to_string()),
7783 filter_text: Some("Some".to_string()),
7784 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
7785 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7786 range: lsp::Range {
7787 start: lsp::Position {
7788 line: 0,
7789 character: 22,
7790 },
7791 end: lsp::Position {
7792 line: 0,
7793 character: 22,
7794 },
7795 },
7796 new_text: "Some(2)".to_string(),
7797 })),
7798 additional_text_edits: Some(vec![lsp::TextEdit {
7799 range: lsp::Range {
7800 start: lsp::Position {
7801 line: 0,
7802 character: 20,
7803 },
7804 end: lsp::Position {
7805 line: 0,
7806 character: 22,
7807 },
7808 },
7809 new_text: "".to_string(),
7810 }]),
7811 ..Default::default()
7812 };
7813
7814 let closure_completion_item = completion_item.clone();
7815 let counter = Arc::new(AtomicUsize::new(0));
7816 let counter_clone = counter.clone();
7817 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
7818 let task_completion_item = closure_completion_item.clone();
7819 counter_clone.fetch_add(1, atomic::Ordering::Release);
7820 async move {
7821 Ok(Some(lsp::CompletionResponse::Array(vec![
7822 task_completion_item,
7823 ])))
7824 }
7825 });
7826
7827 cx.condition(|editor, _| editor.context_menu_visible())
7828 .await;
7829 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
7830 assert!(request.next().await.is_some());
7831 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
7832
7833 cx.simulate_keystroke("S");
7834 cx.simulate_keystroke("o");
7835 cx.simulate_keystroke("m");
7836 cx.condition(|editor, _| editor.context_menu_visible())
7837 .await;
7838 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
7839 assert!(request.next().await.is_some());
7840 assert!(request.next().await.is_some());
7841 assert!(request.next().await.is_some());
7842 request.close();
7843 assert!(request.next().await.is_none());
7844 assert_eq!(
7845 counter.load(atomic::Ordering::Acquire),
7846 4,
7847 "With the completions menu open, only one LSP request should happen per input"
7848 );
7849}
7850
7851#[gpui::test]
7852async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
7853 init_test(cx, |_| {});
7854 let mut cx = EditorTestContext::new(cx).await;
7855 let language = Arc::new(Language::new(
7856 LanguageConfig {
7857 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
7858 ..Default::default()
7859 },
7860 Some(tree_sitter_rust::language()),
7861 ));
7862 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
7863
7864 // If multiple selections intersect a line, the line is only toggled once.
7865 cx.set_state(indoc! {"
7866 fn a() {
7867 «//b();
7868 ˇ»// «c();
7869 //ˇ» d();
7870 }
7871 "});
7872
7873 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7874
7875 cx.assert_editor_state(indoc! {"
7876 fn a() {
7877 «b();
7878 c();
7879 ˇ» d();
7880 }
7881 "});
7882
7883 // The comment prefix is inserted at the same column for every line in a
7884 // selection.
7885 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7886
7887 cx.assert_editor_state(indoc! {"
7888 fn a() {
7889 // «b();
7890 // c();
7891 ˇ»// d();
7892 }
7893 "});
7894
7895 // If a selection ends at the beginning of a line, that line is not toggled.
7896 cx.set_selections_state(indoc! {"
7897 fn a() {
7898 // b();
7899 «// c();
7900 ˇ» // d();
7901 }
7902 "});
7903
7904 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7905
7906 cx.assert_editor_state(indoc! {"
7907 fn a() {
7908 // b();
7909 «c();
7910 ˇ» // d();
7911 }
7912 "});
7913
7914 // If a selection span a single line and is empty, the line is toggled.
7915 cx.set_state(indoc! {"
7916 fn a() {
7917 a();
7918 b();
7919 ˇ
7920 }
7921 "});
7922
7923 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7924
7925 cx.assert_editor_state(indoc! {"
7926 fn a() {
7927 a();
7928 b();
7929 //•ˇ
7930 }
7931 "});
7932
7933 // If a selection span multiple lines, empty lines are not toggled.
7934 cx.set_state(indoc! {"
7935 fn a() {
7936 «a();
7937
7938 c();ˇ»
7939 }
7940 "});
7941
7942 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7943
7944 cx.assert_editor_state(indoc! {"
7945 fn a() {
7946 // «a();
7947
7948 // c();ˇ»
7949 }
7950 "});
7951
7952 // If a selection includes multiple comment prefixes, all lines are uncommented.
7953 cx.set_state(indoc! {"
7954 fn a() {
7955 «// a();
7956 /// b();
7957 //! c();ˇ»
7958 }
7959 "});
7960
7961 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7962
7963 cx.assert_editor_state(indoc! {"
7964 fn a() {
7965 «a();
7966 b();
7967 c();ˇ»
7968 }
7969 "});
7970}
7971
7972#[gpui::test]
7973async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
7974 init_test(cx, |_| {});
7975
7976 let language = Arc::new(Language::new(
7977 LanguageConfig {
7978 line_comments: vec!["// ".into()],
7979 ..Default::default()
7980 },
7981 Some(tree_sitter_rust::language()),
7982 ));
7983
7984 let mut cx = EditorTestContext::new(cx).await;
7985
7986 cx.language_registry().add(language.clone());
7987 cx.update_buffer(|buffer, cx| {
7988 buffer.set_language(Some(language), cx);
7989 });
7990
7991 let toggle_comments = &ToggleComments {
7992 advance_downwards: true,
7993 };
7994
7995 // Single cursor on one line -> advance
7996 // Cursor moves horizontally 3 characters as well on non-blank line
7997 cx.set_state(indoc!(
7998 "fn a() {
7999 ˇdog();
8000 cat();
8001 }"
8002 ));
8003 cx.update_editor(|editor, cx| {
8004 editor.toggle_comments(toggle_comments, cx);
8005 });
8006 cx.assert_editor_state(indoc!(
8007 "fn a() {
8008 // dog();
8009 catˇ();
8010 }"
8011 ));
8012
8013 // Single selection on one line -> don't advance
8014 cx.set_state(indoc!(
8015 "fn a() {
8016 «dog()ˇ»;
8017 cat();
8018 }"
8019 ));
8020 cx.update_editor(|editor, cx| {
8021 editor.toggle_comments(toggle_comments, cx);
8022 });
8023 cx.assert_editor_state(indoc!(
8024 "fn a() {
8025 // «dog()ˇ»;
8026 cat();
8027 }"
8028 ));
8029
8030 // Multiple cursors on one line -> advance
8031 cx.set_state(indoc!(
8032 "fn a() {
8033 ˇdˇog();
8034 cat();
8035 }"
8036 ));
8037 cx.update_editor(|editor, cx| {
8038 editor.toggle_comments(toggle_comments, cx);
8039 });
8040 cx.assert_editor_state(indoc!(
8041 "fn a() {
8042 // dog();
8043 catˇ(ˇ);
8044 }"
8045 ));
8046
8047 // Multiple cursors on one line, with selection -> don't advance
8048 cx.set_state(indoc!(
8049 "fn a() {
8050 ˇdˇog«()ˇ»;
8051 cat();
8052 }"
8053 ));
8054 cx.update_editor(|editor, cx| {
8055 editor.toggle_comments(toggle_comments, cx);
8056 });
8057 cx.assert_editor_state(indoc!(
8058 "fn a() {
8059 // ˇdˇog«()ˇ»;
8060 cat();
8061 }"
8062 ));
8063
8064 // Single cursor on one line -> advance
8065 // Cursor moves to column 0 on blank line
8066 cx.set_state(indoc!(
8067 "fn a() {
8068 ˇdog();
8069
8070 cat();
8071 }"
8072 ));
8073 cx.update_editor(|editor, cx| {
8074 editor.toggle_comments(toggle_comments, cx);
8075 });
8076 cx.assert_editor_state(indoc!(
8077 "fn a() {
8078 // dog();
8079 ˇ
8080 cat();
8081 }"
8082 ));
8083
8084 // Single cursor on one line -> advance
8085 // Cursor starts and ends at column 0
8086 cx.set_state(indoc!(
8087 "fn a() {
8088 ˇ dog();
8089 cat();
8090 }"
8091 ));
8092 cx.update_editor(|editor, cx| {
8093 editor.toggle_comments(toggle_comments, cx);
8094 });
8095 cx.assert_editor_state(indoc!(
8096 "fn a() {
8097 // dog();
8098 ˇ cat();
8099 }"
8100 ));
8101}
8102
8103#[gpui::test]
8104async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
8105 init_test(cx, |_| {});
8106
8107 let mut cx = EditorTestContext::new(cx).await;
8108
8109 let html_language = Arc::new(
8110 Language::new(
8111 LanguageConfig {
8112 name: "HTML".into(),
8113 block_comment: Some(("<!-- ".into(), " -->".into())),
8114 ..Default::default()
8115 },
8116 Some(tree_sitter_html::language()),
8117 )
8118 .with_injection_query(
8119 r#"
8120 (script_element
8121 (raw_text) @content
8122 (#set! "language" "javascript"))
8123 "#,
8124 )
8125 .unwrap(),
8126 );
8127
8128 let javascript_language = Arc::new(Language::new(
8129 LanguageConfig {
8130 name: "JavaScript".into(),
8131 line_comments: vec!["// ".into()],
8132 ..Default::default()
8133 },
8134 Some(tree_sitter_typescript::language_tsx()),
8135 ));
8136
8137 cx.language_registry().add(html_language.clone());
8138 cx.language_registry().add(javascript_language.clone());
8139 cx.update_buffer(|buffer, cx| {
8140 buffer.set_language(Some(html_language), cx);
8141 });
8142
8143 // Toggle comments for empty selections
8144 cx.set_state(
8145 &r#"
8146 <p>A</p>ˇ
8147 <p>B</p>ˇ
8148 <p>C</p>ˇ
8149 "#
8150 .unindent(),
8151 );
8152 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8153 cx.assert_editor_state(
8154 &r#"
8155 <!-- <p>A</p>ˇ -->
8156 <!-- <p>B</p>ˇ -->
8157 <!-- <p>C</p>ˇ -->
8158 "#
8159 .unindent(),
8160 );
8161 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8162 cx.assert_editor_state(
8163 &r#"
8164 <p>A</p>ˇ
8165 <p>B</p>ˇ
8166 <p>C</p>ˇ
8167 "#
8168 .unindent(),
8169 );
8170
8171 // Toggle comments for mixture of empty and non-empty selections, where
8172 // multiple selections occupy a given line.
8173 cx.set_state(
8174 &r#"
8175 <p>A«</p>
8176 <p>ˇ»B</p>ˇ
8177 <p>C«</p>
8178 <p>ˇ»D</p>ˇ
8179 "#
8180 .unindent(),
8181 );
8182
8183 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8184 cx.assert_editor_state(
8185 &r#"
8186 <!-- <p>A«</p>
8187 <p>ˇ»B</p>ˇ -->
8188 <!-- <p>C«</p>
8189 <p>ˇ»D</p>ˇ -->
8190 "#
8191 .unindent(),
8192 );
8193 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8194 cx.assert_editor_state(
8195 &r#"
8196 <p>A«</p>
8197 <p>ˇ»B</p>ˇ
8198 <p>C«</p>
8199 <p>ˇ»D</p>ˇ
8200 "#
8201 .unindent(),
8202 );
8203
8204 // Toggle comments when different languages are active for different
8205 // selections.
8206 cx.set_state(
8207 &r#"
8208 ˇ<script>
8209 ˇvar x = new Y();
8210 ˇ</script>
8211 "#
8212 .unindent(),
8213 );
8214 cx.executor().run_until_parked();
8215 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8216 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
8217 // Uncommenting and commenting from this position brings in even more wrong artifacts.
8218 cx.assert_editor_state(
8219 &r#"
8220 <!-- ˇ<script> -->
8221 // ˇvar x = new Y();
8222 // ˇ</script>
8223 "#
8224 .unindent(),
8225 );
8226}
8227
8228#[gpui::test]
8229fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
8230 init_test(cx, |_| {});
8231
8232 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8233 let multibuffer = cx.new_model(|cx| {
8234 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8235 multibuffer.push_excerpts(
8236 buffer.clone(),
8237 [
8238 ExcerptRange {
8239 context: Point::new(0, 0)..Point::new(0, 4),
8240 primary: None,
8241 },
8242 ExcerptRange {
8243 context: Point::new(1, 0)..Point::new(1, 4),
8244 primary: None,
8245 },
8246 ],
8247 cx,
8248 );
8249 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
8250 multibuffer
8251 });
8252
8253 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8254 _ = view.update(cx, |view, cx| {
8255 assert_eq!(view.text(cx), "aaaa\nbbbb");
8256 view.change_selections(None, cx, |s| {
8257 s.select_ranges([
8258 Point::new(0, 0)..Point::new(0, 0),
8259 Point::new(1, 0)..Point::new(1, 0),
8260 ])
8261 });
8262
8263 view.handle_input("X", cx);
8264 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
8265 assert_eq!(
8266 view.selections.ranges(cx),
8267 [
8268 Point::new(0, 1)..Point::new(0, 1),
8269 Point::new(1, 1)..Point::new(1, 1),
8270 ]
8271 );
8272
8273 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
8274 view.change_selections(None, cx, |s| {
8275 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
8276 });
8277 view.backspace(&Default::default(), cx);
8278 assert_eq!(view.text(cx), "Xa\nbbb");
8279 assert_eq!(
8280 view.selections.ranges(cx),
8281 [Point::new(1, 0)..Point::new(1, 0)]
8282 );
8283
8284 view.change_selections(None, cx, |s| {
8285 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
8286 });
8287 view.backspace(&Default::default(), cx);
8288 assert_eq!(view.text(cx), "X\nbb");
8289 assert_eq!(
8290 view.selections.ranges(cx),
8291 [Point::new(0, 1)..Point::new(0, 1)]
8292 );
8293 });
8294}
8295
8296#[gpui::test]
8297fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
8298 init_test(cx, |_| {});
8299
8300 let markers = vec![('[', ']').into(), ('(', ')').into()];
8301 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
8302 indoc! {"
8303 [aaaa
8304 (bbbb]
8305 cccc)",
8306 },
8307 markers.clone(),
8308 );
8309 let excerpt_ranges = markers.into_iter().map(|marker| {
8310 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
8311 ExcerptRange {
8312 context,
8313 primary: None,
8314 }
8315 });
8316 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
8317 let multibuffer = cx.new_model(|cx| {
8318 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8319 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
8320 multibuffer
8321 });
8322
8323 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8324 _ = view.update(cx, |view, cx| {
8325 let (expected_text, selection_ranges) = marked_text_ranges(
8326 indoc! {"
8327 aaaa
8328 bˇbbb
8329 bˇbbˇb
8330 cccc"
8331 },
8332 true,
8333 );
8334 assert_eq!(view.text(cx), expected_text);
8335 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
8336
8337 view.handle_input("X", cx);
8338
8339 let (expected_text, expected_selections) = marked_text_ranges(
8340 indoc! {"
8341 aaaa
8342 bXˇbbXb
8343 bXˇbbXˇb
8344 cccc"
8345 },
8346 false,
8347 );
8348 assert_eq!(view.text(cx), expected_text);
8349 assert_eq!(view.selections.ranges(cx), expected_selections);
8350
8351 view.newline(&Newline, cx);
8352 let (expected_text, expected_selections) = marked_text_ranges(
8353 indoc! {"
8354 aaaa
8355 bX
8356 ˇbbX
8357 b
8358 bX
8359 ˇbbX
8360 ˇb
8361 cccc"
8362 },
8363 false,
8364 );
8365 assert_eq!(view.text(cx), expected_text);
8366 assert_eq!(view.selections.ranges(cx), expected_selections);
8367 });
8368}
8369
8370#[gpui::test]
8371fn test_refresh_selections(cx: &mut TestAppContext) {
8372 init_test(cx, |_| {});
8373
8374 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8375 let mut excerpt1_id = None;
8376 let multibuffer = cx.new_model(|cx| {
8377 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8378 excerpt1_id = multibuffer
8379 .push_excerpts(
8380 buffer.clone(),
8381 [
8382 ExcerptRange {
8383 context: Point::new(0, 0)..Point::new(1, 4),
8384 primary: None,
8385 },
8386 ExcerptRange {
8387 context: Point::new(1, 0)..Point::new(2, 4),
8388 primary: None,
8389 },
8390 ],
8391 cx,
8392 )
8393 .into_iter()
8394 .next();
8395 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8396 multibuffer
8397 });
8398
8399 let editor = cx.add_window(|cx| {
8400 let mut editor = build_editor(multibuffer.clone(), cx);
8401 let snapshot = editor.snapshot(cx);
8402 editor.change_selections(None, cx, |s| {
8403 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
8404 });
8405 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
8406 assert_eq!(
8407 editor.selections.ranges(cx),
8408 [
8409 Point::new(1, 3)..Point::new(1, 3),
8410 Point::new(2, 1)..Point::new(2, 1),
8411 ]
8412 );
8413 editor
8414 });
8415
8416 // Refreshing selections is a no-op when excerpts haven't changed.
8417 _ = editor.update(cx, |editor, cx| {
8418 editor.change_selections(None, cx, |s| s.refresh());
8419 assert_eq!(
8420 editor.selections.ranges(cx),
8421 [
8422 Point::new(1, 3)..Point::new(1, 3),
8423 Point::new(2, 1)..Point::new(2, 1),
8424 ]
8425 );
8426 });
8427
8428 _ = multibuffer.update(cx, |multibuffer, cx| {
8429 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8430 });
8431 _ = editor.update(cx, |editor, cx| {
8432 // Removing an excerpt causes the first selection to become degenerate.
8433 assert_eq!(
8434 editor.selections.ranges(cx),
8435 [
8436 Point::new(0, 0)..Point::new(0, 0),
8437 Point::new(0, 1)..Point::new(0, 1)
8438 ]
8439 );
8440
8441 // Refreshing selections will relocate the first selection to the original buffer
8442 // location.
8443 editor.change_selections(None, cx, |s| s.refresh());
8444 assert_eq!(
8445 editor.selections.ranges(cx),
8446 [
8447 Point::new(0, 1)..Point::new(0, 1),
8448 Point::new(0, 3)..Point::new(0, 3)
8449 ]
8450 );
8451 assert!(editor.selections.pending_anchor().is_some());
8452 });
8453}
8454
8455#[gpui::test]
8456fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
8457 init_test(cx, |_| {});
8458
8459 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8460 let mut excerpt1_id = None;
8461 let multibuffer = cx.new_model(|cx| {
8462 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8463 excerpt1_id = multibuffer
8464 .push_excerpts(
8465 buffer.clone(),
8466 [
8467 ExcerptRange {
8468 context: Point::new(0, 0)..Point::new(1, 4),
8469 primary: None,
8470 },
8471 ExcerptRange {
8472 context: Point::new(1, 0)..Point::new(2, 4),
8473 primary: None,
8474 },
8475 ],
8476 cx,
8477 )
8478 .into_iter()
8479 .next();
8480 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8481 multibuffer
8482 });
8483
8484 let editor = cx.add_window(|cx| {
8485 let mut editor = build_editor(multibuffer.clone(), cx);
8486 let snapshot = editor.snapshot(cx);
8487 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
8488 assert_eq!(
8489 editor.selections.ranges(cx),
8490 [Point::new(1, 3)..Point::new(1, 3)]
8491 );
8492 editor
8493 });
8494
8495 _ = multibuffer.update(cx, |multibuffer, cx| {
8496 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8497 });
8498 _ = editor.update(cx, |editor, cx| {
8499 assert_eq!(
8500 editor.selections.ranges(cx),
8501 [Point::new(0, 0)..Point::new(0, 0)]
8502 );
8503
8504 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
8505 editor.change_selections(None, cx, |s| s.refresh());
8506 assert_eq!(
8507 editor.selections.ranges(cx),
8508 [Point::new(0, 3)..Point::new(0, 3)]
8509 );
8510 assert!(editor.selections.pending_anchor().is_some());
8511 });
8512}
8513
8514#[gpui::test]
8515async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
8516 init_test(cx, |_| {});
8517
8518 let language = Arc::new(
8519 Language::new(
8520 LanguageConfig {
8521 brackets: BracketPairConfig {
8522 pairs: vec![
8523 BracketPair {
8524 start: "{".to_string(),
8525 end: "}".to_string(),
8526 close: true,
8527 surround: true,
8528 newline: true,
8529 },
8530 BracketPair {
8531 start: "/* ".to_string(),
8532 end: " */".to_string(),
8533 close: true,
8534 surround: true,
8535 newline: true,
8536 },
8537 ],
8538 ..Default::default()
8539 },
8540 ..Default::default()
8541 },
8542 Some(tree_sitter_rust::language()),
8543 )
8544 .with_indents_query("")
8545 .unwrap(),
8546 );
8547
8548 let text = concat!(
8549 "{ }\n", //
8550 " x\n", //
8551 " /* */\n", //
8552 "x\n", //
8553 "{{} }\n", //
8554 );
8555
8556 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
8557 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
8558 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
8559 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
8560 .await;
8561
8562 _ = view.update(cx, |view, cx| {
8563 view.change_selections(None, cx, |s| {
8564 s.select_display_ranges([
8565 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
8566 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
8567 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
8568 ])
8569 });
8570 view.newline(&Newline, cx);
8571
8572 assert_eq!(
8573 view.buffer().read(cx).read(cx).text(),
8574 concat!(
8575 "{ \n", // Suppress rustfmt
8576 "\n", //
8577 "}\n", //
8578 " x\n", //
8579 " /* \n", //
8580 " \n", //
8581 " */\n", //
8582 "x\n", //
8583 "{{} \n", //
8584 "}\n", //
8585 )
8586 );
8587 });
8588}
8589
8590#[gpui::test]
8591fn test_highlighted_ranges(cx: &mut TestAppContext) {
8592 init_test(cx, |_| {});
8593
8594 let editor = cx.add_window(|cx| {
8595 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
8596 build_editor(buffer.clone(), cx)
8597 });
8598
8599 _ = editor.update(cx, |editor, cx| {
8600 struct Type1;
8601 struct Type2;
8602
8603 let buffer = editor.buffer.read(cx).snapshot(cx);
8604
8605 let anchor_range =
8606 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
8607
8608 editor.highlight_background::<Type1>(
8609 &[
8610 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
8611 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
8612 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
8613 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
8614 ],
8615 |_| Hsla::red(),
8616 cx,
8617 );
8618 editor.highlight_background::<Type2>(
8619 &[
8620 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
8621 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
8622 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
8623 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
8624 ],
8625 |_| Hsla::green(),
8626 cx,
8627 );
8628
8629 let snapshot = editor.snapshot(cx);
8630 let mut highlighted_ranges = editor.background_highlights_in_range(
8631 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
8632 &snapshot,
8633 cx.theme().colors(),
8634 );
8635 // Enforce a consistent ordering based on color without relying on the ordering of the
8636 // highlight's `TypeId` which is non-executor.
8637 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
8638 assert_eq!(
8639 highlighted_ranges,
8640 &[
8641 (
8642 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
8643 Hsla::red(),
8644 ),
8645 (
8646 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
8647 Hsla::red(),
8648 ),
8649 (
8650 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
8651 Hsla::green(),
8652 ),
8653 (
8654 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
8655 Hsla::green(),
8656 ),
8657 ]
8658 );
8659 assert_eq!(
8660 editor.background_highlights_in_range(
8661 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
8662 &snapshot,
8663 cx.theme().colors(),
8664 ),
8665 &[(
8666 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
8667 Hsla::red(),
8668 )]
8669 );
8670 });
8671}
8672
8673#[gpui::test]
8674async fn test_following(cx: &mut gpui::TestAppContext) {
8675 init_test(cx, |_| {});
8676
8677 let fs = FakeFs::new(cx.executor());
8678 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8679
8680 let buffer = project.update(cx, |project, cx| {
8681 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
8682 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
8683 });
8684 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
8685 let follower = cx.update(|cx| {
8686 cx.open_window(
8687 WindowOptions {
8688 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
8689 gpui::Point::new(px(0.), px(0.)),
8690 gpui::Point::new(px(10.), px(80.)),
8691 ))),
8692 ..Default::default()
8693 },
8694 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
8695 )
8696 .unwrap()
8697 });
8698
8699 let is_still_following = Rc::new(RefCell::new(true));
8700 let follower_edit_event_count = Rc::new(RefCell::new(0));
8701 let pending_update = Rc::new(RefCell::new(None));
8702 _ = follower.update(cx, {
8703 let update = pending_update.clone();
8704 let is_still_following = is_still_following.clone();
8705 let follower_edit_event_count = follower_edit_event_count.clone();
8706 |_, cx| {
8707 cx.subscribe(
8708 &leader.root_view(cx).unwrap(),
8709 move |_, leader, event, cx| {
8710 leader
8711 .read(cx)
8712 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
8713 },
8714 )
8715 .detach();
8716
8717 cx.subscribe(
8718 &follower.root_view(cx).unwrap(),
8719 move |_, _, event: &EditorEvent, _cx| {
8720 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
8721 *is_still_following.borrow_mut() = false;
8722 }
8723
8724 if let EditorEvent::BufferEdited = event {
8725 *follower_edit_event_count.borrow_mut() += 1;
8726 }
8727 },
8728 )
8729 .detach();
8730 }
8731 });
8732
8733 // Update the selections only
8734 _ = leader.update(cx, |leader, cx| {
8735 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
8736 });
8737 follower
8738 .update(cx, |follower, cx| {
8739 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8740 })
8741 .unwrap()
8742 .await
8743 .unwrap();
8744 _ = follower.update(cx, |follower, cx| {
8745 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
8746 });
8747 assert_eq!(*is_still_following.borrow(), true);
8748 assert_eq!(*follower_edit_event_count.borrow(), 0);
8749
8750 // Update the scroll position only
8751 _ = leader.update(cx, |leader, cx| {
8752 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
8753 });
8754 follower
8755 .update(cx, |follower, cx| {
8756 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8757 })
8758 .unwrap()
8759 .await
8760 .unwrap();
8761 assert_eq!(
8762 follower
8763 .update(cx, |follower, cx| follower.scroll_position(cx))
8764 .unwrap(),
8765 gpui::Point::new(1.5, 3.5)
8766 );
8767 assert_eq!(*is_still_following.borrow(), true);
8768 assert_eq!(*follower_edit_event_count.borrow(), 0);
8769
8770 // Update the selections and scroll position. The follower's scroll position is updated
8771 // via autoscroll, not via the leader's exact scroll position.
8772 _ = leader.update(cx, |leader, cx| {
8773 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
8774 leader.request_autoscroll(Autoscroll::newest(), cx);
8775 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
8776 });
8777 follower
8778 .update(cx, |follower, cx| {
8779 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8780 })
8781 .unwrap()
8782 .await
8783 .unwrap();
8784 _ = follower.update(cx, |follower, cx| {
8785 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
8786 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
8787 });
8788 assert_eq!(*is_still_following.borrow(), true);
8789
8790 // Creating a pending selection that precedes another selection
8791 _ = leader.update(cx, |leader, cx| {
8792 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
8793 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
8794 });
8795 follower
8796 .update(cx, |follower, cx| {
8797 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8798 })
8799 .unwrap()
8800 .await
8801 .unwrap();
8802 _ = follower.update(cx, |follower, cx| {
8803 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
8804 });
8805 assert_eq!(*is_still_following.borrow(), true);
8806
8807 // Extend the pending selection so that it surrounds another selection
8808 _ = leader.update(cx, |leader, cx| {
8809 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
8810 });
8811 follower
8812 .update(cx, |follower, cx| {
8813 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8814 })
8815 .unwrap()
8816 .await
8817 .unwrap();
8818 _ = follower.update(cx, |follower, cx| {
8819 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
8820 });
8821
8822 // Scrolling locally breaks the follow
8823 _ = follower.update(cx, |follower, cx| {
8824 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
8825 follower.set_scroll_anchor(
8826 ScrollAnchor {
8827 anchor: top_anchor,
8828 offset: gpui::Point::new(0.0, 0.5),
8829 },
8830 cx,
8831 );
8832 });
8833 assert_eq!(*is_still_following.borrow(), false);
8834}
8835
8836#[gpui::test]
8837async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
8838 init_test(cx, |_| {});
8839
8840 let fs = FakeFs::new(cx.executor());
8841 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8842 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8843 let pane = workspace
8844 .update(cx, |workspace, _| workspace.active_pane().clone())
8845 .unwrap();
8846
8847 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
8848
8849 let leader = pane.update(cx, |_, cx| {
8850 let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
8851 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
8852 });
8853
8854 // Start following the editor when it has no excerpts.
8855 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
8856 let follower_1 = cx
8857 .update_window(*workspace.deref(), |_, cx| {
8858 Editor::from_state_proto(
8859 workspace.root_view(cx).unwrap(),
8860 ViewId {
8861 creator: Default::default(),
8862 id: 0,
8863 },
8864 &mut state_message,
8865 cx,
8866 )
8867 })
8868 .unwrap()
8869 .unwrap()
8870 .await
8871 .unwrap();
8872
8873 let update_message = Rc::new(RefCell::new(None));
8874 follower_1.update(cx, {
8875 let update = update_message.clone();
8876 |_, cx| {
8877 cx.subscribe(&leader, move |_, leader, event, cx| {
8878 leader
8879 .read(cx)
8880 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
8881 })
8882 .detach();
8883 }
8884 });
8885
8886 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
8887 (
8888 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
8889 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
8890 )
8891 });
8892
8893 // Insert some excerpts.
8894 _ = leader.update(cx, |leader, cx| {
8895 leader.buffer.update(cx, |multibuffer, cx| {
8896 let excerpt_ids = multibuffer.push_excerpts(
8897 buffer_1.clone(),
8898 [
8899 ExcerptRange {
8900 context: 1..6,
8901 primary: None,
8902 },
8903 ExcerptRange {
8904 context: 12..15,
8905 primary: None,
8906 },
8907 ExcerptRange {
8908 context: 0..3,
8909 primary: None,
8910 },
8911 ],
8912 cx,
8913 );
8914 multibuffer.insert_excerpts_after(
8915 excerpt_ids[0],
8916 buffer_2.clone(),
8917 [
8918 ExcerptRange {
8919 context: 8..12,
8920 primary: None,
8921 },
8922 ExcerptRange {
8923 context: 0..6,
8924 primary: None,
8925 },
8926 ],
8927 cx,
8928 );
8929 });
8930 });
8931
8932 // Apply the update of adding the excerpts.
8933 follower_1
8934 .update(cx, |follower, cx| {
8935 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
8936 })
8937 .await
8938 .unwrap();
8939 assert_eq!(
8940 follower_1.update(cx, |editor, cx| editor.text(cx)),
8941 leader.update(cx, |editor, cx| editor.text(cx))
8942 );
8943 update_message.borrow_mut().take();
8944
8945 // Start following separately after it already has excerpts.
8946 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
8947 let follower_2 = cx
8948 .update_window(*workspace.deref(), |_, cx| {
8949 Editor::from_state_proto(
8950 workspace.root_view(cx).unwrap().clone(),
8951 ViewId {
8952 creator: Default::default(),
8953 id: 0,
8954 },
8955 &mut state_message,
8956 cx,
8957 )
8958 })
8959 .unwrap()
8960 .unwrap()
8961 .await
8962 .unwrap();
8963 assert_eq!(
8964 follower_2.update(cx, |editor, cx| editor.text(cx)),
8965 leader.update(cx, |editor, cx| editor.text(cx))
8966 );
8967
8968 // Remove some excerpts.
8969 _ = leader.update(cx, |leader, cx| {
8970 leader.buffer.update(cx, |multibuffer, cx| {
8971 let excerpt_ids = multibuffer.excerpt_ids();
8972 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
8973 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
8974 });
8975 });
8976
8977 // Apply the update of removing the excerpts.
8978 follower_1
8979 .update(cx, |follower, cx| {
8980 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
8981 })
8982 .await
8983 .unwrap();
8984 follower_2
8985 .update(cx, |follower, cx| {
8986 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
8987 })
8988 .await
8989 .unwrap();
8990 update_message.borrow_mut().take();
8991 assert_eq!(
8992 follower_1.update(cx, |editor, cx| editor.text(cx)),
8993 leader.update(cx, |editor, cx| editor.text(cx))
8994 );
8995}
8996
8997#[gpui::test]
8998async fn go_to_prev_overlapping_diagnostic(
8999 executor: BackgroundExecutor,
9000 cx: &mut gpui::TestAppContext,
9001) {
9002 init_test(cx, |_| {});
9003
9004 let mut cx = EditorTestContext::new(cx).await;
9005 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9006
9007 cx.set_state(indoc! {"
9008 ˇfn func(abc def: i32) -> u32 {
9009 }
9010 "});
9011
9012 _ = cx.update(|cx| {
9013 _ = project.update(cx, |project, cx| {
9014 project
9015 .update_diagnostics(
9016 LanguageServerId(0),
9017 lsp::PublishDiagnosticsParams {
9018 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9019 version: None,
9020 diagnostics: vec![
9021 lsp::Diagnostic {
9022 range: lsp::Range::new(
9023 lsp::Position::new(0, 11),
9024 lsp::Position::new(0, 12),
9025 ),
9026 severity: Some(lsp::DiagnosticSeverity::ERROR),
9027 ..Default::default()
9028 },
9029 lsp::Diagnostic {
9030 range: lsp::Range::new(
9031 lsp::Position::new(0, 12),
9032 lsp::Position::new(0, 15),
9033 ),
9034 severity: Some(lsp::DiagnosticSeverity::ERROR),
9035 ..Default::default()
9036 },
9037 lsp::Diagnostic {
9038 range: lsp::Range::new(
9039 lsp::Position::new(0, 25),
9040 lsp::Position::new(0, 28),
9041 ),
9042 severity: Some(lsp::DiagnosticSeverity::ERROR),
9043 ..Default::default()
9044 },
9045 ],
9046 },
9047 &[],
9048 cx,
9049 )
9050 .unwrap()
9051 });
9052 });
9053
9054 executor.run_until_parked();
9055
9056 cx.update_editor(|editor, cx| {
9057 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9058 });
9059
9060 cx.assert_editor_state(indoc! {"
9061 fn func(abc def: i32) -> ˇu32 {
9062 }
9063 "});
9064
9065 cx.update_editor(|editor, cx| {
9066 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9067 });
9068
9069 cx.assert_editor_state(indoc! {"
9070 fn func(abc ˇdef: i32) -> u32 {
9071 }
9072 "});
9073
9074 cx.update_editor(|editor, cx| {
9075 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9076 });
9077
9078 cx.assert_editor_state(indoc! {"
9079 fn func(abcˇ def: i32) -> u32 {
9080 }
9081 "});
9082
9083 cx.update_editor(|editor, cx| {
9084 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9085 });
9086
9087 cx.assert_editor_state(indoc! {"
9088 fn func(abc def: i32) -> ˇu32 {
9089 }
9090 "});
9091}
9092
9093#[gpui::test]
9094async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
9095 init_test(cx, |_| {});
9096
9097 let mut cx = EditorTestContext::new(cx).await;
9098
9099 cx.set_state(indoc! {"
9100 fn func(abˇc def: i32) -> u32 {
9101 }
9102 "});
9103 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9104
9105 cx.update(|cx| {
9106 project.update(cx, |project, cx| {
9107 project.update_diagnostics(
9108 LanguageServerId(0),
9109 lsp::PublishDiagnosticsParams {
9110 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9111 version: None,
9112 diagnostics: vec![lsp::Diagnostic {
9113 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
9114 severity: Some(lsp::DiagnosticSeverity::ERROR),
9115 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
9116 ..Default::default()
9117 }],
9118 },
9119 &[],
9120 cx,
9121 )
9122 })
9123 }).unwrap();
9124 cx.run_until_parked();
9125 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
9126 cx.run_until_parked();
9127 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
9128}
9129
9130#[gpui::test]
9131async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9132 init_test(cx, |_| {});
9133
9134 let mut cx = EditorTestContext::new(cx).await;
9135
9136 let diff_base = r#"
9137 use some::mod;
9138
9139 const A: u32 = 42;
9140
9141 fn main() {
9142 println!("hello");
9143
9144 println!("world");
9145 }
9146 "#
9147 .unindent();
9148
9149 // Edits are modified, removed, modified, added
9150 cx.set_state(
9151 &r#"
9152 use some::modified;
9153
9154 ˇ
9155 fn main() {
9156 println!("hello there");
9157
9158 println!("around the");
9159 println!("world");
9160 }
9161 "#
9162 .unindent(),
9163 );
9164
9165 cx.set_diff_base(Some(&diff_base));
9166 executor.run_until_parked();
9167
9168 cx.update_editor(|editor, cx| {
9169 //Wrap around the bottom of the buffer
9170 for _ in 0..3 {
9171 editor.go_to_hunk(&GoToHunk, cx);
9172 }
9173 });
9174
9175 cx.assert_editor_state(
9176 &r#"
9177 ˇuse some::modified;
9178
9179
9180 fn main() {
9181 println!("hello there");
9182
9183 println!("around the");
9184 println!("world");
9185 }
9186 "#
9187 .unindent(),
9188 );
9189
9190 cx.update_editor(|editor, cx| {
9191 //Wrap around the top of the buffer
9192 for _ in 0..2 {
9193 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9194 }
9195 });
9196
9197 cx.assert_editor_state(
9198 &r#"
9199 use some::modified;
9200
9201
9202 fn main() {
9203 ˇ println!("hello there");
9204
9205 println!("around the");
9206 println!("world");
9207 }
9208 "#
9209 .unindent(),
9210 );
9211
9212 cx.update_editor(|editor, cx| {
9213 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9214 });
9215
9216 cx.assert_editor_state(
9217 &r#"
9218 use some::modified;
9219
9220 ˇ
9221 fn main() {
9222 println!("hello there");
9223
9224 println!("around the");
9225 println!("world");
9226 }
9227 "#
9228 .unindent(),
9229 );
9230
9231 cx.update_editor(|editor, cx| {
9232 for _ in 0..3 {
9233 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9234 }
9235 });
9236
9237 cx.assert_editor_state(
9238 &r#"
9239 use some::modified;
9240
9241
9242 fn main() {
9243 ˇ println!("hello there");
9244
9245 println!("around the");
9246 println!("world");
9247 }
9248 "#
9249 .unindent(),
9250 );
9251
9252 cx.update_editor(|editor, cx| {
9253 editor.fold(&Fold, cx);
9254
9255 //Make sure that the fold only gets one hunk
9256 for _ in 0..4 {
9257 editor.go_to_hunk(&GoToHunk, cx);
9258 }
9259 });
9260
9261 cx.assert_editor_state(
9262 &r#"
9263 ˇuse some::modified;
9264
9265
9266 fn main() {
9267 println!("hello there");
9268
9269 println!("around the");
9270 println!("world");
9271 }
9272 "#
9273 .unindent(),
9274 );
9275}
9276
9277#[test]
9278fn test_split_words() {
9279 fn split(text: &str) -> Vec<&str> {
9280 split_words(text).collect()
9281 }
9282
9283 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
9284 assert_eq!(split("hello_world"), &["hello_", "world"]);
9285 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
9286 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
9287 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
9288 assert_eq!(split("helloworld"), &["helloworld"]);
9289
9290 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
9291}
9292
9293#[gpui::test]
9294async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
9295 init_test(cx, |_| {});
9296
9297 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
9298 let mut assert = |before, after| {
9299 let _state_context = cx.set_state(before);
9300 cx.update_editor(|editor, cx| {
9301 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
9302 });
9303 cx.assert_editor_state(after);
9304 };
9305
9306 // Outside bracket jumps to outside of matching bracket
9307 assert("console.logˇ(var);", "console.log(var)ˇ;");
9308 assert("console.log(var)ˇ;", "console.logˇ(var);");
9309
9310 // Inside bracket jumps to inside of matching bracket
9311 assert("console.log(ˇvar);", "console.log(varˇ);");
9312 assert("console.log(varˇ);", "console.log(ˇvar);");
9313
9314 // When outside a bracket and inside, favor jumping to the inside bracket
9315 assert(
9316 "console.log('foo', [1, 2, 3]ˇ);",
9317 "console.log(ˇ'foo', [1, 2, 3]);",
9318 );
9319 assert(
9320 "console.log(ˇ'foo', [1, 2, 3]);",
9321 "console.log('foo', [1, 2, 3]ˇ);",
9322 );
9323
9324 // Bias forward if two options are equally likely
9325 assert(
9326 "let result = curried_fun()ˇ();",
9327 "let result = curried_fun()()ˇ;",
9328 );
9329
9330 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
9331 assert(
9332 indoc! {"
9333 function test() {
9334 console.log('test')ˇ
9335 }"},
9336 indoc! {"
9337 function test() {
9338 console.logˇ('test')
9339 }"},
9340 );
9341}
9342
9343#[gpui::test]
9344async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
9345 init_test(cx, |_| {});
9346
9347 let fs = FakeFs::new(cx.executor());
9348 fs.insert_tree(
9349 "/a",
9350 json!({
9351 "main.rs": "fn main() { let a = 5; }",
9352 "other.rs": "// Test file",
9353 }),
9354 )
9355 .await;
9356 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9357
9358 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9359 language_registry.add(Arc::new(Language::new(
9360 LanguageConfig {
9361 name: "Rust".into(),
9362 matcher: LanguageMatcher {
9363 path_suffixes: vec!["rs".to_string()],
9364 ..Default::default()
9365 },
9366 brackets: BracketPairConfig {
9367 pairs: vec![BracketPair {
9368 start: "{".to_string(),
9369 end: "}".to_string(),
9370 close: true,
9371 surround: true,
9372 newline: true,
9373 }],
9374 disabled_scopes_by_bracket_ix: Vec::new(),
9375 },
9376 ..Default::default()
9377 },
9378 Some(tree_sitter_rust::language()),
9379 )));
9380 let mut fake_servers = language_registry.register_fake_lsp_adapter(
9381 "Rust",
9382 FakeLspAdapter {
9383 capabilities: lsp::ServerCapabilities {
9384 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
9385 first_trigger_character: "{".to_string(),
9386 more_trigger_character: None,
9387 }),
9388 ..Default::default()
9389 },
9390 ..Default::default()
9391 },
9392 );
9393
9394 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9395
9396 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9397
9398 let worktree_id = workspace
9399 .update(cx, |workspace, cx| {
9400 workspace.project().update(cx, |project, cx| {
9401 project.worktrees(cx).next().unwrap().read(cx).id()
9402 })
9403 })
9404 .unwrap();
9405
9406 let buffer = project
9407 .update(cx, |project, cx| {
9408 project.open_local_buffer("/a/main.rs", cx)
9409 })
9410 .await
9411 .unwrap();
9412 cx.executor().run_until_parked();
9413 cx.executor().start_waiting();
9414 let fake_server = fake_servers.next().await.unwrap();
9415 let editor_handle = workspace
9416 .update(cx, |workspace, cx| {
9417 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
9418 })
9419 .unwrap()
9420 .await
9421 .unwrap()
9422 .downcast::<Editor>()
9423 .unwrap();
9424
9425 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
9426 assert_eq!(
9427 params.text_document_position.text_document.uri,
9428 lsp::Url::from_file_path("/a/main.rs").unwrap(),
9429 );
9430 assert_eq!(
9431 params.text_document_position.position,
9432 lsp::Position::new(0, 21),
9433 );
9434
9435 Ok(Some(vec![lsp::TextEdit {
9436 new_text: "]".to_string(),
9437 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
9438 }]))
9439 });
9440
9441 editor_handle.update(cx, |editor, cx| {
9442 editor.focus(cx);
9443 editor.change_selections(None, cx, |s| {
9444 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
9445 });
9446 editor.handle_input("{", cx);
9447 });
9448
9449 cx.executor().run_until_parked();
9450
9451 _ = buffer.update(cx, |buffer, _| {
9452 assert_eq!(
9453 buffer.text(),
9454 "fn main() { let a = {5}; }",
9455 "No extra braces from on type formatting should appear in the buffer"
9456 )
9457 });
9458}
9459
9460#[gpui::test]
9461async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
9462 init_test(cx, |_| {});
9463
9464 let fs = FakeFs::new(cx.executor());
9465 fs.insert_tree(
9466 "/a",
9467 json!({
9468 "main.rs": "fn main() { let a = 5; }",
9469 "other.rs": "// Test file",
9470 }),
9471 )
9472 .await;
9473
9474 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9475
9476 let server_restarts = Arc::new(AtomicUsize::new(0));
9477 let closure_restarts = Arc::clone(&server_restarts);
9478 let language_server_name = "test language server";
9479 let language_name: Arc<str> = "Rust".into();
9480
9481 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9482 language_registry.add(Arc::new(Language::new(
9483 LanguageConfig {
9484 name: Arc::clone(&language_name),
9485 matcher: LanguageMatcher {
9486 path_suffixes: vec!["rs".to_string()],
9487 ..Default::default()
9488 },
9489 ..Default::default()
9490 },
9491 Some(tree_sitter_rust::language()),
9492 )));
9493 let mut fake_servers = language_registry.register_fake_lsp_adapter(
9494 "Rust",
9495 FakeLspAdapter {
9496 name: language_server_name,
9497 initialization_options: Some(json!({
9498 "testOptionValue": true
9499 })),
9500 initializer: Some(Box::new(move |fake_server| {
9501 let task_restarts = Arc::clone(&closure_restarts);
9502 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
9503 task_restarts.fetch_add(1, atomic::Ordering::Release);
9504 futures::future::ready(Ok(()))
9505 });
9506 })),
9507 ..Default::default()
9508 },
9509 );
9510
9511 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9512 let _buffer = project
9513 .update(cx, |project, cx| {
9514 project.open_local_buffer("/a/main.rs", cx)
9515 })
9516 .await
9517 .unwrap();
9518 let _fake_server = fake_servers.next().await.unwrap();
9519 update_test_language_settings(cx, |language_settings| {
9520 language_settings.languages.insert(
9521 Arc::clone(&language_name),
9522 LanguageSettingsContent {
9523 tab_size: NonZeroU32::new(8),
9524 ..Default::default()
9525 },
9526 );
9527 });
9528 cx.executor().run_until_parked();
9529 assert_eq!(
9530 server_restarts.load(atomic::Ordering::Acquire),
9531 0,
9532 "Should not restart LSP server on an unrelated change"
9533 );
9534
9535 update_test_project_settings(cx, |project_settings| {
9536 project_settings.lsp.insert(
9537 "Some other server name".into(),
9538 LspSettings {
9539 binary: None,
9540 settings: None,
9541 initialization_options: Some(json!({
9542 "some other init value": false
9543 })),
9544 },
9545 );
9546 });
9547 cx.executor().run_until_parked();
9548 assert_eq!(
9549 server_restarts.load(atomic::Ordering::Acquire),
9550 0,
9551 "Should not restart LSP server on an unrelated LSP settings change"
9552 );
9553
9554 update_test_project_settings(cx, |project_settings| {
9555 project_settings.lsp.insert(
9556 language_server_name.into(),
9557 LspSettings {
9558 binary: None,
9559 settings: None,
9560 initialization_options: Some(json!({
9561 "anotherInitValue": false
9562 })),
9563 },
9564 );
9565 });
9566 cx.executor().run_until_parked();
9567 assert_eq!(
9568 server_restarts.load(atomic::Ordering::Acquire),
9569 1,
9570 "Should restart LSP server on a related LSP settings change"
9571 );
9572
9573 update_test_project_settings(cx, |project_settings| {
9574 project_settings.lsp.insert(
9575 language_server_name.into(),
9576 LspSettings {
9577 binary: None,
9578 settings: None,
9579 initialization_options: Some(json!({
9580 "anotherInitValue": false
9581 })),
9582 },
9583 );
9584 });
9585 cx.executor().run_until_parked();
9586 assert_eq!(
9587 server_restarts.load(atomic::Ordering::Acquire),
9588 1,
9589 "Should not restart LSP server on a related LSP settings change that is the same"
9590 );
9591
9592 update_test_project_settings(cx, |project_settings| {
9593 project_settings.lsp.insert(
9594 language_server_name.into(),
9595 LspSettings {
9596 binary: None,
9597 settings: None,
9598 initialization_options: None,
9599 },
9600 );
9601 });
9602 cx.executor().run_until_parked();
9603 assert_eq!(
9604 server_restarts.load(atomic::Ordering::Acquire),
9605 2,
9606 "Should restart LSP server on another related LSP settings change"
9607 );
9608}
9609
9610#[gpui::test]
9611async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
9612 init_test(cx, |_| {});
9613
9614 let mut cx = EditorLspTestContext::new_rust(
9615 lsp::ServerCapabilities {
9616 completion_provider: Some(lsp::CompletionOptions {
9617 trigger_characters: Some(vec![".".to_string()]),
9618 resolve_provider: Some(true),
9619 ..Default::default()
9620 }),
9621 ..Default::default()
9622 },
9623 cx,
9624 )
9625 .await;
9626
9627 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9628 cx.simulate_keystroke(".");
9629 let completion_item = lsp::CompletionItem {
9630 label: "some".into(),
9631 kind: Some(lsp::CompletionItemKind::SNIPPET),
9632 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9633 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9634 kind: lsp::MarkupKind::Markdown,
9635 value: "```rust\nSome(2)\n```".to_string(),
9636 })),
9637 deprecated: Some(false),
9638 sort_text: Some("fffffff2".to_string()),
9639 filter_text: Some("some".to_string()),
9640 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9641 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9642 range: lsp::Range {
9643 start: lsp::Position {
9644 line: 0,
9645 character: 22,
9646 },
9647 end: lsp::Position {
9648 line: 0,
9649 character: 22,
9650 },
9651 },
9652 new_text: "Some(2)".to_string(),
9653 })),
9654 additional_text_edits: Some(vec![lsp::TextEdit {
9655 range: lsp::Range {
9656 start: lsp::Position {
9657 line: 0,
9658 character: 20,
9659 },
9660 end: lsp::Position {
9661 line: 0,
9662 character: 22,
9663 },
9664 },
9665 new_text: "".to_string(),
9666 }]),
9667 ..Default::default()
9668 };
9669
9670 let closure_completion_item = completion_item.clone();
9671 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9672 let task_completion_item = closure_completion_item.clone();
9673 async move {
9674 Ok(Some(lsp::CompletionResponse::Array(vec![
9675 task_completion_item,
9676 ])))
9677 }
9678 });
9679
9680 request.next().await;
9681
9682 cx.condition(|editor, _| editor.context_menu_visible())
9683 .await;
9684 let apply_additional_edits = cx.update_editor(|editor, cx| {
9685 editor
9686 .confirm_completion(&ConfirmCompletion::default(), cx)
9687 .unwrap()
9688 });
9689 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
9690
9691 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
9692 let task_completion_item = completion_item.clone();
9693 async move { Ok(task_completion_item) }
9694 })
9695 .next()
9696 .await
9697 .unwrap();
9698 apply_additional_edits.await.unwrap();
9699 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
9700}
9701
9702#[gpui::test]
9703async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
9704 init_test(cx, |_| {});
9705
9706 let mut cx = EditorLspTestContext::new(
9707 Language::new(
9708 LanguageConfig {
9709 matcher: LanguageMatcher {
9710 path_suffixes: vec!["jsx".into()],
9711 ..Default::default()
9712 },
9713 overrides: [(
9714 "element".into(),
9715 LanguageConfigOverride {
9716 word_characters: Override::Set(['-'].into_iter().collect()),
9717 ..Default::default()
9718 },
9719 )]
9720 .into_iter()
9721 .collect(),
9722 ..Default::default()
9723 },
9724 Some(tree_sitter_typescript::language_tsx()),
9725 )
9726 .with_override_query("(jsx_self_closing_element) @element")
9727 .unwrap(),
9728 lsp::ServerCapabilities {
9729 completion_provider: Some(lsp::CompletionOptions {
9730 trigger_characters: Some(vec![":".to_string()]),
9731 ..Default::default()
9732 }),
9733 ..Default::default()
9734 },
9735 cx,
9736 )
9737 .await;
9738
9739 cx.lsp
9740 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9741 Ok(Some(lsp::CompletionResponse::Array(vec![
9742 lsp::CompletionItem {
9743 label: "bg-blue".into(),
9744 ..Default::default()
9745 },
9746 lsp::CompletionItem {
9747 label: "bg-red".into(),
9748 ..Default::default()
9749 },
9750 lsp::CompletionItem {
9751 label: "bg-yellow".into(),
9752 ..Default::default()
9753 },
9754 ])))
9755 });
9756
9757 cx.set_state(r#"<p class="bgˇ" />"#);
9758
9759 // Trigger completion when typing a dash, because the dash is an extra
9760 // word character in the 'element' scope, which contains the cursor.
9761 cx.simulate_keystroke("-");
9762 cx.executor().run_until_parked();
9763 cx.update_editor(|editor, _| {
9764 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9765 assert_eq!(
9766 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9767 &["bg-red", "bg-blue", "bg-yellow"]
9768 );
9769 } else {
9770 panic!("expected completion menu to be open");
9771 }
9772 });
9773
9774 cx.simulate_keystroke("l");
9775 cx.executor().run_until_parked();
9776 cx.update_editor(|editor, _| {
9777 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9778 assert_eq!(
9779 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9780 &["bg-blue", "bg-yellow"]
9781 );
9782 } else {
9783 panic!("expected completion menu to be open");
9784 }
9785 });
9786
9787 // When filtering completions, consider the character after the '-' to
9788 // be the start of a subword.
9789 cx.set_state(r#"<p class="yelˇ" />"#);
9790 cx.simulate_keystroke("l");
9791 cx.executor().run_until_parked();
9792 cx.update_editor(|editor, _| {
9793 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9794 assert_eq!(
9795 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9796 &["bg-yellow"]
9797 );
9798 } else {
9799 panic!("expected completion menu to be open");
9800 }
9801 });
9802}
9803
9804#[gpui::test]
9805async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
9806 init_test(cx, |settings| {
9807 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
9808 FormatterList(vec![Formatter::Prettier].into()),
9809 ))
9810 });
9811
9812 let fs = FakeFs::new(cx.executor());
9813 fs.insert_file("/file.ts", Default::default()).await;
9814
9815 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
9816 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9817
9818 language_registry.add(Arc::new(Language::new(
9819 LanguageConfig {
9820 name: "TypeScript".into(),
9821 matcher: LanguageMatcher {
9822 path_suffixes: vec!["ts".to_string()],
9823 ..Default::default()
9824 },
9825 ..Default::default()
9826 },
9827 Some(tree_sitter_rust::language()),
9828 )));
9829 update_test_language_settings(cx, |settings| {
9830 settings.defaults.prettier = Some(PrettierSettings {
9831 allowed: true,
9832 ..PrettierSettings::default()
9833 });
9834 });
9835
9836 let test_plugin = "test_plugin";
9837 let _ = language_registry.register_fake_lsp_adapter(
9838 "TypeScript",
9839 FakeLspAdapter {
9840 prettier_plugins: vec![test_plugin],
9841 ..Default::default()
9842 },
9843 );
9844
9845 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
9846 let buffer = project
9847 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
9848 .await
9849 .unwrap();
9850
9851 let buffer_text = "one\ntwo\nthree\n";
9852 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9853 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9854 _ = editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
9855
9856 editor
9857 .update(cx, |editor, cx| {
9858 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
9859 })
9860 .unwrap()
9861 .await;
9862 assert_eq!(
9863 editor.update(cx, |editor, cx| editor.text(cx)),
9864 buffer_text.to_string() + prettier_format_suffix,
9865 "Test prettier formatting was not applied to the original buffer text",
9866 );
9867
9868 update_test_language_settings(cx, |settings| {
9869 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
9870 });
9871 let format = editor.update(cx, |editor, cx| {
9872 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
9873 });
9874 format.await.unwrap();
9875 assert_eq!(
9876 editor.update(cx, |editor, cx| editor.text(cx)),
9877 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
9878 "Autoformatting (via test prettier) was not applied to the original buffer text",
9879 );
9880}
9881
9882#[gpui::test]
9883async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
9884 init_test(cx, |_| {});
9885 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9886 let base_text = indoc! {r#"struct Row;
9887struct Row1;
9888struct Row2;
9889
9890struct Row4;
9891struct Row5;
9892struct Row6;
9893
9894struct Row8;
9895struct Row9;
9896struct Row10;"#};
9897
9898 // When addition hunks are not adjacent to carets, no hunk revert is performed
9899 assert_hunk_revert(
9900 indoc! {r#"struct Row;
9901 struct Row1;
9902 struct Row1.1;
9903 struct Row1.2;
9904 struct Row2;ˇ
9905
9906 struct Row4;
9907 struct Row5;
9908 struct Row6;
9909
9910 struct Row8;
9911 ˇstruct Row9;
9912 struct Row9.1;
9913 struct Row9.2;
9914 struct Row9.3;
9915 struct Row10;"#},
9916 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
9917 indoc! {r#"struct Row;
9918 struct Row1;
9919 struct Row1.1;
9920 struct Row1.2;
9921 struct Row2;ˇ
9922
9923 struct Row4;
9924 struct Row5;
9925 struct Row6;
9926
9927 struct Row8;
9928 ˇstruct Row9;
9929 struct Row9.1;
9930 struct Row9.2;
9931 struct Row9.3;
9932 struct Row10;"#},
9933 base_text,
9934 &mut cx,
9935 );
9936 // Same for selections
9937 assert_hunk_revert(
9938 indoc! {r#"struct Row;
9939 struct Row1;
9940 struct Row2;
9941 struct Row2.1;
9942 struct Row2.2;
9943 «ˇ
9944 struct Row4;
9945 struct» Row5;
9946 «struct Row6;
9947 ˇ»
9948 struct Row9.1;
9949 struct Row9.2;
9950 struct Row9.3;
9951 struct Row8;
9952 struct Row9;
9953 struct Row10;"#},
9954 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
9955 indoc! {r#"struct Row;
9956 struct Row1;
9957 struct Row2;
9958 struct Row2.1;
9959 struct Row2.2;
9960 «ˇ
9961 struct Row4;
9962 struct» Row5;
9963 «struct Row6;
9964 ˇ»
9965 struct Row9.1;
9966 struct Row9.2;
9967 struct Row9.3;
9968 struct Row8;
9969 struct Row9;
9970 struct Row10;"#},
9971 base_text,
9972 &mut cx,
9973 );
9974
9975 // When carets and selections intersect the addition hunks, those are reverted.
9976 // Adjacent carets got merged.
9977 assert_hunk_revert(
9978 indoc! {r#"struct Row;
9979 ˇ// something on the top
9980 struct Row1;
9981 struct Row2;
9982 struct Roˇw3.1;
9983 struct Row2.2;
9984 struct Row2.3;ˇ
9985
9986 struct Row4;
9987 struct ˇRow5.1;
9988 struct Row5.2;
9989 struct «Rowˇ»5.3;
9990 struct Row5;
9991 struct Row6;
9992 ˇ
9993 struct Row9.1;
9994 struct «Rowˇ»9.2;
9995 struct «ˇRow»9.3;
9996 struct Row8;
9997 struct Row9;
9998 «ˇ// something on bottom»
9999 struct Row10;"#},
10000 vec![
10001 DiffHunkStatus::Added,
10002 DiffHunkStatus::Added,
10003 DiffHunkStatus::Added,
10004 DiffHunkStatus::Added,
10005 DiffHunkStatus::Added,
10006 ],
10007 indoc! {r#"struct Row;
10008 ˇstruct Row1;
10009 struct Row2;
10010 ˇ
10011 struct Row4;
10012 ˇstruct Row5;
10013 struct Row6;
10014 ˇ
10015 ˇstruct Row8;
10016 struct Row9;
10017 ˇstruct Row10;"#},
10018 base_text,
10019 &mut cx,
10020 );
10021}
10022
10023#[gpui::test]
10024async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
10025 init_test(cx, |_| {});
10026 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10027 let base_text = indoc! {r#"struct Row;
10028struct Row1;
10029struct Row2;
10030
10031struct Row4;
10032struct Row5;
10033struct Row6;
10034
10035struct Row8;
10036struct Row9;
10037struct Row10;"#};
10038
10039 // Modification hunks behave the same as the addition ones.
10040 assert_hunk_revert(
10041 indoc! {r#"struct Row;
10042 struct Row1;
10043 struct Row33;
10044 ˇ
10045 struct Row4;
10046 struct Row5;
10047 struct Row6;
10048 ˇ
10049 struct Row99;
10050 struct Row9;
10051 struct Row10;"#},
10052 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10053 indoc! {r#"struct Row;
10054 struct Row1;
10055 struct Row33;
10056 ˇ
10057 struct Row4;
10058 struct Row5;
10059 struct Row6;
10060 ˇ
10061 struct Row99;
10062 struct Row9;
10063 struct Row10;"#},
10064 base_text,
10065 &mut cx,
10066 );
10067 assert_hunk_revert(
10068 indoc! {r#"struct Row;
10069 struct Row1;
10070 struct Row33;
10071 «ˇ
10072 struct Row4;
10073 struct» Row5;
10074 «struct Row6;
10075 ˇ»
10076 struct Row99;
10077 struct Row9;
10078 struct Row10;"#},
10079 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10080 indoc! {r#"struct Row;
10081 struct Row1;
10082 struct Row33;
10083 «ˇ
10084 struct Row4;
10085 struct» Row5;
10086 «struct Row6;
10087 ˇ»
10088 struct Row99;
10089 struct Row9;
10090 struct Row10;"#},
10091 base_text,
10092 &mut cx,
10093 );
10094
10095 assert_hunk_revert(
10096 indoc! {r#"ˇstruct Row1.1;
10097 struct Row1;
10098 «ˇstr»uct Row22;
10099
10100 struct ˇRow44;
10101 struct Row5;
10102 struct «Rˇ»ow66;ˇ
10103
10104 «struˇ»ct Row88;
10105 struct Row9;
10106 struct Row1011;ˇ"#},
10107 vec![
10108 DiffHunkStatus::Modified,
10109 DiffHunkStatus::Modified,
10110 DiffHunkStatus::Modified,
10111 DiffHunkStatus::Modified,
10112 DiffHunkStatus::Modified,
10113 DiffHunkStatus::Modified,
10114 ],
10115 indoc! {r#"struct Row;
10116 ˇstruct Row1;
10117 struct Row2;
10118 ˇ
10119 struct Row4;
10120 ˇstruct Row5;
10121 struct Row6;
10122 ˇ
10123 struct Row8;
10124 ˇstruct Row9;
10125 struct Row10;ˇ"#},
10126 base_text,
10127 &mut cx,
10128 );
10129}
10130
10131#[gpui::test]
10132async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
10133 init_test(cx, |_| {});
10134 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10135 let base_text = indoc! {r#"struct Row;
10136struct Row1;
10137struct Row2;
10138
10139struct Row4;
10140struct Row5;
10141struct Row6;
10142
10143struct Row8;
10144struct Row9;
10145struct Row10;"#};
10146
10147 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
10148 assert_hunk_revert(
10149 indoc! {r#"struct Row;
10150 struct Row2;
10151
10152 ˇstruct Row4;
10153 struct Row5;
10154 struct Row6;
10155 ˇ
10156 struct Row8;
10157 struct Row10;"#},
10158 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10159 indoc! {r#"struct Row;
10160 struct Row2;
10161
10162 ˇstruct Row4;
10163 struct Row5;
10164 struct Row6;
10165 ˇ
10166 struct Row8;
10167 struct Row10;"#},
10168 base_text,
10169 &mut cx,
10170 );
10171 assert_hunk_revert(
10172 indoc! {r#"struct Row;
10173 struct Row2;
10174
10175 «ˇstruct Row4;
10176 struct» Row5;
10177 «struct Row6;
10178 ˇ»
10179 struct Row8;
10180 struct Row10;"#},
10181 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10182 indoc! {r#"struct Row;
10183 struct Row2;
10184
10185 «ˇstruct Row4;
10186 struct» Row5;
10187 «struct Row6;
10188 ˇ»
10189 struct Row8;
10190 struct Row10;"#},
10191 base_text,
10192 &mut cx,
10193 );
10194
10195 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
10196 assert_hunk_revert(
10197 indoc! {r#"struct Row;
10198 ˇstruct Row2;
10199
10200 struct Row4;
10201 struct Row5;
10202 struct Row6;
10203
10204 struct Row8;ˇ
10205 struct Row10;"#},
10206 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10207 indoc! {r#"struct Row;
10208 struct Row1;
10209 ˇstruct Row2;
10210
10211 struct Row4;
10212 struct Row5;
10213 struct Row6;
10214
10215 struct Row8;ˇ
10216 struct Row9;
10217 struct Row10;"#},
10218 base_text,
10219 &mut cx,
10220 );
10221 assert_hunk_revert(
10222 indoc! {r#"struct Row;
10223 struct Row2«ˇ;
10224 struct Row4;
10225 struct» Row5;
10226 «struct Row6;
10227
10228 struct Row8;ˇ»
10229 struct Row10;"#},
10230 vec![
10231 DiffHunkStatus::Removed,
10232 DiffHunkStatus::Removed,
10233 DiffHunkStatus::Removed,
10234 ],
10235 indoc! {r#"struct Row;
10236 struct Row1;
10237 struct Row2«ˇ;
10238
10239 struct Row4;
10240 struct» Row5;
10241 «struct Row6;
10242
10243 struct Row8;ˇ»
10244 struct Row9;
10245 struct Row10;"#},
10246 base_text,
10247 &mut cx,
10248 );
10249}
10250
10251#[gpui::test]
10252async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
10253 init_test(cx, |_| {});
10254
10255 let cols = 4;
10256 let rows = 10;
10257 let sample_text_1 = sample_text(rows, cols, 'a');
10258 assert_eq!(
10259 sample_text_1,
10260 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10261 );
10262 let sample_text_2 = sample_text(rows, cols, 'l');
10263 assert_eq!(
10264 sample_text_2,
10265 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10266 );
10267 let sample_text_3 = sample_text(rows, cols, 'v');
10268 assert_eq!(
10269 sample_text_3,
10270 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10271 );
10272
10273 fn diff_every_buffer_row(
10274 buffer: &Model<Buffer>,
10275 sample_text: String,
10276 cols: usize,
10277 cx: &mut gpui::TestAppContext,
10278 ) {
10279 // revert first character in each row, creating one large diff hunk per buffer
10280 let is_first_char = |offset: usize| offset % cols == 0;
10281 buffer.update(cx, |buffer, cx| {
10282 buffer.set_text(
10283 sample_text
10284 .chars()
10285 .enumerate()
10286 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
10287 .collect::<String>(),
10288 cx,
10289 );
10290 buffer.set_diff_base(Some(sample_text), cx);
10291 });
10292 cx.executor().run_until_parked();
10293 }
10294
10295 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10296 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10297
10298 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10299 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10300
10301 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10302 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10303
10304 let multibuffer = cx.new_model(|cx| {
10305 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10306 multibuffer.push_excerpts(
10307 buffer_1.clone(),
10308 [
10309 ExcerptRange {
10310 context: Point::new(0, 0)..Point::new(3, 0),
10311 primary: None,
10312 },
10313 ExcerptRange {
10314 context: Point::new(5, 0)..Point::new(7, 0),
10315 primary: None,
10316 },
10317 ExcerptRange {
10318 context: Point::new(9, 0)..Point::new(10, 4),
10319 primary: None,
10320 },
10321 ],
10322 cx,
10323 );
10324 multibuffer.push_excerpts(
10325 buffer_2.clone(),
10326 [
10327 ExcerptRange {
10328 context: Point::new(0, 0)..Point::new(3, 0),
10329 primary: None,
10330 },
10331 ExcerptRange {
10332 context: Point::new(5, 0)..Point::new(7, 0),
10333 primary: None,
10334 },
10335 ExcerptRange {
10336 context: Point::new(9, 0)..Point::new(10, 4),
10337 primary: None,
10338 },
10339 ],
10340 cx,
10341 );
10342 multibuffer.push_excerpts(
10343 buffer_3.clone(),
10344 [
10345 ExcerptRange {
10346 context: Point::new(0, 0)..Point::new(3, 0),
10347 primary: None,
10348 },
10349 ExcerptRange {
10350 context: Point::new(5, 0)..Point::new(7, 0),
10351 primary: None,
10352 },
10353 ExcerptRange {
10354 context: Point::new(9, 0)..Point::new(10, 4),
10355 primary: None,
10356 },
10357 ],
10358 cx,
10359 );
10360 multibuffer
10361 });
10362
10363 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
10364 editor.update(cx, |editor, cx| {
10365 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");
10366 editor.select_all(&SelectAll, cx);
10367 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10368 });
10369 cx.executor().run_until_parked();
10370 // When all ranges are selected, all buffer hunks are reverted.
10371 editor.update(cx, |editor, cx| {
10372 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");
10373 });
10374 buffer_1.update(cx, |buffer, _| {
10375 assert_eq!(buffer.text(), sample_text_1);
10376 });
10377 buffer_2.update(cx, |buffer, _| {
10378 assert_eq!(buffer.text(), sample_text_2);
10379 });
10380 buffer_3.update(cx, |buffer, _| {
10381 assert_eq!(buffer.text(), sample_text_3);
10382 });
10383
10384 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10385 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10386 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10387 editor.update(cx, |editor, cx| {
10388 editor.change_selections(None, cx, |s| {
10389 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
10390 });
10391 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10392 });
10393 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
10394 // but not affect buffer_2 and its related excerpts.
10395 editor.update(cx, |editor, cx| {
10396 assert_eq!(
10397 editor.text(cx),
10398 "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"
10399 );
10400 });
10401 buffer_1.update(cx, |buffer, _| {
10402 assert_eq!(buffer.text(), sample_text_1);
10403 });
10404 buffer_2.update(cx, |buffer, _| {
10405 assert_eq!(
10406 buffer.text(),
10407 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
10408 );
10409 });
10410 buffer_3.update(cx, |buffer, _| {
10411 assert_eq!(
10412 buffer.text(),
10413 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
10414 );
10415 });
10416}
10417
10418#[gpui::test]
10419async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
10420 init_test(cx, |_| {});
10421
10422 let cols = 4;
10423 let rows = 10;
10424 let sample_text_1 = sample_text(rows, cols, 'a');
10425 assert_eq!(
10426 sample_text_1,
10427 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10428 );
10429 let sample_text_2 = sample_text(rows, cols, 'l');
10430 assert_eq!(
10431 sample_text_2,
10432 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10433 );
10434 let sample_text_3 = sample_text(rows, cols, 'v');
10435 assert_eq!(
10436 sample_text_3,
10437 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10438 );
10439
10440 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10441 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10442 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10443
10444 let multi_buffer = cx.new_model(|cx| {
10445 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10446 multibuffer.push_excerpts(
10447 buffer_1.clone(),
10448 [
10449 ExcerptRange {
10450 context: Point::new(0, 0)..Point::new(3, 0),
10451 primary: None,
10452 },
10453 ExcerptRange {
10454 context: Point::new(5, 0)..Point::new(7, 0),
10455 primary: None,
10456 },
10457 ExcerptRange {
10458 context: Point::new(9, 0)..Point::new(10, 4),
10459 primary: None,
10460 },
10461 ],
10462 cx,
10463 );
10464 multibuffer.push_excerpts(
10465 buffer_2.clone(),
10466 [
10467 ExcerptRange {
10468 context: Point::new(0, 0)..Point::new(3, 0),
10469 primary: None,
10470 },
10471 ExcerptRange {
10472 context: Point::new(5, 0)..Point::new(7, 0),
10473 primary: None,
10474 },
10475 ExcerptRange {
10476 context: Point::new(9, 0)..Point::new(10, 4),
10477 primary: None,
10478 },
10479 ],
10480 cx,
10481 );
10482 multibuffer.push_excerpts(
10483 buffer_3.clone(),
10484 [
10485 ExcerptRange {
10486 context: Point::new(0, 0)..Point::new(3, 0),
10487 primary: None,
10488 },
10489 ExcerptRange {
10490 context: Point::new(5, 0)..Point::new(7, 0),
10491 primary: None,
10492 },
10493 ExcerptRange {
10494 context: Point::new(9, 0)..Point::new(10, 4),
10495 primary: None,
10496 },
10497 ],
10498 cx,
10499 );
10500 multibuffer
10501 });
10502
10503 let fs = FakeFs::new(cx.executor());
10504 fs.insert_tree(
10505 "/a",
10506 json!({
10507 "main.rs": sample_text_1,
10508 "other.rs": sample_text_2,
10509 "lib.rs": sample_text_3,
10510 }),
10511 )
10512 .await;
10513 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10514 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10515 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10516 let multi_buffer_editor = cx.new_view(|cx| {
10517 Editor::new(
10518 EditorMode::Full,
10519 multi_buffer,
10520 Some(project.clone()),
10521 true,
10522 cx,
10523 )
10524 });
10525 let multibuffer_item_id = workspace
10526 .update(cx, |workspace, cx| {
10527 assert!(
10528 workspace.active_item(cx).is_none(),
10529 "active item should be None before the first item is added"
10530 );
10531 workspace.add_item_to_active_pane(
10532 Box::new(multi_buffer_editor.clone()),
10533 None,
10534 true,
10535 cx,
10536 );
10537 let active_item = workspace
10538 .active_item(cx)
10539 .expect("should have an active item after adding the multi buffer");
10540 assert!(
10541 !active_item.is_singleton(cx),
10542 "A multi buffer was expected to active after adding"
10543 );
10544 active_item.item_id()
10545 })
10546 .unwrap();
10547 cx.executor().run_until_parked();
10548
10549 multi_buffer_editor.update(cx, |editor, cx| {
10550 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
10551 editor.open_excerpts(&OpenExcerpts, cx);
10552 });
10553 cx.executor().run_until_parked();
10554 let first_item_id = workspace
10555 .update(cx, |workspace, cx| {
10556 let active_item = workspace
10557 .active_item(cx)
10558 .expect("should have an active item after navigating into the 1st buffer");
10559 let first_item_id = active_item.item_id();
10560 assert_ne!(
10561 first_item_id, multibuffer_item_id,
10562 "Should navigate into the 1st buffer and activate it"
10563 );
10564 assert!(
10565 active_item.is_singleton(cx),
10566 "New active item should be a singleton buffer"
10567 );
10568 assert_eq!(
10569 active_item
10570 .act_as::<Editor>(cx)
10571 .expect("should have navigated into an editor for the 1st buffer")
10572 .read(cx)
10573 .text(cx),
10574 sample_text_1
10575 );
10576
10577 workspace
10578 .go_back(workspace.active_pane().downgrade(), cx)
10579 .detach_and_log_err(cx);
10580
10581 first_item_id
10582 })
10583 .unwrap();
10584 cx.executor().run_until_parked();
10585 workspace
10586 .update(cx, |workspace, cx| {
10587 let active_item = workspace
10588 .active_item(cx)
10589 .expect("should have an active item after navigating back");
10590 assert_eq!(
10591 active_item.item_id(),
10592 multibuffer_item_id,
10593 "Should navigate back to the multi buffer"
10594 );
10595 assert!(!active_item.is_singleton(cx));
10596 })
10597 .unwrap();
10598
10599 multi_buffer_editor.update(cx, |editor, cx| {
10600 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
10601 s.select_ranges(Some(39..40))
10602 });
10603 editor.open_excerpts(&OpenExcerpts, cx);
10604 });
10605 cx.executor().run_until_parked();
10606 let second_item_id = workspace
10607 .update(cx, |workspace, cx| {
10608 let active_item = workspace
10609 .active_item(cx)
10610 .expect("should have an active item after navigating into the 2nd buffer");
10611 let second_item_id = active_item.item_id();
10612 assert_ne!(
10613 second_item_id, multibuffer_item_id,
10614 "Should navigate away from the multibuffer"
10615 );
10616 assert_ne!(
10617 second_item_id, first_item_id,
10618 "Should navigate into the 2nd buffer and activate it"
10619 );
10620 assert!(
10621 active_item.is_singleton(cx),
10622 "New active item should be a singleton buffer"
10623 );
10624 assert_eq!(
10625 active_item
10626 .act_as::<Editor>(cx)
10627 .expect("should have navigated into an editor")
10628 .read(cx)
10629 .text(cx),
10630 sample_text_2
10631 );
10632
10633 workspace
10634 .go_back(workspace.active_pane().downgrade(), cx)
10635 .detach_and_log_err(cx);
10636
10637 second_item_id
10638 })
10639 .unwrap();
10640 cx.executor().run_until_parked();
10641 workspace
10642 .update(cx, |workspace, cx| {
10643 let active_item = workspace
10644 .active_item(cx)
10645 .expect("should have an active item after navigating back from the 2nd buffer");
10646 assert_eq!(
10647 active_item.item_id(),
10648 multibuffer_item_id,
10649 "Should navigate back from the 2nd buffer to the multi buffer"
10650 );
10651 assert!(!active_item.is_singleton(cx));
10652 })
10653 .unwrap();
10654
10655 multi_buffer_editor.update(cx, |editor, cx| {
10656 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
10657 s.select_ranges(Some(60..70))
10658 });
10659 editor.open_excerpts(&OpenExcerpts, cx);
10660 });
10661 cx.executor().run_until_parked();
10662 workspace
10663 .update(cx, |workspace, cx| {
10664 let active_item = workspace
10665 .active_item(cx)
10666 .expect("should have an active item after navigating into the 3rd buffer");
10667 let third_item_id = active_item.item_id();
10668 assert_ne!(
10669 third_item_id, multibuffer_item_id,
10670 "Should navigate into the 3rd buffer and activate it"
10671 );
10672 assert_ne!(third_item_id, first_item_id);
10673 assert_ne!(third_item_id, second_item_id);
10674 assert!(
10675 active_item.is_singleton(cx),
10676 "New active item should be a singleton buffer"
10677 );
10678 assert_eq!(
10679 active_item
10680 .act_as::<Editor>(cx)
10681 .expect("should have navigated into an editor")
10682 .read(cx)
10683 .text(cx),
10684 sample_text_3
10685 );
10686
10687 workspace
10688 .go_back(workspace.active_pane().downgrade(), cx)
10689 .detach_and_log_err(cx);
10690 })
10691 .unwrap();
10692 cx.executor().run_until_parked();
10693 workspace
10694 .update(cx, |workspace, cx| {
10695 let active_item = workspace
10696 .active_item(cx)
10697 .expect("should have an active item after navigating back from the 3rd buffer");
10698 assert_eq!(
10699 active_item.item_id(),
10700 multibuffer_item_id,
10701 "Should navigate back from the 3rd buffer to the multi buffer"
10702 );
10703 assert!(!active_item.is_singleton(cx));
10704 })
10705 .unwrap();
10706}
10707
10708#[gpui::test]
10709async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10710 init_test(cx, |_| {});
10711
10712 let mut cx = EditorTestContext::new(cx).await;
10713
10714 let diff_base = r#"
10715 use some::mod;
10716
10717 const A: u32 = 42;
10718
10719 fn main() {
10720 println!("hello");
10721
10722 println!("world");
10723 }
10724 "#
10725 .unindent();
10726
10727 cx.set_state(
10728 &r#"
10729 use some::modified;
10730
10731 ˇ
10732 fn main() {
10733 println!("hello there");
10734
10735 println!("around the");
10736 println!("world");
10737 }
10738 "#
10739 .unindent(),
10740 );
10741
10742 cx.set_diff_base(Some(&diff_base));
10743 executor.run_until_parked();
10744 let unexpanded_hunks = vec![
10745 (
10746 "use some::mod;\n".to_string(),
10747 DiffHunkStatus::Modified,
10748 DisplayRow(0)..DisplayRow(1),
10749 ),
10750 (
10751 "const A: u32 = 42;\n".to_string(),
10752 DiffHunkStatus::Removed,
10753 DisplayRow(2)..DisplayRow(2),
10754 ),
10755 (
10756 " println!(\"hello\");\n".to_string(),
10757 DiffHunkStatus::Modified,
10758 DisplayRow(4)..DisplayRow(5),
10759 ),
10760 (
10761 "".to_string(),
10762 DiffHunkStatus::Added,
10763 DisplayRow(6)..DisplayRow(7),
10764 ),
10765 ];
10766 cx.update_editor(|editor, cx| {
10767 let snapshot = editor.snapshot(cx);
10768 let all_hunks = editor_hunks(editor, &snapshot, cx);
10769 assert_eq!(all_hunks, unexpanded_hunks);
10770 });
10771
10772 cx.update_editor(|editor, cx| {
10773 for _ in 0..4 {
10774 editor.go_to_hunk(&GoToHunk, cx);
10775 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10776 }
10777 });
10778 executor.run_until_parked();
10779 cx.assert_editor_state(
10780 &r#"
10781 use some::modified;
10782
10783 ˇ
10784 fn main() {
10785 println!("hello there");
10786
10787 println!("around the");
10788 println!("world");
10789 }
10790 "#
10791 .unindent(),
10792 );
10793 cx.update_editor(|editor, cx| {
10794 let snapshot = editor.snapshot(cx);
10795 let all_hunks = editor_hunks(editor, &snapshot, cx);
10796 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10797 assert_eq!(
10798 expanded_hunks_background_highlights(editor, cx),
10799 vec![DisplayRow(1)..=DisplayRow(1), DisplayRow(7)..=DisplayRow(7), DisplayRow(9)..=DisplayRow(9)],
10800 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
10801 );
10802 assert_eq!(
10803 all_hunks,
10804 vec![
10805 ("use some::mod;\n".to_string(), DiffHunkStatus::Modified, DisplayRow(1)..DisplayRow(2)),
10806 ("const A: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(4)..DisplayRow(4)),
10807 (" println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(7)..DisplayRow(8)),
10808 ("".to_string(), DiffHunkStatus::Added, DisplayRow(9)..DisplayRow(10)),
10809 ],
10810 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
10811 (from modified and removed hunks)"
10812 );
10813 assert_eq!(
10814 all_hunks, all_expanded_hunks,
10815 "Editor hunks should not change and all be expanded"
10816 );
10817 });
10818
10819 cx.update_editor(|editor, cx| {
10820 editor.cancel(&Cancel, cx);
10821
10822 let snapshot = editor.snapshot(cx);
10823 let all_hunks = editor_hunks(editor, &snapshot, cx);
10824 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
10825 assert_eq!(
10826 expanded_hunks_background_highlights(editor, cx),
10827 Vec::new(),
10828 "After cancelling in editor, no git highlights should be left"
10829 );
10830 assert_eq!(
10831 all_expanded_hunks,
10832 Vec::new(),
10833 "After cancelling in editor, no hunks should be expanded"
10834 );
10835 assert_eq!(
10836 all_hunks, unexpanded_hunks,
10837 "After cancelling in editor, regular hunks' coordinates should get back to normal"
10838 );
10839 });
10840}
10841
10842#[gpui::test]
10843async fn test_toggled_diff_base_change(
10844 executor: BackgroundExecutor,
10845 cx: &mut gpui::TestAppContext,
10846) {
10847 init_test(cx, |_| {});
10848
10849 let mut cx = EditorTestContext::new(cx).await;
10850
10851 let diff_base = r#"
10852 use some::mod1;
10853 use some::mod2;
10854
10855 const A: u32 = 42;
10856 const B: u32 = 42;
10857 const C: u32 = 42;
10858
10859 fn main(ˇ) {
10860 println!("hello");
10861
10862 println!("world");
10863 }
10864 "#
10865 .unindent();
10866
10867 cx.set_state(
10868 &r#"
10869 use some::mod2;
10870
10871 const A: u32 = 42;
10872 const C: u32 = 42;
10873
10874 fn main(ˇ) {
10875 //println!("hello");
10876
10877 println!("world");
10878 //
10879 //
10880 }
10881 "#
10882 .unindent(),
10883 );
10884
10885 cx.set_diff_base(Some(&diff_base));
10886 executor.run_until_parked();
10887 cx.update_editor(|editor, cx| {
10888 let snapshot = editor.snapshot(cx);
10889 let all_hunks = editor_hunks(editor, &snapshot, cx);
10890 assert_eq!(
10891 all_hunks,
10892 vec![
10893 (
10894 "use some::mod1;\n".to_string(),
10895 DiffHunkStatus::Removed,
10896 DisplayRow(0)..DisplayRow(0)
10897 ),
10898 (
10899 "const B: u32 = 42;\n".to_string(),
10900 DiffHunkStatus::Removed,
10901 DisplayRow(3)..DisplayRow(3)
10902 ),
10903 (
10904 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
10905 DiffHunkStatus::Modified,
10906 DisplayRow(5)..DisplayRow(7)
10907 ),
10908 (
10909 "".to_string(),
10910 DiffHunkStatus::Added,
10911 DisplayRow(9)..DisplayRow(11)
10912 ),
10913 ]
10914 );
10915 });
10916
10917 cx.update_editor(|editor, cx| {
10918 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10919 });
10920 executor.run_until_parked();
10921 cx.assert_editor_state(
10922 &r#"
10923 use some::mod2;
10924
10925 const A: u32 = 42;
10926 const C: u32 = 42;
10927
10928 fn main(ˇ) {
10929 //println!("hello");
10930
10931 println!("world");
10932 //
10933 //
10934 }
10935 "#
10936 .unindent(),
10937 );
10938 cx.update_editor(|editor, cx| {
10939 let snapshot = editor.snapshot(cx);
10940 let all_hunks = editor_hunks(editor, &snapshot, cx);
10941 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10942 assert_eq!(
10943 expanded_hunks_background_highlights(editor, cx),
10944 vec![DisplayRow(9)..=DisplayRow(10), DisplayRow(13)..=DisplayRow(14)],
10945 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
10946 );
10947 assert_eq!(
10948 all_hunks,
10949 vec![
10950 ("use some::mod1;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(1)..DisplayRow(1)),
10951 ("const B: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(5)..DisplayRow(5)),
10952 ("fn main(ˇ) {\n println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(9)..DisplayRow(11)),
10953 ("".to_string(), DiffHunkStatus::Added, DisplayRow(13)..DisplayRow(15)),
10954 ],
10955 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
10956 (from modified and removed hunks)"
10957 );
10958 assert_eq!(
10959 all_hunks, all_expanded_hunks,
10960 "Editor hunks should not change and all be expanded"
10961 );
10962 });
10963
10964 cx.set_diff_base(Some("new diff base!"));
10965 executor.run_until_parked();
10966
10967 cx.update_editor(|editor, cx| {
10968 let snapshot = editor.snapshot(cx);
10969 let all_hunks = editor_hunks(editor, &snapshot, cx);
10970 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
10971 assert_eq!(
10972 expanded_hunks_background_highlights(editor, cx),
10973 Vec::new(),
10974 "After diff base is changed, old git highlights should be removed"
10975 );
10976 assert_eq!(
10977 all_expanded_hunks,
10978 Vec::new(),
10979 "After diff base is changed, old git hunk expansions should be removed"
10980 );
10981 assert_eq!(
10982 all_hunks,
10983 vec![(
10984 "new diff base!".to_string(),
10985 DiffHunkStatus::Modified,
10986 DisplayRow(0)..snapshot.display_snapshot.max_point().row()
10987 )],
10988 "After diff base is changed, hunks should update"
10989 );
10990 });
10991}
10992
10993#[gpui::test]
10994async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10995 init_test(cx, |_| {});
10996
10997 let mut cx = EditorTestContext::new(cx).await;
10998
10999 let diff_base = r#"
11000 use some::mod1;
11001 use some::mod2;
11002
11003 const A: u32 = 42;
11004 const B: u32 = 42;
11005 const C: u32 = 42;
11006
11007 fn main(ˇ) {
11008 println!("hello");
11009
11010 println!("world");
11011 }
11012
11013 fn another() {
11014 println!("another");
11015 }
11016
11017 fn another2() {
11018 println!("another2");
11019 }
11020 "#
11021 .unindent();
11022
11023 cx.set_state(
11024 &r#"
11025 «use some::mod2;
11026
11027 const A: u32 = 42;
11028 const C: u32 = 42;
11029
11030 fn main() {
11031 //println!("hello");
11032
11033 println!("world");
11034 //
11035 //ˇ»
11036 }
11037
11038 fn another() {
11039 println!("another");
11040 println!("another");
11041 }
11042
11043 println!("another2");
11044 }
11045 "#
11046 .unindent(),
11047 );
11048
11049 cx.set_diff_base(Some(&diff_base));
11050 executor.run_until_parked();
11051 cx.update_editor(|editor, cx| {
11052 let snapshot = editor.snapshot(cx);
11053 let all_hunks = editor_hunks(editor, &snapshot, cx);
11054 assert_eq!(
11055 all_hunks,
11056 vec![
11057 (
11058 "use some::mod1;\n".to_string(),
11059 DiffHunkStatus::Removed,
11060 DisplayRow(0)..DisplayRow(0)
11061 ),
11062 (
11063 "const B: u32 = 42;\n".to_string(),
11064 DiffHunkStatus::Removed,
11065 DisplayRow(3)..DisplayRow(3)
11066 ),
11067 (
11068 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11069 DiffHunkStatus::Modified,
11070 DisplayRow(5)..DisplayRow(7)
11071 ),
11072 (
11073 "".to_string(),
11074 DiffHunkStatus::Added,
11075 DisplayRow(9)..DisplayRow(11)
11076 ),
11077 (
11078 "".to_string(),
11079 DiffHunkStatus::Added,
11080 DisplayRow(15)..DisplayRow(16)
11081 ),
11082 (
11083 "fn another2() {\n".to_string(),
11084 DiffHunkStatus::Removed,
11085 DisplayRow(18)..DisplayRow(18)
11086 ),
11087 ]
11088 );
11089 });
11090
11091 cx.update_editor(|editor, cx| {
11092 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11093 });
11094 executor.run_until_parked();
11095 cx.assert_editor_state(
11096 &r#"
11097 «use some::mod2;
11098
11099 const A: u32 = 42;
11100 const C: u32 = 42;
11101
11102 fn main() {
11103 //println!("hello");
11104
11105 println!("world");
11106 //
11107 //ˇ»
11108 }
11109
11110 fn another() {
11111 println!("another");
11112 println!("another");
11113 }
11114
11115 println!("another2");
11116 }
11117 "#
11118 .unindent(),
11119 );
11120 cx.update_editor(|editor, cx| {
11121 let snapshot = editor.snapshot(cx);
11122 let all_hunks = editor_hunks(editor, &snapshot, cx);
11123 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11124 assert_eq!(
11125 expanded_hunks_background_highlights(editor, cx),
11126 vec![
11127 DisplayRow(9)..=DisplayRow(10),
11128 DisplayRow(13)..=DisplayRow(14),
11129 DisplayRow(19)..=DisplayRow(19)
11130 ]
11131 );
11132 assert_eq!(
11133 all_hunks,
11134 vec![
11135 (
11136 "use some::mod1;\n".to_string(),
11137 DiffHunkStatus::Removed,
11138 DisplayRow(1)..DisplayRow(1)
11139 ),
11140 (
11141 "const B: u32 = 42;\n".to_string(),
11142 DiffHunkStatus::Removed,
11143 DisplayRow(5)..DisplayRow(5)
11144 ),
11145 (
11146 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11147 DiffHunkStatus::Modified,
11148 DisplayRow(9)..DisplayRow(11)
11149 ),
11150 (
11151 "".to_string(),
11152 DiffHunkStatus::Added,
11153 DisplayRow(13)..DisplayRow(15)
11154 ),
11155 (
11156 "".to_string(),
11157 DiffHunkStatus::Added,
11158 DisplayRow(19)..DisplayRow(20)
11159 ),
11160 (
11161 "fn another2() {\n".to_string(),
11162 DiffHunkStatus::Removed,
11163 DisplayRow(23)..DisplayRow(23)
11164 ),
11165 ],
11166 );
11167 assert_eq!(all_hunks, all_expanded_hunks);
11168 });
11169
11170 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
11171 cx.executor().run_until_parked();
11172 cx.assert_editor_state(
11173 &r#"
11174 «use some::mod2;
11175
11176 const A: u32 = 42;
11177 const C: u32 = 42;
11178
11179 fn main() {
11180 //println!("hello");
11181
11182 println!("world");
11183 //
11184 //ˇ»
11185 }
11186
11187 fn another() {
11188 println!("another");
11189 println!("another");
11190 }
11191
11192 println!("another2");
11193 }
11194 "#
11195 .unindent(),
11196 );
11197 cx.update_editor(|editor, cx| {
11198 let snapshot = editor.snapshot(cx);
11199 let all_hunks = editor_hunks(editor, &snapshot, cx);
11200 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11201 assert_eq!(
11202 expanded_hunks_background_highlights(editor, cx),
11203 vec![DisplayRow(0)..=DisplayRow(0), DisplayRow(5)..=DisplayRow(5)],
11204 "Only one hunk is left not folded, its highlight should be visible"
11205 );
11206 assert_eq!(
11207 all_hunks,
11208 vec![
11209 (
11210 "use some::mod1;\n".to_string(),
11211 DiffHunkStatus::Removed,
11212 DisplayRow(0)..DisplayRow(0)
11213 ),
11214 (
11215 "const B: u32 = 42;\n".to_string(),
11216 DiffHunkStatus::Removed,
11217 DisplayRow(0)..DisplayRow(0)
11218 ),
11219 (
11220 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11221 DiffHunkStatus::Modified,
11222 DisplayRow(0)..DisplayRow(0)
11223 ),
11224 (
11225 "".to_string(),
11226 DiffHunkStatus::Added,
11227 DisplayRow(0)..DisplayRow(1)
11228 ),
11229 (
11230 "".to_string(),
11231 DiffHunkStatus::Added,
11232 DisplayRow(5)..DisplayRow(6)
11233 ),
11234 (
11235 "fn another2() {\n".to_string(),
11236 DiffHunkStatus::Removed,
11237 DisplayRow(9)..DisplayRow(9)
11238 ),
11239 ],
11240 "Hunk list should still return shifted folded hunks"
11241 );
11242 assert_eq!(
11243 all_expanded_hunks,
11244 vec![
11245 (
11246 "".to_string(),
11247 DiffHunkStatus::Added,
11248 DisplayRow(5)..DisplayRow(6)
11249 ),
11250 (
11251 "fn another2() {\n".to_string(),
11252 DiffHunkStatus::Removed,
11253 DisplayRow(9)..DisplayRow(9)
11254 ),
11255 ],
11256 "Only non-folded hunks should be left expanded"
11257 );
11258 });
11259
11260 cx.update_editor(|editor, cx| {
11261 editor.select_all(&SelectAll, cx);
11262 editor.unfold_lines(&UnfoldLines, cx);
11263 });
11264 cx.executor().run_until_parked();
11265 cx.assert_editor_state(
11266 &r#"
11267 «use some::mod2;
11268
11269 const A: u32 = 42;
11270 const C: u32 = 42;
11271
11272 fn main() {
11273 //println!("hello");
11274
11275 println!("world");
11276 //
11277 //
11278 }
11279
11280 fn another() {
11281 println!("another");
11282 println!("another");
11283 }
11284
11285 println!("another2");
11286 }
11287 ˇ»"#
11288 .unindent(),
11289 );
11290 cx.update_editor(|editor, cx| {
11291 let snapshot = editor.snapshot(cx);
11292 let all_hunks = editor_hunks(editor, &snapshot, cx);
11293 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11294 assert_eq!(
11295 expanded_hunks_background_highlights(editor, cx),
11296 vec![
11297 DisplayRow(9)..=DisplayRow(10),
11298 DisplayRow(13)..=DisplayRow(14),
11299 DisplayRow(19)..=DisplayRow(19)
11300 ],
11301 "After unfolding, all hunk diffs should be visible again"
11302 );
11303 assert_eq!(
11304 all_hunks,
11305 vec![
11306 (
11307 "use some::mod1;\n".to_string(),
11308 DiffHunkStatus::Removed,
11309 DisplayRow(1)..DisplayRow(1)
11310 ),
11311 (
11312 "const B: u32 = 42;\n".to_string(),
11313 DiffHunkStatus::Removed,
11314 DisplayRow(5)..DisplayRow(5)
11315 ),
11316 (
11317 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11318 DiffHunkStatus::Modified,
11319 DisplayRow(9)..DisplayRow(11)
11320 ),
11321 (
11322 "".to_string(),
11323 DiffHunkStatus::Added,
11324 DisplayRow(13)..DisplayRow(15)
11325 ),
11326 (
11327 "".to_string(),
11328 DiffHunkStatus::Added,
11329 DisplayRow(19)..DisplayRow(20)
11330 ),
11331 (
11332 "fn another2() {\n".to_string(),
11333 DiffHunkStatus::Removed,
11334 DisplayRow(23)..DisplayRow(23)
11335 ),
11336 ],
11337 );
11338 assert_eq!(all_hunks, all_expanded_hunks);
11339 });
11340}
11341
11342#[gpui::test]
11343async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
11344 init_test(cx, |_| {});
11345
11346 let cols = 4;
11347 let rows = 10;
11348 let sample_text_1 = sample_text(rows, cols, 'a');
11349 assert_eq!(
11350 sample_text_1,
11351 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11352 );
11353 let modified_sample_text_1 = "aaaa\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
11354 let sample_text_2 = sample_text(rows, cols, 'l');
11355 assert_eq!(
11356 sample_text_2,
11357 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11358 );
11359 let modified_sample_text_2 = "llll\nmmmm\n1n1n1n1n1\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
11360 let sample_text_3 = sample_text(rows, cols, 'v');
11361 assert_eq!(
11362 sample_text_3,
11363 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11364 );
11365 let modified_sample_text_3 =
11366 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n@@@@\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
11367 let buffer_1 = cx.new_model(|cx| {
11368 let mut buffer = Buffer::local(modified_sample_text_1.to_string(), cx);
11369 buffer.set_diff_base(Some(sample_text_1.clone()), cx);
11370 buffer
11371 });
11372 let buffer_2 = cx.new_model(|cx| {
11373 let mut buffer = Buffer::local(modified_sample_text_2.to_string(), cx);
11374 buffer.set_diff_base(Some(sample_text_2.clone()), cx);
11375 buffer
11376 });
11377 let buffer_3 = cx.new_model(|cx| {
11378 let mut buffer = Buffer::local(modified_sample_text_3.to_string(), cx);
11379 buffer.set_diff_base(Some(sample_text_3.clone()), cx);
11380 buffer
11381 });
11382
11383 let multi_buffer = cx.new_model(|cx| {
11384 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
11385 multibuffer.push_excerpts(
11386 buffer_1.clone(),
11387 [
11388 ExcerptRange {
11389 context: Point::new(0, 0)..Point::new(3, 0),
11390 primary: None,
11391 },
11392 ExcerptRange {
11393 context: Point::new(5, 0)..Point::new(7, 0),
11394 primary: None,
11395 },
11396 ExcerptRange {
11397 context: Point::new(9, 0)..Point::new(10, 4),
11398 primary: None,
11399 },
11400 ],
11401 cx,
11402 );
11403 multibuffer.push_excerpts(
11404 buffer_2.clone(),
11405 [
11406 ExcerptRange {
11407 context: Point::new(0, 0)..Point::new(3, 0),
11408 primary: None,
11409 },
11410 ExcerptRange {
11411 context: Point::new(5, 0)..Point::new(7, 0),
11412 primary: None,
11413 },
11414 ExcerptRange {
11415 context: Point::new(9, 0)..Point::new(10, 4),
11416 primary: None,
11417 },
11418 ],
11419 cx,
11420 );
11421 multibuffer.push_excerpts(
11422 buffer_3.clone(),
11423 [
11424 ExcerptRange {
11425 context: Point::new(0, 0)..Point::new(3, 0),
11426 primary: None,
11427 },
11428 ExcerptRange {
11429 context: Point::new(5, 0)..Point::new(7, 0),
11430 primary: None,
11431 },
11432 ExcerptRange {
11433 context: Point::new(9, 0)..Point::new(10, 4),
11434 primary: None,
11435 },
11436 ],
11437 cx,
11438 );
11439 multibuffer
11440 });
11441
11442 let fs = FakeFs::new(cx.executor());
11443 fs.insert_tree(
11444 "/a",
11445 json!({
11446 "main.rs": modified_sample_text_1,
11447 "other.rs": modified_sample_text_2,
11448 "lib.rs": modified_sample_text_3,
11449 }),
11450 )
11451 .await;
11452
11453 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11454 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11455 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11456 let multi_buffer_editor = cx.new_view(|cx| {
11457 Editor::new(
11458 EditorMode::Full,
11459 multi_buffer,
11460 Some(project.clone()),
11461 true,
11462 cx,
11463 )
11464 });
11465 cx.executor().run_until_parked();
11466
11467 let expected_all_hunks = vec![
11468 (
11469 "bbbb\n".to_string(),
11470 DiffHunkStatus::Removed,
11471 DisplayRow(4)..DisplayRow(4),
11472 ),
11473 (
11474 "nnnn\n".to_string(),
11475 DiffHunkStatus::Modified,
11476 DisplayRow(21)..DisplayRow(22),
11477 ),
11478 (
11479 "".to_string(),
11480 DiffHunkStatus::Added,
11481 DisplayRow(41)..DisplayRow(42),
11482 ),
11483 ];
11484 let expected_all_hunks_shifted = vec![
11485 (
11486 "bbbb\n".to_string(),
11487 DiffHunkStatus::Removed,
11488 DisplayRow(5)..DisplayRow(5),
11489 ),
11490 (
11491 "nnnn\n".to_string(),
11492 DiffHunkStatus::Modified,
11493 DisplayRow(23)..DisplayRow(24),
11494 ),
11495 (
11496 "".to_string(),
11497 DiffHunkStatus::Added,
11498 DisplayRow(43)..DisplayRow(44),
11499 ),
11500 ];
11501
11502 multi_buffer_editor.update(cx, |editor, cx| {
11503 let snapshot = editor.snapshot(cx);
11504 let all_hunks = editor_hunks(editor, &snapshot, cx);
11505 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11506 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11507 assert_eq!(all_hunks, expected_all_hunks);
11508 assert_eq!(all_expanded_hunks, Vec::new());
11509 });
11510
11511 multi_buffer_editor.update(cx, |editor, cx| {
11512 editor.select_all(&SelectAll, cx);
11513 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11514 });
11515 cx.executor().run_until_parked();
11516 multi_buffer_editor.update(cx, |editor, cx| {
11517 let snapshot = editor.snapshot(cx);
11518 let all_hunks = editor_hunks(editor, &snapshot, cx);
11519 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11520 assert_eq!(
11521 expanded_hunks_background_highlights(editor, cx),
11522 vec![
11523 DisplayRow(23)..=DisplayRow(23),
11524 DisplayRow(43)..=DisplayRow(43)
11525 ],
11526 );
11527 assert_eq!(all_hunks, expected_all_hunks_shifted);
11528 assert_eq!(all_hunks, all_expanded_hunks);
11529 });
11530
11531 multi_buffer_editor.update(cx, |editor, cx| {
11532 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11533 });
11534 cx.executor().run_until_parked();
11535 multi_buffer_editor.update(cx, |editor, cx| {
11536 let snapshot = editor.snapshot(cx);
11537 let all_hunks = editor_hunks(editor, &snapshot, cx);
11538 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11539 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11540 assert_eq!(all_hunks, expected_all_hunks);
11541 assert_eq!(all_expanded_hunks, Vec::new());
11542 });
11543
11544 multi_buffer_editor.update(cx, |editor, cx| {
11545 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11546 });
11547 cx.executor().run_until_parked();
11548 multi_buffer_editor.update(cx, |editor, cx| {
11549 let snapshot = editor.snapshot(cx);
11550 let all_hunks = editor_hunks(editor, &snapshot, cx);
11551 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11552 assert_eq!(
11553 expanded_hunks_background_highlights(editor, cx),
11554 vec![
11555 DisplayRow(23)..=DisplayRow(23),
11556 DisplayRow(43)..=DisplayRow(43)
11557 ],
11558 );
11559 assert_eq!(all_hunks, expected_all_hunks_shifted);
11560 assert_eq!(all_hunks, all_expanded_hunks);
11561 });
11562
11563 multi_buffer_editor.update(cx, |editor, cx| {
11564 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11565 });
11566 cx.executor().run_until_parked();
11567 multi_buffer_editor.update(cx, |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!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11572 assert_eq!(all_hunks, expected_all_hunks);
11573 assert_eq!(all_expanded_hunks, Vec::new());
11574 });
11575}
11576
11577#[gpui::test]
11578async fn test_edits_around_toggled_additions(
11579 executor: BackgroundExecutor,
11580 cx: &mut gpui::TestAppContext,
11581) {
11582 init_test(cx, |_| {});
11583
11584 let mut cx = EditorTestContext::new(cx).await;
11585
11586 let diff_base = r#"
11587 use some::mod1;
11588 use some::mod2;
11589
11590 const A: u32 = 42;
11591
11592 fn main() {
11593 println!("hello");
11594
11595 println!("world");
11596 }
11597 "#
11598 .unindent();
11599 executor.run_until_parked();
11600 cx.set_state(
11601 &r#"
11602 use some::mod1;
11603 use some::mod2;
11604
11605 const A: u32 = 42;
11606 const B: u32 = 42;
11607 const C: u32 = 42;
11608 ˇ
11609
11610 fn main() {
11611 println!("hello");
11612
11613 println!("world");
11614 }
11615 "#
11616 .unindent(),
11617 );
11618
11619 cx.set_diff_base(Some(&diff_base));
11620 executor.run_until_parked();
11621 cx.update_editor(|editor, cx| {
11622 let snapshot = editor.snapshot(cx);
11623 let all_hunks = editor_hunks(editor, &snapshot, cx);
11624 assert_eq!(
11625 all_hunks,
11626 vec![(
11627 "".to_string(),
11628 DiffHunkStatus::Added,
11629 DisplayRow(4)..DisplayRow(7)
11630 )]
11631 );
11632 });
11633 cx.update_editor(|editor, cx| {
11634 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11635 });
11636 executor.run_until_parked();
11637 cx.assert_editor_state(
11638 &r#"
11639 use some::mod1;
11640 use some::mod2;
11641
11642 const A: u32 = 42;
11643 const B: u32 = 42;
11644 const C: u32 = 42;
11645 ˇ
11646
11647 fn main() {
11648 println!("hello");
11649
11650 println!("world");
11651 }
11652 "#
11653 .unindent(),
11654 );
11655 cx.update_editor(|editor, cx| {
11656 let snapshot = editor.snapshot(cx);
11657 let all_hunks = editor_hunks(editor, &snapshot, cx);
11658 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11659 assert_eq!(
11660 all_hunks,
11661 vec![(
11662 "".to_string(),
11663 DiffHunkStatus::Added,
11664 DisplayRow(4)..DisplayRow(7)
11665 )]
11666 );
11667 assert_eq!(
11668 expanded_hunks_background_highlights(editor, cx),
11669 vec![DisplayRow(4)..=DisplayRow(6)]
11670 );
11671 assert_eq!(all_hunks, all_expanded_hunks);
11672 });
11673
11674 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
11675 executor.run_until_parked();
11676 cx.assert_editor_state(
11677 &r#"
11678 use some::mod1;
11679 use some::mod2;
11680
11681 const A: u32 = 42;
11682 const B: u32 = 42;
11683 const C: u32 = 42;
11684 const D: u32 = 42;
11685 ˇ
11686
11687 fn main() {
11688 println!("hello");
11689
11690 println!("world");
11691 }
11692 "#
11693 .unindent(),
11694 );
11695 cx.update_editor(|editor, cx| {
11696 let snapshot = editor.snapshot(cx);
11697 let all_hunks = editor_hunks(editor, &snapshot, cx);
11698 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11699 assert_eq!(
11700 all_hunks,
11701 vec![(
11702 "".to_string(),
11703 DiffHunkStatus::Added,
11704 DisplayRow(4)..DisplayRow(8)
11705 )]
11706 );
11707 assert_eq!(
11708 expanded_hunks_background_highlights(editor, cx),
11709 vec![DisplayRow(4)..=DisplayRow(6)],
11710 "Edited hunk should have one more line added"
11711 );
11712 assert_eq!(
11713 all_hunks, all_expanded_hunks,
11714 "Expanded hunk should also grow with the addition"
11715 );
11716 });
11717
11718 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
11719 executor.run_until_parked();
11720 cx.assert_editor_state(
11721 &r#"
11722 use some::mod1;
11723 use some::mod2;
11724
11725 const A: u32 = 42;
11726 const B: u32 = 42;
11727 const C: u32 = 42;
11728 const D: u32 = 42;
11729 const E: u32 = 42;
11730 ˇ
11731
11732 fn main() {
11733 println!("hello");
11734
11735 println!("world");
11736 }
11737 "#
11738 .unindent(),
11739 );
11740 cx.update_editor(|editor, cx| {
11741 let snapshot = editor.snapshot(cx);
11742 let all_hunks = editor_hunks(editor, &snapshot, cx);
11743 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11744 assert_eq!(
11745 all_hunks,
11746 vec![(
11747 "".to_string(),
11748 DiffHunkStatus::Added,
11749 DisplayRow(4)..DisplayRow(9)
11750 )]
11751 );
11752 assert_eq!(
11753 expanded_hunks_background_highlights(editor, cx),
11754 vec![DisplayRow(4)..=DisplayRow(6)],
11755 "Edited hunk should have one more line added"
11756 );
11757 assert_eq!(all_hunks, all_expanded_hunks);
11758 });
11759
11760 cx.update_editor(|editor, cx| {
11761 editor.move_up(&MoveUp, cx);
11762 editor.delete_line(&DeleteLine, cx);
11763 });
11764 executor.run_until_parked();
11765 cx.assert_editor_state(
11766 &r#"
11767 use some::mod1;
11768 use some::mod2;
11769
11770 const A: u32 = 42;
11771 const B: u32 = 42;
11772 const C: u32 = 42;
11773 const D: u32 = 42;
11774 ˇ
11775
11776 fn main() {
11777 println!("hello");
11778
11779 println!("world");
11780 }
11781 "#
11782 .unindent(),
11783 );
11784 cx.update_editor(|editor, cx| {
11785 let snapshot = editor.snapshot(cx);
11786 let all_hunks = editor_hunks(editor, &snapshot, cx);
11787 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11788 assert_eq!(
11789 all_hunks,
11790 vec![(
11791 "".to_string(),
11792 DiffHunkStatus::Added,
11793 DisplayRow(4)..DisplayRow(8)
11794 )]
11795 );
11796 assert_eq!(
11797 expanded_hunks_background_highlights(editor, cx),
11798 vec![DisplayRow(4)..=DisplayRow(6)],
11799 "Deleting a line should shrint the hunk"
11800 );
11801 assert_eq!(
11802 all_hunks, all_expanded_hunks,
11803 "Expanded hunk should also shrink with the addition"
11804 );
11805 });
11806
11807 cx.update_editor(|editor, cx| {
11808 editor.move_up(&MoveUp, cx);
11809 editor.delete_line(&DeleteLine, cx);
11810 editor.move_up(&MoveUp, cx);
11811 editor.delete_line(&DeleteLine, cx);
11812 editor.move_up(&MoveUp, cx);
11813 editor.delete_line(&DeleteLine, cx);
11814 });
11815 executor.run_until_parked();
11816 cx.assert_editor_state(
11817 &r#"
11818 use some::mod1;
11819 use some::mod2;
11820
11821 const A: u32 = 42;
11822 ˇ
11823
11824 fn main() {
11825 println!("hello");
11826
11827 println!("world");
11828 }
11829 "#
11830 .unindent(),
11831 );
11832 cx.update_editor(|editor, cx| {
11833 let snapshot = editor.snapshot(cx);
11834 let all_hunks = editor_hunks(editor, &snapshot, cx);
11835 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11836 assert_eq!(
11837 all_hunks,
11838 vec![(
11839 "".to_string(),
11840 DiffHunkStatus::Added,
11841 DisplayRow(5)..DisplayRow(6)
11842 )]
11843 );
11844 assert_eq!(
11845 expanded_hunks_background_highlights(editor, cx),
11846 vec![DisplayRow(5)..=DisplayRow(5)]
11847 );
11848 assert_eq!(all_hunks, all_expanded_hunks);
11849 });
11850
11851 cx.update_editor(|editor, cx| {
11852 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
11853 editor.delete_line(&DeleteLine, cx);
11854 });
11855 executor.run_until_parked();
11856 cx.assert_editor_state(
11857 &r#"
11858 ˇ
11859
11860 fn main() {
11861 println!("hello");
11862
11863 println!("world");
11864 }
11865 "#
11866 .unindent(),
11867 );
11868 cx.update_editor(|editor, cx| {
11869 let snapshot = editor.snapshot(cx);
11870 let all_hunks = editor_hunks(editor, &snapshot, cx);
11871 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11872 assert_eq!(
11873 all_hunks,
11874 vec![
11875 (
11876 "use some::mod1;\nuse some::mod2;\n".to_string(),
11877 DiffHunkStatus::Removed,
11878 DisplayRow(0)..DisplayRow(0)
11879 ),
11880 (
11881 "const A: u32 = 42;\n".to_string(),
11882 DiffHunkStatus::Removed,
11883 DisplayRow(2)..DisplayRow(2)
11884 )
11885 ]
11886 );
11887 assert_eq!(
11888 expanded_hunks_background_highlights(editor, cx),
11889 Vec::new(),
11890 "Should close all stale expanded addition hunks"
11891 );
11892 assert_eq!(
11893 all_expanded_hunks,
11894 vec![(
11895 "const A: u32 = 42;\n".to_string(),
11896 DiffHunkStatus::Removed,
11897 DisplayRow(2)..DisplayRow(2)
11898 )],
11899 "Should open hunks that were adjacent to the stale addition one"
11900 );
11901 });
11902}
11903
11904#[gpui::test]
11905async fn test_edits_around_toggled_deletions(
11906 executor: BackgroundExecutor,
11907 cx: &mut gpui::TestAppContext,
11908) {
11909 init_test(cx, |_| {});
11910
11911 let mut cx = EditorTestContext::new(cx).await;
11912
11913 let diff_base = r#"
11914 use some::mod1;
11915 use some::mod2;
11916
11917 const A: u32 = 42;
11918 const B: u32 = 42;
11919 const C: u32 = 42;
11920
11921
11922 fn main() {
11923 println!("hello");
11924
11925 println!("world");
11926 }
11927 "#
11928 .unindent();
11929 executor.run_until_parked();
11930 cx.set_state(
11931 &r#"
11932 use some::mod1;
11933 use some::mod2;
11934
11935 ˇconst B: u32 = 42;
11936 const C: u32 = 42;
11937
11938
11939 fn main() {
11940 println!("hello");
11941
11942 println!("world");
11943 }
11944 "#
11945 .unindent(),
11946 );
11947
11948 cx.set_diff_base(Some(&diff_base));
11949 executor.run_until_parked();
11950 cx.update_editor(|editor, cx| {
11951 let snapshot = editor.snapshot(cx);
11952 let all_hunks = editor_hunks(editor, &snapshot, cx);
11953 assert_eq!(
11954 all_hunks,
11955 vec![(
11956 "const A: u32 = 42;\n".to_string(),
11957 DiffHunkStatus::Removed,
11958 DisplayRow(3)..DisplayRow(3)
11959 )]
11960 );
11961 });
11962 cx.update_editor(|editor, cx| {
11963 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11964 });
11965 executor.run_until_parked();
11966 cx.assert_editor_state(
11967 &r#"
11968 use some::mod1;
11969 use some::mod2;
11970
11971 ˇconst B: u32 = 42;
11972 const C: u32 = 42;
11973
11974
11975 fn main() {
11976 println!("hello");
11977
11978 println!("world");
11979 }
11980 "#
11981 .unindent(),
11982 );
11983 cx.update_editor(|editor, cx| {
11984 let snapshot = editor.snapshot(cx);
11985 let all_hunks = editor_hunks(editor, &snapshot, cx);
11986 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11987 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11988 assert_eq!(
11989 all_hunks,
11990 vec![(
11991 "const A: u32 = 42;\n".to_string(),
11992 DiffHunkStatus::Removed,
11993 DisplayRow(4)..DisplayRow(4)
11994 )]
11995 );
11996 assert_eq!(all_hunks, all_expanded_hunks);
11997 });
11998
11999 cx.update_editor(|editor, cx| {
12000 editor.delete_line(&DeleteLine, cx);
12001 });
12002 executor.run_until_parked();
12003 cx.assert_editor_state(
12004 &r#"
12005 use some::mod1;
12006 use some::mod2;
12007
12008 ˇconst C: u32 = 42;
12009
12010
12011 fn main() {
12012 println!("hello");
12013
12014 println!("world");
12015 }
12016 "#
12017 .unindent(),
12018 );
12019 cx.update_editor(|editor, cx| {
12020 let snapshot = editor.snapshot(cx);
12021 let all_hunks = editor_hunks(editor, &snapshot, cx);
12022 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12023 assert_eq!(
12024 expanded_hunks_background_highlights(editor, cx),
12025 Vec::new(),
12026 "Deleted hunks do not highlight current editor's background"
12027 );
12028 assert_eq!(
12029 all_hunks,
12030 vec![(
12031 "const A: u32 = 42;\nconst B: u32 = 42;\n".to_string(),
12032 DiffHunkStatus::Removed,
12033 DisplayRow(5)..DisplayRow(5)
12034 )]
12035 );
12036 assert_eq!(all_hunks, all_expanded_hunks);
12037 });
12038
12039 cx.update_editor(|editor, cx| {
12040 editor.delete_line(&DeleteLine, cx);
12041 });
12042 executor.run_until_parked();
12043 cx.assert_editor_state(
12044 &r#"
12045 use some::mod1;
12046 use some::mod2;
12047
12048 ˇ
12049
12050 fn main() {
12051 println!("hello");
12052
12053 println!("world");
12054 }
12055 "#
12056 .unindent(),
12057 );
12058 cx.update_editor(|editor, cx| {
12059 let snapshot = editor.snapshot(cx);
12060 let all_hunks = editor_hunks(editor, &snapshot, cx);
12061 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12062 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
12063 assert_eq!(
12064 all_hunks,
12065 vec![(
12066 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12067 DiffHunkStatus::Removed,
12068 DisplayRow(6)..DisplayRow(6)
12069 )]
12070 );
12071 assert_eq!(all_hunks, all_expanded_hunks);
12072 });
12073
12074 cx.update_editor(|editor, cx| {
12075 editor.handle_input("replacement", cx);
12076 });
12077 executor.run_until_parked();
12078 cx.assert_editor_state(
12079 &r#"
12080 use some::mod1;
12081 use some::mod2;
12082
12083 replacementˇ
12084
12085 fn main() {
12086 println!("hello");
12087
12088 println!("world");
12089 }
12090 "#
12091 .unindent(),
12092 );
12093 cx.update_editor(|editor, cx| {
12094 let snapshot = editor.snapshot(cx);
12095 let all_hunks = editor_hunks(editor, &snapshot, cx);
12096 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12097 assert_eq!(
12098 all_hunks,
12099 vec![(
12100 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n\n".to_string(),
12101 DiffHunkStatus::Modified,
12102 DisplayRow(7)..DisplayRow(8)
12103 )]
12104 );
12105 assert_eq!(
12106 expanded_hunks_background_highlights(editor, cx),
12107 vec![DisplayRow(7)..=DisplayRow(7)],
12108 "Modified expanded hunks should display additions and highlight their background"
12109 );
12110 assert_eq!(all_hunks, all_expanded_hunks);
12111 });
12112}
12113
12114#[gpui::test]
12115async fn test_edits_around_toggled_modifications(
12116 executor: BackgroundExecutor,
12117 cx: &mut gpui::TestAppContext,
12118) {
12119 init_test(cx, |_| {});
12120
12121 let mut cx = EditorTestContext::new(cx).await;
12122
12123 let diff_base = r#"
12124 use some::mod1;
12125 use some::mod2;
12126
12127 const A: u32 = 42;
12128 const B: u32 = 42;
12129 const C: u32 = 42;
12130 const D: u32 = 42;
12131
12132
12133 fn main() {
12134 println!("hello");
12135
12136 println!("world");
12137 }"#
12138 .unindent();
12139 executor.run_until_parked();
12140 cx.set_state(
12141 &r#"
12142 use some::mod1;
12143 use some::mod2;
12144
12145 const A: u32 = 42;
12146 const B: u32 = 42;
12147 const C: u32 = 43ˇ
12148 const D: u32 = 42;
12149
12150
12151 fn main() {
12152 println!("hello");
12153
12154 println!("world");
12155 }"#
12156 .unindent(),
12157 );
12158
12159 cx.set_diff_base(Some(&diff_base));
12160 executor.run_until_parked();
12161 cx.update_editor(|editor, cx| {
12162 let snapshot = editor.snapshot(cx);
12163 let all_hunks = editor_hunks(editor, &snapshot, cx);
12164 assert_eq!(
12165 all_hunks,
12166 vec![(
12167 "const C: u32 = 42;\n".to_string(),
12168 DiffHunkStatus::Modified,
12169 DisplayRow(5)..DisplayRow(6)
12170 )]
12171 );
12172 });
12173 cx.update_editor(|editor, cx| {
12174 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, 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 B: u32 = 42;
12184 const C: u32 = 43ˇ
12185 const D: u32 = 42;
12186
12187
12188 fn main() {
12189 println!("hello");
12190
12191 println!("world");
12192 }"#
12193 .unindent(),
12194 );
12195 cx.update_editor(|editor, cx| {
12196 let snapshot = editor.snapshot(cx);
12197 let all_hunks = editor_hunks(editor, &snapshot, cx);
12198 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12199 assert_eq!(
12200 expanded_hunks_background_highlights(editor, cx),
12201 vec![DisplayRow(6)..=DisplayRow(6)],
12202 );
12203 assert_eq!(
12204 all_hunks,
12205 vec![(
12206 "const C: u32 = 42;\n".to_string(),
12207 DiffHunkStatus::Modified,
12208 DisplayRow(6)..DisplayRow(7)
12209 )]
12210 );
12211 assert_eq!(all_hunks, all_expanded_hunks);
12212 });
12213
12214 cx.update_editor(|editor, cx| {
12215 editor.handle_input("\nnew_line\n", cx);
12216 });
12217 executor.run_until_parked();
12218 cx.assert_editor_state(
12219 &r#"
12220 use some::mod1;
12221 use some::mod2;
12222
12223 const A: u32 = 42;
12224 const B: u32 = 42;
12225 const C: u32 = 43
12226 new_line
12227 ˇ
12228 const D: u32 = 42;
12229
12230
12231 fn main() {
12232 println!("hello");
12233
12234 println!("world");
12235 }"#
12236 .unindent(),
12237 );
12238 cx.update_editor(|editor, cx| {
12239 let snapshot = editor.snapshot(cx);
12240 let all_hunks = editor_hunks(editor, &snapshot, cx);
12241 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12242 assert_eq!(
12243 expanded_hunks_background_highlights(editor, cx),
12244 vec![DisplayRow(6)..=DisplayRow(6)],
12245 "Modified hunk should grow highlighted lines on more text additions"
12246 );
12247 assert_eq!(
12248 all_hunks,
12249 vec![(
12250 "const C: u32 = 42;\n".to_string(),
12251 DiffHunkStatus::Modified,
12252 DisplayRow(6)..DisplayRow(9)
12253 )]
12254 );
12255 assert_eq!(all_hunks, all_expanded_hunks);
12256 });
12257
12258 cx.update_editor(|editor, cx| {
12259 editor.move_up(&MoveUp, cx);
12260 editor.move_up(&MoveUp, cx);
12261 editor.move_up(&MoveUp, cx);
12262 editor.delete_line(&DeleteLine, cx);
12263 });
12264 executor.run_until_parked();
12265 cx.assert_editor_state(
12266 &r#"
12267 use some::mod1;
12268 use some::mod2;
12269
12270 const A: u32 = 42;
12271 ˇconst C: u32 = 43
12272 new_line
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 );
12292 assert_eq!(
12293 all_hunks,
12294 vec![(
12295 "const B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12296 DiffHunkStatus::Modified,
12297 DisplayRow(6)..DisplayRow(9)
12298 )],
12299 "Modified hunk should grow deleted lines on text deletions above"
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.handle_input("v", cx);
12307 });
12308 executor.run_until_parked();
12309 cx.assert_editor_state(
12310 &r#"
12311 use some::mod1;
12312 use some::mod2;
12313
12314 vˇconst A: u32 = 42;
12315 const C: u32 = 43
12316 new_line
12317
12318 const D: u32 = 42;
12319
12320
12321 fn main() {
12322 println!("hello");
12323
12324 println!("world");
12325 }"#
12326 .unindent(),
12327 );
12328 cx.update_editor(|editor, cx| {
12329 let snapshot = editor.snapshot(cx);
12330 let all_hunks = editor_hunks(editor, &snapshot, cx);
12331 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12332 assert_eq!(
12333 expanded_hunks_background_highlights(editor, cx),
12334 vec![DisplayRow(6)..=DisplayRow(9)],
12335 "Modified hunk should grow deleted lines on text modifications above"
12336 );
12337 assert_eq!(
12338 all_hunks,
12339 vec![(
12340 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12341 DiffHunkStatus::Modified,
12342 DisplayRow(6)..DisplayRow(10)
12343 )]
12344 );
12345 assert_eq!(all_hunks, all_expanded_hunks);
12346 });
12347
12348 cx.update_editor(|editor, cx| {
12349 editor.move_down(&MoveDown, cx);
12350 editor.move_down(&MoveDown, cx);
12351 editor.delete_line(&DeleteLine, cx)
12352 });
12353 executor.run_until_parked();
12354 cx.assert_editor_state(
12355 &r#"
12356 use some::mod1;
12357 use some::mod2;
12358
12359 vconst A: u32 = 42;
12360 const C: u32 = 43
12361 ˇ
12362 const D: u32 = 42;
12363
12364
12365 fn main() {
12366 println!("hello");
12367
12368 println!("world");
12369 }"#
12370 .unindent(),
12371 );
12372 cx.update_editor(|editor, cx| {
12373 let snapshot = editor.snapshot(cx);
12374 let all_hunks = editor_hunks(editor, &snapshot, cx);
12375 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12376 assert_eq!(
12377 expanded_hunks_background_highlights(editor, cx),
12378 vec![DisplayRow(6)..=DisplayRow(8)],
12379 "Modified hunk should grow shrink lines on modification lines removal"
12380 );
12381 assert_eq!(
12382 all_hunks,
12383 vec![(
12384 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12385 DiffHunkStatus::Modified,
12386 DisplayRow(6)..DisplayRow(9)
12387 )]
12388 );
12389 assert_eq!(all_hunks, all_expanded_hunks);
12390 });
12391
12392 cx.update_editor(|editor, cx| {
12393 editor.move_up(&MoveUp, cx);
12394 editor.move_up(&MoveUp, cx);
12395 editor.select_down_by_lines(&SelectDownByLines { lines: 4 }, cx);
12396 editor.delete_line(&DeleteLine, cx)
12397 });
12398 executor.run_until_parked();
12399 cx.assert_editor_state(
12400 &r#"
12401 use some::mod1;
12402 use some::mod2;
12403
12404 ˇ
12405
12406 fn main() {
12407 println!("hello");
12408
12409 println!("world");
12410 }"#
12411 .unindent(),
12412 );
12413 cx.update_editor(|editor, cx| {
12414 let snapshot = editor.snapshot(cx);
12415 let all_hunks = editor_hunks(editor, &snapshot, cx);
12416 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12417 assert_eq!(
12418 expanded_hunks_background_highlights(editor, cx),
12419 Vec::new(),
12420 "Modified hunk should turn into a removed one on all modified lines removal"
12421 );
12422 assert_eq!(
12423 all_hunks,
12424 vec![(
12425 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\nconst D: u32 = 42;\n"
12426 .to_string(),
12427 DiffHunkStatus::Removed,
12428 DisplayRow(7)..DisplayRow(7)
12429 )]
12430 );
12431 assert_eq!(all_hunks, all_expanded_hunks);
12432 });
12433}
12434
12435#[gpui::test]
12436async fn test_multiple_expanded_hunks_merge(
12437 executor: BackgroundExecutor,
12438 cx: &mut gpui::TestAppContext,
12439) {
12440 init_test(cx, |_| {});
12441
12442 let mut cx = EditorTestContext::new(cx).await;
12443
12444 let diff_base = r#"
12445 use some::mod1;
12446 use some::mod2;
12447
12448 const A: u32 = 42;
12449 const B: u32 = 42;
12450 const C: u32 = 42;
12451 const D: u32 = 42;
12452
12453
12454 fn main() {
12455 println!("hello");
12456
12457 println!("world");
12458 }"#
12459 .unindent();
12460 executor.run_until_parked();
12461 cx.set_state(
12462 &r#"
12463 use some::mod1;
12464 use some::mod2;
12465
12466 const A: u32 = 42;
12467 const B: u32 = 42;
12468 const C: u32 = 43ˇ
12469 const D: u32 = 42;
12470
12471
12472 fn main() {
12473 println!("hello");
12474
12475 println!("world");
12476 }"#
12477 .unindent(),
12478 );
12479
12480 cx.set_diff_base(Some(&diff_base));
12481 executor.run_until_parked();
12482 cx.update_editor(|editor, cx| {
12483 let snapshot = editor.snapshot(cx);
12484 let all_hunks = editor_hunks(editor, &snapshot, cx);
12485 assert_eq!(
12486 all_hunks,
12487 vec![(
12488 "const C: u32 = 42;\n".to_string(),
12489 DiffHunkStatus::Modified,
12490 DisplayRow(5)..DisplayRow(6)
12491 )]
12492 );
12493 });
12494 cx.update_editor(|editor, cx| {
12495 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12496 });
12497 executor.run_until_parked();
12498 cx.assert_editor_state(
12499 &r#"
12500 use some::mod1;
12501 use some::mod2;
12502
12503 const A: u32 = 42;
12504 const B: u32 = 42;
12505 const C: u32 = 43ˇ
12506 const D: u32 = 42;
12507
12508
12509 fn main() {
12510 println!("hello");
12511
12512 println!("world");
12513 }"#
12514 .unindent(),
12515 );
12516 cx.update_editor(|editor, cx| {
12517 let snapshot = editor.snapshot(cx);
12518 let all_hunks = editor_hunks(editor, &snapshot, cx);
12519 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12520 assert_eq!(
12521 expanded_hunks_background_highlights(editor, cx),
12522 vec![DisplayRow(6)..=DisplayRow(6)],
12523 );
12524 assert_eq!(
12525 all_hunks,
12526 vec![(
12527 "const C: u32 = 42;\n".to_string(),
12528 DiffHunkStatus::Modified,
12529 DisplayRow(6)..DisplayRow(7)
12530 )]
12531 );
12532 assert_eq!(all_hunks, all_expanded_hunks);
12533 });
12534
12535 cx.update_editor(|editor, cx| {
12536 editor.handle_input("\nnew_line\n", cx);
12537 });
12538 executor.run_until_parked();
12539 cx.assert_editor_state(
12540 &r#"
12541 use some::mod1;
12542 use some::mod2;
12543
12544 const A: u32 = 42;
12545 const B: u32 = 42;
12546 const C: u32 = 43
12547 new_line
12548 ˇ
12549 const D: u32 = 42;
12550
12551
12552 fn main() {
12553 println!("hello");
12554
12555 println!("world");
12556 }"#
12557 .unindent(),
12558 );
12559}
12560
12561async fn setup_indent_guides_editor(
12562 text: &str,
12563 cx: &mut gpui::TestAppContext,
12564) -> (BufferId, EditorTestContext) {
12565 init_test(cx, |_| {});
12566
12567 let mut cx = EditorTestContext::new(cx).await;
12568
12569 let buffer_id = cx.update_editor(|editor, cx| {
12570 editor.set_text(text, cx);
12571 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12572 let buffer_id = buffer_ids[0];
12573 buffer_id
12574 });
12575
12576 (buffer_id, cx)
12577}
12578
12579fn assert_indent_guides(
12580 range: Range<u32>,
12581 expected: Vec<IndentGuide>,
12582 active_indices: Option<Vec<usize>>,
12583 cx: &mut EditorTestContext,
12584) {
12585 let indent_guides = cx.update_editor(|editor, cx| {
12586 let snapshot = editor.snapshot(cx).display_snapshot;
12587 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12588 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12589 true,
12590 &snapshot,
12591 cx,
12592 );
12593
12594 indent_guides.sort_by(|a, b| {
12595 a.depth.cmp(&b.depth).then(
12596 a.start_row
12597 .cmp(&b.start_row)
12598 .then(a.end_row.cmp(&b.end_row)),
12599 )
12600 });
12601 indent_guides
12602 });
12603
12604 if let Some(expected) = active_indices {
12605 let active_indices = cx.update_editor(|editor, cx| {
12606 let snapshot = editor.snapshot(cx).display_snapshot;
12607 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
12608 });
12609
12610 assert_eq!(
12611 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
12612 expected,
12613 "Active indent guide indices do not match"
12614 );
12615 }
12616
12617 let expected: Vec<_> = expected
12618 .into_iter()
12619 .map(|guide| MultiBufferIndentGuide {
12620 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
12621 buffer: guide,
12622 })
12623 .collect();
12624
12625 assert_eq!(indent_guides, expected, "Indent guides do not match");
12626}
12627
12628fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
12629 IndentGuide {
12630 buffer_id,
12631 start_row,
12632 end_row,
12633 depth,
12634 tab_size: 4,
12635 settings: IndentGuideSettings {
12636 enabled: true,
12637 line_width: 1,
12638 active_line_width: 1,
12639 ..Default::default()
12640 },
12641 }
12642}
12643
12644#[gpui::test]
12645async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12646 let (buffer_id, mut cx) = setup_indent_guides_editor(
12647 &"
12648 fn main() {
12649 let a = 1;
12650 }"
12651 .unindent(),
12652 cx,
12653 )
12654 .await;
12655
12656 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12657}
12658
12659#[gpui::test]
12660async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
12661 let (buffer_id, mut cx) = setup_indent_guides_editor(
12662 &"
12663 fn main() {
12664 let a = 1;
12665 let b = 2;
12666 }"
12667 .unindent(),
12668 cx,
12669 )
12670 .await;
12671
12672 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
12673}
12674
12675#[gpui::test]
12676async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
12677 let (buffer_id, mut cx) = setup_indent_guides_editor(
12678 &"
12679 fn main() {
12680 let a = 1;
12681 if a == 3 {
12682 let b = 2;
12683 } else {
12684 let c = 3;
12685 }
12686 }"
12687 .unindent(),
12688 cx,
12689 )
12690 .await;
12691
12692 assert_indent_guides(
12693 0..8,
12694 vec![
12695 indent_guide(buffer_id, 1, 6, 0),
12696 indent_guide(buffer_id, 3, 3, 1),
12697 indent_guide(buffer_id, 5, 5, 1),
12698 ],
12699 None,
12700 &mut cx,
12701 );
12702}
12703
12704#[gpui::test]
12705async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
12706 let (buffer_id, mut cx) = setup_indent_guides_editor(
12707 &"
12708 fn main() {
12709 let a = 1;
12710 let b = 2;
12711 let c = 3;
12712 }"
12713 .unindent(),
12714 cx,
12715 )
12716 .await;
12717
12718 assert_indent_guides(
12719 0..5,
12720 vec![
12721 indent_guide(buffer_id, 1, 3, 0),
12722 indent_guide(buffer_id, 2, 2, 1),
12723 ],
12724 None,
12725 &mut cx,
12726 );
12727}
12728
12729#[gpui::test]
12730async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
12731 let (buffer_id, mut cx) = setup_indent_guides_editor(
12732 &"
12733 fn main() {
12734 let a = 1;
12735
12736 let c = 3;
12737 }"
12738 .unindent(),
12739 cx,
12740 )
12741 .await;
12742
12743 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
12744}
12745
12746#[gpui::test]
12747async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
12748 let (buffer_id, mut cx) = setup_indent_guides_editor(
12749 &"
12750 fn main() {
12751 let a = 1;
12752
12753 let c = 3;
12754
12755 if a == 3 {
12756 let b = 2;
12757 } else {
12758 let c = 3;
12759 }
12760 }"
12761 .unindent(),
12762 cx,
12763 )
12764 .await;
12765
12766 assert_indent_guides(
12767 0..11,
12768 vec![
12769 indent_guide(buffer_id, 1, 9, 0),
12770 indent_guide(buffer_id, 6, 6, 1),
12771 indent_guide(buffer_id, 8, 8, 1),
12772 ],
12773 None,
12774 &mut cx,
12775 );
12776}
12777
12778#[gpui::test]
12779async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
12780 let (buffer_id, mut cx) = setup_indent_guides_editor(
12781 &"
12782 fn main() {
12783 let a = 1;
12784
12785 let c = 3;
12786
12787 if a == 3 {
12788 let b = 2;
12789 } else {
12790 let c = 3;
12791 }
12792 }"
12793 .unindent(),
12794 cx,
12795 )
12796 .await;
12797
12798 assert_indent_guides(
12799 1..11,
12800 vec![
12801 indent_guide(buffer_id, 1, 9, 0),
12802 indent_guide(buffer_id, 6, 6, 1),
12803 indent_guide(buffer_id, 8, 8, 1),
12804 ],
12805 None,
12806 &mut cx,
12807 );
12808}
12809
12810#[gpui::test]
12811async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
12812 let (buffer_id, mut cx) = setup_indent_guides_editor(
12813 &"
12814 fn main() {
12815 let a = 1;
12816
12817 let c = 3;
12818
12819 if a == 3 {
12820 let b = 2;
12821 } else {
12822 let c = 3;
12823 }
12824 }"
12825 .unindent(),
12826 cx,
12827 )
12828 .await;
12829
12830 assert_indent_guides(
12831 1..10,
12832 vec![
12833 indent_guide(buffer_id, 1, 9, 0),
12834 indent_guide(buffer_id, 6, 6, 1),
12835 indent_guide(buffer_id, 8, 8, 1),
12836 ],
12837 None,
12838 &mut cx,
12839 );
12840}
12841
12842#[gpui::test]
12843async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
12844 let (buffer_id, mut cx) = setup_indent_guides_editor(
12845 &"
12846 block1
12847 block2
12848 block3
12849 block4
12850 block2
12851 block1
12852 block1"
12853 .unindent(),
12854 cx,
12855 )
12856 .await;
12857
12858 assert_indent_guides(
12859 1..10,
12860 vec![
12861 indent_guide(buffer_id, 1, 4, 0),
12862 indent_guide(buffer_id, 2, 3, 1),
12863 indent_guide(buffer_id, 3, 3, 2),
12864 ],
12865 None,
12866 &mut cx,
12867 );
12868}
12869
12870#[gpui::test]
12871async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
12872 let (buffer_id, mut cx) = setup_indent_guides_editor(
12873 &"
12874 block1
12875 block2
12876 block3
12877
12878 block1
12879 block1"
12880 .unindent(),
12881 cx,
12882 )
12883 .await;
12884
12885 assert_indent_guides(
12886 0..6,
12887 vec![
12888 indent_guide(buffer_id, 1, 2, 0),
12889 indent_guide(buffer_id, 2, 2, 1),
12890 ],
12891 None,
12892 &mut cx,
12893 );
12894}
12895
12896#[gpui::test]
12897async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
12898 let (buffer_id, mut cx) = setup_indent_guides_editor(
12899 &"
12900 block1
12901
12902
12903
12904 block2
12905 "
12906 .unindent(),
12907 cx,
12908 )
12909 .await;
12910
12911 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12912}
12913
12914#[gpui::test]
12915async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
12916 let (buffer_id, mut cx) = setup_indent_guides_editor(
12917 &"
12918 def a:
12919 \tb = 3
12920 \tif True:
12921 \t\tc = 4
12922 \t\td = 5
12923 \tprint(b)
12924 "
12925 .unindent(),
12926 cx,
12927 )
12928 .await;
12929
12930 assert_indent_guides(
12931 0..6,
12932 vec![
12933 indent_guide(buffer_id, 1, 6, 0),
12934 indent_guide(buffer_id, 3, 4, 1),
12935 ],
12936 None,
12937 &mut cx,
12938 );
12939}
12940
12941#[gpui::test]
12942async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12943 let (buffer_id, mut cx) = setup_indent_guides_editor(
12944 &"
12945 fn main() {
12946 let a = 1;
12947 }"
12948 .unindent(),
12949 cx,
12950 )
12951 .await;
12952
12953 cx.update_editor(|editor, cx| {
12954 editor.change_selections(None, cx, |s| {
12955 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12956 });
12957 });
12958
12959 assert_indent_guides(
12960 0..3,
12961 vec![indent_guide(buffer_id, 1, 1, 0)],
12962 Some(vec![0]),
12963 &mut cx,
12964 );
12965}
12966
12967#[gpui::test]
12968async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
12969 let (buffer_id, mut cx) = setup_indent_guides_editor(
12970 &"
12971 fn main() {
12972 if 1 == 2 {
12973 let a = 1;
12974 }
12975 }"
12976 .unindent(),
12977 cx,
12978 )
12979 .await;
12980
12981 cx.update_editor(|editor, cx| {
12982 editor.change_selections(None, cx, |s| {
12983 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12984 });
12985 });
12986
12987 assert_indent_guides(
12988 0..4,
12989 vec![
12990 indent_guide(buffer_id, 1, 3, 0),
12991 indent_guide(buffer_id, 2, 2, 1),
12992 ],
12993 Some(vec![1]),
12994 &mut cx,
12995 );
12996
12997 cx.update_editor(|editor, cx| {
12998 editor.change_selections(None, cx, |s| {
12999 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13000 });
13001 });
13002
13003 assert_indent_guides(
13004 0..4,
13005 vec![
13006 indent_guide(buffer_id, 1, 3, 0),
13007 indent_guide(buffer_id, 2, 2, 1),
13008 ],
13009 Some(vec![1]),
13010 &mut cx,
13011 );
13012
13013 cx.update_editor(|editor, cx| {
13014 editor.change_selections(None, cx, |s| {
13015 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13016 });
13017 });
13018
13019 assert_indent_guides(
13020 0..4,
13021 vec![
13022 indent_guide(buffer_id, 1, 3, 0),
13023 indent_guide(buffer_id, 2, 2, 1),
13024 ],
13025 Some(vec![0]),
13026 &mut cx,
13027 );
13028}
13029
13030#[gpui::test]
13031async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13032 let (buffer_id, mut cx) = setup_indent_guides_editor(
13033 &"
13034 fn main() {
13035 let a = 1;
13036
13037 let b = 2;
13038 }"
13039 .unindent(),
13040 cx,
13041 )
13042 .await;
13043
13044 cx.update_editor(|editor, cx| {
13045 editor.change_selections(None, cx, |s| {
13046 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13047 });
13048 });
13049
13050 assert_indent_guides(
13051 0..5,
13052 vec![indent_guide(buffer_id, 1, 3, 0)],
13053 Some(vec![0]),
13054 &mut cx,
13055 );
13056}
13057
13058#[gpui::test]
13059async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13060 let (buffer_id, mut cx) = setup_indent_guides_editor(
13061 &"
13062 def m:
13063 a = 1
13064 pass"
13065 .unindent(),
13066 cx,
13067 )
13068 .await;
13069
13070 cx.update_editor(|editor, cx| {
13071 editor.change_selections(None, cx, |s| {
13072 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13073 });
13074 });
13075
13076 assert_indent_guides(
13077 0..3,
13078 vec![indent_guide(buffer_id, 1, 2, 0)],
13079 Some(vec![0]),
13080 &mut cx,
13081 );
13082}
13083
13084#[gpui::test]
13085fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13086 init_test(cx, |_| {});
13087
13088 let editor = cx.add_window(|cx| {
13089 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13090 build_editor(buffer, cx)
13091 });
13092
13093 let render_args = Arc::new(Mutex::new(None));
13094 let snapshot = editor
13095 .update(cx, |editor, cx| {
13096 let snapshot = editor.buffer().read(cx).snapshot(cx);
13097 let range =
13098 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13099
13100 struct RenderArgs {
13101 row: MultiBufferRow,
13102 folded: bool,
13103 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13104 }
13105
13106 let crease = Crease::new(
13107 range,
13108 FoldPlaceholder::test(),
13109 {
13110 let toggle_callback = render_args.clone();
13111 move |row, folded, callback, _cx| {
13112 *toggle_callback.lock() = Some(RenderArgs {
13113 row,
13114 folded,
13115 callback,
13116 });
13117 div()
13118 }
13119 },
13120 |_row, _folded, _cx| div(),
13121 );
13122
13123 editor.insert_creases(Some(crease), cx);
13124 let snapshot = editor.snapshot(cx);
13125 let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13126 snapshot
13127 })
13128 .unwrap();
13129
13130 let render_args = render_args.lock().take().unwrap();
13131 assert_eq!(render_args.row, MultiBufferRow(1));
13132 assert_eq!(render_args.folded, false);
13133 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13134
13135 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13136 .unwrap();
13137 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13138 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13139
13140 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13141 .unwrap();
13142 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13143 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13144}
13145
13146#[gpui::test]
13147async fn test_input_text(cx: &mut gpui::TestAppContext) {
13148 init_test(cx, |_| {});
13149 let mut cx = EditorTestContext::new(cx).await;
13150
13151 cx.set_state(
13152 &r#"ˇone
13153 two
13154
13155 three
13156 fourˇ
13157 five
13158
13159 siˇx"#
13160 .unindent(),
13161 );
13162
13163 cx.dispatch_action(HandleInput(String::new()));
13164 cx.assert_editor_state(
13165 &r#"ˇone
13166 two
13167
13168 three
13169 fourˇ
13170 five
13171
13172 siˇx"#
13173 .unindent(),
13174 );
13175
13176 cx.dispatch_action(HandleInput("AAAA".to_string()));
13177 cx.assert_editor_state(
13178 &r#"AAAAˇone
13179 two
13180
13181 three
13182 fourAAAAˇ
13183 five
13184
13185 siAAAAˇx"#
13186 .unindent(),
13187 );
13188}
13189
13190#[gpui::test]
13191async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13192 init_test(cx, |_| {});
13193
13194 let mut cx = EditorTestContext::new(cx).await;
13195 cx.set_state(
13196 r#"let foo = 1;
13197let foo = 2;
13198let foo = 3;
13199let fooˇ = 4;
13200let foo = 5;
13201let foo = 6;
13202let foo = 7;
13203let foo = 8;
13204let foo = 9;
13205let foo = 10;
13206let foo = 11;
13207let foo = 12;
13208let foo = 13;
13209let foo = 14;
13210let foo = 15;"#,
13211 );
13212
13213 cx.update_editor(|e, cx| {
13214 assert_eq!(
13215 e.next_scroll_position,
13216 NextScrollCursorCenterTopBottom::Center,
13217 "Default next scroll direction is center",
13218 );
13219
13220 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13221 assert_eq!(
13222 e.next_scroll_position,
13223 NextScrollCursorCenterTopBottom::Top,
13224 "After center, next scroll direction should be top",
13225 );
13226
13227 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13228 assert_eq!(
13229 e.next_scroll_position,
13230 NextScrollCursorCenterTopBottom::Bottom,
13231 "After top, next scroll direction should be bottom",
13232 );
13233
13234 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13235 assert_eq!(
13236 e.next_scroll_position,
13237 NextScrollCursorCenterTopBottom::Center,
13238 "After bottom, scrolling should start over",
13239 );
13240
13241 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13242 assert_eq!(
13243 e.next_scroll_position,
13244 NextScrollCursorCenterTopBottom::Top,
13245 "Scrolling continues if retriggered fast enough"
13246 );
13247 });
13248
13249 cx.executor()
13250 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13251 cx.executor().run_until_parked();
13252 cx.update_editor(|e, _| {
13253 assert_eq!(
13254 e.next_scroll_position,
13255 NextScrollCursorCenterTopBottom::Center,
13256 "If scrolling is not triggered fast enough, it should reset"
13257 );
13258 });
13259}
13260
13261#[gpui::test]
13262async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13263 init_test(cx, |_| {});
13264 let mut cx = EditorLspTestContext::new_rust(
13265 lsp::ServerCapabilities {
13266 definition_provider: Some(lsp::OneOf::Left(true)),
13267 references_provider: Some(lsp::OneOf::Left(true)),
13268 ..lsp::ServerCapabilities::default()
13269 },
13270 cx,
13271 )
13272 .await;
13273
13274 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13275 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13276 move |params, _| async move {
13277 if empty_go_to_definition {
13278 Ok(None)
13279 } else {
13280 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13281 uri: params.text_document_position_params.text_document.uri,
13282 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13283 })))
13284 }
13285 },
13286 );
13287 let references =
13288 cx.lsp
13289 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13290 Ok(Some(vec![lsp::Location {
13291 uri: params.text_document_position.text_document.uri,
13292 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13293 }]))
13294 });
13295 (go_to_definition, references)
13296 };
13297
13298 cx.set_state(
13299 &r#"fn one() {
13300 let mut a = ˇtwo();
13301 }
13302
13303 fn two() {}"#
13304 .unindent(),
13305 );
13306 set_up_lsp_handlers(false, &mut cx);
13307 let navigated = cx
13308 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13309 .await
13310 .expect("Failed to navigate to definition");
13311 assert_eq!(
13312 navigated,
13313 Navigated::Yes,
13314 "Should have navigated to definition from the GetDefinition response"
13315 );
13316 cx.assert_editor_state(
13317 &r#"fn one() {
13318 let mut a = two();
13319 }
13320
13321 fn «twoˇ»() {}"#
13322 .unindent(),
13323 );
13324
13325 let editors = cx.update_workspace(|workspace, cx| {
13326 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13327 });
13328 cx.update_editor(|_, test_editor_cx| {
13329 assert_eq!(
13330 editors.len(),
13331 1,
13332 "Initially, only one, test, editor should be open in the workspace"
13333 );
13334 assert_eq!(
13335 test_editor_cx.view(),
13336 editors.last().expect("Asserted len is 1")
13337 );
13338 });
13339
13340 set_up_lsp_handlers(true, &mut cx);
13341 let navigated = cx
13342 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13343 .await
13344 .expect("Failed to navigate to lookup references");
13345 assert_eq!(
13346 navigated,
13347 Navigated::Yes,
13348 "Should have navigated to references as a fallback after empty GoToDefinition response"
13349 );
13350 // We should not change the selections in the existing file,
13351 // if opening another milti buffer with the references
13352 cx.assert_editor_state(
13353 &r#"fn one() {
13354 let mut a = two();
13355 }
13356
13357 fn «twoˇ»() {}"#
13358 .unindent(),
13359 );
13360 let editors = cx.update_workspace(|workspace, cx| {
13361 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13362 });
13363 cx.update_editor(|_, test_editor_cx| {
13364 assert_eq!(
13365 editors.len(),
13366 2,
13367 "After falling back to references search, we open a new editor with the results"
13368 );
13369 let references_fallback_text = editors
13370 .into_iter()
13371 .find(|new_editor| new_editor != test_editor_cx.view())
13372 .expect("Should have one non-test editor now")
13373 .read(test_editor_cx)
13374 .text(test_editor_cx);
13375 assert_eq!(
13376 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13377 "Should use the range from the references response and not the GoToDefinition one"
13378 );
13379 });
13380}
13381
13382fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13383 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13384 point..point
13385}
13386
13387fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13388 let (text, ranges) = marked_text_ranges(marked_text, true);
13389 assert_eq!(view.text(cx), text);
13390 assert_eq!(
13391 view.selections.ranges(cx),
13392 ranges,
13393 "Assert selections are {}",
13394 marked_text
13395 );
13396}
13397
13398pub fn handle_signature_help_request(
13399 cx: &mut EditorLspTestContext,
13400 mocked_response: lsp::SignatureHelp,
13401) -> impl Future<Output = ()> {
13402 let mut request =
13403 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13404 let mocked_response = mocked_response.clone();
13405 async move { Ok(Some(mocked_response)) }
13406 });
13407
13408 async move {
13409 request.next().await;
13410 }
13411}
13412
13413/// Handle completion request passing a marked string specifying where the completion
13414/// should be triggered from using '|' character, what range should be replaced, and what completions
13415/// should be returned using '<' and '>' to delimit the range
13416pub fn handle_completion_request(
13417 cx: &mut EditorLspTestContext,
13418 marked_string: &str,
13419 completions: Vec<&'static str>,
13420 counter: Arc<AtomicUsize>,
13421) -> impl Future<Output = ()> {
13422 let complete_from_marker: TextRangeMarker = '|'.into();
13423 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13424 let (_, mut marked_ranges) = marked_text_ranges_by(
13425 marked_string,
13426 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13427 );
13428
13429 let complete_from_position =
13430 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13431 let replace_range =
13432 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13433
13434 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13435 let completions = completions.clone();
13436 counter.fetch_add(1, atomic::Ordering::Release);
13437 async move {
13438 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13439 assert_eq!(
13440 params.text_document_position.position,
13441 complete_from_position
13442 );
13443 Ok(Some(lsp::CompletionResponse::Array(
13444 completions
13445 .iter()
13446 .map(|completion_text| lsp::CompletionItem {
13447 label: completion_text.to_string(),
13448 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13449 range: replace_range,
13450 new_text: completion_text.to_string(),
13451 })),
13452 ..Default::default()
13453 })
13454 .collect(),
13455 )))
13456 }
13457 });
13458
13459 async move {
13460 request.next().await;
13461 }
13462}
13463
13464fn handle_resolve_completion_request(
13465 cx: &mut EditorLspTestContext,
13466 edits: Option<Vec<(&'static str, &'static str)>>,
13467) -> impl Future<Output = ()> {
13468 let edits = edits.map(|edits| {
13469 edits
13470 .iter()
13471 .map(|(marked_string, new_text)| {
13472 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13473 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13474 lsp::TextEdit::new(replace_range, new_text.to_string())
13475 })
13476 .collect::<Vec<_>>()
13477 });
13478
13479 let mut request =
13480 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13481 let edits = edits.clone();
13482 async move {
13483 Ok(lsp::CompletionItem {
13484 additional_text_edits: edits,
13485 ..Default::default()
13486 })
13487 }
13488 });
13489
13490 async move {
13491 request.next().await;
13492 }
13493}
13494
13495pub(crate) fn update_test_language_settings(
13496 cx: &mut TestAppContext,
13497 f: impl Fn(&mut AllLanguageSettingsContent),
13498) {
13499 _ = cx.update(|cx| {
13500 SettingsStore::update_global(cx, |store, cx| {
13501 store.update_user_settings::<AllLanguageSettings>(cx, f);
13502 });
13503 });
13504}
13505
13506pub(crate) fn update_test_project_settings(
13507 cx: &mut TestAppContext,
13508 f: impl Fn(&mut ProjectSettings),
13509) {
13510 _ = cx.update(|cx| {
13511 SettingsStore::update_global(cx, |store, cx| {
13512 store.update_user_settings::<ProjectSettings>(cx, f);
13513 });
13514 });
13515}
13516
13517pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
13518 _ = cx.update(|cx| {
13519 assets::Assets.load_test_fonts(cx);
13520 let store = SettingsStore::test(cx);
13521 cx.set_global(store);
13522 theme::init(theme::LoadThemes::JustBase, cx);
13523 release_channel::init(SemanticVersion::default(), cx);
13524 client::init_settings(cx);
13525 language::init(cx);
13526 Project::init_settings(cx);
13527 workspace::init_settings(cx);
13528 crate::init(cx);
13529 });
13530
13531 update_test_language_settings(cx, f);
13532}
13533
13534pub(crate) fn rust_lang() -> Arc<Language> {
13535 Arc::new(Language::new(
13536 LanguageConfig {
13537 name: "Rust".into(),
13538 matcher: LanguageMatcher {
13539 path_suffixes: vec!["rs".to_string()],
13540 ..Default::default()
13541 },
13542 ..Default::default()
13543 },
13544 Some(tree_sitter_rust::language()),
13545 ))
13546}
13547
13548#[track_caller]
13549fn assert_hunk_revert(
13550 not_reverted_text_with_selections: &str,
13551 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
13552 expected_reverted_text_with_selections: &str,
13553 base_text: &str,
13554 cx: &mut EditorLspTestContext,
13555) {
13556 cx.set_state(not_reverted_text_with_selections);
13557 cx.update_editor(|editor, cx| {
13558 editor
13559 .buffer()
13560 .read(cx)
13561 .as_singleton()
13562 .unwrap()
13563 .update(cx, |buffer, cx| {
13564 buffer.set_diff_base(Some(base_text.into()), cx);
13565 });
13566 });
13567 cx.executor().run_until_parked();
13568
13569 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
13570 let snapshot = editor.buffer().read(cx).snapshot(cx);
13571 let reverted_hunk_statuses = snapshot
13572 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
13573 .map(|hunk| hunk_status(&hunk))
13574 .collect::<Vec<_>>();
13575
13576 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
13577 reverted_hunk_statuses
13578 });
13579 cx.executor().run_until_parked();
13580 cx.assert_editor_state(expected_reverted_text_with_selections);
13581 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
13582}