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 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
7511 ..Default::default()
7512 },
7513 cx,
7514 )
7515 .await;
7516 let counter = Arc::new(AtomicUsize::new(0));
7517
7518 cx.set_state(indoc! {"
7519 oneˇ
7520 two
7521 three
7522 "});
7523 cx.simulate_keystroke(".");
7524 handle_completion_request(
7525 &mut cx,
7526 indoc! {"
7527 one.|<>
7528 two
7529 three
7530 "},
7531 vec!["first_completion", "second_completion"],
7532 counter.clone(),
7533 )
7534 .await;
7535 cx.condition(|editor, _| editor.context_menu_visible())
7536 .await;
7537 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
7538
7539 let _handler = handle_signature_help_request(
7540 &mut cx,
7541 lsp::SignatureHelp {
7542 signatures: vec![lsp::SignatureInformation {
7543 label: "test signature".to_string(),
7544 documentation: None,
7545 parameters: Some(vec![lsp::ParameterInformation {
7546 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
7547 documentation: None,
7548 }]),
7549 active_parameter: None,
7550 }],
7551 active_signature: None,
7552 active_parameter: None,
7553 },
7554 );
7555 cx.update_editor(|editor, cx| {
7556 assert!(
7557 !editor.signature_help_state.is_shown(),
7558 "No signature help was called for"
7559 );
7560 editor.show_signature_help(&ShowSignatureHelp, cx);
7561 });
7562 cx.run_until_parked();
7563 cx.update_editor(|editor, _| {
7564 assert!(
7565 !editor.signature_help_state.is_shown(),
7566 "No signature help should be shown when completions menu is open"
7567 );
7568 });
7569
7570 let apply_additional_edits = cx.update_editor(|editor, cx| {
7571 editor.context_menu_next(&Default::default(), cx);
7572 editor
7573 .confirm_completion(&ConfirmCompletion::default(), cx)
7574 .unwrap()
7575 });
7576 cx.assert_editor_state(indoc! {"
7577 one.second_completionˇ
7578 two
7579 three
7580 "});
7581
7582 handle_resolve_completion_request(
7583 &mut cx,
7584 Some(vec![
7585 (
7586 //This overlaps with the primary completion edit which is
7587 //misbehavior from the LSP spec, test that we filter it out
7588 indoc! {"
7589 one.second_ˇcompletion
7590 two
7591 threeˇ
7592 "},
7593 "overlapping additional edit",
7594 ),
7595 (
7596 indoc! {"
7597 one.second_completion
7598 two
7599 threeˇ
7600 "},
7601 "\nadditional edit",
7602 ),
7603 ]),
7604 )
7605 .await;
7606 apply_additional_edits.await.unwrap();
7607 cx.assert_editor_state(indoc! {"
7608 one.second_completionˇ
7609 two
7610 three
7611 additional edit
7612 "});
7613
7614 cx.set_state(indoc! {"
7615 one.second_completion
7616 twoˇ
7617 threeˇ
7618 additional edit
7619 "});
7620 cx.simulate_keystroke(" ");
7621 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7622 cx.simulate_keystroke("s");
7623 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7624
7625 cx.assert_editor_state(indoc! {"
7626 one.second_completion
7627 two sˇ
7628 three sˇ
7629 additional edit
7630 "});
7631 handle_completion_request(
7632 &mut cx,
7633 indoc! {"
7634 one.second_completion
7635 two s
7636 three <s|>
7637 additional edit
7638 "},
7639 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
7640 counter.clone(),
7641 )
7642 .await;
7643 cx.condition(|editor, _| editor.context_menu_visible())
7644 .await;
7645 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
7646
7647 cx.simulate_keystroke("i");
7648
7649 handle_completion_request(
7650 &mut cx,
7651 indoc! {"
7652 one.second_completion
7653 two si
7654 three <si|>
7655 additional edit
7656 "},
7657 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
7658 counter.clone(),
7659 )
7660 .await;
7661 cx.condition(|editor, _| editor.context_menu_visible())
7662 .await;
7663 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
7664
7665 let apply_additional_edits = cx.update_editor(|editor, cx| {
7666 editor
7667 .confirm_completion(&ConfirmCompletion::default(), cx)
7668 .unwrap()
7669 });
7670 cx.assert_editor_state(indoc! {"
7671 one.second_completion
7672 two sixth_completionˇ
7673 three sixth_completionˇ
7674 additional edit
7675 "});
7676
7677 handle_resolve_completion_request(&mut cx, None).await;
7678 apply_additional_edits.await.unwrap();
7679
7680 _ = cx.update(|cx| {
7681 cx.update_global::<SettingsStore, _>(|settings, cx| {
7682 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7683 settings.show_completions_on_input = Some(false);
7684 });
7685 })
7686 });
7687 cx.set_state("editorˇ");
7688 cx.simulate_keystroke(".");
7689 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7690 cx.simulate_keystroke("c");
7691 cx.simulate_keystroke("l");
7692 cx.simulate_keystroke("o");
7693 cx.assert_editor_state("editor.cloˇ");
7694 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7695 cx.update_editor(|editor, cx| {
7696 editor.show_completions(&ShowCompletions { trigger: None }, cx);
7697 });
7698 handle_completion_request(
7699 &mut cx,
7700 "editor.<clo|>",
7701 vec!["close", "clobber"],
7702 counter.clone(),
7703 )
7704 .await;
7705 cx.condition(|editor, _| editor.context_menu_visible())
7706 .await;
7707 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
7708
7709 let apply_additional_edits = cx.update_editor(|editor, cx| {
7710 editor
7711 .confirm_completion(&ConfirmCompletion::default(), cx)
7712 .unwrap()
7713 });
7714 cx.assert_editor_state("editor.closeˇ");
7715 handle_resolve_completion_request(&mut cx, None).await;
7716 apply_additional_edits.await.unwrap();
7717}
7718
7719#[gpui::test]
7720async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
7721 init_test(cx, |_| {});
7722 let mut cx = EditorLspTestContext::new_rust(
7723 lsp::ServerCapabilities {
7724 completion_provider: Some(lsp::CompletionOptions {
7725 trigger_characters: Some(vec![".".to_string()]),
7726 ..Default::default()
7727 }),
7728 ..Default::default()
7729 },
7730 cx,
7731 )
7732 .await;
7733 cx.lsp
7734 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
7735 Ok(Some(lsp::CompletionResponse::Array(vec![
7736 lsp::CompletionItem {
7737 label: "first".into(),
7738 ..Default::default()
7739 },
7740 lsp::CompletionItem {
7741 label: "last".into(),
7742 ..Default::default()
7743 },
7744 ])))
7745 });
7746 cx.set_state("variableˇ");
7747 cx.simulate_keystroke(".");
7748 cx.executor().run_until_parked();
7749
7750 cx.update_editor(|editor, _| {
7751 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7752 assert_eq!(
7753 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
7754 &["first", "last"]
7755 );
7756 } else {
7757 panic!("expected completion menu to be open");
7758 }
7759 });
7760
7761 cx.update_editor(|editor, cx| {
7762 editor.move_page_down(&MovePageDown::default(), cx);
7763 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7764 assert!(
7765 menu.selected_item == 1,
7766 "expected PageDown to select the last item from the context menu"
7767 );
7768 } else {
7769 panic!("expected completion menu to stay open after PageDown");
7770 }
7771 });
7772
7773 cx.update_editor(|editor, cx| {
7774 editor.move_page_up(&MovePageUp::default(), cx);
7775 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7776 assert!(
7777 menu.selected_item == 0,
7778 "expected PageUp to select the first item from the context menu"
7779 );
7780 } else {
7781 panic!("expected completion menu to stay open after PageUp");
7782 }
7783 });
7784}
7785
7786#[gpui::test]
7787async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
7788 init_test(cx, |_| {});
7789
7790 let mut cx = EditorLspTestContext::new_rust(
7791 lsp::ServerCapabilities {
7792 completion_provider: Some(lsp::CompletionOptions {
7793 trigger_characters: Some(vec![".".to_string()]),
7794 resolve_provider: Some(true),
7795 ..Default::default()
7796 }),
7797 ..Default::default()
7798 },
7799 cx,
7800 )
7801 .await;
7802
7803 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
7804 cx.simulate_keystroke(".");
7805 let completion_item = lsp::CompletionItem {
7806 label: "Some".into(),
7807 kind: Some(lsp::CompletionItemKind::SNIPPET),
7808 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
7809 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
7810 kind: lsp::MarkupKind::Markdown,
7811 value: "```rust\nSome(2)\n```".to_string(),
7812 })),
7813 deprecated: Some(false),
7814 sort_text: Some("Some".to_string()),
7815 filter_text: Some("Some".to_string()),
7816 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
7817 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7818 range: lsp::Range {
7819 start: lsp::Position {
7820 line: 0,
7821 character: 22,
7822 },
7823 end: lsp::Position {
7824 line: 0,
7825 character: 22,
7826 },
7827 },
7828 new_text: "Some(2)".to_string(),
7829 })),
7830 additional_text_edits: Some(vec![lsp::TextEdit {
7831 range: lsp::Range {
7832 start: lsp::Position {
7833 line: 0,
7834 character: 20,
7835 },
7836 end: lsp::Position {
7837 line: 0,
7838 character: 22,
7839 },
7840 },
7841 new_text: "".to_string(),
7842 }]),
7843 ..Default::default()
7844 };
7845
7846 let closure_completion_item = completion_item.clone();
7847 let counter = Arc::new(AtomicUsize::new(0));
7848 let counter_clone = counter.clone();
7849 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
7850 let task_completion_item = closure_completion_item.clone();
7851 counter_clone.fetch_add(1, atomic::Ordering::Release);
7852 async move {
7853 Ok(Some(lsp::CompletionResponse::Array(vec![
7854 task_completion_item,
7855 ])))
7856 }
7857 });
7858
7859 cx.condition(|editor, _| editor.context_menu_visible())
7860 .await;
7861 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
7862 assert!(request.next().await.is_some());
7863 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
7864
7865 cx.simulate_keystroke("S");
7866 cx.simulate_keystroke("o");
7867 cx.simulate_keystroke("m");
7868 cx.condition(|editor, _| editor.context_menu_visible())
7869 .await;
7870 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
7871 assert!(request.next().await.is_some());
7872 assert!(request.next().await.is_some());
7873 assert!(request.next().await.is_some());
7874 request.close();
7875 assert!(request.next().await.is_none());
7876 assert_eq!(
7877 counter.load(atomic::Ordering::Acquire),
7878 4,
7879 "With the completions menu open, only one LSP request should happen per input"
7880 );
7881}
7882
7883#[gpui::test]
7884async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
7885 init_test(cx, |_| {});
7886 let mut cx = EditorTestContext::new(cx).await;
7887 let language = Arc::new(Language::new(
7888 LanguageConfig {
7889 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
7890 ..Default::default()
7891 },
7892 Some(tree_sitter_rust::language()),
7893 ));
7894 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
7895
7896 // If multiple selections intersect a line, the line is only toggled once.
7897 cx.set_state(indoc! {"
7898 fn a() {
7899 «//b();
7900 ˇ»// «c();
7901 //ˇ» d();
7902 }
7903 "});
7904
7905 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7906
7907 cx.assert_editor_state(indoc! {"
7908 fn a() {
7909 «b();
7910 c();
7911 ˇ» d();
7912 }
7913 "});
7914
7915 // The comment prefix is inserted at the same column for every line in a
7916 // selection.
7917 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7918
7919 cx.assert_editor_state(indoc! {"
7920 fn a() {
7921 // «b();
7922 // c();
7923 ˇ»// d();
7924 }
7925 "});
7926
7927 // If a selection ends at the beginning of a line, that line is not toggled.
7928 cx.set_selections_state(indoc! {"
7929 fn a() {
7930 // b();
7931 «// c();
7932 ˇ» // d();
7933 }
7934 "});
7935
7936 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7937
7938 cx.assert_editor_state(indoc! {"
7939 fn a() {
7940 // b();
7941 «c();
7942 ˇ» // d();
7943 }
7944 "});
7945
7946 // If a selection span a single line and is empty, the line is toggled.
7947 cx.set_state(indoc! {"
7948 fn a() {
7949 a();
7950 b();
7951 ˇ
7952 }
7953 "});
7954
7955 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7956
7957 cx.assert_editor_state(indoc! {"
7958 fn a() {
7959 a();
7960 b();
7961 //•ˇ
7962 }
7963 "});
7964
7965 // If a selection span multiple lines, empty lines are not toggled.
7966 cx.set_state(indoc! {"
7967 fn a() {
7968 «a();
7969
7970 c();ˇ»
7971 }
7972 "});
7973
7974 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7975
7976 cx.assert_editor_state(indoc! {"
7977 fn a() {
7978 // «a();
7979
7980 // c();ˇ»
7981 }
7982 "});
7983
7984 // If a selection includes multiple comment prefixes, all lines are uncommented.
7985 cx.set_state(indoc! {"
7986 fn a() {
7987 «// a();
7988 /// b();
7989 //! c();ˇ»
7990 }
7991 "});
7992
7993 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7994
7995 cx.assert_editor_state(indoc! {"
7996 fn a() {
7997 «a();
7998 b();
7999 c();ˇ»
8000 }
8001 "});
8002}
8003
8004#[gpui::test]
8005async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8006 init_test(cx, |_| {});
8007
8008 let language = Arc::new(Language::new(
8009 LanguageConfig {
8010 line_comments: vec!["// ".into()],
8011 ..Default::default()
8012 },
8013 Some(tree_sitter_rust::language()),
8014 ));
8015
8016 let mut cx = EditorTestContext::new(cx).await;
8017
8018 cx.language_registry().add(language.clone());
8019 cx.update_buffer(|buffer, cx| {
8020 buffer.set_language(Some(language), cx);
8021 });
8022
8023 let toggle_comments = &ToggleComments {
8024 advance_downwards: true,
8025 };
8026
8027 // Single cursor on one line -> advance
8028 // Cursor moves horizontally 3 characters as well on non-blank line
8029 cx.set_state(indoc!(
8030 "fn a() {
8031 ˇdog();
8032 cat();
8033 }"
8034 ));
8035 cx.update_editor(|editor, cx| {
8036 editor.toggle_comments(toggle_comments, cx);
8037 });
8038 cx.assert_editor_state(indoc!(
8039 "fn a() {
8040 // dog();
8041 catˇ();
8042 }"
8043 ));
8044
8045 // Single selection on one line -> don't advance
8046 cx.set_state(indoc!(
8047 "fn a() {
8048 «dog()ˇ»;
8049 cat();
8050 }"
8051 ));
8052 cx.update_editor(|editor, cx| {
8053 editor.toggle_comments(toggle_comments, cx);
8054 });
8055 cx.assert_editor_state(indoc!(
8056 "fn a() {
8057 // «dog()ˇ»;
8058 cat();
8059 }"
8060 ));
8061
8062 // Multiple cursors on one line -> advance
8063 cx.set_state(indoc!(
8064 "fn a() {
8065 ˇdˇog();
8066 cat();
8067 }"
8068 ));
8069 cx.update_editor(|editor, cx| {
8070 editor.toggle_comments(toggle_comments, cx);
8071 });
8072 cx.assert_editor_state(indoc!(
8073 "fn a() {
8074 // dog();
8075 catˇ(ˇ);
8076 }"
8077 ));
8078
8079 // Multiple cursors on one line, with selection -> don't advance
8080 cx.set_state(indoc!(
8081 "fn a() {
8082 ˇdˇog«()ˇ»;
8083 cat();
8084 }"
8085 ));
8086 cx.update_editor(|editor, cx| {
8087 editor.toggle_comments(toggle_comments, cx);
8088 });
8089 cx.assert_editor_state(indoc!(
8090 "fn a() {
8091 // ˇdˇog«()ˇ»;
8092 cat();
8093 }"
8094 ));
8095
8096 // Single cursor on one line -> advance
8097 // Cursor moves to column 0 on blank line
8098 cx.set_state(indoc!(
8099 "fn a() {
8100 ˇdog();
8101
8102 cat();
8103 }"
8104 ));
8105 cx.update_editor(|editor, cx| {
8106 editor.toggle_comments(toggle_comments, cx);
8107 });
8108 cx.assert_editor_state(indoc!(
8109 "fn a() {
8110 // dog();
8111 ˇ
8112 cat();
8113 }"
8114 ));
8115
8116 // Single cursor on one line -> advance
8117 // Cursor starts and ends at column 0
8118 cx.set_state(indoc!(
8119 "fn a() {
8120 ˇ dog();
8121 cat();
8122 }"
8123 ));
8124 cx.update_editor(|editor, cx| {
8125 editor.toggle_comments(toggle_comments, cx);
8126 });
8127 cx.assert_editor_state(indoc!(
8128 "fn a() {
8129 // dog();
8130 ˇ cat();
8131 }"
8132 ));
8133}
8134
8135#[gpui::test]
8136async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
8137 init_test(cx, |_| {});
8138
8139 let mut cx = EditorTestContext::new(cx).await;
8140
8141 let html_language = Arc::new(
8142 Language::new(
8143 LanguageConfig {
8144 name: "HTML".into(),
8145 block_comment: Some(("<!-- ".into(), " -->".into())),
8146 ..Default::default()
8147 },
8148 Some(tree_sitter_html::language()),
8149 )
8150 .with_injection_query(
8151 r#"
8152 (script_element
8153 (raw_text) @content
8154 (#set! "language" "javascript"))
8155 "#,
8156 )
8157 .unwrap(),
8158 );
8159
8160 let javascript_language = Arc::new(Language::new(
8161 LanguageConfig {
8162 name: "JavaScript".into(),
8163 line_comments: vec!["// ".into()],
8164 ..Default::default()
8165 },
8166 Some(tree_sitter_typescript::language_tsx()),
8167 ));
8168
8169 cx.language_registry().add(html_language.clone());
8170 cx.language_registry().add(javascript_language.clone());
8171 cx.update_buffer(|buffer, cx| {
8172 buffer.set_language(Some(html_language), cx);
8173 });
8174
8175 // Toggle comments for empty selections
8176 cx.set_state(
8177 &r#"
8178 <p>A</p>ˇ
8179 <p>B</p>ˇ
8180 <p>C</p>ˇ
8181 "#
8182 .unindent(),
8183 );
8184 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8185 cx.assert_editor_state(
8186 &r#"
8187 <!-- <p>A</p>ˇ -->
8188 <!-- <p>B</p>ˇ -->
8189 <!-- <p>C</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 "#
8200 .unindent(),
8201 );
8202
8203 // Toggle comments for mixture of empty and non-empty selections, where
8204 // multiple selections occupy a given line.
8205 cx.set_state(
8206 &r#"
8207 <p>A«</p>
8208 <p>ˇ»B</p>ˇ
8209 <p>C«</p>
8210 <p>ˇ»D</p>ˇ
8211 "#
8212 .unindent(),
8213 );
8214
8215 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8216 cx.assert_editor_state(
8217 &r#"
8218 <!-- <p>A«</p>
8219 <p>ˇ»B</p>ˇ -->
8220 <!-- <p>C«</p>
8221 <p>ˇ»D</p>ˇ -->
8222 "#
8223 .unindent(),
8224 );
8225 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8226 cx.assert_editor_state(
8227 &r#"
8228 <p>A«</p>
8229 <p>ˇ»B</p>ˇ
8230 <p>C«</p>
8231 <p>ˇ»D</p>ˇ
8232 "#
8233 .unindent(),
8234 );
8235
8236 // Toggle comments when different languages are active for different
8237 // selections.
8238 cx.set_state(
8239 &r#"
8240 ˇ<script>
8241 ˇvar x = new Y();
8242 ˇ</script>
8243 "#
8244 .unindent(),
8245 );
8246 cx.executor().run_until_parked();
8247 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8248 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
8249 // Uncommenting and commenting from this position brings in even more wrong artifacts.
8250 cx.assert_editor_state(
8251 &r#"
8252 <!-- ˇ<script> -->
8253 // ˇvar x = new Y();
8254 // ˇ</script>
8255 "#
8256 .unindent(),
8257 );
8258}
8259
8260#[gpui::test]
8261fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
8262 init_test(cx, |_| {});
8263
8264 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8265 let multibuffer = cx.new_model(|cx| {
8266 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8267 multibuffer.push_excerpts(
8268 buffer.clone(),
8269 [
8270 ExcerptRange {
8271 context: Point::new(0, 0)..Point::new(0, 4),
8272 primary: None,
8273 },
8274 ExcerptRange {
8275 context: Point::new(1, 0)..Point::new(1, 4),
8276 primary: None,
8277 },
8278 ],
8279 cx,
8280 );
8281 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
8282 multibuffer
8283 });
8284
8285 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8286 _ = view.update(cx, |view, cx| {
8287 assert_eq!(view.text(cx), "aaaa\nbbbb");
8288 view.change_selections(None, cx, |s| {
8289 s.select_ranges([
8290 Point::new(0, 0)..Point::new(0, 0),
8291 Point::new(1, 0)..Point::new(1, 0),
8292 ])
8293 });
8294
8295 view.handle_input("X", cx);
8296 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
8297 assert_eq!(
8298 view.selections.ranges(cx),
8299 [
8300 Point::new(0, 1)..Point::new(0, 1),
8301 Point::new(1, 1)..Point::new(1, 1),
8302 ]
8303 );
8304
8305 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
8306 view.change_selections(None, cx, |s| {
8307 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
8308 });
8309 view.backspace(&Default::default(), cx);
8310 assert_eq!(view.text(cx), "Xa\nbbb");
8311 assert_eq!(
8312 view.selections.ranges(cx),
8313 [Point::new(1, 0)..Point::new(1, 0)]
8314 );
8315
8316 view.change_selections(None, cx, |s| {
8317 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
8318 });
8319 view.backspace(&Default::default(), cx);
8320 assert_eq!(view.text(cx), "X\nbb");
8321 assert_eq!(
8322 view.selections.ranges(cx),
8323 [Point::new(0, 1)..Point::new(0, 1)]
8324 );
8325 });
8326}
8327
8328#[gpui::test]
8329fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
8330 init_test(cx, |_| {});
8331
8332 let markers = vec![('[', ']').into(), ('(', ')').into()];
8333 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
8334 indoc! {"
8335 [aaaa
8336 (bbbb]
8337 cccc)",
8338 },
8339 markers.clone(),
8340 );
8341 let excerpt_ranges = markers.into_iter().map(|marker| {
8342 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
8343 ExcerptRange {
8344 context,
8345 primary: None,
8346 }
8347 });
8348 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
8349 let multibuffer = cx.new_model(|cx| {
8350 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8351 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
8352 multibuffer
8353 });
8354
8355 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8356 _ = view.update(cx, |view, cx| {
8357 let (expected_text, selection_ranges) = marked_text_ranges(
8358 indoc! {"
8359 aaaa
8360 bˇbbb
8361 bˇbbˇb
8362 cccc"
8363 },
8364 true,
8365 );
8366 assert_eq!(view.text(cx), expected_text);
8367 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
8368
8369 view.handle_input("X", cx);
8370
8371 let (expected_text, expected_selections) = marked_text_ranges(
8372 indoc! {"
8373 aaaa
8374 bXˇbbXb
8375 bXˇbbXˇb
8376 cccc"
8377 },
8378 false,
8379 );
8380 assert_eq!(view.text(cx), expected_text);
8381 assert_eq!(view.selections.ranges(cx), expected_selections);
8382
8383 view.newline(&Newline, cx);
8384 let (expected_text, expected_selections) = marked_text_ranges(
8385 indoc! {"
8386 aaaa
8387 bX
8388 ˇbbX
8389 b
8390 bX
8391 ˇbbX
8392 ˇb
8393 cccc"
8394 },
8395 false,
8396 );
8397 assert_eq!(view.text(cx), expected_text);
8398 assert_eq!(view.selections.ranges(cx), expected_selections);
8399 });
8400}
8401
8402#[gpui::test]
8403fn test_refresh_selections(cx: &mut TestAppContext) {
8404 init_test(cx, |_| {});
8405
8406 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8407 let mut excerpt1_id = None;
8408 let multibuffer = cx.new_model(|cx| {
8409 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8410 excerpt1_id = multibuffer
8411 .push_excerpts(
8412 buffer.clone(),
8413 [
8414 ExcerptRange {
8415 context: Point::new(0, 0)..Point::new(1, 4),
8416 primary: None,
8417 },
8418 ExcerptRange {
8419 context: Point::new(1, 0)..Point::new(2, 4),
8420 primary: None,
8421 },
8422 ],
8423 cx,
8424 )
8425 .into_iter()
8426 .next();
8427 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8428 multibuffer
8429 });
8430
8431 let editor = cx.add_window(|cx| {
8432 let mut editor = build_editor(multibuffer.clone(), cx);
8433 let snapshot = editor.snapshot(cx);
8434 editor.change_selections(None, cx, |s| {
8435 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
8436 });
8437 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
8438 assert_eq!(
8439 editor.selections.ranges(cx),
8440 [
8441 Point::new(1, 3)..Point::new(1, 3),
8442 Point::new(2, 1)..Point::new(2, 1),
8443 ]
8444 );
8445 editor
8446 });
8447
8448 // Refreshing selections is a no-op when excerpts haven't changed.
8449 _ = editor.update(cx, |editor, cx| {
8450 editor.change_selections(None, cx, |s| s.refresh());
8451 assert_eq!(
8452 editor.selections.ranges(cx),
8453 [
8454 Point::new(1, 3)..Point::new(1, 3),
8455 Point::new(2, 1)..Point::new(2, 1),
8456 ]
8457 );
8458 });
8459
8460 _ = multibuffer.update(cx, |multibuffer, cx| {
8461 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8462 });
8463 _ = editor.update(cx, |editor, cx| {
8464 // Removing an excerpt causes the first selection to become degenerate.
8465 assert_eq!(
8466 editor.selections.ranges(cx),
8467 [
8468 Point::new(0, 0)..Point::new(0, 0),
8469 Point::new(0, 1)..Point::new(0, 1)
8470 ]
8471 );
8472
8473 // Refreshing selections will relocate the first selection to the original buffer
8474 // location.
8475 editor.change_selections(None, cx, |s| s.refresh());
8476 assert_eq!(
8477 editor.selections.ranges(cx),
8478 [
8479 Point::new(0, 1)..Point::new(0, 1),
8480 Point::new(0, 3)..Point::new(0, 3)
8481 ]
8482 );
8483 assert!(editor.selections.pending_anchor().is_some());
8484 });
8485}
8486
8487#[gpui::test]
8488fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
8489 init_test(cx, |_| {});
8490
8491 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8492 let mut excerpt1_id = None;
8493 let multibuffer = cx.new_model(|cx| {
8494 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8495 excerpt1_id = multibuffer
8496 .push_excerpts(
8497 buffer.clone(),
8498 [
8499 ExcerptRange {
8500 context: Point::new(0, 0)..Point::new(1, 4),
8501 primary: None,
8502 },
8503 ExcerptRange {
8504 context: Point::new(1, 0)..Point::new(2, 4),
8505 primary: None,
8506 },
8507 ],
8508 cx,
8509 )
8510 .into_iter()
8511 .next();
8512 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8513 multibuffer
8514 });
8515
8516 let editor = cx.add_window(|cx| {
8517 let mut editor = build_editor(multibuffer.clone(), cx);
8518 let snapshot = editor.snapshot(cx);
8519 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
8520 assert_eq!(
8521 editor.selections.ranges(cx),
8522 [Point::new(1, 3)..Point::new(1, 3)]
8523 );
8524 editor
8525 });
8526
8527 _ = multibuffer.update(cx, |multibuffer, cx| {
8528 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8529 });
8530 _ = editor.update(cx, |editor, cx| {
8531 assert_eq!(
8532 editor.selections.ranges(cx),
8533 [Point::new(0, 0)..Point::new(0, 0)]
8534 );
8535
8536 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
8537 editor.change_selections(None, cx, |s| s.refresh());
8538 assert_eq!(
8539 editor.selections.ranges(cx),
8540 [Point::new(0, 3)..Point::new(0, 3)]
8541 );
8542 assert!(editor.selections.pending_anchor().is_some());
8543 });
8544}
8545
8546#[gpui::test]
8547async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
8548 init_test(cx, |_| {});
8549
8550 let language = Arc::new(
8551 Language::new(
8552 LanguageConfig {
8553 brackets: BracketPairConfig {
8554 pairs: vec![
8555 BracketPair {
8556 start: "{".to_string(),
8557 end: "}".to_string(),
8558 close: true,
8559 surround: true,
8560 newline: true,
8561 },
8562 BracketPair {
8563 start: "/* ".to_string(),
8564 end: " */".to_string(),
8565 close: true,
8566 surround: true,
8567 newline: true,
8568 },
8569 ],
8570 ..Default::default()
8571 },
8572 ..Default::default()
8573 },
8574 Some(tree_sitter_rust::language()),
8575 )
8576 .with_indents_query("")
8577 .unwrap(),
8578 );
8579
8580 let text = concat!(
8581 "{ }\n", //
8582 " x\n", //
8583 " /* */\n", //
8584 "x\n", //
8585 "{{} }\n", //
8586 );
8587
8588 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
8589 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
8590 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
8591 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
8592 .await;
8593
8594 _ = view.update(cx, |view, cx| {
8595 view.change_selections(None, cx, |s| {
8596 s.select_display_ranges([
8597 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
8598 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
8599 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
8600 ])
8601 });
8602 view.newline(&Newline, cx);
8603
8604 assert_eq!(
8605 view.buffer().read(cx).read(cx).text(),
8606 concat!(
8607 "{ \n", // Suppress rustfmt
8608 "\n", //
8609 "}\n", //
8610 " x\n", //
8611 " /* \n", //
8612 " \n", //
8613 " */\n", //
8614 "x\n", //
8615 "{{} \n", //
8616 "}\n", //
8617 )
8618 );
8619 });
8620}
8621
8622#[gpui::test]
8623fn test_highlighted_ranges(cx: &mut TestAppContext) {
8624 init_test(cx, |_| {});
8625
8626 let editor = cx.add_window(|cx| {
8627 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
8628 build_editor(buffer.clone(), cx)
8629 });
8630
8631 _ = editor.update(cx, |editor, cx| {
8632 struct Type1;
8633 struct Type2;
8634
8635 let buffer = editor.buffer.read(cx).snapshot(cx);
8636
8637 let anchor_range =
8638 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
8639
8640 editor.highlight_background::<Type1>(
8641 &[
8642 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
8643 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
8644 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
8645 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
8646 ],
8647 |_| Hsla::red(),
8648 cx,
8649 );
8650 editor.highlight_background::<Type2>(
8651 &[
8652 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
8653 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
8654 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
8655 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
8656 ],
8657 |_| Hsla::green(),
8658 cx,
8659 );
8660
8661 let snapshot = editor.snapshot(cx);
8662 let mut highlighted_ranges = editor.background_highlights_in_range(
8663 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
8664 &snapshot,
8665 cx.theme().colors(),
8666 );
8667 // Enforce a consistent ordering based on color without relying on the ordering of the
8668 // highlight's `TypeId` which is non-executor.
8669 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
8670 assert_eq!(
8671 highlighted_ranges,
8672 &[
8673 (
8674 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
8675 Hsla::red(),
8676 ),
8677 (
8678 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
8679 Hsla::red(),
8680 ),
8681 (
8682 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
8683 Hsla::green(),
8684 ),
8685 (
8686 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
8687 Hsla::green(),
8688 ),
8689 ]
8690 );
8691 assert_eq!(
8692 editor.background_highlights_in_range(
8693 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
8694 &snapshot,
8695 cx.theme().colors(),
8696 ),
8697 &[(
8698 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
8699 Hsla::red(),
8700 )]
8701 );
8702 });
8703}
8704
8705#[gpui::test]
8706async fn test_following(cx: &mut gpui::TestAppContext) {
8707 init_test(cx, |_| {});
8708
8709 let fs = FakeFs::new(cx.executor());
8710 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8711
8712 let buffer = project.update(cx, |project, cx| {
8713 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
8714 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
8715 });
8716 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
8717 let follower = cx.update(|cx| {
8718 cx.open_window(
8719 WindowOptions {
8720 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
8721 gpui::Point::new(px(0.), px(0.)),
8722 gpui::Point::new(px(10.), px(80.)),
8723 ))),
8724 ..Default::default()
8725 },
8726 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
8727 )
8728 .unwrap()
8729 });
8730
8731 let is_still_following = Rc::new(RefCell::new(true));
8732 let follower_edit_event_count = Rc::new(RefCell::new(0));
8733 let pending_update = Rc::new(RefCell::new(None));
8734 _ = follower.update(cx, {
8735 let update = pending_update.clone();
8736 let is_still_following = is_still_following.clone();
8737 let follower_edit_event_count = follower_edit_event_count.clone();
8738 |_, cx| {
8739 cx.subscribe(
8740 &leader.root_view(cx).unwrap(),
8741 move |_, leader, event, cx| {
8742 leader
8743 .read(cx)
8744 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
8745 },
8746 )
8747 .detach();
8748
8749 cx.subscribe(
8750 &follower.root_view(cx).unwrap(),
8751 move |_, _, event: &EditorEvent, _cx| {
8752 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
8753 *is_still_following.borrow_mut() = false;
8754 }
8755
8756 if let EditorEvent::BufferEdited = event {
8757 *follower_edit_event_count.borrow_mut() += 1;
8758 }
8759 },
8760 )
8761 .detach();
8762 }
8763 });
8764
8765 // Update the selections only
8766 _ = leader.update(cx, |leader, cx| {
8767 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
8768 });
8769 follower
8770 .update(cx, |follower, cx| {
8771 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8772 })
8773 .unwrap()
8774 .await
8775 .unwrap();
8776 _ = follower.update(cx, |follower, cx| {
8777 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
8778 });
8779 assert_eq!(*is_still_following.borrow(), true);
8780 assert_eq!(*follower_edit_event_count.borrow(), 0);
8781
8782 // Update the scroll position only
8783 _ = leader.update(cx, |leader, cx| {
8784 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
8785 });
8786 follower
8787 .update(cx, |follower, cx| {
8788 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8789 })
8790 .unwrap()
8791 .await
8792 .unwrap();
8793 assert_eq!(
8794 follower
8795 .update(cx, |follower, cx| follower.scroll_position(cx))
8796 .unwrap(),
8797 gpui::Point::new(1.5, 3.5)
8798 );
8799 assert_eq!(*is_still_following.borrow(), true);
8800 assert_eq!(*follower_edit_event_count.borrow(), 0);
8801
8802 // Update the selections and scroll position. The follower's scroll position is updated
8803 // via autoscroll, not via the leader's exact scroll position.
8804 _ = leader.update(cx, |leader, cx| {
8805 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
8806 leader.request_autoscroll(Autoscroll::newest(), cx);
8807 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
8808 });
8809 follower
8810 .update(cx, |follower, cx| {
8811 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8812 })
8813 .unwrap()
8814 .await
8815 .unwrap();
8816 _ = follower.update(cx, |follower, cx| {
8817 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
8818 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
8819 });
8820 assert_eq!(*is_still_following.borrow(), true);
8821
8822 // Creating a pending selection that precedes another selection
8823 _ = leader.update(cx, |leader, cx| {
8824 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
8825 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
8826 });
8827 follower
8828 .update(cx, |follower, cx| {
8829 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8830 })
8831 .unwrap()
8832 .await
8833 .unwrap();
8834 _ = follower.update(cx, |follower, cx| {
8835 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
8836 });
8837 assert_eq!(*is_still_following.borrow(), true);
8838
8839 // Extend the pending selection so that it surrounds another selection
8840 _ = leader.update(cx, |leader, cx| {
8841 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
8842 });
8843 follower
8844 .update(cx, |follower, cx| {
8845 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8846 })
8847 .unwrap()
8848 .await
8849 .unwrap();
8850 _ = follower.update(cx, |follower, cx| {
8851 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
8852 });
8853
8854 // Scrolling locally breaks the follow
8855 _ = follower.update(cx, |follower, cx| {
8856 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
8857 follower.set_scroll_anchor(
8858 ScrollAnchor {
8859 anchor: top_anchor,
8860 offset: gpui::Point::new(0.0, 0.5),
8861 },
8862 cx,
8863 );
8864 });
8865 assert_eq!(*is_still_following.borrow(), false);
8866}
8867
8868#[gpui::test]
8869async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
8870 init_test(cx, |_| {});
8871
8872 let fs = FakeFs::new(cx.executor());
8873 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8874 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8875 let pane = workspace
8876 .update(cx, |workspace, _| workspace.active_pane().clone())
8877 .unwrap();
8878
8879 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
8880
8881 let leader = pane.update(cx, |_, cx| {
8882 let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
8883 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
8884 });
8885
8886 // Start following the editor when it has no excerpts.
8887 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
8888 let follower_1 = cx
8889 .update_window(*workspace.deref(), |_, cx| {
8890 Editor::from_state_proto(
8891 workspace.root_view(cx).unwrap(),
8892 ViewId {
8893 creator: Default::default(),
8894 id: 0,
8895 },
8896 &mut state_message,
8897 cx,
8898 )
8899 })
8900 .unwrap()
8901 .unwrap()
8902 .await
8903 .unwrap();
8904
8905 let update_message = Rc::new(RefCell::new(None));
8906 follower_1.update(cx, {
8907 let update = update_message.clone();
8908 |_, cx| {
8909 cx.subscribe(&leader, move |_, leader, event, cx| {
8910 leader
8911 .read(cx)
8912 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
8913 })
8914 .detach();
8915 }
8916 });
8917
8918 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
8919 (
8920 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
8921 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
8922 )
8923 });
8924
8925 // Insert some excerpts.
8926 _ = leader.update(cx, |leader, cx| {
8927 leader.buffer.update(cx, |multibuffer, cx| {
8928 let excerpt_ids = multibuffer.push_excerpts(
8929 buffer_1.clone(),
8930 [
8931 ExcerptRange {
8932 context: 1..6,
8933 primary: None,
8934 },
8935 ExcerptRange {
8936 context: 12..15,
8937 primary: None,
8938 },
8939 ExcerptRange {
8940 context: 0..3,
8941 primary: None,
8942 },
8943 ],
8944 cx,
8945 );
8946 multibuffer.insert_excerpts_after(
8947 excerpt_ids[0],
8948 buffer_2.clone(),
8949 [
8950 ExcerptRange {
8951 context: 8..12,
8952 primary: None,
8953 },
8954 ExcerptRange {
8955 context: 0..6,
8956 primary: None,
8957 },
8958 ],
8959 cx,
8960 );
8961 });
8962 });
8963
8964 // Apply the update of adding the excerpts.
8965 follower_1
8966 .update(cx, |follower, cx| {
8967 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
8968 })
8969 .await
8970 .unwrap();
8971 assert_eq!(
8972 follower_1.update(cx, |editor, cx| editor.text(cx)),
8973 leader.update(cx, |editor, cx| editor.text(cx))
8974 );
8975 update_message.borrow_mut().take();
8976
8977 // Start following separately after it already has excerpts.
8978 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
8979 let follower_2 = cx
8980 .update_window(*workspace.deref(), |_, cx| {
8981 Editor::from_state_proto(
8982 workspace.root_view(cx).unwrap().clone(),
8983 ViewId {
8984 creator: Default::default(),
8985 id: 0,
8986 },
8987 &mut state_message,
8988 cx,
8989 )
8990 })
8991 .unwrap()
8992 .unwrap()
8993 .await
8994 .unwrap();
8995 assert_eq!(
8996 follower_2.update(cx, |editor, cx| editor.text(cx)),
8997 leader.update(cx, |editor, cx| editor.text(cx))
8998 );
8999
9000 // Remove some excerpts.
9001 _ = leader.update(cx, |leader, cx| {
9002 leader.buffer.update(cx, |multibuffer, cx| {
9003 let excerpt_ids = multibuffer.excerpt_ids();
9004 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9005 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9006 });
9007 });
9008
9009 // Apply the update of removing the excerpts.
9010 follower_1
9011 .update(cx, |follower, cx| {
9012 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9013 })
9014 .await
9015 .unwrap();
9016 follower_2
9017 .update(cx, |follower, cx| {
9018 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9019 })
9020 .await
9021 .unwrap();
9022 update_message.borrow_mut().take();
9023 assert_eq!(
9024 follower_1.update(cx, |editor, cx| editor.text(cx)),
9025 leader.update(cx, |editor, cx| editor.text(cx))
9026 );
9027}
9028
9029#[gpui::test]
9030async fn go_to_prev_overlapping_diagnostic(
9031 executor: BackgroundExecutor,
9032 cx: &mut gpui::TestAppContext,
9033) {
9034 init_test(cx, |_| {});
9035
9036 let mut cx = EditorTestContext::new(cx).await;
9037 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9038
9039 cx.set_state(indoc! {"
9040 ˇfn func(abc def: i32) -> u32 {
9041 }
9042 "});
9043
9044 _ = cx.update(|cx| {
9045 _ = project.update(cx, |project, cx| {
9046 project
9047 .update_diagnostics(
9048 LanguageServerId(0),
9049 lsp::PublishDiagnosticsParams {
9050 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9051 version: None,
9052 diagnostics: vec![
9053 lsp::Diagnostic {
9054 range: lsp::Range::new(
9055 lsp::Position::new(0, 11),
9056 lsp::Position::new(0, 12),
9057 ),
9058 severity: Some(lsp::DiagnosticSeverity::ERROR),
9059 ..Default::default()
9060 },
9061 lsp::Diagnostic {
9062 range: lsp::Range::new(
9063 lsp::Position::new(0, 12),
9064 lsp::Position::new(0, 15),
9065 ),
9066 severity: Some(lsp::DiagnosticSeverity::ERROR),
9067 ..Default::default()
9068 },
9069 lsp::Diagnostic {
9070 range: lsp::Range::new(
9071 lsp::Position::new(0, 25),
9072 lsp::Position::new(0, 28),
9073 ),
9074 severity: Some(lsp::DiagnosticSeverity::ERROR),
9075 ..Default::default()
9076 },
9077 ],
9078 },
9079 &[],
9080 cx,
9081 )
9082 .unwrap()
9083 });
9084 });
9085
9086 executor.run_until_parked();
9087
9088 cx.update_editor(|editor, cx| {
9089 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9090 });
9091
9092 cx.assert_editor_state(indoc! {"
9093 fn func(abc def: i32) -> ˇu32 {
9094 }
9095 "});
9096
9097 cx.update_editor(|editor, cx| {
9098 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9099 });
9100
9101 cx.assert_editor_state(indoc! {"
9102 fn func(abc ˇdef: i32) -> u32 {
9103 }
9104 "});
9105
9106 cx.update_editor(|editor, cx| {
9107 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9108 });
9109
9110 cx.assert_editor_state(indoc! {"
9111 fn func(abcˇ def: i32) -> u32 {
9112 }
9113 "});
9114
9115 cx.update_editor(|editor, cx| {
9116 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9117 });
9118
9119 cx.assert_editor_state(indoc! {"
9120 fn func(abc def: i32) -> ˇu32 {
9121 }
9122 "});
9123}
9124
9125#[gpui::test]
9126async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
9127 init_test(cx, |_| {});
9128
9129 let mut cx = EditorTestContext::new(cx).await;
9130
9131 cx.set_state(indoc! {"
9132 fn func(abˇc def: i32) -> u32 {
9133 }
9134 "});
9135 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9136
9137 cx.update(|cx| {
9138 project.update(cx, |project, cx| {
9139 project.update_diagnostics(
9140 LanguageServerId(0),
9141 lsp::PublishDiagnosticsParams {
9142 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9143 version: None,
9144 diagnostics: vec![lsp::Diagnostic {
9145 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
9146 severity: Some(lsp::DiagnosticSeverity::ERROR),
9147 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
9148 ..Default::default()
9149 }],
9150 },
9151 &[],
9152 cx,
9153 )
9154 })
9155 }).unwrap();
9156 cx.run_until_parked();
9157 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
9158 cx.run_until_parked();
9159 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
9160}
9161
9162#[gpui::test]
9163async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9164 init_test(cx, |_| {});
9165
9166 let mut cx = EditorTestContext::new(cx).await;
9167
9168 let diff_base = r#"
9169 use some::mod;
9170
9171 const A: u32 = 42;
9172
9173 fn main() {
9174 println!("hello");
9175
9176 println!("world");
9177 }
9178 "#
9179 .unindent();
9180
9181 // Edits are modified, removed, modified, added
9182 cx.set_state(
9183 &r#"
9184 use some::modified;
9185
9186 ˇ
9187 fn main() {
9188 println!("hello there");
9189
9190 println!("around the");
9191 println!("world");
9192 }
9193 "#
9194 .unindent(),
9195 );
9196
9197 cx.set_diff_base(Some(&diff_base));
9198 executor.run_until_parked();
9199
9200 cx.update_editor(|editor, cx| {
9201 //Wrap around the bottom of the buffer
9202 for _ in 0..3 {
9203 editor.go_to_hunk(&GoToHunk, cx);
9204 }
9205 });
9206
9207 cx.assert_editor_state(
9208 &r#"
9209 ˇuse some::modified;
9210
9211
9212 fn main() {
9213 println!("hello there");
9214
9215 println!("around the");
9216 println!("world");
9217 }
9218 "#
9219 .unindent(),
9220 );
9221
9222 cx.update_editor(|editor, cx| {
9223 //Wrap around the top of the buffer
9224 for _ in 0..2 {
9225 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9226 }
9227 });
9228
9229 cx.assert_editor_state(
9230 &r#"
9231 use some::modified;
9232
9233
9234 fn main() {
9235 ˇ println!("hello there");
9236
9237 println!("around the");
9238 println!("world");
9239 }
9240 "#
9241 .unindent(),
9242 );
9243
9244 cx.update_editor(|editor, cx| {
9245 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9246 });
9247
9248 cx.assert_editor_state(
9249 &r#"
9250 use some::modified;
9251
9252 ˇ
9253 fn main() {
9254 println!("hello there");
9255
9256 println!("around the");
9257 println!("world");
9258 }
9259 "#
9260 .unindent(),
9261 );
9262
9263 cx.update_editor(|editor, cx| {
9264 for _ in 0..3 {
9265 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9266 }
9267 });
9268
9269 cx.assert_editor_state(
9270 &r#"
9271 use some::modified;
9272
9273
9274 fn main() {
9275 ˇ println!("hello there");
9276
9277 println!("around the");
9278 println!("world");
9279 }
9280 "#
9281 .unindent(),
9282 );
9283
9284 cx.update_editor(|editor, cx| {
9285 editor.fold(&Fold, cx);
9286
9287 //Make sure that the fold only gets one hunk
9288 for _ in 0..4 {
9289 editor.go_to_hunk(&GoToHunk, cx);
9290 }
9291 });
9292
9293 cx.assert_editor_state(
9294 &r#"
9295 ˇuse some::modified;
9296
9297
9298 fn main() {
9299 println!("hello there");
9300
9301 println!("around the");
9302 println!("world");
9303 }
9304 "#
9305 .unindent(),
9306 );
9307}
9308
9309#[test]
9310fn test_split_words() {
9311 fn split(text: &str) -> Vec<&str> {
9312 split_words(text).collect()
9313 }
9314
9315 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
9316 assert_eq!(split("hello_world"), &["hello_", "world"]);
9317 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
9318 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
9319 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
9320 assert_eq!(split("helloworld"), &["helloworld"]);
9321
9322 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
9323}
9324
9325#[gpui::test]
9326async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
9327 init_test(cx, |_| {});
9328
9329 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
9330 let mut assert = |before, after| {
9331 let _state_context = cx.set_state(before);
9332 cx.update_editor(|editor, cx| {
9333 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
9334 });
9335 cx.assert_editor_state(after);
9336 };
9337
9338 // Outside bracket jumps to outside of matching bracket
9339 assert("console.logˇ(var);", "console.log(var)ˇ;");
9340 assert("console.log(var)ˇ;", "console.logˇ(var);");
9341
9342 // Inside bracket jumps to inside of matching bracket
9343 assert("console.log(ˇvar);", "console.log(varˇ);");
9344 assert("console.log(varˇ);", "console.log(ˇvar);");
9345
9346 // When outside a bracket and inside, favor jumping to the inside bracket
9347 assert(
9348 "console.log('foo', [1, 2, 3]ˇ);",
9349 "console.log(ˇ'foo', [1, 2, 3]);",
9350 );
9351 assert(
9352 "console.log(ˇ'foo', [1, 2, 3]);",
9353 "console.log('foo', [1, 2, 3]ˇ);",
9354 );
9355
9356 // Bias forward if two options are equally likely
9357 assert(
9358 "let result = curried_fun()ˇ();",
9359 "let result = curried_fun()()ˇ;",
9360 );
9361
9362 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
9363 assert(
9364 indoc! {"
9365 function test() {
9366 console.log('test')ˇ
9367 }"},
9368 indoc! {"
9369 function test() {
9370 console.logˇ('test')
9371 }"},
9372 );
9373}
9374
9375#[gpui::test]
9376async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
9377 init_test(cx, |_| {});
9378
9379 let fs = FakeFs::new(cx.executor());
9380 fs.insert_tree(
9381 "/a",
9382 json!({
9383 "main.rs": "fn main() { let a = 5; }",
9384 "other.rs": "// Test file",
9385 }),
9386 )
9387 .await;
9388 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9389
9390 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9391 language_registry.add(Arc::new(Language::new(
9392 LanguageConfig {
9393 name: "Rust".into(),
9394 matcher: LanguageMatcher {
9395 path_suffixes: vec!["rs".to_string()],
9396 ..Default::default()
9397 },
9398 brackets: BracketPairConfig {
9399 pairs: vec![BracketPair {
9400 start: "{".to_string(),
9401 end: "}".to_string(),
9402 close: true,
9403 surround: true,
9404 newline: true,
9405 }],
9406 disabled_scopes_by_bracket_ix: Vec::new(),
9407 },
9408 ..Default::default()
9409 },
9410 Some(tree_sitter_rust::language()),
9411 )));
9412 let mut fake_servers = language_registry.register_fake_lsp_adapter(
9413 "Rust",
9414 FakeLspAdapter {
9415 capabilities: lsp::ServerCapabilities {
9416 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
9417 first_trigger_character: "{".to_string(),
9418 more_trigger_character: None,
9419 }),
9420 ..Default::default()
9421 },
9422 ..Default::default()
9423 },
9424 );
9425
9426 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9427
9428 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9429
9430 let worktree_id = workspace
9431 .update(cx, |workspace, cx| {
9432 workspace.project().update(cx, |project, cx| {
9433 project.worktrees(cx).next().unwrap().read(cx).id()
9434 })
9435 })
9436 .unwrap();
9437
9438 let buffer = project
9439 .update(cx, |project, cx| {
9440 project.open_local_buffer("/a/main.rs", cx)
9441 })
9442 .await
9443 .unwrap();
9444 cx.executor().run_until_parked();
9445 cx.executor().start_waiting();
9446 let fake_server = fake_servers.next().await.unwrap();
9447 let editor_handle = workspace
9448 .update(cx, |workspace, cx| {
9449 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
9450 })
9451 .unwrap()
9452 .await
9453 .unwrap()
9454 .downcast::<Editor>()
9455 .unwrap();
9456
9457 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
9458 assert_eq!(
9459 params.text_document_position.text_document.uri,
9460 lsp::Url::from_file_path("/a/main.rs").unwrap(),
9461 );
9462 assert_eq!(
9463 params.text_document_position.position,
9464 lsp::Position::new(0, 21),
9465 );
9466
9467 Ok(Some(vec![lsp::TextEdit {
9468 new_text: "]".to_string(),
9469 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
9470 }]))
9471 });
9472
9473 editor_handle.update(cx, |editor, cx| {
9474 editor.focus(cx);
9475 editor.change_selections(None, cx, |s| {
9476 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
9477 });
9478 editor.handle_input("{", cx);
9479 });
9480
9481 cx.executor().run_until_parked();
9482
9483 _ = buffer.update(cx, |buffer, _| {
9484 assert_eq!(
9485 buffer.text(),
9486 "fn main() { let a = {5}; }",
9487 "No extra braces from on type formatting should appear in the buffer"
9488 )
9489 });
9490}
9491
9492#[gpui::test]
9493async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
9494 init_test(cx, |_| {});
9495
9496 let fs = FakeFs::new(cx.executor());
9497 fs.insert_tree(
9498 "/a",
9499 json!({
9500 "main.rs": "fn main() { let a = 5; }",
9501 "other.rs": "// Test file",
9502 }),
9503 )
9504 .await;
9505
9506 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9507
9508 let server_restarts = Arc::new(AtomicUsize::new(0));
9509 let closure_restarts = Arc::clone(&server_restarts);
9510 let language_server_name = "test language server";
9511 let language_name: Arc<str> = "Rust".into();
9512
9513 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9514 language_registry.add(Arc::new(Language::new(
9515 LanguageConfig {
9516 name: Arc::clone(&language_name),
9517 matcher: LanguageMatcher {
9518 path_suffixes: vec!["rs".to_string()],
9519 ..Default::default()
9520 },
9521 ..Default::default()
9522 },
9523 Some(tree_sitter_rust::language()),
9524 )));
9525 let mut fake_servers = language_registry.register_fake_lsp_adapter(
9526 "Rust",
9527 FakeLspAdapter {
9528 name: language_server_name,
9529 initialization_options: Some(json!({
9530 "testOptionValue": true
9531 })),
9532 initializer: Some(Box::new(move |fake_server| {
9533 let task_restarts = Arc::clone(&closure_restarts);
9534 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
9535 task_restarts.fetch_add(1, atomic::Ordering::Release);
9536 futures::future::ready(Ok(()))
9537 });
9538 })),
9539 ..Default::default()
9540 },
9541 );
9542
9543 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9544 let _buffer = project
9545 .update(cx, |project, cx| {
9546 project.open_local_buffer("/a/main.rs", cx)
9547 })
9548 .await
9549 .unwrap();
9550 let _fake_server = fake_servers.next().await.unwrap();
9551 update_test_language_settings(cx, |language_settings| {
9552 language_settings.languages.insert(
9553 Arc::clone(&language_name),
9554 LanguageSettingsContent {
9555 tab_size: NonZeroU32::new(8),
9556 ..Default::default()
9557 },
9558 );
9559 });
9560 cx.executor().run_until_parked();
9561 assert_eq!(
9562 server_restarts.load(atomic::Ordering::Acquire),
9563 0,
9564 "Should not restart LSP server on an unrelated change"
9565 );
9566
9567 update_test_project_settings(cx, |project_settings| {
9568 project_settings.lsp.insert(
9569 "Some other server name".into(),
9570 LspSettings {
9571 binary: None,
9572 settings: None,
9573 initialization_options: Some(json!({
9574 "some other init value": false
9575 })),
9576 },
9577 );
9578 });
9579 cx.executor().run_until_parked();
9580 assert_eq!(
9581 server_restarts.load(atomic::Ordering::Acquire),
9582 0,
9583 "Should not restart LSP server on an unrelated LSP settings change"
9584 );
9585
9586 update_test_project_settings(cx, |project_settings| {
9587 project_settings.lsp.insert(
9588 language_server_name.into(),
9589 LspSettings {
9590 binary: None,
9591 settings: None,
9592 initialization_options: Some(json!({
9593 "anotherInitValue": false
9594 })),
9595 },
9596 );
9597 });
9598 cx.executor().run_until_parked();
9599 assert_eq!(
9600 server_restarts.load(atomic::Ordering::Acquire),
9601 1,
9602 "Should restart LSP server on a related LSP settings change"
9603 );
9604
9605 update_test_project_settings(cx, |project_settings| {
9606 project_settings.lsp.insert(
9607 language_server_name.into(),
9608 LspSettings {
9609 binary: None,
9610 settings: None,
9611 initialization_options: Some(json!({
9612 "anotherInitValue": false
9613 })),
9614 },
9615 );
9616 });
9617 cx.executor().run_until_parked();
9618 assert_eq!(
9619 server_restarts.load(atomic::Ordering::Acquire),
9620 1,
9621 "Should not restart LSP server on a related LSP settings change that is the same"
9622 );
9623
9624 update_test_project_settings(cx, |project_settings| {
9625 project_settings.lsp.insert(
9626 language_server_name.into(),
9627 LspSettings {
9628 binary: None,
9629 settings: None,
9630 initialization_options: None,
9631 },
9632 );
9633 });
9634 cx.executor().run_until_parked();
9635 assert_eq!(
9636 server_restarts.load(atomic::Ordering::Acquire),
9637 2,
9638 "Should restart LSP server on another related LSP settings change"
9639 );
9640}
9641
9642#[gpui::test]
9643async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
9644 init_test(cx, |_| {});
9645
9646 let mut cx = EditorLspTestContext::new_rust(
9647 lsp::ServerCapabilities {
9648 completion_provider: Some(lsp::CompletionOptions {
9649 trigger_characters: Some(vec![".".to_string()]),
9650 resolve_provider: Some(true),
9651 ..Default::default()
9652 }),
9653 ..Default::default()
9654 },
9655 cx,
9656 )
9657 .await;
9658
9659 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9660 cx.simulate_keystroke(".");
9661 let completion_item = lsp::CompletionItem {
9662 label: "some".into(),
9663 kind: Some(lsp::CompletionItemKind::SNIPPET),
9664 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9665 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9666 kind: lsp::MarkupKind::Markdown,
9667 value: "```rust\nSome(2)\n```".to_string(),
9668 })),
9669 deprecated: Some(false),
9670 sort_text: Some("fffffff2".to_string()),
9671 filter_text: Some("some".to_string()),
9672 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9673 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9674 range: lsp::Range {
9675 start: lsp::Position {
9676 line: 0,
9677 character: 22,
9678 },
9679 end: lsp::Position {
9680 line: 0,
9681 character: 22,
9682 },
9683 },
9684 new_text: "Some(2)".to_string(),
9685 })),
9686 additional_text_edits: Some(vec![lsp::TextEdit {
9687 range: lsp::Range {
9688 start: lsp::Position {
9689 line: 0,
9690 character: 20,
9691 },
9692 end: lsp::Position {
9693 line: 0,
9694 character: 22,
9695 },
9696 },
9697 new_text: "".to_string(),
9698 }]),
9699 ..Default::default()
9700 };
9701
9702 let closure_completion_item = completion_item.clone();
9703 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9704 let task_completion_item = closure_completion_item.clone();
9705 async move {
9706 Ok(Some(lsp::CompletionResponse::Array(vec![
9707 task_completion_item,
9708 ])))
9709 }
9710 });
9711
9712 request.next().await;
9713
9714 cx.condition(|editor, _| editor.context_menu_visible())
9715 .await;
9716 let apply_additional_edits = cx.update_editor(|editor, cx| {
9717 editor
9718 .confirm_completion(&ConfirmCompletion::default(), cx)
9719 .unwrap()
9720 });
9721 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
9722
9723 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
9724 let task_completion_item = completion_item.clone();
9725 async move { Ok(task_completion_item) }
9726 })
9727 .next()
9728 .await
9729 .unwrap();
9730 apply_additional_edits.await.unwrap();
9731 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
9732}
9733
9734#[gpui::test]
9735async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
9736 init_test(cx, |_| {});
9737
9738 let mut cx = EditorLspTestContext::new(
9739 Language::new(
9740 LanguageConfig {
9741 matcher: LanguageMatcher {
9742 path_suffixes: vec!["jsx".into()],
9743 ..Default::default()
9744 },
9745 overrides: [(
9746 "element".into(),
9747 LanguageConfigOverride {
9748 word_characters: Override::Set(['-'].into_iter().collect()),
9749 ..Default::default()
9750 },
9751 )]
9752 .into_iter()
9753 .collect(),
9754 ..Default::default()
9755 },
9756 Some(tree_sitter_typescript::language_tsx()),
9757 )
9758 .with_override_query("(jsx_self_closing_element) @element")
9759 .unwrap(),
9760 lsp::ServerCapabilities {
9761 completion_provider: Some(lsp::CompletionOptions {
9762 trigger_characters: Some(vec![":".to_string()]),
9763 ..Default::default()
9764 }),
9765 ..Default::default()
9766 },
9767 cx,
9768 )
9769 .await;
9770
9771 cx.lsp
9772 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9773 Ok(Some(lsp::CompletionResponse::Array(vec![
9774 lsp::CompletionItem {
9775 label: "bg-blue".into(),
9776 ..Default::default()
9777 },
9778 lsp::CompletionItem {
9779 label: "bg-red".into(),
9780 ..Default::default()
9781 },
9782 lsp::CompletionItem {
9783 label: "bg-yellow".into(),
9784 ..Default::default()
9785 },
9786 ])))
9787 });
9788
9789 cx.set_state(r#"<p class="bgˇ" />"#);
9790
9791 // Trigger completion when typing a dash, because the dash is an extra
9792 // word character in the 'element' scope, which contains the cursor.
9793 cx.simulate_keystroke("-");
9794 cx.executor().run_until_parked();
9795 cx.update_editor(|editor, _| {
9796 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9797 assert_eq!(
9798 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9799 &["bg-red", "bg-blue", "bg-yellow"]
9800 );
9801 } else {
9802 panic!("expected completion menu to be open");
9803 }
9804 });
9805
9806 cx.simulate_keystroke("l");
9807 cx.executor().run_until_parked();
9808 cx.update_editor(|editor, _| {
9809 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9810 assert_eq!(
9811 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9812 &["bg-blue", "bg-yellow"]
9813 );
9814 } else {
9815 panic!("expected completion menu to be open");
9816 }
9817 });
9818
9819 // When filtering completions, consider the character after the '-' to
9820 // be the start of a subword.
9821 cx.set_state(r#"<p class="yelˇ" />"#);
9822 cx.simulate_keystroke("l");
9823 cx.executor().run_until_parked();
9824 cx.update_editor(|editor, _| {
9825 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9826 assert_eq!(
9827 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9828 &["bg-yellow"]
9829 );
9830 } else {
9831 panic!("expected completion menu to be open");
9832 }
9833 });
9834}
9835
9836#[gpui::test]
9837async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
9838 init_test(cx, |settings| {
9839 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
9840 FormatterList(vec![Formatter::Prettier].into()),
9841 ))
9842 });
9843
9844 let fs = FakeFs::new(cx.executor());
9845 fs.insert_file("/file.ts", Default::default()).await;
9846
9847 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
9848 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9849
9850 language_registry.add(Arc::new(Language::new(
9851 LanguageConfig {
9852 name: "TypeScript".into(),
9853 matcher: LanguageMatcher {
9854 path_suffixes: vec!["ts".to_string()],
9855 ..Default::default()
9856 },
9857 ..Default::default()
9858 },
9859 Some(tree_sitter_rust::language()),
9860 )));
9861 update_test_language_settings(cx, |settings| {
9862 settings.defaults.prettier = Some(PrettierSettings {
9863 allowed: true,
9864 ..PrettierSettings::default()
9865 });
9866 });
9867
9868 let test_plugin = "test_plugin";
9869 let _ = language_registry.register_fake_lsp_adapter(
9870 "TypeScript",
9871 FakeLspAdapter {
9872 prettier_plugins: vec![test_plugin],
9873 ..Default::default()
9874 },
9875 );
9876
9877 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
9878 let buffer = project
9879 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
9880 .await
9881 .unwrap();
9882
9883 let buffer_text = "one\ntwo\nthree\n";
9884 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9885 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9886 _ = editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
9887
9888 editor
9889 .update(cx, |editor, cx| {
9890 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
9891 })
9892 .unwrap()
9893 .await;
9894 assert_eq!(
9895 editor.update(cx, |editor, cx| editor.text(cx)),
9896 buffer_text.to_string() + prettier_format_suffix,
9897 "Test prettier formatting was not applied to the original buffer text",
9898 );
9899
9900 update_test_language_settings(cx, |settings| {
9901 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
9902 });
9903 let format = editor.update(cx, |editor, cx| {
9904 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
9905 });
9906 format.await.unwrap();
9907 assert_eq!(
9908 editor.update(cx, |editor, cx| editor.text(cx)),
9909 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
9910 "Autoformatting (via test prettier) was not applied to the original buffer text",
9911 );
9912}
9913
9914#[gpui::test]
9915async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
9916 init_test(cx, |_| {});
9917 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9918 let base_text = indoc! {r#"struct Row;
9919struct Row1;
9920struct Row2;
9921
9922struct Row4;
9923struct Row5;
9924struct Row6;
9925
9926struct Row8;
9927struct Row9;
9928struct Row10;"#};
9929
9930 // When addition hunks are not adjacent to carets, no hunk revert is performed
9931 assert_hunk_revert(
9932 indoc! {r#"struct Row;
9933 struct Row1;
9934 struct Row1.1;
9935 struct Row1.2;
9936 struct Row2;ˇ
9937
9938 struct Row4;
9939 struct Row5;
9940 struct Row6;
9941
9942 struct Row8;
9943 ˇstruct Row9;
9944 struct Row9.1;
9945 struct Row9.2;
9946 struct Row9.3;
9947 struct Row10;"#},
9948 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
9949 indoc! {r#"struct Row;
9950 struct Row1;
9951 struct Row1.1;
9952 struct Row1.2;
9953 struct Row2;ˇ
9954
9955 struct Row4;
9956 struct Row5;
9957 struct Row6;
9958
9959 struct Row8;
9960 ˇstruct Row9;
9961 struct Row9.1;
9962 struct Row9.2;
9963 struct Row9.3;
9964 struct Row10;"#},
9965 base_text,
9966 &mut cx,
9967 );
9968 // Same for selections
9969 assert_hunk_revert(
9970 indoc! {r#"struct Row;
9971 struct Row1;
9972 struct Row2;
9973 struct Row2.1;
9974 struct Row2.2;
9975 «ˇ
9976 struct Row4;
9977 struct» Row5;
9978 «struct Row6;
9979 ˇ»
9980 struct Row9.1;
9981 struct Row9.2;
9982 struct Row9.3;
9983 struct Row8;
9984 struct Row9;
9985 struct Row10;"#},
9986 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
9987 indoc! {r#"struct Row;
9988 struct Row1;
9989 struct Row2;
9990 struct Row2.1;
9991 struct Row2.2;
9992 «ˇ
9993 struct Row4;
9994 struct» Row5;
9995 «struct Row6;
9996 ˇ»
9997 struct Row9.1;
9998 struct Row9.2;
9999 struct Row9.3;
10000 struct Row8;
10001 struct Row9;
10002 struct Row10;"#},
10003 base_text,
10004 &mut cx,
10005 );
10006
10007 // When carets and selections intersect the addition hunks, those are reverted.
10008 // Adjacent carets got merged.
10009 assert_hunk_revert(
10010 indoc! {r#"struct Row;
10011 ˇ// something on the top
10012 struct Row1;
10013 struct Row2;
10014 struct Roˇw3.1;
10015 struct Row2.2;
10016 struct Row2.3;ˇ
10017
10018 struct Row4;
10019 struct ˇRow5.1;
10020 struct Row5.2;
10021 struct «Rowˇ»5.3;
10022 struct Row5;
10023 struct Row6;
10024 ˇ
10025 struct Row9.1;
10026 struct «Rowˇ»9.2;
10027 struct «ˇRow»9.3;
10028 struct Row8;
10029 struct Row9;
10030 «ˇ// something on bottom»
10031 struct Row10;"#},
10032 vec![
10033 DiffHunkStatus::Added,
10034 DiffHunkStatus::Added,
10035 DiffHunkStatus::Added,
10036 DiffHunkStatus::Added,
10037 DiffHunkStatus::Added,
10038 ],
10039 indoc! {r#"struct Row;
10040 ˇstruct Row1;
10041 struct Row2;
10042 ˇ
10043 struct Row4;
10044 ˇstruct Row5;
10045 struct Row6;
10046 ˇ
10047 ˇstruct Row8;
10048 struct Row9;
10049 ˇstruct Row10;"#},
10050 base_text,
10051 &mut cx,
10052 );
10053}
10054
10055#[gpui::test]
10056async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
10057 init_test(cx, |_| {});
10058 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10059 let base_text = indoc! {r#"struct Row;
10060struct Row1;
10061struct Row2;
10062
10063struct Row4;
10064struct Row5;
10065struct Row6;
10066
10067struct Row8;
10068struct Row9;
10069struct Row10;"#};
10070
10071 // Modification hunks behave the same as the addition ones.
10072 assert_hunk_revert(
10073 indoc! {r#"struct Row;
10074 struct Row1;
10075 struct Row33;
10076 ˇ
10077 struct Row4;
10078 struct Row5;
10079 struct Row6;
10080 ˇ
10081 struct Row99;
10082 struct Row9;
10083 struct Row10;"#},
10084 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10085 indoc! {r#"struct Row;
10086 struct Row1;
10087 struct Row33;
10088 ˇ
10089 struct Row4;
10090 struct Row5;
10091 struct Row6;
10092 ˇ
10093 struct Row99;
10094 struct Row9;
10095 struct Row10;"#},
10096 base_text,
10097 &mut cx,
10098 );
10099 assert_hunk_revert(
10100 indoc! {r#"struct Row;
10101 struct Row1;
10102 struct Row33;
10103 «ˇ
10104 struct Row4;
10105 struct» Row5;
10106 «struct Row6;
10107 ˇ»
10108 struct Row99;
10109 struct Row9;
10110 struct Row10;"#},
10111 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10112 indoc! {r#"struct Row;
10113 struct Row1;
10114 struct Row33;
10115 «ˇ
10116 struct Row4;
10117 struct» Row5;
10118 «struct Row6;
10119 ˇ»
10120 struct Row99;
10121 struct Row9;
10122 struct Row10;"#},
10123 base_text,
10124 &mut cx,
10125 );
10126
10127 assert_hunk_revert(
10128 indoc! {r#"ˇstruct Row1.1;
10129 struct Row1;
10130 «ˇstr»uct Row22;
10131
10132 struct ˇRow44;
10133 struct Row5;
10134 struct «Rˇ»ow66;ˇ
10135
10136 «struˇ»ct Row88;
10137 struct Row9;
10138 struct Row1011;ˇ"#},
10139 vec![
10140 DiffHunkStatus::Modified,
10141 DiffHunkStatus::Modified,
10142 DiffHunkStatus::Modified,
10143 DiffHunkStatus::Modified,
10144 DiffHunkStatus::Modified,
10145 DiffHunkStatus::Modified,
10146 ],
10147 indoc! {r#"struct Row;
10148 ˇstruct Row1;
10149 struct Row2;
10150 ˇ
10151 struct Row4;
10152 ˇstruct Row5;
10153 struct Row6;
10154 ˇ
10155 struct Row8;
10156 ˇstruct Row9;
10157 struct Row10;ˇ"#},
10158 base_text,
10159 &mut cx,
10160 );
10161}
10162
10163#[gpui::test]
10164async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
10165 init_test(cx, |_| {});
10166 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10167 let base_text = indoc! {r#"struct Row;
10168struct Row1;
10169struct Row2;
10170
10171struct Row4;
10172struct Row5;
10173struct Row6;
10174
10175struct Row8;
10176struct Row9;
10177struct Row10;"#};
10178
10179 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
10180 assert_hunk_revert(
10181 indoc! {r#"struct Row;
10182 struct Row2;
10183
10184 ˇstruct Row4;
10185 struct Row5;
10186 struct Row6;
10187 ˇ
10188 struct Row8;
10189 struct Row10;"#},
10190 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10191 indoc! {r#"struct Row;
10192 struct Row2;
10193
10194 ˇstruct Row4;
10195 struct Row5;
10196 struct Row6;
10197 ˇ
10198 struct Row8;
10199 struct Row10;"#},
10200 base_text,
10201 &mut cx,
10202 );
10203 assert_hunk_revert(
10204 indoc! {r#"struct Row;
10205 struct Row2;
10206
10207 «ˇstruct Row4;
10208 struct» Row5;
10209 «struct Row6;
10210 ˇ»
10211 struct Row8;
10212 struct Row10;"#},
10213 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10214 indoc! {r#"struct Row;
10215 struct Row2;
10216
10217 «ˇstruct Row4;
10218 struct» Row5;
10219 «struct Row6;
10220 ˇ»
10221 struct Row8;
10222 struct Row10;"#},
10223 base_text,
10224 &mut cx,
10225 );
10226
10227 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
10228 assert_hunk_revert(
10229 indoc! {r#"struct Row;
10230 ˇstruct Row2;
10231
10232 struct Row4;
10233 struct Row5;
10234 struct Row6;
10235
10236 struct Row8;ˇ
10237 struct Row10;"#},
10238 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10239 indoc! {r#"struct Row;
10240 struct Row1;
10241 ˇstruct Row2;
10242
10243 struct Row4;
10244 struct Row5;
10245 struct Row6;
10246
10247 struct Row8;ˇ
10248 struct Row9;
10249 struct Row10;"#},
10250 base_text,
10251 &mut cx,
10252 );
10253 assert_hunk_revert(
10254 indoc! {r#"struct Row;
10255 struct Row2«ˇ;
10256 struct Row4;
10257 struct» Row5;
10258 «struct Row6;
10259
10260 struct Row8;ˇ»
10261 struct Row10;"#},
10262 vec![
10263 DiffHunkStatus::Removed,
10264 DiffHunkStatus::Removed,
10265 DiffHunkStatus::Removed,
10266 ],
10267 indoc! {r#"struct Row;
10268 struct Row1;
10269 struct Row2«ˇ;
10270
10271 struct Row4;
10272 struct» Row5;
10273 «struct Row6;
10274
10275 struct Row8;ˇ»
10276 struct Row9;
10277 struct Row10;"#},
10278 base_text,
10279 &mut cx,
10280 );
10281}
10282
10283#[gpui::test]
10284async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
10285 init_test(cx, |_| {});
10286
10287 let cols = 4;
10288 let rows = 10;
10289 let sample_text_1 = sample_text(rows, cols, 'a');
10290 assert_eq!(
10291 sample_text_1,
10292 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10293 );
10294 let sample_text_2 = sample_text(rows, cols, 'l');
10295 assert_eq!(
10296 sample_text_2,
10297 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10298 );
10299 let sample_text_3 = sample_text(rows, cols, 'v');
10300 assert_eq!(
10301 sample_text_3,
10302 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10303 );
10304
10305 fn diff_every_buffer_row(
10306 buffer: &Model<Buffer>,
10307 sample_text: String,
10308 cols: usize,
10309 cx: &mut gpui::TestAppContext,
10310 ) {
10311 // revert first character in each row, creating one large diff hunk per buffer
10312 let is_first_char = |offset: usize| offset % cols == 0;
10313 buffer.update(cx, |buffer, cx| {
10314 buffer.set_text(
10315 sample_text
10316 .chars()
10317 .enumerate()
10318 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
10319 .collect::<String>(),
10320 cx,
10321 );
10322 buffer.set_diff_base(Some(sample_text), cx);
10323 });
10324 cx.executor().run_until_parked();
10325 }
10326
10327 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10328 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10329
10330 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10331 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10332
10333 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10334 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10335
10336 let multibuffer = cx.new_model(|cx| {
10337 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10338 multibuffer.push_excerpts(
10339 buffer_1.clone(),
10340 [
10341 ExcerptRange {
10342 context: Point::new(0, 0)..Point::new(3, 0),
10343 primary: None,
10344 },
10345 ExcerptRange {
10346 context: Point::new(5, 0)..Point::new(7, 0),
10347 primary: None,
10348 },
10349 ExcerptRange {
10350 context: Point::new(9, 0)..Point::new(10, 4),
10351 primary: None,
10352 },
10353 ],
10354 cx,
10355 );
10356 multibuffer.push_excerpts(
10357 buffer_2.clone(),
10358 [
10359 ExcerptRange {
10360 context: Point::new(0, 0)..Point::new(3, 0),
10361 primary: None,
10362 },
10363 ExcerptRange {
10364 context: Point::new(5, 0)..Point::new(7, 0),
10365 primary: None,
10366 },
10367 ExcerptRange {
10368 context: Point::new(9, 0)..Point::new(10, 4),
10369 primary: None,
10370 },
10371 ],
10372 cx,
10373 );
10374 multibuffer.push_excerpts(
10375 buffer_3.clone(),
10376 [
10377 ExcerptRange {
10378 context: Point::new(0, 0)..Point::new(3, 0),
10379 primary: None,
10380 },
10381 ExcerptRange {
10382 context: Point::new(5, 0)..Point::new(7, 0),
10383 primary: None,
10384 },
10385 ExcerptRange {
10386 context: Point::new(9, 0)..Point::new(10, 4),
10387 primary: None,
10388 },
10389 ],
10390 cx,
10391 );
10392 multibuffer
10393 });
10394
10395 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
10396 editor.update(cx, |editor, cx| {
10397 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");
10398 editor.select_all(&SelectAll, cx);
10399 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10400 });
10401 cx.executor().run_until_parked();
10402 // When all ranges are selected, all buffer hunks are reverted.
10403 editor.update(cx, |editor, cx| {
10404 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");
10405 });
10406 buffer_1.update(cx, |buffer, _| {
10407 assert_eq!(buffer.text(), sample_text_1);
10408 });
10409 buffer_2.update(cx, |buffer, _| {
10410 assert_eq!(buffer.text(), sample_text_2);
10411 });
10412 buffer_3.update(cx, |buffer, _| {
10413 assert_eq!(buffer.text(), sample_text_3);
10414 });
10415
10416 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10417 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10418 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10419 editor.update(cx, |editor, cx| {
10420 editor.change_selections(None, cx, |s| {
10421 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
10422 });
10423 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10424 });
10425 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
10426 // but not affect buffer_2 and its related excerpts.
10427 editor.update(cx, |editor, cx| {
10428 assert_eq!(
10429 editor.text(cx),
10430 "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"
10431 );
10432 });
10433 buffer_1.update(cx, |buffer, _| {
10434 assert_eq!(buffer.text(), sample_text_1);
10435 });
10436 buffer_2.update(cx, |buffer, _| {
10437 assert_eq!(
10438 buffer.text(),
10439 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
10440 );
10441 });
10442 buffer_3.update(cx, |buffer, _| {
10443 assert_eq!(
10444 buffer.text(),
10445 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
10446 );
10447 });
10448}
10449
10450#[gpui::test]
10451async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
10452 init_test(cx, |_| {});
10453
10454 let cols = 4;
10455 let rows = 10;
10456 let sample_text_1 = sample_text(rows, cols, 'a');
10457 assert_eq!(
10458 sample_text_1,
10459 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10460 );
10461 let sample_text_2 = sample_text(rows, cols, 'l');
10462 assert_eq!(
10463 sample_text_2,
10464 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10465 );
10466 let sample_text_3 = sample_text(rows, cols, 'v');
10467 assert_eq!(
10468 sample_text_3,
10469 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10470 );
10471
10472 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10473 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10474 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10475
10476 let multi_buffer = cx.new_model(|cx| {
10477 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10478 multibuffer.push_excerpts(
10479 buffer_1.clone(),
10480 [
10481 ExcerptRange {
10482 context: Point::new(0, 0)..Point::new(3, 0),
10483 primary: None,
10484 },
10485 ExcerptRange {
10486 context: Point::new(5, 0)..Point::new(7, 0),
10487 primary: None,
10488 },
10489 ExcerptRange {
10490 context: Point::new(9, 0)..Point::new(10, 4),
10491 primary: None,
10492 },
10493 ],
10494 cx,
10495 );
10496 multibuffer.push_excerpts(
10497 buffer_2.clone(),
10498 [
10499 ExcerptRange {
10500 context: Point::new(0, 0)..Point::new(3, 0),
10501 primary: None,
10502 },
10503 ExcerptRange {
10504 context: Point::new(5, 0)..Point::new(7, 0),
10505 primary: None,
10506 },
10507 ExcerptRange {
10508 context: Point::new(9, 0)..Point::new(10, 4),
10509 primary: None,
10510 },
10511 ],
10512 cx,
10513 );
10514 multibuffer.push_excerpts(
10515 buffer_3.clone(),
10516 [
10517 ExcerptRange {
10518 context: Point::new(0, 0)..Point::new(3, 0),
10519 primary: None,
10520 },
10521 ExcerptRange {
10522 context: Point::new(5, 0)..Point::new(7, 0),
10523 primary: None,
10524 },
10525 ExcerptRange {
10526 context: Point::new(9, 0)..Point::new(10, 4),
10527 primary: None,
10528 },
10529 ],
10530 cx,
10531 );
10532 multibuffer
10533 });
10534
10535 let fs = FakeFs::new(cx.executor());
10536 fs.insert_tree(
10537 "/a",
10538 json!({
10539 "main.rs": sample_text_1,
10540 "other.rs": sample_text_2,
10541 "lib.rs": sample_text_3,
10542 }),
10543 )
10544 .await;
10545 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10546 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10547 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10548 let multi_buffer_editor = cx.new_view(|cx| {
10549 Editor::new(
10550 EditorMode::Full,
10551 multi_buffer,
10552 Some(project.clone()),
10553 true,
10554 cx,
10555 )
10556 });
10557 let multibuffer_item_id = workspace
10558 .update(cx, |workspace, cx| {
10559 assert!(
10560 workspace.active_item(cx).is_none(),
10561 "active item should be None before the first item is added"
10562 );
10563 workspace.add_item_to_active_pane(
10564 Box::new(multi_buffer_editor.clone()),
10565 None,
10566 true,
10567 cx,
10568 );
10569 let active_item = workspace
10570 .active_item(cx)
10571 .expect("should have an active item after adding the multi buffer");
10572 assert!(
10573 !active_item.is_singleton(cx),
10574 "A multi buffer was expected to active after adding"
10575 );
10576 active_item.item_id()
10577 })
10578 .unwrap();
10579 cx.executor().run_until_parked();
10580
10581 multi_buffer_editor.update(cx, |editor, cx| {
10582 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
10583 editor.open_excerpts(&OpenExcerpts, cx);
10584 });
10585 cx.executor().run_until_parked();
10586 let first_item_id = workspace
10587 .update(cx, |workspace, cx| {
10588 let active_item = workspace
10589 .active_item(cx)
10590 .expect("should have an active item after navigating into the 1st buffer");
10591 let first_item_id = active_item.item_id();
10592 assert_ne!(
10593 first_item_id, multibuffer_item_id,
10594 "Should navigate into the 1st buffer and activate it"
10595 );
10596 assert!(
10597 active_item.is_singleton(cx),
10598 "New active item should be a singleton buffer"
10599 );
10600 assert_eq!(
10601 active_item
10602 .act_as::<Editor>(cx)
10603 .expect("should have navigated into an editor for the 1st buffer")
10604 .read(cx)
10605 .text(cx),
10606 sample_text_1
10607 );
10608
10609 workspace
10610 .go_back(workspace.active_pane().downgrade(), cx)
10611 .detach_and_log_err(cx);
10612
10613 first_item_id
10614 })
10615 .unwrap();
10616 cx.executor().run_until_parked();
10617 workspace
10618 .update(cx, |workspace, cx| {
10619 let active_item = workspace
10620 .active_item(cx)
10621 .expect("should have an active item after navigating back");
10622 assert_eq!(
10623 active_item.item_id(),
10624 multibuffer_item_id,
10625 "Should navigate back to the multi buffer"
10626 );
10627 assert!(!active_item.is_singleton(cx));
10628 })
10629 .unwrap();
10630
10631 multi_buffer_editor.update(cx, |editor, cx| {
10632 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
10633 s.select_ranges(Some(39..40))
10634 });
10635 editor.open_excerpts(&OpenExcerpts, cx);
10636 });
10637 cx.executor().run_until_parked();
10638 let second_item_id = workspace
10639 .update(cx, |workspace, cx| {
10640 let active_item = workspace
10641 .active_item(cx)
10642 .expect("should have an active item after navigating into the 2nd buffer");
10643 let second_item_id = active_item.item_id();
10644 assert_ne!(
10645 second_item_id, multibuffer_item_id,
10646 "Should navigate away from the multibuffer"
10647 );
10648 assert_ne!(
10649 second_item_id, first_item_id,
10650 "Should navigate into the 2nd buffer and activate it"
10651 );
10652 assert!(
10653 active_item.is_singleton(cx),
10654 "New active item should be a singleton buffer"
10655 );
10656 assert_eq!(
10657 active_item
10658 .act_as::<Editor>(cx)
10659 .expect("should have navigated into an editor")
10660 .read(cx)
10661 .text(cx),
10662 sample_text_2
10663 );
10664
10665 workspace
10666 .go_back(workspace.active_pane().downgrade(), cx)
10667 .detach_and_log_err(cx);
10668
10669 second_item_id
10670 })
10671 .unwrap();
10672 cx.executor().run_until_parked();
10673 workspace
10674 .update(cx, |workspace, cx| {
10675 let active_item = workspace
10676 .active_item(cx)
10677 .expect("should have an active item after navigating back from the 2nd buffer");
10678 assert_eq!(
10679 active_item.item_id(),
10680 multibuffer_item_id,
10681 "Should navigate back from the 2nd buffer to the multi buffer"
10682 );
10683 assert!(!active_item.is_singleton(cx));
10684 })
10685 .unwrap();
10686
10687 multi_buffer_editor.update(cx, |editor, cx| {
10688 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
10689 s.select_ranges(Some(60..70))
10690 });
10691 editor.open_excerpts(&OpenExcerpts, cx);
10692 });
10693 cx.executor().run_until_parked();
10694 workspace
10695 .update(cx, |workspace, cx| {
10696 let active_item = workspace
10697 .active_item(cx)
10698 .expect("should have an active item after navigating into the 3rd buffer");
10699 let third_item_id = active_item.item_id();
10700 assert_ne!(
10701 third_item_id, multibuffer_item_id,
10702 "Should navigate into the 3rd buffer and activate it"
10703 );
10704 assert_ne!(third_item_id, first_item_id);
10705 assert_ne!(third_item_id, second_item_id);
10706 assert!(
10707 active_item.is_singleton(cx),
10708 "New active item should be a singleton buffer"
10709 );
10710 assert_eq!(
10711 active_item
10712 .act_as::<Editor>(cx)
10713 .expect("should have navigated into an editor")
10714 .read(cx)
10715 .text(cx),
10716 sample_text_3
10717 );
10718
10719 workspace
10720 .go_back(workspace.active_pane().downgrade(), cx)
10721 .detach_and_log_err(cx);
10722 })
10723 .unwrap();
10724 cx.executor().run_until_parked();
10725 workspace
10726 .update(cx, |workspace, cx| {
10727 let active_item = workspace
10728 .active_item(cx)
10729 .expect("should have an active item after navigating back from the 3rd buffer");
10730 assert_eq!(
10731 active_item.item_id(),
10732 multibuffer_item_id,
10733 "Should navigate back from the 3rd buffer to the multi buffer"
10734 );
10735 assert!(!active_item.is_singleton(cx));
10736 })
10737 .unwrap();
10738}
10739
10740#[gpui::test]
10741async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10742 init_test(cx, |_| {});
10743
10744 let mut cx = EditorTestContext::new(cx).await;
10745
10746 let diff_base = r#"
10747 use some::mod;
10748
10749 const A: u32 = 42;
10750
10751 fn main() {
10752 println!("hello");
10753
10754 println!("world");
10755 }
10756 "#
10757 .unindent();
10758
10759 cx.set_state(
10760 &r#"
10761 use some::modified;
10762
10763 ˇ
10764 fn main() {
10765 println!("hello there");
10766
10767 println!("around the");
10768 println!("world");
10769 }
10770 "#
10771 .unindent(),
10772 );
10773
10774 cx.set_diff_base(Some(&diff_base));
10775 executor.run_until_parked();
10776 let unexpanded_hunks = vec![
10777 (
10778 "use some::mod;\n".to_string(),
10779 DiffHunkStatus::Modified,
10780 DisplayRow(0)..DisplayRow(1),
10781 ),
10782 (
10783 "const A: u32 = 42;\n".to_string(),
10784 DiffHunkStatus::Removed,
10785 DisplayRow(2)..DisplayRow(2),
10786 ),
10787 (
10788 " println!(\"hello\");\n".to_string(),
10789 DiffHunkStatus::Modified,
10790 DisplayRow(4)..DisplayRow(5),
10791 ),
10792 (
10793 "".to_string(),
10794 DiffHunkStatus::Added,
10795 DisplayRow(6)..DisplayRow(7),
10796 ),
10797 ];
10798 cx.update_editor(|editor, cx| {
10799 let snapshot = editor.snapshot(cx);
10800 let all_hunks = editor_hunks(editor, &snapshot, cx);
10801 assert_eq!(all_hunks, unexpanded_hunks);
10802 });
10803
10804 cx.update_editor(|editor, cx| {
10805 for _ in 0..4 {
10806 editor.go_to_hunk(&GoToHunk, cx);
10807 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10808 }
10809 });
10810 executor.run_until_parked();
10811 cx.assert_editor_state(
10812 &r#"
10813 use some::modified;
10814
10815 ˇ
10816 fn main() {
10817 println!("hello there");
10818
10819 println!("around the");
10820 println!("world");
10821 }
10822 "#
10823 .unindent(),
10824 );
10825 cx.update_editor(|editor, cx| {
10826 let snapshot = editor.snapshot(cx);
10827 let all_hunks = editor_hunks(editor, &snapshot, cx);
10828 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10829 assert_eq!(
10830 expanded_hunks_background_highlights(editor, cx),
10831 vec![DisplayRow(1)..=DisplayRow(1), DisplayRow(7)..=DisplayRow(7), DisplayRow(9)..=DisplayRow(9)],
10832 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
10833 );
10834 assert_eq!(
10835 all_hunks,
10836 vec![
10837 ("use some::mod;\n".to_string(), DiffHunkStatus::Modified, DisplayRow(1)..DisplayRow(2)),
10838 ("const A: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(4)..DisplayRow(4)),
10839 (" println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(7)..DisplayRow(8)),
10840 ("".to_string(), DiffHunkStatus::Added, DisplayRow(9)..DisplayRow(10)),
10841 ],
10842 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
10843 (from modified and removed hunks)"
10844 );
10845 assert_eq!(
10846 all_hunks, all_expanded_hunks,
10847 "Editor hunks should not change and all be expanded"
10848 );
10849 });
10850
10851 cx.update_editor(|editor, cx| {
10852 editor.cancel(&Cancel, cx);
10853
10854 let snapshot = editor.snapshot(cx);
10855 let all_hunks = editor_hunks(editor, &snapshot, cx);
10856 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
10857 assert_eq!(
10858 expanded_hunks_background_highlights(editor, cx),
10859 Vec::new(),
10860 "After cancelling in editor, no git highlights should be left"
10861 );
10862 assert_eq!(
10863 all_expanded_hunks,
10864 Vec::new(),
10865 "After cancelling in editor, no hunks should be expanded"
10866 );
10867 assert_eq!(
10868 all_hunks, unexpanded_hunks,
10869 "After cancelling in editor, regular hunks' coordinates should get back to normal"
10870 );
10871 });
10872}
10873
10874#[gpui::test]
10875async fn test_toggled_diff_base_change(
10876 executor: BackgroundExecutor,
10877 cx: &mut gpui::TestAppContext,
10878) {
10879 init_test(cx, |_| {});
10880
10881 let mut cx = EditorTestContext::new(cx).await;
10882
10883 let diff_base = r#"
10884 use some::mod1;
10885 use some::mod2;
10886
10887 const A: u32 = 42;
10888 const B: u32 = 42;
10889 const C: u32 = 42;
10890
10891 fn main(ˇ) {
10892 println!("hello");
10893
10894 println!("world");
10895 }
10896 "#
10897 .unindent();
10898
10899 cx.set_state(
10900 &r#"
10901 use some::mod2;
10902
10903 const A: u32 = 42;
10904 const C: u32 = 42;
10905
10906 fn main(ˇ) {
10907 //println!("hello");
10908
10909 println!("world");
10910 //
10911 //
10912 }
10913 "#
10914 .unindent(),
10915 );
10916
10917 cx.set_diff_base(Some(&diff_base));
10918 executor.run_until_parked();
10919 cx.update_editor(|editor, cx| {
10920 let snapshot = editor.snapshot(cx);
10921 let all_hunks = editor_hunks(editor, &snapshot, cx);
10922 assert_eq!(
10923 all_hunks,
10924 vec![
10925 (
10926 "use some::mod1;\n".to_string(),
10927 DiffHunkStatus::Removed,
10928 DisplayRow(0)..DisplayRow(0)
10929 ),
10930 (
10931 "const B: u32 = 42;\n".to_string(),
10932 DiffHunkStatus::Removed,
10933 DisplayRow(3)..DisplayRow(3)
10934 ),
10935 (
10936 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
10937 DiffHunkStatus::Modified,
10938 DisplayRow(5)..DisplayRow(7)
10939 ),
10940 (
10941 "".to_string(),
10942 DiffHunkStatus::Added,
10943 DisplayRow(9)..DisplayRow(11)
10944 ),
10945 ]
10946 );
10947 });
10948
10949 cx.update_editor(|editor, cx| {
10950 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10951 });
10952 executor.run_until_parked();
10953 cx.assert_editor_state(
10954 &r#"
10955 use some::mod2;
10956
10957 const A: u32 = 42;
10958 const C: u32 = 42;
10959
10960 fn main(ˇ) {
10961 //println!("hello");
10962
10963 println!("world");
10964 //
10965 //
10966 }
10967 "#
10968 .unindent(),
10969 );
10970 cx.update_editor(|editor, cx| {
10971 let snapshot = editor.snapshot(cx);
10972 let all_hunks = editor_hunks(editor, &snapshot, cx);
10973 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10974 assert_eq!(
10975 expanded_hunks_background_highlights(editor, cx),
10976 vec![DisplayRow(9)..=DisplayRow(10), DisplayRow(13)..=DisplayRow(14)],
10977 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
10978 );
10979 assert_eq!(
10980 all_hunks,
10981 vec![
10982 ("use some::mod1;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(1)..DisplayRow(1)),
10983 ("const B: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(5)..DisplayRow(5)),
10984 ("fn main(ˇ) {\n println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(9)..DisplayRow(11)),
10985 ("".to_string(), DiffHunkStatus::Added, DisplayRow(13)..DisplayRow(15)),
10986 ],
10987 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
10988 (from modified and removed hunks)"
10989 );
10990 assert_eq!(
10991 all_hunks, all_expanded_hunks,
10992 "Editor hunks should not change and all be expanded"
10993 );
10994 });
10995
10996 cx.set_diff_base(Some("new diff base!"));
10997 executor.run_until_parked();
10998
10999 cx.update_editor(|editor, cx| {
11000 let snapshot = editor.snapshot(cx);
11001 let all_hunks = editor_hunks(editor, &snapshot, cx);
11002 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
11003 assert_eq!(
11004 expanded_hunks_background_highlights(editor, cx),
11005 Vec::new(),
11006 "After diff base is changed, old git highlights should be removed"
11007 );
11008 assert_eq!(
11009 all_expanded_hunks,
11010 Vec::new(),
11011 "After diff base is changed, old git hunk expansions should be removed"
11012 );
11013 assert_eq!(
11014 all_hunks,
11015 vec![(
11016 "new diff base!".to_string(),
11017 DiffHunkStatus::Modified,
11018 DisplayRow(0)..snapshot.display_snapshot.max_point().row()
11019 )],
11020 "After diff base is changed, hunks should update"
11021 );
11022 });
11023}
11024
11025#[gpui::test]
11026async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11027 init_test(cx, |_| {});
11028
11029 let mut cx = EditorTestContext::new(cx).await;
11030
11031 let diff_base = r#"
11032 use some::mod1;
11033 use some::mod2;
11034
11035 const A: u32 = 42;
11036 const B: u32 = 42;
11037 const C: u32 = 42;
11038
11039 fn main(ˇ) {
11040 println!("hello");
11041
11042 println!("world");
11043 }
11044
11045 fn another() {
11046 println!("another");
11047 }
11048
11049 fn another2() {
11050 println!("another2");
11051 }
11052 "#
11053 .unindent();
11054
11055 cx.set_state(
11056 &r#"
11057 «use some::mod2;
11058
11059 const A: u32 = 42;
11060 const C: u32 = 42;
11061
11062 fn main() {
11063 //println!("hello");
11064
11065 println!("world");
11066 //
11067 //ˇ»
11068 }
11069
11070 fn another() {
11071 println!("another");
11072 println!("another");
11073 }
11074
11075 println!("another2");
11076 }
11077 "#
11078 .unindent(),
11079 );
11080
11081 cx.set_diff_base(Some(&diff_base));
11082 executor.run_until_parked();
11083 cx.update_editor(|editor, cx| {
11084 let snapshot = editor.snapshot(cx);
11085 let all_hunks = editor_hunks(editor, &snapshot, cx);
11086 assert_eq!(
11087 all_hunks,
11088 vec![
11089 (
11090 "use some::mod1;\n".to_string(),
11091 DiffHunkStatus::Removed,
11092 DisplayRow(0)..DisplayRow(0)
11093 ),
11094 (
11095 "const B: u32 = 42;\n".to_string(),
11096 DiffHunkStatus::Removed,
11097 DisplayRow(3)..DisplayRow(3)
11098 ),
11099 (
11100 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11101 DiffHunkStatus::Modified,
11102 DisplayRow(5)..DisplayRow(7)
11103 ),
11104 (
11105 "".to_string(),
11106 DiffHunkStatus::Added,
11107 DisplayRow(9)..DisplayRow(11)
11108 ),
11109 (
11110 "".to_string(),
11111 DiffHunkStatus::Added,
11112 DisplayRow(15)..DisplayRow(16)
11113 ),
11114 (
11115 "fn another2() {\n".to_string(),
11116 DiffHunkStatus::Removed,
11117 DisplayRow(18)..DisplayRow(18)
11118 ),
11119 ]
11120 );
11121 });
11122
11123 cx.update_editor(|editor, cx| {
11124 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11125 });
11126 executor.run_until_parked();
11127 cx.assert_editor_state(
11128 &r#"
11129 «use some::mod2;
11130
11131 const A: u32 = 42;
11132 const C: u32 = 42;
11133
11134 fn main() {
11135 //println!("hello");
11136
11137 println!("world");
11138 //
11139 //ˇ»
11140 }
11141
11142 fn another() {
11143 println!("another");
11144 println!("another");
11145 }
11146
11147 println!("another2");
11148 }
11149 "#
11150 .unindent(),
11151 );
11152 cx.update_editor(|editor, cx| {
11153 let snapshot = editor.snapshot(cx);
11154 let all_hunks = editor_hunks(editor, &snapshot, cx);
11155 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11156 assert_eq!(
11157 expanded_hunks_background_highlights(editor, cx),
11158 vec![
11159 DisplayRow(9)..=DisplayRow(10),
11160 DisplayRow(13)..=DisplayRow(14),
11161 DisplayRow(19)..=DisplayRow(19)
11162 ]
11163 );
11164 assert_eq!(
11165 all_hunks,
11166 vec![
11167 (
11168 "use some::mod1;\n".to_string(),
11169 DiffHunkStatus::Removed,
11170 DisplayRow(1)..DisplayRow(1)
11171 ),
11172 (
11173 "const B: u32 = 42;\n".to_string(),
11174 DiffHunkStatus::Removed,
11175 DisplayRow(5)..DisplayRow(5)
11176 ),
11177 (
11178 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11179 DiffHunkStatus::Modified,
11180 DisplayRow(9)..DisplayRow(11)
11181 ),
11182 (
11183 "".to_string(),
11184 DiffHunkStatus::Added,
11185 DisplayRow(13)..DisplayRow(15)
11186 ),
11187 (
11188 "".to_string(),
11189 DiffHunkStatus::Added,
11190 DisplayRow(19)..DisplayRow(20)
11191 ),
11192 (
11193 "fn another2() {\n".to_string(),
11194 DiffHunkStatus::Removed,
11195 DisplayRow(23)..DisplayRow(23)
11196 ),
11197 ],
11198 );
11199 assert_eq!(all_hunks, all_expanded_hunks);
11200 });
11201
11202 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
11203 cx.executor().run_until_parked();
11204 cx.assert_editor_state(
11205 &r#"
11206 «use some::mod2;
11207
11208 const A: u32 = 42;
11209 const C: u32 = 42;
11210
11211 fn main() {
11212 //println!("hello");
11213
11214 println!("world");
11215 //
11216 //ˇ»
11217 }
11218
11219 fn another() {
11220 println!("another");
11221 println!("another");
11222 }
11223
11224 println!("another2");
11225 }
11226 "#
11227 .unindent(),
11228 );
11229 cx.update_editor(|editor, cx| {
11230 let snapshot = editor.snapshot(cx);
11231 let all_hunks = editor_hunks(editor, &snapshot, cx);
11232 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11233 assert_eq!(
11234 expanded_hunks_background_highlights(editor, cx),
11235 vec![DisplayRow(0)..=DisplayRow(0), DisplayRow(5)..=DisplayRow(5)],
11236 "Only one hunk is left not folded, its highlight should be visible"
11237 );
11238 assert_eq!(
11239 all_hunks,
11240 vec![
11241 (
11242 "use some::mod1;\n".to_string(),
11243 DiffHunkStatus::Removed,
11244 DisplayRow(0)..DisplayRow(0)
11245 ),
11246 (
11247 "const B: u32 = 42;\n".to_string(),
11248 DiffHunkStatus::Removed,
11249 DisplayRow(0)..DisplayRow(0)
11250 ),
11251 (
11252 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11253 DiffHunkStatus::Modified,
11254 DisplayRow(0)..DisplayRow(0)
11255 ),
11256 (
11257 "".to_string(),
11258 DiffHunkStatus::Added,
11259 DisplayRow(0)..DisplayRow(1)
11260 ),
11261 (
11262 "".to_string(),
11263 DiffHunkStatus::Added,
11264 DisplayRow(5)..DisplayRow(6)
11265 ),
11266 (
11267 "fn another2() {\n".to_string(),
11268 DiffHunkStatus::Removed,
11269 DisplayRow(9)..DisplayRow(9)
11270 ),
11271 ],
11272 "Hunk list should still return shifted folded hunks"
11273 );
11274 assert_eq!(
11275 all_expanded_hunks,
11276 vec![
11277 (
11278 "".to_string(),
11279 DiffHunkStatus::Added,
11280 DisplayRow(5)..DisplayRow(6)
11281 ),
11282 (
11283 "fn another2() {\n".to_string(),
11284 DiffHunkStatus::Removed,
11285 DisplayRow(9)..DisplayRow(9)
11286 ),
11287 ],
11288 "Only non-folded hunks should be left expanded"
11289 );
11290 });
11291
11292 cx.update_editor(|editor, cx| {
11293 editor.select_all(&SelectAll, cx);
11294 editor.unfold_lines(&UnfoldLines, cx);
11295 });
11296 cx.executor().run_until_parked();
11297 cx.assert_editor_state(
11298 &r#"
11299 «use some::mod2;
11300
11301 const A: u32 = 42;
11302 const C: u32 = 42;
11303
11304 fn main() {
11305 //println!("hello");
11306
11307 println!("world");
11308 //
11309 //
11310 }
11311
11312 fn another() {
11313 println!("another");
11314 println!("another");
11315 }
11316
11317 println!("another2");
11318 }
11319 ˇ»"#
11320 .unindent(),
11321 );
11322 cx.update_editor(|editor, cx| {
11323 let snapshot = editor.snapshot(cx);
11324 let all_hunks = editor_hunks(editor, &snapshot, cx);
11325 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11326 assert_eq!(
11327 expanded_hunks_background_highlights(editor, cx),
11328 vec![
11329 DisplayRow(9)..=DisplayRow(10),
11330 DisplayRow(13)..=DisplayRow(14),
11331 DisplayRow(19)..=DisplayRow(19)
11332 ],
11333 "After unfolding, all hunk diffs should be visible again"
11334 );
11335 assert_eq!(
11336 all_hunks,
11337 vec![
11338 (
11339 "use some::mod1;\n".to_string(),
11340 DiffHunkStatus::Removed,
11341 DisplayRow(1)..DisplayRow(1)
11342 ),
11343 (
11344 "const B: u32 = 42;\n".to_string(),
11345 DiffHunkStatus::Removed,
11346 DisplayRow(5)..DisplayRow(5)
11347 ),
11348 (
11349 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11350 DiffHunkStatus::Modified,
11351 DisplayRow(9)..DisplayRow(11)
11352 ),
11353 (
11354 "".to_string(),
11355 DiffHunkStatus::Added,
11356 DisplayRow(13)..DisplayRow(15)
11357 ),
11358 (
11359 "".to_string(),
11360 DiffHunkStatus::Added,
11361 DisplayRow(19)..DisplayRow(20)
11362 ),
11363 (
11364 "fn another2() {\n".to_string(),
11365 DiffHunkStatus::Removed,
11366 DisplayRow(23)..DisplayRow(23)
11367 ),
11368 ],
11369 );
11370 assert_eq!(all_hunks, all_expanded_hunks);
11371 });
11372}
11373
11374#[gpui::test]
11375async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
11376 init_test(cx, |_| {});
11377
11378 let cols = 4;
11379 let rows = 10;
11380 let sample_text_1 = sample_text(rows, cols, 'a');
11381 assert_eq!(
11382 sample_text_1,
11383 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11384 );
11385 let modified_sample_text_1 = "aaaa\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
11386 let sample_text_2 = sample_text(rows, cols, 'l');
11387 assert_eq!(
11388 sample_text_2,
11389 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11390 );
11391 let modified_sample_text_2 = "llll\nmmmm\n1n1n1n1n1\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
11392 let sample_text_3 = sample_text(rows, cols, 'v');
11393 assert_eq!(
11394 sample_text_3,
11395 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11396 );
11397 let modified_sample_text_3 =
11398 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n@@@@\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
11399 let buffer_1 = cx.new_model(|cx| {
11400 let mut buffer = Buffer::local(modified_sample_text_1.to_string(), cx);
11401 buffer.set_diff_base(Some(sample_text_1.clone()), cx);
11402 buffer
11403 });
11404 let buffer_2 = cx.new_model(|cx| {
11405 let mut buffer = Buffer::local(modified_sample_text_2.to_string(), cx);
11406 buffer.set_diff_base(Some(sample_text_2.clone()), cx);
11407 buffer
11408 });
11409 let buffer_3 = cx.new_model(|cx| {
11410 let mut buffer = Buffer::local(modified_sample_text_3.to_string(), cx);
11411 buffer.set_diff_base(Some(sample_text_3.clone()), cx);
11412 buffer
11413 });
11414
11415 let multi_buffer = cx.new_model(|cx| {
11416 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
11417 multibuffer.push_excerpts(
11418 buffer_1.clone(),
11419 [
11420 ExcerptRange {
11421 context: Point::new(0, 0)..Point::new(3, 0),
11422 primary: None,
11423 },
11424 ExcerptRange {
11425 context: Point::new(5, 0)..Point::new(7, 0),
11426 primary: None,
11427 },
11428 ExcerptRange {
11429 context: Point::new(9, 0)..Point::new(10, 4),
11430 primary: None,
11431 },
11432 ],
11433 cx,
11434 );
11435 multibuffer.push_excerpts(
11436 buffer_2.clone(),
11437 [
11438 ExcerptRange {
11439 context: Point::new(0, 0)..Point::new(3, 0),
11440 primary: None,
11441 },
11442 ExcerptRange {
11443 context: Point::new(5, 0)..Point::new(7, 0),
11444 primary: None,
11445 },
11446 ExcerptRange {
11447 context: Point::new(9, 0)..Point::new(10, 4),
11448 primary: None,
11449 },
11450 ],
11451 cx,
11452 );
11453 multibuffer.push_excerpts(
11454 buffer_3.clone(),
11455 [
11456 ExcerptRange {
11457 context: Point::new(0, 0)..Point::new(3, 0),
11458 primary: None,
11459 },
11460 ExcerptRange {
11461 context: Point::new(5, 0)..Point::new(7, 0),
11462 primary: None,
11463 },
11464 ExcerptRange {
11465 context: Point::new(9, 0)..Point::new(10, 4),
11466 primary: None,
11467 },
11468 ],
11469 cx,
11470 );
11471 multibuffer
11472 });
11473
11474 let fs = FakeFs::new(cx.executor());
11475 fs.insert_tree(
11476 "/a",
11477 json!({
11478 "main.rs": modified_sample_text_1,
11479 "other.rs": modified_sample_text_2,
11480 "lib.rs": modified_sample_text_3,
11481 }),
11482 )
11483 .await;
11484
11485 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11486 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11487 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11488 let multi_buffer_editor = cx.new_view(|cx| {
11489 Editor::new(
11490 EditorMode::Full,
11491 multi_buffer,
11492 Some(project.clone()),
11493 true,
11494 cx,
11495 )
11496 });
11497 cx.executor().run_until_parked();
11498
11499 let expected_all_hunks = vec![
11500 (
11501 "bbbb\n".to_string(),
11502 DiffHunkStatus::Removed,
11503 DisplayRow(4)..DisplayRow(4),
11504 ),
11505 (
11506 "nnnn\n".to_string(),
11507 DiffHunkStatus::Modified,
11508 DisplayRow(21)..DisplayRow(22),
11509 ),
11510 (
11511 "".to_string(),
11512 DiffHunkStatus::Added,
11513 DisplayRow(41)..DisplayRow(42),
11514 ),
11515 ];
11516 let expected_all_hunks_shifted = vec![
11517 (
11518 "bbbb\n".to_string(),
11519 DiffHunkStatus::Removed,
11520 DisplayRow(5)..DisplayRow(5),
11521 ),
11522 (
11523 "nnnn\n".to_string(),
11524 DiffHunkStatus::Modified,
11525 DisplayRow(23)..DisplayRow(24),
11526 ),
11527 (
11528 "".to_string(),
11529 DiffHunkStatus::Added,
11530 DisplayRow(43)..DisplayRow(44),
11531 ),
11532 ];
11533
11534 multi_buffer_editor.update(cx, |editor, cx| {
11535 let snapshot = editor.snapshot(cx);
11536 let all_hunks = editor_hunks(editor, &snapshot, cx);
11537 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11538 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11539 assert_eq!(all_hunks, expected_all_hunks);
11540 assert_eq!(all_expanded_hunks, Vec::new());
11541 });
11542
11543 multi_buffer_editor.update(cx, |editor, cx| {
11544 editor.select_all(&SelectAll, 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 multi_buffer_editor.update(cx, |editor, cx| {
11577 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11578 });
11579 cx.executor().run_until_parked();
11580 multi_buffer_editor.update(cx, |editor, cx| {
11581 let snapshot = editor.snapshot(cx);
11582 let all_hunks = editor_hunks(editor, &snapshot, cx);
11583 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11584 assert_eq!(
11585 expanded_hunks_background_highlights(editor, cx),
11586 vec![
11587 DisplayRow(23)..=DisplayRow(23),
11588 DisplayRow(43)..=DisplayRow(43)
11589 ],
11590 );
11591 assert_eq!(all_hunks, expected_all_hunks_shifted);
11592 assert_eq!(all_hunks, all_expanded_hunks);
11593 });
11594
11595 multi_buffer_editor.update(cx, |editor, cx| {
11596 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11597 });
11598 cx.executor().run_until_parked();
11599 multi_buffer_editor.update(cx, |editor, cx| {
11600 let snapshot = editor.snapshot(cx);
11601 let all_hunks = editor_hunks(editor, &snapshot, cx);
11602 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11603 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11604 assert_eq!(all_hunks, expected_all_hunks);
11605 assert_eq!(all_expanded_hunks, Vec::new());
11606 });
11607}
11608
11609#[gpui::test]
11610async fn test_edits_around_toggled_additions(
11611 executor: BackgroundExecutor,
11612 cx: &mut gpui::TestAppContext,
11613) {
11614 init_test(cx, |_| {});
11615
11616 let mut cx = EditorTestContext::new(cx).await;
11617
11618 let diff_base = r#"
11619 use some::mod1;
11620 use some::mod2;
11621
11622 const A: u32 = 42;
11623
11624 fn main() {
11625 println!("hello");
11626
11627 println!("world");
11628 }
11629 "#
11630 .unindent();
11631 executor.run_until_parked();
11632 cx.set_state(
11633 &r#"
11634 use some::mod1;
11635 use some::mod2;
11636
11637 const A: u32 = 42;
11638 const B: u32 = 42;
11639 const C: u32 = 42;
11640 ˇ
11641
11642 fn main() {
11643 println!("hello");
11644
11645 println!("world");
11646 }
11647 "#
11648 .unindent(),
11649 );
11650
11651 cx.set_diff_base(Some(&diff_base));
11652 executor.run_until_parked();
11653 cx.update_editor(|editor, cx| {
11654 let snapshot = editor.snapshot(cx);
11655 let all_hunks = editor_hunks(editor, &snapshot, cx);
11656 assert_eq!(
11657 all_hunks,
11658 vec![(
11659 "".to_string(),
11660 DiffHunkStatus::Added,
11661 DisplayRow(4)..DisplayRow(7)
11662 )]
11663 );
11664 });
11665 cx.update_editor(|editor, cx| {
11666 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11667 });
11668 executor.run_until_parked();
11669 cx.assert_editor_state(
11670 &r#"
11671 use some::mod1;
11672 use some::mod2;
11673
11674 const A: u32 = 42;
11675 const B: u32 = 42;
11676 const C: u32 = 42;
11677 ˇ
11678
11679 fn main() {
11680 println!("hello");
11681
11682 println!("world");
11683 }
11684 "#
11685 .unindent(),
11686 );
11687 cx.update_editor(|editor, cx| {
11688 let snapshot = editor.snapshot(cx);
11689 let all_hunks = editor_hunks(editor, &snapshot, cx);
11690 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11691 assert_eq!(
11692 all_hunks,
11693 vec![(
11694 "".to_string(),
11695 DiffHunkStatus::Added,
11696 DisplayRow(4)..DisplayRow(7)
11697 )]
11698 );
11699 assert_eq!(
11700 expanded_hunks_background_highlights(editor, cx),
11701 vec![DisplayRow(4)..=DisplayRow(6)]
11702 );
11703 assert_eq!(all_hunks, all_expanded_hunks);
11704 });
11705
11706 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
11707 executor.run_until_parked();
11708 cx.assert_editor_state(
11709 &r#"
11710 use some::mod1;
11711 use some::mod2;
11712
11713 const A: u32 = 42;
11714 const B: u32 = 42;
11715 const C: u32 = 42;
11716 const D: u32 = 42;
11717 ˇ
11718
11719 fn main() {
11720 println!("hello");
11721
11722 println!("world");
11723 }
11724 "#
11725 .unindent(),
11726 );
11727 cx.update_editor(|editor, cx| {
11728 let snapshot = editor.snapshot(cx);
11729 let all_hunks = editor_hunks(editor, &snapshot, cx);
11730 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11731 assert_eq!(
11732 all_hunks,
11733 vec![(
11734 "".to_string(),
11735 DiffHunkStatus::Added,
11736 DisplayRow(4)..DisplayRow(8)
11737 )]
11738 );
11739 assert_eq!(
11740 expanded_hunks_background_highlights(editor, cx),
11741 vec![DisplayRow(4)..=DisplayRow(6)],
11742 "Edited hunk should have one more line added"
11743 );
11744 assert_eq!(
11745 all_hunks, all_expanded_hunks,
11746 "Expanded hunk should also grow with the addition"
11747 );
11748 });
11749
11750 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
11751 executor.run_until_parked();
11752 cx.assert_editor_state(
11753 &r#"
11754 use some::mod1;
11755 use some::mod2;
11756
11757 const A: u32 = 42;
11758 const B: u32 = 42;
11759 const C: u32 = 42;
11760 const D: u32 = 42;
11761 const E: u32 = 42;
11762 ˇ
11763
11764 fn main() {
11765 println!("hello");
11766
11767 println!("world");
11768 }
11769 "#
11770 .unindent(),
11771 );
11772 cx.update_editor(|editor, cx| {
11773 let snapshot = editor.snapshot(cx);
11774 let all_hunks = editor_hunks(editor, &snapshot, cx);
11775 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11776 assert_eq!(
11777 all_hunks,
11778 vec![(
11779 "".to_string(),
11780 DiffHunkStatus::Added,
11781 DisplayRow(4)..DisplayRow(9)
11782 )]
11783 );
11784 assert_eq!(
11785 expanded_hunks_background_highlights(editor, cx),
11786 vec![DisplayRow(4)..=DisplayRow(6)],
11787 "Edited hunk should have one more line added"
11788 );
11789 assert_eq!(all_hunks, all_expanded_hunks);
11790 });
11791
11792 cx.update_editor(|editor, cx| {
11793 editor.move_up(&MoveUp, cx);
11794 editor.delete_line(&DeleteLine, cx);
11795 });
11796 executor.run_until_parked();
11797 cx.assert_editor_state(
11798 &r#"
11799 use some::mod1;
11800 use some::mod2;
11801
11802 const A: u32 = 42;
11803 const B: u32 = 42;
11804 const C: u32 = 42;
11805 const D: u32 = 42;
11806 ˇ
11807
11808 fn main() {
11809 println!("hello");
11810
11811 println!("world");
11812 }
11813 "#
11814 .unindent(),
11815 );
11816 cx.update_editor(|editor, cx| {
11817 let snapshot = editor.snapshot(cx);
11818 let all_hunks = editor_hunks(editor, &snapshot, cx);
11819 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11820 assert_eq!(
11821 all_hunks,
11822 vec![(
11823 "".to_string(),
11824 DiffHunkStatus::Added,
11825 DisplayRow(4)..DisplayRow(8)
11826 )]
11827 );
11828 assert_eq!(
11829 expanded_hunks_background_highlights(editor, cx),
11830 vec![DisplayRow(4)..=DisplayRow(6)],
11831 "Deleting a line should shrint the hunk"
11832 );
11833 assert_eq!(
11834 all_hunks, all_expanded_hunks,
11835 "Expanded hunk should also shrink with the addition"
11836 );
11837 });
11838
11839 cx.update_editor(|editor, cx| {
11840 editor.move_up(&MoveUp, cx);
11841 editor.delete_line(&DeleteLine, cx);
11842 editor.move_up(&MoveUp, cx);
11843 editor.delete_line(&DeleteLine, cx);
11844 editor.move_up(&MoveUp, cx);
11845 editor.delete_line(&DeleteLine, cx);
11846 });
11847 executor.run_until_parked();
11848 cx.assert_editor_state(
11849 &r#"
11850 use some::mod1;
11851 use some::mod2;
11852
11853 const A: u32 = 42;
11854 ˇ
11855
11856 fn main() {
11857 println!("hello");
11858
11859 println!("world");
11860 }
11861 "#
11862 .unindent(),
11863 );
11864 cx.update_editor(|editor, cx| {
11865 let snapshot = editor.snapshot(cx);
11866 let all_hunks = editor_hunks(editor, &snapshot, cx);
11867 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11868 assert_eq!(
11869 all_hunks,
11870 vec![(
11871 "".to_string(),
11872 DiffHunkStatus::Added,
11873 DisplayRow(5)..DisplayRow(6)
11874 )]
11875 );
11876 assert_eq!(
11877 expanded_hunks_background_highlights(editor, cx),
11878 vec![DisplayRow(5)..=DisplayRow(5)]
11879 );
11880 assert_eq!(all_hunks, all_expanded_hunks);
11881 });
11882
11883 cx.update_editor(|editor, cx| {
11884 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
11885 editor.delete_line(&DeleteLine, cx);
11886 });
11887 executor.run_until_parked();
11888 cx.assert_editor_state(
11889 &r#"
11890 ˇ
11891
11892 fn main() {
11893 println!("hello");
11894
11895 println!("world");
11896 }
11897 "#
11898 .unindent(),
11899 );
11900 cx.update_editor(|editor, cx| {
11901 let snapshot = editor.snapshot(cx);
11902 let all_hunks = editor_hunks(editor, &snapshot, cx);
11903 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11904 assert_eq!(
11905 all_hunks,
11906 vec![
11907 (
11908 "use some::mod1;\nuse some::mod2;\n".to_string(),
11909 DiffHunkStatus::Removed,
11910 DisplayRow(0)..DisplayRow(0)
11911 ),
11912 (
11913 "const A: u32 = 42;\n".to_string(),
11914 DiffHunkStatus::Removed,
11915 DisplayRow(2)..DisplayRow(2)
11916 )
11917 ]
11918 );
11919 assert_eq!(
11920 expanded_hunks_background_highlights(editor, cx),
11921 Vec::new(),
11922 "Should close all stale expanded addition hunks"
11923 );
11924 assert_eq!(
11925 all_expanded_hunks,
11926 vec![(
11927 "const A: u32 = 42;\n".to_string(),
11928 DiffHunkStatus::Removed,
11929 DisplayRow(2)..DisplayRow(2)
11930 )],
11931 "Should open hunks that were adjacent to the stale addition one"
11932 );
11933 });
11934}
11935
11936#[gpui::test]
11937async fn test_edits_around_toggled_deletions(
11938 executor: BackgroundExecutor,
11939 cx: &mut gpui::TestAppContext,
11940) {
11941 init_test(cx, |_| {});
11942
11943 let mut cx = EditorTestContext::new(cx).await;
11944
11945 let diff_base = r#"
11946 use some::mod1;
11947 use some::mod2;
11948
11949 const A: u32 = 42;
11950 const B: u32 = 42;
11951 const C: u32 = 42;
11952
11953
11954 fn main() {
11955 println!("hello");
11956
11957 println!("world");
11958 }
11959 "#
11960 .unindent();
11961 executor.run_until_parked();
11962 cx.set_state(
11963 &r#"
11964 use some::mod1;
11965 use some::mod2;
11966
11967 ˇconst B: u32 = 42;
11968 const C: u32 = 42;
11969
11970
11971 fn main() {
11972 println!("hello");
11973
11974 println!("world");
11975 }
11976 "#
11977 .unindent(),
11978 );
11979
11980 cx.set_diff_base(Some(&diff_base));
11981 executor.run_until_parked();
11982 cx.update_editor(|editor, cx| {
11983 let snapshot = editor.snapshot(cx);
11984 let all_hunks = editor_hunks(editor, &snapshot, cx);
11985 assert_eq!(
11986 all_hunks,
11987 vec![(
11988 "const A: u32 = 42;\n".to_string(),
11989 DiffHunkStatus::Removed,
11990 DisplayRow(3)..DisplayRow(3)
11991 )]
11992 );
11993 });
11994 cx.update_editor(|editor, cx| {
11995 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11996 });
11997 executor.run_until_parked();
11998 cx.assert_editor_state(
11999 &r#"
12000 use some::mod1;
12001 use some::mod2;
12002
12003 ˇconst B: u32 = 42;
12004 const C: u32 = 42;
12005
12006
12007 fn main() {
12008 println!("hello");
12009
12010 println!("world");
12011 }
12012 "#
12013 .unindent(),
12014 );
12015 cx.update_editor(|editor, cx| {
12016 let snapshot = editor.snapshot(cx);
12017 let all_hunks = editor_hunks(editor, &snapshot, cx);
12018 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12019 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
12020 assert_eq!(
12021 all_hunks,
12022 vec![(
12023 "const A: u32 = 42;\n".to_string(),
12024 DiffHunkStatus::Removed,
12025 DisplayRow(4)..DisplayRow(4)
12026 )]
12027 );
12028 assert_eq!(all_hunks, all_expanded_hunks);
12029 });
12030
12031 cx.update_editor(|editor, cx| {
12032 editor.delete_line(&DeleteLine, cx);
12033 });
12034 executor.run_until_parked();
12035 cx.assert_editor_state(
12036 &r#"
12037 use some::mod1;
12038 use some::mod2;
12039
12040 ˇconst C: u32 = 42;
12041
12042
12043 fn main() {
12044 println!("hello");
12045
12046 println!("world");
12047 }
12048 "#
12049 .unindent(),
12050 );
12051 cx.update_editor(|editor, cx| {
12052 let snapshot = editor.snapshot(cx);
12053 let all_hunks = editor_hunks(editor, &snapshot, cx);
12054 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12055 assert_eq!(
12056 expanded_hunks_background_highlights(editor, cx),
12057 Vec::new(),
12058 "Deleted hunks do not highlight current editor's background"
12059 );
12060 assert_eq!(
12061 all_hunks,
12062 vec![(
12063 "const A: u32 = 42;\nconst B: u32 = 42;\n".to_string(),
12064 DiffHunkStatus::Removed,
12065 DisplayRow(5)..DisplayRow(5)
12066 )]
12067 );
12068 assert_eq!(all_hunks, all_expanded_hunks);
12069 });
12070
12071 cx.update_editor(|editor, cx| {
12072 editor.delete_line(&DeleteLine, cx);
12073 });
12074 executor.run_until_parked();
12075 cx.assert_editor_state(
12076 &r#"
12077 use some::mod1;
12078 use some::mod2;
12079
12080 ˇ
12081
12082 fn main() {
12083 println!("hello");
12084
12085 println!("world");
12086 }
12087 "#
12088 .unindent(),
12089 );
12090 cx.update_editor(|editor, cx| {
12091 let snapshot = editor.snapshot(cx);
12092 let all_hunks = editor_hunks(editor, &snapshot, cx);
12093 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12094 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
12095 assert_eq!(
12096 all_hunks,
12097 vec![(
12098 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12099 DiffHunkStatus::Removed,
12100 DisplayRow(6)..DisplayRow(6)
12101 )]
12102 );
12103 assert_eq!(all_hunks, all_expanded_hunks);
12104 });
12105
12106 cx.update_editor(|editor, cx| {
12107 editor.handle_input("replacement", cx);
12108 });
12109 executor.run_until_parked();
12110 cx.assert_editor_state(
12111 &r#"
12112 use some::mod1;
12113 use some::mod2;
12114
12115 replacementˇ
12116
12117 fn main() {
12118 println!("hello");
12119
12120 println!("world");
12121 }
12122 "#
12123 .unindent(),
12124 );
12125 cx.update_editor(|editor, cx| {
12126 let snapshot = editor.snapshot(cx);
12127 let all_hunks = editor_hunks(editor, &snapshot, cx);
12128 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12129 assert_eq!(
12130 all_hunks,
12131 vec![(
12132 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n\n".to_string(),
12133 DiffHunkStatus::Modified,
12134 DisplayRow(7)..DisplayRow(8)
12135 )]
12136 );
12137 assert_eq!(
12138 expanded_hunks_background_highlights(editor, cx),
12139 vec![DisplayRow(7)..=DisplayRow(7)],
12140 "Modified expanded hunks should display additions and highlight their background"
12141 );
12142 assert_eq!(all_hunks, all_expanded_hunks);
12143 });
12144}
12145
12146#[gpui::test]
12147async fn test_edits_around_toggled_modifications(
12148 executor: BackgroundExecutor,
12149 cx: &mut gpui::TestAppContext,
12150) {
12151 init_test(cx, |_| {});
12152
12153 let mut cx = EditorTestContext::new(cx).await;
12154
12155 let diff_base = r#"
12156 use some::mod1;
12157 use some::mod2;
12158
12159 const A: u32 = 42;
12160 const B: u32 = 42;
12161 const C: u32 = 42;
12162 const D: u32 = 42;
12163
12164
12165 fn main() {
12166 println!("hello");
12167
12168 println!("world");
12169 }"#
12170 .unindent();
12171 executor.run_until_parked();
12172 cx.set_state(
12173 &r#"
12174 use some::mod1;
12175 use some::mod2;
12176
12177 const A: u32 = 42;
12178 const B: u32 = 42;
12179 const C: u32 = 43ˇ
12180 const D: u32 = 42;
12181
12182
12183 fn main() {
12184 println!("hello");
12185
12186 println!("world");
12187 }"#
12188 .unindent(),
12189 );
12190
12191 cx.set_diff_base(Some(&diff_base));
12192 executor.run_until_parked();
12193 cx.update_editor(|editor, cx| {
12194 let snapshot = editor.snapshot(cx);
12195 let all_hunks = editor_hunks(editor, &snapshot, cx);
12196 assert_eq!(
12197 all_hunks,
12198 vec![(
12199 "const C: u32 = 42;\n".to_string(),
12200 DiffHunkStatus::Modified,
12201 DisplayRow(5)..DisplayRow(6)
12202 )]
12203 );
12204 });
12205 cx.update_editor(|editor, cx| {
12206 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12207 });
12208 executor.run_until_parked();
12209 cx.assert_editor_state(
12210 &r#"
12211 use some::mod1;
12212 use some::mod2;
12213
12214 const A: u32 = 42;
12215 const B: u32 = 42;
12216 const C: u32 = 43ˇ
12217 const D: u32 = 42;
12218
12219
12220 fn main() {
12221 println!("hello");
12222
12223 println!("world");
12224 }"#
12225 .unindent(),
12226 );
12227 cx.update_editor(|editor, cx| {
12228 let snapshot = editor.snapshot(cx);
12229 let all_hunks = editor_hunks(editor, &snapshot, cx);
12230 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12231 assert_eq!(
12232 expanded_hunks_background_highlights(editor, cx),
12233 vec![DisplayRow(6)..=DisplayRow(6)],
12234 );
12235 assert_eq!(
12236 all_hunks,
12237 vec![(
12238 "const C: u32 = 42;\n".to_string(),
12239 DiffHunkStatus::Modified,
12240 DisplayRow(6)..DisplayRow(7)
12241 )]
12242 );
12243 assert_eq!(all_hunks, all_expanded_hunks);
12244 });
12245
12246 cx.update_editor(|editor, cx| {
12247 editor.handle_input("\nnew_line\n", cx);
12248 });
12249 executor.run_until_parked();
12250 cx.assert_editor_state(
12251 &r#"
12252 use some::mod1;
12253 use some::mod2;
12254
12255 const A: u32 = 42;
12256 const B: u32 = 42;
12257 const C: u32 = 43
12258 new_line
12259 ˇ
12260 const D: u32 = 42;
12261
12262
12263 fn main() {
12264 println!("hello");
12265
12266 println!("world");
12267 }"#
12268 .unindent(),
12269 );
12270 cx.update_editor(|editor, cx| {
12271 let snapshot = editor.snapshot(cx);
12272 let all_hunks = editor_hunks(editor, &snapshot, cx);
12273 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12274 assert_eq!(
12275 expanded_hunks_background_highlights(editor, cx),
12276 vec![DisplayRow(6)..=DisplayRow(6)],
12277 "Modified hunk should grow highlighted lines on more text additions"
12278 );
12279 assert_eq!(
12280 all_hunks,
12281 vec![(
12282 "const C: u32 = 42;\n".to_string(),
12283 DiffHunkStatus::Modified,
12284 DisplayRow(6)..DisplayRow(9)
12285 )]
12286 );
12287 assert_eq!(all_hunks, all_expanded_hunks);
12288 });
12289
12290 cx.update_editor(|editor, cx| {
12291 editor.move_up(&MoveUp, cx);
12292 editor.move_up(&MoveUp, cx);
12293 editor.move_up(&MoveUp, cx);
12294 editor.delete_line(&DeleteLine, cx);
12295 });
12296 executor.run_until_parked();
12297 cx.assert_editor_state(
12298 &r#"
12299 use some::mod1;
12300 use some::mod2;
12301
12302 const A: u32 = 42;
12303 ˇconst C: u32 = 43
12304 new_line
12305
12306 const D: u32 = 42;
12307
12308
12309 fn main() {
12310 println!("hello");
12311
12312 println!("world");
12313 }"#
12314 .unindent(),
12315 );
12316 cx.update_editor(|editor, cx| {
12317 let snapshot = editor.snapshot(cx);
12318 let all_hunks = editor_hunks(editor, &snapshot, cx);
12319 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12320 assert_eq!(
12321 expanded_hunks_background_highlights(editor, cx),
12322 vec![DisplayRow(6)..=DisplayRow(8)],
12323 );
12324 assert_eq!(
12325 all_hunks,
12326 vec![(
12327 "const B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12328 DiffHunkStatus::Modified,
12329 DisplayRow(6)..DisplayRow(9)
12330 )],
12331 "Modified hunk should grow deleted lines on text deletions above"
12332 );
12333 assert_eq!(all_hunks, all_expanded_hunks);
12334 });
12335
12336 cx.update_editor(|editor, cx| {
12337 editor.move_up(&MoveUp, cx);
12338 editor.handle_input("v", cx);
12339 });
12340 executor.run_until_parked();
12341 cx.assert_editor_state(
12342 &r#"
12343 use some::mod1;
12344 use some::mod2;
12345
12346 vˇconst A: u32 = 42;
12347 const C: u32 = 43
12348 new_line
12349
12350 const D: u32 = 42;
12351
12352
12353 fn main() {
12354 println!("hello");
12355
12356 println!("world");
12357 }"#
12358 .unindent(),
12359 );
12360 cx.update_editor(|editor, cx| {
12361 let snapshot = editor.snapshot(cx);
12362 let all_hunks = editor_hunks(editor, &snapshot, cx);
12363 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12364 assert_eq!(
12365 expanded_hunks_background_highlights(editor, cx),
12366 vec![DisplayRow(6)..=DisplayRow(9)],
12367 "Modified hunk should grow deleted lines on text modifications above"
12368 );
12369 assert_eq!(
12370 all_hunks,
12371 vec![(
12372 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12373 DiffHunkStatus::Modified,
12374 DisplayRow(6)..DisplayRow(10)
12375 )]
12376 );
12377 assert_eq!(all_hunks, all_expanded_hunks);
12378 });
12379
12380 cx.update_editor(|editor, cx| {
12381 editor.move_down(&MoveDown, cx);
12382 editor.move_down(&MoveDown, cx);
12383 editor.delete_line(&DeleteLine, cx)
12384 });
12385 executor.run_until_parked();
12386 cx.assert_editor_state(
12387 &r#"
12388 use some::mod1;
12389 use some::mod2;
12390
12391 vconst A: u32 = 42;
12392 const C: u32 = 43
12393 ˇ
12394 const D: u32 = 42;
12395
12396
12397 fn main() {
12398 println!("hello");
12399
12400 println!("world");
12401 }"#
12402 .unindent(),
12403 );
12404 cx.update_editor(|editor, cx| {
12405 let snapshot = editor.snapshot(cx);
12406 let all_hunks = editor_hunks(editor, &snapshot, cx);
12407 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12408 assert_eq!(
12409 expanded_hunks_background_highlights(editor, cx),
12410 vec![DisplayRow(6)..=DisplayRow(8)],
12411 "Modified hunk should grow shrink lines on modification lines removal"
12412 );
12413 assert_eq!(
12414 all_hunks,
12415 vec![(
12416 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12417 DiffHunkStatus::Modified,
12418 DisplayRow(6)..DisplayRow(9)
12419 )]
12420 );
12421 assert_eq!(all_hunks, all_expanded_hunks);
12422 });
12423
12424 cx.update_editor(|editor, cx| {
12425 editor.move_up(&MoveUp, cx);
12426 editor.move_up(&MoveUp, cx);
12427 editor.select_down_by_lines(&SelectDownByLines { lines: 4 }, cx);
12428 editor.delete_line(&DeleteLine, cx)
12429 });
12430 executor.run_until_parked();
12431 cx.assert_editor_state(
12432 &r#"
12433 use some::mod1;
12434 use some::mod2;
12435
12436 ˇ
12437
12438 fn main() {
12439 println!("hello");
12440
12441 println!("world");
12442 }"#
12443 .unindent(),
12444 );
12445 cx.update_editor(|editor, cx| {
12446 let snapshot = editor.snapshot(cx);
12447 let all_hunks = editor_hunks(editor, &snapshot, cx);
12448 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12449 assert_eq!(
12450 expanded_hunks_background_highlights(editor, cx),
12451 Vec::new(),
12452 "Modified hunk should turn into a removed one on all modified lines removal"
12453 );
12454 assert_eq!(
12455 all_hunks,
12456 vec![(
12457 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\nconst D: u32 = 42;\n"
12458 .to_string(),
12459 DiffHunkStatus::Removed,
12460 DisplayRow(7)..DisplayRow(7)
12461 )]
12462 );
12463 assert_eq!(all_hunks, all_expanded_hunks);
12464 });
12465}
12466
12467#[gpui::test]
12468async fn test_multiple_expanded_hunks_merge(
12469 executor: BackgroundExecutor,
12470 cx: &mut gpui::TestAppContext,
12471) {
12472 init_test(cx, |_| {});
12473
12474 let mut cx = EditorTestContext::new(cx).await;
12475
12476 let diff_base = r#"
12477 use some::mod1;
12478 use some::mod2;
12479
12480 const A: u32 = 42;
12481 const B: u32 = 42;
12482 const C: u32 = 42;
12483 const D: u32 = 42;
12484
12485
12486 fn main() {
12487 println!("hello");
12488
12489 println!("world");
12490 }"#
12491 .unindent();
12492 executor.run_until_parked();
12493 cx.set_state(
12494 &r#"
12495 use some::mod1;
12496 use some::mod2;
12497
12498 const A: u32 = 42;
12499 const B: u32 = 42;
12500 const C: u32 = 43ˇ
12501 const D: u32 = 42;
12502
12503
12504 fn main() {
12505 println!("hello");
12506
12507 println!("world");
12508 }"#
12509 .unindent(),
12510 );
12511
12512 cx.set_diff_base(Some(&diff_base));
12513 executor.run_until_parked();
12514 cx.update_editor(|editor, cx| {
12515 let snapshot = editor.snapshot(cx);
12516 let all_hunks = editor_hunks(editor, &snapshot, cx);
12517 assert_eq!(
12518 all_hunks,
12519 vec![(
12520 "const C: u32 = 42;\n".to_string(),
12521 DiffHunkStatus::Modified,
12522 DisplayRow(5)..DisplayRow(6)
12523 )]
12524 );
12525 });
12526 cx.update_editor(|editor, cx| {
12527 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12528 });
12529 executor.run_until_parked();
12530 cx.assert_editor_state(
12531 &r#"
12532 use some::mod1;
12533 use some::mod2;
12534
12535 const A: u32 = 42;
12536 const B: u32 = 42;
12537 const C: u32 = 43ˇ
12538 const D: u32 = 42;
12539
12540
12541 fn main() {
12542 println!("hello");
12543
12544 println!("world");
12545 }"#
12546 .unindent(),
12547 );
12548 cx.update_editor(|editor, cx| {
12549 let snapshot = editor.snapshot(cx);
12550 let all_hunks = editor_hunks(editor, &snapshot, cx);
12551 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12552 assert_eq!(
12553 expanded_hunks_background_highlights(editor, cx),
12554 vec![DisplayRow(6)..=DisplayRow(6)],
12555 );
12556 assert_eq!(
12557 all_hunks,
12558 vec![(
12559 "const C: u32 = 42;\n".to_string(),
12560 DiffHunkStatus::Modified,
12561 DisplayRow(6)..DisplayRow(7)
12562 )]
12563 );
12564 assert_eq!(all_hunks, all_expanded_hunks);
12565 });
12566
12567 cx.update_editor(|editor, cx| {
12568 editor.handle_input("\nnew_line\n", cx);
12569 });
12570 executor.run_until_parked();
12571 cx.assert_editor_state(
12572 &r#"
12573 use some::mod1;
12574 use some::mod2;
12575
12576 const A: u32 = 42;
12577 const B: u32 = 42;
12578 const C: u32 = 43
12579 new_line
12580 ˇ
12581 const D: u32 = 42;
12582
12583
12584 fn main() {
12585 println!("hello");
12586
12587 println!("world");
12588 }"#
12589 .unindent(),
12590 );
12591}
12592
12593async fn setup_indent_guides_editor(
12594 text: &str,
12595 cx: &mut gpui::TestAppContext,
12596) -> (BufferId, EditorTestContext) {
12597 init_test(cx, |_| {});
12598
12599 let mut cx = EditorTestContext::new(cx).await;
12600
12601 let buffer_id = cx.update_editor(|editor, cx| {
12602 editor.set_text(text, cx);
12603 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12604 let buffer_id = buffer_ids[0];
12605 buffer_id
12606 });
12607
12608 (buffer_id, cx)
12609}
12610
12611fn assert_indent_guides(
12612 range: Range<u32>,
12613 expected: Vec<IndentGuide>,
12614 active_indices: Option<Vec<usize>>,
12615 cx: &mut EditorTestContext,
12616) {
12617 let indent_guides = cx.update_editor(|editor, cx| {
12618 let snapshot = editor.snapshot(cx).display_snapshot;
12619 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12620 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12621 true,
12622 &snapshot,
12623 cx,
12624 );
12625
12626 indent_guides.sort_by(|a, b| {
12627 a.depth.cmp(&b.depth).then(
12628 a.start_row
12629 .cmp(&b.start_row)
12630 .then(a.end_row.cmp(&b.end_row)),
12631 )
12632 });
12633 indent_guides
12634 });
12635
12636 if let Some(expected) = active_indices {
12637 let active_indices = cx.update_editor(|editor, cx| {
12638 let snapshot = editor.snapshot(cx).display_snapshot;
12639 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
12640 });
12641
12642 assert_eq!(
12643 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
12644 expected,
12645 "Active indent guide indices do not match"
12646 );
12647 }
12648
12649 let expected: Vec<_> = expected
12650 .into_iter()
12651 .map(|guide| MultiBufferIndentGuide {
12652 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
12653 buffer: guide,
12654 })
12655 .collect();
12656
12657 assert_eq!(indent_guides, expected, "Indent guides do not match");
12658}
12659
12660fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
12661 IndentGuide {
12662 buffer_id,
12663 start_row,
12664 end_row,
12665 depth,
12666 tab_size: 4,
12667 settings: IndentGuideSettings {
12668 enabled: true,
12669 line_width: 1,
12670 active_line_width: 1,
12671 ..Default::default()
12672 },
12673 }
12674}
12675
12676#[gpui::test]
12677async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12678 let (buffer_id, mut cx) = setup_indent_guides_editor(
12679 &"
12680 fn main() {
12681 let a = 1;
12682 }"
12683 .unindent(),
12684 cx,
12685 )
12686 .await;
12687
12688 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12689}
12690
12691#[gpui::test]
12692async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
12693 let (buffer_id, mut cx) = setup_indent_guides_editor(
12694 &"
12695 fn main() {
12696 let a = 1;
12697 let b = 2;
12698 }"
12699 .unindent(),
12700 cx,
12701 )
12702 .await;
12703
12704 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
12705}
12706
12707#[gpui::test]
12708async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
12709 let (buffer_id, mut cx) = setup_indent_guides_editor(
12710 &"
12711 fn main() {
12712 let a = 1;
12713 if a == 3 {
12714 let b = 2;
12715 } else {
12716 let c = 3;
12717 }
12718 }"
12719 .unindent(),
12720 cx,
12721 )
12722 .await;
12723
12724 assert_indent_guides(
12725 0..8,
12726 vec![
12727 indent_guide(buffer_id, 1, 6, 0),
12728 indent_guide(buffer_id, 3, 3, 1),
12729 indent_guide(buffer_id, 5, 5, 1),
12730 ],
12731 None,
12732 &mut cx,
12733 );
12734}
12735
12736#[gpui::test]
12737async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
12738 let (buffer_id, mut cx) = setup_indent_guides_editor(
12739 &"
12740 fn main() {
12741 let a = 1;
12742 let b = 2;
12743 let c = 3;
12744 }"
12745 .unindent(),
12746 cx,
12747 )
12748 .await;
12749
12750 assert_indent_guides(
12751 0..5,
12752 vec![
12753 indent_guide(buffer_id, 1, 3, 0),
12754 indent_guide(buffer_id, 2, 2, 1),
12755 ],
12756 None,
12757 &mut cx,
12758 );
12759}
12760
12761#[gpui::test]
12762async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
12763 let (buffer_id, mut cx) = setup_indent_guides_editor(
12764 &"
12765 fn main() {
12766 let a = 1;
12767
12768 let c = 3;
12769 }"
12770 .unindent(),
12771 cx,
12772 )
12773 .await;
12774
12775 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
12776}
12777
12778#[gpui::test]
12779async fn test_indent_guide_complex(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 0..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_starts_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..11,
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_ends_off_screen(cx: &mut gpui::TestAppContext) {
12844 let (buffer_id, mut cx) = setup_indent_guides_editor(
12845 &"
12846 fn main() {
12847 let a = 1;
12848
12849 let c = 3;
12850
12851 if a == 3 {
12852 let b = 2;
12853 } else {
12854 let c = 3;
12855 }
12856 }"
12857 .unindent(),
12858 cx,
12859 )
12860 .await;
12861
12862 assert_indent_guides(
12863 1..10,
12864 vec![
12865 indent_guide(buffer_id, 1, 9, 0),
12866 indent_guide(buffer_id, 6, 6, 1),
12867 indent_guide(buffer_id, 8, 8, 1),
12868 ],
12869 None,
12870 &mut cx,
12871 );
12872}
12873
12874#[gpui::test]
12875async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
12876 let (buffer_id, mut cx) = setup_indent_guides_editor(
12877 &"
12878 block1
12879 block2
12880 block3
12881 block4
12882 block2
12883 block1
12884 block1"
12885 .unindent(),
12886 cx,
12887 )
12888 .await;
12889
12890 assert_indent_guides(
12891 1..10,
12892 vec![
12893 indent_guide(buffer_id, 1, 4, 0),
12894 indent_guide(buffer_id, 2, 3, 1),
12895 indent_guide(buffer_id, 3, 3, 2),
12896 ],
12897 None,
12898 &mut cx,
12899 );
12900}
12901
12902#[gpui::test]
12903async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
12904 let (buffer_id, mut cx) = setup_indent_guides_editor(
12905 &"
12906 block1
12907 block2
12908 block3
12909
12910 block1
12911 block1"
12912 .unindent(),
12913 cx,
12914 )
12915 .await;
12916
12917 assert_indent_guides(
12918 0..6,
12919 vec![
12920 indent_guide(buffer_id, 1, 2, 0),
12921 indent_guide(buffer_id, 2, 2, 1),
12922 ],
12923 None,
12924 &mut cx,
12925 );
12926}
12927
12928#[gpui::test]
12929async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
12930 let (buffer_id, mut cx) = setup_indent_guides_editor(
12931 &"
12932 block1
12933
12934
12935
12936 block2
12937 "
12938 .unindent(),
12939 cx,
12940 )
12941 .await;
12942
12943 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12944}
12945
12946#[gpui::test]
12947async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
12948 let (buffer_id, mut cx) = setup_indent_guides_editor(
12949 &"
12950 def a:
12951 \tb = 3
12952 \tif True:
12953 \t\tc = 4
12954 \t\td = 5
12955 \tprint(b)
12956 "
12957 .unindent(),
12958 cx,
12959 )
12960 .await;
12961
12962 assert_indent_guides(
12963 0..6,
12964 vec![
12965 indent_guide(buffer_id, 1, 6, 0),
12966 indent_guide(buffer_id, 3, 4, 1),
12967 ],
12968 None,
12969 &mut cx,
12970 );
12971}
12972
12973#[gpui::test]
12974async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12975 let (buffer_id, mut cx) = setup_indent_guides_editor(
12976 &"
12977 fn main() {
12978 let a = 1;
12979 }"
12980 .unindent(),
12981 cx,
12982 )
12983 .await;
12984
12985 cx.update_editor(|editor, cx| {
12986 editor.change_selections(None, cx, |s| {
12987 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12988 });
12989 });
12990
12991 assert_indent_guides(
12992 0..3,
12993 vec![indent_guide(buffer_id, 1, 1, 0)],
12994 Some(vec![0]),
12995 &mut cx,
12996 );
12997}
12998
12999#[gpui::test]
13000async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13001 let (buffer_id, mut cx) = setup_indent_guides_editor(
13002 &"
13003 fn main() {
13004 if 1 == 2 {
13005 let a = 1;
13006 }
13007 }"
13008 .unindent(),
13009 cx,
13010 )
13011 .await;
13012
13013 cx.update_editor(|editor, cx| {
13014 editor.change_selections(None, cx, |s| {
13015 s.select_ranges([Point::new(1, 0)..Point::new(1, 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![1]),
13026 &mut cx,
13027 );
13028
13029 cx.update_editor(|editor, cx| {
13030 editor.change_selections(None, cx, |s| {
13031 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13032 });
13033 });
13034
13035 assert_indent_guides(
13036 0..4,
13037 vec![
13038 indent_guide(buffer_id, 1, 3, 0),
13039 indent_guide(buffer_id, 2, 2, 1),
13040 ],
13041 Some(vec![1]),
13042 &mut cx,
13043 );
13044
13045 cx.update_editor(|editor, cx| {
13046 editor.change_selections(None, cx, |s| {
13047 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13048 });
13049 });
13050
13051 assert_indent_guides(
13052 0..4,
13053 vec![
13054 indent_guide(buffer_id, 1, 3, 0),
13055 indent_guide(buffer_id, 2, 2, 1),
13056 ],
13057 Some(vec![0]),
13058 &mut cx,
13059 );
13060}
13061
13062#[gpui::test]
13063async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13064 let (buffer_id, mut cx) = setup_indent_guides_editor(
13065 &"
13066 fn main() {
13067 let a = 1;
13068
13069 let b = 2;
13070 }"
13071 .unindent(),
13072 cx,
13073 )
13074 .await;
13075
13076 cx.update_editor(|editor, cx| {
13077 editor.change_selections(None, cx, |s| {
13078 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13079 });
13080 });
13081
13082 assert_indent_guides(
13083 0..5,
13084 vec![indent_guide(buffer_id, 1, 3, 0)],
13085 Some(vec![0]),
13086 &mut cx,
13087 );
13088}
13089
13090#[gpui::test]
13091async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13092 let (buffer_id, mut cx) = setup_indent_guides_editor(
13093 &"
13094 def m:
13095 a = 1
13096 pass"
13097 .unindent(),
13098 cx,
13099 )
13100 .await;
13101
13102 cx.update_editor(|editor, cx| {
13103 editor.change_selections(None, cx, |s| {
13104 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13105 });
13106 });
13107
13108 assert_indent_guides(
13109 0..3,
13110 vec![indent_guide(buffer_id, 1, 2, 0)],
13111 Some(vec![0]),
13112 &mut cx,
13113 );
13114}
13115
13116#[gpui::test]
13117fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13118 init_test(cx, |_| {});
13119
13120 let editor = cx.add_window(|cx| {
13121 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13122 build_editor(buffer, cx)
13123 });
13124
13125 let render_args = Arc::new(Mutex::new(None));
13126 let snapshot = editor
13127 .update(cx, |editor, cx| {
13128 let snapshot = editor.buffer().read(cx).snapshot(cx);
13129 let range =
13130 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13131
13132 struct RenderArgs {
13133 row: MultiBufferRow,
13134 folded: bool,
13135 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13136 }
13137
13138 let crease = Crease::new(
13139 range,
13140 FoldPlaceholder::test(),
13141 {
13142 let toggle_callback = render_args.clone();
13143 move |row, folded, callback, _cx| {
13144 *toggle_callback.lock() = Some(RenderArgs {
13145 row,
13146 folded,
13147 callback,
13148 });
13149 div()
13150 }
13151 },
13152 |_row, _folded, _cx| div(),
13153 );
13154
13155 editor.insert_creases(Some(crease), cx);
13156 let snapshot = editor.snapshot(cx);
13157 let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13158 snapshot
13159 })
13160 .unwrap();
13161
13162 let render_args = render_args.lock().take().unwrap();
13163 assert_eq!(render_args.row, MultiBufferRow(1));
13164 assert_eq!(render_args.folded, false);
13165 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13166
13167 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13168 .unwrap();
13169 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13170 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13171
13172 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13173 .unwrap();
13174 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13175 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13176}
13177
13178#[gpui::test]
13179async fn test_input_text(cx: &mut gpui::TestAppContext) {
13180 init_test(cx, |_| {});
13181 let mut cx = EditorTestContext::new(cx).await;
13182
13183 cx.set_state(
13184 &r#"ˇone
13185 two
13186
13187 three
13188 fourˇ
13189 five
13190
13191 siˇx"#
13192 .unindent(),
13193 );
13194
13195 cx.dispatch_action(HandleInput(String::new()));
13196 cx.assert_editor_state(
13197 &r#"ˇone
13198 two
13199
13200 three
13201 fourˇ
13202 five
13203
13204 siˇx"#
13205 .unindent(),
13206 );
13207
13208 cx.dispatch_action(HandleInput("AAAA".to_string()));
13209 cx.assert_editor_state(
13210 &r#"AAAAˇone
13211 two
13212
13213 three
13214 fourAAAAˇ
13215 five
13216
13217 siAAAAˇx"#
13218 .unindent(),
13219 );
13220}
13221
13222#[gpui::test]
13223async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13224 init_test(cx, |_| {});
13225
13226 let mut cx = EditorTestContext::new(cx).await;
13227 cx.set_state(
13228 r#"let foo = 1;
13229let foo = 2;
13230let foo = 3;
13231let fooˇ = 4;
13232let foo = 5;
13233let foo = 6;
13234let foo = 7;
13235let foo = 8;
13236let foo = 9;
13237let foo = 10;
13238let foo = 11;
13239let foo = 12;
13240let foo = 13;
13241let foo = 14;
13242let foo = 15;"#,
13243 );
13244
13245 cx.update_editor(|e, cx| {
13246 assert_eq!(
13247 e.next_scroll_position,
13248 NextScrollCursorCenterTopBottom::Center,
13249 "Default next scroll direction is center",
13250 );
13251
13252 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13253 assert_eq!(
13254 e.next_scroll_position,
13255 NextScrollCursorCenterTopBottom::Top,
13256 "After center, next scroll direction should be top",
13257 );
13258
13259 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13260 assert_eq!(
13261 e.next_scroll_position,
13262 NextScrollCursorCenterTopBottom::Bottom,
13263 "After top, next scroll direction should be bottom",
13264 );
13265
13266 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13267 assert_eq!(
13268 e.next_scroll_position,
13269 NextScrollCursorCenterTopBottom::Center,
13270 "After bottom, scrolling should start over",
13271 );
13272
13273 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13274 assert_eq!(
13275 e.next_scroll_position,
13276 NextScrollCursorCenterTopBottom::Top,
13277 "Scrolling continues if retriggered fast enough"
13278 );
13279 });
13280
13281 cx.executor()
13282 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13283 cx.executor().run_until_parked();
13284 cx.update_editor(|e, _| {
13285 assert_eq!(
13286 e.next_scroll_position,
13287 NextScrollCursorCenterTopBottom::Center,
13288 "If scrolling is not triggered fast enough, it should reset"
13289 );
13290 });
13291}
13292
13293#[gpui::test]
13294async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13295 init_test(cx, |_| {});
13296 let mut cx = EditorLspTestContext::new_rust(
13297 lsp::ServerCapabilities {
13298 definition_provider: Some(lsp::OneOf::Left(true)),
13299 references_provider: Some(lsp::OneOf::Left(true)),
13300 ..lsp::ServerCapabilities::default()
13301 },
13302 cx,
13303 )
13304 .await;
13305
13306 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13307 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13308 move |params, _| async move {
13309 if empty_go_to_definition {
13310 Ok(None)
13311 } else {
13312 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13313 uri: params.text_document_position_params.text_document.uri,
13314 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13315 })))
13316 }
13317 },
13318 );
13319 let references =
13320 cx.lsp
13321 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13322 Ok(Some(vec![lsp::Location {
13323 uri: params.text_document_position.text_document.uri,
13324 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13325 }]))
13326 });
13327 (go_to_definition, references)
13328 };
13329
13330 cx.set_state(
13331 &r#"fn one() {
13332 let mut a = ˇtwo();
13333 }
13334
13335 fn two() {}"#
13336 .unindent(),
13337 );
13338 set_up_lsp_handlers(false, &mut cx);
13339 let navigated = cx
13340 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13341 .await
13342 .expect("Failed to navigate to definition");
13343 assert_eq!(
13344 navigated,
13345 Navigated::Yes,
13346 "Should have navigated to definition from the GetDefinition response"
13347 );
13348 cx.assert_editor_state(
13349 &r#"fn one() {
13350 let mut a = two();
13351 }
13352
13353 fn «twoˇ»() {}"#
13354 .unindent(),
13355 );
13356
13357 let editors = cx.update_workspace(|workspace, cx| {
13358 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13359 });
13360 cx.update_editor(|_, test_editor_cx| {
13361 assert_eq!(
13362 editors.len(),
13363 1,
13364 "Initially, only one, test, editor should be open in the workspace"
13365 );
13366 assert_eq!(
13367 test_editor_cx.view(),
13368 editors.last().expect("Asserted len is 1")
13369 );
13370 });
13371
13372 set_up_lsp_handlers(true, &mut cx);
13373 let navigated = cx
13374 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13375 .await
13376 .expect("Failed to navigate to lookup references");
13377 assert_eq!(
13378 navigated,
13379 Navigated::Yes,
13380 "Should have navigated to references as a fallback after empty GoToDefinition response"
13381 );
13382 // We should not change the selections in the existing file,
13383 // if opening another milti buffer with the references
13384 cx.assert_editor_state(
13385 &r#"fn one() {
13386 let mut a = two();
13387 }
13388
13389 fn «twoˇ»() {}"#
13390 .unindent(),
13391 );
13392 let editors = cx.update_workspace(|workspace, cx| {
13393 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13394 });
13395 cx.update_editor(|_, test_editor_cx| {
13396 assert_eq!(
13397 editors.len(),
13398 2,
13399 "After falling back to references search, we open a new editor with the results"
13400 );
13401 let references_fallback_text = editors
13402 .into_iter()
13403 .find(|new_editor| new_editor != test_editor_cx.view())
13404 .expect("Should have one non-test editor now")
13405 .read(test_editor_cx)
13406 .text(test_editor_cx);
13407 assert_eq!(
13408 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13409 "Should use the range from the references response and not the GoToDefinition one"
13410 );
13411 });
13412}
13413
13414fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13415 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13416 point..point
13417}
13418
13419fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13420 let (text, ranges) = marked_text_ranges(marked_text, true);
13421 assert_eq!(view.text(cx), text);
13422 assert_eq!(
13423 view.selections.ranges(cx),
13424 ranges,
13425 "Assert selections are {}",
13426 marked_text
13427 );
13428}
13429
13430pub fn handle_signature_help_request(
13431 cx: &mut EditorLspTestContext,
13432 mocked_response: lsp::SignatureHelp,
13433) -> impl Future<Output = ()> {
13434 let mut request =
13435 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13436 let mocked_response = mocked_response.clone();
13437 async move { Ok(Some(mocked_response)) }
13438 });
13439
13440 async move {
13441 request.next().await;
13442 }
13443}
13444
13445/// Handle completion request passing a marked string specifying where the completion
13446/// should be triggered from using '|' character, what range should be replaced, and what completions
13447/// should be returned using '<' and '>' to delimit the range
13448pub fn handle_completion_request(
13449 cx: &mut EditorLspTestContext,
13450 marked_string: &str,
13451 completions: Vec<&'static str>,
13452 counter: Arc<AtomicUsize>,
13453) -> impl Future<Output = ()> {
13454 let complete_from_marker: TextRangeMarker = '|'.into();
13455 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13456 let (_, mut marked_ranges) = marked_text_ranges_by(
13457 marked_string,
13458 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13459 );
13460
13461 let complete_from_position =
13462 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13463 let replace_range =
13464 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13465
13466 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13467 let completions = completions.clone();
13468 counter.fetch_add(1, atomic::Ordering::Release);
13469 async move {
13470 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13471 assert_eq!(
13472 params.text_document_position.position,
13473 complete_from_position
13474 );
13475 Ok(Some(lsp::CompletionResponse::Array(
13476 completions
13477 .iter()
13478 .map(|completion_text| lsp::CompletionItem {
13479 label: completion_text.to_string(),
13480 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13481 range: replace_range,
13482 new_text: completion_text.to_string(),
13483 })),
13484 ..Default::default()
13485 })
13486 .collect(),
13487 )))
13488 }
13489 });
13490
13491 async move {
13492 request.next().await;
13493 }
13494}
13495
13496fn handle_resolve_completion_request(
13497 cx: &mut EditorLspTestContext,
13498 edits: Option<Vec<(&'static str, &'static str)>>,
13499) -> impl Future<Output = ()> {
13500 let edits = edits.map(|edits| {
13501 edits
13502 .iter()
13503 .map(|(marked_string, new_text)| {
13504 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13505 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13506 lsp::TextEdit::new(replace_range, new_text.to_string())
13507 })
13508 .collect::<Vec<_>>()
13509 });
13510
13511 let mut request =
13512 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13513 let edits = edits.clone();
13514 async move {
13515 Ok(lsp::CompletionItem {
13516 additional_text_edits: edits,
13517 ..Default::default()
13518 })
13519 }
13520 });
13521
13522 async move {
13523 request.next().await;
13524 }
13525}
13526
13527pub(crate) fn update_test_language_settings(
13528 cx: &mut TestAppContext,
13529 f: impl Fn(&mut AllLanguageSettingsContent),
13530) {
13531 _ = cx.update(|cx| {
13532 SettingsStore::update_global(cx, |store, cx| {
13533 store.update_user_settings::<AllLanguageSettings>(cx, f);
13534 });
13535 });
13536}
13537
13538pub(crate) fn update_test_project_settings(
13539 cx: &mut TestAppContext,
13540 f: impl Fn(&mut ProjectSettings),
13541) {
13542 _ = cx.update(|cx| {
13543 SettingsStore::update_global(cx, |store, cx| {
13544 store.update_user_settings::<ProjectSettings>(cx, f);
13545 });
13546 });
13547}
13548
13549pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
13550 _ = cx.update(|cx| {
13551 assets::Assets.load_test_fonts(cx);
13552 let store = SettingsStore::test(cx);
13553 cx.set_global(store);
13554 theme::init(theme::LoadThemes::JustBase, cx);
13555 release_channel::init(SemanticVersion::default(), cx);
13556 client::init_settings(cx);
13557 language::init(cx);
13558 Project::init_settings(cx);
13559 workspace::init_settings(cx);
13560 crate::init(cx);
13561 });
13562
13563 update_test_language_settings(cx, f);
13564}
13565
13566pub(crate) fn rust_lang() -> Arc<Language> {
13567 Arc::new(Language::new(
13568 LanguageConfig {
13569 name: "Rust".into(),
13570 matcher: LanguageMatcher {
13571 path_suffixes: vec!["rs".to_string()],
13572 ..Default::default()
13573 },
13574 ..Default::default()
13575 },
13576 Some(tree_sitter_rust::language()),
13577 ))
13578}
13579
13580#[track_caller]
13581fn assert_hunk_revert(
13582 not_reverted_text_with_selections: &str,
13583 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
13584 expected_reverted_text_with_selections: &str,
13585 base_text: &str,
13586 cx: &mut EditorLspTestContext,
13587) {
13588 cx.set_state(not_reverted_text_with_selections);
13589 cx.update_editor(|editor, cx| {
13590 editor
13591 .buffer()
13592 .read(cx)
13593 .as_singleton()
13594 .unwrap()
13595 .update(cx, |buffer, cx| {
13596 buffer.set_diff_base(Some(base_text.into()), cx);
13597 });
13598 });
13599 cx.executor().run_until_parked();
13600
13601 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
13602 let snapshot = editor.buffer().read(cx).snapshot(cx);
13603 let reverted_hunk_statuses = snapshot
13604 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
13605 .map(|hunk| hunk_status(&hunk))
13606 .collect::<Vec<_>>();
13607
13608 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
13609 reverted_hunk_statuses
13610 });
13611 cx.executor().run_until_parked();
13612 cx.assert_editor_state(expected_reverted_text_with_selections);
13613 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
13614}