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