1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_hunks,
6 editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext,
7 expanded_hunks, expanded_hunks_background_highlights, select_ranges,
8 },
9 JoinLines,
10};
11use futures::StreamExt;
12use gpui::{
13 div, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds,
14 WindowOptions,
15};
16use indoc::indoc;
17use language::{
18 language_settings::{
19 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
20 },
21 BracketPairConfig,
22 Capability::ReadWrite,
23 FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher, Override,
24 ParsedMarkdown, Point,
25};
26use language_settings::{Formatter, FormatterList, IndentGuideSettings};
27use multi_buffer::MultiBufferIndentGuide;
28use parking_lot::Mutex;
29use project::FakeFs;
30use project::{
31 lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
32 project_settings::{LspSettings, ProjectSettings},
33};
34use serde_json::{self, json};
35use std::sync::atomic;
36use std::sync::atomic::AtomicUsize;
37use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
38use unindent::Unindent;
39use util::{
40 assert_set_eq,
41 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
42};
43use workspace::{
44 item::{FollowEvent, FollowableItem, Item, ItemHandle},
45 NavigationEntry, ViewId,
46};
47
48#[gpui::test]
49fn test_edit_events(cx: &mut TestAppContext) {
50 init_test(cx, |_| {});
51
52 let buffer = cx.new_model(|cx| {
53 let mut buffer = language::Buffer::local("123456", cx);
54 buffer.set_group_interval(Duration::from_secs(1));
55 buffer
56 });
57
58 let events = Rc::new(RefCell::new(Vec::new()));
59 let editor1 = cx.add_window({
60 let events = events.clone();
61 |cx| {
62 let view = cx.view().clone();
63 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
64 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
65 EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
66 _ => {}
67 })
68 .detach();
69 Editor::for_buffer(buffer.clone(), None, cx)
70 }
71 });
72
73 let editor2 = cx.add_window({
74 let events = events.clone();
75 |cx| {
76 cx.subscribe(
77 &cx.view().clone(),
78 move |_, _, event: &EditorEvent, _| match event {
79 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
80 EditorEvent::BufferEdited => {
81 events.borrow_mut().push(("editor2", "buffer edited"))
82 }
83 _ => {}
84 },
85 )
86 .detach();
87 Editor::for_buffer(buffer.clone(), None, cx)
88 }
89 });
90
91 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
92
93 // Mutating editor 1 will emit an `Edited` event only for that editor.
94 _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
95 assert_eq!(
96 mem::take(&mut *events.borrow_mut()),
97 [
98 ("editor1", "edited"),
99 ("editor1", "buffer edited"),
100 ("editor2", "buffer edited"),
101 ]
102 );
103
104 // Mutating editor 2 will emit an `Edited` event only for that editor.
105 _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
106 assert_eq!(
107 mem::take(&mut *events.borrow_mut()),
108 [
109 ("editor2", "edited"),
110 ("editor1", "buffer edited"),
111 ("editor2", "buffer edited"),
112 ]
113 );
114
115 // Undoing on editor 1 will emit an `Edited` event only for that editor.
116 _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
117 assert_eq!(
118 mem::take(&mut *events.borrow_mut()),
119 [
120 ("editor1", "edited"),
121 ("editor1", "buffer edited"),
122 ("editor2", "buffer edited"),
123 ]
124 );
125
126 // Redoing on editor 1 will emit an `Edited` event only for that editor.
127 _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
128 assert_eq!(
129 mem::take(&mut *events.borrow_mut()),
130 [
131 ("editor1", "edited"),
132 ("editor1", "buffer edited"),
133 ("editor2", "buffer edited"),
134 ]
135 );
136
137 // Undoing on editor 2 will emit an `Edited` event only for that editor.
138 _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
139 assert_eq!(
140 mem::take(&mut *events.borrow_mut()),
141 [
142 ("editor2", "edited"),
143 ("editor1", "buffer edited"),
144 ("editor2", "buffer edited"),
145 ]
146 );
147
148 // Redoing on editor 2 will emit an `Edited` event only for that editor.
149 _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
150 assert_eq!(
151 mem::take(&mut *events.borrow_mut()),
152 [
153 ("editor2", "edited"),
154 ("editor1", "buffer edited"),
155 ("editor2", "buffer edited"),
156 ]
157 );
158
159 // No event is emitted when the mutation is a no-op.
160 _ = editor2.update(cx, |editor, cx| {
161 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
162
163 editor.backspace(&Backspace, cx);
164 });
165 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
166}
167
168#[gpui::test]
169fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
170 init_test(cx, |_| {});
171
172 let mut now = Instant::now();
173 let buffer = cx.new_model(|cx| language::Buffer::local("123456", cx));
174 let group_interval = buffer.update(cx, |buffer, _| buffer.transaction_group_interval());
175 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
176 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
177
178 _ = editor.update(cx, |editor, cx| {
179 editor.start_transaction_at(now, cx);
180 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
181
182 editor.insert("cd", cx);
183 editor.end_transaction_at(now, cx);
184 assert_eq!(editor.text(cx), "12cd56");
185 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
186
187 editor.start_transaction_at(now, cx);
188 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
189 editor.insert("e", cx);
190 editor.end_transaction_at(now, cx);
191 assert_eq!(editor.text(cx), "12cde6");
192 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
193
194 now += group_interval + Duration::from_millis(1);
195 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
196
197 // Simulate an edit in another editor
198 _ = buffer.update(cx, |buffer, cx| {
199 buffer.start_transaction_at(now, cx);
200 buffer.edit([(0..1, "a")], None, cx);
201 buffer.edit([(1..1, "b")], None, cx);
202 buffer.end_transaction_at(now, cx);
203 });
204
205 assert_eq!(editor.text(cx), "ab2cde6");
206 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
207
208 // Last transaction happened past the group interval in a different editor.
209 // Undo it individually and don't restore selections.
210 editor.undo(&Undo, cx);
211 assert_eq!(editor.text(cx), "12cde6");
212 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
213
214 // First two transactions happened within the group interval in this editor.
215 // Undo them together and restore selections.
216 editor.undo(&Undo, cx);
217 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
218 assert_eq!(editor.text(cx), "123456");
219 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
220
221 // Redo the first two transactions together.
222 editor.redo(&Redo, cx);
223 assert_eq!(editor.text(cx), "12cde6");
224 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
225
226 // Redo the last transaction on its own.
227 editor.redo(&Redo, cx);
228 assert_eq!(editor.text(cx), "ab2cde6");
229 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
230
231 // Test empty transactions.
232 editor.start_transaction_at(now, cx);
233 editor.end_transaction_at(now, cx);
234 editor.undo(&Undo, cx);
235 assert_eq!(editor.text(cx), "12cde6");
236 });
237}
238
239#[gpui::test]
240fn test_ime_composition(cx: &mut TestAppContext) {
241 init_test(cx, |_| {});
242
243 let buffer = cx.new_model(|cx| {
244 let mut buffer = language::Buffer::local("abcde", cx);
245 // Ensure automatic grouping doesn't occur.
246 buffer.set_group_interval(Duration::ZERO);
247 buffer
248 });
249
250 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
251 cx.add_window(|cx| {
252 let mut editor = build_editor(buffer.clone(), cx);
253
254 // Start a new IME composition.
255 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
256 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
257 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
258 assert_eq!(editor.text(cx), "äbcde");
259 assert_eq!(
260 editor.marked_text_ranges(cx),
261 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
262 );
263
264 // Finalize IME composition.
265 editor.replace_text_in_range(None, "ā", cx);
266 assert_eq!(editor.text(cx), "ābcde");
267 assert_eq!(editor.marked_text_ranges(cx), None);
268
269 // IME composition edits are grouped and are undone/redone at once.
270 editor.undo(&Default::default(), cx);
271 assert_eq!(editor.text(cx), "abcde");
272 assert_eq!(editor.marked_text_ranges(cx), None);
273 editor.redo(&Default::default(), cx);
274 assert_eq!(editor.text(cx), "ābcde");
275 assert_eq!(editor.marked_text_ranges(cx), None);
276
277 // Start a new IME composition.
278 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
279 assert_eq!(
280 editor.marked_text_ranges(cx),
281 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
282 );
283
284 // Undoing during an IME composition cancels it.
285 editor.undo(&Default::default(), cx);
286 assert_eq!(editor.text(cx), "ābcde");
287 assert_eq!(editor.marked_text_ranges(cx), None);
288
289 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
290 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
291 assert_eq!(editor.text(cx), "ābcdè");
292 assert_eq!(
293 editor.marked_text_ranges(cx),
294 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
295 );
296
297 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
298 editor.replace_text_in_range(Some(4..999), "ę", cx);
299 assert_eq!(editor.text(cx), "ābcdę");
300 assert_eq!(editor.marked_text_ranges(cx), None);
301
302 // Start a new IME composition with multiple cursors.
303 editor.change_selections(None, cx, |s| {
304 s.select_ranges([
305 OffsetUtf16(1)..OffsetUtf16(1),
306 OffsetUtf16(3)..OffsetUtf16(3),
307 OffsetUtf16(5)..OffsetUtf16(5),
308 ])
309 });
310 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
311 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
312 assert_eq!(
313 editor.marked_text_ranges(cx),
314 Some(vec![
315 OffsetUtf16(0)..OffsetUtf16(3),
316 OffsetUtf16(4)..OffsetUtf16(7),
317 OffsetUtf16(8)..OffsetUtf16(11)
318 ])
319 );
320
321 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
322 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
323 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
324 assert_eq!(
325 editor.marked_text_ranges(cx),
326 Some(vec![
327 OffsetUtf16(1)..OffsetUtf16(2),
328 OffsetUtf16(5)..OffsetUtf16(6),
329 OffsetUtf16(9)..OffsetUtf16(10)
330 ])
331 );
332
333 // Finalize IME composition with multiple cursors.
334 editor.replace_text_in_range(Some(9..10), "2", cx);
335 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
336 assert_eq!(editor.marked_text_ranges(cx), None);
337
338 editor
339 });
340}
341
342#[gpui::test]
343fn test_selection_with_mouse(cx: &mut TestAppContext) {
344 init_test(cx, |_| {});
345
346 let editor = cx.add_window(|cx| {
347 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
348 build_editor(buffer, cx)
349 });
350
351 _ = editor.update(cx, |view, cx| {
352 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
353 });
354 assert_eq!(
355 editor
356 .update(cx, |view, cx| view.selections.display_ranges(cx))
357 .unwrap(),
358 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
359 );
360
361 _ = editor.update(cx, |view, cx| {
362 view.update_selection(
363 DisplayPoint::new(DisplayRow(3), 3),
364 0,
365 gpui::Point::<f32>::default(),
366 cx,
367 );
368 });
369
370 assert_eq!(
371 editor
372 .update(cx, |view, cx| view.selections.display_ranges(cx))
373 .unwrap(),
374 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
375 );
376
377 _ = editor.update(cx, |view, cx| {
378 view.update_selection(
379 DisplayPoint::new(DisplayRow(1), 1),
380 0,
381 gpui::Point::<f32>::default(),
382 cx,
383 );
384 });
385
386 assert_eq!(
387 editor
388 .update(cx, |view, cx| view.selections.display_ranges(cx))
389 .unwrap(),
390 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
391 );
392
393 _ = editor.update(cx, |view, cx| {
394 view.end_selection(cx);
395 view.update_selection(
396 DisplayPoint::new(DisplayRow(3), 3),
397 0,
398 gpui::Point::<f32>::default(),
399 cx,
400 );
401 });
402
403 assert_eq!(
404 editor
405 .update(cx, |view, cx| view.selections.display_ranges(cx))
406 .unwrap(),
407 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
408 );
409
410 _ = editor.update(cx, |view, cx| {
411 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
412 view.update_selection(
413 DisplayPoint::new(DisplayRow(0), 0),
414 0,
415 gpui::Point::<f32>::default(),
416 cx,
417 );
418 });
419
420 assert_eq!(
421 editor
422 .update(cx, |view, cx| view.selections.display_ranges(cx))
423 .unwrap(),
424 [
425 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
426 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
427 ]
428 );
429
430 _ = editor.update(cx, |view, cx| {
431 view.end_selection(cx);
432 });
433
434 assert_eq!(
435 editor
436 .update(cx, |view, cx| view.selections.display_ranges(cx))
437 .unwrap(),
438 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
439 );
440}
441
442#[gpui::test]
443fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
444 init_test(cx, |_| {});
445
446 let editor = cx.add_window(|cx| {
447 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
448 build_editor(buffer, cx)
449 });
450
451 _ = editor.update(cx, |view, cx| {
452 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
453 });
454
455 _ = editor.update(cx, |view, cx| {
456 view.end_selection(cx);
457 });
458
459 _ = editor.update(cx, |view, cx| {
460 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
461 });
462
463 _ = editor.update(cx, |view, cx| {
464 view.end_selection(cx);
465 });
466
467 assert_eq!(
468 editor
469 .update(cx, |view, cx| view.selections.display_ranges(cx))
470 .unwrap(),
471 [
472 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
473 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
474 ]
475 );
476
477 _ = editor.update(cx, |view, cx| {
478 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
479 });
480
481 _ = editor.update(cx, |view, cx| {
482 view.end_selection(cx);
483 });
484
485 assert_eq!(
486 editor
487 .update(cx, |view, cx| view.selections.display_ranges(cx))
488 .unwrap(),
489 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
490 );
491}
492
493#[gpui::test]
494fn test_canceling_pending_selection(cx: &mut TestAppContext) {
495 init_test(cx, |_| {});
496
497 let view = cx.add_window(|cx| {
498 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
499 build_editor(buffer, cx)
500 });
501
502 _ = view.update(cx, |view, cx| {
503 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
504 assert_eq!(
505 view.selections.display_ranges(cx),
506 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
507 );
508 });
509
510 _ = view.update(cx, |view, cx| {
511 view.update_selection(
512 DisplayPoint::new(DisplayRow(3), 3),
513 0,
514 gpui::Point::<f32>::default(),
515 cx,
516 );
517 assert_eq!(
518 view.selections.display_ranges(cx),
519 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
520 );
521 });
522
523 _ = view.update(cx, |view, cx| {
524 view.cancel(&Cancel, cx);
525 view.update_selection(
526 DisplayPoint::new(DisplayRow(1), 1),
527 0,
528 gpui::Point::<f32>::default(),
529 cx,
530 );
531 assert_eq!(
532 view.selections.display_ranges(cx),
533 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
534 );
535 });
536}
537
538#[gpui::test]
539fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
540 init_test(cx, |_| {});
541
542 let view = cx.add_window(|cx| {
543 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
544 build_editor(buffer, cx)
545 });
546
547 _ = view.update(cx, |view, cx| {
548 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
549 assert_eq!(
550 view.selections.display_ranges(cx),
551 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
552 );
553
554 view.move_down(&Default::default(), cx);
555 assert_eq!(
556 view.selections.display_ranges(cx),
557 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
558 );
559
560 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
561 assert_eq!(
562 view.selections.display_ranges(cx),
563 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
564 );
565
566 view.move_up(&Default::default(), cx);
567 assert_eq!(
568 view.selections.display_ranges(cx),
569 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
570 );
571 });
572}
573
574#[gpui::test]
575fn test_clone(cx: &mut TestAppContext) {
576 init_test(cx, |_| {});
577
578 let (text, selection_ranges) = marked_text_ranges(
579 indoc! {"
580 one
581 two
582 threeˇ
583 four
584 fiveˇ
585 "},
586 true,
587 );
588
589 let editor = cx.add_window(|cx| {
590 let buffer = MultiBuffer::build_simple(&text, cx);
591 build_editor(buffer, cx)
592 });
593
594 _ = editor.update(cx, |editor, cx| {
595 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
596 editor.fold_ranges(
597 [
598 (Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
599 (Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
600 ],
601 true,
602 cx,
603 );
604 });
605
606 let cloned_editor = editor
607 .update(cx, |editor, cx| {
608 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
609 })
610 .unwrap()
611 .unwrap();
612
613 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
614 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
615
616 assert_eq!(
617 cloned_editor
618 .update(cx, |e, cx| e.display_text(cx))
619 .unwrap(),
620 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
621 );
622 assert_eq!(
623 cloned_snapshot
624 .folds_in_range(0..text.len())
625 .collect::<Vec<_>>(),
626 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
627 );
628 assert_set_eq!(
629 cloned_editor
630 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
631 .unwrap(),
632 editor
633 .update(cx, |editor, cx| editor.selections.ranges(cx))
634 .unwrap()
635 );
636 assert_set_eq!(
637 cloned_editor
638 .update(cx, |e, cx| e.selections.display_ranges(cx))
639 .unwrap(),
640 editor
641 .update(cx, |e, cx| e.selections.display_ranges(cx))
642 .unwrap()
643 );
644}
645
646#[gpui::test]
647async fn test_navigation_history(cx: &mut TestAppContext) {
648 init_test(cx, |_| {});
649
650 use workspace::item::Item;
651
652 let fs = FakeFs::new(cx.executor());
653 let project = Project::test(fs, [], cx).await;
654 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
655 let pane = workspace
656 .update(cx, |workspace, _| workspace.active_pane().clone())
657 .unwrap();
658
659 _ = workspace.update(cx, |_v, cx| {
660 cx.new_view(|cx| {
661 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
662 let mut editor = build_editor(buffer.clone(), cx);
663 let handle = cx.view();
664 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
665
666 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
667 editor.nav_history.as_mut().unwrap().pop_backward(cx)
668 }
669
670 // Move the cursor a small distance.
671 // Nothing is added to the navigation history.
672 editor.change_selections(None, cx, |s| {
673 s.select_display_ranges([
674 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
675 ])
676 });
677 editor.change_selections(None, cx, |s| {
678 s.select_display_ranges([
679 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
680 ])
681 });
682 assert!(pop_history(&mut editor, cx).is_none());
683
684 // Move the cursor a large distance.
685 // The history can jump back to the previous position.
686 editor.change_selections(None, cx, |s| {
687 s.select_display_ranges([
688 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
689 ])
690 });
691 let nav_entry = pop_history(&mut editor, cx).unwrap();
692 editor.navigate(nav_entry.data.unwrap(), cx);
693 assert_eq!(nav_entry.item.id(), cx.entity_id());
694 assert_eq!(
695 editor.selections.display_ranges(cx),
696 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
697 );
698 assert!(pop_history(&mut editor, cx).is_none());
699
700 // Move the cursor a small distance via the mouse.
701 // Nothing is added to the navigation history.
702 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
703 editor.end_selection(cx);
704 assert_eq!(
705 editor.selections.display_ranges(cx),
706 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
707 );
708 assert!(pop_history(&mut editor, cx).is_none());
709
710 // Move the cursor a large distance via the mouse.
711 // The history can jump back to the previous position.
712 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
713 editor.end_selection(cx);
714 assert_eq!(
715 editor.selections.display_ranges(cx),
716 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
717 );
718 let nav_entry = pop_history(&mut editor, cx).unwrap();
719 editor.navigate(nav_entry.data.unwrap(), cx);
720 assert_eq!(nav_entry.item.id(), cx.entity_id());
721 assert_eq!(
722 editor.selections.display_ranges(cx),
723 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
724 );
725 assert!(pop_history(&mut editor, cx).is_none());
726
727 // Set scroll position to check later
728 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
729 let original_scroll_position = editor.scroll_manager.anchor();
730
731 // Jump to the end of the document and adjust scroll
732 editor.move_to_end(&MoveToEnd, cx);
733 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
734 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
735
736 let nav_entry = pop_history(&mut editor, cx).unwrap();
737 editor.navigate(nav_entry.data.unwrap(), cx);
738 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
739
740 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
741 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
742 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
743 let invalid_point = Point::new(9999, 0);
744 editor.navigate(
745 Box::new(NavigationData {
746 cursor_anchor: invalid_anchor,
747 cursor_position: invalid_point,
748 scroll_anchor: ScrollAnchor {
749 anchor: invalid_anchor,
750 offset: Default::default(),
751 },
752 scroll_top_row: invalid_point.row,
753 }),
754 cx,
755 );
756 assert_eq!(
757 editor.selections.display_ranges(cx),
758 &[editor.max_point(cx)..editor.max_point(cx)]
759 );
760 assert_eq!(
761 editor.scroll_position(cx),
762 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
763 );
764
765 editor
766 })
767 });
768}
769
770#[gpui::test]
771fn test_cancel(cx: &mut TestAppContext) {
772 init_test(cx, |_| {});
773
774 let view = cx.add_window(|cx| {
775 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
776 build_editor(buffer, cx)
777 });
778
779 _ = view.update(cx, |view, cx| {
780 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
781 view.update_selection(
782 DisplayPoint::new(DisplayRow(1), 1),
783 0,
784 gpui::Point::<f32>::default(),
785 cx,
786 );
787 view.end_selection(cx);
788
789 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
790 view.update_selection(
791 DisplayPoint::new(DisplayRow(0), 3),
792 0,
793 gpui::Point::<f32>::default(),
794 cx,
795 );
796 view.end_selection(cx);
797 assert_eq!(
798 view.selections.display_ranges(cx),
799 [
800 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
801 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
802 ]
803 );
804 });
805
806 _ = view.update(cx, |view, cx| {
807 view.cancel(&Cancel, cx);
808 assert_eq!(
809 view.selections.display_ranges(cx),
810 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
811 );
812 });
813
814 _ = view.update(cx, |view, cx| {
815 view.cancel(&Cancel, cx);
816 assert_eq!(
817 view.selections.display_ranges(cx),
818 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
819 );
820 });
821}
822
823#[gpui::test]
824fn test_fold_action(cx: &mut TestAppContext) {
825 init_test(cx, |_| {});
826
827 let view = cx.add_window(|cx| {
828 let buffer = MultiBuffer::build_simple(
829 &"
830 impl Foo {
831 // Hello!
832
833 fn a() {
834 1
835 }
836
837 fn b() {
838 2
839 }
840
841 fn c() {
842 3
843 }
844 }
845 "
846 .unindent(),
847 cx,
848 );
849 build_editor(buffer.clone(), cx)
850 });
851
852 _ = view.update(cx, |view, cx| {
853 view.change_selections(None, cx, |s| {
854 s.select_display_ranges([
855 DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(12), 0)
856 ]);
857 });
858 view.fold(&Fold, cx);
859 assert_eq!(
860 view.display_text(cx),
861 "
862 impl Foo {
863 // Hello!
864
865 fn a() {
866 1
867 }
868
869 fn b() {⋯
870 }
871
872 fn c() {⋯
873 }
874 }
875 "
876 .unindent(),
877 );
878
879 view.fold(&Fold, cx);
880 assert_eq!(
881 view.display_text(cx),
882 "
883 impl Foo {⋯
884 }
885 "
886 .unindent(),
887 );
888
889 view.unfold_lines(&UnfoldLines, cx);
890 assert_eq!(
891 view.display_text(cx),
892 "
893 impl Foo {
894 // Hello!
895
896 fn a() {
897 1
898 }
899
900 fn b() {⋯
901 }
902
903 fn c() {⋯
904 }
905 }
906 "
907 .unindent(),
908 );
909
910 view.unfold_lines(&UnfoldLines, cx);
911 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
912 });
913}
914
915#[gpui::test]
916fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
917 init_test(cx, |_| {});
918
919 let view = cx.add_window(|cx| {
920 let buffer = MultiBuffer::build_simple(
921 &"
922 class Foo:
923 # Hello!
924
925 def a():
926 print(1)
927
928 def b():
929 print(2)
930
931 def c():
932 print(3)
933 "
934 .unindent(),
935 cx,
936 );
937 build_editor(buffer.clone(), cx)
938 });
939
940 _ = view.update(cx, |view, cx| {
941 view.change_selections(None, cx, |s| {
942 s.select_display_ranges([
943 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(10), 0)
944 ]);
945 });
946 view.fold(&Fold, cx);
947 assert_eq!(
948 view.display_text(cx),
949 "
950 class Foo:
951 # Hello!
952
953 def a():
954 print(1)
955
956 def b():⋯
957
958 def c():⋯
959 "
960 .unindent(),
961 );
962
963 view.fold(&Fold, cx);
964 assert_eq!(
965 view.display_text(cx),
966 "
967 class Foo:⋯
968 "
969 .unindent(),
970 );
971
972 view.unfold_lines(&UnfoldLines, cx);
973 assert_eq!(
974 view.display_text(cx),
975 "
976 class Foo:
977 # Hello!
978
979 def a():
980 print(1)
981
982 def b():⋯
983
984 def c():⋯
985 "
986 .unindent(),
987 );
988
989 view.unfold_lines(&UnfoldLines, cx);
990 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
991 });
992}
993
994#[gpui::test]
995fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
996 init_test(cx, |_| {});
997
998 let view = cx.add_window(|cx| {
999 let buffer = MultiBuffer::build_simple(
1000 &"
1001 class Foo:
1002 # Hello!
1003
1004 def a():
1005 print(1)
1006
1007 def b():
1008 print(2)
1009
1010
1011 def c():
1012 print(3)
1013
1014
1015 "
1016 .unindent(),
1017 cx,
1018 );
1019 build_editor(buffer.clone(), cx)
1020 });
1021
1022 _ = view.update(cx, |view, cx| {
1023 view.change_selections(None, cx, |s| {
1024 s.select_display_ranges([
1025 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(11), 0)
1026 ]);
1027 });
1028 view.fold(&Fold, cx);
1029 assert_eq!(
1030 view.display_text(cx),
1031 "
1032 class Foo:
1033 # Hello!
1034
1035 def a():
1036 print(1)
1037
1038 def b():⋯
1039
1040
1041 def c():⋯
1042
1043
1044 "
1045 .unindent(),
1046 );
1047
1048 view.fold(&Fold, cx);
1049 assert_eq!(
1050 view.display_text(cx),
1051 "
1052 class Foo:⋯
1053
1054
1055 "
1056 .unindent(),
1057 );
1058
1059 view.unfold_lines(&UnfoldLines, cx);
1060 assert_eq!(
1061 view.display_text(cx),
1062 "
1063 class Foo:
1064 # Hello!
1065
1066 def a():
1067 print(1)
1068
1069 def b():⋯
1070
1071
1072 def c():⋯
1073
1074
1075 "
1076 .unindent(),
1077 );
1078
1079 view.unfold_lines(&UnfoldLines, cx);
1080 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1081 });
1082}
1083
1084#[gpui::test]
1085fn test_move_cursor(cx: &mut TestAppContext) {
1086 init_test(cx, |_| {});
1087
1088 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1089 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1090
1091 _ = buffer.update(cx, |buffer, cx| {
1092 buffer.edit(
1093 vec![
1094 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1095 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1096 ],
1097 None,
1098 cx,
1099 );
1100 });
1101 _ = view.update(cx, |view, cx| {
1102 assert_eq!(
1103 view.selections.display_ranges(cx),
1104 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1105 );
1106
1107 view.move_down(&MoveDown, cx);
1108 assert_eq!(
1109 view.selections.display_ranges(cx),
1110 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1111 );
1112
1113 view.move_right(&MoveRight, cx);
1114 assert_eq!(
1115 view.selections.display_ranges(cx),
1116 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1117 );
1118
1119 view.move_left(&MoveLeft, cx);
1120 assert_eq!(
1121 view.selections.display_ranges(cx),
1122 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1123 );
1124
1125 view.move_up(&MoveUp, cx);
1126 assert_eq!(
1127 view.selections.display_ranges(cx),
1128 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1129 );
1130
1131 view.move_to_end(&MoveToEnd, cx);
1132 assert_eq!(
1133 view.selections.display_ranges(cx),
1134 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1135 );
1136
1137 view.move_to_beginning(&MoveToBeginning, cx);
1138 assert_eq!(
1139 view.selections.display_ranges(cx),
1140 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1141 );
1142
1143 view.change_selections(None, cx, |s| {
1144 s.select_display_ranges([
1145 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1146 ]);
1147 });
1148 view.select_to_beginning(&SelectToBeginning, cx);
1149 assert_eq!(
1150 view.selections.display_ranges(cx),
1151 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1152 );
1153
1154 view.select_to_end(&SelectToEnd, cx);
1155 assert_eq!(
1156 view.selections.display_ranges(cx),
1157 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1158 );
1159 });
1160}
1161
1162// TODO: Re-enable this test
1163#[cfg(target_os = "macos")]
1164#[gpui::test]
1165fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1166 init_test(cx, |_| {});
1167
1168 let view = cx.add_window(|cx| {
1169 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1170 build_editor(buffer.clone(), cx)
1171 });
1172
1173 assert_eq!('ⓐ'.len_utf8(), 3);
1174 assert_eq!('α'.len_utf8(), 2);
1175
1176 _ = view.update(cx, |view, cx| {
1177 view.fold_ranges(
1178 vec![
1179 (Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1180 (Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1181 (Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1182 ],
1183 true,
1184 cx,
1185 );
1186 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1187
1188 view.move_right(&MoveRight, cx);
1189 assert_eq!(
1190 view.selections.display_ranges(cx),
1191 &[empty_range(0, "ⓐ".len())]
1192 );
1193 view.move_right(&MoveRight, cx);
1194 assert_eq!(
1195 view.selections.display_ranges(cx),
1196 &[empty_range(0, "ⓐⓑ".len())]
1197 );
1198 view.move_right(&MoveRight, cx);
1199 assert_eq!(
1200 view.selections.display_ranges(cx),
1201 &[empty_range(0, "ⓐⓑ⋯".len())]
1202 );
1203
1204 view.move_down(&MoveDown, cx);
1205 assert_eq!(
1206 view.selections.display_ranges(cx),
1207 &[empty_range(1, "ab⋯e".len())]
1208 );
1209 view.move_left(&MoveLeft, cx);
1210 assert_eq!(
1211 view.selections.display_ranges(cx),
1212 &[empty_range(1, "ab⋯".len())]
1213 );
1214 view.move_left(&MoveLeft, cx);
1215 assert_eq!(
1216 view.selections.display_ranges(cx),
1217 &[empty_range(1, "ab".len())]
1218 );
1219 view.move_left(&MoveLeft, cx);
1220 assert_eq!(
1221 view.selections.display_ranges(cx),
1222 &[empty_range(1, "a".len())]
1223 );
1224
1225 view.move_down(&MoveDown, cx);
1226 assert_eq!(
1227 view.selections.display_ranges(cx),
1228 &[empty_range(2, "α".len())]
1229 );
1230 view.move_right(&MoveRight, cx);
1231 assert_eq!(
1232 view.selections.display_ranges(cx),
1233 &[empty_range(2, "αβ".len())]
1234 );
1235 view.move_right(&MoveRight, cx);
1236 assert_eq!(
1237 view.selections.display_ranges(cx),
1238 &[empty_range(2, "αβ⋯".len())]
1239 );
1240 view.move_right(&MoveRight, cx);
1241 assert_eq!(
1242 view.selections.display_ranges(cx),
1243 &[empty_range(2, "αβ⋯ε".len())]
1244 );
1245
1246 view.move_up(&MoveUp, cx);
1247 assert_eq!(
1248 view.selections.display_ranges(cx),
1249 &[empty_range(1, "ab⋯e".len())]
1250 );
1251 view.move_down(&MoveDown, cx);
1252 assert_eq!(
1253 view.selections.display_ranges(cx),
1254 &[empty_range(2, "αβ⋯ε".len())]
1255 );
1256 view.move_up(&MoveUp, cx);
1257 assert_eq!(
1258 view.selections.display_ranges(cx),
1259 &[empty_range(1, "ab⋯e".len())]
1260 );
1261
1262 view.move_up(&MoveUp, cx);
1263 assert_eq!(
1264 view.selections.display_ranges(cx),
1265 &[empty_range(0, "ⓐⓑ".len())]
1266 );
1267 view.move_left(&MoveLeft, cx);
1268 assert_eq!(
1269 view.selections.display_ranges(cx),
1270 &[empty_range(0, "ⓐ".len())]
1271 );
1272 view.move_left(&MoveLeft, cx);
1273 assert_eq!(
1274 view.selections.display_ranges(cx),
1275 &[empty_range(0, "".len())]
1276 );
1277 });
1278}
1279
1280#[gpui::test]
1281fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1282 init_test(cx, |_| {});
1283
1284 let view = cx.add_window(|cx| {
1285 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1286 build_editor(buffer.clone(), cx)
1287 });
1288 _ = view.update(cx, |view, cx| {
1289 view.change_selections(None, cx, |s| {
1290 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1291 });
1292 view.move_down(&MoveDown, cx);
1293 assert_eq!(
1294 view.selections.display_ranges(cx),
1295 &[empty_range(1, "abcd".len())]
1296 );
1297
1298 view.move_down(&MoveDown, cx);
1299 assert_eq!(
1300 view.selections.display_ranges(cx),
1301 &[empty_range(2, "αβγ".len())]
1302 );
1303
1304 view.move_down(&MoveDown, cx);
1305 assert_eq!(
1306 view.selections.display_ranges(cx),
1307 &[empty_range(3, "abcd".len())]
1308 );
1309
1310 view.move_down(&MoveDown, cx);
1311 assert_eq!(
1312 view.selections.display_ranges(cx),
1313 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1314 );
1315
1316 view.move_up(&MoveUp, cx);
1317 assert_eq!(
1318 view.selections.display_ranges(cx),
1319 &[empty_range(3, "abcd".len())]
1320 );
1321
1322 view.move_up(&MoveUp, cx);
1323 assert_eq!(
1324 view.selections.display_ranges(cx),
1325 &[empty_range(2, "αβγ".len())]
1326 );
1327 });
1328}
1329
1330#[gpui::test]
1331fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1332 init_test(cx, |_| {});
1333 let move_to_beg = MoveToBeginningOfLine {
1334 stop_at_soft_wraps: true,
1335 };
1336
1337 let move_to_end = MoveToEndOfLine {
1338 stop_at_soft_wraps: true,
1339 };
1340
1341 let view = cx.add_window(|cx| {
1342 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1343 build_editor(buffer, cx)
1344 });
1345 _ = view.update(cx, |view, cx| {
1346 view.change_selections(None, cx, |s| {
1347 s.select_display_ranges([
1348 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1349 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1350 ]);
1351 });
1352 });
1353
1354 _ = view.update(cx, |view, cx| {
1355 view.move_to_beginning_of_line(&move_to_beg, cx);
1356 assert_eq!(
1357 view.selections.display_ranges(cx),
1358 &[
1359 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1360 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1361 ]
1362 );
1363 });
1364
1365 _ = view.update(cx, |view, cx| {
1366 view.move_to_beginning_of_line(&move_to_beg, cx);
1367 assert_eq!(
1368 view.selections.display_ranges(cx),
1369 &[
1370 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1371 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1372 ]
1373 );
1374 });
1375
1376 _ = view.update(cx, |view, cx| {
1377 view.move_to_beginning_of_line(&move_to_beg, cx);
1378 assert_eq!(
1379 view.selections.display_ranges(cx),
1380 &[
1381 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1382 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1383 ]
1384 );
1385 });
1386
1387 _ = view.update(cx, |view, cx| {
1388 view.move_to_end_of_line(&move_to_end, cx);
1389 assert_eq!(
1390 view.selections.display_ranges(cx),
1391 &[
1392 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1393 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1394 ]
1395 );
1396 });
1397
1398 // Moving to the end of line again is a no-op.
1399 _ = view.update(cx, |view, cx| {
1400 view.move_to_end_of_line(&move_to_end, cx);
1401 assert_eq!(
1402 view.selections.display_ranges(cx),
1403 &[
1404 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1405 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1406 ]
1407 );
1408 });
1409
1410 _ = view.update(cx, |view, cx| {
1411 view.move_left(&MoveLeft, cx);
1412 view.select_to_beginning_of_line(
1413 &SelectToBeginningOfLine {
1414 stop_at_soft_wraps: true,
1415 },
1416 cx,
1417 );
1418 assert_eq!(
1419 view.selections.display_ranges(cx),
1420 &[
1421 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1422 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1423 ]
1424 );
1425 });
1426
1427 _ = view.update(cx, |view, cx| {
1428 view.select_to_beginning_of_line(
1429 &SelectToBeginningOfLine {
1430 stop_at_soft_wraps: true,
1431 },
1432 cx,
1433 );
1434 assert_eq!(
1435 view.selections.display_ranges(cx),
1436 &[
1437 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1438 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1439 ]
1440 );
1441 });
1442
1443 _ = view.update(cx, |view, cx| {
1444 view.select_to_beginning_of_line(
1445 &SelectToBeginningOfLine {
1446 stop_at_soft_wraps: true,
1447 },
1448 cx,
1449 );
1450 assert_eq!(
1451 view.selections.display_ranges(cx),
1452 &[
1453 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1454 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1455 ]
1456 );
1457 });
1458
1459 _ = view.update(cx, |view, cx| {
1460 view.select_to_end_of_line(
1461 &SelectToEndOfLine {
1462 stop_at_soft_wraps: true,
1463 },
1464 cx,
1465 );
1466 assert_eq!(
1467 view.selections.display_ranges(cx),
1468 &[
1469 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1470 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1471 ]
1472 );
1473 });
1474
1475 _ = view.update(cx, |view, cx| {
1476 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1477 assert_eq!(view.display_text(cx), "ab\n de");
1478 assert_eq!(
1479 view.selections.display_ranges(cx),
1480 &[
1481 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1482 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1483 ]
1484 );
1485 });
1486
1487 _ = view.update(cx, |view, cx| {
1488 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1489 assert_eq!(view.display_text(cx), "\n");
1490 assert_eq!(
1491 view.selections.display_ranges(cx),
1492 &[
1493 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1494 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1495 ]
1496 );
1497 });
1498}
1499
1500#[gpui::test]
1501fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1502 init_test(cx, |_| {});
1503 let move_to_beg = MoveToBeginningOfLine {
1504 stop_at_soft_wraps: false,
1505 };
1506
1507 let move_to_end = MoveToEndOfLine {
1508 stop_at_soft_wraps: false,
1509 };
1510
1511 let view = cx.add_window(|cx| {
1512 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1513 build_editor(buffer, cx)
1514 });
1515
1516 _ = view.update(cx, |view, cx| {
1517 view.set_wrap_width(Some(140.0.into()), cx);
1518
1519 // We expect the following lines after wrapping
1520 // ```
1521 // thequickbrownfox
1522 // jumpedoverthelazydo
1523 // gs
1524 // ```
1525 // The final `gs` was soft-wrapped onto a new line.
1526 assert_eq!(
1527 "thequickbrownfox\njumpedoverthelaz\nydogs",
1528 view.display_text(cx),
1529 );
1530
1531 // First, let's assert behavior on the first line, that was not soft-wrapped.
1532 // Start the cursor at the `k` on the first line
1533 view.change_selections(None, cx, |s| {
1534 s.select_display_ranges([
1535 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1536 ]);
1537 });
1538
1539 // Moving to the beginning of the line should put us at the beginning of the line.
1540 view.move_to_beginning_of_line(&move_to_beg, cx);
1541 assert_eq!(
1542 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1543 view.selections.display_ranges(cx)
1544 );
1545
1546 // Moving to the end of the line should put us at the end of the line.
1547 view.move_to_end_of_line(&move_to_end, cx);
1548 assert_eq!(
1549 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1550 view.selections.display_ranges(cx)
1551 );
1552
1553 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1554 // Start the cursor at the last line (`y` that was wrapped to a new line)
1555 view.change_selections(None, cx, |s| {
1556 s.select_display_ranges([
1557 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1558 ]);
1559 });
1560
1561 // Moving to the beginning of the line should put us at the start of the second line of
1562 // display text, i.e., the `j`.
1563 view.move_to_beginning_of_line(&move_to_beg, cx);
1564 assert_eq!(
1565 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1566 view.selections.display_ranges(cx)
1567 );
1568
1569 // Moving to the beginning of the line again should be a no-op.
1570 view.move_to_beginning_of_line(&move_to_beg, cx);
1571 assert_eq!(
1572 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1573 view.selections.display_ranges(cx)
1574 );
1575
1576 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1577 // next display line.
1578 view.move_to_end_of_line(&move_to_end, cx);
1579 assert_eq!(
1580 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1581 view.selections.display_ranges(cx)
1582 );
1583
1584 // Moving to the end of the line again should be a no-op.
1585 view.move_to_end_of_line(&move_to_end, cx);
1586 assert_eq!(
1587 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1588 view.selections.display_ranges(cx)
1589 );
1590 });
1591}
1592
1593#[gpui::test]
1594fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1595 init_test(cx, |_| {});
1596
1597 let view = cx.add_window(|cx| {
1598 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1599 build_editor(buffer, cx)
1600 });
1601 _ = view.update(cx, |view, cx| {
1602 view.change_selections(None, cx, |s| {
1603 s.select_display_ranges([
1604 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1605 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1606 ])
1607 });
1608
1609 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1610 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1611
1612 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1613 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1614
1615 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1616 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1617
1618 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1619 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1620
1621 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1622 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1623
1624 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1625 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1626
1627 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1628 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1629
1630 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1631 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1632
1633 view.move_right(&MoveRight, cx);
1634 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1635 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1636
1637 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1638 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1639
1640 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1641 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1642 });
1643}
1644
1645#[gpui::test]
1646fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1647 init_test(cx, |_| {});
1648
1649 let view = cx.add_window(|cx| {
1650 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1651 build_editor(buffer, cx)
1652 });
1653
1654 _ = view.update(cx, |view, cx| {
1655 view.set_wrap_width(Some(140.0.into()), cx);
1656 assert_eq!(
1657 view.display_text(cx),
1658 "use one::{\n two::three::\n four::five\n};"
1659 );
1660
1661 view.change_selections(None, cx, |s| {
1662 s.select_display_ranges([
1663 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1664 ]);
1665 });
1666
1667 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1668 assert_eq!(
1669 view.selections.display_ranges(cx),
1670 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1671 );
1672
1673 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1674 assert_eq!(
1675 view.selections.display_ranges(cx),
1676 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1677 );
1678
1679 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1680 assert_eq!(
1681 view.selections.display_ranges(cx),
1682 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1683 );
1684
1685 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1686 assert_eq!(
1687 view.selections.display_ranges(cx),
1688 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1689 );
1690
1691 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1692 assert_eq!(
1693 view.selections.display_ranges(cx),
1694 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1695 );
1696
1697 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1698 assert_eq!(
1699 view.selections.display_ranges(cx),
1700 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1701 );
1702 });
1703}
1704
1705#[gpui::test]
1706async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1707 init_test(cx, |_| {});
1708 let mut cx = EditorTestContext::new(cx).await;
1709
1710 let line_height = cx.editor(|editor, cx| {
1711 editor
1712 .style()
1713 .unwrap()
1714 .text
1715 .line_height_in_pixels(cx.rem_size())
1716 });
1717 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1718
1719 cx.set_state(
1720 &r#"ˇone
1721 two
1722
1723 three
1724 fourˇ
1725 five
1726
1727 six"#
1728 .unindent(),
1729 );
1730
1731 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1732 cx.assert_editor_state(
1733 &r#"one
1734 two
1735 ˇ
1736 three
1737 four
1738 five
1739 ˇ
1740 six"#
1741 .unindent(),
1742 );
1743
1744 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1745 cx.assert_editor_state(
1746 &r#"one
1747 two
1748
1749 three
1750 four
1751 five
1752 ˇ
1753 sixˇ"#
1754 .unindent(),
1755 );
1756
1757 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1758 cx.assert_editor_state(
1759 &r#"one
1760 two
1761
1762 three
1763 four
1764 five
1765
1766 sixˇ"#
1767 .unindent(),
1768 );
1769
1770 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1771 cx.assert_editor_state(
1772 &r#"one
1773 two
1774
1775 three
1776 four
1777 five
1778 ˇ
1779 six"#
1780 .unindent(),
1781 );
1782
1783 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1784 cx.assert_editor_state(
1785 &r#"one
1786 two
1787 ˇ
1788 three
1789 four
1790 five
1791
1792 six"#
1793 .unindent(),
1794 );
1795
1796 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1797 cx.assert_editor_state(
1798 &r#"ˇone
1799 two
1800
1801 three
1802 four
1803 five
1804
1805 six"#
1806 .unindent(),
1807 );
1808}
1809
1810#[gpui::test]
1811async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1812 init_test(cx, |_| {});
1813 let mut cx = EditorTestContext::new(cx).await;
1814 let line_height = cx.editor(|editor, cx| {
1815 editor
1816 .style()
1817 .unwrap()
1818 .text
1819 .line_height_in_pixels(cx.rem_size())
1820 });
1821 let window = cx.window;
1822 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1823
1824 cx.set_state(
1825 &r#"ˇone
1826 two
1827 three
1828 four
1829 five
1830 six
1831 seven
1832 eight
1833 nine
1834 ten
1835 "#,
1836 );
1837
1838 cx.update_editor(|editor, cx| {
1839 assert_eq!(
1840 editor.snapshot(cx).scroll_position(),
1841 gpui::Point::new(0., 0.)
1842 );
1843 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1844 assert_eq!(
1845 editor.snapshot(cx).scroll_position(),
1846 gpui::Point::new(0., 3.)
1847 );
1848 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1849 assert_eq!(
1850 editor.snapshot(cx).scroll_position(),
1851 gpui::Point::new(0., 6.)
1852 );
1853 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1854 assert_eq!(
1855 editor.snapshot(cx).scroll_position(),
1856 gpui::Point::new(0., 3.)
1857 );
1858
1859 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1860 assert_eq!(
1861 editor.snapshot(cx).scroll_position(),
1862 gpui::Point::new(0., 1.)
1863 );
1864 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
1865 assert_eq!(
1866 editor.snapshot(cx).scroll_position(),
1867 gpui::Point::new(0., 3.)
1868 );
1869 });
1870}
1871
1872#[gpui::test]
1873async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
1874 init_test(cx, |_| {});
1875 let mut cx = EditorTestContext::new(cx).await;
1876
1877 let line_height = cx.update_editor(|editor, cx| {
1878 editor.set_vertical_scroll_margin(2, cx);
1879 editor
1880 .style()
1881 .unwrap()
1882 .text
1883 .line_height_in_pixels(cx.rem_size())
1884 });
1885 let window = cx.window;
1886 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
1887
1888 cx.set_state(
1889 &r#"ˇone
1890 two
1891 three
1892 four
1893 five
1894 six
1895 seven
1896 eight
1897 nine
1898 ten
1899 "#,
1900 );
1901 cx.update_editor(|editor, cx| {
1902 assert_eq!(
1903 editor.snapshot(cx).scroll_position(),
1904 gpui::Point::new(0., 0.0)
1905 );
1906 });
1907
1908 // Add a cursor below the visible area. Since both cursors cannot fit
1909 // on screen, the editor autoscrolls to reveal the newest cursor, and
1910 // allows the vertical scroll margin below that cursor.
1911 cx.update_editor(|editor, cx| {
1912 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1913 selections.select_ranges([
1914 Point::new(0, 0)..Point::new(0, 0),
1915 Point::new(6, 0)..Point::new(6, 0),
1916 ]);
1917 })
1918 });
1919 cx.update_editor(|editor, cx| {
1920 assert_eq!(
1921 editor.snapshot(cx).scroll_position(),
1922 gpui::Point::new(0., 3.0)
1923 );
1924 });
1925
1926 // Move down. The editor cursor scrolls down to track the newest cursor.
1927 cx.update_editor(|editor, cx| {
1928 editor.move_down(&Default::default(), cx);
1929 });
1930 cx.update_editor(|editor, cx| {
1931 assert_eq!(
1932 editor.snapshot(cx).scroll_position(),
1933 gpui::Point::new(0., 4.0)
1934 );
1935 });
1936
1937 // Add a cursor above the visible area. Since both cursors fit on screen,
1938 // the editor scrolls to show both.
1939 cx.update_editor(|editor, cx| {
1940 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1941 selections.select_ranges([
1942 Point::new(1, 0)..Point::new(1, 0),
1943 Point::new(6, 0)..Point::new(6, 0),
1944 ]);
1945 })
1946 });
1947 cx.update_editor(|editor, cx| {
1948 assert_eq!(
1949 editor.snapshot(cx).scroll_position(),
1950 gpui::Point::new(0., 1.0)
1951 );
1952 });
1953}
1954
1955#[gpui::test]
1956async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
1957 init_test(cx, |_| {});
1958 let mut cx = EditorTestContext::new(cx).await;
1959
1960 let line_height = cx.editor(|editor, cx| {
1961 editor
1962 .style()
1963 .unwrap()
1964 .text
1965 .line_height_in_pixels(cx.rem_size())
1966 });
1967 let window = cx.window;
1968 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
1969 cx.set_state(
1970 &r#"
1971 ˇone
1972 two
1973 threeˇ
1974 four
1975 five
1976 six
1977 seven
1978 eight
1979 nine
1980 ten
1981 "#
1982 .unindent(),
1983 );
1984
1985 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1986 cx.assert_editor_state(
1987 &r#"
1988 one
1989 two
1990 three
1991 ˇfour
1992 five
1993 sixˇ
1994 seven
1995 eight
1996 nine
1997 ten
1998 "#
1999 .unindent(),
2000 );
2001
2002 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2003 cx.assert_editor_state(
2004 &r#"
2005 one
2006 two
2007 three
2008 four
2009 five
2010 six
2011 ˇseven
2012 eight
2013 nineˇ
2014 ten
2015 "#
2016 .unindent(),
2017 );
2018
2019 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2020 cx.assert_editor_state(
2021 &r#"
2022 one
2023 two
2024 three
2025 ˇfour
2026 five
2027 sixˇ
2028 seven
2029 eight
2030 nine
2031 ten
2032 "#
2033 .unindent(),
2034 );
2035
2036 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2037 cx.assert_editor_state(
2038 &r#"
2039 ˇone
2040 two
2041 threeˇ
2042 four
2043 five
2044 six
2045 seven
2046 eight
2047 nine
2048 ten
2049 "#
2050 .unindent(),
2051 );
2052
2053 // Test select collapsing
2054 cx.update_editor(|editor, cx| {
2055 editor.move_page_down(&MovePageDown::default(), cx);
2056 editor.move_page_down(&MovePageDown::default(), cx);
2057 editor.move_page_down(&MovePageDown::default(), cx);
2058 });
2059 cx.assert_editor_state(
2060 &r#"
2061 one
2062 two
2063 three
2064 four
2065 five
2066 six
2067 seven
2068 eight
2069 nine
2070 ˇten
2071 ˇ"#
2072 .unindent(),
2073 );
2074}
2075
2076#[gpui::test]
2077async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2078 init_test(cx, |_| {});
2079 let mut cx = EditorTestContext::new(cx).await;
2080 cx.set_state("one «two threeˇ» four");
2081 cx.update_editor(|editor, cx| {
2082 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2083 assert_eq!(editor.text(cx), " four");
2084 });
2085}
2086
2087#[gpui::test]
2088fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2089 init_test(cx, |_| {});
2090
2091 let view = cx.add_window(|cx| {
2092 let buffer = MultiBuffer::build_simple("one two three four", cx);
2093 build_editor(buffer.clone(), cx)
2094 });
2095
2096 _ = view.update(cx, |view, cx| {
2097 view.change_selections(None, cx, |s| {
2098 s.select_display_ranges([
2099 // an empty selection - the preceding word fragment is deleted
2100 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2101 // characters selected - they are deleted
2102 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2103 ])
2104 });
2105 view.delete_to_previous_word_start(&DeleteToPreviousWordStart, cx);
2106 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2107 });
2108
2109 _ = view.update(cx, |view, cx| {
2110 view.change_selections(None, cx, |s| {
2111 s.select_display_ranges([
2112 // an empty selection - the following word fragment is deleted
2113 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2114 // characters selected - they are deleted
2115 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2116 ])
2117 });
2118 view.delete_to_next_word_end(&DeleteToNextWordEnd, cx);
2119 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2120 });
2121}
2122
2123#[gpui::test]
2124fn test_newline(cx: &mut TestAppContext) {
2125 init_test(cx, |_| {});
2126
2127 let view = cx.add_window(|cx| {
2128 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2129 build_editor(buffer.clone(), cx)
2130 });
2131
2132 _ = view.update(cx, |view, cx| {
2133 view.change_selections(None, cx, |s| {
2134 s.select_display_ranges([
2135 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2136 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2137 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2138 ])
2139 });
2140
2141 view.newline(&Newline, cx);
2142 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2143 });
2144}
2145
2146#[gpui::test]
2147fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2148 init_test(cx, |_| {});
2149
2150 let editor = cx.add_window(|cx| {
2151 let buffer = MultiBuffer::build_simple(
2152 "
2153 a
2154 b(
2155 X
2156 )
2157 c(
2158 X
2159 )
2160 "
2161 .unindent()
2162 .as_str(),
2163 cx,
2164 );
2165 let mut editor = build_editor(buffer.clone(), cx);
2166 editor.change_selections(None, cx, |s| {
2167 s.select_ranges([
2168 Point::new(2, 4)..Point::new(2, 5),
2169 Point::new(5, 4)..Point::new(5, 5),
2170 ])
2171 });
2172 editor
2173 });
2174
2175 _ = editor.update(cx, |editor, cx| {
2176 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2177 editor.buffer.update(cx, |buffer, cx| {
2178 buffer.edit(
2179 [
2180 (Point::new(1, 2)..Point::new(3, 0), ""),
2181 (Point::new(4, 2)..Point::new(6, 0), ""),
2182 ],
2183 None,
2184 cx,
2185 );
2186 assert_eq!(
2187 buffer.read(cx).text(),
2188 "
2189 a
2190 b()
2191 c()
2192 "
2193 .unindent()
2194 );
2195 });
2196 assert_eq!(
2197 editor.selections.ranges(cx),
2198 &[
2199 Point::new(1, 2)..Point::new(1, 2),
2200 Point::new(2, 2)..Point::new(2, 2),
2201 ],
2202 );
2203
2204 editor.newline(&Newline, cx);
2205 assert_eq!(
2206 editor.text(cx),
2207 "
2208 a
2209 b(
2210 )
2211 c(
2212 )
2213 "
2214 .unindent()
2215 );
2216
2217 // The selections are moved after the inserted newlines
2218 assert_eq!(
2219 editor.selections.ranges(cx),
2220 &[
2221 Point::new(2, 0)..Point::new(2, 0),
2222 Point::new(4, 0)..Point::new(4, 0),
2223 ],
2224 );
2225 });
2226}
2227
2228#[gpui::test]
2229async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2230 init_test(cx, |settings| {
2231 settings.defaults.tab_size = NonZeroU32::new(4)
2232 });
2233
2234 let language = Arc::new(
2235 Language::new(
2236 LanguageConfig::default(),
2237 Some(tree_sitter_rust::language()),
2238 )
2239 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2240 .unwrap(),
2241 );
2242
2243 let mut cx = EditorTestContext::new(cx).await;
2244 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2245 cx.set_state(indoc! {"
2246 const a: ˇA = (
2247 (ˇ
2248 «const_functionˇ»(ˇ),
2249 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2250 )ˇ
2251 ˇ);ˇ
2252 "});
2253
2254 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2255 cx.assert_editor_state(indoc! {"
2256 ˇ
2257 const a: A = (
2258 ˇ
2259 (
2260 ˇ
2261 ˇ
2262 const_function(),
2263 ˇ
2264 ˇ
2265 ˇ
2266 ˇ
2267 something_else,
2268 ˇ
2269 )
2270 ˇ
2271 ˇ
2272 );
2273 "});
2274}
2275
2276#[gpui::test]
2277async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2278 init_test(cx, |settings| {
2279 settings.defaults.tab_size = NonZeroU32::new(4)
2280 });
2281
2282 let language = Arc::new(
2283 Language::new(
2284 LanguageConfig::default(),
2285 Some(tree_sitter_rust::language()),
2286 )
2287 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2288 .unwrap(),
2289 );
2290
2291 let mut cx = EditorTestContext::new(cx).await;
2292 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2293 cx.set_state(indoc! {"
2294 const a: ˇA = (
2295 (ˇ
2296 «const_functionˇ»(ˇ),
2297 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2298 )ˇ
2299 ˇ);ˇ
2300 "});
2301
2302 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2303 cx.assert_editor_state(indoc! {"
2304 const a: A = (
2305 ˇ
2306 (
2307 ˇ
2308 const_function(),
2309 ˇ
2310 ˇ
2311 something_else,
2312 ˇ
2313 ˇ
2314 ˇ
2315 ˇ
2316 )
2317 ˇ
2318 );
2319 ˇ
2320 ˇ
2321 "});
2322}
2323
2324#[gpui::test]
2325async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2326 init_test(cx, |settings| {
2327 settings.defaults.tab_size = NonZeroU32::new(4)
2328 });
2329
2330 let language = Arc::new(Language::new(
2331 LanguageConfig {
2332 line_comments: vec!["//".into()],
2333 ..LanguageConfig::default()
2334 },
2335 None,
2336 ));
2337 {
2338 let mut cx = EditorTestContext::new(cx).await;
2339 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2340 cx.set_state(indoc! {"
2341 // Fooˇ
2342 "});
2343
2344 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2345 cx.assert_editor_state(indoc! {"
2346 // Foo
2347 //ˇ
2348 "});
2349 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2350 cx.set_state(indoc! {"
2351 ˇ// Foo
2352 "});
2353 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2354 cx.assert_editor_state(indoc! {"
2355
2356 ˇ// Foo
2357 "});
2358 }
2359 // Ensure that comment continuations can be disabled.
2360 update_test_language_settings(cx, |settings| {
2361 settings.defaults.extend_comment_on_newline = Some(false);
2362 });
2363 let mut cx = EditorTestContext::new(cx).await;
2364 cx.set_state(indoc! {"
2365 // Fooˇ
2366 "});
2367 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2368 cx.assert_editor_state(indoc! {"
2369 // Foo
2370 ˇ
2371 "});
2372}
2373
2374#[gpui::test]
2375fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2376 init_test(cx, |_| {});
2377
2378 let editor = cx.add_window(|cx| {
2379 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2380 let mut editor = build_editor(buffer.clone(), cx);
2381 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2382 editor
2383 });
2384
2385 _ = editor.update(cx, |editor, cx| {
2386 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2387 editor.buffer.update(cx, |buffer, cx| {
2388 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2389 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2390 });
2391 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2392
2393 editor.insert("Z", cx);
2394 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2395
2396 // The selections are moved after the inserted characters
2397 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2398 });
2399}
2400
2401#[gpui::test]
2402async fn test_tab(cx: &mut gpui::TestAppContext) {
2403 init_test(cx, |settings| {
2404 settings.defaults.tab_size = NonZeroU32::new(3)
2405 });
2406
2407 let mut cx = EditorTestContext::new(cx).await;
2408 cx.set_state(indoc! {"
2409 ˇabˇc
2410 ˇ🏀ˇ🏀ˇefg
2411 dˇ
2412 "});
2413 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2414 cx.assert_editor_state(indoc! {"
2415 ˇab ˇc
2416 ˇ🏀 ˇ🏀 ˇefg
2417 d ˇ
2418 "});
2419
2420 cx.set_state(indoc! {"
2421 a
2422 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2423 "});
2424 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2425 cx.assert_editor_state(indoc! {"
2426 a
2427 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2428 "});
2429}
2430
2431#[gpui::test]
2432async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2433 init_test(cx, |_| {});
2434
2435 let mut cx = EditorTestContext::new(cx).await;
2436 let language = Arc::new(
2437 Language::new(
2438 LanguageConfig::default(),
2439 Some(tree_sitter_rust::language()),
2440 )
2441 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2442 .unwrap(),
2443 );
2444 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2445
2446 // cursors that are already at the suggested indent level insert
2447 // a soft tab. cursors that are to the left of the suggested indent
2448 // auto-indent their line.
2449 cx.set_state(indoc! {"
2450 ˇ
2451 const a: B = (
2452 c(
2453 d(
2454 ˇ
2455 )
2456 ˇ
2457 ˇ )
2458 );
2459 "});
2460 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2461 cx.assert_editor_state(indoc! {"
2462 ˇ
2463 const a: B = (
2464 c(
2465 d(
2466 ˇ
2467 )
2468 ˇ
2469 ˇ)
2470 );
2471 "});
2472
2473 // handle auto-indent when there are multiple cursors on the same line
2474 cx.set_state(indoc! {"
2475 const a: B = (
2476 c(
2477 ˇ ˇ
2478 ˇ )
2479 );
2480 "});
2481 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2482 cx.assert_editor_state(indoc! {"
2483 const a: B = (
2484 c(
2485 ˇ
2486 ˇ)
2487 );
2488 "});
2489}
2490
2491#[gpui::test]
2492async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2493 init_test(cx, |settings| {
2494 settings.defaults.tab_size = NonZeroU32::new(4)
2495 });
2496
2497 let language = Arc::new(
2498 Language::new(
2499 LanguageConfig::default(),
2500 Some(tree_sitter_rust::language()),
2501 )
2502 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2503 .unwrap(),
2504 );
2505
2506 let mut cx = EditorTestContext::new(cx).await;
2507 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2508 cx.set_state(indoc! {"
2509 fn a() {
2510 if b {
2511 \t ˇc
2512 }
2513 }
2514 "});
2515
2516 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2517 cx.assert_editor_state(indoc! {"
2518 fn a() {
2519 if b {
2520 ˇc
2521 }
2522 }
2523 "});
2524}
2525
2526#[gpui::test]
2527async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2528 init_test(cx, |settings| {
2529 settings.defaults.tab_size = NonZeroU32::new(4);
2530 });
2531
2532 let mut cx = EditorTestContext::new(cx).await;
2533
2534 cx.set_state(indoc! {"
2535 «oneˇ» «twoˇ»
2536 three
2537 four
2538 "});
2539 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2540 cx.assert_editor_state(indoc! {"
2541 «oneˇ» «twoˇ»
2542 three
2543 four
2544 "});
2545
2546 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2547 cx.assert_editor_state(indoc! {"
2548 «oneˇ» «twoˇ»
2549 three
2550 four
2551 "});
2552
2553 // select across line ending
2554 cx.set_state(indoc! {"
2555 one two
2556 t«hree
2557 ˇ» four
2558 "});
2559 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2560 cx.assert_editor_state(indoc! {"
2561 one two
2562 t«hree
2563 ˇ» four
2564 "});
2565
2566 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2567 cx.assert_editor_state(indoc! {"
2568 one two
2569 t«hree
2570 ˇ» four
2571 "});
2572
2573 // Ensure that indenting/outdenting works when the cursor is at column 0.
2574 cx.set_state(indoc! {"
2575 one two
2576 ˇthree
2577 four
2578 "});
2579 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2580 cx.assert_editor_state(indoc! {"
2581 one two
2582 ˇthree
2583 four
2584 "});
2585
2586 cx.set_state(indoc! {"
2587 one two
2588 ˇ three
2589 four
2590 "});
2591 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2592 cx.assert_editor_state(indoc! {"
2593 one two
2594 ˇthree
2595 four
2596 "});
2597}
2598
2599#[gpui::test]
2600async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2601 init_test(cx, |settings| {
2602 settings.defaults.hard_tabs = Some(true);
2603 });
2604
2605 let mut cx = EditorTestContext::new(cx).await;
2606
2607 // select two ranges on one line
2608 cx.set_state(indoc! {"
2609 «oneˇ» «twoˇ»
2610 three
2611 four
2612 "});
2613 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2614 cx.assert_editor_state(indoc! {"
2615 \t«oneˇ» «twoˇ»
2616 three
2617 four
2618 "});
2619 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2620 cx.assert_editor_state(indoc! {"
2621 \t\t«oneˇ» «twoˇ»
2622 three
2623 four
2624 "});
2625 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2626 cx.assert_editor_state(indoc! {"
2627 \t«oneˇ» «twoˇ»
2628 three
2629 four
2630 "});
2631 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2632 cx.assert_editor_state(indoc! {"
2633 «oneˇ» «twoˇ»
2634 three
2635 four
2636 "});
2637
2638 // select across a line ending
2639 cx.set_state(indoc! {"
2640 one two
2641 t«hree
2642 ˇ»four
2643 "});
2644 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2645 cx.assert_editor_state(indoc! {"
2646 one two
2647 \tt«hree
2648 ˇ»four
2649 "});
2650 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2651 cx.assert_editor_state(indoc! {"
2652 one two
2653 \t\tt«hree
2654 ˇ»four
2655 "});
2656 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2657 cx.assert_editor_state(indoc! {"
2658 one two
2659 \tt«hree
2660 ˇ»four
2661 "});
2662 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2663 cx.assert_editor_state(indoc! {"
2664 one two
2665 t«hree
2666 ˇ»four
2667 "});
2668
2669 // Ensure that indenting/outdenting works when the cursor is at column 0.
2670 cx.set_state(indoc! {"
2671 one two
2672 ˇthree
2673 four
2674 "});
2675 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2676 cx.assert_editor_state(indoc! {"
2677 one two
2678 ˇthree
2679 four
2680 "});
2681 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2682 cx.assert_editor_state(indoc! {"
2683 one two
2684 \tˇthree
2685 four
2686 "});
2687 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2688 cx.assert_editor_state(indoc! {"
2689 one two
2690 ˇthree
2691 four
2692 "});
2693}
2694
2695#[gpui::test]
2696fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2697 init_test(cx, |settings| {
2698 settings.languages.extend([
2699 (
2700 "TOML".into(),
2701 LanguageSettingsContent {
2702 tab_size: NonZeroU32::new(2),
2703 ..Default::default()
2704 },
2705 ),
2706 (
2707 "Rust".into(),
2708 LanguageSettingsContent {
2709 tab_size: NonZeroU32::new(4),
2710 ..Default::default()
2711 },
2712 ),
2713 ]);
2714 });
2715
2716 let toml_language = Arc::new(Language::new(
2717 LanguageConfig {
2718 name: "TOML".into(),
2719 ..Default::default()
2720 },
2721 None,
2722 ));
2723 let rust_language = Arc::new(Language::new(
2724 LanguageConfig {
2725 name: "Rust".into(),
2726 ..Default::default()
2727 },
2728 None,
2729 ));
2730
2731 let toml_buffer =
2732 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2733 let rust_buffer = cx.new_model(|cx| {
2734 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2735 });
2736 let multibuffer = cx.new_model(|cx| {
2737 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
2738 multibuffer.push_excerpts(
2739 toml_buffer.clone(),
2740 [ExcerptRange {
2741 context: Point::new(0, 0)..Point::new(2, 0),
2742 primary: None,
2743 }],
2744 cx,
2745 );
2746 multibuffer.push_excerpts(
2747 rust_buffer.clone(),
2748 [ExcerptRange {
2749 context: Point::new(0, 0)..Point::new(1, 0),
2750 primary: None,
2751 }],
2752 cx,
2753 );
2754 multibuffer
2755 });
2756
2757 cx.add_window(|cx| {
2758 let mut editor = build_editor(multibuffer, cx);
2759
2760 assert_eq!(
2761 editor.text(cx),
2762 indoc! {"
2763 a = 1
2764 b = 2
2765
2766 const c: usize = 3;
2767 "}
2768 );
2769
2770 select_ranges(
2771 &mut editor,
2772 indoc! {"
2773 «aˇ» = 1
2774 b = 2
2775
2776 «const c:ˇ» usize = 3;
2777 "},
2778 cx,
2779 );
2780
2781 editor.tab(&Tab, cx);
2782 assert_text_with_selections(
2783 &mut editor,
2784 indoc! {"
2785 «aˇ» = 1
2786 b = 2
2787
2788 «const c:ˇ» usize = 3;
2789 "},
2790 cx,
2791 );
2792 editor.tab_prev(&TabPrev, cx);
2793 assert_text_with_selections(
2794 &mut editor,
2795 indoc! {"
2796 «aˇ» = 1
2797 b = 2
2798
2799 «const c:ˇ» usize = 3;
2800 "},
2801 cx,
2802 );
2803
2804 editor
2805 });
2806}
2807
2808#[gpui::test]
2809async fn test_backspace(cx: &mut gpui::TestAppContext) {
2810 init_test(cx, |_| {});
2811
2812 let mut cx = EditorTestContext::new(cx).await;
2813
2814 // Basic backspace
2815 cx.set_state(indoc! {"
2816 onˇe two three
2817 fou«rˇ» five six
2818 seven «ˇeight nine
2819 »ten
2820 "});
2821 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2822 cx.assert_editor_state(indoc! {"
2823 oˇe two three
2824 fouˇ five six
2825 seven ˇten
2826 "});
2827
2828 // Test backspace inside and around indents
2829 cx.set_state(indoc! {"
2830 zero
2831 ˇone
2832 ˇtwo
2833 ˇ ˇ ˇ three
2834 ˇ ˇ four
2835 "});
2836 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2837 cx.assert_editor_state(indoc! {"
2838 zero
2839 ˇone
2840 ˇtwo
2841 ˇ threeˇ four
2842 "});
2843
2844 // Test backspace with line_mode set to true
2845 cx.update_editor(|e, _| e.selections.line_mode = true);
2846 cx.set_state(indoc! {"
2847 The ˇquick ˇbrown
2848 fox jumps over
2849 the lazy dog
2850 ˇThe qu«ick bˇ»rown"});
2851 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2852 cx.assert_editor_state(indoc! {"
2853 ˇfox jumps over
2854 the lazy dogˇ"});
2855}
2856
2857#[gpui::test]
2858async fn test_delete(cx: &mut gpui::TestAppContext) {
2859 init_test(cx, |_| {});
2860
2861 let mut cx = EditorTestContext::new(cx).await;
2862 cx.set_state(indoc! {"
2863 onˇe two three
2864 fou«rˇ» five six
2865 seven «ˇeight nine
2866 »ten
2867 "});
2868 cx.update_editor(|e, cx| e.delete(&Delete, cx));
2869 cx.assert_editor_state(indoc! {"
2870 onˇ two three
2871 fouˇ five six
2872 seven ˇten
2873 "});
2874
2875 // Test backspace with line_mode set to true
2876 cx.update_editor(|e, _| e.selections.line_mode = true);
2877 cx.set_state(indoc! {"
2878 The ˇquick ˇbrown
2879 fox «ˇjum»ps over
2880 the lazy dog
2881 ˇThe qu«ick bˇ»rown"});
2882 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2883 cx.assert_editor_state("ˇthe lazy dogˇ");
2884}
2885
2886#[gpui::test]
2887fn test_delete_line(cx: &mut TestAppContext) {
2888 init_test(cx, |_| {});
2889
2890 let view = cx.add_window(|cx| {
2891 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2892 build_editor(buffer, cx)
2893 });
2894 _ = view.update(cx, |view, cx| {
2895 view.change_selections(None, cx, |s| {
2896 s.select_display_ranges([
2897 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
2898 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
2899 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
2900 ])
2901 });
2902 view.delete_line(&DeleteLine, cx);
2903 assert_eq!(view.display_text(cx), "ghi");
2904 assert_eq!(
2905 view.selections.display_ranges(cx),
2906 vec![
2907 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
2908 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
2909 ]
2910 );
2911 });
2912
2913 let view = cx.add_window(|cx| {
2914 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2915 build_editor(buffer, cx)
2916 });
2917 _ = view.update(cx, |view, cx| {
2918 view.change_selections(None, cx, |s| {
2919 s.select_display_ranges([
2920 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
2921 ])
2922 });
2923 view.delete_line(&DeleteLine, cx);
2924 assert_eq!(view.display_text(cx), "ghi\n");
2925 assert_eq!(
2926 view.selections.display_ranges(cx),
2927 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
2928 );
2929 });
2930}
2931
2932#[gpui::test]
2933fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
2934 init_test(cx, |_| {});
2935
2936 cx.add_window(|cx| {
2937 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
2938 let mut editor = build_editor(buffer.clone(), cx);
2939 let buffer = buffer.read(cx).as_singleton().unwrap();
2940
2941 assert_eq!(
2942 editor.selections.ranges::<Point>(cx),
2943 &[Point::new(0, 0)..Point::new(0, 0)]
2944 );
2945
2946 // When on single line, replace newline at end by space
2947 editor.join_lines(&JoinLines, cx);
2948 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
2949 assert_eq!(
2950 editor.selections.ranges::<Point>(cx),
2951 &[Point::new(0, 3)..Point::new(0, 3)]
2952 );
2953
2954 // When multiple lines are selected, remove newlines that are spanned by the selection
2955 editor.change_selections(None, cx, |s| {
2956 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
2957 });
2958 editor.join_lines(&JoinLines, cx);
2959 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
2960 assert_eq!(
2961 editor.selections.ranges::<Point>(cx),
2962 &[Point::new(0, 11)..Point::new(0, 11)]
2963 );
2964
2965 // Undo should be transactional
2966 editor.undo(&Undo, cx);
2967 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
2968 assert_eq!(
2969 editor.selections.ranges::<Point>(cx),
2970 &[Point::new(0, 5)..Point::new(2, 2)]
2971 );
2972
2973 // When joining an empty line don't insert a space
2974 editor.change_selections(None, cx, |s| {
2975 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
2976 });
2977 editor.join_lines(&JoinLines, cx);
2978 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
2979 assert_eq!(
2980 editor.selections.ranges::<Point>(cx),
2981 [Point::new(2, 3)..Point::new(2, 3)]
2982 );
2983
2984 // We can remove trailing newlines
2985 editor.join_lines(&JoinLines, cx);
2986 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
2987 assert_eq!(
2988 editor.selections.ranges::<Point>(cx),
2989 [Point::new(2, 3)..Point::new(2, 3)]
2990 );
2991
2992 // We don't blow up on the last line
2993 editor.join_lines(&JoinLines, cx);
2994 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
2995 assert_eq!(
2996 editor.selections.ranges::<Point>(cx),
2997 [Point::new(2, 3)..Point::new(2, 3)]
2998 );
2999
3000 // reset to test indentation
3001 editor.buffer.update(cx, |buffer, cx| {
3002 buffer.edit(
3003 [
3004 (Point::new(1, 0)..Point::new(1, 2), " "),
3005 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3006 ],
3007 None,
3008 cx,
3009 )
3010 });
3011
3012 // We remove any leading spaces
3013 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3014 editor.change_selections(None, cx, |s| {
3015 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3016 });
3017 editor.join_lines(&JoinLines, cx);
3018 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3019
3020 // We don't insert a space for a line containing only spaces
3021 editor.join_lines(&JoinLines, cx);
3022 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3023
3024 // We ignore any leading tabs
3025 editor.join_lines(&JoinLines, cx);
3026 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3027
3028 editor
3029 });
3030}
3031
3032#[gpui::test]
3033fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3034 init_test(cx, |_| {});
3035
3036 cx.add_window(|cx| {
3037 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3038 let mut editor = build_editor(buffer.clone(), cx);
3039 let buffer = buffer.read(cx).as_singleton().unwrap();
3040
3041 editor.change_selections(None, cx, |s| {
3042 s.select_ranges([
3043 Point::new(0, 2)..Point::new(1, 1),
3044 Point::new(1, 2)..Point::new(1, 2),
3045 Point::new(3, 1)..Point::new(3, 2),
3046 ])
3047 });
3048
3049 editor.join_lines(&JoinLines, cx);
3050 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3051
3052 assert_eq!(
3053 editor.selections.ranges::<Point>(cx),
3054 [
3055 Point::new(0, 7)..Point::new(0, 7),
3056 Point::new(1, 3)..Point::new(1, 3)
3057 ]
3058 );
3059 editor
3060 });
3061}
3062
3063#[gpui::test]
3064async fn test_join_lines_with_git_diff_base(
3065 executor: BackgroundExecutor,
3066 cx: &mut gpui::TestAppContext,
3067) {
3068 init_test(cx, |_| {});
3069
3070 let mut cx = EditorTestContext::new(cx).await;
3071
3072 let diff_base = r#"
3073 Line 0
3074 Line 1
3075 Line 2
3076 Line 3
3077 "#
3078 .unindent();
3079
3080 cx.set_state(
3081 &r#"
3082 ˇLine 0
3083 Line 1
3084 Line 2
3085 Line 3
3086 "#
3087 .unindent(),
3088 );
3089
3090 cx.set_diff_base(Some(&diff_base));
3091 executor.run_until_parked();
3092
3093 // Join lines
3094 cx.update_editor(|editor, cx| {
3095 editor.join_lines(&JoinLines, cx);
3096 });
3097 executor.run_until_parked();
3098
3099 cx.assert_editor_state(
3100 &r#"
3101 Line 0ˇ Line 1
3102 Line 2
3103 Line 3
3104 "#
3105 .unindent(),
3106 );
3107 // Join again
3108 cx.update_editor(|editor, cx| {
3109 editor.join_lines(&JoinLines, cx);
3110 });
3111 executor.run_until_parked();
3112
3113 cx.assert_editor_state(
3114 &r#"
3115 Line 0 Line 1ˇ Line 2
3116 Line 3
3117 "#
3118 .unindent(),
3119 );
3120}
3121
3122#[gpui::test]
3123async fn test_custom_newlines_cause_no_false_positive_diffs(
3124 executor: BackgroundExecutor,
3125 cx: &mut gpui::TestAppContext,
3126) {
3127 init_test(cx, |_| {});
3128 let mut cx = EditorTestContext::new(cx).await;
3129 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3130 cx.set_diff_base(Some("Line 0\r\nLine 1\r\nLine 2\r\nLine 3"));
3131 executor.run_until_parked();
3132
3133 cx.update_editor(|editor, cx| {
3134 assert_eq!(
3135 editor
3136 .buffer()
3137 .read(cx)
3138 .snapshot(cx)
3139 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
3140 .collect::<Vec<_>>(),
3141 Vec::new(),
3142 "Should not have any diffs for files with custom newlines"
3143 );
3144 });
3145}
3146
3147#[gpui::test]
3148async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3149 init_test(cx, |_| {});
3150
3151 let mut cx = EditorTestContext::new(cx).await;
3152
3153 // Test sort_lines_case_insensitive()
3154 cx.set_state(indoc! {"
3155 «z
3156 y
3157 x
3158 Z
3159 Y
3160 Xˇ»
3161 "});
3162 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3163 cx.assert_editor_state(indoc! {"
3164 «x
3165 X
3166 y
3167 Y
3168 z
3169 Zˇ»
3170 "});
3171
3172 // Test reverse_lines()
3173 cx.set_state(indoc! {"
3174 «5
3175 4
3176 3
3177 2
3178 1ˇ»
3179 "});
3180 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3181 cx.assert_editor_state(indoc! {"
3182 «1
3183 2
3184 3
3185 4
3186 5ˇ»
3187 "});
3188
3189 // Skip testing shuffle_line()
3190
3191 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3192 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3193
3194 // Don't manipulate when cursor is on single line, but expand the selection
3195 cx.set_state(indoc! {"
3196 ddˇdd
3197 ccc
3198 bb
3199 a
3200 "});
3201 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3202 cx.assert_editor_state(indoc! {"
3203 «ddddˇ»
3204 ccc
3205 bb
3206 a
3207 "});
3208
3209 // Basic manipulate case
3210 // Start selection moves to column 0
3211 // End of selection shrinks to fit shorter line
3212 cx.set_state(indoc! {"
3213 dd«d
3214 ccc
3215 bb
3216 aaaaaˇ»
3217 "});
3218 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3219 cx.assert_editor_state(indoc! {"
3220 «aaaaa
3221 bb
3222 ccc
3223 dddˇ»
3224 "});
3225
3226 // Manipulate case with newlines
3227 cx.set_state(indoc! {"
3228 dd«d
3229 ccc
3230
3231 bb
3232 aaaaa
3233
3234 ˇ»
3235 "});
3236 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3237 cx.assert_editor_state(indoc! {"
3238 «
3239
3240 aaaaa
3241 bb
3242 ccc
3243 dddˇ»
3244
3245 "});
3246
3247 // Adding new line
3248 cx.set_state(indoc! {"
3249 aa«a
3250 bbˇ»b
3251 "});
3252 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3253 cx.assert_editor_state(indoc! {"
3254 «aaa
3255 bbb
3256 added_lineˇ»
3257 "});
3258
3259 // Removing line
3260 cx.set_state(indoc! {"
3261 aa«a
3262 bbbˇ»
3263 "});
3264 cx.update_editor(|e, cx| {
3265 e.manipulate_lines(cx, |lines| {
3266 lines.pop();
3267 })
3268 });
3269 cx.assert_editor_state(indoc! {"
3270 «aaaˇ»
3271 "});
3272
3273 // Removing all lines
3274 cx.set_state(indoc! {"
3275 aa«a
3276 bbbˇ»
3277 "});
3278 cx.update_editor(|e, cx| {
3279 e.manipulate_lines(cx, |lines| {
3280 lines.drain(..);
3281 })
3282 });
3283 cx.assert_editor_state(indoc! {"
3284 ˇ
3285 "});
3286}
3287
3288#[gpui::test]
3289async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3290 init_test(cx, |_| {});
3291
3292 let mut cx = EditorTestContext::new(cx).await;
3293
3294 // Consider continuous selection as single selection
3295 cx.set_state(indoc! {"
3296 Aaa«aa
3297 cˇ»c«c
3298 bb
3299 aaaˇ»aa
3300 "});
3301 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3302 cx.assert_editor_state(indoc! {"
3303 «Aaaaa
3304 ccc
3305 bb
3306 aaaaaˇ»
3307 "});
3308
3309 cx.set_state(indoc! {"
3310 Aaa«aa
3311 cˇ»c«c
3312 bb
3313 aaaˇ»aa
3314 "});
3315 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3316 cx.assert_editor_state(indoc! {"
3317 «Aaaaa
3318 ccc
3319 bbˇ»
3320 "});
3321
3322 // Consider non continuous selection as distinct dedup operations
3323 cx.set_state(indoc! {"
3324 «aaaaa
3325 bb
3326 aaaaa
3327 aaaaaˇ»
3328
3329 aaa«aaˇ»
3330 "});
3331 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3332 cx.assert_editor_state(indoc! {"
3333 «aaaaa
3334 bbˇ»
3335
3336 «aaaaaˇ»
3337 "});
3338}
3339
3340#[gpui::test]
3341async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3342 init_test(cx, |_| {});
3343
3344 let mut cx = EditorTestContext::new(cx).await;
3345
3346 cx.set_state(indoc! {"
3347 «Aaa
3348 aAa
3349 Aaaˇ»
3350 "});
3351 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3352 cx.assert_editor_state(indoc! {"
3353 «Aaa
3354 aAaˇ»
3355 "});
3356
3357 cx.set_state(indoc! {"
3358 «Aaa
3359 aAa
3360 aaAˇ»
3361 "});
3362 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3363 cx.assert_editor_state(indoc! {"
3364 «Aaaˇ»
3365 "});
3366}
3367
3368#[gpui::test]
3369async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3370 init_test(cx, |_| {});
3371
3372 let mut cx = EditorTestContext::new(cx).await;
3373
3374 // Manipulate with multiple selections on a single line
3375 cx.set_state(indoc! {"
3376 dd«dd
3377 cˇ»c«c
3378 bb
3379 aaaˇ»aa
3380 "});
3381 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3382 cx.assert_editor_state(indoc! {"
3383 «aaaaa
3384 bb
3385 ccc
3386 ddddˇ»
3387 "});
3388
3389 // Manipulate with multiple disjoin selections
3390 cx.set_state(indoc! {"
3391 5«
3392 4
3393 3
3394 2
3395 1ˇ»
3396
3397 dd«dd
3398 ccc
3399 bb
3400 aaaˇ»aa
3401 "});
3402 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3403 cx.assert_editor_state(indoc! {"
3404 «1
3405 2
3406 3
3407 4
3408 5ˇ»
3409
3410 «aaaaa
3411 bb
3412 ccc
3413 ddddˇ»
3414 "});
3415
3416 // Adding lines on each selection
3417 cx.set_state(indoc! {"
3418 2«
3419 1ˇ»
3420
3421 bb«bb
3422 aaaˇ»aa
3423 "});
3424 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3425 cx.assert_editor_state(indoc! {"
3426 «2
3427 1
3428 added lineˇ»
3429
3430 «bbbb
3431 aaaaa
3432 added lineˇ»
3433 "});
3434
3435 // Removing lines on each selection
3436 cx.set_state(indoc! {"
3437 2«
3438 1ˇ»
3439
3440 bb«bb
3441 aaaˇ»aa
3442 "});
3443 cx.update_editor(|e, cx| {
3444 e.manipulate_lines(cx, |lines| {
3445 lines.pop();
3446 })
3447 });
3448 cx.assert_editor_state(indoc! {"
3449 «2ˇ»
3450
3451 «bbbbˇ»
3452 "});
3453}
3454
3455#[gpui::test]
3456async fn test_manipulate_text(cx: &mut TestAppContext) {
3457 init_test(cx, |_| {});
3458
3459 let mut cx = EditorTestContext::new(cx).await;
3460
3461 // Test convert_to_upper_case()
3462 cx.set_state(indoc! {"
3463 «hello worldˇ»
3464 "});
3465 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3466 cx.assert_editor_state(indoc! {"
3467 «HELLO WORLDˇ»
3468 "});
3469
3470 // Test convert_to_lower_case()
3471 cx.set_state(indoc! {"
3472 «HELLO WORLDˇ»
3473 "});
3474 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3475 cx.assert_editor_state(indoc! {"
3476 «hello worldˇ»
3477 "});
3478
3479 // Test multiple line, single selection case
3480 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3481 cx.set_state(indoc! {"
3482 «The quick brown
3483 fox jumps over
3484 the lazy dogˇ»
3485 "});
3486 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3487 cx.assert_editor_state(indoc! {"
3488 «The Quick Brown
3489 Fox Jumps Over
3490 The Lazy Dogˇ»
3491 "});
3492
3493 // Test multiple line, single selection case
3494 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3495 cx.set_state(indoc! {"
3496 «The quick brown
3497 fox jumps over
3498 the lazy dogˇ»
3499 "});
3500 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3501 cx.assert_editor_state(indoc! {"
3502 «TheQuickBrown
3503 FoxJumpsOver
3504 TheLazyDogˇ»
3505 "});
3506
3507 // From here on out, test more complex cases of manipulate_text()
3508
3509 // Test no selection case - should affect words cursors are in
3510 // Cursor at beginning, middle, and end of word
3511 cx.set_state(indoc! {"
3512 ˇhello big beauˇtiful worldˇ
3513 "});
3514 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3515 cx.assert_editor_state(indoc! {"
3516 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3517 "});
3518
3519 // Test multiple selections on a single line and across multiple lines
3520 cx.set_state(indoc! {"
3521 «Theˇ» quick «brown
3522 foxˇ» jumps «overˇ»
3523 the «lazyˇ» dog
3524 "});
3525 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3526 cx.assert_editor_state(indoc! {"
3527 «THEˇ» quick «BROWN
3528 FOXˇ» jumps «OVERˇ»
3529 the «LAZYˇ» dog
3530 "});
3531
3532 // Test case where text length grows
3533 cx.set_state(indoc! {"
3534 «tschüߡ»
3535 "});
3536 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3537 cx.assert_editor_state(indoc! {"
3538 «TSCHÜSSˇ»
3539 "});
3540
3541 // Test to make sure we don't crash when text shrinks
3542 cx.set_state(indoc! {"
3543 aaa_bbbˇ
3544 "});
3545 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3546 cx.assert_editor_state(indoc! {"
3547 «aaaBbbˇ»
3548 "});
3549
3550 // Test to make sure we all aware of the fact that each word can grow and shrink
3551 // Final selections should be aware of this fact
3552 cx.set_state(indoc! {"
3553 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3554 "});
3555 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3556 cx.assert_editor_state(indoc! {"
3557 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3558 "});
3559
3560 cx.set_state(indoc! {"
3561 «hElLo, WoRld!ˇ»
3562 "});
3563 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3564 cx.assert_editor_state(indoc! {"
3565 «HeLlO, wOrLD!ˇ»
3566 "});
3567}
3568
3569#[gpui::test]
3570fn test_duplicate_line(cx: &mut TestAppContext) {
3571 init_test(cx, |_| {});
3572
3573 let view = cx.add_window(|cx| {
3574 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3575 build_editor(buffer, cx)
3576 });
3577 _ = view.update(cx, |view, cx| {
3578 view.change_selections(None, cx, |s| {
3579 s.select_display_ranges([
3580 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3581 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3582 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3583 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3584 ])
3585 });
3586 view.duplicate_line_down(&DuplicateLineDown, cx);
3587 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3588 assert_eq!(
3589 view.selections.display_ranges(cx),
3590 vec![
3591 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3592 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3593 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3594 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3595 ]
3596 );
3597 });
3598
3599 let view = cx.add_window(|cx| {
3600 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3601 build_editor(buffer, cx)
3602 });
3603 _ = view.update(cx, |view, cx| {
3604 view.change_selections(None, cx, |s| {
3605 s.select_display_ranges([
3606 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3607 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3608 ])
3609 });
3610 view.duplicate_line_down(&DuplicateLineDown, cx);
3611 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3612 assert_eq!(
3613 view.selections.display_ranges(cx),
3614 vec![
3615 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3616 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3617 ]
3618 );
3619 });
3620
3621 // With `move_upwards` the selections stay in place, except for
3622 // the lines inserted above them
3623 let view = cx.add_window(|cx| {
3624 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3625 build_editor(buffer, cx)
3626 });
3627 _ = view.update(cx, |view, cx| {
3628 view.change_selections(None, cx, |s| {
3629 s.select_display_ranges([
3630 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3631 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3632 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3633 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3634 ])
3635 });
3636 view.duplicate_line_up(&DuplicateLineUp, cx);
3637 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3638 assert_eq!(
3639 view.selections.display_ranges(cx),
3640 vec![
3641 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3642 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3643 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3644 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3645 ]
3646 );
3647 });
3648
3649 let view = cx.add_window(|cx| {
3650 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3651 build_editor(buffer, cx)
3652 });
3653 _ = view.update(cx, |view, cx| {
3654 view.change_selections(None, cx, |s| {
3655 s.select_display_ranges([
3656 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3657 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3658 ])
3659 });
3660 view.duplicate_line_up(&DuplicateLineUp, cx);
3661 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3662 assert_eq!(
3663 view.selections.display_ranges(cx),
3664 vec![
3665 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3666 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3667 ]
3668 );
3669 });
3670}
3671
3672#[gpui::test]
3673fn test_move_line_up_down(cx: &mut TestAppContext) {
3674 init_test(cx, |_| {});
3675
3676 let view = cx.add_window(|cx| {
3677 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3678 build_editor(buffer, cx)
3679 });
3680 _ = view.update(cx, |view, cx| {
3681 view.fold_ranges(
3682 vec![
3683 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3684 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3685 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3686 ],
3687 true,
3688 cx,
3689 );
3690 view.change_selections(None, cx, |s| {
3691 s.select_display_ranges([
3692 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3693 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3694 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3695 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3696 ])
3697 });
3698 assert_eq!(
3699 view.display_text(cx),
3700 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3701 );
3702
3703 view.move_line_up(&MoveLineUp, cx);
3704 assert_eq!(
3705 view.display_text(cx),
3706 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3707 );
3708 assert_eq!(
3709 view.selections.display_ranges(cx),
3710 vec![
3711 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3712 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3713 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3714 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3715 ]
3716 );
3717 });
3718
3719 _ = view.update(cx, |view, cx| {
3720 view.move_line_down(&MoveLineDown, cx);
3721 assert_eq!(
3722 view.display_text(cx),
3723 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3724 );
3725 assert_eq!(
3726 view.selections.display_ranges(cx),
3727 vec![
3728 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3729 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3730 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3731 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3732 ]
3733 );
3734 });
3735
3736 _ = view.update(cx, |view, cx| {
3737 view.move_line_down(&MoveLineDown, cx);
3738 assert_eq!(
3739 view.display_text(cx),
3740 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3741 );
3742 assert_eq!(
3743 view.selections.display_ranges(cx),
3744 vec![
3745 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3746 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3747 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3748 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3749 ]
3750 );
3751 });
3752
3753 _ = view.update(cx, |view, cx| {
3754 view.move_line_up(&MoveLineUp, cx);
3755 assert_eq!(
3756 view.display_text(cx),
3757 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3758 );
3759 assert_eq!(
3760 view.selections.display_ranges(cx),
3761 vec![
3762 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3763 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3764 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3765 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3766 ]
3767 );
3768 });
3769}
3770
3771#[gpui::test]
3772fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3773 init_test(cx, |_| {});
3774
3775 let editor = cx.add_window(|cx| {
3776 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3777 build_editor(buffer, cx)
3778 });
3779 _ = editor.update(cx, |editor, cx| {
3780 let snapshot = editor.buffer.read(cx).snapshot(cx);
3781 editor.insert_blocks(
3782 [BlockProperties {
3783 style: BlockStyle::Fixed,
3784 position: snapshot.anchor_after(Point::new(2, 0)),
3785 disposition: BlockDisposition::Below,
3786 height: 1,
3787 render: Box::new(|_| div().into_any()),
3788 }],
3789 Some(Autoscroll::fit()),
3790 cx,
3791 );
3792 editor.change_selections(None, cx, |s| {
3793 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
3794 });
3795 editor.move_line_down(&MoveLineDown, cx);
3796 });
3797}
3798
3799#[gpui::test]
3800fn test_transpose(cx: &mut TestAppContext) {
3801 init_test(cx, |_| {});
3802
3803 _ = cx.add_window(|cx| {
3804 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
3805 editor.set_style(EditorStyle::default(), cx);
3806 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
3807 editor.transpose(&Default::default(), cx);
3808 assert_eq!(editor.text(cx), "bac");
3809 assert_eq!(editor.selections.ranges(cx), [2..2]);
3810
3811 editor.transpose(&Default::default(), cx);
3812 assert_eq!(editor.text(cx), "bca");
3813 assert_eq!(editor.selections.ranges(cx), [3..3]);
3814
3815 editor.transpose(&Default::default(), cx);
3816 assert_eq!(editor.text(cx), "bac");
3817 assert_eq!(editor.selections.ranges(cx), [3..3]);
3818
3819 editor
3820 });
3821
3822 _ = cx.add_window(|cx| {
3823 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3824 editor.set_style(EditorStyle::default(), cx);
3825 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
3826 editor.transpose(&Default::default(), cx);
3827 assert_eq!(editor.text(cx), "acb\nde");
3828 assert_eq!(editor.selections.ranges(cx), [3..3]);
3829
3830 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3831 editor.transpose(&Default::default(), cx);
3832 assert_eq!(editor.text(cx), "acbd\ne");
3833 assert_eq!(editor.selections.ranges(cx), [5..5]);
3834
3835 editor.transpose(&Default::default(), cx);
3836 assert_eq!(editor.text(cx), "acbde\n");
3837 assert_eq!(editor.selections.ranges(cx), [6..6]);
3838
3839 editor.transpose(&Default::default(), cx);
3840 assert_eq!(editor.text(cx), "acbd\ne");
3841 assert_eq!(editor.selections.ranges(cx), [6..6]);
3842
3843 editor
3844 });
3845
3846 _ = cx.add_window(|cx| {
3847 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3848 editor.set_style(EditorStyle::default(), cx);
3849 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
3850 editor.transpose(&Default::default(), cx);
3851 assert_eq!(editor.text(cx), "bacd\ne");
3852 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
3853
3854 editor.transpose(&Default::default(), cx);
3855 assert_eq!(editor.text(cx), "bcade\n");
3856 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
3857
3858 editor.transpose(&Default::default(), cx);
3859 assert_eq!(editor.text(cx), "bcda\ne");
3860 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3861
3862 editor.transpose(&Default::default(), cx);
3863 assert_eq!(editor.text(cx), "bcade\n");
3864 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3865
3866 editor.transpose(&Default::default(), cx);
3867 assert_eq!(editor.text(cx), "bcaed\n");
3868 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
3869
3870 editor
3871 });
3872
3873 _ = cx.add_window(|cx| {
3874 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
3875 editor.set_style(EditorStyle::default(), cx);
3876 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3877 editor.transpose(&Default::default(), cx);
3878 assert_eq!(editor.text(cx), "🏀🍐✋");
3879 assert_eq!(editor.selections.ranges(cx), [8..8]);
3880
3881 editor.transpose(&Default::default(), cx);
3882 assert_eq!(editor.text(cx), "🏀✋🍐");
3883 assert_eq!(editor.selections.ranges(cx), [11..11]);
3884
3885 editor.transpose(&Default::default(), cx);
3886 assert_eq!(editor.text(cx), "🏀🍐✋");
3887 assert_eq!(editor.selections.ranges(cx), [11..11]);
3888
3889 editor
3890 });
3891}
3892
3893#[gpui::test]
3894async fn test_clipboard(cx: &mut gpui::TestAppContext) {
3895 init_test(cx, |_| {});
3896
3897 let mut cx = EditorTestContext::new(cx).await;
3898
3899 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
3900 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3901 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
3902
3903 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
3904 cx.set_state("two ˇfour ˇsix ˇ");
3905 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3906 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
3907
3908 // Paste again but with only two cursors. Since the number of cursors doesn't
3909 // match the number of slices in the clipboard, the entire clipboard text
3910 // is pasted at each cursor.
3911 cx.set_state("ˇtwo one✅ four three six five ˇ");
3912 cx.update_editor(|e, cx| {
3913 e.handle_input("( ", cx);
3914 e.paste(&Paste, cx);
3915 e.handle_input(") ", cx);
3916 });
3917 cx.assert_editor_state(
3918 &([
3919 "( one✅ ",
3920 "three ",
3921 "five ) ˇtwo one✅ four three six five ( one✅ ",
3922 "three ",
3923 "five ) ˇ",
3924 ]
3925 .join("\n")),
3926 );
3927
3928 // Cut with three selections, one of which is full-line.
3929 cx.set_state(indoc! {"
3930 1«2ˇ»3
3931 4ˇ567
3932 «8ˇ»9"});
3933 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3934 cx.assert_editor_state(indoc! {"
3935 1ˇ3
3936 ˇ9"});
3937
3938 // Paste with three selections, noticing how the copied selection that was full-line
3939 // gets inserted before the second cursor.
3940 cx.set_state(indoc! {"
3941 1ˇ3
3942 9ˇ
3943 «oˇ»ne"});
3944 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3945 cx.assert_editor_state(indoc! {"
3946 12ˇ3
3947 4567
3948 9ˇ
3949 8ˇne"});
3950
3951 // Copy with a single cursor only, which writes the whole line into the clipboard.
3952 cx.set_state(indoc! {"
3953 The quick brown
3954 fox juˇmps over
3955 the lazy dog"});
3956 cx.update_editor(|e, cx| e.copy(&Copy, cx));
3957 assert_eq!(
3958 cx.read_from_clipboard().map(|item| item.text().to_owned()),
3959 Some("fox jumps over\n".to_owned())
3960 );
3961
3962 // Paste with three selections, noticing how the copied full-line selection is inserted
3963 // before the empty selections but replaces the selection that is non-empty.
3964 cx.set_state(indoc! {"
3965 Tˇhe quick brown
3966 «foˇ»x jumps over
3967 tˇhe lazy dog"});
3968 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3969 cx.assert_editor_state(indoc! {"
3970 fox jumps over
3971 Tˇhe quick brown
3972 fox jumps over
3973 ˇx jumps over
3974 fox jumps over
3975 tˇhe lazy dog"});
3976}
3977
3978#[gpui::test]
3979async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
3980 init_test(cx, |_| {});
3981
3982 let mut cx = EditorTestContext::new(cx).await;
3983 let language = Arc::new(Language::new(
3984 LanguageConfig::default(),
3985 Some(tree_sitter_rust::language()),
3986 ));
3987 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
3988
3989 // Cut an indented block, without the leading whitespace.
3990 cx.set_state(indoc! {"
3991 const a: B = (
3992 c(),
3993 «d(
3994 e,
3995 f
3996 )ˇ»
3997 );
3998 "});
3999 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4000 cx.assert_editor_state(indoc! {"
4001 const a: B = (
4002 c(),
4003 ˇ
4004 );
4005 "});
4006
4007 // Paste it at the same position.
4008 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4009 cx.assert_editor_state(indoc! {"
4010 const a: B = (
4011 c(),
4012 d(
4013 e,
4014 f
4015 )ˇ
4016 );
4017 "});
4018
4019 // Paste it at a line with a lower indent level.
4020 cx.set_state(indoc! {"
4021 ˇ
4022 const a: B = (
4023 c(),
4024 );
4025 "});
4026 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4027 cx.assert_editor_state(indoc! {"
4028 d(
4029 e,
4030 f
4031 )ˇ
4032 const a: B = (
4033 c(),
4034 );
4035 "});
4036
4037 // Cut an indented block, with the leading whitespace.
4038 cx.set_state(indoc! {"
4039 const a: B = (
4040 c(),
4041 « d(
4042 e,
4043 f
4044 )
4045 ˇ»);
4046 "});
4047 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4048 cx.assert_editor_state(indoc! {"
4049 const a: B = (
4050 c(),
4051 ˇ);
4052 "});
4053
4054 // Paste it at the same position.
4055 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4056 cx.assert_editor_state(indoc! {"
4057 const a: B = (
4058 c(),
4059 d(
4060 e,
4061 f
4062 )
4063 ˇ);
4064 "});
4065
4066 // Paste it at a line with a higher indent level.
4067 cx.set_state(indoc! {"
4068 const a: B = (
4069 c(),
4070 d(
4071 e,
4072 fˇ
4073 )
4074 );
4075 "});
4076 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4077 cx.assert_editor_state(indoc! {"
4078 const a: B = (
4079 c(),
4080 d(
4081 e,
4082 f d(
4083 e,
4084 f
4085 )
4086 ˇ
4087 )
4088 );
4089 "});
4090}
4091
4092#[gpui::test]
4093fn test_select_all(cx: &mut TestAppContext) {
4094 init_test(cx, |_| {});
4095
4096 let view = cx.add_window(|cx| {
4097 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4098 build_editor(buffer, cx)
4099 });
4100 _ = view.update(cx, |view, cx| {
4101 view.select_all(&SelectAll, cx);
4102 assert_eq!(
4103 view.selections.display_ranges(cx),
4104 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4105 );
4106 });
4107}
4108
4109#[gpui::test]
4110fn test_select_line(cx: &mut TestAppContext) {
4111 init_test(cx, |_| {});
4112
4113 let view = cx.add_window(|cx| {
4114 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4115 build_editor(buffer, cx)
4116 });
4117 _ = view.update(cx, |view, cx| {
4118 view.change_selections(None, cx, |s| {
4119 s.select_display_ranges([
4120 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4121 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4122 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4123 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4124 ])
4125 });
4126 view.select_line(&SelectLine, cx);
4127 assert_eq!(
4128 view.selections.display_ranges(cx),
4129 vec![
4130 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4131 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4132 ]
4133 );
4134 });
4135
4136 _ = view.update(cx, |view, cx| {
4137 view.select_line(&SelectLine, cx);
4138 assert_eq!(
4139 view.selections.display_ranges(cx),
4140 vec![
4141 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4142 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4143 ]
4144 );
4145 });
4146
4147 _ = view.update(cx, |view, cx| {
4148 view.select_line(&SelectLine, cx);
4149 assert_eq!(
4150 view.selections.display_ranges(cx),
4151 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4152 );
4153 });
4154}
4155
4156#[gpui::test]
4157fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4158 init_test(cx, |_| {});
4159
4160 let view = cx.add_window(|cx| {
4161 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4162 build_editor(buffer, cx)
4163 });
4164 _ = view.update(cx, |view, cx| {
4165 view.fold_ranges(
4166 vec![
4167 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4168 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4169 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4170 ],
4171 true,
4172 cx,
4173 );
4174 view.change_selections(None, cx, |s| {
4175 s.select_display_ranges([
4176 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4177 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4178 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4179 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4180 ])
4181 });
4182 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4183 });
4184
4185 _ = view.update(cx, |view, cx| {
4186 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4187 assert_eq!(
4188 view.display_text(cx),
4189 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4190 );
4191 assert_eq!(
4192 view.selections.display_ranges(cx),
4193 [
4194 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4195 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4196 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4197 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4198 ]
4199 );
4200 });
4201
4202 _ = view.update(cx, |view, cx| {
4203 view.change_selections(None, cx, |s| {
4204 s.select_display_ranges([
4205 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4206 ])
4207 });
4208 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4209 assert_eq!(
4210 view.display_text(cx),
4211 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4212 );
4213 assert_eq!(
4214 view.selections.display_ranges(cx),
4215 [
4216 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4217 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4218 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4219 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4220 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4221 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4222 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4223 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4224 ]
4225 );
4226 });
4227}
4228
4229#[gpui::test]
4230async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4231 init_test(cx, |_| {});
4232
4233 let mut cx = EditorTestContext::new(cx).await;
4234
4235 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4236 cx.set_state(indoc!(
4237 r#"abc
4238 defˇghi
4239
4240 jk
4241 nlmo
4242 "#
4243 ));
4244
4245 cx.update_editor(|editor, cx| {
4246 editor.add_selection_above(&Default::default(), cx);
4247 });
4248
4249 cx.assert_editor_state(indoc!(
4250 r#"abcˇ
4251 defˇghi
4252
4253 jk
4254 nlmo
4255 "#
4256 ));
4257
4258 cx.update_editor(|editor, cx| {
4259 editor.add_selection_above(&Default::default(), cx);
4260 });
4261
4262 cx.assert_editor_state(indoc!(
4263 r#"abcˇ
4264 defˇghi
4265
4266 jk
4267 nlmo
4268 "#
4269 ));
4270
4271 cx.update_editor(|view, cx| {
4272 view.add_selection_below(&Default::default(), cx);
4273 });
4274
4275 cx.assert_editor_state(indoc!(
4276 r#"abc
4277 defˇghi
4278
4279 jk
4280 nlmo
4281 "#
4282 ));
4283
4284 cx.update_editor(|view, cx| {
4285 view.undo_selection(&Default::default(), cx);
4286 });
4287
4288 cx.assert_editor_state(indoc!(
4289 r#"abcˇ
4290 defˇghi
4291
4292 jk
4293 nlmo
4294 "#
4295 ));
4296
4297 cx.update_editor(|view, cx| {
4298 view.redo_selection(&Default::default(), cx);
4299 });
4300
4301 cx.assert_editor_state(indoc!(
4302 r#"abc
4303 defˇghi
4304
4305 jk
4306 nlmo
4307 "#
4308 ));
4309
4310 cx.update_editor(|view, cx| {
4311 view.add_selection_below(&Default::default(), cx);
4312 });
4313
4314 cx.assert_editor_state(indoc!(
4315 r#"abc
4316 defˇghi
4317
4318 jk
4319 nlmˇo
4320 "#
4321 ));
4322
4323 cx.update_editor(|view, cx| {
4324 view.add_selection_below(&Default::default(), cx);
4325 });
4326
4327 cx.assert_editor_state(indoc!(
4328 r#"abc
4329 defˇghi
4330
4331 jk
4332 nlmˇo
4333 "#
4334 ));
4335
4336 // change selections
4337 cx.set_state(indoc!(
4338 r#"abc
4339 def«ˇg»hi
4340
4341 jk
4342 nlmo
4343 "#
4344 ));
4345
4346 cx.update_editor(|view, cx| {
4347 view.add_selection_below(&Default::default(), cx);
4348 });
4349
4350 cx.assert_editor_state(indoc!(
4351 r#"abc
4352 def«ˇg»hi
4353
4354 jk
4355 nlm«ˇo»
4356 "#
4357 ));
4358
4359 cx.update_editor(|view, cx| {
4360 view.add_selection_below(&Default::default(), cx);
4361 });
4362
4363 cx.assert_editor_state(indoc!(
4364 r#"abc
4365 def«ˇg»hi
4366
4367 jk
4368 nlm«ˇo»
4369 "#
4370 ));
4371
4372 cx.update_editor(|view, cx| {
4373 view.add_selection_above(&Default::default(), cx);
4374 });
4375
4376 cx.assert_editor_state(indoc!(
4377 r#"abc
4378 def«ˇg»hi
4379
4380 jk
4381 nlmo
4382 "#
4383 ));
4384
4385 cx.update_editor(|view, cx| {
4386 view.add_selection_above(&Default::default(), cx);
4387 });
4388
4389 cx.assert_editor_state(indoc!(
4390 r#"abc
4391 def«ˇg»hi
4392
4393 jk
4394 nlmo
4395 "#
4396 ));
4397
4398 // Change selections again
4399 cx.set_state(indoc!(
4400 r#"a«bc
4401 defgˇ»hi
4402
4403 jk
4404 nlmo
4405 "#
4406 ));
4407
4408 cx.update_editor(|view, cx| {
4409 view.add_selection_below(&Default::default(), cx);
4410 });
4411
4412 cx.assert_editor_state(indoc!(
4413 r#"a«bcˇ»
4414 d«efgˇ»hi
4415
4416 j«kˇ»
4417 nlmo
4418 "#
4419 ));
4420
4421 cx.update_editor(|view, cx| {
4422 view.add_selection_below(&Default::default(), cx);
4423 });
4424 cx.assert_editor_state(indoc!(
4425 r#"a«bcˇ»
4426 d«efgˇ»hi
4427
4428 j«kˇ»
4429 n«lmoˇ»
4430 "#
4431 ));
4432 cx.update_editor(|view, cx| {
4433 view.add_selection_above(&Default::default(), cx);
4434 });
4435
4436 cx.assert_editor_state(indoc!(
4437 r#"a«bcˇ»
4438 d«efgˇ»hi
4439
4440 j«kˇ»
4441 nlmo
4442 "#
4443 ));
4444
4445 // Change selections again
4446 cx.set_state(indoc!(
4447 r#"abc
4448 d«ˇefghi
4449
4450 jk
4451 nlm»o
4452 "#
4453 ));
4454
4455 cx.update_editor(|view, cx| {
4456 view.add_selection_above(&Default::default(), cx);
4457 });
4458
4459 cx.assert_editor_state(indoc!(
4460 r#"a«ˇbc»
4461 d«ˇef»ghi
4462
4463 j«ˇk»
4464 n«ˇlm»o
4465 "#
4466 ));
4467
4468 cx.update_editor(|view, cx| {
4469 view.add_selection_below(&Default::default(), cx);
4470 });
4471
4472 cx.assert_editor_state(indoc!(
4473 r#"abc
4474 d«ˇef»ghi
4475
4476 j«ˇk»
4477 n«ˇlm»o
4478 "#
4479 ));
4480}
4481
4482#[gpui::test]
4483async fn test_select_next(cx: &mut gpui::TestAppContext) {
4484 init_test(cx, |_| {});
4485
4486 let mut cx = EditorTestContext::new(cx).await;
4487 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4488
4489 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4490 .unwrap();
4491 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4492
4493 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4494 .unwrap();
4495 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4496
4497 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4498 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4499
4500 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4501 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4502
4503 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4504 .unwrap();
4505 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4506
4507 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4508 .unwrap();
4509 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4510}
4511
4512#[gpui::test]
4513async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
4514 init_test(cx, |_| {});
4515
4516 let mut cx = EditorTestContext::new(cx).await;
4517 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4518
4519 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
4520 .unwrap();
4521 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4522}
4523
4524#[gpui::test]
4525async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4526 init_test(cx, |_| {});
4527
4528 let mut cx = EditorTestContext::new(cx).await;
4529 cx.set_state(
4530 r#"let foo = 2;
4531lˇet foo = 2;
4532let fooˇ = 2;
4533let foo = 2;
4534let foo = ˇ2;"#,
4535 );
4536
4537 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4538 .unwrap();
4539 cx.assert_editor_state(
4540 r#"let foo = 2;
4541«letˇ» foo = 2;
4542let «fooˇ» = 2;
4543let foo = 2;
4544let foo = «2ˇ»;"#,
4545 );
4546
4547 // noop for multiple selections with different contents
4548 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4549 .unwrap();
4550 cx.assert_editor_state(
4551 r#"let foo = 2;
4552«letˇ» foo = 2;
4553let «fooˇ» = 2;
4554let foo = 2;
4555let foo = «2ˇ»;"#,
4556 );
4557}
4558
4559#[gpui::test]
4560async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
4561 init_test(cx, |_| {});
4562
4563 let mut cx = EditorTestContext::new_multibuffer(
4564 cx,
4565 [
4566 &indoc! {
4567 "aaa\n«bbb\nccc\n»ddd"
4568 },
4569 &indoc! {
4570 "aaa\n«bbb\nccc\n»ddd"
4571 },
4572 ],
4573 );
4574
4575 cx.assert_editor_state(indoc! {"
4576 ˇbbb
4577 ccc
4578
4579 bbb
4580 ccc
4581 "});
4582 cx.dispatch_action(SelectPrevious::default());
4583 cx.assert_editor_state(indoc! {"
4584 «bbbˇ»
4585 ccc
4586
4587 bbb
4588 ccc
4589 "});
4590 cx.dispatch_action(SelectPrevious::default());
4591 cx.assert_editor_state(indoc! {"
4592 «bbbˇ»
4593 ccc
4594
4595 «bbbˇ»
4596 ccc
4597 "});
4598}
4599
4600#[gpui::test]
4601async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
4602 init_test(cx, |_| {});
4603
4604 let mut cx = EditorTestContext::new(cx).await;
4605 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4606
4607 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4608 .unwrap();
4609 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4610
4611 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4612 .unwrap();
4613 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4614
4615 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4616 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4617
4618 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4619 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4620
4621 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4622 .unwrap();
4623 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
4624
4625 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4626 .unwrap();
4627 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
4628
4629 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4630 .unwrap();
4631 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
4632}
4633
4634#[gpui::test]
4635async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4636 init_test(cx, |_| {});
4637
4638 let mut cx = EditorTestContext::new(cx).await;
4639 cx.set_state(
4640 r#"let foo = 2;
4641lˇet foo = 2;
4642let fooˇ = 2;
4643let foo = 2;
4644let foo = ˇ2;"#,
4645 );
4646
4647 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4648 .unwrap();
4649 cx.assert_editor_state(
4650 r#"let foo = 2;
4651«letˇ» foo = 2;
4652let «fooˇ» = 2;
4653let foo = 2;
4654let foo = «2ˇ»;"#,
4655 );
4656
4657 // noop for multiple selections with different contents
4658 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4659 .unwrap();
4660 cx.assert_editor_state(
4661 r#"let foo = 2;
4662«letˇ» foo = 2;
4663let «fooˇ» = 2;
4664let foo = 2;
4665let foo = «2ˇ»;"#,
4666 );
4667}
4668
4669#[gpui::test]
4670async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
4671 init_test(cx, |_| {});
4672
4673 let mut cx = EditorTestContext::new(cx).await;
4674 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
4675
4676 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4677 .unwrap();
4678 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
4679
4680 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4681 .unwrap();
4682 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
4683
4684 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4685 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
4686
4687 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4688 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
4689
4690 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4691 .unwrap();
4692 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
4693
4694 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4695 .unwrap();
4696 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
4697}
4698
4699#[gpui::test]
4700async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
4701 init_test(cx, |_| {});
4702
4703 let language = Arc::new(Language::new(
4704 LanguageConfig::default(),
4705 Some(tree_sitter_rust::language()),
4706 ));
4707
4708 let text = r#"
4709 use mod1::mod2::{mod3, mod4};
4710
4711 fn fn_1(param1: bool, param2: &str) {
4712 let var1 = "text";
4713 }
4714 "#
4715 .unindent();
4716
4717 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
4718 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4719 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4720
4721 editor
4722 .condition::<crate::EditorEvent>(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4723 .await;
4724
4725 editor.update(cx, |view, cx| {
4726 view.change_selections(None, cx, |s| {
4727 s.select_display_ranges([
4728 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
4729 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
4730 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
4731 ]);
4732 });
4733 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4734 });
4735 editor.update(cx, |editor, cx| {
4736 assert_text_with_selections(
4737 editor,
4738 indoc! {r#"
4739 use mod1::mod2::{mod3, «mod4ˇ»};
4740
4741 fn fn_1«ˇ(param1: bool, param2: &str)» {
4742 let var1 = "«textˇ»";
4743 }
4744 "#},
4745 cx,
4746 );
4747 });
4748
4749 editor.update(cx, |view, cx| {
4750 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4751 });
4752 editor.update(cx, |editor, cx| {
4753 assert_text_with_selections(
4754 editor,
4755 indoc! {r#"
4756 use mod1::mod2::«{mod3, mod4}ˇ»;
4757
4758 «ˇfn fn_1(param1: bool, param2: &str) {
4759 let var1 = "text";
4760 }»
4761 "#},
4762 cx,
4763 );
4764 });
4765
4766 editor.update(cx, |view, cx| {
4767 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4768 });
4769 assert_eq!(
4770 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
4771 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
4772 );
4773
4774 // Trying to expand the selected syntax node one more time has no effect.
4775 editor.update(cx, |view, cx| {
4776 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4777 });
4778 assert_eq!(
4779 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
4780 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
4781 );
4782
4783 editor.update(cx, |view, cx| {
4784 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4785 });
4786 editor.update(cx, |editor, cx| {
4787 assert_text_with_selections(
4788 editor,
4789 indoc! {r#"
4790 use mod1::mod2::«{mod3, mod4}ˇ»;
4791
4792 «ˇfn fn_1(param1: bool, param2: &str) {
4793 let var1 = "text";
4794 }»
4795 "#},
4796 cx,
4797 );
4798 });
4799
4800 editor.update(cx, |view, cx| {
4801 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4802 });
4803 editor.update(cx, |editor, cx| {
4804 assert_text_with_selections(
4805 editor,
4806 indoc! {r#"
4807 use mod1::mod2::{mod3, «mod4ˇ»};
4808
4809 fn fn_1«ˇ(param1: bool, param2: &str)» {
4810 let var1 = "«textˇ»";
4811 }
4812 "#},
4813 cx,
4814 );
4815 });
4816
4817 editor.update(cx, |view, cx| {
4818 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4819 });
4820 editor.update(cx, |editor, cx| {
4821 assert_text_with_selections(
4822 editor,
4823 indoc! {r#"
4824 use mod1::mod2::{mod3, mo«ˇ»d4};
4825
4826 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
4827 let var1 = "te«ˇ»xt";
4828 }
4829 "#},
4830 cx,
4831 );
4832 });
4833
4834 // Trying to shrink the selected syntax node one more time has no effect.
4835 editor.update(cx, |view, cx| {
4836 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4837 });
4838 editor.update(cx, |editor, cx| {
4839 assert_text_with_selections(
4840 editor,
4841 indoc! {r#"
4842 use mod1::mod2::{mod3, mo«ˇ»d4};
4843
4844 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
4845 let var1 = "te«ˇ»xt";
4846 }
4847 "#},
4848 cx,
4849 );
4850 });
4851
4852 // Ensure that we keep expanding the selection if the larger selection starts or ends within
4853 // a fold.
4854 editor.update(cx, |view, cx| {
4855 view.fold_ranges(
4856 vec![
4857 (
4858 Point::new(0, 21)..Point::new(0, 24),
4859 FoldPlaceholder::test(),
4860 ),
4861 (
4862 Point::new(3, 20)..Point::new(3, 22),
4863 FoldPlaceholder::test(),
4864 ),
4865 ],
4866 true,
4867 cx,
4868 );
4869 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4870 });
4871 editor.update(cx, |editor, cx| {
4872 assert_text_with_selections(
4873 editor,
4874 indoc! {r#"
4875 use mod1::mod2::«{mod3, mod4}ˇ»;
4876
4877 fn fn_1«ˇ(param1: bool, param2: &str)» {
4878 «let var1 = "text";ˇ»
4879 }
4880 "#},
4881 cx,
4882 );
4883 });
4884}
4885
4886#[gpui::test]
4887async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
4888 init_test(cx, |_| {});
4889
4890 let language = Arc::new(
4891 Language::new(
4892 LanguageConfig {
4893 brackets: BracketPairConfig {
4894 pairs: vec![
4895 BracketPair {
4896 start: "{".to_string(),
4897 end: "}".to_string(),
4898 close: false,
4899 surround: false,
4900 newline: true,
4901 },
4902 BracketPair {
4903 start: "(".to_string(),
4904 end: ")".to_string(),
4905 close: false,
4906 surround: false,
4907 newline: true,
4908 },
4909 ],
4910 ..Default::default()
4911 },
4912 ..Default::default()
4913 },
4914 Some(tree_sitter_rust::language()),
4915 )
4916 .with_indents_query(
4917 r#"
4918 (_ "(" ")" @end) @indent
4919 (_ "{" "}" @end) @indent
4920 "#,
4921 )
4922 .unwrap(),
4923 );
4924
4925 let text = "fn a() {}";
4926
4927 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
4928 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4929 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4930 editor
4931 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
4932 .await;
4933
4934 _ = editor.update(cx, |editor, cx| {
4935 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
4936 editor.newline(&Newline, cx);
4937 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
4938 assert_eq!(
4939 editor.selections.ranges(cx),
4940 &[
4941 Point::new(1, 4)..Point::new(1, 4),
4942 Point::new(3, 4)..Point::new(3, 4),
4943 Point::new(5, 0)..Point::new(5, 0)
4944 ]
4945 );
4946 });
4947}
4948
4949#[gpui::test]
4950async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
4951 init_test(cx, |_| {});
4952
4953 let mut cx = EditorTestContext::new(cx).await;
4954
4955 let language = Arc::new(Language::new(
4956 LanguageConfig {
4957 brackets: BracketPairConfig {
4958 pairs: vec![
4959 BracketPair {
4960 start: "{".to_string(),
4961 end: "}".to_string(),
4962 close: true,
4963 surround: true,
4964 newline: true,
4965 },
4966 BracketPair {
4967 start: "(".to_string(),
4968 end: ")".to_string(),
4969 close: true,
4970 surround: true,
4971 newline: true,
4972 },
4973 BracketPair {
4974 start: "/*".to_string(),
4975 end: " */".to_string(),
4976 close: true,
4977 surround: true,
4978 newline: true,
4979 },
4980 BracketPair {
4981 start: "[".to_string(),
4982 end: "]".to_string(),
4983 close: false,
4984 surround: false,
4985 newline: true,
4986 },
4987 BracketPair {
4988 start: "\"".to_string(),
4989 end: "\"".to_string(),
4990 close: true,
4991 surround: true,
4992 newline: false,
4993 },
4994 BracketPair {
4995 start: "<".to_string(),
4996 end: ">".to_string(),
4997 close: false,
4998 surround: true,
4999 newline: true,
5000 },
5001 ],
5002 ..Default::default()
5003 },
5004 autoclose_before: "})]".to_string(),
5005 ..Default::default()
5006 },
5007 Some(tree_sitter_rust::language()),
5008 ));
5009
5010 cx.language_registry().add(language.clone());
5011 cx.update_buffer(|buffer, cx| {
5012 buffer.set_language(Some(language), cx);
5013 });
5014
5015 cx.set_state(
5016 &r#"
5017 🏀ˇ
5018 εˇ
5019 ❤️ˇ
5020 "#
5021 .unindent(),
5022 );
5023
5024 // autoclose multiple nested brackets at multiple cursors
5025 cx.update_editor(|view, cx| {
5026 view.handle_input("{", cx);
5027 view.handle_input("{", cx);
5028 view.handle_input("{", cx);
5029 });
5030 cx.assert_editor_state(
5031 &"
5032 🏀{{{ˇ}}}
5033 ε{{{ˇ}}}
5034 ❤️{{{ˇ}}}
5035 "
5036 .unindent(),
5037 );
5038
5039 // insert a different closing bracket
5040 cx.update_editor(|view, cx| {
5041 view.handle_input(")", cx);
5042 });
5043 cx.assert_editor_state(
5044 &"
5045 🏀{{{)ˇ}}}
5046 ε{{{)ˇ}}}
5047 ❤️{{{)ˇ}}}
5048 "
5049 .unindent(),
5050 );
5051
5052 // skip over the auto-closed brackets when typing a closing bracket
5053 cx.update_editor(|view, cx| {
5054 view.move_right(&MoveRight, cx);
5055 view.handle_input("}", cx);
5056 view.handle_input("}", cx);
5057 view.handle_input("}", cx);
5058 });
5059 cx.assert_editor_state(
5060 &"
5061 🏀{{{)}}}}ˇ
5062 ε{{{)}}}}ˇ
5063 ❤️{{{)}}}}ˇ
5064 "
5065 .unindent(),
5066 );
5067
5068 // autoclose multi-character pairs
5069 cx.set_state(
5070 &"
5071 ˇ
5072 ˇ
5073 "
5074 .unindent(),
5075 );
5076 cx.update_editor(|view, cx| {
5077 view.handle_input("/", cx);
5078 view.handle_input("*", cx);
5079 });
5080 cx.assert_editor_state(
5081 &"
5082 /*ˇ */
5083 /*ˇ */
5084 "
5085 .unindent(),
5086 );
5087
5088 // one cursor autocloses a multi-character pair, one cursor
5089 // does not autoclose.
5090 cx.set_state(
5091 &"
5092 /ˇ
5093 ˇ
5094 "
5095 .unindent(),
5096 );
5097 cx.update_editor(|view, cx| view.handle_input("*", cx));
5098 cx.assert_editor_state(
5099 &"
5100 /*ˇ */
5101 *ˇ
5102 "
5103 .unindent(),
5104 );
5105
5106 // Don't autoclose if the next character isn't whitespace and isn't
5107 // listed in the language's "autoclose_before" section.
5108 cx.set_state("ˇa b");
5109 cx.update_editor(|view, cx| view.handle_input("{", cx));
5110 cx.assert_editor_state("{ˇa b");
5111
5112 // Don't autoclose if `close` is false for the bracket pair
5113 cx.set_state("ˇ");
5114 cx.update_editor(|view, cx| view.handle_input("[", cx));
5115 cx.assert_editor_state("[ˇ");
5116
5117 // Surround with brackets if text is selected
5118 cx.set_state("«aˇ» b");
5119 cx.update_editor(|view, cx| view.handle_input("{", cx));
5120 cx.assert_editor_state("{«aˇ»} b");
5121
5122 // Autclose pair where the start and end characters are the same
5123 cx.set_state("aˇ");
5124 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5125 cx.assert_editor_state("a\"ˇ\"");
5126 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5127 cx.assert_editor_state("a\"\"ˇ");
5128
5129 // Don't autoclose pair if autoclose is disabled
5130 cx.set_state("ˇ");
5131 cx.update_editor(|view, cx| view.handle_input("<", cx));
5132 cx.assert_editor_state("<ˇ");
5133
5134 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5135 cx.set_state("«aˇ» b");
5136 cx.update_editor(|view, cx| view.handle_input("<", cx));
5137 cx.assert_editor_state("<«aˇ»> b");
5138}
5139
5140#[gpui::test]
5141async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5142 init_test(cx, |settings| {
5143 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5144 });
5145
5146 let mut cx = EditorTestContext::new(cx).await;
5147
5148 let language = Arc::new(Language::new(
5149 LanguageConfig {
5150 brackets: BracketPairConfig {
5151 pairs: vec![
5152 BracketPair {
5153 start: "{".to_string(),
5154 end: "}".to_string(),
5155 close: true,
5156 surround: true,
5157 newline: true,
5158 },
5159 BracketPair {
5160 start: "(".to_string(),
5161 end: ")".to_string(),
5162 close: true,
5163 surround: true,
5164 newline: true,
5165 },
5166 BracketPair {
5167 start: "[".to_string(),
5168 end: "]".to_string(),
5169 close: false,
5170 surround: false,
5171 newline: true,
5172 },
5173 ],
5174 ..Default::default()
5175 },
5176 autoclose_before: "})]".to_string(),
5177 ..Default::default()
5178 },
5179 Some(tree_sitter_rust::language()),
5180 ));
5181
5182 cx.language_registry().add(language.clone());
5183 cx.update_buffer(|buffer, cx| {
5184 buffer.set_language(Some(language), cx);
5185 });
5186
5187 cx.set_state(
5188 &"
5189 ˇ
5190 ˇ
5191 ˇ
5192 "
5193 .unindent(),
5194 );
5195
5196 // ensure only matching closing brackets are skipped over
5197 cx.update_editor(|view, cx| {
5198 view.handle_input("}", cx);
5199 view.move_left(&MoveLeft, cx);
5200 view.handle_input(")", cx);
5201 view.move_left(&MoveLeft, cx);
5202 });
5203 cx.assert_editor_state(
5204 &"
5205 ˇ)}
5206 ˇ)}
5207 ˇ)}
5208 "
5209 .unindent(),
5210 );
5211
5212 // skip-over closing brackets at multiple cursors
5213 cx.update_editor(|view, cx| {
5214 view.handle_input(")", cx);
5215 view.handle_input("}", cx);
5216 });
5217 cx.assert_editor_state(
5218 &"
5219 )}ˇ
5220 )}ˇ
5221 )}ˇ
5222 "
5223 .unindent(),
5224 );
5225
5226 // ignore non-close brackets
5227 cx.update_editor(|view, cx| {
5228 view.handle_input("]", cx);
5229 view.move_left(&MoveLeft, cx);
5230 view.handle_input("]", cx);
5231 });
5232 cx.assert_editor_state(
5233 &"
5234 )}]ˇ]
5235 )}]ˇ]
5236 )}]ˇ]
5237 "
5238 .unindent(),
5239 );
5240}
5241
5242#[gpui::test]
5243async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5244 init_test(cx, |_| {});
5245
5246 let mut cx = EditorTestContext::new(cx).await;
5247
5248 let html_language = Arc::new(
5249 Language::new(
5250 LanguageConfig {
5251 name: "HTML".into(),
5252 brackets: BracketPairConfig {
5253 pairs: vec![
5254 BracketPair {
5255 start: "<".into(),
5256 end: ">".into(),
5257 close: true,
5258 ..Default::default()
5259 },
5260 BracketPair {
5261 start: "{".into(),
5262 end: "}".into(),
5263 close: true,
5264 ..Default::default()
5265 },
5266 BracketPair {
5267 start: "(".into(),
5268 end: ")".into(),
5269 close: true,
5270 ..Default::default()
5271 },
5272 ],
5273 ..Default::default()
5274 },
5275 autoclose_before: "})]>".into(),
5276 ..Default::default()
5277 },
5278 Some(tree_sitter_html::language()),
5279 )
5280 .with_injection_query(
5281 r#"
5282 (script_element
5283 (raw_text) @content
5284 (#set! "language" "javascript"))
5285 "#,
5286 )
5287 .unwrap(),
5288 );
5289
5290 let javascript_language = Arc::new(Language::new(
5291 LanguageConfig {
5292 name: "JavaScript".into(),
5293 brackets: BracketPairConfig {
5294 pairs: vec![
5295 BracketPair {
5296 start: "/*".into(),
5297 end: " */".into(),
5298 close: true,
5299 ..Default::default()
5300 },
5301 BracketPair {
5302 start: "{".into(),
5303 end: "}".into(),
5304 close: true,
5305 ..Default::default()
5306 },
5307 BracketPair {
5308 start: "(".into(),
5309 end: ")".into(),
5310 close: true,
5311 ..Default::default()
5312 },
5313 ],
5314 ..Default::default()
5315 },
5316 autoclose_before: "})]>".into(),
5317 ..Default::default()
5318 },
5319 Some(tree_sitter_typescript::language_tsx()),
5320 ));
5321
5322 cx.language_registry().add(html_language.clone());
5323 cx.language_registry().add(javascript_language.clone());
5324
5325 cx.update_buffer(|buffer, cx| {
5326 buffer.set_language(Some(html_language), cx);
5327 });
5328
5329 cx.set_state(
5330 &r#"
5331 <body>ˇ
5332 <script>
5333 var x = 1;ˇ
5334 </script>
5335 </body>ˇ
5336 "#
5337 .unindent(),
5338 );
5339
5340 // Precondition: different languages are active at different locations.
5341 cx.update_editor(|editor, cx| {
5342 let snapshot = editor.snapshot(cx);
5343 let cursors = editor.selections.ranges::<usize>(cx);
5344 let languages = cursors
5345 .iter()
5346 .map(|c| snapshot.language_at(c.start).unwrap().name())
5347 .collect::<Vec<_>>();
5348 assert_eq!(
5349 languages,
5350 &["HTML".into(), "JavaScript".into(), "HTML".into()]
5351 );
5352 });
5353
5354 // Angle brackets autoclose in HTML, but not JavaScript.
5355 cx.update_editor(|editor, cx| {
5356 editor.handle_input("<", cx);
5357 editor.handle_input("a", cx);
5358 });
5359 cx.assert_editor_state(
5360 &r#"
5361 <body><aˇ>
5362 <script>
5363 var x = 1;<aˇ
5364 </script>
5365 </body><aˇ>
5366 "#
5367 .unindent(),
5368 );
5369
5370 // Curly braces and parens autoclose in both HTML and JavaScript.
5371 cx.update_editor(|editor, cx| {
5372 editor.handle_input(" b=", cx);
5373 editor.handle_input("{", cx);
5374 editor.handle_input("c", cx);
5375 editor.handle_input("(", cx);
5376 });
5377 cx.assert_editor_state(
5378 &r#"
5379 <body><a b={c(ˇ)}>
5380 <script>
5381 var x = 1;<a b={c(ˇ)}
5382 </script>
5383 </body><a b={c(ˇ)}>
5384 "#
5385 .unindent(),
5386 );
5387
5388 // Brackets that were already autoclosed are skipped.
5389 cx.update_editor(|editor, cx| {
5390 editor.handle_input(")", cx);
5391 editor.handle_input("d", cx);
5392 editor.handle_input("}", cx);
5393 });
5394 cx.assert_editor_state(
5395 &r#"
5396 <body><a b={c()d}ˇ>
5397 <script>
5398 var x = 1;<a b={c()d}ˇ
5399 </script>
5400 </body><a b={c()d}ˇ>
5401 "#
5402 .unindent(),
5403 );
5404 cx.update_editor(|editor, cx| {
5405 editor.handle_input(">", cx);
5406 });
5407 cx.assert_editor_state(
5408 &r#"
5409 <body><a b={c()d}>ˇ
5410 <script>
5411 var x = 1;<a b={c()d}>ˇ
5412 </script>
5413 </body><a b={c()d}>ˇ
5414 "#
5415 .unindent(),
5416 );
5417
5418 // Reset
5419 cx.set_state(
5420 &r#"
5421 <body>ˇ
5422 <script>
5423 var x = 1;ˇ
5424 </script>
5425 </body>ˇ
5426 "#
5427 .unindent(),
5428 );
5429
5430 cx.update_editor(|editor, cx| {
5431 editor.handle_input("<", cx);
5432 });
5433 cx.assert_editor_state(
5434 &r#"
5435 <body><ˇ>
5436 <script>
5437 var x = 1;<ˇ
5438 </script>
5439 </body><ˇ>
5440 "#
5441 .unindent(),
5442 );
5443
5444 // When backspacing, the closing angle brackets are removed.
5445 cx.update_editor(|editor, cx| {
5446 editor.backspace(&Backspace, cx);
5447 });
5448 cx.assert_editor_state(
5449 &r#"
5450 <body>ˇ
5451 <script>
5452 var x = 1;ˇ
5453 </script>
5454 </body>ˇ
5455 "#
5456 .unindent(),
5457 );
5458
5459 // Block comments autoclose in JavaScript, but not HTML.
5460 cx.update_editor(|editor, cx| {
5461 editor.handle_input("/", cx);
5462 editor.handle_input("*", cx);
5463 });
5464 cx.assert_editor_state(
5465 &r#"
5466 <body>/*ˇ
5467 <script>
5468 var x = 1;/*ˇ */
5469 </script>
5470 </body>/*ˇ
5471 "#
5472 .unindent(),
5473 );
5474}
5475
5476#[gpui::test]
5477async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
5478 init_test(cx, |_| {});
5479
5480 let mut cx = EditorTestContext::new(cx).await;
5481
5482 let rust_language = Arc::new(
5483 Language::new(
5484 LanguageConfig {
5485 name: "Rust".into(),
5486 brackets: serde_json::from_value(json!([
5487 { "start": "{", "end": "}", "close": true, "newline": true },
5488 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
5489 ]))
5490 .unwrap(),
5491 autoclose_before: "})]>".into(),
5492 ..Default::default()
5493 },
5494 Some(tree_sitter_rust::language()),
5495 )
5496 .with_override_query("(string_literal) @string")
5497 .unwrap(),
5498 );
5499
5500 cx.language_registry().add(rust_language.clone());
5501 cx.update_buffer(|buffer, cx| {
5502 buffer.set_language(Some(rust_language), cx);
5503 });
5504
5505 cx.set_state(
5506 &r#"
5507 let x = ˇ
5508 "#
5509 .unindent(),
5510 );
5511
5512 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
5513 cx.update_editor(|editor, cx| {
5514 editor.handle_input("\"", cx);
5515 });
5516 cx.assert_editor_state(
5517 &r#"
5518 let x = "ˇ"
5519 "#
5520 .unindent(),
5521 );
5522
5523 // Inserting another quotation mark. The cursor moves across the existing
5524 // automatically-inserted quotation mark.
5525 cx.update_editor(|editor, cx| {
5526 editor.handle_input("\"", cx);
5527 });
5528 cx.assert_editor_state(
5529 &r#"
5530 let x = ""ˇ
5531 "#
5532 .unindent(),
5533 );
5534
5535 // Reset
5536 cx.set_state(
5537 &r#"
5538 let x = ˇ
5539 "#
5540 .unindent(),
5541 );
5542
5543 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
5544 cx.update_editor(|editor, cx| {
5545 editor.handle_input("\"", cx);
5546 editor.handle_input(" ", cx);
5547 editor.move_left(&Default::default(), cx);
5548 editor.handle_input("\\", cx);
5549 editor.handle_input("\"", cx);
5550 });
5551 cx.assert_editor_state(
5552 &r#"
5553 let x = "\"ˇ "
5554 "#
5555 .unindent(),
5556 );
5557
5558 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
5559 // mark. Nothing is inserted.
5560 cx.update_editor(|editor, cx| {
5561 editor.move_right(&Default::default(), cx);
5562 editor.handle_input("\"", cx);
5563 });
5564 cx.assert_editor_state(
5565 &r#"
5566 let x = "\" "ˇ
5567 "#
5568 .unindent(),
5569 );
5570}
5571
5572#[gpui::test]
5573async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
5574 init_test(cx, |_| {});
5575
5576 let language = Arc::new(Language::new(
5577 LanguageConfig {
5578 brackets: BracketPairConfig {
5579 pairs: vec![
5580 BracketPair {
5581 start: "{".to_string(),
5582 end: "}".to_string(),
5583 close: true,
5584 surround: true,
5585 newline: true,
5586 },
5587 BracketPair {
5588 start: "/* ".to_string(),
5589 end: "*/".to_string(),
5590 close: true,
5591 surround: true,
5592 ..Default::default()
5593 },
5594 ],
5595 ..Default::default()
5596 },
5597 ..Default::default()
5598 },
5599 Some(tree_sitter_rust::language()),
5600 ));
5601
5602 let text = r#"
5603 a
5604 b
5605 c
5606 "#
5607 .unindent();
5608
5609 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5610 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5611 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5612 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5613 .await;
5614
5615 _ = view.update(cx, |view, cx| {
5616 view.change_selections(None, cx, |s| {
5617 s.select_display_ranges([
5618 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5619 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5620 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
5621 ])
5622 });
5623
5624 view.handle_input("{", cx);
5625 view.handle_input("{", cx);
5626 view.handle_input("{", cx);
5627 assert_eq!(
5628 view.text(cx),
5629 "
5630 {{{a}}}
5631 {{{b}}}
5632 {{{c}}}
5633 "
5634 .unindent()
5635 );
5636 assert_eq!(
5637 view.selections.display_ranges(cx),
5638 [
5639 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
5640 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
5641 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
5642 ]
5643 );
5644
5645 view.undo(&Undo, cx);
5646 view.undo(&Undo, cx);
5647 view.undo(&Undo, cx);
5648 assert_eq!(
5649 view.text(cx),
5650 "
5651 a
5652 b
5653 c
5654 "
5655 .unindent()
5656 );
5657 assert_eq!(
5658 view.selections.display_ranges(cx),
5659 [
5660 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5661 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5662 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
5663 ]
5664 );
5665
5666 // Ensure inserting the first character of a multi-byte bracket pair
5667 // doesn't surround the selections with the bracket.
5668 view.handle_input("/", cx);
5669 assert_eq!(
5670 view.text(cx),
5671 "
5672 /
5673 /
5674 /
5675 "
5676 .unindent()
5677 );
5678 assert_eq!(
5679 view.selections.display_ranges(cx),
5680 [
5681 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
5682 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
5683 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
5684 ]
5685 );
5686
5687 view.undo(&Undo, cx);
5688 assert_eq!(
5689 view.text(cx),
5690 "
5691 a
5692 b
5693 c
5694 "
5695 .unindent()
5696 );
5697 assert_eq!(
5698 view.selections.display_ranges(cx),
5699 [
5700 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5701 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5702 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
5703 ]
5704 );
5705
5706 // Ensure inserting the last character of a multi-byte bracket pair
5707 // doesn't surround the selections with the bracket.
5708 view.handle_input("*", cx);
5709 assert_eq!(
5710 view.text(cx),
5711 "
5712 *
5713 *
5714 *
5715 "
5716 .unindent()
5717 );
5718 assert_eq!(
5719 view.selections.display_ranges(cx),
5720 [
5721 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
5722 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
5723 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
5724 ]
5725 );
5726 });
5727}
5728
5729#[gpui::test]
5730async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
5731 init_test(cx, |_| {});
5732
5733 let language = Arc::new(Language::new(
5734 LanguageConfig {
5735 brackets: BracketPairConfig {
5736 pairs: vec![BracketPair {
5737 start: "{".to_string(),
5738 end: "}".to_string(),
5739 close: true,
5740 surround: true,
5741 newline: true,
5742 }],
5743 ..Default::default()
5744 },
5745 autoclose_before: "}".to_string(),
5746 ..Default::default()
5747 },
5748 Some(tree_sitter_rust::language()),
5749 ));
5750
5751 let text = r#"
5752 a
5753 b
5754 c
5755 "#
5756 .unindent();
5757
5758 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5759 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5760 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5761 editor
5762 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5763 .await;
5764
5765 _ = editor.update(cx, |editor, cx| {
5766 editor.change_selections(None, cx, |s| {
5767 s.select_ranges([
5768 Point::new(0, 1)..Point::new(0, 1),
5769 Point::new(1, 1)..Point::new(1, 1),
5770 Point::new(2, 1)..Point::new(2, 1),
5771 ])
5772 });
5773
5774 editor.handle_input("{", cx);
5775 editor.handle_input("{", cx);
5776 editor.handle_input("_", cx);
5777 assert_eq!(
5778 editor.text(cx),
5779 "
5780 a{{_}}
5781 b{{_}}
5782 c{{_}}
5783 "
5784 .unindent()
5785 );
5786 assert_eq!(
5787 editor.selections.ranges::<Point>(cx),
5788 [
5789 Point::new(0, 4)..Point::new(0, 4),
5790 Point::new(1, 4)..Point::new(1, 4),
5791 Point::new(2, 4)..Point::new(2, 4)
5792 ]
5793 );
5794
5795 editor.backspace(&Default::default(), cx);
5796 editor.backspace(&Default::default(), cx);
5797 assert_eq!(
5798 editor.text(cx),
5799 "
5800 a{}
5801 b{}
5802 c{}
5803 "
5804 .unindent()
5805 );
5806 assert_eq!(
5807 editor.selections.ranges::<Point>(cx),
5808 [
5809 Point::new(0, 2)..Point::new(0, 2),
5810 Point::new(1, 2)..Point::new(1, 2),
5811 Point::new(2, 2)..Point::new(2, 2)
5812 ]
5813 );
5814
5815 editor.delete_to_previous_word_start(&Default::default(), cx);
5816 assert_eq!(
5817 editor.text(cx),
5818 "
5819 a
5820 b
5821 c
5822 "
5823 .unindent()
5824 );
5825 assert_eq!(
5826 editor.selections.ranges::<Point>(cx),
5827 [
5828 Point::new(0, 1)..Point::new(0, 1),
5829 Point::new(1, 1)..Point::new(1, 1),
5830 Point::new(2, 1)..Point::new(2, 1)
5831 ]
5832 );
5833 });
5834}
5835
5836#[gpui::test]
5837async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
5838 init_test(cx, |settings| {
5839 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5840 });
5841
5842 let mut cx = EditorTestContext::new(cx).await;
5843
5844 let language = Arc::new(Language::new(
5845 LanguageConfig {
5846 brackets: BracketPairConfig {
5847 pairs: vec![
5848 BracketPair {
5849 start: "{".to_string(),
5850 end: "}".to_string(),
5851 close: true,
5852 surround: true,
5853 newline: true,
5854 },
5855 BracketPair {
5856 start: "(".to_string(),
5857 end: ")".to_string(),
5858 close: true,
5859 surround: true,
5860 newline: true,
5861 },
5862 BracketPair {
5863 start: "[".to_string(),
5864 end: "]".to_string(),
5865 close: false,
5866 surround: true,
5867 newline: true,
5868 },
5869 ],
5870 ..Default::default()
5871 },
5872 autoclose_before: "})]".to_string(),
5873 ..Default::default()
5874 },
5875 Some(tree_sitter_rust::language()),
5876 ));
5877
5878 cx.language_registry().add(language.clone());
5879 cx.update_buffer(|buffer, cx| {
5880 buffer.set_language(Some(language), cx);
5881 });
5882
5883 cx.set_state(
5884 &"
5885 {(ˇ)}
5886 [[ˇ]]
5887 {(ˇ)}
5888 "
5889 .unindent(),
5890 );
5891
5892 cx.update_editor(|view, cx| {
5893 view.backspace(&Default::default(), cx);
5894 view.backspace(&Default::default(), cx);
5895 });
5896
5897 cx.assert_editor_state(
5898 &"
5899 ˇ
5900 ˇ]]
5901 ˇ
5902 "
5903 .unindent(),
5904 );
5905
5906 cx.update_editor(|view, cx| {
5907 view.handle_input("{", cx);
5908 view.handle_input("{", cx);
5909 view.move_right(&MoveRight, cx);
5910 view.move_right(&MoveRight, cx);
5911 view.move_left(&MoveLeft, cx);
5912 view.move_left(&MoveLeft, cx);
5913 view.backspace(&Default::default(), cx);
5914 });
5915
5916 cx.assert_editor_state(
5917 &"
5918 {ˇ}
5919 {ˇ}]]
5920 {ˇ}
5921 "
5922 .unindent(),
5923 );
5924
5925 cx.update_editor(|view, cx| {
5926 view.backspace(&Default::default(), cx);
5927 });
5928
5929 cx.assert_editor_state(
5930 &"
5931 ˇ
5932 ˇ]]
5933 ˇ
5934 "
5935 .unindent(),
5936 );
5937}
5938
5939#[gpui::test]
5940async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
5941 init_test(cx, |_| {});
5942
5943 let language = Arc::new(Language::new(
5944 LanguageConfig::default(),
5945 Some(tree_sitter_rust::language()),
5946 ));
5947
5948 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
5949 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5950 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5951 editor
5952 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5953 .await;
5954
5955 _ = editor.update(cx, |editor, cx| {
5956 editor.set_auto_replace_emoji_shortcode(true);
5957
5958 editor.handle_input("Hello ", cx);
5959 editor.handle_input(":wave", cx);
5960 assert_eq!(editor.text(cx), "Hello :wave".unindent());
5961
5962 editor.handle_input(":", cx);
5963 assert_eq!(editor.text(cx), "Hello 👋".unindent());
5964
5965 editor.handle_input(" :smile", cx);
5966 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
5967
5968 editor.handle_input(":", cx);
5969 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
5970
5971 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
5972 editor.handle_input(":wave", cx);
5973 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
5974
5975 editor.handle_input(":", cx);
5976 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
5977
5978 editor.handle_input(":1", cx);
5979 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
5980
5981 editor.handle_input(":", cx);
5982 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
5983
5984 // Ensure shortcode does not get replaced when it is part of a word
5985 editor.handle_input(" Test:wave", cx);
5986 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
5987
5988 editor.handle_input(":", cx);
5989 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
5990
5991 editor.set_auto_replace_emoji_shortcode(false);
5992
5993 // Ensure shortcode does not get replaced when auto replace is off
5994 editor.handle_input(" :wave", cx);
5995 assert_eq!(
5996 editor.text(cx),
5997 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
5998 );
5999
6000 editor.handle_input(":", cx);
6001 assert_eq!(
6002 editor.text(cx),
6003 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6004 );
6005 });
6006}
6007
6008#[gpui::test]
6009async fn test_snippets(cx: &mut gpui::TestAppContext) {
6010 init_test(cx, |_| {});
6011
6012 let (text, insertion_ranges) = marked_text_ranges(
6013 indoc! {"
6014 a.ˇ b
6015 a.ˇ b
6016 a.ˇ b
6017 "},
6018 false,
6019 );
6020
6021 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6022 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6023
6024 _ = editor.update(cx, |editor, cx| {
6025 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6026
6027 editor
6028 .insert_snippet(&insertion_ranges, snippet, cx)
6029 .unwrap();
6030
6031 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6032 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6033 assert_eq!(editor.text(cx), expected_text);
6034 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6035 }
6036
6037 assert(
6038 editor,
6039 cx,
6040 indoc! {"
6041 a.f(«one», two, «three») b
6042 a.f(«one», two, «three») b
6043 a.f(«one», two, «three») b
6044 "},
6045 );
6046
6047 // Can't move earlier than the first tab stop
6048 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6049 assert(
6050 editor,
6051 cx,
6052 indoc! {"
6053 a.f(«one», two, «three») b
6054 a.f(«one», two, «three») b
6055 a.f(«one», two, «three») b
6056 "},
6057 );
6058
6059 assert!(editor.move_to_next_snippet_tabstop(cx));
6060 assert(
6061 editor,
6062 cx,
6063 indoc! {"
6064 a.f(one, «two», three) b
6065 a.f(one, «two», three) b
6066 a.f(one, «two», three) b
6067 "},
6068 );
6069
6070 editor.move_to_prev_snippet_tabstop(cx);
6071 assert(
6072 editor,
6073 cx,
6074 indoc! {"
6075 a.f(«one», two, «three») b
6076 a.f(«one», two, «three») b
6077 a.f(«one», two, «three») b
6078 "},
6079 );
6080
6081 assert!(editor.move_to_next_snippet_tabstop(cx));
6082 assert(
6083 editor,
6084 cx,
6085 indoc! {"
6086 a.f(one, «two», three) b
6087 a.f(one, «two», three) b
6088 a.f(one, «two», three) b
6089 "},
6090 );
6091 assert!(editor.move_to_next_snippet_tabstop(cx));
6092 assert(
6093 editor,
6094 cx,
6095 indoc! {"
6096 a.f(one, two, three)ˇ b
6097 a.f(one, two, three)ˇ b
6098 a.f(one, two, three)ˇ b
6099 "},
6100 );
6101
6102 // As soon as the last tab stop is reached, snippet state is gone
6103 editor.move_to_prev_snippet_tabstop(cx);
6104 assert(
6105 editor,
6106 cx,
6107 indoc! {"
6108 a.f(one, two, three)ˇ b
6109 a.f(one, two, three)ˇ b
6110 a.f(one, two, three)ˇ b
6111 "},
6112 );
6113 });
6114}
6115
6116#[gpui::test]
6117async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6118 init_test(cx, |_| {});
6119
6120 let fs = FakeFs::new(cx.executor());
6121 fs.insert_file("/file.rs", Default::default()).await;
6122
6123 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6124
6125 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6126 language_registry.add(rust_lang());
6127 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6128 "Rust",
6129 FakeLspAdapter {
6130 capabilities: lsp::ServerCapabilities {
6131 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6132 ..Default::default()
6133 },
6134 ..Default::default()
6135 },
6136 );
6137
6138 let buffer = project
6139 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6140 .await
6141 .unwrap();
6142
6143 cx.executor().start_waiting();
6144 let fake_server = fake_servers.next().await.unwrap();
6145
6146 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6147 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6148 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6149 assert!(cx.read(|cx| editor.is_dirty(cx)));
6150
6151 let save = editor
6152 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6153 .unwrap();
6154 fake_server
6155 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6156 assert_eq!(
6157 params.text_document.uri,
6158 lsp::Url::from_file_path("/file.rs").unwrap()
6159 );
6160 assert_eq!(params.options.tab_size, 4);
6161 Ok(Some(vec![lsp::TextEdit::new(
6162 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6163 ", ".to_string(),
6164 )]))
6165 })
6166 .next()
6167 .await;
6168 cx.executor().start_waiting();
6169 save.await;
6170
6171 assert_eq!(
6172 editor.update(cx, |editor, cx| editor.text(cx)),
6173 "one, two\nthree\n"
6174 );
6175 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6176
6177 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6178 assert!(cx.read(|cx| editor.is_dirty(cx)));
6179
6180 // Ensure we can still save even if formatting hangs.
6181 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6182 assert_eq!(
6183 params.text_document.uri,
6184 lsp::Url::from_file_path("/file.rs").unwrap()
6185 );
6186 futures::future::pending::<()>().await;
6187 unreachable!()
6188 });
6189 let save = editor
6190 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6191 .unwrap();
6192 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6193 cx.executor().start_waiting();
6194 save.await;
6195 assert_eq!(
6196 editor.update(cx, |editor, cx| editor.text(cx)),
6197 "one\ntwo\nthree\n"
6198 );
6199 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6200
6201 // For non-dirty buffer, no formatting request should be sent
6202 let save = editor
6203 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6204 .unwrap();
6205 let _pending_format_request = fake_server
6206 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6207 panic!("Should not be invoked on non-dirty buffer");
6208 })
6209 .next();
6210 cx.executor().start_waiting();
6211 save.await;
6212
6213 // Set rust language override and assert overridden tabsize is sent to language server
6214 update_test_language_settings(cx, |settings| {
6215 settings.languages.insert(
6216 "Rust".into(),
6217 LanguageSettingsContent {
6218 tab_size: NonZeroU32::new(8),
6219 ..Default::default()
6220 },
6221 );
6222 });
6223
6224 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6225 assert!(cx.read(|cx| editor.is_dirty(cx)));
6226 let save = editor
6227 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6228 .unwrap();
6229 fake_server
6230 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6231 assert_eq!(
6232 params.text_document.uri,
6233 lsp::Url::from_file_path("/file.rs").unwrap()
6234 );
6235 assert_eq!(params.options.tab_size, 8);
6236 Ok(Some(vec![]))
6237 })
6238 .next()
6239 .await;
6240 cx.executor().start_waiting();
6241 save.await;
6242}
6243
6244#[gpui::test]
6245async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6246 init_test(cx, |_| {});
6247
6248 let cols = 4;
6249 let rows = 10;
6250 let sample_text_1 = sample_text(rows, cols, 'a');
6251 assert_eq!(
6252 sample_text_1,
6253 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6254 );
6255 let sample_text_2 = sample_text(rows, cols, 'l');
6256 assert_eq!(
6257 sample_text_2,
6258 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6259 );
6260 let sample_text_3 = sample_text(rows, cols, 'v');
6261 assert_eq!(
6262 sample_text_3,
6263 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6264 );
6265
6266 let fs = FakeFs::new(cx.executor());
6267 fs.insert_tree(
6268 "/a",
6269 json!({
6270 "main.rs": sample_text_1,
6271 "other.rs": sample_text_2,
6272 "lib.rs": sample_text_3,
6273 }),
6274 )
6275 .await;
6276
6277 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6278 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6279 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6280
6281 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6282 language_registry.add(rust_lang());
6283 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6284 "Rust",
6285 FakeLspAdapter {
6286 capabilities: lsp::ServerCapabilities {
6287 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6288 ..Default::default()
6289 },
6290 ..Default::default()
6291 },
6292 );
6293
6294 let worktree = project.update(cx, |project, cx| {
6295 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
6296 assert_eq!(worktrees.len(), 1);
6297 worktrees.pop().unwrap()
6298 });
6299 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6300
6301 let buffer_1 = project
6302 .update(cx, |project, cx| {
6303 project.open_buffer((worktree_id, "main.rs"), cx)
6304 })
6305 .await
6306 .unwrap();
6307 let buffer_2 = project
6308 .update(cx, |project, cx| {
6309 project.open_buffer((worktree_id, "other.rs"), cx)
6310 })
6311 .await
6312 .unwrap();
6313 let buffer_3 = project
6314 .update(cx, |project, cx| {
6315 project.open_buffer((worktree_id, "lib.rs"), cx)
6316 })
6317 .await
6318 .unwrap();
6319
6320 let multi_buffer = cx.new_model(|cx| {
6321 let mut multi_buffer = MultiBuffer::new(0, ReadWrite);
6322 multi_buffer.push_excerpts(
6323 buffer_1.clone(),
6324 [
6325 ExcerptRange {
6326 context: Point::new(0, 0)..Point::new(3, 0),
6327 primary: None,
6328 },
6329 ExcerptRange {
6330 context: Point::new(5, 0)..Point::new(7, 0),
6331 primary: None,
6332 },
6333 ExcerptRange {
6334 context: Point::new(9, 0)..Point::new(10, 4),
6335 primary: None,
6336 },
6337 ],
6338 cx,
6339 );
6340 multi_buffer.push_excerpts(
6341 buffer_2.clone(),
6342 [
6343 ExcerptRange {
6344 context: Point::new(0, 0)..Point::new(3, 0),
6345 primary: None,
6346 },
6347 ExcerptRange {
6348 context: Point::new(5, 0)..Point::new(7, 0),
6349 primary: None,
6350 },
6351 ExcerptRange {
6352 context: Point::new(9, 0)..Point::new(10, 4),
6353 primary: None,
6354 },
6355 ],
6356 cx,
6357 );
6358 multi_buffer.push_excerpts(
6359 buffer_3.clone(),
6360 [
6361 ExcerptRange {
6362 context: Point::new(0, 0)..Point::new(3, 0),
6363 primary: None,
6364 },
6365 ExcerptRange {
6366 context: Point::new(5, 0)..Point::new(7, 0),
6367 primary: None,
6368 },
6369 ExcerptRange {
6370 context: Point::new(9, 0)..Point::new(10, 4),
6371 primary: None,
6372 },
6373 ],
6374 cx,
6375 );
6376 multi_buffer
6377 });
6378 let multi_buffer_editor = cx.new_view(|cx| {
6379 Editor::new(
6380 EditorMode::Full,
6381 multi_buffer,
6382 Some(project.clone()),
6383 true,
6384 cx,
6385 )
6386 });
6387
6388 multi_buffer_editor.update(cx, |editor, cx| {
6389 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
6390 editor.insert("|one|two|three|", cx);
6391 });
6392 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6393 multi_buffer_editor.update(cx, |editor, cx| {
6394 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
6395 s.select_ranges(Some(60..70))
6396 });
6397 editor.insert("|four|five|six|", cx);
6398 });
6399 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6400
6401 // First two buffers should be edited, but not the third one.
6402 assert_eq!(
6403 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6404 "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}",
6405 );
6406 buffer_1.update(cx, |buffer, _| {
6407 assert!(buffer.is_dirty());
6408 assert_eq!(
6409 buffer.text(),
6410 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
6411 )
6412 });
6413 buffer_2.update(cx, |buffer, _| {
6414 assert!(buffer.is_dirty());
6415 assert_eq!(
6416 buffer.text(),
6417 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
6418 )
6419 });
6420 buffer_3.update(cx, |buffer, _| {
6421 assert!(!buffer.is_dirty());
6422 assert_eq!(buffer.text(), sample_text_3,)
6423 });
6424
6425 cx.executor().start_waiting();
6426 let save = multi_buffer_editor
6427 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6428 .unwrap();
6429
6430 let fake_server = fake_servers.next().await.unwrap();
6431 fake_server
6432 .server
6433 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6434 Ok(Some(vec![lsp::TextEdit::new(
6435 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6436 format!("[{} formatted]", params.text_document.uri),
6437 )]))
6438 })
6439 .detach();
6440 save.await;
6441
6442 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
6443 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
6444 assert_eq!(
6445 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6446 "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}",
6447 );
6448 buffer_1.update(cx, |buffer, _| {
6449 assert!(!buffer.is_dirty());
6450 assert_eq!(
6451 buffer.text(),
6452 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
6453 )
6454 });
6455 buffer_2.update(cx, |buffer, _| {
6456 assert!(!buffer.is_dirty());
6457 assert_eq!(
6458 buffer.text(),
6459 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
6460 )
6461 });
6462 buffer_3.update(cx, |buffer, _| {
6463 assert!(!buffer.is_dirty());
6464 assert_eq!(buffer.text(), sample_text_3,)
6465 });
6466}
6467
6468#[gpui::test]
6469async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
6470 init_test(cx, |_| {});
6471
6472 let fs = FakeFs::new(cx.executor());
6473 fs.insert_file("/file.rs", Default::default()).await;
6474
6475 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6476
6477 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6478 language_registry.add(rust_lang());
6479 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6480 "Rust",
6481 FakeLspAdapter {
6482 capabilities: lsp::ServerCapabilities {
6483 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
6484 ..Default::default()
6485 },
6486 ..Default::default()
6487 },
6488 );
6489
6490 let buffer = project
6491 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6492 .await
6493 .unwrap();
6494
6495 cx.executor().start_waiting();
6496 let fake_server = fake_servers.next().await.unwrap();
6497
6498 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6499 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6500 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6501 assert!(cx.read(|cx| editor.is_dirty(cx)));
6502
6503 let save = editor
6504 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6505 .unwrap();
6506 fake_server
6507 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6508 assert_eq!(
6509 params.text_document.uri,
6510 lsp::Url::from_file_path("/file.rs").unwrap()
6511 );
6512 assert_eq!(params.options.tab_size, 4);
6513 Ok(Some(vec![lsp::TextEdit::new(
6514 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6515 ", ".to_string(),
6516 )]))
6517 })
6518 .next()
6519 .await;
6520 cx.executor().start_waiting();
6521 save.await;
6522 assert_eq!(
6523 editor.update(cx, |editor, cx| editor.text(cx)),
6524 "one, two\nthree\n"
6525 );
6526 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6527
6528 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6529 assert!(cx.read(|cx| editor.is_dirty(cx)));
6530
6531 // Ensure we can still save even if formatting hangs.
6532 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
6533 move |params, _| async move {
6534 assert_eq!(
6535 params.text_document.uri,
6536 lsp::Url::from_file_path("/file.rs").unwrap()
6537 );
6538 futures::future::pending::<()>().await;
6539 unreachable!()
6540 },
6541 );
6542 let save = editor
6543 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6544 .unwrap();
6545 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6546 cx.executor().start_waiting();
6547 save.await;
6548 assert_eq!(
6549 editor.update(cx, |editor, cx| editor.text(cx)),
6550 "one\ntwo\nthree\n"
6551 );
6552 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6553
6554 // For non-dirty buffer, no formatting request should be sent
6555 let save = editor
6556 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6557 .unwrap();
6558 let _pending_format_request = fake_server
6559 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6560 panic!("Should not be invoked on non-dirty buffer");
6561 })
6562 .next();
6563 cx.executor().start_waiting();
6564 save.await;
6565
6566 // Set Rust language override and assert overridden tabsize is sent to language server
6567 update_test_language_settings(cx, |settings| {
6568 settings.languages.insert(
6569 "Rust".into(),
6570 LanguageSettingsContent {
6571 tab_size: NonZeroU32::new(8),
6572 ..Default::default()
6573 },
6574 );
6575 });
6576
6577 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6578 assert!(cx.read(|cx| editor.is_dirty(cx)));
6579 let save = editor
6580 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6581 .unwrap();
6582 fake_server
6583 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6584 assert_eq!(
6585 params.text_document.uri,
6586 lsp::Url::from_file_path("/file.rs").unwrap()
6587 );
6588 assert_eq!(params.options.tab_size, 8);
6589 Ok(Some(vec![]))
6590 })
6591 .next()
6592 .await;
6593 cx.executor().start_waiting();
6594 save.await;
6595}
6596
6597#[gpui::test]
6598async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
6599 init_test(cx, |settings| {
6600 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
6601 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
6602 ))
6603 });
6604
6605 let fs = FakeFs::new(cx.executor());
6606 fs.insert_file("/file.rs", Default::default()).await;
6607
6608 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6609
6610 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6611 language_registry.add(Arc::new(Language::new(
6612 LanguageConfig {
6613 name: "Rust".into(),
6614 matcher: LanguageMatcher {
6615 path_suffixes: vec!["rs".to_string()],
6616 ..Default::default()
6617 },
6618 ..LanguageConfig::default()
6619 },
6620 Some(tree_sitter_rust::language()),
6621 )));
6622 update_test_language_settings(cx, |settings| {
6623 // Enable Prettier formatting for the same buffer, and ensure
6624 // LSP is called instead of Prettier.
6625 settings.defaults.prettier = Some(PrettierSettings {
6626 allowed: true,
6627 ..PrettierSettings::default()
6628 });
6629 });
6630 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6631 "Rust",
6632 FakeLspAdapter {
6633 capabilities: lsp::ServerCapabilities {
6634 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6635 ..Default::default()
6636 },
6637 ..Default::default()
6638 },
6639 );
6640
6641 let buffer = project
6642 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6643 .await
6644 .unwrap();
6645
6646 cx.executor().start_waiting();
6647 let fake_server = fake_servers.next().await.unwrap();
6648
6649 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6650 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6651 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6652
6653 let format = editor
6654 .update(cx, |editor, cx| {
6655 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
6656 })
6657 .unwrap();
6658 fake_server
6659 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6660 assert_eq!(
6661 params.text_document.uri,
6662 lsp::Url::from_file_path("/file.rs").unwrap()
6663 );
6664 assert_eq!(params.options.tab_size, 4);
6665 Ok(Some(vec![lsp::TextEdit::new(
6666 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6667 ", ".to_string(),
6668 )]))
6669 })
6670 .next()
6671 .await;
6672 cx.executor().start_waiting();
6673 format.await;
6674 assert_eq!(
6675 editor.update(cx, |editor, cx| editor.text(cx)),
6676 "one, two\nthree\n"
6677 );
6678
6679 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6680 // Ensure we don't lock if formatting hangs.
6681 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6682 assert_eq!(
6683 params.text_document.uri,
6684 lsp::Url::from_file_path("/file.rs").unwrap()
6685 );
6686 futures::future::pending::<()>().await;
6687 unreachable!()
6688 });
6689 let format = editor
6690 .update(cx, |editor, cx| {
6691 editor.perform_format(project, FormatTrigger::Manual, cx)
6692 })
6693 .unwrap();
6694 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6695 cx.executor().start_waiting();
6696 format.await;
6697 assert_eq!(
6698 editor.update(cx, |editor, cx| editor.text(cx)),
6699 "one\ntwo\nthree\n"
6700 );
6701}
6702
6703#[gpui::test]
6704async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
6705 init_test(cx, |_| {});
6706
6707 let mut cx = EditorLspTestContext::new_rust(
6708 lsp::ServerCapabilities {
6709 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6710 ..Default::default()
6711 },
6712 cx,
6713 )
6714 .await;
6715
6716 cx.set_state(indoc! {"
6717 one.twoˇ
6718 "});
6719
6720 // The format request takes a long time. When it completes, it inserts
6721 // a newline and an indent before the `.`
6722 cx.lsp
6723 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
6724 let executor = cx.background_executor().clone();
6725 async move {
6726 executor.timer(Duration::from_millis(100)).await;
6727 Ok(Some(vec![lsp::TextEdit {
6728 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
6729 new_text: "\n ".into(),
6730 }]))
6731 }
6732 });
6733
6734 // Submit a format request.
6735 let format_1 = cx
6736 .update_editor(|editor, cx| editor.format(&Format, cx))
6737 .unwrap();
6738 cx.executor().run_until_parked();
6739
6740 // Submit a second format request.
6741 let format_2 = cx
6742 .update_editor(|editor, cx| editor.format(&Format, cx))
6743 .unwrap();
6744 cx.executor().run_until_parked();
6745
6746 // Wait for both format requests to complete
6747 cx.executor().advance_clock(Duration::from_millis(200));
6748 cx.executor().start_waiting();
6749 format_1.await.unwrap();
6750 cx.executor().start_waiting();
6751 format_2.await.unwrap();
6752
6753 // The formatting edits only happens once.
6754 cx.assert_editor_state(indoc! {"
6755 one
6756 .twoˇ
6757 "});
6758}
6759
6760#[gpui::test]
6761async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
6762 init_test(cx, |settings| {
6763 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
6764 });
6765
6766 let mut cx = EditorLspTestContext::new_rust(
6767 lsp::ServerCapabilities {
6768 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6769 ..Default::default()
6770 },
6771 cx,
6772 )
6773 .await;
6774
6775 // Set up a buffer white some trailing whitespace and no trailing newline.
6776 cx.set_state(
6777 &[
6778 "one ", //
6779 "twoˇ", //
6780 "three ", //
6781 "four", //
6782 ]
6783 .join("\n"),
6784 );
6785
6786 // Submit a format request.
6787 let format = cx
6788 .update_editor(|editor, cx| editor.format(&Format, cx))
6789 .unwrap();
6790
6791 // Record which buffer changes have been sent to the language server
6792 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
6793 cx.lsp
6794 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
6795 let buffer_changes = buffer_changes.clone();
6796 move |params, _| {
6797 buffer_changes.lock().extend(
6798 params
6799 .content_changes
6800 .into_iter()
6801 .map(|e| (e.range.unwrap(), e.text)),
6802 );
6803 }
6804 });
6805
6806 // Handle formatting requests to the language server.
6807 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
6808 let buffer_changes = buffer_changes.clone();
6809 move |_, _| {
6810 // When formatting is requested, trailing whitespace has already been stripped,
6811 // and the trailing newline has already been added.
6812 assert_eq!(
6813 &buffer_changes.lock()[1..],
6814 &[
6815 (
6816 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
6817 "".into()
6818 ),
6819 (
6820 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
6821 "".into()
6822 ),
6823 (
6824 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
6825 "\n".into()
6826 ),
6827 ]
6828 );
6829
6830 // Insert blank lines between each line of the buffer.
6831 async move {
6832 Ok(Some(vec![
6833 lsp::TextEdit {
6834 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
6835 new_text: "\n".into(),
6836 },
6837 lsp::TextEdit {
6838 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
6839 new_text: "\n".into(),
6840 },
6841 ]))
6842 }
6843 }
6844 });
6845
6846 // After formatting the buffer, the trailing whitespace is stripped,
6847 // a newline is appended, and the edits provided by the language server
6848 // have been applied.
6849 format.await.unwrap();
6850 cx.assert_editor_state(
6851 &[
6852 "one", //
6853 "", //
6854 "twoˇ", //
6855 "", //
6856 "three", //
6857 "four", //
6858 "", //
6859 ]
6860 .join("\n"),
6861 );
6862
6863 // Undoing the formatting undoes the trailing whitespace removal, the
6864 // trailing newline, and the LSP edits.
6865 cx.update_buffer(|buffer, cx| buffer.undo(cx));
6866 cx.assert_editor_state(
6867 &[
6868 "one ", //
6869 "twoˇ", //
6870 "three ", //
6871 "four", //
6872 ]
6873 .join("\n"),
6874 );
6875}
6876
6877#[gpui::test]
6878async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
6879 cx: &mut gpui::TestAppContext,
6880) {
6881 init_test(cx, |_| {});
6882
6883 cx.update(|cx| {
6884 cx.update_global::<SettingsStore, _>(|settings, cx| {
6885 settings.update_user_settings::<EditorSettings>(cx, |settings| {
6886 settings.auto_signature_help = Some(true);
6887 });
6888 });
6889 });
6890
6891 let mut cx = EditorLspTestContext::new_rust(
6892 lsp::ServerCapabilities {
6893 signature_help_provider: Some(lsp::SignatureHelpOptions {
6894 ..Default::default()
6895 }),
6896 ..Default::default()
6897 },
6898 cx,
6899 )
6900 .await;
6901
6902 let language = Language::new(
6903 LanguageConfig {
6904 name: "Rust".into(),
6905 brackets: BracketPairConfig {
6906 pairs: vec![
6907 BracketPair {
6908 start: "{".to_string(),
6909 end: "}".to_string(),
6910 close: true,
6911 surround: true,
6912 newline: true,
6913 },
6914 BracketPair {
6915 start: "(".to_string(),
6916 end: ")".to_string(),
6917 close: true,
6918 surround: true,
6919 newline: true,
6920 },
6921 BracketPair {
6922 start: "/*".to_string(),
6923 end: " */".to_string(),
6924 close: true,
6925 surround: true,
6926 newline: true,
6927 },
6928 BracketPair {
6929 start: "[".to_string(),
6930 end: "]".to_string(),
6931 close: false,
6932 surround: false,
6933 newline: true,
6934 },
6935 BracketPair {
6936 start: "\"".to_string(),
6937 end: "\"".to_string(),
6938 close: true,
6939 surround: true,
6940 newline: false,
6941 },
6942 BracketPair {
6943 start: "<".to_string(),
6944 end: ">".to_string(),
6945 close: false,
6946 surround: true,
6947 newline: true,
6948 },
6949 ],
6950 ..Default::default()
6951 },
6952 autoclose_before: "})]".to_string(),
6953 ..Default::default()
6954 },
6955 Some(tree_sitter_rust::language()),
6956 );
6957 let language = Arc::new(language);
6958
6959 cx.language_registry().add(language.clone());
6960 cx.update_buffer(|buffer, cx| {
6961 buffer.set_language(Some(language), cx);
6962 });
6963
6964 cx.set_state(
6965 &r#"
6966 fn main() {
6967 sampleˇ
6968 }
6969 "#
6970 .unindent(),
6971 );
6972
6973 cx.update_editor(|view, cx| {
6974 view.handle_input("(", cx);
6975 });
6976 cx.assert_editor_state(
6977 &"
6978 fn main() {
6979 sample(ˇ)
6980 }
6981 "
6982 .unindent(),
6983 );
6984
6985 let mocked_response = lsp::SignatureHelp {
6986 signatures: vec![lsp::SignatureInformation {
6987 label: "fn sample(param1: u8, param2: u8)".to_string(),
6988 documentation: None,
6989 parameters: Some(vec![
6990 lsp::ParameterInformation {
6991 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
6992 documentation: None,
6993 },
6994 lsp::ParameterInformation {
6995 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
6996 documentation: None,
6997 },
6998 ]),
6999 active_parameter: None,
7000 }],
7001 active_signature: Some(0),
7002 active_parameter: Some(0),
7003 };
7004 handle_signature_help_request(&mut cx, mocked_response).await;
7005
7006 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7007 .await;
7008
7009 cx.editor(|editor, _| {
7010 let signature_help_state = editor.signature_help_state.popover().cloned();
7011 assert!(signature_help_state.is_some());
7012 let ParsedMarkdown {
7013 text, highlights, ..
7014 } = signature_help_state.unwrap().parsed_content;
7015 assert_eq!(text, "param1: u8, param2: u8");
7016 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7017 });
7018}
7019
7020#[gpui::test]
7021async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7022 init_test(cx, |_| {});
7023
7024 cx.update(|cx| {
7025 cx.update_global::<SettingsStore, _>(|settings, cx| {
7026 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7027 settings.auto_signature_help = Some(false);
7028 settings.show_signature_help_after_edits = Some(false);
7029 });
7030 });
7031 });
7032
7033 let mut cx = EditorLspTestContext::new_rust(
7034 lsp::ServerCapabilities {
7035 signature_help_provider: Some(lsp::SignatureHelpOptions {
7036 ..Default::default()
7037 }),
7038 ..Default::default()
7039 },
7040 cx,
7041 )
7042 .await;
7043
7044 let language = Language::new(
7045 LanguageConfig {
7046 name: "Rust".into(),
7047 brackets: BracketPairConfig {
7048 pairs: vec![
7049 BracketPair {
7050 start: "{".to_string(),
7051 end: "}".to_string(),
7052 close: true,
7053 surround: true,
7054 newline: true,
7055 },
7056 BracketPair {
7057 start: "(".to_string(),
7058 end: ")".to_string(),
7059 close: true,
7060 surround: true,
7061 newline: true,
7062 },
7063 BracketPair {
7064 start: "/*".to_string(),
7065 end: " */".to_string(),
7066 close: true,
7067 surround: true,
7068 newline: true,
7069 },
7070 BracketPair {
7071 start: "[".to_string(),
7072 end: "]".to_string(),
7073 close: false,
7074 surround: false,
7075 newline: true,
7076 },
7077 BracketPair {
7078 start: "\"".to_string(),
7079 end: "\"".to_string(),
7080 close: true,
7081 surround: true,
7082 newline: false,
7083 },
7084 BracketPair {
7085 start: "<".to_string(),
7086 end: ">".to_string(),
7087 close: false,
7088 surround: true,
7089 newline: true,
7090 },
7091 ],
7092 ..Default::default()
7093 },
7094 autoclose_before: "})]".to_string(),
7095 ..Default::default()
7096 },
7097 Some(tree_sitter_rust::language()),
7098 );
7099 let language = Arc::new(language);
7100
7101 cx.language_registry().add(language.clone());
7102 cx.update_buffer(|buffer, cx| {
7103 buffer.set_language(Some(language), cx);
7104 });
7105
7106 // Ensure that signature_help is not called when no signature help is enabled.
7107 cx.set_state(
7108 &r#"
7109 fn main() {
7110 sampleˇ
7111 }
7112 "#
7113 .unindent(),
7114 );
7115 cx.update_editor(|view, cx| {
7116 view.handle_input("(", cx);
7117 });
7118 cx.assert_editor_state(
7119 &"
7120 fn main() {
7121 sample(ˇ)
7122 }
7123 "
7124 .unindent(),
7125 );
7126 cx.editor(|editor, _| {
7127 assert!(editor.signature_help_state.task().is_none());
7128 });
7129
7130 let mocked_response = lsp::SignatureHelp {
7131 signatures: vec![lsp::SignatureInformation {
7132 label: "fn sample(param1: u8, param2: u8)".to_string(),
7133 documentation: None,
7134 parameters: Some(vec![
7135 lsp::ParameterInformation {
7136 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7137 documentation: None,
7138 },
7139 lsp::ParameterInformation {
7140 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7141 documentation: None,
7142 },
7143 ]),
7144 active_parameter: None,
7145 }],
7146 active_signature: Some(0),
7147 active_parameter: Some(0),
7148 };
7149
7150 // Ensure that signature_help is called when enabled afte edits
7151 cx.update(|cx| {
7152 cx.update_global::<SettingsStore, _>(|settings, cx| {
7153 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7154 settings.auto_signature_help = Some(false);
7155 settings.show_signature_help_after_edits = Some(true);
7156 });
7157 });
7158 });
7159 cx.set_state(
7160 &r#"
7161 fn main() {
7162 sampleˇ
7163 }
7164 "#
7165 .unindent(),
7166 );
7167 cx.update_editor(|view, cx| {
7168 view.handle_input("(", cx);
7169 });
7170 cx.assert_editor_state(
7171 &"
7172 fn main() {
7173 sample(ˇ)
7174 }
7175 "
7176 .unindent(),
7177 );
7178 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7179 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7180 .await;
7181 cx.update_editor(|editor, _| {
7182 let signature_help_state = editor.signature_help_state.popover().cloned();
7183 assert!(signature_help_state.is_some());
7184 let ParsedMarkdown {
7185 text, highlights, ..
7186 } = signature_help_state.unwrap().parsed_content;
7187 assert_eq!(text, "param1: u8, param2: u8");
7188 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7189 editor.signature_help_state = SignatureHelpState::default();
7190 });
7191
7192 // Ensure that signature_help is called when auto signature help override is enabled
7193 cx.update(|cx| {
7194 cx.update_global::<SettingsStore, _>(|settings, cx| {
7195 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7196 settings.auto_signature_help = Some(true);
7197 settings.show_signature_help_after_edits = Some(false);
7198 });
7199 });
7200 });
7201 cx.set_state(
7202 &r#"
7203 fn main() {
7204 sampleˇ
7205 }
7206 "#
7207 .unindent(),
7208 );
7209 cx.update_editor(|view, cx| {
7210 view.handle_input("(", cx);
7211 });
7212 cx.assert_editor_state(
7213 &"
7214 fn main() {
7215 sample(ˇ)
7216 }
7217 "
7218 .unindent(),
7219 );
7220 handle_signature_help_request(&mut cx, mocked_response).await;
7221 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7222 .await;
7223 cx.editor(|editor, _| {
7224 let signature_help_state = editor.signature_help_state.popover().cloned();
7225 assert!(signature_help_state.is_some());
7226 let ParsedMarkdown {
7227 text, highlights, ..
7228 } = signature_help_state.unwrap().parsed_content;
7229 assert_eq!(text, "param1: u8, param2: u8");
7230 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7231 });
7232}
7233
7234#[gpui::test]
7235async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7236 init_test(cx, |_| {});
7237 cx.update(|cx| {
7238 cx.update_global::<SettingsStore, _>(|settings, cx| {
7239 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7240 settings.auto_signature_help = Some(true);
7241 });
7242 });
7243 });
7244
7245 let mut cx = EditorLspTestContext::new_rust(
7246 lsp::ServerCapabilities {
7247 signature_help_provider: Some(lsp::SignatureHelpOptions {
7248 ..Default::default()
7249 }),
7250 ..Default::default()
7251 },
7252 cx,
7253 )
7254 .await;
7255
7256 // A test that directly calls `show_signature_help`
7257 cx.update_editor(|editor, cx| {
7258 editor.show_signature_help(&ShowSignatureHelp, cx);
7259 });
7260
7261 let mocked_response = lsp::SignatureHelp {
7262 signatures: vec![lsp::SignatureInformation {
7263 label: "fn sample(param1: u8, param2: u8)".to_string(),
7264 documentation: None,
7265 parameters: Some(vec![
7266 lsp::ParameterInformation {
7267 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7268 documentation: None,
7269 },
7270 lsp::ParameterInformation {
7271 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7272 documentation: None,
7273 },
7274 ]),
7275 active_parameter: None,
7276 }],
7277 active_signature: Some(0),
7278 active_parameter: Some(0),
7279 };
7280 handle_signature_help_request(&mut cx, mocked_response).await;
7281
7282 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7283 .await;
7284
7285 cx.editor(|editor, _| {
7286 let signature_help_state = editor.signature_help_state.popover().cloned();
7287 assert!(signature_help_state.is_some());
7288 let ParsedMarkdown {
7289 text, highlights, ..
7290 } = signature_help_state.unwrap().parsed_content;
7291 assert_eq!(text, "param1: u8, param2: u8");
7292 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7293 });
7294
7295 // When exiting outside from inside the brackets, `signature_help` is closed.
7296 cx.set_state(indoc! {"
7297 fn main() {
7298 sample(ˇ);
7299 }
7300
7301 fn sample(param1: u8, param2: u8) {}
7302 "});
7303
7304 cx.update_editor(|editor, cx| {
7305 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
7306 });
7307
7308 let mocked_response = lsp::SignatureHelp {
7309 signatures: Vec::new(),
7310 active_signature: None,
7311 active_parameter: None,
7312 };
7313 handle_signature_help_request(&mut cx, mocked_response).await;
7314
7315 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7316 .await;
7317
7318 cx.editor(|editor, _| {
7319 assert!(!editor.signature_help_state.is_shown());
7320 });
7321
7322 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
7323 cx.set_state(indoc! {"
7324 fn main() {
7325 sample(ˇ);
7326 }
7327
7328 fn sample(param1: u8, param2: u8) {}
7329 "});
7330
7331 let mocked_response = lsp::SignatureHelp {
7332 signatures: vec![lsp::SignatureInformation {
7333 label: "fn sample(param1: u8, param2: u8)".to_string(),
7334 documentation: None,
7335 parameters: Some(vec![
7336 lsp::ParameterInformation {
7337 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7338 documentation: None,
7339 },
7340 lsp::ParameterInformation {
7341 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7342 documentation: None,
7343 },
7344 ]),
7345 active_parameter: None,
7346 }],
7347 active_signature: Some(0),
7348 active_parameter: Some(0),
7349 };
7350 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7351 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7352 .await;
7353 cx.editor(|editor, _| {
7354 assert!(editor.signature_help_state.is_shown());
7355 });
7356
7357 // Restore the popover with more parameter input
7358 cx.set_state(indoc! {"
7359 fn main() {
7360 sample(param1, param2ˇ);
7361 }
7362
7363 fn sample(param1: u8, param2: u8) {}
7364 "});
7365
7366 let mocked_response = lsp::SignatureHelp {
7367 signatures: vec![lsp::SignatureInformation {
7368 label: "fn sample(param1: u8, param2: u8)".to_string(),
7369 documentation: None,
7370 parameters: Some(vec![
7371 lsp::ParameterInformation {
7372 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7373 documentation: None,
7374 },
7375 lsp::ParameterInformation {
7376 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7377 documentation: None,
7378 },
7379 ]),
7380 active_parameter: None,
7381 }],
7382 active_signature: Some(0),
7383 active_parameter: Some(1),
7384 };
7385 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7386 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7387 .await;
7388
7389 // When selecting a range, the popover is gone.
7390 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
7391 cx.update_editor(|editor, cx| {
7392 editor.change_selections(None, cx, |s| {
7393 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7394 })
7395 });
7396 cx.assert_editor_state(indoc! {"
7397 fn main() {
7398 sample(param1, «ˇparam2»);
7399 }
7400
7401 fn sample(param1: u8, param2: u8) {}
7402 "});
7403 cx.editor(|editor, _| {
7404 assert!(!editor.signature_help_state.is_shown());
7405 });
7406
7407 // When unselecting again, the popover is back if within the brackets.
7408 cx.update_editor(|editor, cx| {
7409 editor.change_selections(None, cx, |s| {
7410 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7411 })
7412 });
7413 cx.assert_editor_state(indoc! {"
7414 fn main() {
7415 sample(param1, ˇparam2);
7416 }
7417
7418 fn sample(param1: u8, param2: u8) {}
7419 "});
7420 handle_signature_help_request(&mut cx, mocked_response).await;
7421 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7422 .await;
7423 cx.editor(|editor, _| {
7424 assert!(editor.signature_help_state.is_shown());
7425 });
7426
7427 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
7428 cx.update_editor(|editor, cx| {
7429 editor.change_selections(None, cx, |s| {
7430 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
7431 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7432 })
7433 });
7434 cx.assert_editor_state(indoc! {"
7435 fn main() {
7436 sample(param1, ˇparam2);
7437 }
7438
7439 fn sample(param1: u8, param2: u8) {}
7440 "});
7441
7442 let mocked_response = lsp::SignatureHelp {
7443 signatures: vec![lsp::SignatureInformation {
7444 label: "fn sample(param1: u8, param2: u8)".to_string(),
7445 documentation: None,
7446 parameters: Some(vec![
7447 lsp::ParameterInformation {
7448 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7449 documentation: None,
7450 },
7451 lsp::ParameterInformation {
7452 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7453 documentation: None,
7454 },
7455 ]),
7456 active_parameter: None,
7457 }],
7458 active_signature: Some(0),
7459 active_parameter: Some(1),
7460 };
7461 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7462 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7463 .await;
7464 cx.update_editor(|editor, cx| {
7465 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
7466 });
7467 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7468 .await;
7469 cx.update_editor(|editor, cx| {
7470 editor.change_selections(None, cx, |s| {
7471 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7472 })
7473 });
7474 cx.assert_editor_state(indoc! {"
7475 fn main() {
7476 sample(param1, «ˇparam2»);
7477 }
7478
7479 fn sample(param1: u8, param2: u8) {}
7480 "});
7481 cx.update_editor(|editor, cx| {
7482 editor.change_selections(None, cx, |s| {
7483 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7484 })
7485 });
7486 cx.assert_editor_state(indoc! {"
7487 fn main() {
7488 sample(param1, ˇparam2);
7489 }
7490
7491 fn sample(param1: u8, param2: u8) {}
7492 "});
7493 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
7494 .await;
7495}
7496
7497#[gpui::test]
7498async fn test_completion(cx: &mut gpui::TestAppContext) {
7499 init_test(cx, |_| {});
7500
7501 let mut cx = EditorLspTestContext::new_rust(
7502 lsp::ServerCapabilities {
7503 completion_provider: Some(lsp::CompletionOptions {
7504 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7505 resolve_provider: Some(true),
7506 ..Default::default()
7507 }),
7508 ..Default::default()
7509 },
7510 cx,
7511 )
7512 .await;
7513 let counter = Arc::new(AtomicUsize::new(0));
7514
7515 cx.set_state(indoc! {"
7516 oneˇ
7517 two
7518 three
7519 "});
7520 cx.simulate_keystroke(".");
7521 handle_completion_request(
7522 &mut cx,
7523 indoc! {"
7524 one.|<>
7525 two
7526 three
7527 "},
7528 vec!["first_completion", "second_completion"],
7529 counter.clone(),
7530 )
7531 .await;
7532 cx.condition(|editor, _| editor.context_menu_visible())
7533 .await;
7534 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
7535
7536 let apply_additional_edits = cx.update_editor(|editor, cx| {
7537 editor.context_menu_next(&Default::default(), cx);
7538 editor
7539 .confirm_completion(&ConfirmCompletion::default(), cx)
7540 .unwrap()
7541 });
7542 cx.assert_editor_state(indoc! {"
7543 one.second_completionˇ
7544 two
7545 three
7546 "});
7547
7548 handle_resolve_completion_request(
7549 &mut cx,
7550 Some(vec![
7551 (
7552 //This overlaps with the primary completion edit which is
7553 //misbehavior from the LSP spec, test that we filter it out
7554 indoc! {"
7555 one.second_ˇcompletion
7556 two
7557 threeˇ
7558 "},
7559 "overlapping additional edit",
7560 ),
7561 (
7562 indoc! {"
7563 one.second_completion
7564 two
7565 threeˇ
7566 "},
7567 "\nadditional edit",
7568 ),
7569 ]),
7570 )
7571 .await;
7572 apply_additional_edits.await.unwrap();
7573 cx.assert_editor_state(indoc! {"
7574 one.second_completionˇ
7575 two
7576 three
7577 additional edit
7578 "});
7579
7580 cx.set_state(indoc! {"
7581 one.second_completion
7582 twoˇ
7583 threeˇ
7584 additional edit
7585 "});
7586 cx.simulate_keystroke(" ");
7587 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7588 cx.simulate_keystroke("s");
7589 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7590
7591 cx.assert_editor_state(indoc! {"
7592 one.second_completion
7593 two sˇ
7594 three sˇ
7595 additional edit
7596 "});
7597 handle_completion_request(
7598 &mut cx,
7599 indoc! {"
7600 one.second_completion
7601 two s
7602 three <s|>
7603 additional edit
7604 "},
7605 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
7606 counter.clone(),
7607 )
7608 .await;
7609 cx.condition(|editor, _| editor.context_menu_visible())
7610 .await;
7611 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
7612
7613 cx.simulate_keystroke("i");
7614
7615 handle_completion_request(
7616 &mut cx,
7617 indoc! {"
7618 one.second_completion
7619 two si
7620 three <si|>
7621 additional edit
7622 "},
7623 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
7624 counter.clone(),
7625 )
7626 .await;
7627 cx.condition(|editor, _| editor.context_menu_visible())
7628 .await;
7629 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
7630
7631 let apply_additional_edits = cx.update_editor(|editor, cx| {
7632 editor
7633 .confirm_completion(&ConfirmCompletion::default(), cx)
7634 .unwrap()
7635 });
7636 cx.assert_editor_state(indoc! {"
7637 one.second_completion
7638 two sixth_completionˇ
7639 three sixth_completionˇ
7640 additional edit
7641 "});
7642
7643 handle_resolve_completion_request(&mut cx, None).await;
7644 apply_additional_edits.await.unwrap();
7645
7646 _ = cx.update(|cx| {
7647 cx.update_global::<SettingsStore, _>(|settings, cx| {
7648 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7649 settings.show_completions_on_input = Some(false);
7650 });
7651 })
7652 });
7653 cx.set_state("editorˇ");
7654 cx.simulate_keystroke(".");
7655 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7656 cx.simulate_keystroke("c");
7657 cx.simulate_keystroke("l");
7658 cx.simulate_keystroke("o");
7659 cx.assert_editor_state("editor.cloˇ");
7660 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7661 cx.update_editor(|editor, cx| {
7662 editor.show_completions(&ShowCompletions { trigger: None }, cx);
7663 });
7664 handle_completion_request(
7665 &mut cx,
7666 "editor.<clo|>",
7667 vec!["close", "clobber"],
7668 counter.clone(),
7669 )
7670 .await;
7671 cx.condition(|editor, _| editor.context_menu_visible())
7672 .await;
7673 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
7674
7675 let apply_additional_edits = cx.update_editor(|editor, cx| {
7676 editor
7677 .confirm_completion(&ConfirmCompletion::default(), cx)
7678 .unwrap()
7679 });
7680 cx.assert_editor_state("editor.closeˇ");
7681 handle_resolve_completion_request(&mut cx, None).await;
7682 apply_additional_edits.await.unwrap();
7683}
7684
7685#[gpui::test]
7686async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
7687 init_test(cx, |_| {});
7688 let mut cx = EditorLspTestContext::new_rust(
7689 lsp::ServerCapabilities {
7690 completion_provider: Some(lsp::CompletionOptions {
7691 trigger_characters: Some(vec![".".to_string()]),
7692 ..Default::default()
7693 }),
7694 ..Default::default()
7695 },
7696 cx,
7697 )
7698 .await;
7699 cx.lsp
7700 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
7701 Ok(Some(lsp::CompletionResponse::Array(vec![
7702 lsp::CompletionItem {
7703 label: "first".into(),
7704 ..Default::default()
7705 },
7706 lsp::CompletionItem {
7707 label: "last".into(),
7708 ..Default::default()
7709 },
7710 ])))
7711 });
7712 cx.set_state("variableˇ");
7713 cx.simulate_keystroke(".");
7714 cx.executor().run_until_parked();
7715
7716 cx.update_editor(|editor, _| {
7717 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7718 assert_eq!(
7719 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
7720 &["first", "last"]
7721 );
7722 } else {
7723 panic!("expected completion menu to be open");
7724 }
7725 });
7726
7727 cx.update_editor(|editor, cx| {
7728 editor.move_page_down(&MovePageDown::default(), cx);
7729 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7730 assert!(
7731 menu.selected_item == 1,
7732 "expected PageDown to select the last item from the context menu"
7733 );
7734 } else {
7735 panic!("expected completion menu to stay open after PageDown");
7736 }
7737 });
7738
7739 cx.update_editor(|editor, cx| {
7740 editor.move_page_up(&MovePageUp::default(), cx);
7741 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7742 assert!(
7743 menu.selected_item == 0,
7744 "expected PageUp to select the first item from the context menu"
7745 );
7746 } else {
7747 panic!("expected completion menu to stay open after PageUp");
7748 }
7749 });
7750}
7751
7752#[gpui::test]
7753async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
7754 init_test(cx, |_| {});
7755
7756 let mut cx = EditorLspTestContext::new_rust(
7757 lsp::ServerCapabilities {
7758 completion_provider: Some(lsp::CompletionOptions {
7759 trigger_characters: Some(vec![".".to_string()]),
7760 resolve_provider: Some(true),
7761 ..Default::default()
7762 }),
7763 ..Default::default()
7764 },
7765 cx,
7766 )
7767 .await;
7768
7769 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
7770 cx.simulate_keystroke(".");
7771 let completion_item = lsp::CompletionItem {
7772 label: "Some".into(),
7773 kind: Some(lsp::CompletionItemKind::SNIPPET),
7774 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
7775 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
7776 kind: lsp::MarkupKind::Markdown,
7777 value: "```rust\nSome(2)\n```".to_string(),
7778 })),
7779 deprecated: Some(false),
7780 sort_text: Some("Some".to_string()),
7781 filter_text: Some("Some".to_string()),
7782 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
7783 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7784 range: lsp::Range {
7785 start: lsp::Position {
7786 line: 0,
7787 character: 22,
7788 },
7789 end: lsp::Position {
7790 line: 0,
7791 character: 22,
7792 },
7793 },
7794 new_text: "Some(2)".to_string(),
7795 })),
7796 additional_text_edits: Some(vec![lsp::TextEdit {
7797 range: lsp::Range {
7798 start: lsp::Position {
7799 line: 0,
7800 character: 20,
7801 },
7802 end: lsp::Position {
7803 line: 0,
7804 character: 22,
7805 },
7806 },
7807 new_text: "".to_string(),
7808 }]),
7809 ..Default::default()
7810 };
7811
7812 let closure_completion_item = completion_item.clone();
7813 let counter = Arc::new(AtomicUsize::new(0));
7814 let counter_clone = counter.clone();
7815 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
7816 let task_completion_item = closure_completion_item.clone();
7817 counter_clone.fetch_add(1, atomic::Ordering::Release);
7818 async move {
7819 Ok(Some(lsp::CompletionResponse::Array(vec![
7820 task_completion_item,
7821 ])))
7822 }
7823 });
7824
7825 cx.condition(|editor, _| editor.context_menu_visible())
7826 .await;
7827 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
7828 assert!(request.next().await.is_some());
7829 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
7830
7831 cx.simulate_keystroke("S");
7832 cx.simulate_keystroke("o");
7833 cx.simulate_keystroke("m");
7834 cx.condition(|editor, _| editor.context_menu_visible())
7835 .await;
7836 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
7837 assert!(request.next().await.is_some());
7838 assert!(request.next().await.is_some());
7839 assert!(request.next().await.is_some());
7840 request.close();
7841 assert!(request.next().await.is_none());
7842 assert_eq!(
7843 counter.load(atomic::Ordering::Acquire),
7844 4,
7845 "With the completions menu open, only one LSP request should happen per input"
7846 );
7847}
7848
7849#[gpui::test]
7850async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
7851 init_test(cx, |_| {});
7852 let mut cx = EditorTestContext::new(cx).await;
7853 let language = Arc::new(Language::new(
7854 LanguageConfig {
7855 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
7856 ..Default::default()
7857 },
7858 Some(tree_sitter_rust::language()),
7859 ));
7860 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
7861
7862 // If multiple selections intersect a line, the line is only toggled once.
7863 cx.set_state(indoc! {"
7864 fn a() {
7865 «//b();
7866 ˇ»// «c();
7867 //ˇ» d();
7868 }
7869 "});
7870
7871 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7872
7873 cx.assert_editor_state(indoc! {"
7874 fn a() {
7875 «b();
7876 c();
7877 ˇ» d();
7878 }
7879 "});
7880
7881 // The comment prefix is inserted at the same column for every line in a
7882 // selection.
7883 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7884
7885 cx.assert_editor_state(indoc! {"
7886 fn a() {
7887 // «b();
7888 // c();
7889 ˇ»// d();
7890 }
7891 "});
7892
7893 // If a selection ends at the beginning of a line, that line is not toggled.
7894 cx.set_selections_state(indoc! {"
7895 fn a() {
7896 // b();
7897 «// c();
7898 ˇ» // d();
7899 }
7900 "});
7901
7902 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7903
7904 cx.assert_editor_state(indoc! {"
7905 fn a() {
7906 // b();
7907 «c();
7908 ˇ» // d();
7909 }
7910 "});
7911
7912 // If a selection span a single line and is empty, the line is toggled.
7913 cx.set_state(indoc! {"
7914 fn a() {
7915 a();
7916 b();
7917 ˇ
7918 }
7919 "});
7920
7921 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7922
7923 cx.assert_editor_state(indoc! {"
7924 fn a() {
7925 a();
7926 b();
7927 //•ˇ
7928 }
7929 "});
7930
7931 // If a selection span multiple lines, empty lines are not toggled.
7932 cx.set_state(indoc! {"
7933 fn a() {
7934 «a();
7935
7936 c();ˇ»
7937 }
7938 "});
7939
7940 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7941
7942 cx.assert_editor_state(indoc! {"
7943 fn a() {
7944 // «a();
7945
7946 // c();ˇ»
7947 }
7948 "});
7949
7950 // If a selection includes multiple comment prefixes, all lines are uncommented.
7951 cx.set_state(indoc! {"
7952 fn a() {
7953 «// a();
7954 /// b();
7955 //! c();ˇ»
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 «a();
7964 b();
7965 c();ˇ»
7966 }
7967 "});
7968}
7969
7970#[gpui::test]
7971async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
7972 init_test(cx, |_| {});
7973
7974 let language = Arc::new(Language::new(
7975 LanguageConfig {
7976 line_comments: vec!["// ".into()],
7977 ..Default::default()
7978 },
7979 Some(tree_sitter_rust::language()),
7980 ));
7981
7982 let mut cx = EditorTestContext::new(cx).await;
7983
7984 cx.language_registry().add(language.clone());
7985 cx.update_buffer(|buffer, cx| {
7986 buffer.set_language(Some(language), cx);
7987 });
7988
7989 let toggle_comments = &ToggleComments {
7990 advance_downwards: true,
7991 };
7992
7993 // Single cursor on one line -> advance
7994 // Cursor moves horizontally 3 characters as well on non-blank line
7995 cx.set_state(indoc!(
7996 "fn a() {
7997 ˇdog();
7998 cat();
7999 }"
8000 ));
8001 cx.update_editor(|editor, cx| {
8002 editor.toggle_comments(toggle_comments, cx);
8003 });
8004 cx.assert_editor_state(indoc!(
8005 "fn a() {
8006 // dog();
8007 catˇ();
8008 }"
8009 ));
8010
8011 // Single selection on one line -> don't advance
8012 cx.set_state(indoc!(
8013 "fn a() {
8014 «dog()ˇ»;
8015 cat();
8016 }"
8017 ));
8018 cx.update_editor(|editor, cx| {
8019 editor.toggle_comments(toggle_comments, cx);
8020 });
8021 cx.assert_editor_state(indoc!(
8022 "fn a() {
8023 // «dog()ˇ»;
8024 cat();
8025 }"
8026 ));
8027
8028 // Multiple cursors on one line -> advance
8029 cx.set_state(indoc!(
8030 "fn a() {
8031 ˇdˇog();
8032 cat();
8033 }"
8034 ));
8035 cx.update_editor(|editor, cx| {
8036 editor.toggle_comments(toggle_comments, cx);
8037 });
8038 cx.assert_editor_state(indoc!(
8039 "fn a() {
8040 // dog();
8041 catˇ(ˇ);
8042 }"
8043 ));
8044
8045 // Multiple cursors on one line, with selection -> don't advance
8046 cx.set_state(indoc!(
8047 "fn a() {
8048 ˇdˇog«()ˇ»;
8049 cat();
8050 }"
8051 ));
8052 cx.update_editor(|editor, cx| {
8053 editor.toggle_comments(toggle_comments, cx);
8054 });
8055 cx.assert_editor_state(indoc!(
8056 "fn a() {
8057 // ˇdˇog«()ˇ»;
8058 cat();
8059 }"
8060 ));
8061
8062 // Single cursor on one line -> advance
8063 // Cursor moves to column 0 on blank line
8064 cx.set_state(indoc!(
8065 "fn a() {
8066 ˇdog();
8067
8068 cat();
8069 }"
8070 ));
8071 cx.update_editor(|editor, cx| {
8072 editor.toggle_comments(toggle_comments, cx);
8073 });
8074 cx.assert_editor_state(indoc!(
8075 "fn a() {
8076 // dog();
8077 ˇ
8078 cat();
8079 }"
8080 ));
8081
8082 // Single cursor on one line -> advance
8083 // Cursor starts and ends at column 0
8084 cx.set_state(indoc!(
8085 "fn a() {
8086 ˇ dog();
8087 cat();
8088 }"
8089 ));
8090 cx.update_editor(|editor, cx| {
8091 editor.toggle_comments(toggle_comments, cx);
8092 });
8093 cx.assert_editor_state(indoc!(
8094 "fn a() {
8095 // dog();
8096 ˇ cat();
8097 }"
8098 ));
8099}
8100
8101#[gpui::test]
8102async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
8103 init_test(cx, |_| {});
8104
8105 let mut cx = EditorTestContext::new(cx).await;
8106
8107 let html_language = Arc::new(
8108 Language::new(
8109 LanguageConfig {
8110 name: "HTML".into(),
8111 block_comment: Some(("<!-- ".into(), " -->".into())),
8112 ..Default::default()
8113 },
8114 Some(tree_sitter_html::language()),
8115 )
8116 .with_injection_query(
8117 r#"
8118 (script_element
8119 (raw_text) @content
8120 (#set! "language" "javascript"))
8121 "#,
8122 )
8123 .unwrap(),
8124 );
8125
8126 let javascript_language = Arc::new(Language::new(
8127 LanguageConfig {
8128 name: "JavaScript".into(),
8129 line_comments: vec!["// ".into()],
8130 ..Default::default()
8131 },
8132 Some(tree_sitter_typescript::language_tsx()),
8133 ));
8134
8135 cx.language_registry().add(html_language.clone());
8136 cx.language_registry().add(javascript_language.clone());
8137 cx.update_buffer(|buffer, cx| {
8138 buffer.set_language(Some(html_language), cx);
8139 });
8140
8141 // Toggle comments for empty selections
8142 cx.set_state(
8143 &r#"
8144 <p>A</p>ˇ
8145 <p>B</p>ˇ
8146 <p>C</p>ˇ
8147 "#
8148 .unindent(),
8149 );
8150 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8151 cx.assert_editor_state(
8152 &r#"
8153 <!-- <p>A</p>ˇ -->
8154 <!-- <p>B</p>ˇ -->
8155 <!-- <p>C</p>ˇ -->
8156 "#
8157 .unindent(),
8158 );
8159 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8160 cx.assert_editor_state(
8161 &r#"
8162 <p>A</p>ˇ
8163 <p>B</p>ˇ
8164 <p>C</p>ˇ
8165 "#
8166 .unindent(),
8167 );
8168
8169 // Toggle comments for mixture of empty and non-empty selections, where
8170 // multiple selections occupy a given line.
8171 cx.set_state(
8172 &r#"
8173 <p>A«</p>
8174 <p>ˇ»B</p>ˇ
8175 <p>C«</p>
8176 <p>ˇ»D</p>ˇ
8177 "#
8178 .unindent(),
8179 );
8180
8181 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8182 cx.assert_editor_state(
8183 &r#"
8184 <!-- <p>A«</p>
8185 <p>ˇ»B</p>ˇ -->
8186 <!-- <p>C«</p>
8187 <p>ˇ»D</p>ˇ -->
8188 "#
8189 .unindent(),
8190 );
8191 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8192 cx.assert_editor_state(
8193 &r#"
8194 <p>A«</p>
8195 <p>ˇ»B</p>ˇ
8196 <p>C«</p>
8197 <p>ˇ»D</p>ˇ
8198 "#
8199 .unindent(),
8200 );
8201
8202 // Toggle comments when different languages are active for different
8203 // selections.
8204 cx.set_state(
8205 &r#"
8206 ˇ<script>
8207 ˇvar x = new Y();
8208 ˇ</script>
8209 "#
8210 .unindent(),
8211 );
8212 cx.executor().run_until_parked();
8213 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8214 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
8215 // Uncommenting and commenting from this position brings in even more wrong artifacts.
8216 cx.assert_editor_state(
8217 &r#"
8218 <!-- ˇ<script> -->
8219 // ˇvar x = new Y();
8220 // ˇ</script>
8221 "#
8222 .unindent(),
8223 );
8224}
8225
8226#[gpui::test]
8227fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
8228 init_test(cx, |_| {});
8229
8230 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8231 let multibuffer = cx.new_model(|cx| {
8232 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8233 multibuffer.push_excerpts(
8234 buffer.clone(),
8235 [
8236 ExcerptRange {
8237 context: Point::new(0, 0)..Point::new(0, 4),
8238 primary: None,
8239 },
8240 ExcerptRange {
8241 context: Point::new(1, 0)..Point::new(1, 4),
8242 primary: None,
8243 },
8244 ],
8245 cx,
8246 );
8247 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
8248 multibuffer
8249 });
8250
8251 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8252 _ = view.update(cx, |view, cx| {
8253 assert_eq!(view.text(cx), "aaaa\nbbbb");
8254 view.change_selections(None, cx, |s| {
8255 s.select_ranges([
8256 Point::new(0, 0)..Point::new(0, 0),
8257 Point::new(1, 0)..Point::new(1, 0),
8258 ])
8259 });
8260
8261 view.handle_input("X", cx);
8262 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
8263 assert_eq!(
8264 view.selections.ranges(cx),
8265 [
8266 Point::new(0, 1)..Point::new(0, 1),
8267 Point::new(1, 1)..Point::new(1, 1),
8268 ]
8269 );
8270
8271 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
8272 view.change_selections(None, cx, |s| {
8273 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
8274 });
8275 view.backspace(&Default::default(), cx);
8276 assert_eq!(view.text(cx), "Xa\nbbb");
8277 assert_eq!(
8278 view.selections.ranges(cx),
8279 [Point::new(1, 0)..Point::new(1, 0)]
8280 );
8281
8282 view.change_selections(None, cx, |s| {
8283 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
8284 });
8285 view.backspace(&Default::default(), cx);
8286 assert_eq!(view.text(cx), "X\nbb");
8287 assert_eq!(
8288 view.selections.ranges(cx),
8289 [Point::new(0, 1)..Point::new(0, 1)]
8290 );
8291 });
8292}
8293
8294#[gpui::test]
8295fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
8296 init_test(cx, |_| {});
8297
8298 let markers = vec![('[', ']').into(), ('(', ')').into()];
8299 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
8300 indoc! {"
8301 [aaaa
8302 (bbbb]
8303 cccc)",
8304 },
8305 markers.clone(),
8306 );
8307 let excerpt_ranges = markers.into_iter().map(|marker| {
8308 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
8309 ExcerptRange {
8310 context,
8311 primary: None,
8312 }
8313 });
8314 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
8315 let multibuffer = cx.new_model(|cx| {
8316 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8317 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
8318 multibuffer
8319 });
8320
8321 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8322 _ = view.update(cx, |view, cx| {
8323 let (expected_text, selection_ranges) = marked_text_ranges(
8324 indoc! {"
8325 aaaa
8326 bˇbbb
8327 bˇbbˇb
8328 cccc"
8329 },
8330 true,
8331 );
8332 assert_eq!(view.text(cx), expected_text);
8333 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
8334
8335 view.handle_input("X", cx);
8336
8337 let (expected_text, expected_selections) = marked_text_ranges(
8338 indoc! {"
8339 aaaa
8340 bXˇbbXb
8341 bXˇbbXˇb
8342 cccc"
8343 },
8344 false,
8345 );
8346 assert_eq!(view.text(cx), expected_text);
8347 assert_eq!(view.selections.ranges(cx), expected_selections);
8348
8349 view.newline(&Newline, cx);
8350 let (expected_text, expected_selections) = marked_text_ranges(
8351 indoc! {"
8352 aaaa
8353 bX
8354 ˇbbX
8355 b
8356 bX
8357 ˇbbX
8358 ˇb
8359 cccc"
8360 },
8361 false,
8362 );
8363 assert_eq!(view.text(cx), expected_text);
8364 assert_eq!(view.selections.ranges(cx), expected_selections);
8365 });
8366}
8367
8368#[gpui::test]
8369fn test_refresh_selections(cx: &mut TestAppContext) {
8370 init_test(cx, |_| {});
8371
8372 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8373 let mut excerpt1_id = None;
8374 let multibuffer = cx.new_model(|cx| {
8375 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8376 excerpt1_id = multibuffer
8377 .push_excerpts(
8378 buffer.clone(),
8379 [
8380 ExcerptRange {
8381 context: Point::new(0, 0)..Point::new(1, 4),
8382 primary: None,
8383 },
8384 ExcerptRange {
8385 context: Point::new(1, 0)..Point::new(2, 4),
8386 primary: None,
8387 },
8388 ],
8389 cx,
8390 )
8391 .into_iter()
8392 .next();
8393 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8394 multibuffer
8395 });
8396
8397 let editor = cx.add_window(|cx| {
8398 let mut editor = build_editor(multibuffer.clone(), cx);
8399 let snapshot = editor.snapshot(cx);
8400 editor.change_selections(None, cx, |s| {
8401 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
8402 });
8403 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
8404 assert_eq!(
8405 editor.selections.ranges(cx),
8406 [
8407 Point::new(1, 3)..Point::new(1, 3),
8408 Point::new(2, 1)..Point::new(2, 1),
8409 ]
8410 );
8411 editor
8412 });
8413
8414 // Refreshing selections is a no-op when excerpts haven't changed.
8415 _ = editor.update(cx, |editor, cx| {
8416 editor.change_selections(None, cx, |s| s.refresh());
8417 assert_eq!(
8418 editor.selections.ranges(cx),
8419 [
8420 Point::new(1, 3)..Point::new(1, 3),
8421 Point::new(2, 1)..Point::new(2, 1),
8422 ]
8423 );
8424 });
8425
8426 _ = multibuffer.update(cx, |multibuffer, cx| {
8427 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8428 });
8429 _ = editor.update(cx, |editor, cx| {
8430 // Removing an excerpt causes the first selection to become degenerate.
8431 assert_eq!(
8432 editor.selections.ranges(cx),
8433 [
8434 Point::new(0, 0)..Point::new(0, 0),
8435 Point::new(0, 1)..Point::new(0, 1)
8436 ]
8437 );
8438
8439 // Refreshing selections will relocate the first selection to the original buffer
8440 // location.
8441 editor.change_selections(None, cx, |s| s.refresh());
8442 assert_eq!(
8443 editor.selections.ranges(cx),
8444 [
8445 Point::new(0, 1)..Point::new(0, 1),
8446 Point::new(0, 3)..Point::new(0, 3)
8447 ]
8448 );
8449 assert!(editor.selections.pending_anchor().is_some());
8450 });
8451}
8452
8453#[gpui::test]
8454fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
8455 init_test(cx, |_| {});
8456
8457 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8458 let mut excerpt1_id = None;
8459 let multibuffer = cx.new_model(|cx| {
8460 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8461 excerpt1_id = multibuffer
8462 .push_excerpts(
8463 buffer.clone(),
8464 [
8465 ExcerptRange {
8466 context: Point::new(0, 0)..Point::new(1, 4),
8467 primary: None,
8468 },
8469 ExcerptRange {
8470 context: Point::new(1, 0)..Point::new(2, 4),
8471 primary: None,
8472 },
8473 ],
8474 cx,
8475 )
8476 .into_iter()
8477 .next();
8478 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8479 multibuffer
8480 });
8481
8482 let editor = cx.add_window(|cx| {
8483 let mut editor = build_editor(multibuffer.clone(), cx);
8484 let snapshot = editor.snapshot(cx);
8485 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
8486 assert_eq!(
8487 editor.selections.ranges(cx),
8488 [Point::new(1, 3)..Point::new(1, 3)]
8489 );
8490 editor
8491 });
8492
8493 _ = multibuffer.update(cx, |multibuffer, cx| {
8494 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8495 });
8496 _ = editor.update(cx, |editor, cx| {
8497 assert_eq!(
8498 editor.selections.ranges(cx),
8499 [Point::new(0, 0)..Point::new(0, 0)]
8500 );
8501
8502 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
8503 editor.change_selections(None, cx, |s| s.refresh());
8504 assert_eq!(
8505 editor.selections.ranges(cx),
8506 [Point::new(0, 3)..Point::new(0, 3)]
8507 );
8508 assert!(editor.selections.pending_anchor().is_some());
8509 });
8510}
8511
8512#[gpui::test]
8513async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
8514 init_test(cx, |_| {});
8515
8516 let language = Arc::new(
8517 Language::new(
8518 LanguageConfig {
8519 brackets: BracketPairConfig {
8520 pairs: vec![
8521 BracketPair {
8522 start: "{".to_string(),
8523 end: "}".to_string(),
8524 close: true,
8525 surround: true,
8526 newline: true,
8527 },
8528 BracketPair {
8529 start: "/* ".to_string(),
8530 end: " */".to_string(),
8531 close: true,
8532 surround: true,
8533 newline: true,
8534 },
8535 ],
8536 ..Default::default()
8537 },
8538 ..Default::default()
8539 },
8540 Some(tree_sitter_rust::language()),
8541 )
8542 .with_indents_query("")
8543 .unwrap(),
8544 );
8545
8546 let text = concat!(
8547 "{ }\n", //
8548 " x\n", //
8549 " /* */\n", //
8550 "x\n", //
8551 "{{} }\n", //
8552 );
8553
8554 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
8555 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
8556 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
8557 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
8558 .await;
8559
8560 _ = view.update(cx, |view, cx| {
8561 view.change_selections(None, cx, |s| {
8562 s.select_display_ranges([
8563 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
8564 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
8565 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
8566 ])
8567 });
8568 view.newline(&Newline, cx);
8569
8570 assert_eq!(
8571 view.buffer().read(cx).read(cx).text(),
8572 concat!(
8573 "{ \n", // Suppress rustfmt
8574 "\n", //
8575 "}\n", //
8576 " x\n", //
8577 " /* \n", //
8578 " \n", //
8579 " */\n", //
8580 "x\n", //
8581 "{{} \n", //
8582 "}\n", //
8583 )
8584 );
8585 });
8586}
8587
8588#[gpui::test]
8589fn test_highlighted_ranges(cx: &mut TestAppContext) {
8590 init_test(cx, |_| {});
8591
8592 let editor = cx.add_window(|cx| {
8593 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
8594 build_editor(buffer.clone(), cx)
8595 });
8596
8597 _ = editor.update(cx, |editor, cx| {
8598 struct Type1;
8599 struct Type2;
8600
8601 let buffer = editor.buffer.read(cx).snapshot(cx);
8602
8603 let anchor_range =
8604 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
8605
8606 editor.highlight_background::<Type1>(
8607 &[
8608 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
8609 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
8610 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
8611 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
8612 ],
8613 |_| Hsla::red(),
8614 cx,
8615 );
8616 editor.highlight_background::<Type2>(
8617 &[
8618 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
8619 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
8620 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
8621 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
8622 ],
8623 |_| Hsla::green(),
8624 cx,
8625 );
8626
8627 let snapshot = editor.snapshot(cx);
8628 let mut highlighted_ranges = editor.background_highlights_in_range(
8629 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
8630 &snapshot,
8631 cx.theme().colors(),
8632 );
8633 // Enforce a consistent ordering based on color without relying on the ordering of the
8634 // highlight's `TypeId` which is non-executor.
8635 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
8636 assert_eq!(
8637 highlighted_ranges,
8638 &[
8639 (
8640 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
8641 Hsla::red(),
8642 ),
8643 (
8644 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
8645 Hsla::red(),
8646 ),
8647 (
8648 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
8649 Hsla::green(),
8650 ),
8651 (
8652 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
8653 Hsla::green(),
8654 ),
8655 ]
8656 );
8657 assert_eq!(
8658 editor.background_highlights_in_range(
8659 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
8660 &snapshot,
8661 cx.theme().colors(),
8662 ),
8663 &[(
8664 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
8665 Hsla::red(),
8666 )]
8667 );
8668 });
8669}
8670
8671#[gpui::test]
8672async fn test_following(cx: &mut gpui::TestAppContext) {
8673 init_test(cx, |_| {});
8674
8675 let fs = FakeFs::new(cx.executor());
8676 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8677
8678 let buffer = project.update(cx, |project, cx| {
8679 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
8680 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
8681 });
8682 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
8683 let follower = cx.update(|cx| {
8684 cx.open_window(
8685 WindowOptions {
8686 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
8687 gpui::Point::new(px(0.), px(0.)),
8688 gpui::Point::new(px(10.), px(80.)),
8689 ))),
8690 ..Default::default()
8691 },
8692 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
8693 )
8694 .unwrap()
8695 });
8696
8697 let is_still_following = Rc::new(RefCell::new(true));
8698 let follower_edit_event_count = Rc::new(RefCell::new(0));
8699 let pending_update = Rc::new(RefCell::new(None));
8700 _ = follower.update(cx, {
8701 let update = pending_update.clone();
8702 let is_still_following = is_still_following.clone();
8703 let follower_edit_event_count = follower_edit_event_count.clone();
8704 |_, cx| {
8705 cx.subscribe(
8706 &leader.root_view(cx).unwrap(),
8707 move |_, leader, event, cx| {
8708 leader
8709 .read(cx)
8710 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
8711 },
8712 )
8713 .detach();
8714
8715 cx.subscribe(
8716 &follower.root_view(cx).unwrap(),
8717 move |_, _, event: &EditorEvent, _cx| {
8718 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
8719 *is_still_following.borrow_mut() = false;
8720 }
8721
8722 if let EditorEvent::BufferEdited = event {
8723 *follower_edit_event_count.borrow_mut() += 1;
8724 }
8725 },
8726 )
8727 .detach();
8728 }
8729 });
8730
8731 // Update the selections only
8732 _ = leader.update(cx, |leader, cx| {
8733 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
8734 });
8735 follower
8736 .update(cx, |follower, cx| {
8737 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8738 })
8739 .unwrap()
8740 .await
8741 .unwrap();
8742 _ = follower.update(cx, |follower, cx| {
8743 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
8744 });
8745 assert_eq!(*is_still_following.borrow(), true);
8746 assert_eq!(*follower_edit_event_count.borrow(), 0);
8747
8748 // Update the scroll position only
8749 _ = leader.update(cx, |leader, cx| {
8750 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
8751 });
8752 follower
8753 .update(cx, |follower, cx| {
8754 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8755 })
8756 .unwrap()
8757 .await
8758 .unwrap();
8759 assert_eq!(
8760 follower
8761 .update(cx, |follower, cx| follower.scroll_position(cx))
8762 .unwrap(),
8763 gpui::Point::new(1.5, 3.5)
8764 );
8765 assert_eq!(*is_still_following.borrow(), true);
8766 assert_eq!(*follower_edit_event_count.borrow(), 0);
8767
8768 // Update the selections and scroll position. The follower's scroll position is updated
8769 // via autoscroll, not via the leader's exact scroll position.
8770 _ = leader.update(cx, |leader, cx| {
8771 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
8772 leader.request_autoscroll(Autoscroll::newest(), cx);
8773 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
8774 });
8775 follower
8776 .update(cx, |follower, cx| {
8777 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8778 })
8779 .unwrap()
8780 .await
8781 .unwrap();
8782 _ = follower.update(cx, |follower, cx| {
8783 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
8784 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
8785 });
8786 assert_eq!(*is_still_following.borrow(), true);
8787
8788 // Creating a pending selection that precedes another selection
8789 _ = leader.update(cx, |leader, cx| {
8790 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
8791 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
8792 });
8793 follower
8794 .update(cx, |follower, cx| {
8795 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8796 })
8797 .unwrap()
8798 .await
8799 .unwrap();
8800 _ = follower.update(cx, |follower, cx| {
8801 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
8802 });
8803 assert_eq!(*is_still_following.borrow(), true);
8804
8805 // Extend the pending selection so that it surrounds another selection
8806 _ = leader.update(cx, |leader, cx| {
8807 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
8808 });
8809 follower
8810 .update(cx, |follower, cx| {
8811 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8812 })
8813 .unwrap()
8814 .await
8815 .unwrap();
8816 _ = follower.update(cx, |follower, cx| {
8817 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
8818 });
8819
8820 // Scrolling locally breaks the follow
8821 _ = follower.update(cx, |follower, cx| {
8822 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
8823 follower.set_scroll_anchor(
8824 ScrollAnchor {
8825 anchor: top_anchor,
8826 offset: gpui::Point::new(0.0, 0.5),
8827 },
8828 cx,
8829 );
8830 });
8831 assert_eq!(*is_still_following.borrow(), false);
8832}
8833
8834#[gpui::test]
8835async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
8836 init_test(cx, |_| {});
8837
8838 let fs = FakeFs::new(cx.executor());
8839 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8840 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8841 let pane = workspace
8842 .update(cx, |workspace, _| workspace.active_pane().clone())
8843 .unwrap();
8844
8845 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
8846
8847 let leader = pane.update(cx, |_, cx| {
8848 let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
8849 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
8850 });
8851
8852 // Start following the editor when it has no excerpts.
8853 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
8854 let follower_1 = cx
8855 .update_window(*workspace.deref(), |_, cx| {
8856 Editor::from_state_proto(
8857 workspace.root_view(cx).unwrap(),
8858 ViewId {
8859 creator: Default::default(),
8860 id: 0,
8861 },
8862 &mut state_message,
8863 cx,
8864 )
8865 })
8866 .unwrap()
8867 .unwrap()
8868 .await
8869 .unwrap();
8870
8871 let update_message = Rc::new(RefCell::new(None));
8872 follower_1.update(cx, {
8873 let update = update_message.clone();
8874 |_, cx| {
8875 cx.subscribe(&leader, move |_, leader, event, cx| {
8876 leader
8877 .read(cx)
8878 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
8879 })
8880 .detach();
8881 }
8882 });
8883
8884 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
8885 (
8886 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
8887 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
8888 )
8889 });
8890
8891 // Insert some excerpts.
8892 _ = leader.update(cx, |leader, cx| {
8893 leader.buffer.update(cx, |multibuffer, cx| {
8894 let excerpt_ids = multibuffer.push_excerpts(
8895 buffer_1.clone(),
8896 [
8897 ExcerptRange {
8898 context: 1..6,
8899 primary: None,
8900 },
8901 ExcerptRange {
8902 context: 12..15,
8903 primary: None,
8904 },
8905 ExcerptRange {
8906 context: 0..3,
8907 primary: None,
8908 },
8909 ],
8910 cx,
8911 );
8912 multibuffer.insert_excerpts_after(
8913 excerpt_ids[0],
8914 buffer_2.clone(),
8915 [
8916 ExcerptRange {
8917 context: 8..12,
8918 primary: None,
8919 },
8920 ExcerptRange {
8921 context: 0..6,
8922 primary: None,
8923 },
8924 ],
8925 cx,
8926 );
8927 });
8928 });
8929
8930 // Apply the update of adding the excerpts.
8931 follower_1
8932 .update(cx, |follower, cx| {
8933 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
8934 })
8935 .await
8936 .unwrap();
8937 assert_eq!(
8938 follower_1.update(cx, |editor, cx| editor.text(cx)),
8939 leader.update(cx, |editor, cx| editor.text(cx))
8940 );
8941 update_message.borrow_mut().take();
8942
8943 // Start following separately after it already has excerpts.
8944 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
8945 let follower_2 = cx
8946 .update_window(*workspace.deref(), |_, cx| {
8947 Editor::from_state_proto(
8948 workspace.root_view(cx).unwrap().clone(),
8949 ViewId {
8950 creator: Default::default(),
8951 id: 0,
8952 },
8953 &mut state_message,
8954 cx,
8955 )
8956 })
8957 .unwrap()
8958 .unwrap()
8959 .await
8960 .unwrap();
8961 assert_eq!(
8962 follower_2.update(cx, |editor, cx| editor.text(cx)),
8963 leader.update(cx, |editor, cx| editor.text(cx))
8964 );
8965
8966 // Remove some excerpts.
8967 _ = leader.update(cx, |leader, cx| {
8968 leader.buffer.update(cx, |multibuffer, cx| {
8969 let excerpt_ids = multibuffer.excerpt_ids();
8970 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
8971 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
8972 });
8973 });
8974
8975 // Apply the update of removing the excerpts.
8976 follower_1
8977 .update(cx, |follower, cx| {
8978 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
8979 })
8980 .await
8981 .unwrap();
8982 follower_2
8983 .update(cx, |follower, cx| {
8984 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
8985 })
8986 .await
8987 .unwrap();
8988 update_message.borrow_mut().take();
8989 assert_eq!(
8990 follower_1.update(cx, |editor, cx| editor.text(cx)),
8991 leader.update(cx, |editor, cx| editor.text(cx))
8992 );
8993}
8994
8995#[gpui::test]
8996async fn go_to_prev_overlapping_diagnostic(
8997 executor: BackgroundExecutor,
8998 cx: &mut gpui::TestAppContext,
8999) {
9000 init_test(cx, |_| {});
9001
9002 let mut cx = EditorTestContext::new(cx).await;
9003 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9004
9005 cx.set_state(indoc! {"
9006 ˇfn func(abc def: i32) -> u32 {
9007 }
9008 "});
9009
9010 _ = cx.update(|cx| {
9011 _ = project.update(cx, |project, cx| {
9012 project
9013 .update_diagnostics(
9014 LanguageServerId(0),
9015 lsp::PublishDiagnosticsParams {
9016 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9017 version: None,
9018 diagnostics: vec![
9019 lsp::Diagnostic {
9020 range: lsp::Range::new(
9021 lsp::Position::new(0, 11),
9022 lsp::Position::new(0, 12),
9023 ),
9024 severity: Some(lsp::DiagnosticSeverity::ERROR),
9025 ..Default::default()
9026 },
9027 lsp::Diagnostic {
9028 range: lsp::Range::new(
9029 lsp::Position::new(0, 12),
9030 lsp::Position::new(0, 15),
9031 ),
9032 severity: Some(lsp::DiagnosticSeverity::ERROR),
9033 ..Default::default()
9034 },
9035 lsp::Diagnostic {
9036 range: lsp::Range::new(
9037 lsp::Position::new(0, 25),
9038 lsp::Position::new(0, 28),
9039 ),
9040 severity: Some(lsp::DiagnosticSeverity::ERROR),
9041 ..Default::default()
9042 },
9043 ],
9044 },
9045 &[],
9046 cx,
9047 )
9048 .unwrap()
9049 });
9050 });
9051
9052 executor.run_until_parked();
9053
9054 cx.update_editor(|editor, cx| {
9055 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9056 });
9057
9058 cx.assert_editor_state(indoc! {"
9059 fn func(abc def: i32) -> ˇu32 {
9060 }
9061 "});
9062
9063 cx.update_editor(|editor, cx| {
9064 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9065 });
9066
9067 cx.assert_editor_state(indoc! {"
9068 fn func(abc ˇdef: i32) -> u32 {
9069 }
9070 "});
9071
9072 cx.update_editor(|editor, cx| {
9073 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9074 });
9075
9076 cx.assert_editor_state(indoc! {"
9077 fn func(abcˇ def: i32) -> u32 {
9078 }
9079 "});
9080
9081 cx.update_editor(|editor, cx| {
9082 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9083 });
9084
9085 cx.assert_editor_state(indoc! {"
9086 fn func(abc def: i32) -> ˇu32 {
9087 }
9088 "});
9089}
9090
9091#[gpui::test]
9092async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9093 init_test(cx, |_| {});
9094
9095 let mut cx = EditorTestContext::new(cx).await;
9096
9097 let diff_base = r#"
9098 use some::mod;
9099
9100 const A: u32 = 42;
9101
9102 fn main() {
9103 println!("hello");
9104
9105 println!("world");
9106 }
9107 "#
9108 .unindent();
9109
9110 // Edits are modified, removed, modified, added
9111 cx.set_state(
9112 &r#"
9113 use some::modified;
9114
9115 ˇ
9116 fn main() {
9117 println!("hello there");
9118
9119 println!("around the");
9120 println!("world");
9121 }
9122 "#
9123 .unindent(),
9124 );
9125
9126 cx.set_diff_base(Some(&diff_base));
9127 executor.run_until_parked();
9128
9129 cx.update_editor(|editor, cx| {
9130 //Wrap around the bottom of the buffer
9131 for _ in 0..3 {
9132 editor.go_to_hunk(&GoToHunk, cx);
9133 }
9134 });
9135
9136 cx.assert_editor_state(
9137 &r#"
9138 ˇuse some::modified;
9139
9140
9141 fn main() {
9142 println!("hello there");
9143
9144 println!("around the");
9145 println!("world");
9146 }
9147 "#
9148 .unindent(),
9149 );
9150
9151 cx.update_editor(|editor, cx| {
9152 //Wrap around the top of the buffer
9153 for _ in 0..2 {
9154 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9155 }
9156 });
9157
9158 cx.assert_editor_state(
9159 &r#"
9160 use some::modified;
9161
9162
9163 fn main() {
9164 ˇ println!("hello there");
9165
9166 println!("around the");
9167 println!("world");
9168 }
9169 "#
9170 .unindent(),
9171 );
9172
9173 cx.update_editor(|editor, cx| {
9174 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9175 });
9176
9177 cx.assert_editor_state(
9178 &r#"
9179 use some::modified;
9180
9181 ˇ
9182 fn main() {
9183 println!("hello there");
9184
9185 println!("around the");
9186 println!("world");
9187 }
9188 "#
9189 .unindent(),
9190 );
9191
9192 cx.update_editor(|editor, cx| {
9193 for _ in 0..3 {
9194 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9195 }
9196 });
9197
9198 cx.assert_editor_state(
9199 &r#"
9200 use some::modified;
9201
9202
9203 fn main() {
9204 ˇ println!("hello there");
9205
9206 println!("around the");
9207 println!("world");
9208 }
9209 "#
9210 .unindent(),
9211 );
9212
9213 cx.update_editor(|editor, cx| {
9214 editor.fold(&Fold, cx);
9215
9216 //Make sure that the fold only gets one hunk
9217 for _ in 0..4 {
9218 editor.go_to_hunk(&GoToHunk, cx);
9219 }
9220 });
9221
9222 cx.assert_editor_state(
9223 &r#"
9224 ˇuse some::modified;
9225
9226
9227 fn main() {
9228 println!("hello there");
9229
9230 println!("around the");
9231 println!("world");
9232 }
9233 "#
9234 .unindent(),
9235 );
9236}
9237
9238#[test]
9239fn test_split_words() {
9240 fn split(text: &str) -> Vec<&str> {
9241 split_words(text).collect()
9242 }
9243
9244 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
9245 assert_eq!(split("hello_world"), &["hello_", "world"]);
9246 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
9247 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
9248 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
9249 assert_eq!(split("helloworld"), &["helloworld"]);
9250
9251 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
9252}
9253
9254#[gpui::test]
9255async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
9256 init_test(cx, |_| {});
9257
9258 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
9259 let mut assert = |before, after| {
9260 let _state_context = cx.set_state(before);
9261 cx.update_editor(|editor, cx| {
9262 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
9263 });
9264 cx.assert_editor_state(after);
9265 };
9266
9267 // Outside bracket jumps to outside of matching bracket
9268 assert("console.logˇ(var);", "console.log(var)ˇ;");
9269 assert("console.log(var)ˇ;", "console.logˇ(var);");
9270
9271 // Inside bracket jumps to inside of matching bracket
9272 assert("console.log(ˇvar);", "console.log(varˇ);");
9273 assert("console.log(varˇ);", "console.log(ˇvar);");
9274
9275 // When outside a bracket and inside, favor jumping to the inside bracket
9276 assert(
9277 "console.log('foo', [1, 2, 3]ˇ);",
9278 "console.log(ˇ'foo', [1, 2, 3]);",
9279 );
9280 assert(
9281 "console.log(ˇ'foo', [1, 2, 3]);",
9282 "console.log('foo', [1, 2, 3]ˇ);",
9283 );
9284
9285 // Bias forward if two options are equally likely
9286 assert(
9287 "let result = curried_fun()ˇ();",
9288 "let result = curried_fun()()ˇ;",
9289 );
9290
9291 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
9292 assert(
9293 indoc! {"
9294 function test() {
9295 console.log('test')ˇ
9296 }"},
9297 indoc! {"
9298 function test() {
9299 console.logˇ('test')
9300 }"},
9301 );
9302}
9303
9304#[gpui::test]
9305async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
9306 init_test(cx, |_| {});
9307
9308 let fs = FakeFs::new(cx.executor());
9309 fs.insert_tree(
9310 "/a",
9311 json!({
9312 "main.rs": "fn main() { let a = 5; }",
9313 "other.rs": "// Test file",
9314 }),
9315 )
9316 .await;
9317 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9318
9319 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9320 language_registry.add(Arc::new(Language::new(
9321 LanguageConfig {
9322 name: "Rust".into(),
9323 matcher: LanguageMatcher {
9324 path_suffixes: vec!["rs".to_string()],
9325 ..Default::default()
9326 },
9327 brackets: BracketPairConfig {
9328 pairs: vec![BracketPair {
9329 start: "{".to_string(),
9330 end: "}".to_string(),
9331 close: true,
9332 surround: true,
9333 newline: true,
9334 }],
9335 disabled_scopes_by_bracket_ix: Vec::new(),
9336 },
9337 ..Default::default()
9338 },
9339 Some(tree_sitter_rust::language()),
9340 )));
9341 let mut fake_servers = language_registry.register_fake_lsp_adapter(
9342 "Rust",
9343 FakeLspAdapter {
9344 capabilities: lsp::ServerCapabilities {
9345 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
9346 first_trigger_character: "{".to_string(),
9347 more_trigger_character: None,
9348 }),
9349 ..Default::default()
9350 },
9351 ..Default::default()
9352 },
9353 );
9354
9355 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9356
9357 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9358
9359 let worktree_id = workspace
9360 .update(cx, |workspace, cx| {
9361 workspace.project().update(cx, |project, cx| {
9362 project.worktrees(cx).next().unwrap().read(cx).id()
9363 })
9364 })
9365 .unwrap();
9366
9367 let buffer = project
9368 .update(cx, |project, cx| {
9369 project.open_local_buffer("/a/main.rs", cx)
9370 })
9371 .await
9372 .unwrap();
9373 cx.executor().run_until_parked();
9374 cx.executor().start_waiting();
9375 let fake_server = fake_servers.next().await.unwrap();
9376 let editor_handle = workspace
9377 .update(cx, |workspace, cx| {
9378 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
9379 })
9380 .unwrap()
9381 .await
9382 .unwrap()
9383 .downcast::<Editor>()
9384 .unwrap();
9385
9386 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
9387 assert_eq!(
9388 params.text_document_position.text_document.uri,
9389 lsp::Url::from_file_path("/a/main.rs").unwrap(),
9390 );
9391 assert_eq!(
9392 params.text_document_position.position,
9393 lsp::Position::new(0, 21),
9394 );
9395
9396 Ok(Some(vec![lsp::TextEdit {
9397 new_text: "]".to_string(),
9398 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
9399 }]))
9400 });
9401
9402 editor_handle.update(cx, |editor, cx| {
9403 editor.focus(cx);
9404 editor.change_selections(None, cx, |s| {
9405 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
9406 });
9407 editor.handle_input("{", cx);
9408 });
9409
9410 cx.executor().run_until_parked();
9411
9412 _ = buffer.update(cx, |buffer, _| {
9413 assert_eq!(
9414 buffer.text(),
9415 "fn main() { let a = {5}; }",
9416 "No extra braces from on type formatting should appear in the buffer"
9417 )
9418 });
9419}
9420
9421#[gpui::test]
9422async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
9423 init_test(cx, |_| {});
9424
9425 let fs = FakeFs::new(cx.executor());
9426 fs.insert_tree(
9427 "/a",
9428 json!({
9429 "main.rs": "fn main() { let a = 5; }",
9430 "other.rs": "// Test file",
9431 }),
9432 )
9433 .await;
9434
9435 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9436
9437 let server_restarts = Arc::new(AtomicUsize::new(0));
9438 let closure_restarts = Arc::clone(&server_restarts);
9439 let language_server_name = "test language server";
9440 let language_name: Arc<str> = "Rust".into();
9441
9442 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9443 language_registry.add(Arc::new(Language::new(
9444 LanguageConfig {
9445 name: Arc::clone(&language_name),
9446 matcher: LanguageMatcher {
9447 path_suffixes: vec!["rs".to_string()],
9448 ..Default::default()
9449 },
9450 ..Default::default()
9451 },
9452 Some(tree_sitter_rust::language()),
9453 )));
9454 let mut fake_servers = language_registry.register_fake_lsp_adapter(
9455 "Rust",
9456 FakeLspAdapter {
9457 name: language_server_name,
9458 initialization_options: Some(json!({
9459 "testOptionValue": true
9460 })),
9461 initializer: Some(Box::new(move |fake_server| {
9462 let task_restarts = Arc::clone(&closure_restarts);
9463 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
9464 task_restarts.fetch_add(1, atomic::Ordering::Release);
9465 futures::future::ready(Ok(()))
9466 });
9467 })),
9468 ..Default::default()
9469 },
9470 );
9471
9472 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9473 let _buffer = project
9474 .update(cx, |project, cx| {
9475 project.open_local_buffer("/a/main.rs", cx)
9476 })
9477 .await
9478 .unwrap();
9479 let _fake_server = fake_servers.next().await.unwrap();
9480 update_test_language_settings(cx, |language_settings| {
9481 language_settings.languages.insert(
9482 Arc::clone(&language_name),
9483 LanguageSettingsContent {
9484 tab_size: NonZeroU32::new(8),
9485 ..Default::default()
9486 },
9487 );
9488 });
9489 cx.executor().run_until_parked();
9490 assert_eq!(
9491 server_restarts.load(atomic::Ordering::Acquire),
9492 0,
9493 "Should not restart LSP server on an unrelated change"
9494 );
9495
9496 update_test_project_settings(cx, |project_settings| {
9497 project_settings.lsp.insert(
9498 "Some other server name".into(),
9499 LspSettings {
9500 binary: None,
9501 settings: None,
9502 initialization_options: Some(json!({
9503 "some other init value": false
9504 })),
9505 },
9506 );
9507 });
9508 cx.executor().run_until_parked();
9509 assert_eq!(
9510 server_restarts.load(atomic::Ordering::Acquire),
9511 0,
9512 "Should not restart LSP server on an unrelated LSP settings change"
9513 );
9514
9515 update_test_project_settings(cx, |project_settings| {
9516 project_settings.lsp.insert(
9517 language_server_name.into(),
9518 LspSettings {
9519 binary: None,
9520 settings: None,
9521 initialization_options: Some(json!({
9522 "anotherInitValue": false
9523 })),
9524 },
9525 );
9526 });
9527 cx.executor().run_until_parked();
9528 assert_eq!(
9529 server_restarts.load(atomic::Ordering::Acquire),
9530 1,
9531 "Should restart LSP server on a related LSP settings change"
9532 );
9533
9534 update_test_project_settings(cx, |project_settings| {
9535 project_settings.lsp.insert(
9536 language_server_name.into(),
9537 LspSettings {
9538 binary: None,
9539 settings: None,
9540 initialization_options: Some(json!({
9541 "anotherInitValue": false
9542 })),
9543 },
9544 );
9545 });
9546 cx.executor().run_until_parked();
9547 assert_eq!(
9548 server_restarts.load(atomic::Ordering::Acquire),
9549 1,
9550 "Should not restart LSP server on a related LSP settings change that is the same"
9551 );
9552
9553 update_test_project_settings(cx, |project_settings| {
9554 project_settings.lsp.insert(
9555 language_server_name.into(),
9556 LspSettings {
9557 binary: None,
9558 settings: None,
9559 initialization_options: None,
9560 },
9561 );
9562 });
9563 cx.executor().run_until_parked();
9564 assert_eq!(
9565 server_restarts.load(atomic::Ordering::Acquire),
9566 2,
9567 "Should restart LSP server on another related LSP settings change"
9568 );
9569}
9570
9571#[gpui::test]
9572async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
9573 init_test(cx, |_| {});
9574
9575 let mut cx = EditorLspTestContext::new_rust(
9576 lsp::ServerCapabilities {
9577 completion_provider: Some(lsp::CompletionOptions {
9578 trigger_characters: Some(vec![".".to_string()]),
9579 resolve_provider: Some(true),
9580 ..Default::default()
9581 }),
9582 ..Default::default()
9583 },
9584 cx,
9585 )
9586 .await;
9587
9588 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9589 cx.simulate_keystroke(".");
9590 let completion_item = lsp::CompletionItem {
9591 label: "some".into(),
9592 kind: Some(lsp::CompletionItemKind::SNIPPET),
9593 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9594 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9595 kind: lsp::MarkupKind::Markdown,
9596 value: "```rust\nSome(2)\n```".to_string(),
9597 })),
9598 deprecated: Some(false),
9599 sort_text: Some("fffffff2".to_string()),
9600 filter_text: Some("some".to_string()),
9601 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9602 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9603 range: lsp::Range {
9604 start: lsp::Position {
9605 line: 0,
9606 character: 22,
9607 },
9608 end: lsp::Position {
9609 line: 0,
9610 character: 22,
9611 },
9612 },
9613 new_text: "Some(2)".to_string(),
9614 })),
9615 additional_text_edits: Some(vec![lsp::TextEdit {
9616 range: lsp::Range {
9617 start: lsp::Position {
9618 line: 0,
9619 character: 20,
9620 },
9621 end: lsp::Position {
9622 line: 0,
9623 character: 22,
9624 },
9625 },
9626 new_text: "".to_string(),
9627 }]),
9628 ..Default::default()
9629 };
9630
9631 let closure_completion_item = completion_item.clone();
9632 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9633 let task_completion_item = closure_completion_item.clone();
9634 async move {
9635 Ok(Some(lsp::CompletionResponse::Array(vec![
9636 task_completion_item,
9637 ])))
9638 }
9639 });
9640
9641 request.next().await;
9642
9643 cx.condition(|editor, _| editor.context_menu_visible())
9644 .await;
9645 let apply_additional_edits = cx.update_editor(|editor, cx| {
9646 editor
9647 .confirm_completion(&ConfirmCompletion::default(), cx)
9648 .unwrap()
9649 });
9650 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
9651
9652 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
9653 let task_completion_item = completion_item.clone();
9654 async move { Ok(task_completion_item) }
9655 })
9656 .next()
9657 .await
9658 .unwrap();
9659 apply_additional_edits.await.unwrap();
9660 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
9661}
9662
9663#[gpui::test]
9664async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
9665 init_test(cx, |_| {});
9666
9667 let mut cx = EditorLspTestContext::new(
9668 Language::new(
9669 LanguageConfig {
9670 matcher: LanguageMatcher {
9671 path_suffixes: vec!["jsx".into()],
9672 ..Default::default()
9673 },
9674 overrides: [(
9675 "element".into(),
9676 LanguageConfigOverride {
9677 word_characters: Override::Set(['-'].into_iter().collect()),
9678 ..Default::default()
9679 },
9680 )]
9681 .into_iter()
9682 .collect(),
9683 ..Default::default()
9684 },
9685 Some(tree_sitter_typescript::language_tsx()),
9686 )
9687 .with_override_query("(jsx_self_closing_element) @element")
9688 .unwrap(),
9689 lsp::ServerCapabilities {
9690 completion_provider: Some(lsp::CompletionOptions {
9691 trigger_characters: Some(vec![":".to_string()]),
9692 ..Default::default()
9693 }),
9694 ..Default::default()
9695 },
9696 cx,
9697 )
9698 .await;
9699
9700 cx.lsp
9701 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9702 Ok(Some(lsp::CompletionResponse::Array(vec![
9703 lsp::CompletionItem {
9704 label: "bg-blue".into(),
9705 ..Default::default()
9706 },
9707 lsp::CompletionItem {
9708 label: "bg-red".into(),
9709 ..Default::default()
9710 },
9711 lsp::CompletionItem {
9712 label: "bg-yellow".into(),
9713 ..Default::default()
9714 },
9715 ])))
9716 });
9717
9718 cx.set_state(r#"<p class="bgˇ" />"#);
9719
9720 // Trigger completion when typing a dash, because the dash is an extra
9721 // word character in the 'element' scope, which contains the cursor.
9722 cx.simulate_keystroke("-");
9723 cx.executor().run_until_parked();
9724 cx.update_editor(|editor, _| {
9725 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9726 assert_eq!(
9727 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9728 &["bg-red", "bg-blue", "bg-yellow"]
9729 );
9730 } else {
9731 panic!("expected completion menu to be open");
9732 }
9733 });
9734
9735 cx.simulate_keystroke("l");
9736 cx.executor().run_until_parked();
9737 cx.update_editor(|editor, _| {
9738 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9739 assert_eq!(
9740 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9741 &["bg-blue", "bg-yellow"]
9742 );
9743 } else {
9744 panic!("expected completion menu to be open");
9745 }
9746 });
9747
9748 // When filtering completions, consider the character after the '-' to
9749 // be the start of a subword.
9750 cx.set_state(r#"<p class="yelˇ" />"#);
9751 cx.simulate_keystroke("l");
9752 cx.executor().run_until_parked();
9753 cx.update_editor(|editor, _| {
9754 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9755 assert_eq!(
9756 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9757 &["bg-yellow"]
9758 );
9759 } else {
9760 panic!("expected completion menu to be open");
9761 }
9762 });
9763}
9764
9765#[gpui::test]
9766async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
9767 init_test(cx, |settings| {
9768 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
9769 FormatterList(vec![Formatter::Prettier].into()),
9770 ))
9771 });
9772
9773 let fs = FakeFs::new(cx.executor());
9774 fs.insert_file("/file.ts", Default::default()).await;
9775
9776 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
9777 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9778
9779 language_registry.add(Arc::new(Language::new(
9780 LanguageConfig {
9781 name: "TypeScript".into(),
9782 matcher: LanguageMatcher {
9783 path_suffixes: vec!["ts".to_string()],
9784 ..Default::default()
9785 },
9786 ..Default::default()
9787 },
9788 Some(tree_sitter_rust::language()),
9789 )));
9790 update_test_language_settings(cx, |settings| {
9791 settings.defaults.prettier = Some(PrettierSettings {
9792 allowed: true,
9793 ..PrettierSettings::default()
9794 });
9795 });
9796
9797 let test_plugin = "test_plugin";
9798 let _ = language_registry.register_fake_lsp_adapter(
9799 "TypeScript",
9800 FakeLspAdapter {
9801 prettier_plugins: vec![test_plugin],
9802 ..Default::default()
9803 },
9804 );
9805
9806 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
9807 let buffer = project
9808 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
9809 .await
9810 .unwrap();
9811
9812 let buffer_text = "one\ntwo\nthree\n";
9813 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9814 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9815 _ = editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
9816
9817 editor
9818 .update(cx, |editor, cx| {
9819 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
9820 })
9821 .unwrap()
9822 .await;
9823 assert_eq!(
9824 editor.update(cx, |editor, cx| editor.text(cx)),
9825 buffer_text.to_string() + prettier_format_suffix,
9826 "Test prettier formatting was not applied to the original buffer text",
9827 );
9828
9829 update_test_language_settings(cx, |settings| {
9830 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
9831 });
9832 let format = editor.update(cx, |editor, cx| {
9833 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
9834 });
9835 format.await.unwrap();
9836 assert_eq!(
9837 editor.update(cx, |editor, cx| editor.text(cx)),
9838 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
9839 "Autoformatting (via test prettier) was not applied to the original buffer text",
9840 );
9841}
9842
9843#[gpui::test]
9844async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
9845 init_test(cx, |_| {});
9846 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9847 let base_text = indoc! {r#"struct Row;
9848struct Row1;
9849struct Row2;
9850
9851struct Row4;
9852struct Row5;
9853struct Row6;
9854
9855struct Row8;
9856struct Row9;
9857struct Row10;"#};
9858
9859 // When addition hunks are not adjacent to carets, no hunk revert is performed
9860 assert_hunk_revert(
9861 indoc! {r#"struct Row;
9862 struct Row1;
9863 struct Row1.1;
9864 struct Row1.2;
9865 struct Row2;ˇ
9866
9867 struct Row4;
9868 struct Row5;
9869 struct Row6;
9870
9871 struct Row8;
9872 ˇstruct Row9;
9873 struct Row9.1;
9874 struct Row9.2;
9875 struct Row9.3;
9876 struct Row10;"#},
9877 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
9878 indoc! {r#"struct Row;
9879 struct Row1;
9880 struct Row1.1;
9881 struct Row1.2;
9882 struct Row2;ˇ
9883
9884 struct Row4;
9885 struct Row5;
9886 struct Row6;
9887
9888 struct Row8;
9889 ˇstruct Row9;
9890 struct Row9.1;
9891 struct Row9.2;
9892 struct Row9.3;
9893 struct Row10;"#},
9894 base_text,
9895 &mut cx,
9896 );
9897 // Same for selections
9898 assert_hunk_revert(
9899 indoc! {r#"struct Row;
9900 struct Row1;
9901 struct Row2;
9902 struct Row2.1;
9903 struct Row2.2;
9904 «ˇ
9905 struct Row4;
9906 struct» Row5;
9907 «struct Row6;
9908 ˇ»
9909 struct Row9.1;
9910 struct Row9.2;
9911 struct Row9.3;
9912 struct Row8;
9913 struct Row9;
9914 struct Row10;"#},
9915 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
9916 indoc! {r#"struct Row;
9917 struct Row1;
9918 struct Row2;
9919 struct Row2.1;
9920 struct Row2.2;
9921 «ˇ
9922 struct Row4;
9923 struct» Row5;
9924 «struct Row6;
9925 ˇ»
9926 struct Row9.1;
9927 struct Row9.2;
9928 struct Row9.3;
9929 struct Row8;
9930 struct Row9;
9931 struct Row10;"#},
9932 base_text,
9933 &mut cx,
9934 );
9935
9936 // When carets and selections intersect the addition hunks, those are reverted.
9937 // Adjacent carets got merged.
9938 assert_hunk_revert(
9939 indoc! {r#"struct Row;
9940 ˇ// something on the top
9941 struct Row1;
9942 struct Row2;
9943 struct Roˇw3.1;
9944 struct Row2.2;
9945 struct Row2.3;ˇ
9946
9947 struct Row4;
9948 struct ˇRow5.1;
9949 struct Row5.2;
9950 struct «Rowˇ»5.3;
9951 struct Row5;
9952 struct Row6;
9953 ˇ
9954 struct Row9.1;
9955 struct «Rowˇ»9.2;
9956 struct «ˇRow»9.3;
9957 struct Row8;
9958 struct Row9;
9959 «ˇ// something on bottom»
9960 struct Row10;"#},
9961 vec![
9962 DiffHunkStatus::Added,
9963 DiffHunkStatus::Added,
9964 DiffHunkStatus::Added,
9965 DiffHunkStatus::Added,
9966 DiffHunkStatus::Added,
9967 ],
9968 indoc! {r#"struct Row;
9969 ˇstruct Row1;
9970 struct Row2;
9971 ˇ
9972 struct Row4;
9973 ˇstruct Row5;
9974 struct Row6;
9975 ˇ
9976 ˇstruct Row8;
9977 struct Row9;
9978 ˇstruct Row10;"#},
9979 base_text,
9980 &mut cx,
9981 );
9982}
9983
9984#[gpui::test]
9985async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
9986 init_test(cx, |_| {});
9987 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9988 let base_text = indoc! {r#"struct Row;
9989struct Row1;
9990struct Row2;
9991
9992struct Row4;
9993struct Row5;
9994struct Row6;
9995
9996struct Row8;
9997struct Row9;
9998struct Row10;"#};
9999
10000 // Modification hunks behave the same as the addition ones.
10001 assert_hunk_revert(
10002 indoc! {r#"struct Row;
10003 struct Row1;
10004 struct Row33;
10005 ˇ
10006 struct Row4;
10007 struct Row5;
10008 struct Row6;
10009 ˇ
10010 struct Row99;
10011 struct Row9;
10012 struct Row10;"#},
10013 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10014 indoc! {r#"struct Row;
10015 struct Row1;
10016 struct Row33;
10017 ˇ
10018 struct Row4;
10019 struct Row5;
10020 struct Row6;
10021 ˇ
10022 struct Row99;
10023 struct Row9;
10024 struct Row10;"#},
10025 base_text,
10026 &mut cx,
10027 );
10028 assert_hunk_revert(
10029 indoc! {r#"struct Row;
10030 struct Row1;
10031 struct Row33;
10032 «ˇ
10033 struct Row4;
10034 struct» Row5;
10035 «struct Row6;
10036 ˇ»
10037 struct Row99;
10038 struct Row9;
10039 struct Row10;"#},
10040 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10041 indoc! {r#"struct Row;
10042 struct Row1;
10043 struct Row33;
10044 «ˇ
10045 struct Row4;
10046 struct» Row5;
10047 «struct Row6;
10048 ˇ»
10049 struct Row99;
10050 struct Row9;
10051 struct Row10;"#},
10052 base_text,
10053 &mut cx,
10054 );
10055
10056 assert_hunk_revert(
10057 indoc! {r#"ˇstruct Row1.1;
10058 struct Row1;
10059 «ˇstr»uct Row22;
10060
10061 struct ˇRow44;
10062 struct Row5;
10063 struct «Rˇ»ow66;ˇ
10064
10065 «struˇ»ct Row88;
10066 struct Row9;
10067 struct Row1011;ˇ"#},
10068 vec![
10069 DiffHunkStatus::Modified,
10070 DiffHunkStatus::Modified,
10071 DiffHunkStatus::Modified,
10072 DiffHunkStatus::Modified,
10073 DiffHunkStatus::Modified,
10074 DiffHunkStatus::Modified,
10075 ],
10076 indoc! {r#"struct Row;
10077 ˇstruct Row1;
10078 struct Row2;
10079 ˇ
10080 struct Row4;
10081 ˇstruct Row5;
10082 struct Row6;
10083 ˇ
10084 struct Row8;
10085 ˇstruct Row9;
10086 struct Row10;ˇ"#},
10087 base_text,
10088 &mut cx,
10089 );
10090}
10091
10092#[gpui::test]
10093async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
10094 init_test(cx, |_| {});
10095 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10096 let base_text = indoc! {r#"struct Row;
10097struct Row1;
10098struct Row2;
10099
10100struct Row4;
10101struct Row5;
10102struct Row6;
10103
10104struct Row8;
10105struct Row9;
10106struct Row10;"#};
10107
10108 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
10109 assert_hunk_revert(
10110 indoc! {r#"struct Row;
10111 struct Row2;
10112
10113 ˇstruct Row4;
10114 struct Row5;
10115 struct Row6;
10116 ˇ
10117 struct Row8;
10118 struct Row10;"#},
10119 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10120 indoc! {r#"struct Row;
10121 struct Row2;
10122
10123 ˇstruct Row4;
10124 struct Row5;
10125 struct Row6;
10126 ˇ
10127 struct Row8;
10128 struct Row10;"#},
10129 base_text,
10130 &mut cx,
10131 );
10132 assert_hunk_revert(
10133 indoc! {r#"struct Row;
10134 struct Row2;
10135
10136 «ˇstruct Row4;
10137 struct» Row5;
10138 «struct Row6;
10139 ˇ»
10140 struct Row8;
10141 struct Row10;"#},
10142 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10143 indoc! {r#"struct Row;
10144 struct Row2;
10145
10146 «ˇstruct Row4;
10147 struct» Row5;
10148 «struct Row6;
10149 ˇ»
10150 struct Row8;
10151 struct Row10;"#},
10152 base_text,
10153 &mut cx,
10154 );
10155
10156 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
10157 assert_hunk_revert(
10158 indoc! {r#"struct Row;
10159 ˇstruct Row2;
10160
10161 struct Row4;
10162 struct Row5;
10163 struct Row6;
10164
10165 struct Row8;ˇ
10166 struct Row10;"#},
10167 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10168 indoc! {r#"struct Row;
10169 struct Row1;
10170 ˇstruct Row2;
10171
10172 struct Row4;
10173 struct Row5;
10174 struct Row6;
10175
10176 struct Row8;ˇ
10177 struct Row9;
10178 struct Row10;"#},
10179 base_text,
10180 &mut cx,
10181 );
10182 assert_hunk_revert(
10183 indoc! {r#"struct Row;
10184 struct Row2«ˇ;
10185 struct Row4;
10186 struct» Row5;
10187 «struct Row6;
10188
10189 struct Row8;ˇ»
10190 struct Row10;"#},
10191 vec![
10192 DiffHunkStatus::Removed,
10193 DiffHunkStatus::Removed,
10194 DiffHunkStatus::Removed,
10195 ],
10196 indoc! {r#"struct Row;
10197 struct Row1;
10198 struct Row2«ˇ;
10199
10200 struct Row4;
10201 struct» Row5;
10202 «struct Row6;
10203
10204 struct Row8;ˇ»
10205 struct Row9;
10206 struct Row10;"#},
10207 base_text,
10208 &mut cx,
10209 );
10210}
10211
10212#[gpui::test]
10213async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
10214 init_test(cx, |_| {});
10215
10216 let cols = 4;
10217 let rows = 10;
10218 let sample_text_1 = sample_text(rows, cols, 'a');
10219 assert_eq!(
10220 sample_text_1,
10221 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10222 );
10223 let sample_text_2 = sample_text(rows, cols, 'l');
10224 assert_eq!(
10225 sample_text_2,
10226 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10227 );
10228 let sample_text_3 = sample_text(rows, cols, 'v');
10229 assert_eq!(
10230 sample_text_3,
10231 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10232 );
10233
10234 fn diff_every_buffer_row(
10235 buffer: &Model<Buffer>,
10236 sample_text: String,
10237 cols: usize,
10238 cx: &mut gpui::TestAppContext,
10239 ) {
10240 // revert first character in each row, creating one large diff hunk per buffer
10241 let is_first_char = |offset: usize| offset % cols == 0;
10242 buffer.update(cx, |buffer, cx| {
10243 buffer.set_text(
10244 sample_text
10245 .chars()
10246 .enumerate()
10247 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
10248 .collect::<String>(),
10249 cx,
10250 );
10251 buffer.set_diff_base(Some(sample_text), cx);
10252 });
10253 cx.executor().run_until_parked();
10254 }
10255
10256 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10257 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10258
10259 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10260 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10261
10262 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10263 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10264
10265 let multibuffer = cx.new_model(|cx| {
10266 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10267 multibuffer.push_excerpts(
10268 buffer_1.clone(),
10269 [
10270 ExcerptRange {
10271 context: Point::new(0, 0)..Point::new(3, 0),
10272 primary: None,
10273 },
10274 ExcerptRange {
10275 context: Point::new(5, 0)..Point::new(7, 0),
10276 primary: None,
10277 },
10278 ExcerptRange {
10279 context: Point::new(9, 0)..Point::new(10, 4),
10280 primary: None,
10281 },
10282 ],
10283 cx,
10284 );
10285 multibuffer.push_excerpts(
10286 buffer_2.clone(),
10287 [
10288 ExcerptRange {
10289 context: Point::new(0, 0)..Point::new(3, 0),
10290 primary: None,
10291 },
10292 ExcerptRange {
10293 context: Point::new(5, 0)..Point::new(7, 0),
10294 primary: None,
10295 },
10296 ExcerptRange {
10297 context: Point::new(9, 0)..Point::new(10, 4),
10298 primary: None,
10299 },
10300 ],
10301 cx,
10302 );
10303 multibuffer.push_excerpts(
10304 buffer_3.clone(),
10305 [
10306 ExcerptRange {
10307 context: Point::new(0, 0)..Point::new(3, 0),
10308 primary: None,
10309 },
10310 ExcerptRange {
10311 context: Point::new(5, 0)..Point::new(7, 0),
10312 primary: None,
10313 },
10314 ExcerptRange {
10315 context: Point::new(9, 0)..Point::new(10, 4),
10316 primary: None,
10317 },
10318 ],
10319 cx,
10320 );
10321 multibuffer
10322 });
10323
10324 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
10325 editor.update(cx, |editor, cx| {
10326 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");
10327 editor.select_all(&SelectAll, cx);
10328 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10329 });
10330 cx.executor().run_until_parked();
10331 // When all ranges are selected, all buffer hunks are reverted.
10332 editor.update(cx, |editor, cx| {
10333 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");
10334 });
10335 buffer_1.update(cx, |buffer, _| {
10336 assert_eq!(buffer.text(), sample_text_1);
10337 });
10338 buffer_2.update(cx, |buffer, _| {
10339 assert_eq!(buffer.text(), sample_text_2);
10340 });
10341 buffer_3.update(cx, |buffer, _| {
10342 assert_eq!(buffer.text(), sample_text_3);
10343 });
10344
10345 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10346 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10347 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10348 editor.update(cx, |editor, cx| {
10349 editor.change_selections(None, cx, |s| {
10350 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
10351 });
10352 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10353 });
10354 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
10355 // but not affect buffer_2 and its related excerpts.
10356 editor.update(cx, |editor, cx| {
10357 assert_eq!(
10358 editor.text(cx),
10359 "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"
10360 );
10361 });
10362 buffer_1.update(cx, |buffer, _| {
10363 assert_eq!(buffer.text(), sample_text_1);
10364 });
10365 buffer_2.update(cx, |buffer, _| {
10366 assert_eq!(
10367 buffer.text(),
10368 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
10369 );
10370 });
10371 buffer_3.update(cx, |buffer, _| {
10372 assert_eq!(
10373 buffer.text(),
10374 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
10375 );
10376 });
10377}
10378
10379#[gpui::test]
10380async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
10381 init_test(cx, |_| {});
10382
10383 let cols = 4;
10384 let rows = 10;
10385 let sample_text_1 = sample_text(rows, cols, 'a');
10386 assert_eq!(
10387 sample_text_1,
10388 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10389 );
10390 let sample_text_2 = sample_text(rows, cols, 'l');
10391 assert_eq!(
10392 sample_text_2,
10393 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10394 );
10395 let sample_text_3 = sample_text(rows, cols, 'v');
10396 assert_eq!(
10397 sample_text_3,
10398 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10399 );
10400
10401 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10402 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10403 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10404
10405 let multi_buffer = cx.new_model(|cx| {
10406 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10407 multibuffer.push_excerpts(
10408 buffer_1.clone(),
10409 [
10410 ExcerptRange {
10411 context: Point::new(0, 0)..Point::new(3, 0),
10412 primary: None,
10413 },
10414 ExcerptRange {
10415 context: Point::new(5, 0)..Point::new(7, 0),
10416 primary: None,
10417 },
10418 ExcerptRange {
10419 context: Point::new(9, 0)..Point::new(10, 4),
10420 primary: None,
10421 },
10422 ],
10423 cx,
10424 );
10425 multibuffer.push_excerpts(
10426 buffer_2.clone(),
10427 [
10428 ExcerptRange {
10429 context: Point::new(0, 0)..Point::new(3, 0),
10430 primary: None,
10431 },
10432 ExcerptRange {
10433 context: Point::new(5, 0)..Point::new(7, 0),
10434 primary: None,
10435 },
10436 ExcerptRange {
10437 context: Point::new(9, 0)..Point::new(10, 4),
10438 primary: None,
10439 },
10440 ],
10441 cx,
10442 );
10443 multibuffer.push_excerpts(
10444 buffer_3.clone(),
10445 [
10446 ExcerptRange {
10447 context: Point::new(0, 0)..Point::new(3, 0),
10448 primary: None,
10449 },
10450 ExcerptRange {
10451 context: Point::new(5, 0)..Point::new(7, 0),
10452 primary: None,
10453 },
10454 ExcerptRange {
10455 context: Point::new(9, 0)..Point::new(10, 4),
10456 primary: None,
10457 },
10458 ],
10459 cx,
10460 );
10461 multibuffer
10462 });
10463
10464 let fs = FakeFs::new(cx.executor());
10465 fs.insert_tree(
10466 "/a",
10467 json!({
10468 "main.rs": sample_text_1,
10469 "other.rs": sample_text_2,
10470 "lib.rs": sample_text_3,
10471 }),
10472 )
10473 .await;
10474 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10475 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10476 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10477 let multi_buffer_editor = cx.new_view(|cx| {
10478 Editor::new(
10479 EditorMode::Full,
10480 multi_buffer,
10481 Some(project.clone()),
10482 true,
10483 cx,
10484 )
10485 });
10486 let multibuffer_item_id = workspace
10487 .update(cx, |workspace, cx| {
10488 assert!(
10489 workspace.active_item(cx).is_none(),
10490 "active item should be None before the first item is added"
10491 );
10492 workspace.add_item_to_active_pane(
10493 Box::new(multi_buffer_editor.clone()),
10494 None,
10495 true,
10496 cx,
10497 );
10498 let active_item = workspace
10499 .active_item(cx)
10500 .expect("should have an active item after adding the multi buffer");
10501 assert!(
10502 !active_item.is_singleton(cx),
10503 "A multi buffer was expected to active after adding"
10504 );
10505 active_item.item_id()
10506 })
10507 .unwrap();
10508 cx.executor().run_until_parked();
10509
10510 multi_buffer_editor.update(cx, |editor, cx| {
10511 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
10512 editor.open_excerpts(&OpenExcerpts, cx);
10513 });
10514 cx.executor().run_until_parked();
10515 let first_item_id = workspace
10516 .update(cx, |workspace, cx| {
10517 let active_item = workspace
10518 .active_item(cx)
10519 .expect("should have an active item after navigating into the 1st buffer");
10520 let first_item_id = active_item.item_id();
10521 assert_ne!(
10522 first_item_id, multibuffer_item_id,
10523 "Should navigate into the 1st buffer and activate it"
10524 );
10525 assert!(
10526 active_item.is_singleton(cx),
10527 "New active item should be a singleton buffer"
10528 );
10529 assert_eq!(
10530 active_item
10531 .act_as::<Editor>(cx)
10532 .expect("should have navigated into an editor for the 1st buffer")
10533 .read(cx)
10534 .text(cx),
10535 sample_text_1
10536 );
10537
10538 workspace
10539 .go_back(workspace.active_pane().downgrade(), cx)
10540 .detach_and_log_err(cx);
10541
10542 first_item_id
10543 })
10544 .unwrap();
10545 cx.executor().run_until_parked();
10546 workspace
10547 .update(cx, |workspace, cx| {
10548 let active_item = workspace
10549 .active_item(cx)
10550 .expect("should have an active item after navigating back");
10551 assert_eq!(
10552 active_item.item_id(),
10553 multibuffer_item_id,
10554 "Should navigate back to the multi buffer"
10555 );
10556 assert!(!active_item.is_singleton(cx));
10557 })
10558 .unwrap();
10559
10560 multi_buffer_editor.update(cx, |editor, cx| {
10561 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
10562 s.select_ranges(Some(39..40))
10563 });
10564 editor.open_excerpts(&OpenExcerpts, cx);
10565 });
10566 cx.executor().run_until_parked();
10567 let second_item_id = workspace
10568 .update(cx, |workspace, cx| {
10569 let active_item = workspace
10570 .active_item(cx)
10571 .expect("should have an active item after navigating into the 2nd buffer");
10572 let second_item_id = active_item.item_id();
10573 assert_ne!(
10574 second_item_id, multibuffer_item_id,
10575 "Should navigate away from the multibuffer"
10576 );
10577 assert_ne!(
10578 second_item_id, first_item_id,
10579 "Should navigate into the 2nd buffer and activate it"
10580 );
10581 assert!(
10582 active_item.is_singleton(cx),
10583 "New active item should be a singleton buffer"
10584 );
10585 assert_eq!(
10586 active_item
10587 .act_as::<Editor>(cx)
10588 .expect("should have navigated into an editor")
10589 .read(cx)
10590 .text(cx),
10591 sample_text_2
10592 );
10593
10594 workspace
10595 .go_back(workspace.active_pane().downgrade(), cx)
10596 .detach_and_log_err(cx);
10597
10598 second_item_id
10599 })
10600 .unwrap();
10601 cx.executor().run_until_parked();
10602 workspace
10603 .update(cx, |workspace, cx| {
10604 let active_item = workspace
10605 .active_item(cx)
10606 .expect("should have an active item after navigating back from the 2nd buffer");
10607 assert_eq!(
10608 active_item.item_id(),
10609 multibuffer_item_id,
10610 "Should navigate back from the 2nd buffer to the multi buffer"
10611 );
10612 assert!(!active_item.is_singleton(cx));
10613 })
10614 .unwrap();
10615
10616 multi_buffer_editor.update(cx, |editor, cx| {
10617 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
10618 s.select_ranges(Some(60..70))
10619 });
10620 editor.open_excerpts(&OpenExcerpts, cx);
10621 });
10622 cx.executor().run_until_parked();
10623 workspace
10624 .update(cx, |workspace, cx| {
10625 let active_item = workspace
10626 .active_item(cx)
10627 .expect("should have an active item after navigating into the 3rd buffer");
10628 let third_item_id = active_item.item_id();
10629 assert_ne!(
10630 third_item_id, multibuffer_item_id,
10631 "Should navigate into the 3rd buffer and activate it"
10632 );
10633 assert_ne!(third_item_id, first_item_id);
10634 assert_ne!(third_item_id, second_item_id);
10635 assert!(
10636 active_item.is_singleton(cx),
10637 "New active item should be a singleton buffer"
10638 );
10639 assert_eq!(
10640 active_item
10641 .act_as::<Editor>(cx)
10642 .expect("should have navigated into an editor")
10643 .read(cx)
10644 .text(cx),
10645 sample_text_3
10646 );
10647
10648 workspace
10649 .go_back(workspace.active_pane().downgrade(), cx)
10650 .detach_and_log_err(cx);
10651 })
10652 .unwrap();
10653 cx.executor().run_until_parked();
10654 workspace
10655 .update(cx, |workspace, cx| {
10656 let active_item = workspace
10657 .active_item(cx)
10658 .expect("should have an active item after navigating back from the 3rd buffer");
10659 assert_eq!(
10660 active_item.item_id(),
10661 multibuffer_item_id,
10662 "Should navigate back from the 3rd buffer to the multi buffer"
10663 );
10664 assert!(!active_item.is_singleton(cx));
10665 })
10666 .unwrap();
10667}
10668
10669#[gpui::test]
10670async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10671 init_test(cx, |_| {});
10672
10673 let mut cx = EditorTestContext::new(cx).await;
10674
10675 let diff_base = r#"
10676 use some::mod;
10677
10678 const A: u32 = 42;
10679
10680 fn main() {
10681 println!("hello");
10682
10683 println!("world");
10684 }
10685 "#
10686 .unindent();
10687
10688 cx.set_state(
10689 &r#"
10690 use some::modified;
10691
10692 ˇ
10693 fn main() {
10694 println!("hello there");
10695
10696 println!("around the");
10697 println!("world");
10698 }
10699 "#
10700 .unindent(),
10701 );
10702
10703 cx.set_diff_base(Some(&diff_base));
10704 executor.run_until_parked();
10705 let unexpanded_hunks = vec![
10706 (
10707 "use some::mod;\n".to_string(),
10708 DiffHunkStatus::Modified,
10709 DisplayRow(0)..DisplayRow(1),
10710 ),
10711 (
10712 "const A: u32 = 42;\n".to_string(),
10713 DiffHunkStatus::Removed,
10714 DisplayRow(2)..DisplayRow(2),
10715 ),
10716 (
10717 " println!(\"hello\");\n".to_string(),
10718 DiffHunkStatus::Modified,
10719 DisplayRow(4)..DisplayRow(5),
10720 ),
10721 (
10722 "".to_string(),
10723 DiffHunkStatus::Added,
10724 DisplayRow(6)..DisplayRow(7),
10725 ),
10726 ];
10727 cx.update_editor(|editor, cx| {
10728 let snapshot = editor.snapshot(cx);
10729 let all_hunks = editor_hunks(editor, &snapshot, cx);
10730 assert_eq!(all_hunks, unexpanded_hunks);
10731 });
10732
10733 cx.update_editor(|editor, cx| {
10734 for _ in 0..4 {
10735 editor.go_to_hunk(&GoToHunk, cx);
10736 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10737 }
10738 });
10739 executor.run_until_parked();
10740 cx.assert_editor_state(
10741 &r#"
10742 use some::modified;
10743
10744 ˇ
10745 fn main() {
10746 println!("hello there");
10747
10748 println!("around the");
10749 println!("world");
10750 }
10751 "#
10752 .unindent(),
10753 );
10754 cx.update_editor(|editor, cx| {
10755 let snapshot = editor.snapshot(cx);
10756 let all_hunks = editor_hunks(editor, &snapshot, cx);
10757 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10758 assert_eq!(
10759 expanded_hunks_background_highlights(editor, cx),
10760 vec![DisplayRow(1)..=DisplayRow(1), DisplayRow(7)..=DisplayRow(7), DisplayRow(9)..=DisplayRow(9)],
10761 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
10762 );
10763 assert_eq!(
10764 all_hunks,
10765 vec![
10766 ("use some::mod;\n".to_string(), DiffHunkStatus::Modified, DisplayRow(1)..DisplayRow(2)),
10767 ("const A: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(4)..DisplayRow(4)),
10768 (" println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(7)..DisplayRow(8)),
10769 ("".to_string(), DiffHunkStatus::Added, DisplayRow(9)..DisplayRow(10)),
10770 ],
10771 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
10772 (from modified and removed hunks)"
10773 );
10774 assert_eq!(
10775 all_hunks, all_expanded_hunks,
10776 "Editor hunks should not change and all be expanded"
10777 );
10778 });
10779
10780 cx.update_editor(|editor, cx| {
10781 editor.cancel(&Cancel, cx);
10782
10783 let snapshot = editor.snapshot(cx);
10784 let all_hunks = editor_hunks(editor, &snapshot, cx);
10785 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
10786 assert_eq!(
10787 expanded_hunks_background_highlights(editor, cx),
10788 Vec::new(),
10789 "After cancelling in editor, no git highlights should be left"
10790 );
10791 assert_eq!(
10792 all_expanded_hunks,
10793 Vec::new(),
10794 "After cancelling in editor, no hunks should be expanded"
10795 );
10796 assert_eq!(
10797 all_hunks, unexpanded_hunks,
10798 "After cancelling in editor, regular hunks' coordinates should get back to normal"
10799 );
10800 });
10801}
10802
10803#[gpui::test]
10804async fn test_toggled_diff_base_change(
10805 executor: BackgroundExecutor,
10806 cx: &mut gpui::TestAppContext,
10807) {
10808 init_test(cx, |_| {});
10809
10810 let mut cx = EditorTestContext::new(cx).await;
10811
10812 let diff_base = r#"
10813 use some::mod1;
10814 use some::mod2;
10815
10816 const A: u32 = 42;
10817 const B: u32 = 42;
10818 const C: u32 = 42;
10819
10820 fn main(ˇ) {
10821 println!("hello");
10822
10823 println!("world");
10824 }
10825 "#
10826 .unindent();
10827
10828 cx.set_state(
10829 &r#"
10830 use some::mod2;
10831
10832 const A: u32 = 42;
10833 const C: u32 = 42;
10834
10835 fn main(ˇ) {
10836 //println!("hello");
10837
10838 println!("world");
10839 //
10840 //
10841 }
10842 "#
10843 .unindent(),
10844 );
10845
10846 cx.set_diff_base(Some(&diff_base));
10847 executor.run_until_parked();
10848 cx.update_editor(|editor, cx| {
10849 let snapshot = editor.snapshot(cx);
10850 let all_hunks = editor_hunks(editor, &snapshot, cx);
10851 assert_eq!(
10852 all_hunks,
10853 vec![
10854 (
10855 "use some::mod1;\n".to_string(),
10856 DiffHunkStatus::Removed,
10857 DisplayRow(0)..DisplayRow(0)
10858 ),
10859 (
10860 "const B: u32 = 42;\n".to_string(),
10861 DiffHunkStatus::Removed,
10862 DisplayRow(3)..DisplayRow(3)
10863 ),
10864 (
10865 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
10866 DiffHunkStatus::Modified,
10867 DisplayRow(5)..DisplayRow(7)
10868 ),
10869 (
10870 "".to_string(),
10871 DiffHunkStatus::Added,
10872 DisplayRow(9)..DisplayRow(11)
10873 ),
10874 ]
10875 );
10876 });
10877
10878 cx.update_editor(|editor, cx| {
10879 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10880 });
10881 executor.run_until_parked();
10882 cx.assert_editor_state(
10883 &r#"
10884 use some::mod2;
10885
10886 const A: u32 = 42;
10887 const C: u32 = 42;
10888
10889 fn main(ˇ) {
10890 //println!("hello");
10891
10892 println!("world");
10893 //
10894 //
10895 }
10896 "#
10897 .unindent(),
10898 );
10899 cx.update_editor(|editor, cx| {
10900 let snapshot = editor.snapshot(cx);
10901 let all_hunks = editor_hunks(editor, &snapshot, cx);
10902 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10903 assert_eq!(
10904 expanded_hunks_background_highlights(editor, cx),
10905 vec![DisplayRow(9)..=DisplayRow(10), DisplayRow(13)..=DisplayRow(14)],
10906 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
10907 );
10908 assert_eq!(
10909 all_hunks,
10910 vec![
10911 ("use some::mod1;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(1)..DisplayRow(1)),
10912 ("const B: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(5)..DisplayRow(5)),
10913 ("fn main(ˇ) {\n println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(9)..DisplayRow(11)),
10914 ("".to_string(), DiffHunkStatus::Added, DisplayRow(13)..DisplayRow(15)),
10915 ],
10916 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
10917 (from modified and removed hunks)"
10918 );
10919 assert_eq!(
10920 all_hunks, all_expanded_hunks,
10921 "Editor hunks should not change and all be expanded"
10922 );
10923 });
10924
10925 cx.set_diff_base(Some("new diff base!"));
10926 executor.run_until_parked();
10927
10928 cx.update_editor(|editor, cx| {
10929 let snapshot = editor.snapshot(cx);
10930 let all_hunks = editor_hunks(editor, &snapshot, cx);
10931 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
10932 assert_eq!(
10933 expanded_hunks_background_highlights(editor, cx),
10934 Vec::new(),
10935 "After diff base is changed, old git highlights should be removed"
10936 );
10937 assert_eq!(
10938 all_expanded_hunks,
10939 Vec::new(),
10940 "After diff base is changed, old git hunk expansions should be removed"
10941 );
10942 assert_eq!(
10943 all_hunks,
10944 vec![(
10945 "new diff base!".to_string(),
10946 DiffHunkStatus::Modified,
10947 DisplayRow(0)..snapshot.display_snapshot.max_point().row()
10948 )],
10949 "After diff base is changed, hunks should update"
10950 );
10951 });
10952}
10953
10954#[gpui::test]
10955async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10956 init_test(cx, |_| {});
10957
10958 let mut cx = EditorTestContext::new(cx).await;
10959
10960 let diff_base = r#"
10961 use some::mod1;
10962 use some::mod2;
10963
10964 const A: u32 = 42;
10965 const B: u32 = 42;
10966 const C: u32 = 42;
10967
10968 fn main(ˇ) {
10969 println!("hello");
10970
10971 println!("world");
10972 }
10973
10974 fn another() {
10975 println!("another");
10976 }
10977
10978 fn another2() {
10979 println!("another2");
10980 }
10981 "#
10982 .unindent();
10983
10984 cx.set_state(
10985 &r#"
10986 «use some::mod2;
10987
10988 const A: u32 = 42;
10989 const C: u32 = 42;
10990
10991 fn main() {
10992 //println!("hello");
10993
10994 println!("world");
10995 //
10996 //ˇ»
10997 }
10998
10999 fn another() {
11000 println!("another");
11001 println!("another");
11002 }
11003
11004 println!("another2");
11005 }
11006 "#
11007 .unindent(),
11008 );
11009
11010 cx.set_diff_base(Some(&diff_base));
11011 executor.run_until_parked();
11012 cx.update_editor(|editor, cx| {
11013 let snapshot = editor.snapshot(cx);
11014 let all_hunks = editor_hunks(editor, &snapshot, cx);
11015 assert_eq!(
11016 all_hunks,
11017 vec![
11018 (
11019 "use some::mod1;\n".to_string(),
11020 DiffHunkStatus::Removed,
11021 DisplayRow(0)..DisplayRow(0)
11022 ),
11023 (
11024 "const B: u32 = 42;\n".to_string(),
11025 DiffHunkStatus::Removed,
11026 DisplayRow(3)..DisplayRow(3)
11027 ),
11028 (
11029 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11030 DiffHunkStatus::Modified,
11031 DisplayRow(5)..DisplayRow(7)
11032 ),
11033 (
11034 "".to_string(),
11035 DiffHunkStatus::Added,
11036 DisplayRow(9)..DisplayRow(11)
11037 ),
11038 (
11039 "".to_string(),
11040 DiffHunkStatus::Added,
11041 DisplayRow(15)..DisplayRow(16)
11042 ),
11043 (
11044 "fn another2() {\n".to_string(),
11045 DiffHunkStatus::Removed,
11046 DisplayRow(18)..DisplayRow(18)
11047 ),
11048 ]
11049 );
11050 });
11051
11052 cx.update_editor(|editor, cx| {
11053 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11054 });
11055 executor.run_until_parked();
11056 cx.assert_editor_state(
11057 &r#"
11058 «use some::mod2;
11059
11060 const A: u32 = 42;
11061 const C: u32 = 42;
11062
11063 fn main() {
11064 //println!("hello");
11065
11066 println!("world");
11067 //
11068 //ˇ»
11069 }
11070
11071 fn another() {
11072 println!("another");
11073 println!("another");
11074 }
11075
11076 println!("another2");
11077 }
11078 "#
11079 .unindent(),
11080 );
11081 cx.update_editor(|editor, cx| {
11082 let snapshot = editor.snapshot(cx);
11083 let all_hunks = editor_hunks(editor, &snapshot, cx);
11084 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11085 assert_eq!(
11086 expanded_hunks_background_highlights(editor, cx),
11087 vec![
11088 DisplayRow(9)..=DisplayRow(10),
11089 DisplayRow(13)..=DisplayRow(14),
11090 DisplayRow(19)..=DisplayRow(19)
11091 ]
11092 );
11093 assert_eq!(
11094 all_hunks,
11095 vec![
11096 (
11097 "use some::mod1;\n".to_string(),
11098 DiffHunkStatus::Removed,
11099 DisplayRow(1)..DisplayRow(1)
11100 ),
11101 (
11102 "const B: u32 = 42;\n".to_string(),
11103 DiffHunkStatus::Removed,
11104 DisplayRow(5)..DisplayRow(5)
11105 ),
11106 (
11107 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11108 DiffHunkStatus::Modified,
11109 DisplayRow(9)..DisplayRow(11)
11110 ),
11111 (
11112 "".to_string(),
11113 DiffHunkStatus::Added,
11114 DisplayRow(13)..DisplayRow(15)
11115 ),
11116 (
11117 "".to_string(),
11118 DiffHunkStatus::Added,
11119 DisplayRow(19)..DisplayRow(20)
11120 ),
11121 (
11122 "fn another2() {\n".to_string(),
11123 DiffHunkStatus::Removed,
11124 DisplayRow(23)..DisplayRow(23)
11125 ),
11126 ],
11127 );
11128 assert_eq!(all_hunks, all_expanded_hunks);
11129 });
11130
11131 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
11132 cx.executor().run_until_parked();
11133 cx.assert_editor_state(
11134 &r#"
11135 «use some::mod2;
11136
11137 const A: u32 = 42;
11138 const C: u32 = 42;
11139
11140 fn main() {
11141 //println!("hello");
11142
11143 println!("world");
11144 //
11145 //ˇ»
11146 }
11147
11148 fn another() {
11149 println!("another");
11150 println!("another");
11151 }
11152
11153 println!("another2");
11154 }
11155 "#
11156 .unindent(),
11157 );
11158 cx.update_editor(|editor, cx| {
11159 let snapshot = editor.snapshot(cx);
11160 let all_hunks = editor_hunks(editor, &snapshot, cx);
11161 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11162 assert_eq!(
11163 expanded_hunks_background_highlights(editor, cx),
11164 vec![DisplayRow(0)..=DisplayRow(0), DisplayRow(5)..=DisplayRow(5)],
11165 "Only one hunk is left not folded, its highlight should be visible"
11166 );
11167 assert_eq!(
11168 all_hunks,
11169 vec![
11170 (
11171 "use some::mod1;\n".to_string(),
11172 DiffHunkStatus::Removed,
11173 DisplayRow(0)..DisplayRow(0)
11174 ),
11175 (
11176 "const B: u32 = 42;\n".to_string(),
11177 DiffHunkStatus::Removed,
11178 DisplayRow(0)..DisplayRow(0)
11179 ),
11180 (
11181 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11182 DiffHunkStatus::Modified,
11183 DisplayRow(0)..DisplayRow(0)
11184 ),
11185 (
11186 "".to_string(),
11187 DiffHunkStatus::Added,
11188 DisplayRow(0)..DisplayRow(1)
11189 ),
11190 (
11191 "".to_string(),
11192 DiffHunkStatus::Added,
11193 DisplayRow(5)..DisplayRow(6)
11194 ),
11195 (
11196 "fn another2() {\n".to_string(),
11197 DiffHunkStatus::Removed,
11198 DisplayRow(9)..DisplayRow(9)
11199 ),
11200 ],
11201 "Hunk list should still return shifted folded hunks"
11202 );
11203 assert_eq!(
11204 all_expanded_hunks,
11205 vec![
11206 (
11207 "".to_string(),
11208 DiffHunkStatus::Added,
11209 DisplayRow(5)..DisplayRow(6)
11210 ),
11211 (
11212 "fn another2() {\n".to_string(),
11213 DiffHunkStatus::Removed,
11214 DisplayRow(9)..DisplayRow(9)
11215 ),
11216 ],
11217 "Only non-folded hunks should be left expanded"
11218 );
11219 });
11220
11221 cx.update_editor(|editor, cx| {
11222 editor.select_all(&SelectAll, cx);
11223 editor.unfold_lines(&UnfoldLines, cx);
11224 });
11225 cx.executor().run_until_parked();
11226 cx.assert_editor_state(
11227 &r#"
11228 «use some::mod2;
11229
11230 const A: u32 = 42;
11231 const C: u32 = 42;
11232
11233 fn main() {
11234 //println!("hello");
11235
11236 println!("world");
11237 //
11238 //
11239 }
11240
11241 fn another() {
11242 println!("another");
11243 println!("another");
11244 }
11245
11246 println!("another2");
11247 }
11248 ˇ»"#
11249 .unindent(),
11250 );
11251 cx.update_editor(|editor, cx| {
11252 let snapshot = editor.snapshot(cx);
11253 let all_hunks = editor_hunks(editor, &snapshot, cx);
11254 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11255 assert_eq!(
11256 expanded_hunks_background_highlights(editor, cx),
11257 vec![
11258 DisplayRow(9)..=DisplayRow(10),
11259 DisplayRow(13)..=DisplayRow(14),
11260 DisplayRow(19)..=DisplayRow(19)
11261 ],
11262 "After unfolding, all hunk diffs should be visible again"
11263 );
11264 assert_eq!(
11265 all_hunks,
11266 vec![
11267 (
11268 "use some::mod1;\n".to_string(),
11269 DiffHunkStatus::Removed,
11270 DisplayRow(1)..DisplayRow(1)
11271 ),
11272 (
11273 "const B: u32 = 42;\n".to_string(),
11274 DiffHunkStatus::Removed,
11275 DisplayRow(5)..DisplayRow(5)
11276 ),
11277 (
11278 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11279 DiffHunkStatus::Modified,
11280 DisplayRow(9)..DisplayRow(11)
11281 ),
11282 (
11283 "".to_string(),
11284 DiffHunkStatus::Added,
11285 DisplayRow(13)..DisplayRow(15)
11286 ),
11287 (
11288 "".to_string(),
11289 DiffHunkStatus::Added,
11290 DisplayRow(19)..DisplayRow(20)
11291 ),
11292 (
11293 "fn another2() {\n".to_string(),
11294 DiffHunkStatus::Removed,
11295 DisplayRow(23)..DisplayRow(23)
11296 ),
11297 ],
11298 );
11299 assert_eq!(all_hunks, all_expanded_hunks);
11300 });
11301}
11302
11303#[gpui::test]
11304async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
11305 init_test(cx, |_| {});
11306
11307 let cols = 4;
11308 let rows = 10;
11309 let sample_text_1 = sample_text(rows, cols, 'a');
11310 assert_eq!(
11311 sample_text_1,
11312 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11313 );
11314 let modified_sample_text_1 = "aaaa\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
11315 let sample_text_2 = sample_text(rows, cols, 'l');
11316 assert_eq!(
11317 sample_text_2,
11318 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11319 );
11320 let modified_sample_text_2 = "llll\nmmmm\n1n1n1n1n1\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
11321 let sample_text_3 = sample_text(rows, cols, 'v');
11322 assert_eq!(
11323 sample_text_3,
11324 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11325 );
11326 let modified_sample_text_3 =
11327 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n@@@@\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
11328 let buffer_1 = cx.new_model(|cx| {
11329 let mut buffer = Buffer::local(modified_sample_text_1.to_string(), cx);
11330 buffer.set_diff_base(Some(sample_text_1.clone()), cx);
11331 buffer
11332 });
11333 let buffer_2 = cx.new_model(|cx| {
11334 let mut buffer = Buffer::local(modified_sample_text_2.to_string(), cx);
11335 buffer.set_diff_base(Some(sample_text_2.clone()), cx);
11336 buffer
11337 });
11338 let buffer_3 = cx.new_model(|cx| {
11339 let mut buffer = Buffer::local(modified_sample_text_3.to_string(), cx);
11340 buffer.set_diff_base(Some(sample_text_3.clone()), cx);
11341 buffer
11342 });
11343
11344 let multi_buffer = cx.new_model(|cx| {
11345 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
11346 multibuffer.push_excerpts(
11347 buffer_1.clone(),
11348 [
11349 ExcerptRange {
11350 context: Point::new(0, 0)..Point::new(3, 0),
11351 primary: None,
11352 },
11353 ExcerptRange {
11354 context: Point::new(5, 0)..Point::new(7, 0),
11355 primary: None,
11356 },
11357 ExcerptRange {
11358 context: Point::new(9, 0)..Point::new(10, 4),
11359 primary: None,
11360 },
11361 ],
11362 cx,
11363 );
11364 multibuffer.push_excerpts(
11365 buffer_2.clone(),
11366 [
11367 ExcerptRange {
11368 context: Point::new(0, 0)..Point::new(3, 0),
11369 primary: None,
11370 },
11371 ExcerptRange {
11372 context: Point::new(5, 0)..Point::new(7, 0),
11373 primary: None,
11374 },
11375 ExcerptRange {
11376 context: Point::new(9, 0)..Point::new(10, 4),
11377 primary: None,
11378 },
11379 ],
11380 cx,
11381 );
11382 multibuffer.push_excerpts(
11383 buffer_3.clone(),
11384 [
11385 ExcerptRange {
11386 context: Point::new(0, 0)..Point::new(3, 0),
11387 primary: None,
11388 },
11389 ExcerptRange {
11390 context: Point::new(5, 0)..Point::new(7, 0),
11391 primary: None,
11392 },
11393 ExcerptRange {
11394 context: Point::new(9, 0)..Point::new(10, 4),
11395 primary: None,
11396 },
11397 ],
11398 cx,
11399 );
11400 multibuffer
11401 });
11402
11403 let fs = FakeFs::new(cx.executor());
11404 fs.insert_tree(
11405 "/a",
11406 json!({
11407 "main.rs": modified_sample_text_1,
11408 "other.rs": modified_sample_text_2,
11409 "lib.rs": modified_sample_text_3,
11410 }),
11411 )
11412 .await;
11413
11414 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11415 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11416 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11417 let multi_buffer_editor = cx.new_view(|cx| {
11418 Editor::new(
11419 EditorMode::Full,
11420 multi_buffer,
11421 Some(project.clone()),
11422 true,
11423 cx,
11424 )
11425 });
11426 cx.executor().run_until_parked();
11427
11428 let expected_all_hunks = vec![
11429 (
11430 "bbbb\n".to_string(),
11431 DiffHunkStatus::Removed,
11432 DisplayRow(4)..DisplayRow(4),
11433 ),
11434 (
11435 "nnnn\n".to_string(),
11436 DiffHunkStatus::Modified,
11437 DisplayRow(21)..DisplayRow(22),
11438 ),
11439 (
11440 "".to_string(),
11441 DiffHunkStatus::Added,
11442 DisplayRow(41)..DisplayRow(42),
11443 ),
11444 ];
11445 let expected_all_hunks_shifted = vec![
11446 (
11447 "bbbb\n".to_string(),
11448 DiffHunkStatus::Removed,
11449 DisplayRow(5)..DisplayRow(5),
11450 ),
11451 (
11452 "nnnn\n".to_string(),
11453 DiffHunkStatus::Modified,
11454 DisplayRow(23)..DisplayRow(24),
11455 ),
11456 (
11457 "".to_string(),
11458 DiffHunkStatus::Added,
11459 DisplayRow(43)..DisplayRow(44),
11460 ),
11461 ];
11462
11463 multi_buffer_editor.update(cx, |editor, cx| {
11464 let snapshot = editor.snapshot(cx);
11465 let all_hunks = editor_hunks(editor, &snapshot, cx);
11466 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11467 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11468 assert_eq!(all_hunks, expected_all_hunks);
11469 assert_eq!(all_expanded_hunks, Vec::new());
11470 });
11471
11472 multi_buffer_editor.update(cx, |editor, cx| {
11473 editor.select_all(&SelectAll, cx);
11474 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11475 });
11476 cx.executor().run_until_parked();
11477 multi_buffer_editor.update(cx, |editor, cx| {
11478 let snapshot = editor.snapshot(cx);
11479 let all_hunks = editor_hunks(editor, &snapshot, cx);
11480 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11481 assert_eq!(
11482 expanded_hunks_background_highlights(editor, cx),
11483 vec![
11484 DisplayRow(23)..=DisplayRow(23),
11485 DisplayRow(43)..=DisplayRow(43)
11486 ],
11487 );
11488 assert_eq!(all_hunks, expected_all_hunks_shifted);
11489 assert_eq!(all_hunks, all_expanded_hunks);
11490 });
11491
11492 multi_buffer_editor.update(cx, |editor, cx| {
11493 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11494 });
11495 cx.executor().run_until_parked();
11496 multi_buffer_editor.update(cx, |editor, cx| {
11497 let snapshot = editor.snapshot(cx);
11498 let all_hunks = editor_hunks(editor, &snapshot, cx);
11499 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11500 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11501 assert_eq!(all_hunks, expected_all_hunks);
11502 assert_eq!(all_expanded_hunks, Vec::new());
11503 });
11504
11505 multi_buffer_editor.update(cx, |editor, cx| {
11506 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11507 });
11508 cx.executor().run_until_parked();
11509 multi_buffer_editor.update(cx, |editor, cx| {
11510 let snapshot = editor.snapshot(cx);
11511 let all_hunks = editor_hunks(editor, &snapshot, cx);
11512 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11513 assert_eq!(
11514 expanded_hunks_background_highlights(editor, cx),
11515 vec![
11516 DisplayRow(23)..=DisplayRow(23),
11517 DisplayRow(43)..=DisplayRow(43)
11518 ],
11519 );
11520 assert_eq!(all_hunks, expected_all_hunks_shifted);
11521 assert_eq!(all_hunks, all_expanded_hunks);
11522 });
11523
11524 multi_buffer_editor.update(cx, |editor, cx| {
11525 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11526 });
11527 cx.executor().run_until_parked();
11528 multi_buffer_editor.update(cx, |editor, cx| {
11529 let snapshot = editor.snapshot(cx);
11530 let all_hunks = editor_hunks(editor, &snapshot, cx);
11531 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11532 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11533 assert_eq!(all_hunks, expected_all_hunks);
11534 assert_eq!(all_expanded_hunks, Vec::new());
11535 });
11536}
11537
11538#[gpui::test]
11539async fn test_edits_around_toggled_additions(
11540 executor: BackgroundExecutor,
11541 cx: &mut gpui::TestAppContext,
11542) {
11543 init_test(cx, |_| {});
11544
11545 let mut cx = EditorTestContext::new(cx).await;
11546
11547 let diff_base = r#"
11548 use some::mod1;
11549 use some::mod2;
11550
11551 const A: u32 = 42;
11552
11553 fn main() {
11554 println!("hello");
11555
11556 println!("world");
11557 }
11558 "#
11559 .unindent();
11560 executor.run_until_parked();
11561 cx.set_state(
11562 &r#"
11563 use some::mod1;
11564 use some::mod2;
11565
11566 const A: u32 = 42;
11567 const B: u32 = 42;
11568 const C: u32 = 42;
11569 ˇ
11570
11571 fn main() {
11572 println!("hello");
11573
11574 println!("world");
11575 }
11576 "#
11577 .unindent(),
11578 );
11579
11580 cx.set_diff_base(Some(&diff_base));
11581 executor.run_until_parked();
11582 cx.update_editor(|editor, cx| {
11583 let snapshot = editor.snapshot(cx);
11584 let all_hunks = editor_hunks(editor, &snapshot, cx);
11585 assert_eq!(
11586 all_hunks,
11587 vec![(
11588 "".to_string(),
11589 DiffHunkStatus::Added,
11590 DisplayRow(4)..DisplayRow(7)
11591 )]
11592 );
11593 });
11594 cx.update_editor(|editor, cx| {
11595 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11596 });
11597 executor.run_until_parked();
11598 cx.assert_editor_state(
11599 &r#"
11600 use some::mod1;
11601 use some::mod2;
11602
11603 const A: u32 = 42;
11604 const B: u32 = 42;
11605 const C: u32 = 42;
11606 ˇ
11607
11608 fn main() {
11609 println!("hello");
11610
11611 println!("world");
11612 }
11613 "#
11614 .unindent(),
11615 );
11616 cx.update_editor(|editor, cx| {
11617 let snapshot = editor.snapshot(cx);
11618 let all_hunks = editor_hunks(editor, &snapshot, cx);
11619 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11620 assert_eq!(
11621 all_hunks,
11622 vec![(
11623 "".to_string(),
11624 DiffHunkStatus::Added,
11625 DisplayRow(4)..DisplayRow(7)
11626 )]
11627 );
11628 assert_eq!(
11629 expanded_hunks_background_highlights(editor, cx),
11630 vec![DisplayRow(4)..=DisplayRow(6)]
11631 );
11632 assert_eq!(all_hunks, all_expanded_hunks);
11633 });
11634
11635 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
11636 executor.run_until_parked();
11637 cx.assert_editor_state(
11638 &r#"
11639 use some::mod1;
11640 use some::mod2;
11641
11642 const A: u32 = 42;
11643 const B: u32 = 42;
11644 const C: u32 = 42;
11645 const D: u32 = 42;
11646 ˇ
11647
11648 fn main() {
11649 println!("hello");
11650
11651 println!("world");
11652 }
11653 "#
11654 .unindent(),
11655 );
11656 cx.update_editor(|editor, cx| {
11657 let snapshot = editor.snapshot(cx);
11658 let all_hunks = editor_hunks(editor, &snapshot, cx);
11659 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11660 assert_eq!(
11661 all_hunks,
11662 vec![(
11663 "".to_string(),
11664 DiffHunkStatus::Added,
11665 DisplayRow(4)..DisplayRow(8)
11666 )]
11667 );
11668 assert_eq!(
11669 expanded_hunks_background_highlights(editor, cx),
11670 vec![DisplayRow(4)..=DisplayRow(6)],
11671 "Edited hunk should have one more line added"
11672 );
11673 assert_eq!(
11674 all_hunks, all_expanded_hunks,
11675 "Expanded hunk should also grow with the addition"
11676 );
11677 });
11678
11679 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
11680 executor.run_until_parked();
11681 cx.assert_editor_state(
11682 &r#"
11683 use some::mod1;
11684 use some::mod2;
11685
11686 const A: u32 = 42;
11687 const B: u32 = 42;
11688 const C: u32 = 42;
11689 const D: u32 = 42;
11690 const E: u32 = 42;
11691 ˇ
11692
11693 fn main() {
11694 println!("hello");
11695
11696 println!("world");
11697 }
11698 "#
11699 .unindent(),
11700 );
11701 cx.update_editor(|editor, cx| {
11702 let snapshot = editor.snapshot(cx);
11703 let all_hunks = editor_hunks(editor, &snapshot, cx);
11704 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11705 assert_eq!(
11706 all_hunks,
11707 vec![(
11708 "".to_string(),
11709 DiffHunkStatus::Added,
11710 DisplayRow(4)..DisplayRow(9)
11711 )]
11712 );
11713 assert_eq!(
11714 expanded_hunks_background_highlights(editor, cx),
11715 vec![DisplayRow(4)..=DisplayRow(6)],
11716 "Edited hunk should have one more line added"
11717 );
11718 assert_eq!(all_hunks, all_expanded_hunks);
11719 });
11720
11721 cx.update_editor(|editor, cx| {
11722 editor.move_up(&MoveUp, cx);
11723 editor.delete_line(&DeleteLine, cx);
11724 });
11725 executor.run_until_parked();
11726 cx.assert_editor_state(
11727 &r#"
11728 use some::mod1;
11729 use some::mod2;
11730
11731 const A: u32 = 42;
11732 const B: u32 = 42;
11733 const C: u32 = 42;
11734 const D: u32 = 42;
11735 ˇ
11736
11737 fn main() {
11738 println!("hello");
11739
11740 println!("world");
11741 }
11742 "#
11743 .unindent(),
11744 );
11745 cx.update_editor(|editor, cx| {
11746 let snapshot = editor.snapshot(cx);
11747 let all_hunks = editor_hunks(editor, &snapshot, cx);
11748 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11749 assert_eq!(
11750 all_hunks,
11751 vec![(
11752 "".to_string(),
11753 DiffHunkStatus::Added,
11754 DisplayRow(4)..DisplayRow(8)
11755 )]
11756 );
11757 assert_eq!(
11758 expanded_hunks_background_highlights(editor, cx),
11759 vec![DisplayRow(4)..=DisplayRow(6)],
11760 "Deleting a line should shrint the hunk"
11761 );
11762 assert_eq!(
11763 all_hunks, all_expanded_hunks,
11764 "Expanded hunk should also shrink with the addition"
11765 );
11766 });
11767
11768 cx.update_editor(|editor, cx| {
11769 editor.move_up(&MoveUp, cx);
11770 editor.delete_line(&DeleteLine, cx);
11771 editor.move_up(&MoveUp, cx);
11772 editor.delete_line(&DeleteLine, cx);
11773 editor.move_up(&MoveUp, cx);
11774 editor.delete_line(&DeleteLine, cx);
11775 });
11776 executor.run_until_parked();
11777 cx.assert_editor_state(
11778 &r#"
11779 use some::mod1;
11780 use some::mod2;
11781
11782 const A: u32 = 42;
11783 ˇ
11784
11785 fn main() {
11786 println!("hello");
11787
11788 println!("world");
11789 }
11790 "#
11791 .unindent(),
11792 );
11793 cx.update_editor(|editor, cx| {
11794 let snapshot = editor.snapshot(cx);
11795 let all_hunks = editor_hunks(editor, &snapshot, cx);
11796 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11797 assert_eq!(
11798 all_hunks,
11799 vec![(
11800 "".to_string(),
11801 DiffHunkStatus::Added,
11802 DisplayRow(5)..DisplayRow(6)
11803 )]
11804 );
11805 assert_eq!(
11806 expanded_hunks_background_highlights(editor, cx),
11807 vec![DisplayRow(5)..=DisplayRow(5)]
11808 );
11809 assert_eq!(all_hunks, all_expanded_hunks);
11810 });
11811
11812 cx.update_editor(|editor, cx| {
11813 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
11814 editor.delete_line(&DeleteLine, cx);
11815 });
11816 executor.run_until_parked();
11817 cx.assert_editor_state(
11818 &r#"
11819 ˇ
11820
11821 fn main() {
11822 println!("hello");
11823
11824 println!("world");
11825 }
11826 "#
11827 .unindent(),
11828 );
11829 cx.update_editor(|editor, cx| {
11830 let snapshot = editor.snapshot(cx);
11831 let all_hunks = editor_hunks(editor, &snapshot, cx);
11832 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11833 assert_eq!(
11834 all_hunks,
11835 vec![
11836 (
11837 "use some::mod1;\nuse some::mod2;\n".to_string(),
11838 DiffHunkStatus::Removed,
11839 DisplayRow(0)..DisplayRow(0)
11840 ),
11841 (
11842 "const A: u32 = 42;\n".to_string(),
11843 DiffHunkStatus::Removed,
11844 DisplayRow(2)..DisplayRow(2)
11845 )
11846 ]
11847 );
11848 assert_eq!(
11849 expanded_hunks_background_highlights(editor, cx),
11850 Vec::new(),
11851 "Should close all stale expanded addition hunks"
11852 );
11853 assert_eq!(
11854 all_expanded_hunks,
11855 vec![(
11856 "const A: u32 = 42;\n".to_string(),
11857 DiffHunkStatus::Removed,
11858 DisplayRow(2)..DisplayRow(2)
11859 )],
11860 "Should open hunks that were adjacent to the stale addition one"
11861 );
11862 });
11863}
11864
11865#[gpui::test]
11866async fn test_edits_around_toggled_deletions(
11867 executor: BackgroundExecutor,
11868 cx: &mut gpui::TestAppContext,
11869) {
11870 init_test(cx, |_| {});
11871
11872 let mut cx = EditorTestContext::new(cx).await;
11873
11874 let diff_base = r#"
11875 use some::mod1;
11876 use some::mod2;
11877
11878 const A: u32 = 42;
11879 const B: u32 = 42;
11880 const C: u32 = 42;
11881
11882
11883 fn main() {
11884 println!("hello");
11885
11886 println!("world");
11887 }
11888 "#
11889 .unindent();
11890 executor.run_until_parked();
11891 cx.set_state(
11892 &r#"
11893 use some::mod1;
11894 use some::mod2;
11895
11896 ˇconst B: u32 = 42;
11897 const C: u32 = 42;
11898
11899
11900 fn main() {
11901 println!("hello");
11902
11903 println!("world");
11904 }
11905 "#
11906 .unindent(),
11907 );
11908
11909 cx.set_diff_base(Some(&diff_base));
11910 executor.run_until_parked();
11911 cx.update_editor(|editor, cx| {
11912 let snapshot = editor.snapshot(cx);
11913 let all_hunks = editor_hunks(editor, &snapshot, cx);
11914 assert_eq!(
11915 all_hunks,
11916 vec![(
11917 "const A: u32 = 42;\n".to_string(),
11918 DiffHunkStatus::Removed,
11919 DisplayRow(3)..DisplayRow(3)
11920 )]
11921 );
11922 });
11923 cx.update_editor(|editor, cx| {
11924 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11925 });
11926 executor.run_until_parked();
11927 cx.assert_editor_state(
11928 &r#"
11929 use some::mod1;
11930 use some::mod2;
11931
11932 ˇconst B: u32 = 42;
11933 const C: u32 = 42;
11934
11935
11936 fn main() {
11937 println!("hello");
11938
11939 println!("world");
11940 }
11941 "#
11942 .unindent(),
11943 );
11944 cx.update_editor(|editor, cx| {
11945 let snapshot = editor.snapshot(cx);
11946 let all_hunks = editor_hunks(editor, &snapshot, cx);
11947 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11948 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11949 assert_eq!(
11950 all_hunks,
11951 vec![(
11952 "const A: u32 = 42;\n".to_string(),
11953 DiffHunkStatus::Removed,
11954 DisplayRow(4)..DisplayRow(4)
11955 )]
11956 );
11957 assert_eq!(all_hunks, all_expanded_hunks);
11958 });
11959
11960 cx.update_editor(|editor, cx| {
11961 editor.delete_line(&DeleteLine, cx);
11962 });
11963 executor.run_until_parked();
11964 cx.assert_editor_state(
11965 &r#"
11966 use some::mod1;
11967 use some::mod2;
11968
11969 ˇconst C: u32 = 42;
11970
11971
11972 fn main() {
11973 println!("hello");
11974
11975 println!("world");
11976 }
11977 "#
11978 .unindent(),
11979 );
11980 cx.update_editor(|editor, cx| {
11981 let snapshot = editor.snapshot(cx);
11982 let all_hunks = editor_hunks(editor, &snapshot, cx);
11983 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11984 assert_eq!(
11985 expanded_hunks_background_highlights(editor, cx),
11986 Vec::new(),
11987 "Deleted hunks do not highlight current editor's background"
11988 );
11989 assert_eq!(
11990 all_hunks,
11991 vec![(
11992 "const A: u32 = 42;\nconst B: u32 = 42;\n".to_string(),
11993 DiffHunkStatus::Removed,
11994 DisplayRow(5)..DisplayRow(5)
11995 )]
11996 );
11997 assert_eq!(all_hunks, all_expanded_hunks);
11998 });
11999
12000 cx.update_editor(|editor, cx| {
12001 editor.delete_line(&DeleteLine, cx);
12002 });
12003 executor.run_until_parked();
12004 cx.assert_editor_state(
12005 &r#"
12006 use some::mod1;
12007 use some::mod2;
12008
12009 ˇ
12010
12011 fn main() {
12012 println!("hello");
12013
12014 println!("world");
12015 }
12016 "#
12017 .unindent(),
12018 );
12019 cx.update_editor(|editor, cx| {
12020 let snapshot = editor.snapshot(cx);
12021 let all_hunks = editor_hunks(editor, &snapshot, cx);
12022 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12023 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
12024 assert_eq!(
12025 all_hunks,
12026 vec![(
12027 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12028 DiffHunkStatus::Removed,
12029 DisplayRow(6)..DisplayRow(6)
12030 )]
12031 );
12032 assert_eq!(all_hunks, all_expanded_hunks);
12033 });
12034
12035 cx.update_editor(|editor, cx| {
12036 editor.handle_input("replacement", cx);
12037 });
12038 executor.run_until_parked();
12039 cx.assert_editor_state(
12040 &r#"
12041 use some::mod1;
12042 use some::mod2;
12043
12044 replacementˇ
12045
12046 fn main() {
12047 println!("hello");
12048
12049 println!("world");
12050 }
12051 "#
12052 .unindent(),
12053 );
12054 cx.update_editor(|editor, cx| {
12055 let snapshot = editor.snapshot(cx);
12056 let all_hunks = editor_hunks(editor, &snapshot, cx);
12057 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12058 assert_eq!(
12059 all_hunks,
12060 vec![(
12061 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n\n".to_string(),
12062 DiffHunkStatus::Modified,
12063 DisplayRow(7)..DisplayRow(8)
12064 )]
12065 );
12066 assert_eq!(
12067 expanded_hunks_background_highlights(editor, cx),
12068 vec![DisplayRow(7)..=DisplayRow(7)],
12069 "Modified expanded hunks should display additions and highlight their background"
12070 );
12071 assert_eq!(all_hunks, all_expanded_hunks);
12072 });
12073}
12074
12075#[gpui::test]
12076async fn test_edits_around_toggled_modifications(
12077 executor: BackgroundExecutor,
12078 cx: &mut gpui::TestAppContext,
12079) {
12080 init_test(cx, |_| {});
12081
12082 let mut cx = EditorTestContext::new(cx).await;
12083
12084 let diff_base = r#"
12085 use some::mod1;
12086 use some::mod2;
12087
12088 const A: u32 = 42;
12089 const B: u32 = 42;
12090 const C: u32 = 42;
12091 const D: u32 = 42;
12092
12093
12094 fn main() {
12095 println!("hello");
12096
12097 println!("world");
12098 }"#
12099 .unindent();
12100 executor.run_until_parked();
12101 cx.set_state(
12102 &r#"
12103 use some::mod1;
12104 use some::mod2;
12105
12106 const A: u32 = 42;
12107 const B: u32 = 42;
12108 const C: u32 = 43ˇ
12109 const D: u32 = 42;
12110
12111
12112 fn main() {
12113 println!("hello");
12114
12115 println!("world");
12116 }"#
12117 .unindent(),
12118 );
12119
12120 cx.set_diff_base(Some(&diff_base));
12121 executor.run_until_parked();
12122 cx.update_editor(|editor, cx| {
12123 let snapshot = editor.snapshot(cx);
12124 let all_hunks = editor_hunks(editor, &snapshot, cx);
12125 assert_eq!(
12126 all_hunks,
12127 vec![(
12128 "const C: u32 = 42;\n".to_string(),
12129 DiffHunkStatus::Modified,
12130 DisplayRow(5)..DisplayRow(6)
12131 )]
12132 );
12133 });
12134 cx.update_editor(|editor, cx| {
12135 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12136 });
12137 executor.run_until_parked();
12138 cx.assert_editor_state(
12139 &r#"
12140 use some::mod1;
12141 use some::mod2;
12142
12143 const A: u32 = 42;
12144 const B: u32 = 42;
12145 const C: u32 = 43ˇ
12146 const D: u32 = 42;
12147
12148
12149 fn main() {
12150 println!("hello");
12151
12152 println!("world");
12153 }"#
12154 .unindent(),
12155 );
12156 cx.update_editor(|editor, cx| {
12157 let snapshot = editor.snapshot(cx);
12158 let all_hunks = editor_hunks(editor, &snapshot, cx);
12159 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12160 assert_eq!(
12161 expanded_hunks_background_highlights(editor, cx),
12162 vec![DisplayRow(6)..=DisplayRow(6)],
12163 );
12164 assert_eq!(
12165 all_hunks,
12166 vec![(
12167 "const C: u32 = 42;\n".to_string(),
12168 DiffHunkStatus::Modified,
12169 DisplayRow(6)..DisplayRow(7)
12170 )]
12171 );
12172 assert_eq!(all_hunks, all_expanded_hunks);
12173 });
12174
12175 cx.update_editor(|editor, cx| {
12176 editor.handle_input("\nnew_line\n", cx);
12177 });
12178 executor.run_until_parked();
12179 cx.assert_editor_state(
12180 &r#"
12181 use some::mod1;
12182 use some::mod2;
12183
12184 const A: u32 = 42;
12185 const B: u32 = 42;
12186 const C: u32 = 43
12187 new_line
12188 ˇ
12189 const D: u32 = 42;
12190
12191
12192 fn main() {
12193 println!("hello");
12194
12195 println!("world");
12196 }"#
12197 .unindent(),
12198 );
12199 cx.update_editor(|editor, cx| {
12200 let snapshot = editor.snapshot(cx);
12201 let all_hunks = editor_hunks(editor, &snapshot, cx);
12202 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12203 assert_eq!(
12204 expanded_hunks_background_highlights(editor, cx),
12205 vec![DisplayRow(6)..=DisplayRow(6)],
12206 "Modified hunk should grow highlighted lines on more text additions"
12207 );
12208 assert_eq!(
12209 all_hunks,
12210 vec![(
12211 "const C: u32 = 42;\n".to_string(),
12212 DiffHunkStatus::Modified,
12213 DisplayRow(6)..DisplayRow(9)
12214 )]
12215 );
12216 assert_eq!(all_hunks, all_expanded_hunks);
12217 });
12218
12219 cx.update_editor(|editor, cx| {
12220 editor.move_up(&MoveUp, cx);
12221 editor.move_up(&MoveUp, cx);
12222 editor.move_up(&MoveUp, cx);
12223 editor.delete_line(&DeleteLine, cx);
12224 });
12225 executor.run_until_parked();
12226 cx.assert_editor_state(
12227 &r#"
12228 use some::mod1;
12229 use some::mod2;
12230
12231 const A: u32 = 42;
12232 ˇconst C: u32 = 43
12233 new_line
12234
12235 const D: u32 = 42;
12236
12237
12238 fn main() {
12239 println!("hello");
12240
12241 println!("world");
12242 }"#
12243 .unindent(),
12244 );
12245 cx.update_editor(|editor, cx| {
12246 let snapshot = editor.snapshot(cx);
12247 let all_hunks = editor_hunks(editor, &snapshot, cx);
12248 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12249 assert_eq!(
12250 expanded_hunks_background_highlights(editor, cx),
12251 vec![DisplayRow(6)..=DisplayRow(8)],
12252 );
12253 assert_eq!(
12254 all_hunks,
12255 vec![(
12256 "const B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12257 DiffHunkStatus::Modified,
12258 DisplayRow(6)..DisplayRow(9)
12259 )],
12260 "Modified hunk should grow deleted lines on text deletions above"
12261 );
12262 assert_eq!(all_hunks, all_expanded_hunks);
12263 });
12264
12265 cx.update_editor(|editor, cx| {
12266 editor.move_up(&MoveUp, cx);
12267 editor.handle_input("v", cx);
12268 });
12269 executor.run_until_parked();
12270 cx.assert_editor_state(
12271 &r#"
12272 use some::mod1;
12273 use some::mod2;
12274
12275 vˇconst A: u32 = 42;
12276 const C: u32 = 43
12277 new_line
12278
12279 const D: u32 = 42;
12280
12281
12282 fn main() {
12283 println!("hello");
12284
12285 println!("world");
12286 }"#
12287 .unindent(),
12288 );
12289 cx.update_editor(|editor, cx| {
12290 let snapshot = editor.snapshot(cx);
12291 let all_hunks = editor_hunks(editor, &snapshot, cx);
12292 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12293 assert_eq!(
12294 expanded_hunks_background_highlights(editor, cx),
12295 vec![DisplayRow(6)..=DisplayRow(9)],
12296 "Modified hunk should grow deleted lines on text modifications above"
12297 );
12298 assert_eq!(
12299 all_hunks,
12300 vec![(
12301 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12302 DiffHunkStatus::Modified,
12303 DisplayRow(6)..DisplayRow(10)
12304 )]
12305 );
12306 assert_eq!(all_hunks, all_expanded_hunks);
12307 });
12308
12309 cx.update_editor(|editor, cx| {
12310 editor.move_down(&MoveDown, cx);
12311 editor.move_down(&MoveDown, cx);
12312 editor.delete_line(&DeleteLine, cx)
12313 });
12314 executor.run_until_parked();
12315 cx.assert_editor_state(
12316 &r#"
12317 use some::mod1;
12318 use some::mod2;
12319
12320 vconst A: u32 = 42;
12321 const C: u32 = 43
12322 ˇ
12323 const D: u32 = 42;
12324
12325
12326 fn main() {
12327 println!("hello");
12328
12329 println!("world");
12330 }"#
12331 .unindent(),
12332 );
12333 cx.update_editor(|editor, cx| {
12334 let snapshot = editor.snapshot(cx);
12335 let all_hunks = editor_hunks(editor, &snapshot, cx);
12336 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12337 assert_eq!(
12338 expanded_hunks_background_highlights(editor, cx),
12339 vec![DisplayRow(6)..=DisplayRow(8)],
12340 "Modified hunk should grow shrink lines on modification lines removal"
12341 );
12342 assert_eq!(
12343 all_hunks,
12344 vec![(
12345 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12346 DiffHunkStatus::Modified,
12347 DisplayRow(6)..DisplayRow(9)
12348 )]
12349 );
12350 assert_eq!(all_hunks, all_expanded_hunks);
12351 });
12352
12353 cx.update_editor(|editor, cx| {
12354 editor.move_up(&MoveUp, cx);
12355 editor.move_up(&MoveUp, cx);
12356 editor.select_down_by_lines(&SelectDownByLines { lines: 4 }, cx);
12357 editor.delete_line(&DeleteLine, cx)
12358 });
12359 executor.run_until_parked();
12360 cx.assert_editor_state(
12361 &r#"
12362 use some::mod1;
12363 use some::mod2;
12364
12365 ˇ
12366
12367 fn main() {
12368 println!("hello");
12369
12370 println!("world");
12371 }"#
12372 .unindent(),
12373 );
12374 cx.update_editor(|editor, cx| {
12375 let snapshot = editor.snapshot(cx);
12376 let all_hunks = editor_hunks(editor, &snapshot, cx);
12377 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12378 assert_eq!(
12379 expanded_hunks_background_highlights(editor, cx),
12380 Vec::new(),
12381 "Modified hunk should turn into a removed one on all modified lines removal"
12382 );
12383 assert_eq!(
12384 all_hunks,
12385 vec![(
12386 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\nconst D: u32 = 42;\n"
12387 .to_string(),
12388 DiffHunkStatus::Removed,
12389 DisplayRow(7)..DisplayRow(7)
12390 )]
12391 );
12392 assert_eq!(all_hunks, all_expanded_hunks);
12393 });
12394}
12395
12396#[gpui::test]
12397async fn test_multiple_expanded_hunks_merge(
12398 executor: BackgroundExecutor,
12399 cx: &mut gpui::TestAppContext,
12400) {
12401 init_test(cx, |_| {});
12402
12403 let mut cx = EditorTestContext::new(cx).await;
12404
12405 let diff_base = r#"
12406 use some::mod1;
12407 use some::mod2;
12408
12409 const A: u32 = 42;
12410 const B: u32 = 42;
12411 const C: u32 = 42;
12412 const D: u32 = 42;
12413
12414
12415 fn main() {
12416 println!("hello");
12417
12418 println!("world");
12419 }"#
12420 .unindent();
12421 executor.run_until_parked();
12422 cx.set_state(
12423 &r#"
12424 use some::mod1;
12425 use some::mod2;
12426
12427 const A: u32 = 42;
12428 const B: u32 = 42;
12429 const C: u32 = 43ˇ
12430 const D: u32 = 42;
12431
12432
12433 fn main() {
12434 println!("hello");
12435
12436 println!("world");
12437 }"#
12438 .unindent(),
12439 );
12440
12441 cx.set_diff_base(Some(&diff_base));
12442 executor.run_until_parked();
12443 cx.update_editor(|editor, cx| {
12444 let snapshot = editor.snapshot(cx);
12445 let all_hunks = editor_hunks(editor, &snapshot, cx);
12446 assert_eq!(
12447 all_hunks,
12448 vec![(
12449 "const C: u32 = 42;\n".to_string(),
12450 DiffHunkStatus::Modified,
12451 DisplayRow(5)..DisplayRow(6)
12452 )]
12453 );
12454 });
12455 cx.update_editor(|editor, cx| {
12456 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12457 });
12458 executor.run_until_parked();
12459 cx.assert_editor_state(
12460 &r#"
12461 use some::mod1;
12462 use some::mod2;
12463
12464 const A: u32 = 42;
12465 const B: u32 = 42;
12466 const C: u32 = 43ˇ
12467 const D: u32 = 42;
12468
12469
12470 fn main() {
12471 println!("hello");
12472
12473 println!("world");
12474 }"#
12475 .unindent(),
12476 );
12477 cx.update_editor(|editor, cx| {
12478 let snapshot = editor.snapshot(cx);
12479 let all_hunks = editor_hunks(editor, &snapshot, cx);
12480 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12481 assert_eq!(
12482 expanded_hunks_background_highlights(editor, cx),
12483 vec![DisplayRow(6)..=DisplayRow(6)],
12484 );
12485 assert_eq!(
12486 all_hunks,
12487 vec![(
12488 "const C: u32 = 42;\n".to_string(),
12489 DiffHunkStatus::Modified,
12490 DisplayRow(6)..DisplayRow(7)
12491 )]
12492 );
12493 assert_eq!(all_hunks, all_expanded_hunks);
12494 });
12495
12496 cx.update_editor(|editor, cx| {
12497 editor.handle_input("\nnew_line\n", cx);
12498 });
12499 executor.run_until_parked();
12500 cx.assert_editor_state(
12501 &r#"
12502 use some::mod1;
12503 use some::mod2;
12504
12505 const A: u32 = 42;
12506 const B: u32 = 42;
12507 const C: u32 = 43
12508 new_line
12509 ˇ
12510 const D: u32 = 42;
12511
12512
12513 fn main() {
12514 println!("hello");
12515
12516 println!("world");
12517 }"#
12518 .unindent(),
12519 );
12520}
12521
12522async fn setup_indent_guides_editor(
12523 text: &str,
12524 cx: &mut gpui::TestAppContext,
12525) -> (BufferId, EditorTestContext) {
12526 init_test(cx, |_| {});
12527
12528 let mut cx = EditorTestContext::new(cx).await;
12529
12530 let buffer_id = cx.update_editor(|editor, cx| {
12531 editor.set_text(text, cx);
12532 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12533 let buffer_id = buffer_ids[0];
12534 buffer_id
12535 });
12536
12537 (buffer_id, cx)
12538}
12539
12540fn assert_indent_guides(
12541 range: Range<u32>,
12542 expected: Vec<IndentGuide>,
12543 active_indices: Option<Vec<usize>>,
12544 cx: &mut EditorTestContext,
12545) {
12546 let indent_guides = cx.update_editor(|editor, cx| {
12547 let snapshot = editor.snapshot(cx).display_snapshot;
12548 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12549 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12550 true,
12551 &snapshot,
12552 cx,
12553 );
12554
12555 indent_guides.sort_by(|a, b| {
12556 a.depth.cmp(&b.depth).then(
12557 a.start_row
12558 .cmp(&b.start_row)
12559 .then(a.end_row.cmp(&b.end_row)),
12560 )
12561 });
12562 indent_guides
12563 });
12564
12565 if let Some(expected) = active_indices {
12566 let active_indices = cx.update_editor(|editor, cx| {
12567 let snapshot = editor.snapshot(cx).display_snapshot;
12568 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
12569 });
12570
12571 assert_eq!(
12572 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
12573 expected,
12574 "Active indent guide indices do not match"
12575 );
12576 }
12577
12578 let expected: Vec<_> = expected
12579 .into_iter()
12580 .map(|guide| MultiBufferIndentGuide {
12581 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
12582 buffer: guide,
12583 })
12584 .collect();
12585
12586 assert_eq!(indent_guides, expected, "Indent guides do not match");
12587}
12588
12589fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
12590 IndentGuide {
12591 buffer_id,
12592 start_row,
12593 end_row,
12594 depth,
12595 tab_size: 4,
12596 settings: IndentGuideSettings {
12597 enabled: true,
12598 line_width: 1,
12599 active_line_width: 1,
12600 ..Default::default()
12601 },
12602 }
12603}
12604
12605#[gpui::test]
12606async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12607 let (buffer_id, mut cx) = setup_indent_guides_editor(
12608 &"
12609 fn main() {
12610 let a = 1;
12611 }"
12612 .unindent(),
12613 cx,
12614 )
12615 .await;
12616
12617 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12618}
12619
12620#[gpui::test]
12621async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
12622 let (buffer_id, mut cx) = setup_indent_guides_editor(
12623 &"
12624 fn main() {
12625 let a = 1;
12626 let b = 2;
12627 }"
12628 .unindent(),
12629 cx,
12630 )
12631 .await;
12632
12633 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
12634}
12635
12636#[gpui::test]
12637async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
12638 let (buffer_id, mut cx) = setup_indent_guides_editor(
12639 &"
12640 fn main() {
12641 let a = 1;
12642 if a == 3 {
12643 let b = 2;
12644 } else {
12645 let c = 3;
12646 }
12647 }"
12648 .unindent(),
12649 cx,
12650 )
12651 .await;
12652
12653 assert_indent_guides(
12654 0..8,
12655 vec![
12656 indent_guide(buffer_id, 1, 6, 0),
12657 indent_guide(buffer_id, 3, 3, 1),
12658 indent_guide(buffer_id, 5, 5, 1),
12659 ],
12660 None,
12661 &mut cx,
12662 );
12663}
12664
12665#[gpui::test]
12666async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
12667 let (buffer_id, mut cx) = setup_indent_guides_editor(
12668 &"
12669 fn main() {
12670 let a = 1;
12671 let b = 2;
12672 let c = 3;
12673 }"
12674 .unindent(),
12675 cx,
12676 )
12677 .await;
12678
12679 assert_indent_guides(
12680 0..5,
12681 vec![
12682 indent_guide(buffer_id, 1, 3, 0),
12683 indent_guide(buffer_id, 2, 2, 1),
12684 ],
12685 None,
12686 &mut cx,
12687 );
12688}
12689
12690#[gpui::test]
12691async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
12692 let (buffer_id, mut cx) = setup_indent_guides_editor(
12693 &"
12694 fn main() {
12695 let a = 1;
12696
12697 let c = 3;
12698 }"
12699 .unindent(),
12700 cx,
12701 )
12702 .await;
12703
12704 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
12705}
12706
12707#[gpui::test]
12708async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
12709 let (buffer_id, mut cx) = setup_indent_guides_editor(
12710 &"
12711 fn main() {
12712 let a = 1;
12713
12714 let c = 3;
12715
12716 if a == 3 {
12717 let b = 2;
12718 } else {
12719 let c = 3;
12720 }
12721 }"
12722 .unindent(),
12723 cx,
12724 )
12725 .await;
12726
12727 assert_indent_guides(
12728 0..11,
12729 vec![
12730 indent_guide(buffer_id, 1, 9, 0),
12731 indent_guide(buffer_id, 6, 6, 1),
12732 indent_guide(buffer_id, 8, 8, 1),
12733 ],
12734 None,
12735 &mut cx,
12736 );
12737}
12738
12739#[gpui::test]
12740async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
12741 let (buffer_id, mut cx) = setup_indent_guides_editor(
12742 &"
12743 fn main() {
12744 let a = 1;
12745
12746 let c = 3;
12747
12748 if a == 3 {
12749 let b = 2;
12750 } else {
12751 let c = 3;
12752 }
12753 }"
12754 .unindent(),
12755 cx,
12756 )
12757 .await;
12758
12759 assert_indent_guides(
12760 1..11,
12761 vec![
12762 indent_guide(buffer_id, 1, 9, 0),
12763 indent_guide(buffer_id, 6, 6, 1),
12764 indent_guide(buffer_id, 8, 8, 1),
12765 ],
12766 None,
12767 &mut cx,
12768 );
12769}
12770
12771#[gpui::test]
12772async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
12773 let (buffer_id, mut cx) = setup_indent_guides_editor(
12774 &"
12775 fn main() {
12776 let a = 1;
12777
12778 let c = 3;
12779
12780 if a == 3 {
12781 let b = 2;
12782 } else {
12783 let c = 3;
12784 }
12785 }"
12786 .unindent(),
12787 cx,
12788 )
12789 .await;
12790
12791 assert_indent_guides(
12792 1..10,
12793 vec![
12794 indent_guide(buffer_id, 1, 9, 0),
12795 indent_guide(buffer_id, 6, 6, 1),
12796 indent_guide(buffer_id, 8, 8, 1),
12797 ],
12798 None,
12799 &mut cx,
12800 );
12801}
12802
12803#[gpui::test]
12804async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
12805 let (buffer_id, mut cx) = setup_indent_guides_editor(
12806 &"
12807 block1
12808 block2
12809 block3
12810 block4
12811 block2
12812 block1
12813 block1"
12814 .unindent(),
12815 cx,
12816 )
12817 .await;
12818
12819 assert_indent_guides(
12820 1..10,
12821 vec![
12822 indent_guide(buffer_id, 1, 4, 0),
12823 indent_guide(buffer_id, 2, 3, 1),
12824 indent_guide(buffer_id, 3, 3, 2),
12825 ],
12826 None,
12827 &mut cx,
12828 );
12829}
12830
12831#[gpui::test]
12832async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
12833 let (buffer_id, mut cx) = setup_indent_guides_editor(
12834 &"
12835 block1
12836 block2
12837 block3
12838
12839 block1
12840 block1"
12841 .unindent(),
12842 cx,
12843 )
12844 .await;
12845
12846 assert_indent_guides(
12847 0..6,
12848 vec![
12849 indent_guide(buffer_id, 1, 2, 0),
12850 indent_guide(buffer_id, 2, 2, 1),
12851 ],
12852 None,
12853 &mut cx,
12854 );
12855}
12856
12857#[gpui::test]
12858async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
12859 let (buffer_id, mut cx) = setup_indent_guides_editor(
12860 &"
12861 block1
12862
12863
12864
12865 block2
12866 "
12867 .unindent(),
12868 cx,
12869 )
12870 .await;
12871
12872 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12873}
12874
12875#[gpui::test]
12876async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
12877 let (buffer_id, mut cx) = setup_indent_guides_editor(
12878 &"
12879 def a:
12880 \tb = 3
12881 \tif True:
12882 \t\tc = 4
12883 \t\td = 5
12884 \tprint(b)
12885 "
12886 .unindent(),
12887 cx,
12888 )
12889 .await;
12890
12891 assert_indent_guides(
12892 0..6,
12893 vec![
12894 indent_guide(buffer_id, 1, 6, 0),
12895 indent_guide(buffer_id, 3, 4, 1),
12896 ],
12897 None,
12898 &mut cx,
12899 );
12900}
12901
12902#[gpui::test]
12903async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12904 let (buffer_id, mut cx) = setup_indent_guides_editor(
12905 &"
12906 fn main() {
12907 let a = 1;
12908 }"
12909 .unindent(),
12910 cx,
12911 )
12912 .await;
12913
12914 cx.update_editor(|editor, cx| {
12915 editor.change_selections(None, cx, |s| {
12916 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12917 });
12918 });
12919
12920 assert_indent_guides(
12921 0..3,
12922 vec![indent_guide(buffer_id, 1, 1, 0)],
12923 Some(vec![0]),
12924 &mut cx,
12925 );
12926}
12927
12928#[gpui::test]
12929async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
12930 let (buffer_id, mut cx) = setup_indent_guides_editor(
12931 &"
12932 fn main() {
12933 if 1 == 2 {
12934 let a = 1;
12935 }
12936 }"
12937 .unindent(),
12938 cx,
12939 )
12940 .await;
12941
12942 cx.update_editor(|editor, cx| {
12943 editor.change_selections(None, cx, |s| {
12944 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12945 });
12946 });
12947
12948 assert_indent_guides(
12949 0..4,
12950 vec![
12951 indent_guide(buffer_id, 1, 3, 0),
12952 indent_guide(buffer_id, 2, 2, 1),
12953 ],
12954 Some(vec![1]),
12955 &mut cx,
12956 );
12957
12958 cx.update_editor(|editor, cx| {
12959 editor.change_selections(None, cx, |s| {
12960 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12961 });
12962 });
12963
12964 assert_indent_guides(
12965 0..4,
12966 vec![
12967 indent_guide(buffer_id, 1, 3, 0),
12968 indent_guide(buffer_id, 2, 2, 1),
12969 ],
12970 Some(vec![1]),
12971 &mut cx,
12972 );
12973
12974 cx.update_editor(|editor, cx| {
12975 editor.change_selections(None, cx, |s| {
12976 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
12977 });
12978 });
12979
12980 assert_indent_guides(
12981 0..4,
12982 vec![
12983 indent_guide(buffer_id, 1, 3, 0),
12984 indent_guide(buffer_id, 2, 2, 1),
12985 ],
12986 Some(vec![0]),
12987 &mut cx,
12988 );
12989}
12990
12991#[gpui::test]
12992async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
12993 let (buffer_id, mut cx) = setup_indent_guides_editor(
12994 &"
12995 fn main() {
12996 let a = 1;
12997
12998 let b = 2;
12999 }"
13000 .unindent(),
13001 cx,
13002 )
13003 .await;
13004
13005 cx.update_editor(|editor, cx| {
13006 editor.change_selections(None, cx, |s| {
13007 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13008 });
13009 });
13010
13011 assert_indent_guides(
13012 0..5,
13013 vec![indent_guide(buffer_id, 1, 3, 0)],
13014 Some(vec![0]),
13015 &mut cx,
13016 );
13017}
13018
13019#[gpui::test]
13020async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13021 let (buffer_id, mut cx) = setup_indent_guides_editor(
13022 &"
13023 def m:
13024 a = 1
13025 pass"
13026 .unindent(),
13027 cx,
13028 )
13029 .await;
13030
13031 cx.update_editor(|editor, cx| {
13032 editor.change_selections(None, cx, |s| {
13033 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13034 });
13035 });
13036
13037 assert_indent_guides(
13038 0..3,
13039 vec![indent_guide(buffer_id, 1, 2, 0)],
13040 Some(vec![0]),
13041 &mut cx,
13042 );
13043}
13044
13045#[gpui::test]
13046fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13047 init_test(cx, |_| {});
13048
13049 let editor = cx.add_window(|cx| {
13050 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13051 build_editor(buffer, cx)
13052 });
13053
13054 let render_args = Arc::new(Mutex::new(None));
13055 let snapshot = editor
13056 .update(cx, |editor, cx| {
13057 let snapshot = editor.buffer().read(cx).snapshot(cx);
13058 let range =
13059 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13060
13061 struct RenderArgs {
13062 row: MultiBufferRow,
13063 folded: bool,
13064 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13065 }
13066
13067 let crease = Crease::new(
13068 range,
13069 FoldPlaceholder::test(),
13070 {
13071 let toggle_callback = render_args.clone();
13072 move |row, folded, callback, _cx| {
13073 *toggle_callback.lock() = Some(RenderArgs {
13074 row,
13075 folded,
13076 callback,
13077 });
13078 div()
13079 }
13080 },
13081 |_row, _folded, _cx| div(),
13082 );
13083
13084 editor.insert_creases(Some(crease), cx);
13085 let snapshot = editor.snapshot(cx);
13086 let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13087 snapshot
13088 })
13089 .unwrap();
13090
13091 let render_args = render_args.lock().take().unwrap();
13092 assert_eq!(render_args.row, MultiBufferRow(1));
13093 assert_eq!(render_args.folded, false);
13094 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13095
13096 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13097 .unwrap();
13098 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13099 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13100
13101 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13102 .unwrap();
13103 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13104 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13105}
13106
13107fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13108 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13109 point..point
13110}
13111
13112fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13113 let (text, ranges) = marked_text_ranges(marked_text, true);
13114 assert_eq!(view.text(cx), text);
13115 assert_eq!(
13116 view.selections.ranges(cx),
13117 ranges,
13118 "Assert selections are {}",
13119 marked_text
13120 );
13121}
13122
13123pub fn handle_signature_help_request(
13124 cx: &mut EditorLspTestContext,
13125 mocked_response: lsp::SignatureHelp,
13126) -> impl Future<Output = ()> {
13127 let mut request =
13128 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13129 let mocked_response = mocked_response.clone();
13130 async move { Ok(Some(mocked_response)) }
13131 });
13132
13133 async move {
13134 request.next().await;
13135 }
13136}
13137
13138/// Handle completion request passing a marked string specifying where the completion
13139/// should be triggered from using '|' character, what range should be replaced, and what completions
13140/// should be returned using '<' and '>' to delimit the range
13141pub fn handle_completion_request(
13142 cx: &mut EditorLspTestContext,
13143 marked_string: &str,
13144 completions: Vec<&'static str>,
13145 counter: Arc<AtomicUsize>,
13146) -> impl Future<Output = ()> {
13147 let complete_from_marker: TextRangeMarker = '|'.into();
13148 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13149 let (_, mut marked_ranges) = marked_text_ranges_by(
13150 marked_string,
13151 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13152 );
13153
13154 let complete_from_position =
13155 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13156 let replace_range =
13157 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13158
13159 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13160 let completions = completions.clone();
13161 counter.fetch_add(1, atomic::Ordering::Release);
13162 async move {
13163 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13164 assert_eq!(
13165 params.text_document_position.position,
13166 complete_from_position
13167 );
13168 Ok(Some(lsp::CompletionResponse::Array(
13169 completions
13170 .iter()
13171 .map(|completion_text| lsp::CompletionItem {
13172 label: completion_text.to_string(),
13173 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13174 range: replace_range,
13175 new_text: completion_text.to_string(),
13176 })),
13177 ..Default::default()
13178 })
13179 .collect(),
13180 )))
13181 }
13182 });
13183
13184 async move {
13185 request.next().await;
13186 }
13187}
13188
13189fn handle_resolve_completion_request(
13190 cx: &mut EditorLspTestContext,
13191 edits: Option<Vec<(&'static str, &'static str)>>,
13192) -> impl Future<Output = ()> {
13193 let edits = edits.map(|edits| {
13194 edits
13195 .iter()
13196 .map(|(marked_string, new_text)| {
13197 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13198 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13199 lsp::TextEdit::new(replace_range, new_text.to_string())
13200 })
13201 .collect::<Vec<_>>()
13202 });
13203
13204 let mut request =
13205 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13206 let edits = edits.clone();
13207 async move {
13208 Ok(lsp::CompletionItem {
13209 additional_text_edits: edits,
13210 ..Default::default()
13211 })
13212 }
13213 });
13214
13215 async move {
13216 request.next().await;
13217 }
13218}
13219
13220pub(crate) fn update_test_language_settings(
13221 cx: &mut TestAppContext,
13222 f: impl Fn(&mut AllLanguageSettingsContent),
13223) {
13224 _ = cx.update(|cx| {
13225 SettingsStore::update_global(cx, |store, cx| {
13226 store.update_user_settings::<AllLanguageSettings>(cx, f);
13227 });
13228 });
13229}
13230
13231pub(crate) fn update_test_project_settings(
13232 cx: &mut TestAppContext,
13233 f: impl Fn(&mut ProjectSettings),
13234) {
13235 _ = cx.update(|cx| {
13236 SettingsStore::update_global(cx, |store, cx| {
13237 store.update_user_settings::<ProjectSettings>(cx, f);
13238 });
13239 });
13240}
13241
13242pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
13243 _ = cx.update(|cx| {
13244 assets::Assets.load_test_fonts(cx);
13245 let store = SettingsStore::test(cx);
13246 cx.set_global(store);
13247 theme::init(theme::LoadThemes::JustBase, cx);
13248 release_channel::init(SemanticVersion::default(), cx);
13249 client::init_settings(cx);
13250 language::init(cx);
13251 Project::init_settings(cx);
13252 workspace::init_settings(cx);
13253 crate::init(cx);
13254 });
13255
13256 update_test_language_settings(cx, f);
13257}
13258
13259pub(crate) fn rust_lang() -> Arc<Language> {
13260 Arc::new(Language::new(
13261 LanguageConfig {
13262 name: "Rust".into(),
13263 matcher: LanguageMatcher {
13264 path_suffixes: vec!["rs".to_string()],
13265 ..Default::default()
13266 },
13267 ..Default::default()
13268 },
13269 Some(tree_sitter_rust::language()),
13270 ))
13271}
13272
13273#[track_caller]
13274fn assert_hunk_revert(
13275 not_reverted_text_with_selections: &str,
13276 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
13277 expected_reverted_text_with_selections: &str,
13278 base_text: &str,
13279 cx: &mut EditorLspTestContext,
13280) {
13281 cx.set_state(not_reverted_text_with_selections);
13282 cx.update_editor(|editor, cx| {
13283 editor
13284 .buffer()
13285 .read(cx)
13286 .as_singleton()
13287 .unwrap()
13288 .update(cx, |buffer, cx| {
13289 buffer.set_diff_base(Some(base_text.into()), cx);
13290 });
13291 });
13292 cx.executor().run_until_parked();
13293
13294 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
13295 let snapshot = editor.buffer().read(cx).snapshot(cx);
13296 let reverted_hunk_statuses = snapshot
13297 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
13298 .map(|hunk| hunk_status(&hunk))
13299 .collect::<Vec<_>>();
13300
13301 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
13302 reverted_hunk_statuses
13303 });
13304 cx.executor().run_until_parked();
13305 cx.assert_editor_state(expected_reverted_text_with_selections);
13306 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
13307}