1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_hunks,
6 editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext,
7 expanded_hunks, expanded_hunks_background_highlights, select_ranges,
8 },
9 JoinLines,
10};
11use futures::StreamExt;
12use gpui::{
13 div, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds,
14 WindowOptions,
15};
16use indoc::indoc;
17use language::{
18 language_settings::{
19 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
20 },
21 BracketPairConfig,
22 Capability::ReadWrite,
23 FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher, Override,
24 ParsedMarkdown, Point,
25};
26use language_settings::{Formatter, FormatterList, IndentGuideSettings};
27use multi_buffer::MultiBufferIndentGuide;
28use parking_lot::Mutex;
29use project::FakeFs;
30use project::{
31 lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
32 project_settings::{LspSettings, ProjectSettings},
33};
34use serde_json::{self, json};
35use std::sync::atomic;
36use std::sync::atomic::AtomicUsize;
37use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
38use unindent::Unindent;
39use util::{
40 assert_set_eq,
41 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
42};
43use workspace::{
44 item::{FollowEvent, FollowableItem, Item, ItemHandle},
45 NavigationEntry, ViewId,
46};
47
48#[gpui::test]
49fn test_edit_events(cx: &mut TestAppContext) {
50 init_test(cx, |_| {});
51
52 let buffer = cx.new_model(|cx| {
53 let mut buffer = language::Buffer::local("123456", cx);
54 buffer.set_group_interval(Duration::from_secs(1));
55 buffer
56 });
57
58 let events = Rc::new(RefCell::new(Vec::new()));
59 let editor1 = cx.add_window({
60 let events = events.clone();
61 |cx| {
62 let view = cx.view().clone();
63 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
64 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
65 EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
66 _ => {}
67 })
68 .detach();
69 Editor::for_buffer(buffer.clone(), None, cx)
70 }
71 });
72
73 let editor2 = cx.add_window({
74 let events = events.clone();
75 |cx| {
76 cx.subscribe(
77 &cx.view().clone(),
78 move |_, _, event: &EditorEvent, _| match event {
79 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
80 EditorEvent::BufferEdited => {
81 events.borrow_mut().push(("editor2", "buffer edited"))
82 }
83 _ => {}
84 },
85 )
86 .detach();
87 Editor::for_buffer(buffer.clone(), None, cx)
88 }
89 });
90
91 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
92
93 // Mutating editor 1 will emit an `Edited` event only for that editor.
94 _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
95 assert_eq!(
96 mem::take(&mut *events.borrow_mut()),
97 [
98 ("editor1", "edited"),
99 ("editor1", "buffer edited"),
100 ("editor2", "buffer edited"),
101 ]
102 );
103
104 // Mutating editor 2 will emit an `Edited` event only for that editor.
105 _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
106 assert_eq!(
107 mem::take(&mut *events.borrow_mut()),
108 [
109 ("editor2", "edited"),
110 ("editor1", "buffer edited"),
111 ("editor2", "buffer edited"),
112 ]
113 );
114
115 // Undoing on editor 1 will emit an `Edited` event only for that editor.
116 _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
117 assert_eq!(
118 mem::take(&mut *events.borrow_mut()),
119 [
120 ("editor1", "edited"),
121 ("editor1", "buffer edited"),
122 ("editor2", "buffer edited"),
123 ]
124 );
125
126 // Redoing on editor 1 will emit an `Edited` event only for that editor.
127 _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
128 assert_eq!(
129 mem::take(&mut *events.borrow_mut()),
130 [
131 ("editor1", "edited"),
132 ("editor1", "buffer edited"),
133 ("editor2", "buffer edited"),
134 ]
135 );
136
137 // Undoing on editor 2 will emit an `Edited` event only for that editor.
138 _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
139 assert_eq!(
140 mem::take(&mut *events.borrow_mut()),
141 [
142 ("editor2", "edited"),
143 ("editor1", "buffer edited"),
144 ("editor2", "buffer edited"),
145 ]
146 );
147
148 // Redoing on editor 2 will emit an `Edited` event only for that editor.
149 _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
150 assert_eq!(
151 mem::take(&mut *events.borrow_mut()),
152 [
153 ("editor2", "edited"),
154 ("editor1", "buffer edited"),
155 ("editor2", "buffer edited"),
156 ]
157 );
158
159 // No event is emitted when the mutation is a no-op.
160 _ = editor2.update(cx, |editor, cx| {
161 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
162
163 editor.backspace(&Backspace, cx);
164 });
165 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
166}
167
168#[gpui::test]
169fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
170 init_test(cx, |_| {});
171
172 let mut now = Instant::now();
173 let buffer = cx.new_model(|cx| language::Buffer::local("123456", cx));
174 let group_interval = buffer.update(cx, |buffer, _| buffer.transaction_group_interval());
175 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
176 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
177
178 _ = editor.update(cx, |editor, cx| {
179 editor.start_transaction_at(now, cx);
180 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
181
182 editor.insert("cd", cx);
183 editor.end_transaction_at(now, cx);
184 assert_eq!(editor.text(cx), "12cd56");
185 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
186
187 editor.start_transaction_at(now, cx);
188 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
189 editor.insert("e", cx);
190 editor.end_transaction_at(now, cx);
191 assert_eq!(editor.text(cx), "12cde6");
192 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
193
194 now += group_interval + Duration::from_millis(1);
195 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
196
197 // Simulate an edit in another editor
198 _ = buffer.update(cx, |buffer, cx| {
199 buffer.start_transaction_at(now, cx);
200 buffer.edit([(0..1, "a")], None, cx);
201 buffer.edit([(1..1, "b")], None, cx);
202 buffer.end_transaction_at(now, cx);
203 });
204
205 assert_eq!(editor.text(cx), "ab2cde6");
206 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
207
208 // Last transaction happened past the group interval in a different editor.
209 // Undo it individually and don't restore selections.
210 editor.undo(&Undo, cx);
211 assert_eq!(editor.text(cx), "12cde6");
212 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
213
214 // First two transactions happened within the group interval in this editor.
215 // Undo them together and restore selections.
216 editor.undo(&Undo, cx);
217 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
218 assert_eq!(editor.text(cx), "123456");
219 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
220
221 // Redo the first two transactions together.
222 editor.redo(&Redo, cx);
223 assert_eq!(editor.text(cx), "12cde6");
224 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
225
226 // Redo the last transaction on its own.
227 editor.redo(&Redo, cx);
228 assert_eq!(editor.text(cx), "ab2cde6");
229 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
230
231 // Test empty transactions.
232 editor.start_transaction_at(now, cx);
233 editor.end_transaction_at(now, cx);
234 editor.undo(&Undo, cx);
235 assert_eq!(editor.text(cx), "12cde6");
236 });
237}
238
239#[gpui::test]
240fn test_ime_composition(cx: &mut TestAppContext) {
241 init_test(cx, |_| {});
242
243 let buffer = cx.new_model(|cx| {
244 let mut buffer = language::Buffer::local("abcde", cx);
245 // Ensure automatic grouping doesn't occur.
246 buffer.set_group_interval(Duration::ZERO);
247 buffer
248 });
249
250 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
251 cx.add_window(|cx| {
252 let mut editor = build_editor(buffer.clone(), cx);
253
254 // Start a new IME composition.
255 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
256 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
257 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
258 assert_eq!(editor.text(cx), "äbcde");
259 assert_eq!(
260 editor.marked_text_ranges(cx),
261 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
262 );
263
264 // Finalize IME composition.
265 editor.replace_text_in_range(None, "ā", cx);
266 assert_eq!(editor.text(cx), "ābcde");
267 assert_eq!(editor.marked_text_ranges(cx), None);
268
269 // IME composition edits are grouped and are undone/redone at once.
270 editor.undo(&Default::default(), cx);
271 assert_eq!(editor.text(cx), "abcde");
272 assert_eq!(editor.marked_text_ranges(cx), None);
273 editor.redo(&Default::default(), cx);
274 assert_eq!(editor.text(cx), "ābcde");
275 assert_eq!(editor.marked_text_ranges(cx), None);
276
277 // Start a new IME composition.
278 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
279 assert_eq!(
280 editor.marked_text_ranges(cx),
281 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
282 );
283
284 // Undoing during an IME composition cancels it.
285 editor.undo(&Default::default(), cx);
286 assert_eq!(editor.text(cx), "ābcde");
287 assert_eq!(editor.marked_text_ranges(cx), None);
288
289 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
290 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
291 assert_eq!(editor.text(cx), "ābcdè");
292 assert_eq!(
293 editor.marked_text_ranges(cx),
294 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
295 );
296
297 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
298 editor.replace_text_in_range(Some(4..999), "ę", cx);
299 assert_eq!(editor.text(cx), "ābcdę");
300 assert_eq!(editor.marked_text_ranges(cx), None);
301
302 // Start a new IME composition with multiple cursors.
303 editor.change_selections(None, cx, |s| {
304 s.select_ranges([
305 OffsetUtf16(1)..OffsetUtf16(1),
306 OffsetUtf16(3)..OffsetUtf16(3),
307 OffsetUtf16(5)..OffsetUtf16(5),
308 ])
309 });
310 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
311 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
312 assert_eq!(
313 editor.marked_text_ranges(cx),
314 Some(vec![
315 OffsetUtf16(0)..OffsetUtf16(3),
316 OffsetUtf16(4)..OffsetUtf16(7),
317 OffsetUtf16(8)..OffsetUtf16(11)
318 ])
319 );
320
321 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
322 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
323 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
324 assert_eq!(
325 editor.marked_text_ranges(cx),
326 Some(vec![
327 OffsetUtf16(1)..OffsetUtf16(2),
328 OffsetUtf16(5)..OffsetUtf16(6),
329 OffsetUtf16(9)..OffsetUtf16(10)
330 ])
331 );
332
333 // Finalize IME composition with multiple cursors.
334 editor.replace_text_in_range(Some(9..10), "2", cx);
335 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
336 assert_eq!(editor.marked_text_ranges(cx), None);
337
338 editor
339 });
340}
341
342#[gpui::test]
343fn test_selection_with_mouse(cx: &mut TestAppContext) {
344 init_test(cx, |_| {});
345
346 let editor = cx.add_window(|cx| {
347 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
348 build_editor(buffer, cx)
349 });
350
351 _ = editor.update(cx, |view, cx| {
352 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
353 });
354 assert_eq!(
355 editor
356 .update(cx, |view, cx| view.selections.display_ranges(cx))
357 .unwrap(),
358 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
359 );
360
361 _ = editor.update(cx, |view, cx| {
362 view.update_selection(
363 DisplayPoint::new(DisplayRow(3), 3),
364 0,
365 gpui::Point::<f32>::default(),
366 cx,
367 );
368 });
369
370 assert_eq!(
371 editor
372 .update(cx, |view, cx| view.selections.display_ranges(cx))
373 .unwrap(),
374 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
375 );
376
377 _ = editor.update(cx, |view, cx| {
378 view.update_selection(
379 DisplayPoint::new(DisplayRow(1), 1),
380 0,
381 gpui::Point::<f32>::default(),
382 cx,
383 );
384 });
385
386 assert_eq!(
387 editor
388 .update(cx, |view, cx| view.selections.display_ranges(cx))
389 .unwrap(),
390 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
391 );
392
393 _ = editor.update(cx, |view, cx| {
394 view.end_selection(cx);
395 view.update_selection(
396 DisplayPoint::new(DisplayRow(3), 3),
397 0,
398 gpui::Point::<f32>::default(),
399 cx,
400 );
401 });
402
403 assert_eq!(
404 editor
405 .update(cx, |view, cx| view.selections.display_ranges(cx))
406 .unwrap(),
407 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
408 );
409
410 _ = editor.update(cx, |view, cx| {
411 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
412 view.update_selection(
413 DisplayPoint::new(DisplayRow(0), 0),
414 0,
415 gpui::Point::<f32>::default(),
416 cx,
417 );
418 });
419
420 assert_eq!(
421 editor
422 .update(cx, |view, cx| view.selections.display_ranges(cx))
423 .unwrap(),
424 [
425 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
426 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
427 ]
428 );
429
430 _ = editor.update(cx, |view, cx| {
431 view.end_selection(cx);
432 });
433
434 assert_eq!(
435 editor
436 .update(cx, |view, cx| view.selections.display_ranges(cx))
437 .unwrap(),
438 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
439 );
440}
441
442#[gpui::test]
443fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
444 init_test(cx, |_| {});
445
446 let editor = cx.add_window(|cx| {
447 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
448 build_editor(buffer, cx)
449 });
450
451 _ = editor.update(cx, |view, cx| {
452 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
453 });
454
455 _ = editor.update(cx, |view, cx| {
456 view.end_selection(cx);
457 });
458
459 _ = editor.update(cx, |view, cx| {
460 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
461 });
462
463 _ = editor.update(cx, |view, cx| {
464 view.end_selection(cx);
465 });
466
467 assert_eq!(
468 editor
469 .update(cx, |view, cx| view.selections.display_ranges(cx))
470 .unwrap(),
471 [
472 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
473 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
474 ]
475 );
476
477 _ = editor.update(cx, |view, cx| {
478 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
479 });
480
481 _ = editor.update(cx, |view, cx| {
482 view.end_selection(cx);
483 });
484
485 assert_eq!(
486 editor
487 .update(cx, |view, cx| view.selections.display_ranges(cx))
488 .unwrap(),
489 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
490 );
491}
492
493#[gpui::test]
494fn test_canceling_pending_selection(cx: &mut TestAppContext) {
495 init_test(cx, |_| {});
496
497 let view = cx.add_window(|cx| {
498 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
499 build_editor(buffer, cx)
500 });
501
502 _ = view.update(cx, |view, cx| {
503 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
504 assert_eq!(
505 view.selections.display_ranges(cx),
506 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
507 );
508 });
509
510 _ = view.update(cx, |view, cx| {
511 view.update_selection(
512 DisplayPoint::new(DisplayRow(3), 3),
513 0,
514 gpui::Point::<f32>::default(),
515 cx,
516 );
517 assert_eq!(
518 view.selections.display_ranges(cx),
519 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
520 );
521 });
522
523 _ = view.update(cx, |view, cx| {
524 view.cancel(&Cancel, cx);
525 view.update_selection(
526 DisplayPoint::new(DisplayRow(1), 1),
527 0,
528 gpui::Point::<f32>::default(),
529 cx,
530 );
531 assert_eq!(
532 view.selections.display_ranges(cx),
533 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
534 );
535 });
536}
537
538#[gpui::test]
539fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
540 init_test(cx, |_| {});
541
542 let view = cx.add_window(|cx| {
543 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
544 build_editor(buffer, cx)
545 });
546
547 _ = view.update(cx, |view, cx| {
548 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
549 assert_eq!(
550 view.selections.display_ranges(cx),
551 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
552 );
553
554 view.move_down(&Default::default(), cx);
555 assert_eq!(
556 view.selections.display_ranges(cx),
557 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
558 );
559
560 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
561 assert_eq!(
562 view.selections.display_ranges(cx),
563 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
564 );
565
566 view.move_up(&Default::default(), cx);
567 assert_eq!(
568 view.selections.display_ranges(cx),
569 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
570 );
571 });
572}
573
574#[gpui::test]
575fn test_clone(cx: &mut TestAppContext) {
576 init_test(cx, |_| {});
577
578 let (text, selection_ranges) = marked_text_ranges(
579 indoc! {"
580 one
581 two
582 threeˇ
583 four
584 fiveˇ
585 "},
586 true,
587 );
588
589 let editor = cx.add_window(|cx| {
590 let buffer = MultiBuffer::build_simple(&text, cx);
591 build_editor(buffer, cx)
592 });
593
594 _ = editor.update(cx, |editor, cx| {
595 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
596 editor.fold_ranges(
597 [
598 (Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
599 (Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
600 ],
601 true,
602 cx,
603 );
604 });
605
606 let cloned_editor = editor
607 .update(cx, |editor, cx| {
608 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
609 })
610 .unwrap()
611 .unwrap();
612
613 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
614 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
615
616 assert_eq!(
617 cloned_editor
618 .update(cx, |e, cx| e.display_text(cx))
619 .unwrap(),
620 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
621 );
622 assert_eq!(
623 cloned_snapshot
624 .folds_in_range(0..text.len())
625 .collect::<Vec<_>>(),
626 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
627 );
628 assert_set_eq!(
629 cloned_editor
630 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
631 .unwrap(),
632 editor
633 .update(cx, |editor, cx| editor.selections.ranges(cx))
634 .unwrap()
635 );
636 assert_set_eq!(
637 cloned_editor
638 .update(cx, |e, cx| e.selections.display_ranges(cx))
639 .unwrap(),
640 editor
641 .update(cx, |e, cx| e.selections.display_ranges(cx))
642 .unwrap()
643 );
644}
645
646#[gpui::test]
647async fn test_navigation_history(cx: &mut TestAppContext) {
648 init_test(cx, |_| {});
649
650 use workspace::item::Item;
651
652 let fs = FakeFs::new(cx.executor());
653 let project = Project::test(fs, [], cx).await;
654 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
655 let pane = workspace
656 .update(cx, |workspace, _| workspace.active_pane().clone())
657 .unwrap();
658
659 _ = workspace.update(cx, |_v, cx| {
660 cx.new_view(|cx| {
661 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
662 let mut editor = build_editor(buffer.clone(), cx);
663 let handle = cx.view();
664 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
665
666 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
667 editor.nav_history.as_mut().unwrap().pop_backward(cx)
668 }
669
670 // Move the cursor a small distance.
671 // Nothing is added to the navigation history.
672 editor.change_selections(None, cx, |s| {
673 s.select_display_ranges([
674 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
675 ])
676 });
677 editor.change_selections(None, cx, |s| {
678 s.select_display_ranges([
679 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
680 ])
681 });
682 assert!(pop_history(&mut editor, cx).is_none());
683
684 // Move the cursor a large distance.
685 // The history can jump back to the previous position.
686 editor.change_selections(None, cx, |s| {
687 s.select_display_ranges([
688 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
689 ])
690 });
691 let nav_entry = pop_history(&mut editor, cx).unwrap();
692 editor.navigate(nav_entry.data.unwrap(), cx);
693 assert_eq!(nav_entry.item.id(), cx.entity_id());
694 assert_eq!(
695 editor.selections.display_ranges(cx),
696 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
697 );
698 assert!(pop_history(&mut editor, cx).is_none());
699
700 // Move the cursor a small distance via the mouse.
701 // Nothing is added to the navigation history.
702 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
703 editor.end_selection(cx);
704 assert_eq!(
705 editor.selections.display_ranges(cx),
706 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
707 );
708 assert!(pop_history(&mut editor, cx).is_none());
709
710 // Move the cursor a large distance via the mouse.
711 // The history can jump back to the previous position.
712 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
713 editor.end_selection(cx);
714 assert_eq!(
715 editor.selections.display_ranges(cx),
716 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
717 );
718 let nav_entry = pop_history(&mut editor, cx).unwrap();
719 editor.navigate(nav_entry.data.unwrap(), cx);
720 assert_eq!(nav_entry.item.id(), cx.entity_id());
721 assert_eq!(
722 editor.selections.display_ranges(cx),
723 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
724 );
725 assert!(pop_history(&mut editor, cx).is_none());
726
727 // Set scroll position to check later
728 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
729 let original_scroll_position = editor.scroll_manager.anchor();
730
731 // Jump to the end of the document and adjust scroll
732 editor.move_to_end(&MoveToEnd, cx);
733 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
734 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
735
736 let nav_entry = pop_history(&mut editor, cx).unwrap();
737 editor.navigate(nav_entry.data.unwrap(), cx);
738 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
739
740 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
741 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
742 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
743 let invalid_point = Point::new(9999, 0);
744 editor.navigate(
745 Box::new(NavigationData {
746 cursor_anchor: invalid_anchor,
747 cursor_position: invalid_point,
748 scroll_anchor: ScrollAnchor {
749 anchor: invalid_anchor,
750 offset: Default::default(),
751 },
752 scroll_top_row: invalid_point.row,
753 }),
754 cx,
755 );
756 assert_eq!(
757 editor.selections.display_ranges(cx),
758 &[editor.max_point(cx)..editor.max_point(cx)]
759 );
760 assert_eq!(
761 editor.scroll_position(cx),
762 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
763 );
764
765 editor
766 })
767 });
768}
769
770#[gpui::test]
771fn test_cancel(cx: &mut TestAppContext) {
772 init_test(cx, |_| {});
773
774 let view = cx.add_window(|cx| {
775 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
776 build_editor(buffer, cx)
777 });
778
779 _ = view.update(cx, |view, cx| {
780 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
781 view.update_selection(
782 DisplayPoint::new(DisplayRow(1), 1),
783 0,
784 gpui::Point::<f32>::default(),
785 cx,
786 );
787 view.end_selection(cx);
788
789 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
790 view.update_selection(
791 DisplayPoint::new(DisplayRow(0), 3),
792 0,
793 gpui::Point::<f32>::default(),
794 cx,
795 );
796 view.end_selection(cx);
797 assert_eq!(
798 view.selections.display_ranges(cx),
799 [
800 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
801 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
802 ]
803 );
804 });
805
806 _ = view.update(cx, |view, cx| {
807 view.cancel(&Cancel, cx);
808 assert_eq!(
809 view.selections.display_ranges(cx),
810 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
811 );
812 });
813
814 _ = view.update(cx, |view, cx| {
815 view.cancel(&Cancel, cx);
816 assert_eq!(
817 view.selections.display_ranges(cx),
818 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
819 );
820 });
821}
822
823#[gpui::test]
824fn test_fold_action(cx: &mut TestAppContext) {
825 init_test(cx, |_| {});
826
827 let view = cx.add_window(|cx| {
828 let buffer = MultiBuffer::build_simple(
829 &"
830 impl Foo {
831 // Hello!
832
833 fn a() {
834 1
835 }
836
837 fn b() {
838 2
839 }
840
841 fn c() {
842 3
843 }
844 }
845 "
846 .unindent(),
847 cx,
848 );
849 build_editor(buffer.clone(), cx)
850 });
851
852 _ = view.update(cx, |view, cx| {
853 view.change_selections(None, cx, |s| {
854 s.select_display_ranges([
855 DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(12), 0)
856 ]);
857 });
858 view.fold(&Fold, cx);
859 assert_eq!(
860 view.display_text(cx),
861 "
862 impl Foo {
863 // Hello!
864
865 fn a() {
866 1
867 }
868
869 fn b() {⋯
870 }
871
872 fn c() {⋯
873 }
874 }
875 "
876 .unindent(),
877 );
878
879 view.fold(&Fold, cx);
880 assert_eq!(
881 view.display_text(cx),
882 "
883 impl Foo {⋯
884 }
885 "
886 .unindent(),
887 );
888
889 view.unfold_lines(&UnfoldLines, cx);
890 assert_eq!(
891 view.display_text(cx),
892 "
893 impl Foo {
894 // Hello!
895
896 fn a() {
897 1
898 }
899
900 fn b() {⋯
901 }
902
903 fn c() {⋯
904 }
905 }
906 "
907 .unindent(),
908 );
909
910 view.unfold_lines(&UnfoldLines, cx);
911 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
912 });
913}
914
915#[gpui::test]
916fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
917 init_test(cx, |_| {});
918
919 let view = cx.add_window(|cx| {
920 let buffer = MultiBuffer::build_simple(
921 &"
922 class Foo:
923 # Hello!
924
925 def a():
926 print(1)
927
928 def b():
929 print(2)
930
931 def c():
932 print(3)
933 "
934 .unindent(),
935 cx,
936 );
937 build_editor(buffer.clone(), cx)
938 });
939
940 _ = view.update(cx, |view, cx| {
941 view.change_selections(None, cx, |s| {
942 s.select_display_ranges([
943 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(10), 0)
944 ]);
945 });
946 view.fold(&Fold, cx);
947 assert_eq!(
948 view.display_text(cx),
949 "
950 class Foo:
951 # Hello!
952
953 def a():
954 print(1)
955
956 def b():⋯
957
958 def c():⋯
959 "
960 .unindent(),
961 );
962
963 view.fold(&Fold, cx);
964 assert_eq!(
965 view.display_text(cx),
966 "
967 class Foo:⋯
968 "
969 .unindent(),
970 );
971
972 view.unfold_lines(&UnfoldLines, cx);
973 assert_eq!(
974 view.display_text(cx),
975 "
976 class Foo:
977 # Hello!
978
979 def a():
980 print(1)
981
982 def b():⋯
983
984 def c():⋯
985 "
986 .unindent(),
987 );
988
989 view.unfold_lines(&UnfoldLines, cx);
990 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
991 });
992}
993
994#[gpui::test]
995fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
996 init_test(cx, |_| {});
997
998 let view = cx.add_window(|cx| {
999 let buffer = MultiBuffer::build_simple(
1000 &"
1001 class Foo:
1002 # Hello!
1003
1004 def a():
1005 print(1)
1006
1007 def b():
1008 print(2)
1009
1010
1011 def c():
1012 print(3)
1013
1014
1015 "
1016 .unindent(),
1017 cx,
1018 );
1019 build_editor(buffer.clone(), cx)
1020 });
1021
1022 _ = view.update(cx, |view, cx| {
1023 view.change_selections(None, cx, |s| {
1024 s.select_display_ranges([
1025 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(11), 0)
1026 ]);
1027 });
1028 view.fold(&Fold, cx);
1029 assert_eq!(
1030 view.display_text(cx),
1031 "
1032 class Foo:
1033 # Hello!
1034
1035 def a():
1036 print(1)
1037
1038 def b():⋯
1039
1040
1041 def c():⋯
1042
1043
1044 "
1045 .unindent(),
1046 );
1047
1048 view.fold(&Fold, cx);
1049 assert_eq!(
1050 view.display_text(cx),
1051 "
1052 class Foo:⋯
1053
1054
1055 "
1056 .unindent(),
1057 );
1058
1059 view.unfold_lines(&UnfoldLines, cx);
1060 assert_eq!(
1061 view.display_text(cx),
1062 "
1063 class Foo:
1064 # Hello!
1065
1066 def a():
1067 print(1)
1068
1069 def b():⋯
1070
1071
1072 def c():⋯
1073
1074
1075 "
1076 .unindent(),
1077 );
1078
1079 view.unfold_lines(&UnfoldLines, cx);
1080 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1081 });
1082}
1083
1084#[gpui::test]
1085fn test_move_cursor(cx: &mut TestAppContext) {
1086 init_test(cx, |_| {});
1087
1088 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1089 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1090
1091 _ = buffer.update(cx, |buffer, cx| {
1092 buffer.edit(
1093 vec![
1094 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1095 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1096 ],
1097 None,
1098 cx,
1099 );
1100 });
1101 _ = view.update(cx, |view, cx| {
1102 assert_eq!(
1103 view.selections.display_ranges(cx),
1104 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1105 );
1106
1107 view.move_down(&MoveDown, cx);
1108 assert_eq!(
1109 view.selections.display_ranges(cx),
1110 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1111 );
1112
1113 view.move_right(&MoveRight, cx);
1114 assert_eq!(
1115 view.selections.display_ranges(cx),
1116 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1117 );
1118
1119 view.move_left(&MoveLeft, cx);
1120 assert_eq!(
1121 view.selections.display_ranges(cx),
1122 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1123 );
1124
1125 view.move_up(&MoveUp, cx);
1126 assert_eq!(
1127 view.selections.display_ranges(cx),
1128 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1129 );
1130
1131 view.move_to_end(&MoveToEnd, cx);
1132 assert_eq!(
1133 view.selections.display_ranges(cx),
1134 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1135 );
1136
1137 view.move_to_beginning(&MoveToBeginning, cx);
1138 assert_eq!(
1139 view.selections.display_ranges(cx),
1140 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1141 );
1142
1143 view.change_selections(None, cx, |s| {
1144 s.select_display_ranges([
1145 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1146 ]);
1147 });
1148 view.select_to_beginning(&SelectToBeginning, cx);
1149 assert_eq!(
1150 view.selections.display_ranges(cx),
1151 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1152 );
1153
1154 view.select_to_end(&SelectToEnd, cx);
1155 assert_eq!(
1156 view.selections.display_ranges(cx),
1157 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1158 );
1159 });
1160}
1161
1162// TODO: Re-enable this test
1163#[cfg(target_os = "macos")]
1164#[gpui::test]
1165fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1166 init_test(cx, |_| {});
1167
1168 let view = cx.add_window(|cx| {
1169 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1170 build_editor(buffer.clone(), cx)
1171 });
1172
1173 assert_eq!('ⓐ'.len_utf8(), 3);
1174 assert_eq!('α'.len_utf8(), 2);
1175
1176 _ = view.update(cx, |view, cx| {
1177 view.fold_ranges(
1178 vec![
1179 (Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1180 (Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1181 (Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1182 ],
1183 true,
1184 cx,
1185 );
1186 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1187
1188 view.move_right(&MoveRight, cx);
1189 assert_eq!(
1190 view.selections.display_ranges(cx),
1191 &[empty_range(0, "ⓐ".len())]
1192 );
1193 view.move_right(&MoveRight, cx);
1194 assert_eq!(
1195 view.selections.display_ranges(cx),
1196 &[empty_range(0, "ⓐⓑ".len())]
1197 );
1198 view.move_right(&MoveRight, cx);
1199 assert_eq!(
1200 view.selections.display_ranges(cx),
1201 &[empty_range(0, "ⓐⓑ⋯".len())]
1202 );
1203
1204 view.move_down(&MoveDown, cx);
1205 assert_eq!(
1206 view.selections.display_ranges(cx),
1207 &[empty_range(1, "ab⋯e".len())]
1208 );
1209 view.move_left(&MoveLeft, cx);
1210 assert_eq!(
1211 view.selections.display_ranges(cx),
1212 &[empty_range(1, "ab⋯".len())]
1213 );
1214 view.move_left(&MoveLeft, cx);
1215 assert_eq!(
1216 view.selections.display_ranges(cx),
1217 &[empty_range(1, "ab".len())]
1218 );
1219 view.move_left(&MoveLeft, cx);
1220 assert_eq!(
1221 view.selections.display_ranges(cx),
1222 &[empty_range(1, "a".len())]
1223 );
1224
1225 view.move_down(&MoveDown, cx);
1226 assert_eq!(
1227 view.selections.display_ranges(cx),
1228 &[empty_range(2, "α".len())]
1229 );
1230 view.move_right(&MoveRight, cx);
1231 assert_eq!(
1232 view.selections.display_ranges(cx),
1233 &[empty_range(2, "αβ".len())]
1234 );
1235 view.move_right(&MoveRight, cx);
1236 assert_eq!(
1237 view.selections.display_ranges(cx),
1238 &[empty_range(2, "αβ⋯".len())]
1239 );
1240 view.move_right(&MoveRight, cx);
1241 assert_eq!(
1242 view.selections.display_ranges(cx),
1243 &[empty_range(2, "αβ⋯ε".len())]
1244 );
1245
1246 view.move_up(&MoveUp, cx);
1247 assert_eq!(
1248 view.selections.display_ranges(cx),
1249 &[empty_range(1, "ab⋯e".len())]
1250 );
1251 view.move_down(&MoveDown, cx);
1252 assert_eq!(
1253 view.selections.display_ranges(cx),
1254 &[empty_range(2, "αβ⋯ε".len())]
1255 );
1256 view.move_up(&MoveUp, cx);
1257 assert_eq!(
1258 view.selections.display_ranges(cx),
1259 &[empty_range(1, "ab⋯e".len())]
1260 );
1261
1262 view.move_up(&MoveUp, cx);
1263 assert_eq!(
1264 view.selections.display_ranges(cx),
1265 &[empty_range(0, "ⓐⓑ".len())]
1266 );
1267 view.move_left(&MoveLeft, cx);
1268 assert_eq!(
1269 view.selections.display_ranges(cx),
1270 &[empty_range(0, "ⓐ".len())]
1271 );
1272 view.move_left(&MoveLeft, cx);
1273 assert_eq!(
1274 view.selections.display_ranges(cx),
1275 &[empty_range(0, "".len())]
1276 );
1277 });
1278}
1279
1280#[gpui::test]
1281fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1282 init_test(cx, |_| {});
1283
1284 let view = cx.add_window(|cx| {
1285 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1286 build_editor(buffer.clone(), cx)
1287 });
1288 _ = view.update(cx, |view, cx| {
1289 view.change_selections(None, cx, |s| {
1290 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1291 });
1292 view.move_down(&MoveDown, cx);
1293 assert_eq!(
1294 view.selections.display_ranges(cx),
1295 &[empty_range(1, "abcd".len())]
1296 );
1297
1298 view.move_down(&MoveDown, cx);
1299 assert_eq!(
1300 view.selections.display_ranges(cx),
1301 &[empty_range(2, "αβγ".len())]
1302 );
1303
1304 view.move_down(&MoveDown, cx);
1305 assert_eq!(
1306 view.selections.display_ranges(cx),
1307 &[empty_range(3, "abcd".len())]
1308 );
1309
1310 view.move_down(&MoveDown, cx);
1311 assert_eq!(
1312 view.selections.display_ranges(cx),
1313 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1314 );
1315
1316 view.move_up(&MoveUp, cx);
1317 assert_eq!(
1318 view.selections.display_ranges(cx),
1319 &[empty_range(3, "abcd".len())]
1320 );
1321
1322 view.move_up(&MoveUp, cx);
1323 assert_eq!(
1324 view.selections.display_ranges(cx),
1325 &[empty_range(2, "αβγ".len())]
1326 );
1327 });
1328}
1329
1330#[gpui::test]
1331fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1332 init_test(cx, |_| {});
1333 let move_to_beg = MoveToBeginningOfLine {
1334 stop_at_soft_wraps: true,
1335 };
1336
1337 let move_to_end = MoveToEndOfLine {
1338 stop_at_soft_wraps: true,
1339 };
1340
1341 let view = cx.add_window(|cx| {
1342 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1343 build_editor(buffer, cx)
1344 });
1345 _ = view.update(cx, |view, cx| {
1346 view.change_selections(None, cx, |s| {
1347 s.select_display_ranges([
1348 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1349 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1350 ]);
1351 });
1352 });
1353
1354 _ = view.update(cx, |view, cx| {
1355 view.move_to_beginning_of_line(&move_to_beg, cx);
1356 assert_eq!(
1357 view.selections.display_ranges(cx),
1358 &[
1359 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1360 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1361 ]
1362 );
1363 });
1364
1365 _ = view.update(cx, |view, cx| {
1366 view.move_to_beginning_of_line(&move_to_beg, cx);
1367 assert_eq!(
1368 view.selections.display_ranges(cx),
1369 &[
1370 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1371 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1372 ]
1373 );
1374 });
1375
1376 _ = view.update(cx, |view, cx| {
1377 view.move_to_beginning_of_line(&move_to_beg, cx);
1378 assert_eq!(
1379 view.selections.display_ranges(cx),
1380 &[
1381 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1382 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1383 ]
1384 );
1385 });
1386
1387 _ = view.update(cx, |view, cx| {
1388 view.move_to_end_of_line(&move_to_end, cx);
1389 assert_eq!(
1390 view.selections.display_ranges(cx),
1391 &[
1392 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1393 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1394 ]
1395 );
1396 });
1397
1398 // Moving to the end of line again is a no-op.
1399 _ = view.update(cx, |view, cx| {
1400 view.move_to_end_of_line(&move_to_end, cx);
1401 assert_eq!(
1402 view.selections.display_ranges(cx),
1403 &[
1404 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1405 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1406 ]
1407 );
1408 });
1409
1410 _ = view.update(cx, |view, cx| {
1411 view.move_left(&MoveLeft, cx);
1412 view.select_to_beginning_of_line(
1413 &SelectToBeginningOfLine {
1414 stop_at_soft_wraps: true,
1415 },
1416 cx,
1417 );
1418 assert_eq!(
1419 view.selections.display_ranges(cx),
1420 &[
1421 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1422 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1423 ]
1424 );
1425 });
1426
1427 _ = view.update(cx, |view, cx| {
1428 view.select_to_beginning_of_line(
1429 &SelectToBeginningOfLine {
1430 stop_at_soft_wraps: true,
1431 },
1432 cx,
1433 );
1434 assert_eq!(
1435 view.selections.display_ranges(cx),
1436 &[
1437 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1438 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1439 ]
1440 );
1441 });
1442
1443 _ = view.update(cx, |view, cx| {
1444 view.select_to_beginning_of_line(
1445 &SelectToBeginningOfLine {
1446 stop_at_soft_wraps: true,
1447 },
1448 cx,
1449 );
1450 assert_eq!(
1451 view.selections.display_ranges(cx),
1452 &[
1453 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1454 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1455 ]
1456 );
1457 });
1458
1459 _ = view.update(cx, |view, cx| {
1460 view.select_to_end_of_line(
1461 &SelectToEndOfLine {
1462 stop_at_soft_wraps: true,
1463 },
1464 cx,
1465 );
1466 assert_eq!(
1467 view.selections.display_ranges(cx),
1468 &[
1469 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1470 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1471 ]
1472 );
1473 });
1474
1475 _ = view.update(cx, |view, cx| {
1476 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1477 assert_eq!(view.display_text(cx), "ab\n de");
1478 assert_eq!(
1479 view.selections.display_ranges(cx),
1480 &[
1481 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1482 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1483 ]
1484 );
1485 });
1486
1487 _ = view.update(cx, |view, cx| {
1488 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1489 assert_eq!(view.display_text(cx), "\n");
1490 assert_eq!(
1491 view.selections.display_ranges(cx),
1492 &[
1493 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1494 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1495 ]
1496 );
1497 });
1498}
1499
1500#[gpui::test]
1501fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1502 init_test(cx, |_| {});
1503 let move_to_beg = MoveToBeginningOfLine {
1504 stop_at_soft_wraps: false,
1505 };
1506
1507 let move_to_end = MoveToEndOfLine {
1508 stop_at_soft_wraps: false,
1509 };
1510
1511 let view = cx.add_window(|cx| {
1512 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1513 build_editor(buffer, cx)
1514 });
1515
1516 _ = view.update(cx, |view, cx| {
1517 view.set_wrap_width(Some(140.0.into()), cx);
1518
1519 // We expect the following lines after wrapping
1520 // ```
1521 // thequickbrownfox
1522 // jumpedoverthelazydo
1523 // gs
1524 // ```
1525 // The final `gs` was soft-wrapped onto a new line.
1526 assert_eq!(
1527 "thequickbrownfox\njumpedoverthelaz\nydogs",
1528 view.display_text(cx),
1529 );
1530
1531 // First, let's assert behavior on the first line, that was not soft-wrapped.
1532 // Start the cursor at the `k` on the first line
1533 view.change_selections(None, cx, |s| {
1534 s.select_display_ranges([
1535 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1536 ]);
1537 });
1538
1539 // Moving to the beginning of the line should put us at the beginning of the line.
1540 view.move_to_beginning_of_line(&move_to_beg, cx);
1541 assert_eq!(
1542 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1543 view.selections.display_ranges(cx)
1544 );
1545
1546 // Moving to the end of the line should put us at the end of the line.
1547 view.move_to_end_of_line(&move_to_end, cx);
1548 assert_eq!(
1549 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1550 view.selections.display_ranges(cx)
1551 );
1552
1553 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1554 // Start the cursor at the last line (`y` that was wrapped to a new line)
1555 view.change_selections(None, cx, |s| {
1556 s.select_display_ranges([
1557 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1558 ]);
1559 });
1560
1561 // Moving to the beginning of the line should put us at the start of the second line of
1562 // display text, i.e., the `j`.
1563 view.move_to_beginning_of_line(&move_to_beg, cx);
1564 assert_eq!(
1565 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1566 view.selections.display_ranges(cx)
1567 );
1568
1569 // Moving to the beginning of the line again should be a no-op.
1570 view.move_to_beginning_of_line(&move_to_beg, cx);
1571 assert_eq!(
1572 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1573 view.selections.display_ranges(cx)
1574 );
1575
1576 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1577 // next display line.
1578 view.move_to_end_of_line(&move_to_end, cx);
1579 assert_eq!(
1580 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1581 view.selections.display_ranges(cx)
1582 );
1583
1584 // Moving to the end of the line again should be a no-op.
1585 view.move_to_end_of_line(&move_to_end, cx);
1586 assert_eq!(
1587 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1588 view.selections.display_ranges(cx)
1589 );
1590 });
1591}
1592
1593#[gpui::test]
1594fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1595 init_test(cx, |_| {});
1596
1597 let view = cx.add_window(|cx| {
1598 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1599 build_editor(buffer, cx)
1600 });
1601 _ = view.update(cx, |view, cx| {
1602 view.change_selections(None, cx, |s| {
1603 s.select_display_ranges([
1604 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1605 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1606 ])
1607 });
1608
1609 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1610 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1611
1612 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1613 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1614
1615 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1616 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1617
1618 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1619 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1620
1621 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1622 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1623
1624 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1625 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1626
1627 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1628 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1629
1630 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1631 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1632
1633 view.move_right(&MoveRight, cx);
1634 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1635 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1636
1637 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1638 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1639
1640 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1641 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1642 });
1643}
1644
1645#[gpui::test]
1646fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1647 init_test(cx, |_| {});
1648
1649 let view = cx.add_window(|cx| {
1650 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1651 build_editor(buffer, cx)
1652 });
1653
1654 _ = view.update(cx, |view, cx| {
1655 view.set_wrap_width(Some(140.0.into()), cx);
1656 assert_eq!(
1657 view.display_text(cx),
1658 "use one::{\n two::three::\n four::five\n};"
1659 );
1660
1661 view.change_selections(None, cx, |s| {
1662 s.select_display_ranges([
1663 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1664 ]);
1665 });
1666
1667 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1668 assert_eq!(
1669 view.selections.display_ranges(cx),
1670 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1671 );
1672
1673 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1674 assert_eq!(
1675 view.selections.display_ranges(cx),
1676 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1677 );
1678
1679 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1680 assert_eq!(
1681 view.selections.display_ranges(cx),
1682 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1683 );
1684
1685 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1686 assert_eq!(
1687 view.selections.display_ranges(cx),
1688 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1689 );
1690
1691 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1692 assert_eq!(
1693 view.selections.display_ranges(cx),
1694 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1695 );
1696
1697 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1698 assert_eq!(
1699 view.selections.display_ranges(cx),
1700 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1701 );
1702 });
1703}
1704
1705#[gpui::test]
1706async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1707 init_test(cx, |_| {});
1708 let mut cx = EditorTestContext::new(cx).await;
1709
1710 let line_height = cx.editor(|editor, cx| {
1711 editor
1712 .style()
1713 .unwrap()
1714 .text
1715 .line_height_in_pixels(cx.rem_size())
1716 });
1717 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1718
1719 cx.set_state(
1720 &r#"ˇone
1721 two
1722
1723 three
1724 fourˇ
1725 five
1726
1727 six"#
1728 .unindent(),
1729 );
1730
1731 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1732 cx.assert_editor_state(
1733 &r#"one
1734 two
1735 ˇ
1736 three
1737 four
1738 five
1739 ˇ
1740 six"#
1741 .unindent(),
1742 );
1743
1744 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1745 cx.assert_editor_state(
1746 &r#"one
1747 two
1748
1749 three
1750 four
1751 five
1752 ˇ
1753 sixˇ"#
1754 .unindent(),
1755 );
1756
1757 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1758 cx.assert_editor_state(
1759 &r#"one
1760 two
1761
1762 three
1763 four
1764 five
1765
1766 sixˇ"#
1767 .unindent(),
1768 );
1769
1770 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1771 cx.assert_editor_state(
1772 &r#"one
1773 two
1774
1775 three
1776 four
1777 five
1778 ˇ
1779 six"#
1780 .unindent(),
1781 );
1782
1783 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1784 cx.assert_editor_state(
1785 &r#"one
1786 two
1787 ˇ
1788 three
1789 four
1790 five
1791
1792 six"#
1793 .unindent(),
1794 );
1795
1796 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1797 cx.assert_editor_state(
1798 &r#"ˇone
1799 two
1800
1801 three
1802 four
1803 five
1804
1805 six"#
1806 .unindent(),
1807 );
1808}
1809
1810#[gpui::test]
1811async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1812 init_test(cx, |_| {});
1813 let mut cx = EditorTestContext::new(cx).await;
1814 let line_height = cx.editor(|editor, cx| {
1815 editor
1816 .style()
1817 .unwrap()
1818 .text
1819 .line_height_in_pixels(cx.rem_size())
1820 });
1821 let window = cx.window;
1822 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1823
1824 cx.set_state(
1825 &r#"ˇone
1826 two
1827 three
1828 four
1829 five
1830 six
1831 seven
1832 eight
1833 nine
1834 ten
1835 "#,
1836 );
1837
1838 cx.update_editor(|editor, cx| {
1839 assert_eq!(
1840 editor.snapshot(cx).scroll_position(),
1841 gpui::Point::new(0., 0.)
1842 );
1843 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1844 assert_eq!(
1845 editor.snapshot(cx).scroll_position(),
1846 gpui::Point::new(0., 3.)
1847 );
1848 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1849 assert_eq!(
1850 editor.snapshot(cx).scroll_position(),
1851 gpui::Point::new(0., 6.)
1852 );
1853 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1854 assert_eq!(
1855 editor.snapshot(cx).scroll_position(),
1856 gpui::Point::new(0., 3.)
1857 );
1858
1859 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1860 assert_eq!(
1861 editor.snapshot(cx).scroll_position(),
1862 gpui::Point::new(0., 1.)
1863 );
1864 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
1865 assert_eq!(
1866 editor.snapshot(cx).scroll_position(),
1867 gpui::Point::new(0., 3.)
1868 );
1869 });
1870}
1871
1872#[gpui::test]
1873async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
1874 init_test(cx, |_| {});
1875 let mut cx = EditorTestContext::new(cx).await;
1876
1877 let line_height = cx.update_editor(|editor, cx| {
1878 editor.set_vertical_scroll_margin(2, cx);
1879 editor
1880 .style()
1881 .unwrap()
1882 .text
1883 .line_height_in_pixels(cx.rem_size())
1884 });
1885 let window = cx.window;
1886 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
1887
1888 cx.set_state(
1889 &r#"ˇone
1890 two
1891 three
1892 four
1893 five
1894 six
1895 seven
1896 eight
1897 nine
1898 ten
1899 "#,
1900 );
1901 cx.update_editor(|editor, cx| {
1902 assert_eq!(
1903 editor.snapshot(cx).scroll_position(),
1904 gpui::Point::new(0., 0.0)
1905 );
1906 });
1907
1908 // Add a cursor below the visible area. Since both cursors cannot fit
1909 // on screen, the editor autoscrolls to reveal the newest cursor, and
1910 // allows the vertical scroll margin below that cursor.
1911 cx.update_editor(|editor, cx| {
1912 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1913 selections.select_ranges([
1914 Point::new(0, 0)..Point::new(0, 0),
1915 Point::new(6, 0)..Point::new(6, 0),
1916 ]);
1917 })
1918 });
1919 cx.update_editor(|editor, cx| {
1920 assert_eq!(
1921 editor.snapshot(cx).scroll_position(),
1922 gpui::Point::new(0., 3.0)
1923 );
1924 });
1925
1926 // Move down. The editor cursor scrolls down to track the newest cursor.
1927 cx.update_editor(|editor, cx| {
1928 editor.move_down(&Default::default(), cx);
1929 });
1930 cx.update_editor(|editor, cx| {
1931 assert_eq!(
1932 editor.snapshot(cx).scroll_position(),
1933 gpui::Point::new(0., 4.0)
1934 );
1935 });
1936
1937 // Add a cursor above the visible area. Since both cursors fit on screen,
1938 // the editor scrolls to show both.
1939 cx.update_editor(|editor, cx| {
1940 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1941 selections.select_ranges([
1942 Point::new(1, 0)..Point::new(1, 0),
1943 Point::new(6, 0)..Point::new(6, 0),
1944 ]);
1945 })
1946 });
1947 cx.update_editor(|editor, cx| {
1948 assert_eq!(
1949 editor.snapshot(cx).scroll_position(),
1950 gpui::Point::new(0., 1.0)
1951 );
1952 });
1953}
1954
1955#[gpui::test]
1956async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
1957 init_test(cx, |_| {});
1958 let mut cx = EditorTestContext::new(cx).await;
1959
1960 let line_height = cx.editor(|editor, cx| {
1961 editor
1962 .style()
1963 .unwrap()
1964 .text
1965 .line_height_in_pixels(cx.rem_size())
1966 });
1967 let window = cx.window;
1968 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
1969 cx.set_state(
1970 &r#"
1971 ˇone
1972 two
1973 threeˇ
1974 four
1975 five
1976 six
1977 seven
1978 eight
1979 nine
1980 ten
1981 "#
1982 .unindent(),
1983 );
1984
1985 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1986 cx.assert_editor_state(
1987 &r#"
1988 one
1989 two
1990 three
1991 ˇfour
1992 five
1993 sixˇ
1994 seven
1995 eight
1996 nine
1997 ten
1998 "#
1999 .unindent(),
2000 );
2001
2002 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2003 cx.assert_editor_state(
2004 &r#"
2005 one
2006 two
2007 three
2008 four
2009 five
2010 six
2011 ˇseven
2012 eight
2013 nineˇ
2014 ten
2015 "#
2016 .unindent(),
2017 );
2018
2019 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2020 cx.assert_editor_state(
2021 &r#"
2022 one
2023 two
2024 three
2025 ˇfour
2026 five
2027 sixˇ
2028 seven
2029 eight
2030 nine
2031 ten
2032 "#
2033 .unindent(),
2034 );
2035
2036 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2037 cx.assert_editor_state(
2038 &r#"
2039 ˇone
2040 two
2041 threeˇ
2042 four
2043 five
2044 six
2045 seven
2046 eight
2047 nine
2048 ten
2049 "#
2050 .unindent(),
2051 );
2052
2053 // Test select collapsing
2054 cx.update_editor(|editor, cx| {
2055 editor.move_page_down(&MovePageDown::default(), cx);
2056 editor.move_page_down(&MovePageDown::default(), cx);
2057 editor.move_page_down(&MovePageDown::default(), cx);
2058 });
2059 cx.assert_editor_state(
2060 &r#"
2061 one
2062 two
2063 three
2064 four
2065 five
2066 six
2067 seven
2068 eight
2069 nine
2070 ˇten
2071 ˇ"#
2072 .unindent(),
2073 );
2074}
2075
2076#[gpui::test]
2077async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2078 init_test(cx, |_| {});
2079 let mut cx = EditorTestContext::new(cx).await;
2080 cx.set_state("one «two threeˇ» four");
2081 cx.update_editor(|editor, cx| {
2082 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2083 assert_eq!(editor.text(cx), " four");
2084 });
2085}
2086
2087#[gpui::test]
2088fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2089 init_test(cx, |_| {});
2090
2091 let view = cx.add_window(|cx| {
2092 let buffer = MultiBuffer::build_simple("one two three four", cx);
2093 build_editor(buffer.clone(), cx)
2094 });
2095
2096 _ = view.update(cx, |view, cx| {
2097 view.change_selections(None, cx, |s| {
2098 s.select_display_ranges([
2099 // an empty selection - the preceding word fragment is deleted
2100 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2101 // characters selected - they are deleted
2102 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2103 ])
2104 });
2105 view.delete_to_previous_word_start(&DeleteToPreviousWordStart, cx);
2106 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2107 });
2108
2109 _ = view.update(cx, |view, cx| {
2110 view.change_selections(None, cx, |s| {
2111 s.select_display_ranges([
2112 // an empty selection - the following word fragment is deleted
2113 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2114 // characters selected - they are deleted
2115 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2116 ])
2117 });
2118 view.delete_to_next_word_end(&DeleteToNextWordEnd, cx);
2119 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2120 });
2121}
2122
2123#[gpui::test]
2124fn test_newline(cx: &mut TestAppContext) {
2125 init_test(cx, |_| {});
2126
2127 let view = cx.add_window(|cx| {
2128 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2129 build_editor(buffer.clone(), cx)
2130 });
2131
2132 _ = view.update(cx, |view, cx| {
2133 view.change_selections(None, cx, |s| {
2134 s.select_display_ranges([
2135 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2136 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2137 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2138 ])
2139 });
2140
2141 view.newline(&Newline, cx);
2142 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2143 });
2144}
2145
2146#[gpui::test]
2147fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2148 init_test(cx, |_| {});
2149
2150 let editor = cx.add_window(|cx| {
2151 let buffer = MultiBuffer::build_simple(
2152 "
2153 a
2154 b(
2155 X
2156 )
2157 c(
2158 X
2159 )
2160 "
2161 .unindent()
2162 .as_str(),
2163 cx,
2164 );
2165 let mut editor = build_editor(buffer.clone(), cx);
2166 editor.change_selections(None, cx, |s| {
2167 s.select_ranges([
2168 Point::new(2, 4)..Point::new(2, 5),
2169 Point::new(5, 4)..Point::new(5, 5),
2170 ])
2171 });
2172 editor
2173 });
2174
2175 _ = editor.update(cx, |editor, cx| {
2176 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2177 editor.buffer.update(cx, |buffer, cx| {
2178 buffer.edit(
2179 [
2180 (Point::new(1, 2)..Point::new(3, 0), ""),
2181 (Point::new(4, 2)..Point::new(6, 0), ""),
2182 ],
2183 None,
2184 cx,
2185 );
2186 assert_eq!(
2187 buffer.read(cx).text(),
2188 "
2189 a
2190 b()
2191 c()
2192 "
2193 .unindent()
2194 );
2195 });
2196 assert_eq!(
2197 editor.selections.ranges(cx),
2198 &[
2199 Point::new(1, 2)..Point::new(1, 2),
2200 Point::new(2, 2)..Point::new(2, 2),
2201 ],
2202 );
2203
2204 editor.newline(&Newline, cx);
2205 assert_eq!(
2206 editor.text(cx),
2207 "
2208 a
2209 b(
2210 )
2211 c(
2212 )
2213 "
2214 .unindent()
2215 );
2216
2217 // The selections are moved after the inserted newlines
2218 assert_eq!(
2219 editor.selections.ranges(cx),
2220 &[
2221 Point::new(2, 0)..Point::new(2, 0),
2222 Point::new(4, 0)..Point::new(4, 0),
2223 ],
2224 );
2225 });
2226}
2227
2228#[gpui::test]
2229async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2230 init_test(cx, |settings| {
2231 settings.defaults.tab_size = NonZeroU32::new(4)
2232 });
2233
2234 let language = Arc::new(
2235 Language::new(
2236 LanguageConfig::default(),
2237 Some(tree_sitter_rust::language()),
2238 )
2239 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2240 .unwrap(),
2241 );
2242
2243 let mut cx = EditorTestContext::new(cx).await;
2244 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2245 cx.set_state(indoc! {"
2246 const a: ˇA = (
2247 (ˇ
2248 «const_functionˇ»(ˇ),
2249 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2250 )ˇ
2251 ˇ);ˇ
2252 "});
2253
2254 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2255 cx.assert_editor_state(indoc! {"
2256 ˇ
2257 const a: A = (
2258 ˇ
2259 (
2260 ˇ
2261 ˇ
2262 const_function(),
2263 ˇ
2264 ˇ
2265 ˇ
2266 ˇ
2267 something_else,
2268 ˇ
2269 )
2270 ˇ
2271 ˇ
2272 );
2273 "});
2274}
2275
2276#[gpui::test]
2277async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2278 init_test(cx, |settings| {
2279 settings.defaults.tab_size = NonZeroU32::new(4)
2280 });
2281
2282 let language = Arc::new(
2283 Language::new(
2284 LanguageConfig::default(),
2285 Some(tree_sitter_rust::language()),
2286 )
2287 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2288 .unwrap(),
2289 );
2290
2291 let mut cx = EditorTestContext::new(cx).await;
2292 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2293 cx.set_state(indoc! {"
2294 const a: ˇA = (
2295 (ˇ
2296 «const_functionˇ»(ˇ),
2297 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2298 )ˇ
2299 ˇ);ˇ
2300 "});
2301
2302 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2303 cx.assert_editor_state(indoc! {"
2304 const a: A = (
2305 ˇ
2306 (
2307 ˇ
2308 const_function(),
2309 ˇ
2310 ˇ
2311 something_else,
2312 ˇ
2313 ˇ
2314 ˇ
2315 ˇ
2316 )
2317 ˇ
2318 );
2319 ˇ
2320 ˇ
2321 "});
2322}
2323
2324#[gpui::test]
2325async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2326 init_test(cx, |settings| {
2327 settings.defaults.tab_size = NonZeroU32::new(4)
2328 });
2329
2330 let language = Arc::new(Language::new(
2331 LanguageConfig {
2332 line_comments: vec!["//".into()],
2333 ..LanguageConfig::default()
2334 },
2335 None,
2336 ));
2337 {
2338 let mut cx = EditorTestContext::new(cx).await;
2339 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2340 cx.set_state(indoc! {"
2341 // Fooˇ
2342 "});
2343
2344 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2345 cx.assert_editor_state(indoc! {"
2346 // Foo
2347 //ˇ
2348 "});
2349 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2350 cx.set_state(indoc! {"
2351 ˇ// Foo
2352 "});
2353 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2354 cx.assert_editor_state(indoc! {"
2355
2356 ˇ// Foo
2357 "});
2358 }
2359 // Ensure that comment continuations can be disabled.
2360 update_test_language_settings(cx, |settings| {
2361 settings.defaults.extend_comment_on_newline = Some(false);
2362 });
2363 let mut cx = EditorTestContext::new(cx).await;
2364 cx.set_state(indoc! {"
2365 // Fooˇ
2366 "});
2367 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2368 cx.assert_editor_state(indoc! {"
2369 // Foo
2370 ˇ
2371 "});
2372}
2373
2374#[gpui::test]
2375fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2376 init_test(cx, |_| {});
2377
2378 let editor = cx.add_window(|cx| {
2379 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2380 let mut editor = build_editor(buffer.clone(), cx);
2381 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2382 editor
2383 });
2384
2385 _ = editor.update(cx, |editor, cx| {
2386 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2387 editor.buffer.update(cx, |buffer, cx| {
2388 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2389 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2390 });
2391 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2392
2393 editor.insert("Z", cx);
2394 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2395
2396 // The selections are moved after the inserted characters
2397 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2398 });
2399}
2400
2401#[gpui::test]
2402async fn test_tab(cx: &mut gpui::TestAppContext) {
2403 init_test(cx, |settings| {
2404 settings.defaults.tab_size = NonZeroU32::new(3)
2405 });
2406
2407 let mut cx = EditorTestContext::new(cx).await;
2408 cx.set_state(indoc! {"
2409 ˇabˇc
2410 ˇ🏀ˇ🏀ˇefg
2411 dˇ
2412 "});
2413 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2414 cx.assert_editor_state(indoc! {"
2415 ˇab ˇc
2416 ˇ🏀 ˇ🏀 ˇefg
2417 d ˇ
2418 "});
2419
2420 cx.set_state(indoc! {"
2421 a
2422 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2423 "});
2424 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2425 cx.assert_editor_state(indoc! {"
2426 a
2427 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2428 "});
2429}
2430
2431#[gpui::test]
2432async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2433 init_test(cx, |_| {});
2434
2435 let mut cx = EditorTestContext::new(cx).await;
2436 let language = Arc::new(
2437 Language::new(
2438 LanguageConfig::default(),
2439 Some(tree_sitter_rust::language()),
2440 )
2441 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2442 .unwrap(),
2443 );
2444 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2445
2446 // cursors that are already at the suggested indent level insert
2447 // a soft tab. cursors that are to the left of the suggested indent
2448 // auto-indent their line.
2449 cx.set_state(indoc! {"
2450 ˇ
2451 const a: B = (
2452 c(
2453 d(
2454 ˇ
2455 )
2456 ˇ
2457 ˇ )
2458 );
2459 "});
2460 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2461 cx.assert_editor_state(indoc! {"
2462 ˇ
2463 const a: B = (
2464 c(
2465 d(
2466 ˇ
2467 )
2468 ˇ
2469 ˇ)
2470 );
2471 "});
2472
2473 // handle auto-indent when there are multiple cursors on the same line
2474 cx.set_state(indoc! {"
2475 const a: B = (
2476 c(
2477 ˇ ˇ
2478 ˇ )
2479 );
2480 "});
2481 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2482 cx.assert_editor_state(indoc! {"
2483 const a: B = (
2484 c(
2485 ˇ
2486 ˇ)
2487 );
2488 "});
2489}
2490
2491#[gpui::test]
2492async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2493 init_test(cx, |settings| {
2494 settings.defaults.tab_size = NonZeroU32::new(4)
2495 });
2496
2497 let language = Arc::new(
2498 Language::new(
2499 LanguageConfig::default(),
2500 Some(tree_sitter_rust::language()),
2501 )
2502 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2503 .unwrap(),
2504 );
2505
2506 let mut cx = EditorTestContext::new(cx).await;
2507 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2508 cx.set_state(indoc! {"
2509 fn a() {
2510 if b {
2511 \t ˇc
2512 }
2513 }
2514 "});
2515
2516 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2517 cx.assert_editor_state(indoc! {"
2518 fn a() {
2519 if b {
2520 ˇc
2521 }
2522 }
2523 "});
2524}
2525
2526#[gpui::test]
2527async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2528 init_test(cx, |settings| {
2529 settings.defaults.tab_size = NonZeroU32::new(4);
2530 });
2531
2532 let mut cx = EditorTestContext::new(cx).await;
2533
2534 cx.set_state(indoc! {"
2535 «oneˇ» «twoˇ»
2536 three
2537 four
2538 "});
2539 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2540 cx.assert_editor_state(indoc! {"
2541 «oneˇ» «twoˇ»
2542 three
2543 four
2544 "});
2545
2546 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2547 cx.assert_editor_state(indoc! {"
2548 «oneˇ» «twoˇ»
2549 three
2550 four
2551 "});
2552
2553 // select across line ending
2554 cx.set_state(indoc! {"
2555 one two
2556 t«hree
2557 ˇ» four
2558 "});
2559 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2560 cx.assert_editor_state(indoc! {"
2561 one two
2562 t«hree
2563 ˇ» four
2564 "});
2565
2566 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2567 cx.assert_editor_state(indoc! {"
2568 one two
2569 t«hree
2570 ˇ» four
2571 "});
2572
2573 // Ensure that indenting/outdenting works when the cursor is at column 0.
2574 cx.set_state(indoc! {"
2575 one two
2576 ˇthree
2577 four
2578 "});
2579 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2580 cx.assert_editor_state(indoc! {"
2581 one two
2582 ˇthree
2583 four
2584 "});
2585
2586 cx.set_state(indoc! {"
2587 one two
2588 ˇ three
2589 four
2590 "});
2591 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2592 cx.assert_editor_state(indoc! {"
2593 one two
2594 ˇthree
2595 four
2596 "});
2597}
2598
2599#[gpui::test]
2600async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2601 init_test(cx, |settings| {
2602 settings.defaults.hard_tabs = Some(true);
2603 });
2604
2605 let mut cx = EditorTestContext::new(cx).await;
2606
2607 // select two ranges on one line
2608 cx.set_state(indoc! {"
2609 «oneˇ» «twoˇ»
2610 three
2611 four
2612 "});
2613 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2614 cx.assert_editor_state(indoc! {"
2615 \t«oneˇ» «twoˇ»
2616 three
2617 four
2618 "});
2619 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2620 cx.assert_editor_state(indoc! {"
2621 \t\t«oneˇ» «twoˇ»
2622 three
2623 four
2624 "});
2625 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2626 cx.assert_editor_state(indoc! {"
2627 \t«oneˇ» «twoˇ»
2628 three
2629 four
2630 "});
2631 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2632 cx.assert_editor_state(indoc! {"
2633 «oneˇ» «twoˇ»
2634 three
2635 four
2636 "});
2637
2638 // select across a line ending
2639 cx.set_state(indoc! {"
2640 one two
2641 t«hree
2642 ˇ»four
2643 "});
2644 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2645 cx.assert_editor_state(indoc! {"
2646 one two
2647 \tt«hree
2648 ˇ»four
2649 "});
2650 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2651 cx.assert_editor_state(indoc! {"
2652 one two
2653 \t\tt«hree
2654 ˇ»four
2655 "});
2656 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2657 cx.assert_editor_state(indoc! {"
2658 one two
2659 \tt«hree
2660 ˇ»four
2661 "});
2662 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2663 cx.assert_editor_state(indoc! {"
2664 one two
2665 t«hree
2666 ˇ»four
2667 "});
2668
2669 // Ensure that indenting/outdenting works when the cursor is at column 0.
2670 cx.set_state(indoc! {"
2671 one two
2672 ˇthree
2673 four
2674 "});
2675 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2676 cx.assert_editor_state(indoc! {"
2677 one two
2678 ˇthree
2679 four
2680 "});
2681 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2682 cx.assert_editor_state(indoc! {"
2683 one two
2684 \tˇthree
2685 four
2686 "});
2687 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2688 cx.assert_editor_state(indoc! {"
2689 one two
2690 ˇthree
2691 four
2692 "});
2693}
2694
2695#[gpui::test]
2696fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2697 init_test(cx, |settings| {
2698 settings.languages.extend([
2699 (
2700 "TOML".into(),
2701 LanguageSettingsContent {
2702 tab_size: NonZeroU32::new(2),
2703 ..Default::default()
2704 },
2705 ),
2706 (
2707 "Rust".into(),
2708 LanguageSettingsContent {
2709 tab_size: NonZeroU32::new(4),
2710 ..Default::default()
2711 },
2712 ),
2713 ]);
2714 });
2715
2716 let toml_language = Arc::new(Language::new(
2717 LanguageConfig {
2718 name: "TOML".into(),
2719 ..Default::default()
2720 },
2721 None,
2722 ));
2723 let rust_language = Arc::new(Language::new(
2724 LanguageConfig {
2725 name: "Rust".into(),
2726 ..Default::default()
2727 },
2728 None,
2729 ));
2730
2731 let toml_buffer =
2732 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2733 let rust_buffer = cx.new_model(|cx| {
2734 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2735 });
2736 let multibuffer = cx.new_model(|cx| {
2737 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
2738 multibuffer.push_excerpts(
2739 toml_buffer.clone(),
2740 [ExcerptRange {
2741 context: Point::new(0, 0)..Point::new(2, 0),
2742 primary: None,
2743 }],
2744 cx,
2745 );
2746 multibuffer.push_excerpts(
2747 rust_buffer.clone(),
2748 [ExcerptRange {
2749 context: Point::new(0, 0)..Point::new(1, 0),
2750 primary: None,
2751 }],
2752 cx,
2753 );
2754 multibuffer
2755 });
2756
2757 cx.add_window(|cx| {
2758 let mut editor = build_editor(multibuffer, cx);
2759
2760 assert_eq!(
2761 editor.text(cx),
2762 indoc! {"
2763 a = 1
2764 b = 2
2765
2766 const c: usize = 3;
2767 "}
2768 );
2769
2770 select_ranges(
2771 &mut editor,
2772 indoc! {"
2773 «aˇ» = 1
2774 b = 2
2775
2776 «const c:ˇ» usize = 3;
2777 "},
2778 cx,
2779 );
2780
2781 editor.tab(&Tab, cx);
2782 assert_text_with_selections(
2783 &mut editor,
2784 indoc! {"
2785 «aˇ» = 1
2786 b = 2
2787
2788 «const c:ˇ» usize = 3;
2789 "},
2790 cx,
2791 );
2792 editor.tab_prev(&TabPrev, cx);
2793 assert_text_with_selections(
2794 &mut editor,
2795 indoc! {"
2796 «aˇ» = 1
2797 b = 2
2798
2799 «const c:ˇ» usize = 3;
2800 "},
2801 cx,
2802 );
2803
2804 editor
2805 });
2806}
2807
2808#[gpui::test]
2809async fn test_backspace(cx: &mut gpui::TestAppContext) {
2810 init_test(cx, |_| {});
2811
2812 let mut cx = EditorTestContext::new(cx).await;
2813
2814 // Basic backspace
2815 cx.set_state(indoc! {"
2816 onˇe two three
2817 fou«rˇ» five six
2818 seven «ˇeight nine
2819 »ten
2820 "});
2821 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2822 cx.assert_editor_state(indoc! {"
2823 oˇe two three
2824 fouˇ five six
2825 seven ˇten
2826 "});
2827
2828 // Test backspace inside and around indents
2829 cx.set_state(indoc! {"
2830 zero
2831 ˇone
2832 ˇtwo
2833 ˇ ˇ ˇ three
2834 ˇ ˇ four
2835 "});
2836 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2837 cx.assert_editor_state(indoc! {"
2838 zero
2839 ˇone
2840 ˇtwo
2841 ˇ threeˇ four
2842 "});
2843
2844 // Test backspace with line_mode set to true
2845 cx.update_editor(|e, _| e.selections.line_mode = true);
2846 cx.set_state(indoc! {"
2847 The ˇquick ˇbrown
2848 fox jumps over
2849 the lazy dog
2850 ˇThe qu«ick bˇ»rown"});
2851 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2852 cx.assert_editor_state(indoc! {"
2853 ˇfox jumps over
2854 the lazy dogˇ"});
2855}
2856
2857#[gpui::test]
2858async fn test_delete(cx: &mut gpui::TestAppContext) {
2859 init_test(cx, |_| {});
2860
2861 let mut cx = EditorTestContext::new(cx).await;
2862 cx.set_state(indoc! {"
2863 onˇe two three
2864 fou«rˇ» five six
2865 seven «ˇeight nine
2866 »ten
2867 "});
2868 cx.update_editor(|e, cx| e.delete(&Delete, cx));
2869 cx.assert_editor_state(indoc! {"
2870 onˇ two three
2871 fouˇ five six
2872 seven ˇten
2873 "});
2874
2875 // Test backspace with line_mode set to true
2876 cx.update_editor(|e, _| e.selections.line_mode = true);
2877 cx.set_state(indoc! {"
2878 The ˇquick ˇbrown
2879 fox «ˇjum»ps over
2880 the lazy dog
2881 ˇThe qu«ick bˇ»rown"});
2882 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2883 cx.assert_editor_state("ˇthe lazy dogˇ");
2884}
2885
2886#[gpui::test]
2887fn test_delete_line(cx: &mut TestAppContext) {
2888 init_test(cx, |_| {});
2889
2890 let view = cx.add_window(|cx| {
2891 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2892 build_editor(buffer, cx)
2893 });
2894 _ = view.update(cx, |view, cx| {
2895 view.change_selections(None, cx, |s| {
2896 s.select_display_ranges([
2897 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
2898 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
2899 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
2900 ])
2901 });
2902 view.delete_line(&DeleteLine, cx);
2903 assert_eq!(view.display_text(cx), "ghi");
2904 assert_eq!(
2905 view.selections.display_ranges(cx),
2906 vec![
2907 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
2908 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
2909 ]
2910 );
2911 });
2912
2913 let view = cx.add_window(|cx| {
2914 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2915 build_editor(buffer, cx)
2916 });
2917 _ = view.update(cx, |view, cx| {
2918 view.change_selections(None, cx, |s| {
2919 s.select_display_ranges([
2920 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
2921 ])
2922 });
2923 view.delete_line(&DeleteLine, cx);
2924 assert_eq!(view.display_text(cx), "ghi\n");
2925 assert_eq!(
2926 view.selections.display_ranges(cx),
2927 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
2928 );
2929 });
2930}
2931
2932#[gpui::test]
2933fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
2934 init_test(cx, |_| {});
2935
2936 cx.add_window(|cx| {
2937 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
2938 let mut editor = build_editor(buffer.clone(), cx);
2939 let buffer = buffer.read(cx).as_singleton().unwrap();
2940
2941 assert_eq!(
2942 editor.selections.ranges::<Point>(cx),
2943 &[Point::new(0, 0)..Point::new(0, 0)]
2944 );
2945
2946 // When on single line, replace newline at end by space
2947 editor.join_lines(&JoinLines, cx);
2948 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
2949 assert_eq!(
2950 editor.selections.ranges::<Point>(cx),
2951 &[Point::new(0, 3)..Point::new(0, 3)]
2952 );
2953
2954 // When multiple lines are selected, remove newlines that are spanned by the selection
2955 editor.change_selections(None, cx, |s| {
2956 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
2957 });
2958 editor.join_lines(&JoinLines, cx);
2959 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
2960 assert_eq!(
2961 editor.selections.ranges::<Point>(cx),
2962 &[Point::new(0, 11)..Point::new(0, 11)]
2963 );
2964
2965 // Undo should be transactional
2966 editor.undo(&Undo, cx);
2967 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
2968 assert_eq!(
2969 editor.selections.ranges::<Point>(cx),
2970 &[Point::new(0, 5)..Point::new(2, 2)]
2971 );
2972
2973 // When joining an empty line don't insert a space
2974 editor.change_selections(None, cx, |s| {
2975 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
2976 });
2977 editor.join_lines(&JoinLines, cx);
2978 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
2979 assert_eq!(
2980 editor.selections.ranges::<Point>(cx),
2981 [Point::new(2, 3)..Point::new(2, 3)]
2982 );
2983
2984 // We can remove trailing newlines
2985 editor.join_lines(&JoinLines, cx);
2986 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
2987 assert_eq!(
2988 editor.selections.ranges::<Point>(cx),
2989 [Point::new(2, 3)..Point::new(2, 3)]
2990 );
2991
2992 // We don't blow up on the last line
2993 editor.join_lines(&JoinLines, cx);
2994 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
2995 assert_eq!(
2996 editor.selections.ranges::<Point>(cx),
2997 [Point::new(2, 3)..Point::new(2, 3)]
2998 );
2999
3000 // reset to test indentation
3001 editor.buffer.update(cx, |buffer, cx| {
3002 buffer.edit(
3003 [
3004 (Point::new(1, 0)..Point::new(1, 2), " "),
3005 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3006 ],
3007 None,
3008 cx,
3009 )
3010 });
3011
3012 // We remove any leading spaces
3013 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3014 editor.change_selections(None, cx, |s| {
3015 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3016 });
3017 editor.join_lines(&JoinLines, cx);
3018 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3019
3020 // We don't insert a space for a line containing only spaces
3021 editor.join_lines(&JoinLines, cx);
3022 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3023
3024 // We ignore any leading tabs
3025 editor.join_lines(&JoinLines, cx);
3026 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3027
3028 editor
3029 });
3030}
3031
3032#[gpui::test]
3033fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3034 init_test(cx, |_| {});
3035
3036 cx.add_window(|cx| {
3037 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3038 let mut editor = build_editor(buffer.clone(), cx);
3039 let buffer = buffer.read(cx).as_singleton().unwrap();
3040
3041 editor.change_selections(None, cx, |s| {
3042 s.select_ranges([
3043 Point::new(0, 2)..Point::new(1, 1),
3044 Point::new(1, 2)..Point::new(1, 2),
3045 Point::new(3, 1)..Point::new(3, 2),
3046 ])
3047 });
3048
3049 editor.join_lines(&JoinLines, cx);
3050 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3051
3052 assert_eq!(
3053 editor.selections.ranges::<Point>(cx),
3054 [
3055 Point::new(0, 7)..Point::new(0, 7),
3056 Point::new(1, 3)..Point::new(1, 3)
3057 ]
3058 );
3059 editor
3060 });
3061}
3062
3063#[gpui::test]
3064async fn test_join_lines_with_git_diff_base(
3065 executor: BackgroundExecutor,
3066 cx: &mut gpui::TestAppContext,
3067) {
3068 init_test(cx, |_| {});
3069
3070 let mut cx = EditorTestContext::new(cx).await;
3071
3072 let diff_base = r#"
3073 Line 0
3074 Line 1
3075 Line 2
3076 Line 3
3077 "#
3078 .unindent();
3079
3080 cx.set_state(
3081 &r#"
3082 ˇLine 0
3083 Line 1
3084 Line 2
3085 Line 3
3086 "#
3087 .unindent(),
3088 );
3089
3090 cx.set_diff_base(Some(&diff_base));
3091 executor.run_until_parked();
3092
3093 // Join lines
3094 cx.update_editor(|editor, cx| {
3095 editor.join_lines(&JoinLines, cx);
3096 });
3097 executor.run_until_parked();
3098
3099 cx.assert_editor_state(
3100 &r#"
3101 Line 0ˇ Line 1
3102 Line 2
3103 Line 3
3104 "#
3105 .unindent(),
3106 );
3107 // Join again
3108 cx.update_editor(|editor, cx| {
3109 editor.join_lines(&JoinLines, cx);
3110 });
3111 executor.run_until_parked();
3112
3113 cx.assert_editor_state(
3114 &r#"
3115 Line 0 Line 1ˇ Line 2
3116 Line 3
3117 "#
3118 .unindent(),
3119 );
3120}
3121
3122#[gpui::test]
3123async fn test_custom_newlines_cause_no_false_positive_diffs(
3124 executor: BackgroundExecutor,
3125 cx: &mut gpui::TestAppContext,
3126) {
3127 init_test(cx, |_| {});
3128 let mut cx = EditorTestContext::new(cx).await;
3129 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3130 cx.set_diff_base(Some("Line 0\r\nLine 1\r\nLine 2\r\nLine 3"));
3131 executor.run_until_parked();
3132
3133 cx.update_editor(|editor, cx| {
3134 assert_eq!(
3135 editor
3136 .buffer()
3137 .read(cx)
3138 .snapshot(cx)
3139 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
3140 .collect::<Vec<_>>(),
3141 Vec::new(),
3142 "Should not have any diffs for files with custom newlines"
3143 );
3144 });
3145}
3146
3147#[gpui::test]
3148async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3149 init_test(cx, |_| {});
3150
3151 let mut cx = EditorTestContext::new(cx).await;
3152
3153 // Test sort_lines_case_insensitive()
3154 cx.set_state(indoc! {"
3155 «z
3156 y
3157 x
3158 Z
3159 Y
3160 Xˇ»
3161 "});
3162 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3163 cx.assert_editor_state(indoc! {"
3164 «x
3165 X
3166 y
3167 Y
3168 z
3169 Zˇ»
3170 "});
3171
3172 // Test reverse_lines()
3173 cx.set_state(indoc! {"
3174 «5
3175 4
3176 3
3177 2
3178 1ˇ»
3179 "});
3180 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3181 cx.assert_editor_state(indoc! {"
3182 «1
3183 2
3184 3
3185 4
3186 5ˇ»
3187 "});
3188
3189 // Skip testing shuffle_line()
3190
3191 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3192 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3193
3194 // Don't manipulate when cursor is on single line, but expand the selection
3195 cx.set_state(indoc! {"
3196 ddˇdd
3197 ccc
3198 bb
3199 a
3200 "});
3201 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3202 cx.assert_editor_state(indoc! {"
3203 «ddddˇ»
3204 ccc
3205 bb
3206 a
3207 "});
3208
3209 // Basic manipulate case
3210 // Start selection moves to column 0
3211 // End of selection shrinks to fit shorter line
3212 cx.set_state(indoc! {"
3213 dd«d
3214 ccc
3215 bb
3216 aaaaaˇ»
3217 "});
3218 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3219 cx.assert_editor_state(indoc! {"
3220 «aaaaa
3221 bb
3222 ccc
3223 dddˇ»
3224 "});
3225
3226 // Manipulate case with newlines
3227 cx.set_state(indoc! {"
3228 dd«d
3229 ccc
3230
3231 bb
3232 aaaaa
3233
3234 ˇ»
3235 "});
3236 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3237 cx.assert_editor_state(indoc! {"
3238 «
3239
3240 aaaaa
3241 bb
3242 ccc
3243 dddˇ»
3244
3245 "});
3246
3247 // Adding new line
3248 cx.set_state(indoc! {"
3249 aa«a
3250 bbˇ»b
3251 "});
3252 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3253 cx.assert_editor_state(indoc! {"
3254 «aaa
3255 bbb
3256 added_lineˇ»
3257 "});
3258
3259 // Removing line
3260 cx.set_state(indoc! {"
3261 aa«a
3262 bbbˇ»
3263 "});
3264 cx.update_editor(|e, cx| {
3265 e.manipulate_lines(cx, |lines| {
3266 lines.pop();
3267 })
3268 });
3269 cx.assert_editor_state(indoc! {"
3270 «aaaˇ»
3271 "});
3272
3273 // Removing all lines
3274 cx.set_state(indoc! {"
3275 aa«a
3276 bbbˇ»
3277 "});
3278 cx.update_editor(|e, cx| {
3279 e.manipulate_lines(cx, |lines| {
3280 lines.drain(..);
3281 })
3282 });
3283 cx.assert_editor_state(indoc! {"
3284 ˇ
3285 "});
3286}
3287
3288#[gpui::test]
3289async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3290 init_test(cx, |_| {});
3291
3292 let mut cx = EditorTestContext::new(cx).await;
3293
3294 // Consider continuous selection as single selection
3295 cx.set_state(indoc! {"
3296 Aaa«aa
3297 cˇ»c«c
3298 bb
3299 aaaˇ»aa
3300 "});
3301 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3302 cx.assert_editor_state(indoc! {"
3303 «Aaaaa
3304 ccc
3305 bb
3306 aaaaaˇ»
3307 "});
3308
3309 cx.set_state(indoc! {"
3310 Aaa«aa
3311 cˇ»c«c
3312 bb
3313 aaaˇ»aa
3314 "});
3315 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3316 cx.assert_editor_state(indoc! {"
3317 «Aaaaa
3318 ccc
3319 bbˇ»
3320 "});
3321
3322 // Consider non continuous selection as distinct dedup operations
3323 cx.set_state(indoc! {"
3324 «aaaaa
3325 bb
3326 aaaaa
3327 aaaaaˇ»
3328
3329 aaa«aaˇ»
3330 "});
3331 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3332 cx.assert_editor_state(indoc! {"
3333 «aaaaa
3334 bbˇ»
3335
3336 «aaaaaˇ»
3337 "});
3338}
3339
3340#[gpui::test]
3341async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3342 init_test(cx, |_| {});
3343
3344 let mut cx = EditorTestContext::new(cx).await;
3345
3346 cx.set_state(indoc! {"
3347 «Aaa
3348 aAa
3349 Aaaˇ»
3350 "});
3351 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3352 cx.assert_editor_state(indoc! {"
3353 «Aaa
3354 aAaˇ»
3355 "});
3356
3357 cx.set_state(indoc! {"
3358 «Aaa
3359 aAa
3360 aaAˇ»
3361 "});
3362 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3363 cx.assert_editor_state(indoc! {"
3364 «Aaaˇ»
3365 "});
3366}
3367
3368#[gpui::test]
3369async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3370 init_test(cx, |_| {});
3371
3372 let mut cx = EditorTestContext::new(cx).await;
3373
3374 // Manipulate with multiple selections on a single line
3375 cx.set_state(indoc! {"
3376 dd«dd
3377 cˇ»c«c
3378 bb
3379 aaaˇ»aa
3380 "});
3381 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3382 cx.assert_editor_state(indoc! {"
3383 «aaaaa
3384 bb
3385 ccc
3386 ddddˇ»
3387 "});
3388
3389 // Manipulate with multiple disjoin selections
3390 cx.set_state(indoc! {"
3391 5«
3392 4
3393 3
3394 2
3395 1ˇ»
3396
3397 dd«dd
3398 ccc
3399 bb
3400 aaaˇ»aa
3401 "});
3402 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3403 cx.assert_editor_state(indoc! {"
3404 «1
3405 2
3406 3
3407 4
3408 5ˇ»
3409
3410 «aaaaa
3411 bb
3412 ccc
3413 ddddˇ»
3414 "});
3415
3416 // Adding lines on each selection
3417 cx.set_state(indoc! {"
3418 2«
3419 1ˇ»
3420
3421 bb«bb
3422 aaaˇ»aa
3423 "});
3424 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3425 cx.assert_editor_state(indoc! {"
3426 «2
3427 1
3428 added lineˇ»
3429
3430 «bbbb
3431 aaaaa
3432 added lineˇ»
3433 "});
3434
3435 // Removing lines on each selection
3436 cx.set_state(indoc! {"
3437 2«
3438 1ˇ»
3439
3440 bb«bb
3441 aaaˇ»aa
3442 "});
3443 cx.update_editor(|e, cx| {
3444 e.manipulate_lines(cx, |lines| {
3445 lines.pop();
3446 })
3447 });
3448 cx.assert_editor_state(indoc! {"
3449 «2ˇ»
3450
3451 «bbbbˇ»
3452 "});
3453}
3454
3455#[gpui::test]
3456async fn test_manipulate_text(cx: &mut TestAppContext) {
3457 init_test(cx, |_| {});
3458
3459 let mut cx = EditorTestContext::new(cx).await;
3460
3461 // Test convert_to_upper_case()
3462 cx.set_state(indoc! {"
3463 «hello worldˇ»
3464 "});
3465 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3466 cx.assert_editor_state(indoc! {"
3467 «HELLO WORLDˇ»
3468 "});
3469
3470 // Test convert_to_lower_case()
3471 cx.set_state(indoc! {"
3472 «HELLO WORLDˇ»
3473 "});
3474 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3475 cx.assert_editor_state(indoc! {"
3476 «hello worldˇ»
3477 "});
3478
3479 // Test multiple line, single selection case
3480 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3481 cx.set_state(indoc! {"
3482 «The quick brown
3483 fox jumps over
3484 the lazy dogˇ»
3485 "});
3486 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3487 cx.assert_editor_state(indoc! {"
3488 «The Quick Brown
3489 Fox Jumps Over
3490 The Lazy Dogˇ»
3491 "});
3492
3493 // Test multiple line, single selection case
3494 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3495 cx.set_state(indoc! {"
3496 «The quick brown
3497 fox jumps over
3498 the lazy dogˇ»
3499 "});
3500 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3501 cx.assert_editor_state(indoc! {"
3502 «TheQuickBrown
3503 FoxJumpsOver
3504 TheLazyDogˇ»
3505 "});
3506
3507 // From here on out, test more complex cases of manipulate_text()
3508
3509 // Test no selection case - should affect words cursors are in
3510 // Cursor at beginning, middle, and end of word
3511 cx.set_state(indoc! {"
3512 ˇhello big beauˇtiful worldˇ
3513 "});
3514 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3515 cx.assert_editor_state(indoc! {"
3516 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3517 "});
3518
3519 // Test multiple selections on a single line and across multiple lines
3520 cx.set_state(indoc! {"
3521 «Theˇ» quick «brown
3522 foxˇ» jumps «overˇ»
3523 the «lazyˇ» dog
3524 "});
3525 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3526 cx.assert_editor_state(indoc! {"
3527 «THEˇ» quick «BROWN
3528 FOXˇ» jumps «OVERˇ»
3529 the «LAZYˇ» dog
3530 "});
3531
3532 // Test case where text length grows
3533 cx.set_state(indoc! {"
3534 «tschüߡ»
3535 "});
3536 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3537 cx.assert_editor_state(indoc! {"
3538 «TSCHÜSSˇ»
3539 "});
3540
3541 // Test to make sure we don't crash when text shrinks
3542 cx.set_state(indoc! {"
3543 aaa_bbbˇ
3544 "});
3545 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3546 cx.assert_editor_state(indoc! {"
3547 «aaaBbbˇ»
3548 "});
3549
3550 // Test to make sure we all aware of the fact that each word can grow and shrink
3551 // Final selections should be aware of this fact
3552 cx.set_state(indoc! {"
3553 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3554 "});
3555 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3556 cx.assert_editor_state(indoc! {"
3557 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3558 "});
3559
3560 cx.set_state(indoc! {"
3561 «hElLo, WoRld!ˇ»
3562 "});
3563 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3564 cx.assert_editor_state(indoc! {"
3565 «HeLlO, wOrLD!ˇ»
3566 "});
3567}
3568
3569#[gpui::test]
3570fn test_duplicate_line(cx: &mut TestAppContext) {
3571 init_test(cx, |_| {});
3572
3573 let view = cx.add_window(|cx| {
3574 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3575 build_editor(buffer, cx)
3576 });
3577 _ = view.update(cx, |view, cx| {
3578 view.change_selections(None, cx, |s| {
3579 s.select_display_ranges([
3580 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3581 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3582 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3583 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3584 ])
3585 });
3586 view.duplicate_line_down(&DuplicateLineDown, cx);
3587 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3588 assert_eq!(
3589 view.selections.display_ranges(cx),
3590 vec![
3591 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3592 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3593 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3594 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3595 ]
3596 );
3597 });
3598
3599 let view = cx.add_window(|cx| {
3600 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3601 build_editor(buffer, cx)
3602 });
3603 _ = view.update(cx, |view, cx| {
3604 view.change_selections(None, cx, |s| {
3605 s.select_display_ranges([
3606 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3607 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3608 ])
3609 });
3610 view.duplicate_line_down(&DuplicateLineDown, cx);
3611 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3612 assert_eq!(
3613 view.selections.display_ranges(cx),
3614 vec![
3615 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3616 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3617 ]
3618 );
3619 });
3620
3621 // With `move_upwards` the selections stay in place, except for
3622 // the lines inserted above them
3623 let view = cx.add_window(|cx| {
3624 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3625 build_editor(buffer, cx)
3626 });
3627 _ = view.update(cx, |view, cx| {
3628 view.change_selections(None, cx, |s| {
3629 s.select_display_ranges([
3630 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3631 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3632 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3633 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3634 ])
3635 });
3636 view.duplicate_line_up(&DuplicateLineUp, cx);
3637 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3638 assert_eq!(
3639 view.selections.display_ranges(cx),
3640 vec![
3641 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3642 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3643 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3644 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3645 ]
3646 );
3647 });
3648
3649 let view = cx.add_window(|cx| {
3650 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3651 build_editor(buffer, cx)
3652 });
3653 _ = view.update(cx, |view, cx| {
3654 view.change_selections(None, cx, |s| {
3655 s.select_display_ranges([
3656 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3657 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3658 ])
3659 });
3660 view.duplicate_line_up(&DuplicateLineUp, cx);
3661 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3662 assert_eq!(
3663 view.selections.display_ranges(cx),
3664 vec![
3665 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3666 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3667 ]
3668 );
3669 });
3670}
3671
3672#[gpui::test]
3673fn test_move_line_up_down(cx: &mut TestAppContext) {
3674 init_test(cx, |_| {});
3675
3676 let view = cx.add_window(|cx| {
3677 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3678 build_editor(buffer, cx)
3679 });
3680 _ = view.update(cx, |view, cx| {
3681 view.fold_ranges(
3682 vec![
3683 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3684 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3685 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3686 ],
3687 true,
3688 cx,
3689 );
3690 view.change_selections(None, cx, |s| {
3691 s.select_display_ranges([
3692 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3693 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3694 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3695 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3696 ])
3697 });
3698 assert_eq!(
3699 view.display_text(cx),
3700 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3701 );
3702
3703 view.move_line_up(&MoveLineUp, cx);
3704 assert_eq!(
3705 view.display_text(cx),
3706 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3707 );
3708 assert_eq!(
3709 view.selections.display_ranges(cx),
3710 vec![
3711 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3712 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3713 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3714 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3715 ]
3716 );
3717 });
3718
3719 _ = view.update(cx, |view, cx| {
3720 view.move_line_down(&MoveLineDown, cx);
3721 assert_eq!(
3722 view.display_text(cx),
3723 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3724 );
3725 assert_eq!(
3726 view.selections.display_ranges(cx),
3727 vec![
3728 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3729 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3730 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3731 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3732 ]
3733 );
3734 });
3735
3736 _ = view.update(cx, |view, cx| {
3737 view.move_line_down(&MoveLineDown, cx);
3738 assert_eq!(
3739 view.display_text(cx),
3740 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3741 );
3742 assert_eq!(
3743 view.selections.display_ranges(cx),
3744 vec![
3745 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3746 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3747 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3748 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3749 ]
3750 );
3751 });
3752
3753 _ = view.update(cx, |view, cx| {
3754 view.move_line_up(&MoveLineUp, cx);
3755 assert_eq!(
3756 view.display_text(cx),
3757 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3758 );
3759 assert_eq!(
3760 view.selections.display_ranges(cx),
3761 vec![
3762 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3763 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3764 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3765 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3766 ]
3767 );
3768 });
3769}
3770
3771#[gpui::test]
3772fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3773 init_test(cx, |_| {});
3774
3775 let editor = cx.add_window(|cx| {
3776 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3777 build_editor(buffer, cx)
3778 });
3779 _ = editor.update(cx, |editor, cx| {
3780 let snapshot = editor.buffer.read(cx).snapshot(cx);
3781 editor.insert_blocks(
3782 [BlockProperties {
3783 style: BlockStyle::Fixed,
3784 position: snapshot.anchor_after(Point::new(2, 0)),
3785 disposition: BlockDisposition::Below,
3786 height: 1,
3787 render: Box::new(|_| div().into_any()),
3788 priority: 0,
3789 }],
3790 Some(Autoscroll::fit()),
3791 cx,
3792 );
3793 editor.change_selections(None, cx, |s| {
3794 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
3795 });
3796 editor.move_line_down(&MoveLineDown, cx);
3797 });
3798}
3799
3800#[gpui::test]
3801fn test_transpose(cx: &mut TestAppContext) {
3802 init_test(cx, |_| {});
3803
3804 _ = cx.add_window(|cx| {
3805 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
3806 editor.set_style(EditorStyle::default(), cx);
3807 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
3808 editor.transpose(&Default::default(), cx);
3809 assert_eq!(editor.text(cx), "bac");
3810 assert_eq!(editor.selections.ranges(cx), [2..2]);
3811
3812 editor.transpose(&Default::default(), cx);
3813 assert_eq!(editor.text(cx), "bca");
3814 assert_eq!(editor.selections.ranges(cx), [3..3]);
3815
3816 editor.transpose(&Default::default(), cx);
3817 assert_eq!(editor.text(cx), "bac");
3818 assert_eq!(editor.selections.ranges(cx), [3..3]);
3819
3820 editor
3821 });
3822
3823 _ = cx.add_window(|cx| {
3824 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3825 editor.set_style(EditorStyle::default(), cx);
3826 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
3827 editor.transpose(&Default::default(), cx);
3828 assert_eq!(editor.text(cx), "acb\nde");
3829 assert_eq!(editor.selections.ranges(cx), [3..3]);
3830
3831 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3832 editor.transpose(&Default::default(), cx);
3833 assert_eq!(editor.text(cx), "acbd\ne");
3834 assert_eq!(editor.selections.ranges(cx), [5..5]);
3835
3836 editor.transpose(&Default::default(), cx);
3837 assert_eq!(editor.text(cx), "acbde\n");
3838 assert_eq!(editor.selections.ranges(cx), [6..6]);
3839
3840 editor.transpose(&Default::default(), cx);
3841 assert_eq!(editor.text(cx), "acbd\ne");
3842 assert_eq!(editor.selections.ranges(cx), [6..6]);
3843
3844 editor
3845 });
3846
3847 _ = cx.add_window(|cx| {
3848 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3849 editor.set_style(EditorStyle::default(), cx);
3850 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
3851 editor.transpose(&Default::default(), cx);
3852 assert_eq!(editor.text(cx), "bacd\ne");
3853 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
3854
3855 editor.transpose(&Default::default(), cx);
3856 assert_eq!(editor.text(cx), "bcade\n");
3857 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
3858
3859 editor.transpose(&Default::default(), cx);
3860 assert_eq!(editor.text(cx), "bcda\ne");
3861 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3862
3863 editor.transpose(&Default::default(), cx);
3864 assert_eq!(editor.text(cx), "bcade\n");
3865 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3866
3867 editor.transpose(&Default::default(), cx);
3868 assert_eq!(editor.text(cx), "bcaed\n");
3869 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
3870
3871 editor
3872 });
3873
3874 _ = cx.add_window(|cx| {
3875 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
3876 editor.set_style(EditorStyle::default(), cx);
3877 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3878 editor.transpose(&Default::default(), cx);
3879 assert_eq!(editor.text(cx), "🏀🍐✋");
3880 assert_eq!(editor.selections.ranges(cx), [8..8]);
3881
3882 editor.transpose(&Default::default(), cx);
3883 assert_eq!(editor.text(cx), "🏀✋🍐");
3884 assert_eq!(editor.selections.ranges(cx), [11..11]);
3885
3886 editor.transpose(&Default::default(), cx);
3887 assert_eq!(editor.text(cx), "🏀🍐✋");
3888 assert_eq!(editor.selections.ranges(cx), [11..11]);
3889
3890 editor
3891 });
3892}
3893
3894#[gpui::test]
3895async fn test_clipboard(cx: &mut gpui::TestAppContext) {
3896 init_test(cx, |_| {});
3897
3898 let mut cx = EditorTestContext::new(cx).await;
3899
3900 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
3901 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3902 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
3903
3904 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
3905 cx.set_state("two ˇfour ˇsix ˇ");
3906 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3907 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
3908
3909 // Paste again but with only two cursors. Since the number of cursors doesn't
3910 // match the number of slices in the clipboard, the entire clipboard text
3911 // is pasted at each cursor.
3912 cx.set_state("ˇtwo one✅ four three six five ˇ");
3913 cx.update_editor(|e, cx| {
3914 e.handle_input("( ", cx);
3915 e.paste(&Paste, cx);
3916 e.handle_input(") ", cx);
3917 });
3918 cx.assert_editor_state(
3919 &([
3920 "( one✅ ",
3921 "three ",
3922 "five ) ˇtwo one✅ four three six five ( one✅ ",
3923 "three ",
3924 "five ) ˇ",
3925 ]
3926 .join("\n")),
3927 );
3928
3929 // Cut with three selections, one of which is full-line.
3930 cx.set_state(indoc! {"
3931 1«2ˇ»3
3932 4ˇ567
3933 «8ˇ»9"});
3934 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3935 cx.assert_editor_state(indoc! {"
3936 1ˇ3
3937 ˇ9"});
3938
3939 // Paste with three selections, noticing how the copied selection that was full-line
3940 // gets inserted before the second cursor.
3941 cx.set_state(indoc! {"
3942 1ˇ3
3943 9ˇ
3944 «oˇ»ne"});
3945 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3946 cx.assert_editor_state(indoc! {"
3947 12ˇ3
3948 4567
3949 9ˇ
3950 8ˇne"});
3951
3952 // Copy with a single cursor only, which writes the whole line into the clipboard.
3953 cx.set_state(indoc! {"
3954 The quick brown
3955 fox juˇmps over
3956 the lazy dog"});
3957 cx.update_editor(|e, cx| e.copy(&Copy, cx));
3958 assert_eq!(
3959 cx.read_from_clipboard()
3960 .and_then(|item| item.text().as_deref().map(str::to_string)),
3961 Some("fox jumps over\n".to_string())
3962 );
3963
3964 // Paste with three selections, noticing how the copied full-line selection is inserted
3965 // before the empty selections but replaces the selection that is non-empty.
3966 cx.set_state(indoc! {"
3967 Tˇhe quick brown
3968 «foˇ»x jumps over
3969 tˇhe lazy dog"});
3970 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3971 cx.assert_editor_state(indoc! {"
3972 fox jumps over
3973 Tˇhe quick brown
3974 fox jumps over
3975 ˇx jumps over
3976 fox jumps over
3977 tˇhe lazy dog"});
3978}
3979
3980#[gpui::test]
3981async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
3982 init_test(cx, |_| {});
3983
3984 let mut cx = EditorTestContext::new(cx).await;
3985 let language = Arc::new(Language::new(
3986 LanguageConfig::default(),
3987 Some(tree_sitter_rust::language()),
3988 ));
3989 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
3990
3991 // Cut an indented block, without the leading whitespace.
3992 cx.set_state(indoc! {"
3993 const a: B = (
3994 c(),
3995 «d(
3996 e,
3997 f
3998 )ˇ»
3999 );
4000 "});
4001 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4002 cx.assert_editor_state(indoc! {"
4003 const a: B = (
4004 c(),
4005 ˇ
4006 );
4007 "});
4008
4009 // Paste it at the same position.
4010 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4011 cx.assert_editor_state(indoc! {"
4012 const a: B = (
4013 c(),
4014 d(
4015 e,
4016 f
4017 )ˇ
4018 );
4019 "});
4020
4021 // Paste it at a line with a lower indent level.
4022 cx.set_state(indoc! {"
4023 ˇ
4024 const a: B = (
4025 c(),
4026 );
4027 "});
4028 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4029 cx.assert_editor_state(indoc! {"
4030 d(
4031 e,
4032 f
4033 )ˇ
4034 const a: B = (
4035 c(),
4036 );
4037 "});
4038
4039 // Cut an indented block, with the leading whitespace.
4040 cx.set_state(indoc! {"
4041 const a: B = (
4042 c(),
4043 « d(
4044 e,
4045 f
4046 )
4047 ˇ»);
4048 "});
4049 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4050 cx.assert_editor_state(indoc! {"
4051 const a: B = (
4052 c(),
4053 ˇ);
4054 "});
4055
4056 // Paste it at the same position.
4057 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4058 cx.assert_editor_state(indoc! {"
4059 const a: B = (
4060 c(),
4061 d(
4062 e,
4063 f
4064 )
4065 ˇ);
4066 "});
4067
4068 // Paste it at a line with a higher indent level.
4069 cx.set_state(indoc! {"
4070 const a: B = (
4071 c(),
4072 d(
4073 e,
4074 fˇ
4075 )
4076 );
4077 "});
4078 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4079 cx.assert_editor_state(indoc! {"
4080 const a: B = (
4081 c(),
4082 d(
4083 e,
4084 f d(
4085 e,
4086 f
4087 )
4088 ˇ
4089 )
4090 );
4091 "});
4092}
4093
4094#[gpui::test]
4095fn test_select_all(cx: &mut TestAppContext) {
4096 init_test(cx, |_| {});
4097
4098 let view = cx.add_window(|cx| {
4099 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4100 build_editor(buffer, cx)
4101 });
4102 _ = view.update(cx, |view, cx| {
4103 view.select_all(&SelectAll, cx);
4104 assert_eq!(
4105 view.selections.display_ranges(cx),
4106 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4107 );
4108 });
4109}
4110
4111#[gpui::test]
4112fn test_select_line(cx: &mut TestAppContext) {
4113 init_test(cx, |_| {});
4114
4115 let view = cx.add_window(|cx| {
4116 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4117 build_editor(buffer, cx)
4118 });
4119 _ = view.update(cx, |view, cx| {
4120 view.change_selections(None, cx, |s| {
4121 s.select_display_ranges([
4122 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4123 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4124 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4125 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4126 ])
4127 });
4128 view.select_line(&SelectLine, cx);
4129 assert_eq!(
4130 view.selections.display_ranges(cx),
4131 vec![
4132 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4133 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4134 ]
4135 );
4136 });
4137
4138 _ = view.update(cx, |view, cx| {
4139 view.select_line(&SelectLine, cx);
4140 assert_eq!(
4141 view.selections.display_ranges(cx),
4142 vec![
4143 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4144 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4145 ]
4146 );
4147 });
4148
4149 _ = view.update(cx, |view, cx| {
4150 view.select_line(&SelectLine, cx);
4151 assert_eq!(
4152 view.selections.display_ranges(cx),
4153 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4154 );
4155 });
4156}
4157
4158#[gpui::test]
4159fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4160 init_test(cx, |_| {});
4161
4162 let view = cx.add_window(|cx| {
4163 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4164 build_editor(buffer, cx)
4165 });
4166 _ = view.update(cx, |view, cx| {
4167 view.fold_ranges(
4168 vec![
4169 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4170 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4171 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4172 ],
4173 true,
4174 cx,
4175 );
4176 view.change_selections(None, cx, |s| {
4177 s.select_display_ranges([
4178 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4179 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4180 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4181 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4182 ])
4183 });
4184 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4185 });
4186
4187 _ = view.update(cx, |view, cx| {
4188 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4189 assert_eq!(
4190 view.display_text(cx),
4191 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4192 );
4193 assert_eq!(
4194 view.selections.display_ranges(cx),
4195 [
4196 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4197 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4198 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4199 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4200 ]
4201 );
4202 });
4203
4204 _ = view.update(cx, |view, cx| {
4205 view.change_selections(None, cx, |s| {
4206 s.select_display_ranges([
4207 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4208 ])
4209 });
4210 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4211 assert_eq!(
4212 view.display_text(cx),
4213 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4214 );
4215 assert_eq!(
4216 view.selections.display_ranges(cx),
4217 [
4218 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4219 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4220 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4221 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4222 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4223 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4224 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4225 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4226 ]
4227 );
4228 });
4229}
4230
4231#[gpui::test]
4232async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4233 init_test(cx, |_| {});
4234
4235 let mut cx = EditorTestContext::new(cx).await;
4236
4237 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4238 cx.set_state(indoc!(
4239 r#"abc
4240 defˇghi
4241
4242 jk
4243 nlmo
4244 "#
4245 ));
4246
4247 cx.update_editor(|editor, cx| {
4248 editor.add_selection_above(&Default::default(), cx);
4249 });
4250
4251 cx.assert_editor_state(indoc!(
4252 r#"abcˇ
4253 defˇghi
4254
4255 jk
4256 nlmo
4257 "#
4258 ));
4259
4260 cx.update_editor(|editor, cx| {
4261 editor.add_selection_above(&Default::default(), cx);
4262 });
4263
4264 cx.assert_editor_state(indoc!(
4265 r#"abcˇ
4266 defˇghi
4267
4268 jk
4269 nlmo
4270 "#
4271 ));
4272
4273 cx.update_editor(|view, cx| {
4274 view.add_selection_below(&Default::default(), cx);
4275 });
4276
4277 cx.assert_editor_state(indoc!(
4278 r#"abc
4279 defˇghi
4280
4281 jk
4282 nlmo
4283 "#
4284 ));
4285
4286 cx.update_editor(|view, cx| {
4287 view.undo_selection(&Default::default(), cx);
4288 });
4289
4290 cx.assert_editor_state(indoc!(
4291 r#"abcˇ
4292 defˇghi
4293
4294 jk
4295 nlmo
4296 "#
4297 ));
4298
4299 cx.update_editor(|view, cx| {
4300 view.redo_selection(&Default::default(), cx);
4301 });
4302
4303 cx.assert_editor_state(indoc!(
4304 r#"abc
4305 defˇghi
4306
4307 jk
4308 nlmo
4309 "#
4310 ));
4311
4312 cx.update_editor(|view, cx| {
4313 view.add_selection_below(&Default::default(), cx);
4314 });
4315
4316 cx.assert_editor_state(indoc!(
4317 r#"abc
4318 defˇghi
4319
4320 jk
4321 nlmˇo
4322 "#
4323 ));
4324
4325 cx.update_editor(|view, cx| {
4326 view.add_selection_below(&Default::default(), cx);
4327 });
4328
4329 cx.assert_editor_state(indoc!(
4330 r#"abc
4331 defˇghi
4332
4333 jk
4334 nlmˇo
4335 "#
4336 ));
4337
4338 // change selections
4339 cx.set_state(indoc!(
4340 r#"abc
4341 def«ˇg»hi
4342
4343 jk
4344 nlmo
4345 "#
4346 ));
4347
4348 cx.update_editor(|view, cx| {
4349 view.add_selection_below(&Default::default(), cx);
4350 });
4351
4352 cx.assert_editor_state(indoc!(
4353 r#"abc
4354 def«ˇg»hi
4355
4356 jk
4357 nlm«ˇo»
4358 "#
4359 ));
4360
4361 cx.update_editor(|view, cx| {
4362 view.add_selection_below(&Default::default(), cx);
4363 });
4364
4365 cx.assert_editor_state(indoc!(
4366 r#"abc
4367 def«ˇg»hi
4368
4369 jk
4370 nlm«ˇo»
4371 "#
4372 ));
4373
4374 cx.update_editor(|view, cx| {
4375 view.add_selection_above(&Default::default(), cx);
4376 });
4377
4378 cx.assert_editor_state(indoc!(
4379 r#"abc
4380 def«ˇg»hi
4381
4382 jk
4383 nlmo
4384 "#
4385 ));
4386
4387 cx.update_editor(|view, cx| {
4388 view.add_selection_above(&Default::default(), cx);
4389 });
4390
4391 cx.assert_editor_state(indoc!(
4392 r#"abc
4393 def«ˇg»hi
4394
4395 jk
4396 nlmo
4397 "#
4398 ));
4399
4400 // Change selections again
4401 cx.set_state(indoc!(
4402 r#"a«bc
4403 defgˇ»hi
4404
4405 jk
4406 nlmo
4407 "#
4408 ));
4409
4410 cx.update_editor(|view, cx| {
4411 view.add_selection_below(&Default::default(), cx);
4412 });
4413
4414 cx.assert_editor_state(indoc!(
4415 r#"a«bcˇ»
4416 d«efgˇ»hi
4417
4418 j«kˇ»
4419 nlmo
4420 "#
4421 ));
4422
4423 cx.update_editor(|view, cx| {
4424 view.add_selection_below(&Default::default(), cx);
4425 });
4426 cx.assert_editor_state(indoc!(
4427 r#"a«bcˇ»
4428 d«efgˇ»hi
4429
4430 j«kˇ»
4431 n«lmoˇ»
4432 "#
4433 ));
4434 cx.update_editor(|view, cx| {
4435 view.add_selection_above(&Default::default(), cx);
4436 });
4437
4438 cx.assert_editor_state(indoc!(
4439 r#"a«bcˇ»
4440 d«efgˇ»hi
4441
4442 j«kˇ»
4443 nlmo
4444 "#
4445 ));
4446
4447 // Change selections again
4448 cx.set_state(indoc!(
4449 r#"abc
4450 d«ˇefghi
4451
4452 jk
4453 nlm»o
4454 "#
4455 ));
4456
4457 cx.update_editor(|view, cx| {
4458 view.add_selection_above(&Default::default(), cx);
4459 });
4460
4461 cx.assert_editor_state(indoc!(
4462 r#"a«ˇbc»
4463 d«ˇef»ghi
4464
4465 j«ˇk»
4466 n«ˇlm»o
4467 "#
4468 ));
4469
4470 cx.update_editor(|view, cx| {
4471 view.add_selection_below(&Default::default(), cx);
4472 });
4473
4474 cx.assert_editor_state(indoc!(
4475 r#"abc
4476 d«ˇef»ghi
4477
4478 j«ˇk»
4479 n«ˇlm»o
4480 "#
4481 ));
4482}
4483
4484#[gpui::test]
4485async fn test_select_next(cx: &mut gpui::TestAppContext) {
4486 init_test(cx, |_| {});
4487
4488 let mut cx = EditorTestContext::new(cx).await;
4489 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4490
4491 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4492 .unwrap();
4493 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4494
4495 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4496 .unwrap();
4497 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4498
4499 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4500 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4501
4502 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4503 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4504
4505 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4506 .unwrap();
4507 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4508
4509 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4510 .unwrap();
4511 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4512}
4513
4514#[gpui::test]
4515async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
4516 init_test(cx, |_| {});
4517
4518 let mut cx = EditorTestContext::new(cx).await;
4519 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4520
4521 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
4522 .unwrap();
4523 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4524}
4525
4526#[gpui::test]
4527async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4528 init_test(cx, |_| {});
4529
4530 let mut cx = EditorTestContext::new(cx).await;
4531 cx.set_state(
4532 r#"let foo = 2;
4533lˇet foo = 2;
4534let fooˇ = 2;
4535let foo = 2;
4536let foo = ˇ2;"#,
4537 );
4538
4539 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4540 .unwrap();
4541 cx.assert_editor_state(
4542 r#"let foo = 2;
4543«letˇ» foo = 2;
4544let «fooˇ» = 2;
4545let foo = 2;
4546let foo = «2ˇ»;"#,
4547 );
4548
4549 // noop for multiple selections with different contents
4550 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4551 .unwrap();
4552 cx.assert_editor_state(
4553 r#"let foo = 2;
4554«letˇ» foo = 2;
4555let «fooˇ» = 2;
4556let foo = 2;
4557let foo = «2ˇ»;"#,
4558 );
4559}
4560
4561#[gpui::test]
4562async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
4563 init_test(cx, |_| {});
4564
4565 let mut cx = EditorTestContext::new_multibuffer(
4566 cx,
4567 [
4568 &indoc! {
4569 "aaa\n«bbb\nccc\n»ddd"
4570 },
4571 &indoc! {
4572 "aaa\n«bbb\nccc\n»ddd"
4573 },
4574 ],
4575 );
4576
4577 cx.assert_editor_state(indoc! {"
4578 ˇbbb
4579 ccc
4580
4581 bbb
4582 ccc
4583 "});
4584 cx.dispatch_action(SelectPrevious::default());
4585 cx.assert_editor_state(indoc! {"
4586 «bbbˇ»
4587 ccc
4588
4589 bbb
4590 ccc
4591 "});
4592 cx.dispatch_action(SelectPrevious::default());
4593 cx.assert_editor_state(indoc! {"
4594 «bbbˇ»
4595 ccc
4596
4597 «bbbˇ»
4598 ccc
4599 "});
4600}
4601
4602#[gpui::test]
4603async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
4604 init_test(cx, |_| {});
4605
4606 let mut cx = EditorTestContext::new(cx).await;
4607 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4608
4609 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4610 .unwrap();
4611 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4612
4613 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4614 .unwrap();
4615 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4616
4617 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4618 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4619
4620 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4621 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4622
4623 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4624 .unwrap();
4625 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
4626
4627 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4628 .unwrap();
4629 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
4630
4631 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4632 .unwrap();
4633 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
4634}
4635
4636#[gpui::test]
4637async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4638 init_test(cx, |_| {});
4639
4640 let mut cx = EditorTestContext::new(cx).await;
4641 cx.set_state(
4642 r#"let foo = 2;
4643lˇet foo = 2;
4644let fooˇ = 2;
4645let foo = 2;
4646let foo = ˇ2;"#,
4647 );
4648
4649 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4650 .unwrap();
4651 cx.assert_editor_state(
4652 r#"let foo = 2;
4653«letˇ» foo = 2;
4654let «fooˇ» = 2;
4655let foo = 2;
4656let foo = «2ˇ»;"#,
4657 );
4658
4659 // noop for multiple selections with different contents
4660 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4661 .unwrap();
4662 cx.assert_editor_state(
4663 r#"let foo = 2;
4664«letˇ» foo = 2;
4665let «fooˇ» = 2;
4666let foo = 2;
4667let foo = «2ˇ»;"#,
4668 );
4669}
4670
4671#[gpui::test]
4672async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
4673 init_test(cx, |_| {});
4674
4675 let mut cx = EditorTestContext::new(cx).await;
4676 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
4677
4678 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4679 .unwrap();
4680 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
4681
4682 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4683 .unwrap();
4684 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
4685
4686 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4687 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
4688
4689 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4690 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
4691
4692 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4693 .unwrap();
4694 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
4695
4696 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4697 .unwrap();
4698 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
4699}
4700
4701#[gpui::test]
4702async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
4703 init_test(cx, |_| {});
4704
4705 let language = Arc::new(Language::new(
4706 LanguageConfig::default(),
4707 Some(tree_sitter_rust::language()),
4708 ));
4709
4710 let text = r#"
4711 use mod1::mod2::{mod3, mod4};
4712
4713 fn fn_1(param1: bool, param2: &str) {
4714 let var1 = "text";
4715 }
4716 "#
4717 .unindent();
4718
4719 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
4720 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4721 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4722
4723 editor
4724 .condition::<crate::EditorEvent>(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4725 .await;
4726
4727 editor.update(cx, |view, cx| {
4728 view.change_selections(None, cx, |s| {
4729 s.select_display_ranges([
4730 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
4731 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
4732 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
4733 ]);
4734 });
4735 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4736 });
4737 editor.update(cx, |editor, cx| {
4738 assert_text_with_selections(
4739 editor,
4740 indoc! {r#"
4741 use mod1::mod2::{mod3, «mod4ˇ»};
4742
4743 fn fn_1«ˇ(param1: bool, param2: &str)» {
4744 let var1 = "«textˇ»";
4745 }
4746 "#},
4747 cx,
4748 );
4749 });
4750
4751 editor.update(cx, |view, cx| {
4752 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4753 });
4754 editor.update(cx, |editor, cx| {
4755 assert_text_with_selections(
4756 editor,
4757 indoc! {r#"
4758 use mod1::mod2::«{mod3, mod4}ˇ»;
4759
4760 «ˇfn fn_1(param1: bool, param2: &str) {
4761 let var1 = "text";
4762 }»
4763 "#},
4764 cx,
4765 );
4766 });
4767
4768 editor.update(cx, |view, cx| {
4769 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4770 });
4771 assert_eq!(
4772 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
4773 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
4774 );
4775
4776 // Trying to expand the selected syntax node one more time has no effect.
4777 editor.update(cx, |view, cx| {
4778 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4779 });
4780 assert_eq!(
4781 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
4782 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
4783 );
4784
4785 editor.update(cx, |view, cx| {
4786 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4787 });
4788 editor.update(cx, |editor, cx| {
4789 assert_text_with_selections(
4790 editor,
4791 indoc! {r#"
4792 use mod1::mod2::«{mod3, mod4}ˇ»;
4793
4794 «ˇfn fn_1(param1: bool, param2: &str) {
4795 let var1 = "text";
4796 }»
4797 "#},
4798 cx,
4799 );
4800 });
4801
4802 editor.update(cx, |view, cx| {
4803 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4804 });
4805 editor.update(cx, |editor, cx| {
4806 assert_text_with_selections(
4807 editor,
4808 indoc! {r#"
4809 use mod1::mod2::{mod3, «mod4ˇ»};
4810
4811 fn fn_1«ˇ(param1: bool, param2: &str)» {
4812 let var1 = "«textˇ»";
4813 }
4814 "#},
4815 cx,
4816 );
4817 });
4818
4819 editor.update(cx, |view, cx| {
4820 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4821 });
4822 editor.update(cx, |editor, cx| {
4823 assert_text_with_selections(
4824 editor,
4825 indoc! {r#"
4826 use mod1::mod2::{mod3, mo«ˇ»d4};
4827
4828 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
4829 let var1 = "te«ˇ»xt";
4830 }
4831 "#},
4832 cx,
4833 );
4834 });
4835
4836 // Trying to shrink the selected syntax node one more time has no effect.
4837 editor.update(cx, |view, cx| {
4838 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4839 });
4840 editor.update(cx, |editor, cx| {
4841 assert_text_with_selections(
4842 editor,
4843 indoc! {r#"
4844 use mod1::mod2::{mod3, mo«ˇ»d4};
4845
4846 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
4847 let var1 = "te«ˇ»xt";
4848 }
4849 "#},
4850 cx,
4851 );
4852 });
4853
4854 // Ensure that we keep expanding the selection if the larger selection starts or ends within
4855 // a fold.
4856 editor.update(cx, |view, cx| {
4857 view.fold_ranges(
4858 vec![
4859 (
4860 Point::new(0, 21)..Point::new(0, 24),
4861 FoldPlaceholder::test(),
4862 ),
4863 (
4864 Point::new(3, 20)..Point::new(3, 22),
4865 FoldPlaceholder::test(),
4866 ),
4867 ],
4868 true,
4869 cx,
4870 );
4871 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4872 });
4873 editor.update(cx, |editor, cx| {
4874 assert_text_with_selections(
4875 editor,
4876 indoc! {r#"
4877 use mod1::mod2::«{mod3, mod4}ˇ»;
4878
4879 fn fn_1«ˇ(param1: bool, param2: &str)» {
4880 «let var1 = "text";ˇ»
4881 }
4882 "#},
4883 cx,
4884 );
4885 });
4886}
4887
4888#[gpui::test]
4889async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
4890 init_test(cx, |_| {});
4891
4892 let language = Arc::new(
4893 Language::new(
4894 LanguageConfig {
4895 brackets: BracketPairConfig {
4896 pairs: vec![
4897 BracketPair {
4898 start: "{".to_string(),
4899 end: "}".to_string(),
4900 close: false,
4901 surround: false,
4902 newline: true,
4903 },
4904 BracketPair {
4905 start: "(".to_string(),
4906 end: ")".to_string(),
4907 close: false,
4908 surround: false,
4909 newline: true,
4910 },
4911 ],
4912 ..Default::default()
4913 },
4914 ..Default::default()
4915 },
4916 Some(tree_sitter_rust::language()),
4917 )
4918 .with_indents_query(
4919 r#"
4920 (_ "(" ")" @end) @indent
4921 (_ "{" "}" @end) @indent
4922 "#,
4923 )
4924 .unwrap(),
4925 );
4926
4927 let text = "fn a() {}";
4928
4929 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
4930 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4931 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4932 editor
4933 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
4934 .await;
4935
4936 _ = editor.update(cx, |editor, cx| {
4937 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
4938 editor.newline(&Newline, cx);
4939 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
4940 assert_eq!(
4941 editor.selections.ranges(cx),
4942 &[
4943 Point::new(1, 4)..Point::new(1, 4),
4944 Point::new(3, 4)..Point::new(3, 4),
4945 Point::new(5, 0)..Point::new(5, 0)
4946 ]
4947 );
4948 });
4949}
4950
4951#[gpui::test]
4952async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
4953 init_test(cx, |_| {});
4954
4955 let mut cx = EditorTestContext::new(cx).await;
4956
4957 let language = Arc::new(Language::new(
4958 LanguageConfig {
4959 brackets: BracketPairConfig {
4960 pairs: vec![
4961 BracketPair {
4962 start: "{".to_string(),
4963 end: "}".to_string(),
4964 close: true,
4965 surround: true,
4966 newline: true,
4967 },
4968 BracketPair {
4969 start: "(".to_string(),
4970 end: ")".to_string(),
4971 close: true,
4972 surround: true,
4973 newline: true,
4974 },
4975 BracketPair {
4976 start: "/*".to_string(),
4977 end: " */".to_string(),
4978 close: true,
4979 surround: true,
4980 newline: true,
4981 },
4982 BracketPair {
4983 start: "[".to_string(),
4984 end: "]".to_string(),
4985 close: false,
4986 surround: false,
4987 newline: true,
4988 },
4989 BracketPair {
4990 start: "\"".to_string(),
4991 end: "\"".to_string(),
4992 close: true,
4993 surround: true,
4994 newline: false,
4995 },
4996 BracketPair {
4997 start: "<".to_string(),
4998 end: ">".to_string(),
4999 close: false,
5000 surround: true,
5001 newline: true,
5002 },
5003 ],
5004 ..Default::default()
5005 },
5006 autoclose_before: "})]".to_string(),
5007 ..Default::default()
5008 },
5009 Some(tree_sitter_rust::language()),
5010 ));
5011
5012 cx.language_registry().add(language.clone());
5013 cx.update_buffer(|buffer, cx| {
5014 buffer.set_language(Some(language), cx);
5015 });
5016
5017 cx.set_state(
5018 &r#"
5019 🏀ˇ
5020 εˇ
5021 ❤️ˇ
5022 "#
5023 .unindent(),
5024 );
5025
5026 // autoclose multiple nested brackets at multiple cursors
5027 cx.update_editor(|view, cx| {
5028 view.handle_input("{", cx);
5029 view.handle_input("{", cx);
5030 view.handle_input("{", cx);
5031 });
5032 cx.assert_editor_state(
5033 &"
5034 🏀{{{ˇ}}}
5035 ε{{{ˇ}}}
5036 ❤️{{{ˇ}}}
5037 "
5038 .unindent(),
5039 );
5040
5041 // insert a different closing bracket
5042 cx.update_editor(|view, cx| {
5043 view.handle_input(")", cx);
5044 });
5045 cx.assert_editor_state(
5046 &"
5047 🏀{{{)ˇ}}}
5048 ε{{{)ˇ}}}
5049 ❤️{{{)ˇ}}}
5050 "
5051 .unindent(),
5052 );
5053
5054 // skip over the auto-closed brackets when typing a closing bracket
5055 cx.update_editor(|view, cx| {
5056 view.move_right(&MoveRight, cx);
5057 view.handle_input("}", cx);
5058 view.handle_input("}", cx);
5059 view.handle_input("}", cx);
5060 });
5061 cx.assert_editor_state(
5062 &"
5063 🏀{{{)}}}}ˇ
5064 ε{{{)}}}}ˇ
5065 ❤️{{{)}}}}ˇ
5066 "
5067 .unindent(),
5068 );
5069
5070 // autoclose multi-character pairs
5071 cx.set_state(
5072 &"
5073 ˇ
5074 ˇ
5075 "
5076 .unindent(),
5077 );
5078 cx.update_editor(|view, cx| {
5079 view.handle_input("/", cx);
5080 view.handle_input("*", cx);
5081 });
5082 cx.assert_editor_state(
5083 &"
5084 /*ˇ */
5085 /*ˇ */
5086 "
5087 .unindent(),
5088 );
5089
5090 // one cursor autocloses a multi-character pair, one cursor
5091 // does not autoclose.
5092 cx.set_state(
5093 &"
5094 /ˇ
5095 ˇ
5096 "
5097 .unindent(),
5098 );
5099 cx.update_editor(|view, cx| view.handle_input("*", cx));
5100 cx.assert_editor_state(
5101 &"
5102 /*ˇ */
5103 *ˇ
5104 "
5105 .unindent(),
5106 );
5107
5108 // Don't autoclose if the next character isn't whitespace and isn't
5109 // listed in the language's "autoclose_before" section.
5110 cx.set_state("ˇa b");
5111 cx.update_editor(|view, cx| view.handle_input("{", cx));
5112 cx.assert_editor_state("{ˇa b");
5113
5114 // Don't autoclose if `close` is false for the bracket pair
5115 cx.set_state("ˇ");
5116 cx.update_editor(|view, cx| view.handle_input("[", cx));
5117 cx.assert_editor_state("[ˇ");
5118
5119 // Surround with brackets if text is selected
5120 cx.set_state("«aˇ» b");
5121 cx.update_editor(|view, cx| view.handle_input("{", cx));
5122 cx.assert_editor_state("{«aˇ»} b");
5123
5124 // Autclose pair where the start and end characters are the same
5125 cx.set_state("aˇ");
5126 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5127 cx.assert_editor_state("a\"ˇ\"");
5128 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5129 cx.assert_editor_state("a\"\"ˇ");
5130
5131 // Don't autoclose pair if autoclose is disabled
5132 cx.set_state("ˇ");
5133 cx.update_editor(|view, cx| view.handle_input("<", cx));
5134 cx.assert_editor_state("<ˇ");
5135
5136 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5137 cx.set_state("«aˇ» b");
5138 cx.update_editor(|view, cx| view.handle_input("<", cx));
5139 cx.assert_editor_state("<«aˇ»> b");
5140}
5141
5142#[gpui::test]
5143async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5144 init_test(cx, |settings| {
5145 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5146 });
5147
5148 let mut cx = EditorTestContext::new(cx).await;
5149
5150 let language = Arc::new(Language::new(
5151 LanguageConfig {
5152 brackets: BracketPairConfig {
5153 pairs: vec![
5154 BracketPair {
5155 start: "{".to_string(),
5156 end: "}".to_string(),
5157 close: true,
5158 surround: true,
5159 newline: true,
5160 },
5161 BracketPair {
5162 start: "(".to_string(),
5163 end: ")".to_string(),
5164 close: true,
5165 surround: true,
5166 newline: true,
5167 },
5168 BracketPair {
5169 start: "[".to_string(),
5170 end: "]".to_string(),
5171 close: false,
5172 surround: false,
5173 newline: true,
5174 },
5175 ],
5176 ..Default::default()
5177 },
5178 autoclose_before: "})]".to_string(),
5179 ..Default::default()
5180 },
5181 Some(tree_sitter_rust::language()),
5182 ));
5183
5184 cx.language_registry().add(language.clone());
5185 cx.update_buffer(|buffer, cx| {
5186 buffer.set_language(Some(language), cx);
5187 });
5188
5189 cx.set_state(
5190 &"
5191 ˇ
5192 ˇ
5193 ˇ
5194 "
5195 .unindent(),
5196 );
5197
5198 // ensure only matching closing brackets are skipped over
5199 cx.update_editor(|view, cx| {
5200 view.handle_input("}", cx);
5201 view.move_left(&MoveLeft, cx);
5202 view.handle_input(")", cx);
5203 view.move_left(&MoveLeft, cx);
5204 });
5205 cx.assert_editor_state(
5206 &"
5207 ˇ)}
5208 ˇ)}
5209 ˇ)}
5210 "
5211 .unindent(),
5212 );
5213
5214 // skip-over closing brackets at multiple cursors
5215 cx.update_editor(|view, cx| {
5216 view.handle_input(")", cx);
5217 view.handle_input("}", cx);
5218 });
5219 cx.assert_editor_state(
5220 &"
5221 )}ˇ
5222 )}ˇ
5223 )}ˇ
5224 "
5225 .unindent(),
5226 );
5227
5228 // ignore non-close brackets
5229 cx.update_editor(|view, cx| {
5230 view.handle_input("]", cx);
5231 view.move_left(&MoveLeft, cx);
5232 view.handle_input("]", cx);
5233 });
5234 cx.assert_editor_state(
5235 &"
5236 )}]ˇ]
5237 )}]ˇ]
5238 )}]ˇ]
5239 "
5240 .unindent(),
5241 );
5242}
5243
5244#[gpui::test]
5245async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5246 init_test(cx, |_| {});
5247
5248 let mut cx = EditorTestContext::new(cx).await;
5249
5250 let html_language = Arc::new(
5251 Language::new(
5252 LanguageConfig {
5253 name: "HTML".into(),
5254 brackets: BracketPairConfig {
5255 pairs: vec![
5256 BracketPair {
5257 start: "<".into(),
5258 end: ">".into(),
5259 close: true,
5260 ..Default::default()
5261 },
5262 BracketPair {
5263 start: "{".into(),
5264 end: "}".into(),
5265 close: true,
5266 ..Default::default()
5267 },
5268 BracketPair {
5269 start: "(".into(),
5270 end: ")".into(),
5271 close: true,
5272 ..Default::default()
5273 },
5274 ],
5275 ..Default::default()
5276 },
5277 autoclose_before: "})]>".into(),
5278 ..Default::default()
5279 },
5280 Some(tree_sitter_html::language()),
5281 )
5282 .with_injection_query(
5283 r#"
5284 (script_element
5285 (raw_text) @content
5286 (#set! "language" "javascript"))
5287 "#,
5288 )
5289 .unwrap(),
5290 );
5291
5292 let javascript_language = Arc::new(Language::new(
5293 LanguageConfig {
5294 name: "JavaScript".into(),
5295 brackets: BracketPairConfig {
5296 pairs: vec![
5297 BracketPair {
5298 start: "/*".into(),
5299 end: " */".into(),
5300 close: true,
5301 ..Default::default()
5302 },
5303 BracketPair {
5304 start: "{".into(),
5305 end: "}".into(),
5306 close: true,
5307 ..Default::default()
5308 },
5309 BracketPair {
5310 start: "(".into(),
5311 end: ")".into(),
5312 close: true,
5313 ..Default::default()
5314 },
5315 ],
5316 ..Default::default()
5317 },
5318 autoclose_before: "})]>".into(),
5319 ..Default::default()
5320 },
5321 Some(tree_sitter_typescript::language_tsx()),
5322 ));
5323
5324 cx.language_registry().add(html_language.clone());
5325 cx.language_registry().add(javascript_language.clone());
5326
5327 cx.update_buffer(|buffer, cx| {
5328 buffer.set_language(Some(html_language), cx);
5329 });
5330
5331 cx.set_state(
5332 &r#"
5333 <body>ˇ
5334 <script>
5335 var x = 1;ˇ
5336 </script>
5337 </body>ˇ
5338 "#
5339 .unindent(),
5340 );
5341
5342 // Precondition: different languages are active at different locations.
5343 cx.update_editor(|editor, cx| {
5344 let snapshot = editor.snapshot(cx);
5345 let cursors = editor.selections.ranges::<usize>(cx);
5346 let languages = cursors
5347 .iter()
5348 .map(|c| snapshot.language_at(c.start).unwrap().name())
5349 .collect::<Vec<_>>();
5350 assert_eq!(
5351 languages,
5352 &["HTML".into(), "JavaScript".into(), "HTML".into()]
5353 );
5354 });
5355
5356 // Angle brackets autoclose in HTML, but not JavaScript.
5357 cx.update_editor(|editor, cx| {
5358 editor.handle_input("<", cx);
5359 editor.handle_input("a", cx);
5360 });
5361 cx.assert_editor_state(
5362 &r#"
5363 <body><aˇ>
5364 <script>
5365 var x = 1;<aˇ
5366 </script>
5367 </body><aˇ>
5368 "#
5369 .unindent(),
5370 );
5371
5372 // Curly braces and parens autoclose in both HTML and JavaScript.
5373 cx.update_editor(|editor, cx| {
5374 editor.handle_input(" b=", cx);
5375 editor.handle_input("{", cx);
5376 editor.handle_input("c", cx);
5377 editor.handle_input("(", cx);
5378 });
5379 cx.assert_editor_state(
5380 &r#"
5381 <body><a b={c(ˇ)}>
5382 <script>
5383 var x = 1;<a b={c(ˇ)}
5384 </script>
5385 </body><a b={c(ˇ)}>
5386 "#
5387 .unindent(),
5388 );
5389
5390 // Brackets that were already autoclosed are skipped.
5391 cx.update_editor(|editor, cx| {
5392 editor.handle_input(")", cx);
5393 editor.handle_input("d", cx);
5394 editor.handle_input("}", cx);
5395 });
5396 cx.assert_editor_state(
5397 &r#"
5398 <body><a b={c()d}ˇ>
5399 <script>
5400 var x = 1;<a b={c()d}ˇ
5401 </script>
5402 </body><a b={c()d}ˇ>
5403 "#
5404 .unindent(),
5405 );
5406 cx.update_editor(|editor, cx| {
5407 editor.handle_input(">", cx);
5408 });
5409 cx.assert_editor_state(
5410 &r#"
5411 <body><a b={c()d}>ˇ
5412 <script>
5413 var x = 1;<a b={c()d}>ˇ
5414 </script>
5415 </body><a b={c()d}>ˇ
5416 "#
5417 .unindent(),
5418 );
5419
5420 // Reset
5421 cx.set_state(
5422 &r#"
5423 <body>ˇ
5424 <script>
5425 var x = 1;ˇ
5426 </script>
5427 </body>ˇ
5428 "#
5429 .unindent(),
5430 );
5431
5432 cx.update_editor(|editor, cx| {
5433 editor.handle_input("<", cx);
5434 });
5435 cx.assert_editor_state(
5436 &r#"
5437 <body><ˇ>
5438 <script>
5439 var x = 1;<ˇ
5440 </script>
5441 </body><ˇ>
5442 "#
5443 .unindent(),
5444 );
5445
5446 // When backspacing, the closing angle brackets are removed.
5447 cx.update_editor(|editor, cx| {
5448 editor.backspace(&Backspace, cx);
5449 });
5450 cx.assert_editor_state(
5451 &r#"
5452 <body>ˇ
5453 <script>
5454 var x = 1;ˇ
5455 </script>
5456 </body>ˇ
5457 "#
5458 .unindent(),
5459 );
5460
5461 // Block comments autoclose in JavaScript, but not HTML.
5462 cx.update_editor(|editor, cx| {
5463 editor.handle_input("/", cx);
5464 editor.handle_input("*", cx);
5465 });
5466 cx.assert_editor_state(
5467 &r#"
5468 <body>/*ˇ
5469 <script>
5470 var x = 1;/*ˇ */
5471 </script>
5472 </body>/*ˇ
5473 "#
5474 .unindent(),
5475 );
5476}
5477
5478#[gpui::test]
5479async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
5480 init_test(cx, |_| {});
5481
5482 let mut cx = EditorTestContext::new(cx).await;
5483
5484 let rust_language = Arc::new(
5485 Language::new(
5486 LanguageConfig {
5487 name: "Rust".into(),
5488 brackets: serde_json::from_value(json!([
5489 { "start": "{", "end": "}", "close": true, "newline": true },
5490 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
5491 ]))
5492 .unwrap(),
5493 autoclose_before: "})]>".into(),
5494 ..Default::default()
5495 },
5496 Some(tree_sitter_rust::language()),
5497 )
5498 .with_override_query("(string_literal) @string")
5499 .unwrap(),
5500 );
5501
5502 cx.language_registry().add(rust_language.clone());
5503 cx.update_buffer(|buffer, cx| {
5504 buffer.set_language(Some(rust_language), cx);
5505 });
5506
5507 cx.set_state(
5508 &r#"
5509 let x = ˇ
5510 "#
5511 .unindent(),
5512 );
5513
5514 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
5515 cx.update_editor(|editor, cx| {
5516 editor.handle_input("\"", cx);
5517 });
5518 cx.assert_editor_state(
5519 &r#"
5520 let x = "ˇ"
5521 "#
5522 .unindent(),
5523 );
5524
5525 // Inserting another quotation mark. The cursor moves across the existing
5526 // automatically-inserted quotation mark.
5527 cx.update_editor(|editor, cx| {
5528 editor.handle_input("\"", cx);
5529 });
5530 cx.assert_editor_state(
5531 &r#"
5532 let x = ""ˇ
5533 "#
5534 .unindent(),
5535 );
5536
5537 // Reset
5538 cx.set_state(
5539 &r#"
5540 let x = ˇ
5541 "#
5542 .unindent(),
5543 );
5544
5545 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
5546 cx.update_editor(|editor, cx| {
5547 editor.handle_input("\"", cx);
5548 editor.handle_input(" ", cx);
5549 editor.move_left(&Default::default(), cx);
5550 editor.handle_input("\\", cx);
5551 editor.handle_input("\"", cx);
5552 });
5553 cx.assert_editor_state(
5554 &r#"
5555 let x = "\"ˇ "
5556 "#
5557 .unindent(),
5558 );
5559
5560 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
5561 // mark. Nothing is inserted.
5562 cx.update_editor(|editor, cx| {
5563 editor.move_right(&Default::default(), cx);
5564 editor.handle_input("\"", cx);
5565 });
5566 cx.assert_editor_state(
5567 &r#"
5568 let x = "\" "ˇ
5569 "#
5570 .unindent(),
5571 );
5572}
5573
5574#[gpui::test]
5575async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
5576 init_test(cx, |_| {});
5577
5578 let language = Arc::new(Language::new(
5579 LanguageConfig {
5580 brackets: BracketPairConfig {
5581 pairs: vec![
5582 BracketPair {
5583 start: "{".to_string(),
5584 end: "}".to_string(),
5585 close: true,
5586 surround: true,
5587 newline: true,
5588 },
5589 BracketPair {
5590 start: "/* ".to_string(),
5591 end: "*/".to_string(),
5592 close: true,
5593 surround: true,
5594 ..Default::default()
5595 },
5596 ],
5597 ..Default::default()
5598 },
5599 ..Default::default()
5600 },
5601 Some(tree_sitter_rust::language()),
5602 ));
5603
5604 let text = r#"
5605 a
5606 b
5607 c
5608 "#
5609 .unindent();
5610
5611 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5612 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5613 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5614 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5615 .await;
5616
5617 _ = view.update(cx, |view, cx| {
5618 view.change_selections(None, cx, |s| {
5619 s.select_display_ranges([
5620 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5621 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5622 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
5623 ])
5624 });
5625
5626 view.handle_input("{", cx);
5627 view.handle_input("{", cx);
5628 view.handle_input("{", cx);
5629 assert_eq!(
5630 view.text(cx),
5631 "
5632 {{{a}}}
5633 {{{b}}}
5634 {{{c}}}
5635 "
5636 .unindent()
5637 );
5638 assert_eq!(
5639 view.selections.display_ranges(cx),
5640 [
5641 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
5642 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
5643 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
5644 ]
5645 );
5646
5647 view.undo(&Undo, cx);
5648 view.undo(&Undo, cx);
5649 view.undo(&Undo, cx);
5650 assert_eq!(
5651 view.text(cx),
5652 "
5653 a
5654 b
5655 c
5656 "
5657 .unindent()
5658 );
5659 assert_eq!(
5660 view.selections.display_ranges(cx),
5661 [
5662 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5663 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5664 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
5665 ]
5666 );
5667
5668 // Ensure inserting the first character of a multi-byte bracket pair
5669 // doesn't surround the selections with the bracket.
5670 view.handle_input("/", cx);
5671 assert_eq!(
5672 view.text(cx),
5673 "
5674 /
5675 /
5676 /
5677 "
5678 .unindent()
5679 );
5680 assert_eq!(
5681 view.selections.display_ranges(cx),
5682 [
5683 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
5684 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
5685 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
5686 ]
5687 );
5688
5689 view.undo(&Undo, cx);
5690 assert_eq!(
5691 view.text(cx),
5692 "
5693 a
5694 b
5695 c
5696 "
5697 .unindent()
5698 );
5699 assert_eq!(
5700 view.selections.display_ranges(cx),
5701 [
5702 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5703 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5704 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
5705 ]
5706 );
5707
5708 // Ensure inserting the last character of a multi-byte bracket pair
5709 // doesn't surround the selections with the bracket.
5710 view.handle_input("*", cx);
5711 assert_eq!(
5712 view.text(cx),
5713 "
5714 *
5715 *
5716 *
5717 "
5718 .unindent()
5719 );
5720 assert_eq!(
5721 view.selections.display_ranges(cx),
5722 [
5723 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
5724 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
5725 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
5726 ]
5727 );
5728 });
5729}
5730
5731#[gpui::test]
5732async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
5733 init_test(cx, |_| {});
5734
5735 let language = Arc::new(Language::new(
5736 LanguageConfig {
5737 brackets: BracketPairConfig {
5738 pairs: vec![BracketPair {
5739 start: "{".to_string(),
5740 end: "}".to_string(),
5741 close: true,
5742 surround: true,
5743 newline: true,
5744 }],
5745 ..Default::default()
5746 },
5747 autoclose_before: "}".to_string(),
5748 ..Default::default()
5749 },
5750 Some(tree_sitter_rust::language()),
5751 ));
5752
5753 let text = r#"
5754 a
5755 b
5756 c
5757 "#
5758 .unindent();
5759
5760 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5761 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5762 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5763 editor
5764 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5765 .await;
5766
5767 _ = editor.update(cx, |editor, cx| {
5768 editor.change_selections(None, cx, |s| {
5769 s.select_ranges([
5770 Point::new(0, 1)..Point::new(0, 1),
5771 Point::new(1, 1)..Point::new(1, 1),
5772 Point::new(2, 1)..Point::new(2, 1),
5773 ])
5774 });
5775
5776 editor.handle_input("{", cx);
5777 editor.handle_input("{", cx);
5778 editor.handle_input("_", cx);
5779 assert_eq!(
5780 editor.text(cx),
5781 "
5782 a{{_}}
5783 b{{_}}
5784 c{{_}}
5785 "
5786 .unindent()
5787 );
5788 assert_eq!(
5789 editor.selections.ranges::<Point>(cx),
5790 [
5791 Point::new(0, 4)..Point::new(0, 4),
5792 Point::new(1, 4)..Point::new(1, 4),
5793 Point::new(2, 4)..Point::new(2, 4)
5794 ]
5795 );
5796
5797 editor.backspace(&Default::default(), cx);
5798 editor.backspace(&Default::default(), cx);
5799 assert_eq!(
5800 editor.text(cx),
5801 "
5802 a{}
5803 b{}
5804 c{}
5805 "
5806 .unindent()
5807 );
5808 assert_eq!(
5809 editor.selections.ranges::<Point>(cx),
5810 [
5811 Point::new(0, 2)..Point::new(0, 2),
5812 Point::new(1, 2)..Point::new(1, 2),
5813 Point::new(2, 2)..Point::new(2, 2)
5814 ]
5815 );
5816
5817 editor.delete_to_previous_word_start(&Default::default(), cx);
5818 assert_eq!(
5819 editor.text(cx),
5820 "
5821 a
5822 b
5823 c
5824 "
5825 .unindent()
5826 );
5827 assert_eq!(
5828 editor.selections.ranges::<Point>(cx),
5829 [
5830 Point::new(0, 1)..Point::new(0, 1),
5831 Point::new(1, 1)..Point::new(1, 1),
5832 Point::new(2, 1)..Point::new(2, 1)
5833 ]
5834 );
5835 });
5836}
5837
5838#[gpui::test]
5839async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
5840 init_test(cx, |settings| {
5841 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5842 });
5843
5844 let mut cx = EditorTestContext::new(cx).await;
5845
5846 let language = Arc::new(Language::new(
5847 LanguageConfig {
5848 brackets: BracketPairConfig {
5849 pairs: vec![
5850 BracketPair {
5851 start: "{".to_string(),
5852 end: "}".to_string(),
5853 close: true,
5854 surround: true,
5855 newline: true,
5856 },
5857 BracketPair {
5858 start: "(".to_string(),
5859 end: ")".to_string(),
5860 close: true,
5861 surround: true,
5862 newline: true,
5863 },
5864 BracketPair {
5865 start: "[".to_string(),
5866 end: "]".to_string(),
5867 close: false,
5868 surround: true,
5869 newline: true,
5870 },
5871 ],
5872 ..Default::default()
5873 },
5874 autoclose_before: "})]".to_string(),
5875 ..Default::default()
5876 },
5877 Some(tree_sitter_rust::language()),
5878 ));
5879
5880 cx.language_registry().add(language.clone());
5881 cx.update_buffer(|buffer, cx| {
5882 buffer.set_language(Some(language), cx);
5883 });
5884
5885 cx.set_state(
5886 &"
5887 {(ˇ)}
5888 [[ˇ]]
5889 {(ˇ)}
5890 "
5891 .unindent(),
5892 );
5893
5894 cx.update_editor(|view, cx| {
5895 view.backspace(&Default::default(), cx);
5896 view.backspace(&Default::default(), cx);
5897 });
5898
5899 cx.assert_editor_state(
5900 &"
5901 ˇ
5902 ˇ]]
5903 ˇ
5904 "
5905 .unindent(),
5906 );
5907
5908 cx.update_editor(|view, cx| {
5909 view.handle_input("{", cx);
5910 view.handle_input("{", cx);
5911 view.move_right(&MoveRight, cx);
5912 view.move_right(&MoveRight, cx);
5913 view.move_left(&MoveLeft, cx);
5914 view.move_left(&MoveLeft, cx);
5915 view.backspace(&Default::default(), cx);
5916 });
5917
5918 cx.assert_editor_state(
5919 &"
5920 {ˇ}
5921 {ˇ}]]
5922 {ˇ}
5923 "
5924 .unindent(),
5925 );
5926
5927 cx.update_editor(|view, cx| {
5928 view.backspace(&Default::default(), cx);
5929 });
5930
5931 cx.assert_editor_state(
5932 &"
5933 ˇ
5934 ˇ]]
5935 ˇ
5936 "
5937 .unindent(),
5938 );
5939}
5940
5941#[gpui::test]
5942async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
5943 init_test(cx, |_| {});
5944
5945 let language = Arc::new(Language::new(
5946 LanguageConfig::default(),
5947 Some(tree_sitter_rust::language()),
5948 ));
5949
5950 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
5951 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5952 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5953 editor
5954 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5955 .await;
5956
5957 _ = editor.update(cx, |editor, cx| {
5958 editor.set_auto_replace_emoji_shortcode(true);
5959
5960 editor.handle_input("Hello ", cx);
5961 editor.handle_input(":wave", cx);
5962 assert_eq!(editor.text(cx), "Hello :wave".unindent());
5963
5964 editor.handle_input(":", cx);
5965 assert_eq!(editor.text(cx), "Hello 👋".unindent());
5966
5967 editor.handle_input(" :smile", cx);
5968 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
5969
5970 editor.handle_input(":", cx);
5971 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
5972
5973 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
5974 editor.handle_input(":wave", cx);
5975 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
5976
5977 editor.handle_input(":", cx);
5978 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
5979
5980 editor.handle_input(":1", cx);
5981 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
5982
5983 editor.handle_input(":", cx);
5984 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
5985
5986 // Ensure shortcode does not get replaced when it is part of a word
5987 editor.handle_input(" Test:wave", cx);
5988 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
5989
5990 editor.handle_input(":", cx);
5991 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
5992
5993 editor.set_auto_replace_emoji_shortcode(false);
5994
5995 // Ensure shortcode does not get replaced when auto replace is off
5996 editor.handle_input(" :wave", cx);
5997 assert_eq!(
5998 editor.text(cx),
5999 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6000 );
6001
6002 editor.handle_input(":", cx);
6003 assert_eq!(
6004 editor.text(cx),
6005 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6006 );
6007 });
6008}
6009
6010#[gpui::test]
6011async fn test_snippets(cx: &mut gpui::TestAppContext) {
6012 init_test(cx, |_| {});
6013
6014 let (text, insertion_ranges) = marked_text_ranges(
6015 indoc! {"
6016 a.ˇ b
6017 a.ˇ b
6018 a.ˇ b
6019 "},
6020 false,
6021 );
6022
6023 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6024 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6025
6026 _ = editor.update(cx, |editor, cx| {
6027 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6028
6029 editor
6030 .insert_snippet(&insertion_ranges, snippet, cx)
6031 .unwrap();
6032
6033 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6034 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6035 assert_eq!(editor.text(cx), expected_text);
6036 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6037 }
6038
6039 assert(
6040 editor,
6041 cx,
6042 indoc! {"
6043 a.f(«one», two, «three») b
6044 a.f(«one», two, «three») b
6045 a.f(«one», two, «three») b
6046 "},
6047 );
6048
6049 // Can't move earlier than the first tab stop
6050 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6051 assert(
6052 editor,
6053 cx,
6054 indoc! {"
6055 a.f(«one», two, «three») b
6056 a.f(«one», two, «three») b
6057 a.f(«one», two, «three») b
6058 "},
6059 );
6060
6061 assert!(editor.move_to_next_snippet_tabstop(cx));
6062 assert(
6063 editor,
6064 cx,
6065 indoc! {"
6066 a.f(one, «two», three) b
6067 a.f(one, «two», three) b
6068 a.f(one, «two», three) b
6069 "},
6070 );
6071
6072 editor.move_to_prev_snippet_tabstop(cx);
6073 assert(
6074 editor,
6075 cx,
6076 indoc! {"
6077 a.f(«one», two, «three») b
6078 a.f(«one», two, «three») b
6079 a.f(«one», two, «three») b
6080 "},
6081 );
6082
6083 assert!(editor.move_to_next_snippet_tabstop(cx));
6084 assert(
6085 editor,
6086 cx,
6087 indoc! {"
6088 a.f(one, «two», three) b
6089 a.f(one, «two», three) b
6090 a.f(one, «two», three) b
6091 "},
6092 );
6093 assert!(editor.move_to_next_snippet_tabstop(cx));
6094 assert(
6095 editor,
6096 cx,
6097 indoc! {"
6098 a.f(one, two, three)ˇ b
6099 a.f(one, two, three)ˇ b
6100 a.f(one, two, three)ˇ b
6101 "},
6102 );
6103
6104 // As soon as the last tab stop is reached, snippet state is gone
6105 editor.move_to_prev_snippet_tabstop(cx);
6106 assert(
6107 editor,
6108 cx,
6109 indoc! {"
6110 a.f(one, two, three)ˇ b
6111 a.f(one, two, three)ˇ b
6112 a.f(one, two, three)ˇ b
6113 "},
6114 );
6115 });
6116}
6117
6118#[gpui::test]
6119async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6120 init_test(cx, |_| {});
6121
6122 let fs = FakeFs::new(cx.executor());
6123 fs.insert_file("/file.rs", Default::default()).await;
6124
6125 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6126
6127 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6128 language_registry.add(rust_lang());
6129 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6130 "Rust",
6131 FakeLspAdapter {
6132 capabilities: lsp::ServerCapabilities {
6133 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6134 ..Default::default()
6135 },
6136 ..Default::default()
6137 },
6138 );
6139
6140 let buffer = project
6141 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6142 .await
6143 .unwrap();
6144
6145 cx.executor().start_waiting();
6146 let fake_server = fake_servers.next().await.unwrap();
6147
6148 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6149 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6150 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6151 assert!(cx.read(|cx| editor.is_dirty(cx)));
6152
6153 let save = editor
6154 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6155 .unwrap();
6156 fake_server
6157 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6158 assert_eq!(
6159 params.text_document.uri,
6160 lsp::Url::from_file_path("/file.rs").unwrap()
6161 );
6162 assert_eq!(params.options.tab_size, 4);
6163 Ok(Some(vec![lsp::TextEdit::new(
6164 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6165 ", ".to_string(),
6166 )]))
6167 })
6168 .next()
6169 .await;
6170 cx.executor().start_waiting();
6171 save.await;
6172
6173 assert_eq!(
6174 editor.update(cx, |editor, cx| editor.text(cx)),
6175 "one, two\nthree\n"
6176 );
6177 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6178
6179 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6180 assert!(cx.read(|cx| editor.is_dirty(cx)));
6181
6182 // Ensure we can still save even if formatting hangs.
6183 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6184 assert_eq!(
6185 params.text_document.uri,
6186 lsp::Url::from_file_path("/file.rs").unwrap()
6187 );
6188 futures::future::pending::<()>().await;
6189 unreachable!()
6190 });
6191 let save = editor
6192 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6193 .unwrap();
6194 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6195 cx.executor().start_waiting();
6196 save.await;
6197 assert_eq!(
6198 editor.update(cx, |editor, cx| editor.text(cx)),
6199 "one\ntwo\nthree\n"
6200 );
6201 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6202
6203 // For non-dirty buffer, no formatting request should be sent
6204 let save = editor
6205 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6206 .unwrap();
6207 let _pending_format_request = fake_server
6208 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6209 panic!("Should not be invoked on non-dirty buffer");
6210 })
6211 .next();
6212 cx.executor().start_waiting();
6213 save.await;
6214
6215 // Set rust language override and assert overridden tabsize is sent to language server
6216 update_test_language_settings(cx, |settings| {
6217 settings.languages.insert(
6218 "Rust".into(),
6219 LanguageSettingsContent {
6220 tab_size: NonZeroU32::new(8),
6221 ..Default::default()
6222 },
6223 );
6224 });
6225
6226 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6227 assert!(cx.read(|cx| editor.is_dirty(cx)));
6228 let save = editor
6229 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6230 .unwrap();
6231 fake_server
6232 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6233 assert_eq!(
6234 params.text_document.uri,
6235 lsp::Url::from_file_path("/file.rs").unwrap()
6236 );
6237 assert_eq!(params.options.tab_size, 8);
6238 Ok(Some(vec![]))
6239 })
6240 .next()
6241 .await;
6242 cx.executor().start_waiting();
6243 save.await;
6244}
6245
6246#[gpui::test]
6247async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6248 init_test(cx, |_| {});
6249
6250 let cols = 4;
6251 let rows = 10;
6252 let sample_text_1 = sample_text(rows, cols, 'a');
6253 assert_eq!(
6254 sample_text_1,
6255 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6256 );
6257 let sample_text_2 = sample_text(rows, cols, 'l');
6258 assert_eq!(
6259 sample_text_2,
6260 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6261 );
6262 let sample_text_3 = sample_text(rows, cols, 'v');
6263 assert_eq!(
6264 sample_text_3,
6265 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6266 );
6267
6268 let fs = FakeFs::new(cx.executor());
6269 fs.insert_tree(
6270 "/a",
6271 json!({
6272 "main.rs": sample_text_1,
6273 "other.rs": sample_text_2,
6274 "lib.rs": sample_text_3,
6275 }),
6276 )
6277 .await;
6278
6279 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6280 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6281 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6282
6283 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6284 language_registry.add(rust_lang());
6285 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6286 "Rust",
6287 FakeLspAdapter {
6288 capabilities: lsp::ServerCapabilities {
6289 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6290 ..Default::default()
6291 },
6292 ..Default::default()
6293 },
6294 );
6295
6296 let worktree = project.update(cx, |project, cx| {
6297 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
6298 assert_eq!(worktrees.len(), 1);
6299 worktrees.pop().unwrap()
6300 });
6301 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6302
6303 let buffer_1 = project
6304 .update(cx, |project, cx| {
6305 project.open_buffer((worktree_id, "main.rs"), cx)
6306 })
6307 .await
6308 .unwrap();
6309 let buffer_2 = project
6310 .update(cx, |project, cx| {
6311 project.open_buffer((worktree_id, "other.rs"), cx)
6312 })
6313 .await
6314 .unwrap();
6315 let buffer_3 = project
6316 .update(cx, |project, cx| {
6317 project.open_buffer((worktree_id, "lib.rs"), cx)
6318 })
6319 .await
6320 .unwrap();
6321
6322 let multi_buffer = cx.new_model(|cx| {
6323 let mut multi_buffer = MultiBuffer::new(0, ReadWrite);
6324 multi_buffer.push_excerpts(
6325 buffer_1.clone(),
6326 [
6327 ExcerptRange {
6328 context: Point::new(0, 0)..Point::new(3, 0),
6329 primary: None,
6330 },
6331 ExcerptRange {
6332 context: Point::new(5, 0)..Point::new(7, 0),
6333 primary: None,
6334 },
6335 ExcerptRange {
6336 context: Point::new(9, 0)..Point::new(10, 4),
6337 primary: None,
6338 },
6339 ],
6340 cx,
6341 );
6342 multi_buffer.push_excerpts(
6343 buffer_2.clone(),
6344 [
6345 ExcerptRange {
6346 context: Point::new(0, 0)..Point::new(3, 0),
6347 primary: None,
6348 },
6349 ExcerptRange {
6350 context: Point::new(5, 0)..Point::new(7, 0),
6351 primary: None,
6352 },
6353 ExcerptRange {
6354 context: Point::new(9, 0)..Point::new(10, 4),
6355 primary: None,
6356 },
6357 ],
6358 cx,
6359 );
6360 multi_buffer.push_excerpts(
6361 buffer_3.clone(),
6362 [
6363 ExcerptRange {
6364 context: Point::new(0, 0)..Point::new(3, 0),
6365 primary: None,
6366 },
6367 ExcerptRange {
6368 context: Point::new(5, 0)..Point::new(7, 0),
6369 primary: None,
6370 },
6371 ExcerptRange {
6372 context: Point::new(9, 0)..Point::new(10, 4),
6373 primary: None,
6374 },
6375 ],
6376 cx,
6377 );
6378 multi_buffer
6379 });
6380 let multi_buffer_editor = cx.new_view(|cx| {
6381 Editor::new(
6382 EditorMode::Full,
6383 multi_buffer,
6384 Some(project.clone()),
6385 true,
6386 cx,
6387 )
6388 });
6389
6390 multi_buffer_editor.update(cx, |editor, cx| {
6391 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
6392 editor.insert("|one|two|three|", cx);
6393 });
6394 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6395 multi_buffer_editor.update(cx, |editor, cx| {
6396 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
6397 s.select_ranges(Some(60..70))
6398 });
6399 editor.insert("|four|five|six|", cx);
6400 });
6401 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6402
6403 // First two buffers should be edited, but not the third one.
6404 assert_eq!(
6405 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6406 "a|one|two|three|aa\nbbbb\ncccc\n\nffff\ngggg\n\njjjj\nllll\nmmmm\nnnnn|four|five|six|\nr\n\nuuuu\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
6407 );
6408 buffer_1.update(cx, |buffer, _| {
6409 assert!(buffer.is_dirty());
6410 assert_eq!(
6411 buffer.text(),
6412 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
6413 )
6414 });
6415 buffer_2.update(cx, |buffer, _| {
6416 assert!(buffer.is_dirty());
6417 assert_eq!(
6418 buffer.text(),
6419 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
6420 )
6421 });
6422 buffer_3.update(cx, |buffer, _| {
6423 assert!(!buffer.is_dirty());
6424 assert_eq!(buffer.text(), sample_text_3,)
6425 });
6426
6427 cx.executor().start_waiting();
6428 let save = multi_buffer_editor
6429 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6430 .unwrap();
6431
6432 let fake_server = fake_servers.next().await.unwrap();
6433 fake_server
6434 .server
6435 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6436 Ok(Some(vec![lsp::TextEdit::new(
6437 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6438 format!("[{} formatted]", params.text_document.uri),
6439 )]))
6440 })
6441 .detach();
6442 save.await;
6443
6444 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
6445 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
6446 assert_eq!(
6447 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6448 "a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
6449 );
6450 buffer_1.update(cx, |buffer, _| {
6451 assert!(!buffer.is_dirty());
6452 assert_eq!(
6453 buffer.text(),
6454 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
6455 )
6456 });
6457 buffer_2.update(cx, |buffer, _| {
6458 assert!(!buffer.is_dirty());
6459 assert_eq!(
6460 buffer.text(),
6461 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
6462 )
6463 });
6464 buffer_3.update(cx, |buffer, _| {
6465 assert!(!buffer.is_dirty());
6466 assert_eq!(buffer.text(), sample_text_3,)
6467 });
6468}
6469
6470#[gpui::test]
6471async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
6472 init_test(cx, |_| {});
6473
6474 let fs = FakeFs::new(cx.executor());
6475 fs.insert_file("/file.rs", Default::default()).await;
6476
6477 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6478
6479 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6480 language_registry.add(rust_lang());
6481 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6482 "Rust",
6483 FakeLspAdapter {
6484 capabilities: lsp::ServerCapabilities {
6485 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
6486 ..Default::default()
6487 },
6488 ..Default::default()
6489 },
6490 );
6491
6492 let buffer = project
6493 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6494 .await
6495 .unwrap();
6496
6497 cx.executor().start_waiting();
6498 let fake_server = fake_servers.next().await.unwrap();
6499
6500 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6501 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6502 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6503 assert!(cx.read(|cx| editor.is_dirty(cx)));
6504
6505 let save = editor
6506 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6507 .unwrap();
6508 fake_server
6509 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6510 assert_eq!(
6511 params.text_document.uri,
6512 lsp::Url::from_file_path("/file.rs").unwrap()
6513 );
6514 assert_eq!(params.options.tab_size, 4);
6515 Ok(Some(vec![lsp::TextEdit::new(
6516 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6517 ", ".to_string(),
6518 )]))
6519 })
6520 .next()
6521 .await;
6522 cx.executor().start_waiting();
6523 save.await;
6524 assert_eq!(
6525 editor.update(cx, |editor, cx| editor.text(cx)),
6526 "one, two\nthree\n"
6527 );
6528 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6529
6530 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6531 assert!(cx.read(|cx| editor.is_dirty(cx)));
6532
6533 // Ensure we can still save even if formatting hangs.
6534 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
6535 move |params, _| async move {
6536 assert_eq!(
6537 params.text_document.uri,
6538 lsp::Url::from_file_path("/file.rs").unwrap()
6539 );
6540 futures::future::pending::<()>().await;
6541 unreachable!()
6542 },
6543 );
6544 let save = editor
6545 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6546 .unwrap();
6547 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6548 cx.executor().start_waiting();
6549 save.await;
6550 assert_eq!(
6551 editor.update(cx, |editor, cx| editor.text(cx)),
6552 "one\ntwo\nthree\n"
6553 );
6554 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6555
6556 // For non-dirty buffer, no formatting request should be sent
6557 let save = editor
6558 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6559 .unwrap();
6560 let _pending_format_request = fake_server
6561 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6562 panic!("Should not be invoked on non-dirty buffer");
6563 })
6564 .next();
6565 cx.executor().start_waiting();
6566 save.await;
6567
6568 // Set Rust language override and assert overridden tabsize is sent to language server
6569 update_test_language_settings(cx, |settings| {
6570 settings.languages.insert(
6571 "Rust".into(),
6572 LanguageSettingsContent {
6573 tab_size: NonZeroU32::new(8),
6574 ..Default::default()
6575 },
6576 );
6577 });
6578
6579 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6580 assert!(cx.read(|cx| editor.is_dirty(cx)));
6581 let save = editor
6582 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6583 .unwrap();
6584 fake_server
6585 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6586 assert_eq!(
6587 params.text_document.uri,
6588 lsp::Url::from_file_path("/file.rs").unwrap()
6589 );
6590 assert_eq!(params.options.tab_size, 8);
6591 Ok(Some(vec![]))
6592 })
6593 .next()
6594 .await;
6595 cx.executor().start_waiting();
6596 save.await;
6597}
6598
6599#[gpui::test]
6600async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
6601 init_test(cx, |settings| {
6602 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
6603 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
6604 ))
6605 });
6606
6607 let fs = FakeFs::new(cx.executor());
6608 fs.insert_file("/file.rs", Default::default()).await;
6609
6610 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6611
6612 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6613 language_registry.add(Arc::new(Language::new(
6614 LanguageConfig {
6615 name: "Rust".into(),
6616 matcher: LanguageMatcher {
6617 path_suffixes: vec!["rs".to_string()],
6618 ..Default::default()
6619 },
6620 ..LanguageConfig::default()
6621 },
6622 Some(tree_sitter_rust::language()),
6623 )));
6624 update_test_language_settings(cx, |settings| {
6625 // Enable Prettier formatting for the same buffer, and ensure
6626 // LSP is called instead of Prettier.
6627 settings.defaults.prettier = Some(PrettierSettings {
6628 allowed: true,
6629 ..PrettierSettings::default()
6630 });
6631 });
6632 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6633 "Rust",
6634 FakeLspAdapter {
6635 capabilities: lsp::ServerCapabilities {
6636 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6637 ..Default::default()
6638 },
6639 ..Default::default()
6640 },
6641 );
6642
6643 let buffer = project
6644 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6645 .await
6646 .unwrap();
6647
6648 cx.executor().start_waiting();
6649 let fake_server = fake_servers.next().await.unwrap();
6650
6651 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6652 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6653 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6654
6655 let format = editor
6656 .update(cx, |editor, cx| {
6657 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
6658 })
6659 .unwrap();
6660 fake_server
6661 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6662 assert_eq!(
6663 params.text_document.uri,
6664 lsp::Url::from_file_path("/file.rs").unwrap()
6665 );
6666 assert_eq!(params.options.tab_size, 4);
6667 Ok(Some(vec![lsp::TextEdit::new(
6668 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6669 ", ".to_string(),
6670 )]))
6671 })
6672 .next()
6673 .await;
6674 cx.executor().start_waiting();
6675 format.await;
6676 assert_eq!(
6677 editor.update(cx, |editor, cx| editor.text(cx)),
6678 "one, two\nthree\n"
6679 );
6680
6681 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6682 // Ensure we don't lock if formatting hangs.
6683 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6684 assert_eq!(
6685 params.text_document.uri,
6686 lsp::Url::from_file_path("/file.rs").unwrap()
6687 );
6688 futures::future::pending::<()>().await;
6689 unreachable!()
6690 });
6691 let format = editor
6692 .update(cx, |editor, cx| {
6693 editor.perform_format(project, FormatTrigger::Manual, cx)
6694 })
6695 .unwrap();
6696 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6697 cx.executor().start_waiting();
6698 format.await;
6699 assert_eq!(
6700 editor.update(cx, |editor, cx| editor.text(cx)),
6701 "one\ntwo\nthree\n"
6702 );
6703}
6704
6705#[gpui::test]
6706async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
6707 init_test(cx, |_| {});
6708
6709 let mut cx = EditorLspTestContext::new_rust(
6710 lsp::ServerCapabilities {
6711 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6712 ..Default::default()
6713 },
6714 cx,
6715 )
6716 .await;
6717
6718 cx.set_state(indoc! {"
6719 one.twoˇ
6720 "});
6721
6722 // The format request takes a long time. When it completes, it inserts
6723 // a newline and an indent before the `.`
6724 cx.lsp
6725 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
6726 let executor = cx.background_executor().clone();
6727 async move {
6728 executor.timer(Duration::from_millis(100)).await;
6729 Ok(Some(vec![lsp::TextEdit {
6730 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
6731 new_text: "\n ".into(),
6732 }]))
6733 }
6734 });
6735
6736 // Submit a format request.
6737 let format_1 = cx
6738 .update_editor(|editor, cx| editor.format(&Format, cx))
6739 .unwrap();
6740 cx.executor().run_until_parked();
6741
6742 // Submit a second format request.
6743 let format_2 = cx
6744 .update_editor(|editor, cx| editor.format(&Format, cx))
6745 .unwrap();
6746 cx.executor().run_until_parked();
6747
6748 // Wait for both format requests to complete
6749 cx.executor().advance_clock(Duration::from_millis(200));
6750 cx.executor().start_waiting();
6751 format_1.await.unwrap();
6752 cx.executor().start_waiting();
6753 format_2.await.unwrap();
6754
6755 // The formatting edits only happens once.
6756 cx.assert_editor_state(indoc! {"
6757 one
6758 .twoˇ
6759 "});
6760}
6761
6762#[gpui::test]
6763async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
6764 init_test(cx, |settings| {
6765 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
6766 });
6767
6768 let mut cx = EditorLspTestContext::new_rust(
6769 lsp::ServerCapabilities {
6770 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6771 ..Default::default()
6772 },
6773 cx,
6774 )
6775 .await;
6776
6777 // Set up a buffer white some trailing whitespace and no trailing newline.
6778 cx.set_state(
6779 &[
6780 "one ", //
6781 "twoˇ", //
6782 "three ", //
6783 "four", //
6784 ]
6785 .join("\n"),
6786 );
6787
6788 // Submit a format request.
6789 let format = cx
6790 .update_editor(|editor, cx| editor.format(&Format, cx))
6791 .unwrap();
6792
6793 // Record which buffer changes have been sent to the language server
6794 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
6795 cx.lsp
6796 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
6797 let buffer_changes = buffer_changes.clone();
6798 move |params, _| {
6799 buffer_changes.lock().extend(
6800 params
6801 .content_changes
6802 .into_iter()
6803 .map(|e| (e.range.unwrap(), e.text)),
6804 );
6805 }
6806 });
6807
6808 // Handle formatting requests to the language server.
6809 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
6810 let buffer_changes = buffer_changes.clone();
6811 move |_, _| {
6812 // When formatting is requested, trailing whitespace has already been stripped,
6813 // and the trailing newline has already been added.
6814 assert_eq!(
6815 &buffer_changes.lock()[1..],
6816 &[
6817 (
6818 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
6819 "".into()
6820 ),
6821 (
6822 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
6823 "".into()
6824 ),
6825 (
6826 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
6827 "\n".into()
6828 ),
6829 ]
6830 );
6831
6832 // Insert blank lines between each line of the buffer.
6833 async move {
6834 Ok(Some(vec![
6835 lsp::TextEdit {
6836 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
6837 new_text: "\n".into(),
6838 },
6839 lsp::TextEdit {
6840 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
6841 new_text: "\n".into(),
6842 },
6843 ]))
6844 }
6845 }
6846 });
6847
6848 // After formatting the buffer, the trailing whitespace is stripped,
6849 // a newline is appended, and the edits provided by the language server
6850 // have been applied.
6851 format.await.unwrap();
6852 cx.assert_editor_state(
6853 &[
6854 "one", //
6855 "", //
6856 "twoˇ", //
6857 "", //
6858 "three", //
6859 "four", //
6860 "", //
6861 ]
6862 .join("\n"),
6863 );
6864
6865 // Undoing the formatting undoes the trailing whitespace removal, the
6866 // trailing newline, and the LSP edits.
6867 cx.update_buffer(|buffer, cx| buffer.undo(cx));
6868 cx.assert_editor_state(
6869 &[
6870 "one ", //
6871 "twoˇ", //
6872 "three ", //
6873 "four", //
6874 ]
6875 .join("\n"),
6876 );
6877}
6878
6879#[gpui::test]
6880async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
6881 cx: &mut gpui::TestAppContext,
6882) {
6883 init_test(cx, |_| {});
6884
6885 cx.update(|cx| {
6886 cx.update_global::<SettingsStore, _>(|settings, cx| {
6887 settings.update_user_settings::<EditorSettings>(cx, |settings| {
6888 settings.auto_signature_help = Some(true);
6889 });
6890 });
6891 });
6892
6893 let mut cx = EditorLspTestContext::new_rust(
6894 lsp::ServerCapabilities {
6895 signature_help_provider: Some(lsp::SignatureHelpOptions {
6896 ..Default::default()
6897 }),
6898 ..Default::default()
6899 },
6900 cx,
6901 )
6902 .await;
6903
6904 let language = Language::new(
6905 LanguageConfig {
6906 name: "Rust".into(),
6907 brackets: BracketPairConfig {
6908 pairs: vec![
6909 BracketPair {
6910 start: "{".to_string(),
6911 end: "}".to_string(),
6912 close: true,
6913 surround: true,
6914 newline: true,
6915 },
6916 BracketPair {
6917 start: "(".to_string(),
6918 end: ")".to_string(),
6919 close: true,
6920 surround: true,
6921 newline: true,
6922 },
6923 BracketPair {
6924 start: "/*".to_string(),
6925 end: " */".to_string(),
6926 close: true,
6927 surround: true,
6928 newline: true,
6929 },
6930 BracketPair {
6931 start: "[".to_string(),
6932 end: "]".to_string(),
6933 close: false,
6934 surround: false,
6935 newline: true,
6936 },
6937 BracketPair {
6938 start: "\"".to_string(),
6939 end: "\"".to_string(),
6940 close: true,
6941 surround: true,
6942 newline: false,
6943 },
6944 BracketPair {
6945 start: "<".to_string(),
6946 end: ">".to_string(),
6947 close: false,
6948 surround: true,
6949 newline: true,
6950 },
6951 ],
6952 ..Default::default()
6953 },
6954 autoclose_before: "})]".to_string(),
6955 ..Default::default()
6956 },
6957 Some(tree_sitter_rust::language()),
6958 );
6959 let language = Arc::new(language);
6960
6961 cx.language_registry().add(language.clone());
6962 cx.update_buffer(|buffer, cx| {
6963 buffer.set_language(Some(language), cx);
6964 });
6965
6966 cx.set_state(
6967 &r#"
6968 fn main() {
6969 sampleˇ
6970 }
6971 "#
6972 .unindent(),
6973 );
6974
6975 cx.update_editor(|view, cx| {
6976 view.handle_input("(", cx);
6977 });
6978 cx.assert_editor_state(
6979 &"
6980 fn main() {
6981 sample(ˇ)
6982 }
6983 "
6984 .unindent(),
6985 );
6986
6987 let mocked_response = lsp::SignatureHelp {
6988 signatures: vec![lsp::SignatureInformation {
6989 label: "fn sample(param1: u8, param2: u8)".to_string(),
6990 documentation: None,
6991 parameters: Some(vec![
6992 lsp::ParameterInformation {
6993 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
6994 documentation: None,
6995 },
6996 lsp::ParameterInformation {
6997 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
6998 documentation: None,
6999 },
7000 ]),
7001 active_parameter: None,
7002 }],
7003 active_signature: Some(0),
7004 active_parameter: Some(0),
7005 };
7006 handle_signature_help_request(&mut cx, mocked_response).await;
7007
7008 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7009 .await;
7010
7011 cx.editor(|editor, _| {
7012 let signature_help_state = editor.signature_help_state.popover().cloned();
7013 assert!(signature_help_state.is_some());
7014 let ParsedMarkdown {
7015 text, highlights, ..
7016 } = signature_help_state.unwrap().parsed_content;
7017 assert_eq!(text, "param1: u8, param2: u8");
7018 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7019 });
7020}
7021
7022#[gpui::test]
7023async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7024 init_test(cx, |_| {});
7025
7026 cx.update(|cx| {
7027 cx.update_global::<SettingsStore, _>(|settings, cx| {
7028 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7029 settings.auto_signature_help = Some(false);
7030 settings.show_signature_help_after_edits = Some(false);
7031 });
7032 });
7033 });
7034
7035 let mut cx = EditorLspTestContext::new_rust(
7036 lsp::ServerCapabilities {
7037 signature_help_provider: Some(lsp::SignatureHelpOptions {
7038 ..Default::default()
7039 }),
7040 ..Default::default()
7041 },
7042 cx,
7043 )
7044 .await;
7045
7046 let language = Language::new(
7047 LanguageConfig {
7048 name: "Rust".into(),
7049 brackets: BracketPairConfig {
7050 pairs: vec![
7051 BracketPair {
7052 start: "{".to_string(),
7053 end: "}".to_string(),
7054 close: true,
7055 surround: true,
7056 newline: true,
7057 },
7058 BracketPair {
7059 start: "(".to_string(),
7060 end: ")".to_string(),
7061 close: true,
7062 surround: true,
7063 newline: true,
7064 },
7065 BracketPair {
7066 start: "/*".to_string(),
7067 end: " */".to_string(),
7068 close: true,
7069 surround: true,
7070 newline: true,
7071 },
7072 BracketPair {
7073 start: "[".to_string(),
7074 end: "]".to_string(),
7075 close: false,
7076 surround: false,
7077 newline: true,
7078 },
7079 BracketPair {
7080 start: "\"".to_string(),
7081 end: "\"".to_string(),
7082 close: true,
7083 surround: true,
7084 newline: false,
7085 },
7086 BracketPair {
7087 start: "<".to_string(),
7088 end: ">".to_string(),
7089 close: false,
7090 surround: true,
7091 newline: true,
7092 },
7093 ],
7094 ..Default::default()
7095 },
7096 autoclose_before: "})]".to_string(),
7097 ..Default::default()
7098 },
7099 Some(tree_sitter_rust::language()),
7100 );
7101 let language = Arc::new(language);
7102
7103 cx.language_registry().add(language.clone());
7104 cx.update_buffer(|buffer, cx| {
7105 buffer.set_language(Some(language), cx);
7106 });
7107
7108 // Ensure that signature_help is not called when no signature help is enabled.
7109 cx.set_state(
7110 &r#"
7111 fn main() {
7112 sampleˇ
7113 }
7114 "#
7115 .unindent(),
7116 );
7117 cx.update_editor(|view, cx| {
7118 view.handle_input("(", cx);
7119 });
7120 cx.assert_editor_state(
7121 &"
7122 fn main() {
7123 sample(ˇ)
7124 }
7125 "
7126 .unindent(),
7127 );
7128 cx.editor(|editor, _| {
7129 assert!(editor.signature_help_state.task().is_none());
7130 });
7131
7132 let mocked_response = lsp::SignatureHelp {
7133 signatures: vec![lsp::SignatureInformation {
7134 label: "fn sample(param1: u8, param2: u8)".to_string(),
7135 documentation: None,
7136 parameters: Some(vec![
7137 lsp::ParameterInformation {
7138 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7139 documentation: None,
7140 },
7141 lsp::ParameterInformation {
7142 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7143 documentation: None,
7144 },
7145 ]),
7146 active_parameter: None,
7147 }],
7148 active_signature: Some(0),
7149 active_parameter: Some(0),
7150 };
7151
7152 // Ensure that signature_help is called when enabled afte edits
7153 cx.update(|cx| {
7154 cx.update_global::<SettingsStore, _>(|settings, cx| {
7155 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7156 settings.auto_signature_help = Some(false);
7157 settings.show_signature_help_after_edits = Some(true);
7158 });
7159 });
7160 });
7161 cx.set_state(
7162 &r#"
7163 fn main() {
7164 sampleˇ
7165 }
7166 "#
7167 .unindent(),
7168 );
7169 cx.update_editor(|view, cx| {
7170 view.handle_input("(", cx);
7171 });
7172 cx.assert_editor_state(
7173 &"
7174 fn main() {
7175 sample(ˇ)
7176 }
7177 "
7178 .unindent(),
7179 );
7180 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7181 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7182 .await;
7183 cx.update_editor(|editor, _| {
7184 let signature_help_state = editor.signature_help_state.popover().cloned();
7185 assert!(signature_help_state.is_some());
7186 let ParsedMarkdown {
7187 text, highlights, ..
7188 } = signature_help_state.unwrap().parsed_content;
7189 assert_eq!(text, "param1: u8, param2: u8");
7190 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7191 editor.signature_help_state = SignatureHelpState::default();
7192 });
7193
7194 // Ensure that signature_help is called when auto signature help override is enabled
7195 cx.update(|cx| {
7196 cx.update_global::<SettingsStore, _>(|settings, cx| {
7197 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7198 settings.auto_signature_help = Some(true);
7199 settings.show_signature_help_after_edits = Some(false);
7200 });
7201 });
7202 });
7203 cx.set_state(
7204 &r#"
7205 fn main() {
7206 sampleˇ
7207 }
7208 "#
7209 .unindent(),
7210 );
7211 cx.update_editor(|view, cx| {
7212 view.handle_input("(", cx);
7213 });
7214 cx.assert_editor_state(
7215 &"
7216 fn main() {
7217 sample(ˇ)
7218 }
7219 "
7220 .unindent(),
7221 );
7222 handle_signature_help_request(&mut cx, mocked_response).await;
7223 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7224 .await;
7225 cx.editor(|editor, _| {
7226 let signature_help_state = editor.signature_help_state.popover().cloned();
7227 assert!(signature_help_state.is_some());
7228 let ParsedMarkdown {
7229 text, highlights, ..
7230 } = signature_help_state.unwrap().parsed_content;
7231 assert_eq!(text, "param1: u8, param2: u8");
7232 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7233 });
7234}
7235
7236#[gpui::test]
7237async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7238 init_test(cx, |_| {});
7239 cx.update(|cx| {
7240 cx.update_global::<SettingsStore, _>(|settings, cx| {
7241 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7242 settings.auto_signature_help = Some(true);
7243 });
7244 });
7245 });
7246
7247 let mut cx = EditorLspTestContext::new_rust(
7248 lsp::ServerCapabilities {
7249 signature_help_provider: Some(lsp::SignatureHelpOptions {
7250 ..Default::default()
7251 }),
7252 ..Default::default()
7253 },
7254 cx,
7255 )
7256 .await;
7257
7258 // A test that directly calls `show_signature_help`
7259 cx.update_editor(|editor, cx| {
7260 editor.show_signature_help(&ShowSignatureHelp, cx);
7261 });
7262
7263 let mocked_response = lsp::SignatureHelp {
7264 signatures: vec![lsp::SignatureInformation {
7265 label: "fn sample(param1: u8, param2: u8)".to_string(),
7266 documentation: None,
7267 parameters: Some(vec![
7268 lsp::ParameterInformation {
7269 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7270 documentation: None,
7271 },
7272 lsp::ParameterInformation {
7273 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7274 documentation: None,
7275 },
7276 ]),
7277 active_parameter: None,
7278 }],
7279 active_signature: Some(0),
7280 active_parameter: Some(0),
7281 };
7282 handle_signature_help_request(&mut cx, mocked_response).await;
7283
7284 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7285 .await;
7286
7287 cx.editor(|editor, _| {
7288 let signature_help_state = editor.signature_help_state.popover().cloned();
7289 assert!(signature_help_state.is_some());
7290 let ParsedMarkdown {
7291 text, highlights, ..
7292 } = signature_help_state.unwrap().parsed_content;
7293 assert_eq!(text, "param1: u8, param2: u8");
7294 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7295 });
7296
7297 // When exiting outside from inside the brackets, `signature_help` is closed.
7298 cx.set_state(indoc! {"
7299 fn main() {
7300 sample(ˇ);
7301 }
7302
7303 fn sample(param1: u8, param2: u8) {}
7304 "});
7305
7306 cx.update_editor(|editor, cx| {
7307 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
7308 });
7309
7310 let mocked_response = lsp::SignatureHelp {
7311 signatures: Vec::new(),
7312 active_signature: None,
7313 active_parameter: None,
7314 };
7315 handle_signature_help_request(&mut cx, mocked_response).await;
7316
7317 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7318 .await;
7319
7320 cx.editor(|editor, _| {
7321 assert!(!editor.signature_help_state.is_shown());
7322 });
7323
7324 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
7325 cx.set_state(indoc! {"
7326 fn main() {
7327 sample(ˇ);
7328 }
7329
7330 fn sample(param1: u8, param2: u8) {}
7331 "});
7332
7333 let mocked_response = lsp::SignatureHelp {
7334 signatures: vec![lsp::SignatureInformation {
7335 label: "fn sample(param1: u8, param2: u8)".to_string(),
7336 documentation: None,
7337 parameters: Some(vec![
7338 lsp::ParameterInformation {
7339 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7340 documentation: None,
7341 },
7342 lsp::ParameterInformation {
7343 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7344 documentation: None,
7345 },
7346 ]),
7347 active_parameter: None,
7348 }],
7349 active_signature: Some(0),
7350 active_parameter: Some(0),
7351 };
7352 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7353 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7354 .await;
7355 cx.editor(|editor, _| {
7356 assert!(editor.signature_help_state.is_shown());
7357 });
7358
7359 // Restore the popover with more parameter input
7360 cx.set_state(indoc! {"
7361 fn main() {
7362 sample(param1, param2ˇ);
7363 }
7364
7365 fn sample(param1: u8, param2: u8) {}
7366 "});
7367
7368 let mocked_response = lsp::SignatureHelp {
7369 signatures: vec![lsp::SignatureInformation {
7370 label: "fn sample(param1: u8, param2: u8)".to_string(),
7371 documentation: None,
7372 parameters: Some(vec![
7373 lsp::ParameterInformation {
7374 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7375 documentation: None,
7376 },
7377 lsp::ParameterInformation {
7378 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7379 documentation: None,
7380 },
7381 ]),
7382 active_parameter: None,
7383 }],
7384 active_signature: Some(0),
7385 active_parameter: Some(1),
7386 };
7387 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7388 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7389 .await;
7390
7391 // When selecting a range, the popover is gone.
7392 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
7393 cx.update_editor(|editor, cx| {
7394 editor.change_selections(None, cx, |s| {
7395 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7396 })
7397 });
7398 cx.assert_editor_state(indoc! {"
7399 fn main() {
7400 sample(param1, «ˇparam2»);
7401 }
7402
7403 fn sample(param1: u8, param2: u8) {}
7404 "});
7405 cx.editor(|editor, _| {
7406 assert!(!editor.signature_help_state.is_shown());
7407 });
7408
7409 // When unselecting again, the popover is back if within the brackets.
7410 cx.update_editor(|editor, cx| {
7411 editor.change_selections(None, cx, |s| {
7412 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7413 })
7414 });
7415 cx.assert_editor_state(indoc! {"
7416 fn main() {
7417 sample(param1, ˇparam2);
7418 }
7419
7420 fn sample(param1: u8, param2: u8) {}
7421 "});
7422 handle_signature_help_request(&mut cx, mocked_response).await;
7423 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7424 .await;
7425 cx.editor(|editor, _| {
7426 assert!(editor.signature_help_state.is_shown());
7427 });
7428
7429 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
7430 cx.update_editor(|editor, cx| {
7431 editor.change_selections(None, cx, |s| {
7432 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
7433 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7434 })
7435 });
7436 cx.assert_editor_state(indoc! {"
7437 fn main() {
7438 sample(param1, ˇparam2);
7439 }
7440
7441 fn sample(param1: u8, param2: u8) {}
7442 "});
7443
7444 let mocked_response = lsp::SignatureHelp {
7445 signatures: vec![lsp::SignatureInformation {
7446 label: "fn sample(param1: u8, param2: u8)".to_string(),
7447 documentation: None,
7448 parameters: Some(vec![
7449 lsp::ParameterInformation {
7450 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7451 documentation: None,
7452 },
7453 lsp::ParameterInformation {
7454 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7455 documentation: None,
7456 },
7457 ]),
7458 active_parameter: None,
7459 }],
7460 active_signature: Some(0),
7461 active_parameter: Some(1),
7462 };
7463 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7464 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7465 .await;
7466 cx.update_editor(|editor, cx| {
7467 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
7468 });
7469 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7470 .await;
7471 cx.update_editor(|editor, cx| {
7472 editor.change_selections(None, cx, |s| {
7473 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7474 })
7475 });
7476 cx.assert_editor_state(indoc! {"
7477 fn main() {
7478 sample(param1, «ˇparam2»);
7479 }
7480
7481 fn sample(param1: u8, param2: u8) {}
7482 "});
7483 cx.update_editor(|editor, cx| {
7484 editor.change_selections(None, cx, |s| {
7485 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7486 })
7487 });
7488 cx.assert_editor_state(indoc! {"
7489 fn main() {
7490 sample(param1, ˇparam2);
7491 }
7492
7493 fn sample(param1: u8, param2: u8) {}
7494 "});
7495 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
7496 .await;
7497}
7498
7499#[gpui::test]
7500async fn test_completion(cx: &mut gpui::TestAppContext) {
7501 init_test(cx, |_| {});
7502
7503 let mut cx = EditorLspTestContext::new_rust(
7504 lsp::ServerCapabilities {
7505 completion_provider: Some(lsp::CompletionOptions {
7506 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7507 resolve_provider: Some(true),
7508 ..Default::default()
7509 }),
7510 ..Default::default()
7511 },
7512 cx,
7513 )
7514 .await;
7515 let counter = Arc::new(AtomicUsize::new(0));
7516
7517 cx.set_state(indoc! {"
7518 oneˇ
7519 two
7520 three
7521 "});
7522 cx.simulate_keystroke(".");
7523 handle_completion_request(
7524 &mut cx,
7525 indoc! {"
7526 one.|<>
7527 two
7528 three
7529 "},
7530 vec!["first_completion", "second_completion"],
7531 counter.clone(),
7532 )
7533 .await;
7534 cx.condition(|editor, _| editor.context_menu_visible())
7535 .await;
7536 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
7537
7538 let apply_additional_edits = cx.update_editor(|editor, cx| {
7539 editor.context_menu_next(&Default::default(), cx);
7540 editor
7541 .confirm_completion(&ConfirmCompletion::default(), cx)
7542 .unwrap()
7543 });
7544 cx.assert_editor_state(indoc! {"
7545 one.second_completionˇ
7546 two
7547 three
7548 "});
7549
7550 handle_resolve_completion_request(
7551 &mut cx,
7552 Some(vec![
7553 (
7554 //This overlaps with the primary completion edit which is
7555 //misbehavior from the LSP spec, test that we filter it out
7556 indoc! {"
7557 one.second_ˇcompletion
7558 two
7559 threeˇ
7560 "},
7561 "overlapping additional edit",
7562 ),
7563 (
7564 indoc! {"
7565 one.second_completion
7566 two
7567 threeˇ
7568 "},
7569 "\nadditional edit",
7570 ),
7571 ]),
7572 )
7573 .await;
7574 apply_additional_edits.await.unwrap();
7575 cx.assert_editor_state(indoc! {"
7576 one.second_completionˇ
7577 two
7578 three
7579 additional edit
7580 "});
7581
7582 cx.set_state(indoc! {"
7583 one.second_completion
7584 twoˇ
7585 threeˇ
7586 additional edit
7587 "});
7588 cx.simulate_keystroke(" ");
7589 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7590 cx.simulate_keystroke("s");
7591 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7592
7593 cx.assert_editor_state(indoc! {"
7594 one.second_completion
7595 two sˇ
7596 three sˇ
7597 additional edit
7598 "});
7599 handle_completion_request(
7600 &mut cx,
7601 indoc! {"
7602 one.second_completion
7603 two s
7604 three <s|>
7605 additional edit
7606 "},
7607 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
7608 counter.clone(),
7609 )
7610 .await;
7611 cx.condition(|editor, _| editor.context_menu_visible())
7612 .await;
7613 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
7614
7615 cx.simulate_keystroke("i");
7616
7617 handle_completion_request(
7618 &mut cx,
7619 indoc! {"
7620 one.second_completion
7621 two si
7622 three <si|>
7623 additional edit
7624 "},
7625 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
7626 counter.clone(),
7627 )
7628 .await;
7629 cx.condition(|editor, _| editor.context_menu_visible())
7630 .await;
7631 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
7632
7633 let apply_additional_edits = cx.update_editor(|editor, cx| {
7634 editor
7635 .confirm_completion(&ConfirmCompletion::default(), cx)
7636 .unwrap()
7637 });
7638 cx.assert_editor_state(indoc! {"
7639 one.second_completion
7640 two sixth_completionˇ
7641 three sixth_completionˇ
7642 additional edit
7643 "});
7644
7645 handle_resolve_completion_request(&mut cx, None).await;
7646 apply_additional_edits.await.unwrap();
7647
7648 _ = cx.update(|cx| {
7649 cx.update_global::<SettingsStore, _>(|settings, cx| {
7650 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7651 settings.show_completions_on_input = Some(false);
7652 });
7653 })
7654 });
7655 cx.set_state("editorˇ");
7656 cx.simulate_keystroke(".");
7657 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7658 cx.simulate_keystroke("c");
7659 cx.simulate_keystroke("l");
7660 cx.simulate_keystroke("o");
7661 cx.assert_editor_state("editor.cloˇ");
7662 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7663 cx.update_editor(|editor, cx| {
7664 editor.show_completions(&ShowCompletions { trigger: None }, cx);
7665 });
7666 handle_completion_request(
7667 &mut cx,
7668 "editor.<clo|>",
7669 vec!["close", "clobber"],
7670 counter.clone(),
7671 )
7672 .await;
7673 cx.condition(|editor, _| editor.context_menu_visible())
7674 .await;
7675 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
7676
7677 let apply_additional_edits = cx.update_editor(|editor, cx| {
7678 editor
7679 .confirm_completion(&ConfirmCompletion::default(), cx)
7680 .unwrap()
7681 });
7682 cx.assert_editor_state("editor.closeˇ");
7683 handle_resolve_completion_request(&mut cx, None).await;
7684 apply_additional_edits.await.unwrap();
7685}
7686
7687#[gpui::test]
7688async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
7689 init_test(cx, |_| {});
7690 let mut cx = EditorLspTestContext::new_rust(
7691 lsp::ServerCapabilities {
7692 completion_provider: Some(lsp::CompletionOptions {
7693 trigger_characters: Some(vec![".".to_string()]),
7694 ..Default::default()
7695 }),
7696 ..Default::default()
7697 },
7698 cx,
7699 )
7700 .await;
7701 cx.lsp
7702 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
7703 Ok(Some(lsp::CompletionResponse::Array(vec![
7704 lsp::CompletionItem {
7705 label: "first".into(),
7706 ..Default::default()
7707 },
7708 lsp::CompletionItem {
7709 label: "last".into(),
7710 ..Default::default()
7711 },
7712 ])))
7713 });
7714 cx.set_state("variableˇ");
7715 cx.simulate_keystroke(".");
7716 cx.executor().run_until_parked();
7717
7718 cx.update_editor(|editor, _| {
7719 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7720 assert_eq!(
7721 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
7722 &["first", "last"]
7723 );
7724 } else {
7725 panic!("expected completion menu to be open");
7726 }
7727 });
7728
7729 cx.update_editor(|editor, cx| {
7730 editor.move_page_down(&MovePageDown::default(), cx);
7731 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7732 assert!(
7733 menu.selected_item == 1,
7734 "expected PageDown to select the last item from the context menu"
7735 );
7736 } else {
7737 panic!("expected completion menu to stay open after PageDown");
7738 }
7739 });
7740
7741 cx.update_editor(|editor, cx| {
7742 editor.move_page_up(&MovePageUp::default(), cx);
7743 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7744 assert!(
7745 menu.selected_item == 0,
7746 "expected PageUp to select the first item from the context menu"
7747 );
7748 } else {
7749 panic!("expected completion menu to stay open after PageUp");
7750 }
7751 });
7752}
7753
7754#[gpui::test]
7755async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
7756 init_test(cx, |_| {});
7757
7758 let mut cx = EditorLspTestContext::new_rust(
7759 lsp::ServerCapabilities {
7760 completion_provider: Some(lsp::CompletionOptions {
7761 trigger_characters: Some(vec![".".to_string()]),
7762 resolve_provider: Some(true),
7763 ..Default::default()
7764 }),
7765 ..Default::default()
7766 },
7767 cx,
7768 )
7769 .await;
7770
7771 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
7772 cx.simulate_keystroke(".");
7773 let completion_item = lsp::CompletionItem {
7774 label: "Some".into(),
7775 kind: Some(lsp::CompletionItemKind::SNIPPET),
7776 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
7777 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
7778 kind: lsp::MarkupKind::Markdown,
7779 value: "```rust\nSome(2)\n```".to_string(),
7780 })),
7781 deprecated: Some(false),
7782 sort_text: Some("Some".to_string()),
7783 filter_text: Some("Some".to_string()),
7784 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
7785 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7786 range: lsp::Range {
7787 start: lsp::Position {
7788 line: 0,
7789 character: 22,
7790 },
7791 end: lsp::Position {
7792 line: 0,
7793 character: 22,
7794 },
7795 },
7796 new_text: "Some(2)".to_string(),
7797 })),
7798 additional_text_edits: Some(vec![lsp::TextEdit {
7799 range: lsp::Range {
7800 start: lsp::Position {
7801 line: 0,
7802 character: 20,
7803 },
7804 end: lsp::Position {
7805 line: 0,
7806 character: 22,
7807 },
7808 },
7809 new_text: "".to_string(),
7810 }]),
7811 ..Default::default()
7812 };
7813
7814 let closure_completion_item = completion_item.clone();
7815 let counter = Arc::new(AtomicUsize::new(0));
7816 let counter_clone = counter.clone();
7817 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
7818 let task_completion_item = closure_completion_item.clone();
7819 counter_clone.fetch_add(1, atomic::Ordering::Release);
7820 async move {
7821 Ok(Some(lsp::CompletionResponse::Array(vec![
7822 task_completion_item,
7823 ])))
7824 }
7825 });
7826
7827 cx.condition(|editor, _| editor.context_menu_visible())
7828 .await;
7829 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
7830 assert!(request.next().await.is_some());
7831 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
7832
7833 cx.simulate_keystroke("S");
7834 cx.simulate_keystroke("o");
7835 cx.simulate_keystroke("m");
7836 cx.condition(|editor, _| editor.context_menu_visible())
7837 .await;
7838 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
7839 assert!(request.next().await.is_some());
7840 assert!(request.next().await.is_some());
7841 assert!(request.next().await.is_some());
7842 request.close();
7843 assert!(request.next().await.is_none());
7844 assert_eq!(
7845 counter.load(atomic::Ordering::Acquire),
7846 4,
7847 "With the completions menu open, only one LSP request should happen per input"
7848 );
7849}
7850
7851#[gpui::test]
7852async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
7853 init_test(cx, |_| {});
7854 let mut cx = EditorTestContext::new(cx).await;
7855 let language = Arc::new(Language::new(
7856 LanguageConfig {
7857 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
7858 ..Default::default()
7859 },
7860 Some(tree_sitter_rust::language()),
7861 ));
7862 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
7863
7864 // If multiple selections intersect a line, the line is only toggled once.
7865 cx.set_state(indoc! {"
7866 fn a() {
7867 «//b();
7868 ˇ»// «c();
7869 //ˇ» d();
7870 }
7871 "});
7872
7873 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7874
7875 cx.assert_editor_state(indoc! {"
7876 fn a() {
7877 «b();
7878 c();
7879 ˇ» d();
7880 }
7881 "});
7882
7883 // The comment prefix is inserted at the same column for every line in a
7884 // selection.
7885 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7886
7887 cx.assert_editor_state(indoc! {"
7888 fn a() {
7889 // «b();
7890 // c();
7891 ˇ»// d();
7892 }
7893 "});
7894
7895 // If a selection ends at the beginning of a line, that line is not toggled.
7896 cx.set_selections_state(indoc! {"
7897 fn a() {
7898 // b();
7899 «// c();
7900 ˇ» // d();
7901 }
7902 "});
7903
7904 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7905
7906 cx.assert_editor_state(indoc! {"
7907 fn a() {
7908 // b();
7909 «c();
7910 ˇ» // d();
7911 }
7912 "});
7913
7914 // If a selection span a single line and is empty, the line is toggled.
7915 cx.set_state(indoc! {"
7916 fn a() {
7917 a();
7918 b();
7919 ˇ
7920 }
7921 "});
7922
7923 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7924
7925 cx.assert_editor_state(indoc! {"
7926 fn a() {
7927 a();
7928 b();
7929 //•ˇ
7930 }
7931 "});
7932
7933 // If a selection span multiple lines, empty lines are not toggled.
7934 cx.set_state(indoc! {"
7935 fn a() {
7936 «a();
7937
7938 c();ˇ»
7939 }
7940 "});
7941
7942 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7943
7944 cx.assert_editor_state(indoc! {"
7945 fn a() {
7946 // «a();
7947
7948 // c();ˇ»
7949 }
7950 "});
7951
7952 // If a selection includes multiple comment prefixes, all lines are uncommented.
7953 cx.set_state(indoc! {"
7954 fn a() {
7955 «// a();
7956 /// b();
7957 //! c();ˇ»
7958 }
7959 "});
7960
7961 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7962
7963 cx.assert_editor_state(indoc! {"
7964 fn a() {
7965 «a();
7966 b();
7967 c();ˇ»
7968 }
7969 "});
7970}
7971
7972#[gpui::test]
7973async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
7974 init_test(cx, |_| {});
7975
7976 let language = Arc::new(Language::new(
7977 LanguageConfig {
7978 line_comments: vec!["// ".into()],
7979 ..Default::default()
7980 },
7981 Some(tree_sitter_rust::language()),
7982 ));
7983
7984 let mut cx = EditorTestContext::new(cx).await;
7985
7986 cx.language_registry().add(language.clone());
7987 cx.update_buffer(|buffer, cx| {
7988 buffer.set_language(Some(language), cx);
7989 });
7990
7991 let toggle_comments = &ToggleComments {
7992 advance_downwards: true,
7993 };
7994
7995 // Single cursor on one line -> advance
7996 // Cursor moves horizontally 3 characters as well on non-blank line
7997 cx.set_state(indoc!(
7998 "fn a() {
7999 ˇdog();
8000 cat();
8001 }"
8002 ));
8003 cx.update_editor(|editor, cx| {
8004 editor.toggle_comments(toggle_comments, cx);
8005 });
8006 cx.assert_editor_state(indoc!(
8007 "fn a() {
8008 // dog();
8009 catˇ();
8010 }"
8011 ));
8012
8013 // Single selection on one line -> don't advance
8014 cx.set_state(indoc!(
8015 "fn a() {
8016 «dog()ˇ»;
8017 cat();
8018 }"
8019 ));
8020 cx.update_editor(|editor, cx| {
8021 editor.toggle_comments(toggle_comments, cx);
8022 });
8023 cx.assert_editor_state(indoc!(
8024 "fn a() {
8025 // «dog()ˇ»;
8026 cat();
8027 }"
8028 ));
8029
8030 // Multiple cursors on one line -> advance
8031 cx.set_state(indoc!(
8032 "fn a() {
8033 ˇdˇog();
8034 cat();
8035 }"
8036 ));
8037 cx.update_editor(|editor, cx| {
8038 editor.toggle_comments(toggle_comments, cx);
8039 });
8040 cx.assert_editor_state(indoc!(
8041 "fn a() {
8042 // dog();
8043 catˇ(ˇ);
8044 }"
8045 ));
8046
8047 // Multiple cursors on one line, with selection -> don't advance
8048 cx.set_state(indoc!(
8049 "fn a() {
8050 ˇdˇog«()ˇ»;
8051 cat();
8052 }"
8053 ));
8054 cx.update_editor(|editor, cx| {
8055 editor.toggle_comments(toggle_comments, cx);
8056 });
8057 cx.assert_editor_state(indoc!(
8058 "fn a() {
8059 // ˇdˇog«()ˇ»;
8060 cat();
8061 }"
8062 ));
8063
8064 // Single cursor on one line -> advance
8065 // Cursor moves to column 0 on blank line
8066 cx.set_state(indoc!(
8067 "fn a() {
8068 ˇdog();
8069
8070 cat();
8071 }"
8072 ));
8073 cx.update_editor(|editor, cx| {
8074 editor.toggle_comments(toggle_comments, cx);
8075 });
8076 cx.assert_editor_state(indoc!(
8077 "fn a() {
8078 // dog();
8079 ˇ
8080 cat();
8081 }"
8082 ));
8083
8084 // Single cursor on one line -> advance
8085 // Cursor starts and ends at column 0
8086 cx.set_state(indoc!(
8087 "fn a() {
8088 ˇ dog();
8089 cat();
8090 }"
8091 ));
8092 cx.update_editor(|editor, cx| {
8093 editor.toggle_comments(toggle_comments, cx);
8094 });
8095 cx.assert_editor_state(indoc!(
8096 "fn a() {
8097 // dog();
8098 ˇ cat();
8099 }"
8100 ));
8101}
8102
8103#[gpui::test]
8104async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
8105 init_test(cx, |_| {});
8106
8107 let mut cx = EditorTestContext::new(cx).await;
8108
8109 let html_language = Arc::new(
8110 Language::new(
8111 LanguageConfig {
8112 name: "HTML".into(),
8113 block_comment: Some(("<!-- ".into(), " -->".into())),
8114 ..Default::default()
8115 },
8116 Some(tree_sitter_html::language()),
8117 )
8118 .with_injection_query(
8119 r#"
8120 (script_element
8121 (raw_text) @content
8122 (#set! "language" "javascript"))
8123 "#,
8124 )
8125 .unwrap(),
8126 );
8127
8128 let javascript_language = Arc::new(Language::new(
8129 LanguageConfig {
8130 name: "JavaScript".into(),
8131 line_comments: vec!["// ".into()],
8132 ..Default::default()
8133 },
8134 Some(tree_sitter_typescript::language_tsx()),
8135 ));
8136
8137 cx.language_registry().add(html_language.clone());
8138 cx.language_registry().add(javascript_language.clone());
8139 cx.update_buffer(|buffer, cx| {
8140 buffer.set_language(Some(html_language), cx);
8141 });
8142
8143 // Toggle comments for empty selections
8144 cx.set_state(
8145 &r#"
8146 <p>A</p>ˇ
8147 <p>B</p>ˇ
8148 <p>C</p>ˇ
8149 "#
8150 .unindent(),
8151 );
8152 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8153 cx.assert_editor_state(
8154 &r#"
8155 <!-- <p>A</p>ˇ -->
8156 <!-- <p>B</p>ˇ -->
8157 <!-- <p>C</p>ˇ -->
8158 "#
8159 .unindent(),
8160 );
8161 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8162 cx.assert_editor_state(
8163 &r#"
8164 <p>A</p>ˇ
8165 <p>B</p>ˇ
8166 <p>C</p>ˇ
8167 "#
8168 .unindent(),
8169 );
8170
8171 // Toggle comments for mixture of empty and non-empty selections, where
8172 // multiple selections occupy a given line.
8173 cx.set_state(
8174 &r#"
8175 <p>A«</p>
8176 <p>ˇ»B</p>ˇ
8177 <p>C«</p>
8178 <p>ˇ»D</p>ˇ
8179 "#
8180 .unindent(),
8181 );
8182
8183 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8184 cx.assert_editor_state(
8185 &r#"
8186 <!-- <p>A«</p>
8187 <p>ˇ»B</p>ˇ -->
8188 <!-- <p>C«</p>
8189 <p>ˇ»D</p>ˇ -->
8190 "#
8191 .unindent(),
8192 );
8193 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8194 cx.assert_editor_state(
8195 &r#"
8196 <p>A«</p>
8197 <p>ˇ»B</p>ˇ
8198 <p>C«</p>
8199 <p>ˇ»D</p>ˇ
8200 "#
8201 .unindent(),
8202 );
8203
8204 // Toggle comments when different languages are active for different
8205 // selections.
8206 cx.set_state(
8207 &r#"
8208 ˇ<script>
8209 ˇvar x = new Y();
8210 ˇ</script>
8211 "#
8212 .unindent(),
8213 );
8214 cx.executor().run_until_parked();
8215 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8216 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
8217 // Uncommenting and commenting from this position brings in even more wrong artifacts.
8218 cx.assert_editor_state(
8219 &r#"
8220 <!-- ˇ<script> -->
8221 // ˇvar x = new Y();
8222 // ˇ</script>
8223 "#
8224 .unindent(),
8225 );
8226}
8227
8228#[gpui::test]
8229fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
8230 init_test(cx, |_| {});
8231
8232 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8233 let multibuffer = cx.new_model(|cx| {
8234 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8235 multibuffer.push_excerpts(
8236 buffer.clone(),
8237 [
8238 ExcerptRange {
8239 context: Point::new(0, 0)..Point::new(0, 4),
8240 primary: None,
8241 },
8242 ExcerptRange {
8243 context: Point::new(1, 0)..Point::new(1, 4),
8244 primary: None,
8245 },
8246 ],
8247 cx,
8248 );
8249 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
8250 multibuffer
8251 });
8252
8253 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8254 _ = view.update(cx, |view, cx| {
8255 assert_eq!(view.text(cx), "aaaa\nbbbb");
8256 view.change_selections(None, cx, |s| {
8257 s.select_ranges([
8258 Point::new(0, 0)..Point::new(0, 0),
8259 Point::new(1, 0)..Point::new(1, 0),
8260 ])
8261 });
8262
8263 view.handle_input("X", cx);
8264 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
8265 assert_eq!(
8266 view.selections.ranges(cx),
8267 [
8268 Point::new(0, 1)..Point::new(0, 1),
8269 Point::new(1, 1)..Point::new(1, 1),
8270 ]
8271 );
8272
8273 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
8274 view.change_selections(None, cx, |s| {
8275 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
8276 });
8277 view.backspace(&Default::default(), cx);
8278 assert_eq!(view.text(cx), "Xa\nbbb");
8279 assert_eq!(
8280 view.selections.ranges(cx),
8281 [Point::new(1, 0)..Point::new(1, 0)]
8282 );
8283
8284 view.change_selections(None, cx, |s| {
8285 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
8286 });
8287 view.backspace(&Default::default(), cx);
8288 assert_eq!(view.text(cx), "X\nbb");
8289 assert_eq!(
8290 view.selections.ranges(cx),
8291 [Point::new(0, 1)..Point::new(0, 1)]
8292 );
8293 });
8294}
8295
8296#[gpui::test]
8297fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
8298 init_test(cx, |_| {});
8299
8300 let markers = vec![('[', ']').into(), ('(', ')').into()];
8301 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
8302 indoc! {"
8303 [aaaa
8304 (bbbb]
8305 cccc)",
8306 },
8307 markers.clone(),
8308 );
8309 let excerpt_ranges = markers.into_iter().map(|marker| {
8310 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
8311 ExcerptRange {
8312 context,
8313 primary: None,
8314 }
8315 });
8316 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
8317 let multibuffer = cx.new_model(|cx| {
8318 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8319 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
8320 multibuffer
8321 });
8322
8323 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8324 _ = view.update(cx, |view, cx| {
8325 let (expected_text, selection_ranges) = marked_text_ranges(
8326 indoc! {"
8327 aaaa
8328 bˇbbb
8329 bˇbbˇb
8330 cccc"
8331 },
8332 true,
8333 );
8334 assert_eq!(view.text(cx), expected_text);
8335 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
8336
8337 view.handle_input("X", cx);
8338
8339 let (expected_text, expected_selections) = marked_text_ranges(
8340 indoc! {"
8341 aaaa
8342 bXˇbbXb
8343 bXˇbbXˇb
8344 cccc"
8345 },
8346 false,
8347 );
8348 assert_eq!(view.text(cx), expected_text);
8349 assert_eq!(view.selections.ranges(cx), expected_selections);
8350
8351 view.newline(&Newline, cx);
8352 let (expected_text, expected_selections) = marked_text_ranges(
8353 indoc! {"
8354 aaaa
8355 bX
8356 ˇbbX
8357 b
8358 bX
8359 ˇbbX
8360 ˇb
8361 cccc"
8362 },
8363 false,
8364 );
8365 assert_eq!(view.text(cx), expected_text);
8366 assert_eq!(view.selections.ranges(cx), expected_selections);
8367 });
8368}
8369
8370#[gpui::test]
8371fn test_refresh_selections(cx: &mut TestAppContext) {
8372 init_test(cx, |_| {});
8373
8374 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8375 let mut excerpt1_id = None;
8376 let multibuffer = cx.new_model(|cx| {
8377 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8378 excerpt1_id = multibuffer
8379 .push_excerpts(
8380 buffer.clone(),
8381 [
8382 ExcerptRange {
8383 context: Point::new(0, 0)..Point::new(1, 4),
8384 primary: None,
8385 },
8386 ExcerptRange {
8387 context: Point::new(1, 0)..Point::new(2, 4),
8388 primary: None,
8389 },
8390 ],
8391 cx,
8392 )
8393 .into_iter()
8394 .next();
8395 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8396 multibuffer
8397 });
8398
8399 let editor = cx.add_window(|cx| {
8400 let mut editor = build_editor(multibuffer.clone(), cx);
8401 let snapshot = editor.snapshot(cx);
8402 editor.change_selections(None, cx, |s| {
8403 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
8404 });
8405 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
8406 assert_eq!(
8407 editor.selections.ranges(cx),
8408 [
8409 Point::new(1, 3)..Point::new(1, 3),
8410 Point::new(2, 1)..Point::new(2, 1),
8411 ]
8412 );
8413 editor
8414 });
8415
8416 // Refreshing selections is a no-op when excerpts haven't changed.
8417 _ = editor.update(cx, |editor, cx| {
8418 editor.change_selections(None, cx, |s| s.refresh());
8419 assert_eq!(
8420 editor.selections.ranges(cx),
8421 [
8422 Point::new(1, 3)..Point::new(1, 3),
8423 Point::new(2, 1)..Point::new(2, 1),
8424 ]
8425 );
8426 });
8427
8428 _ = multibuffer.update(cx, |multibuffer, cx| {
8429 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8430 });
8431 _ = editor.update(cx, |editor, cx| {
8432 // Removing an excerpt causes the first selection to become degenerate.
8433 assert_eq!(
8434 editor.selections.ranges(cx),
8435 [
8436 Point::new(0, 0)..Point::new(0, 0),
8437 Point::new(0, 1)..Point::new(0, 1)
8438 ]
8439 );
8440
8441 // Refreshing selections will relocate the first selection to the original buffer
8442 // location.
8443 editor.change_selections(None, cx, |s| s.refresh());
8444 assert_eq!(
8445 editor.selections.ranges(cx),
8446 [
8447 Point::new(0, 1)..Point::new(0, 1),
8448 Point::new(0, 3)..Point::new(0, 3)
8449 ]
8450 );
8451 assert!(editor.selections.pending_anchor().is_some());
8452 });
8453}
8454
8455#[gpui::test]
8456fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
8457 init_test(cx, |_| {});
8458
8459 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8460 let mut excerpt1_id = None;
8461 let multibuffer = cx.new_model(|cx| {
8462 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8463 excerpt1_id = multibuffer
8464 .push_excerpts(
8465 buffer.clone(),
8466 [
8467 ExcerptRange {
8468 context: Point::new(0, 0)..Point::new(1, 4),
8469 primary: None,
8470 },
8471 ExcerptRange {
8472 context: Point::new(1, 0)..Point::new(2, 4),
8473 primary: None,
8474 },
8475 ],
8476 cx,
8477 )
8478 .into_iter()
8479 .next();
8480 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8481 multibuffer
8482 });
8483
8484 let editor = cx.add_window(|cx| {
8485 let mut editor = build_editor(multibuffer.clone(), cx);
8486 let snapshot = editor.snapshot(cx);
8487 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
8488 assert_eq!(
8489 editor.selections.ranges(cx),
8490 [Point::new(1, 3)..Point::new(1, 3)]
8491 );
8492 editor
8493 });
8494
8495 _ = multibuffer.update(cx, |multibuffer, cx| {
8496 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8497 });
8498 _ = editor.update(cx, |editor, cx| {
8499 assert_eq!(
8500 editor.selections.ranges(cx),
8501 [Point::new(0, 0)..Point::new(0, 0)]
8502 );
8503
8504 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
8505 editor.change_selections(None, cx, |s| s.refresh());
8506 assert_eq!(
8507 editor.selections.ranges(cx),
8508 [Point::new(0, 3)..Point::new(0, 3)]
8509 );
8510 assert!(editor.selections.pending_anchor().is_some());
8511 });
8512}
8513
8514#[gpui::test]
8515async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
8516 init_test(cx, |_| {});
8517
8518 let language = Arc::new(
8519 Language::new(
8520 LanguageConfig {
8521 brackets: BracketPairConfig {
8522 pairs: vec![
8523 BracketPair {
8524 start: "{".to_string(),
8525 end: "}".to_string(),
8526 close: true,
8527 surround: true,
8528 newline: true,
8529 },
8530 BracketPair {
8531 start: "/* ".to_string(),
8532 end: " */".to_string(),
8533 close: true,
8534 surround: true,
8535 newline: true,
8536 },
8537 ],
8538 ..Default::default()
8539 },
8540 ..Default::default()
8541 },
8542 Some(tree_sitter_rust::language()),
8543 )
8544 .with_indents_query("")
8545 .unwrap(),
8546 );
8547
8548 let text = concat!(
8549 "{ }\n", //
8550 " x\n", //
8551 " /* */\n", //
8552 "x\n", //
8553 "{{} }\n", //
8554 );
8555
8556 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
8557 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
8558 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
8559 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
8560 .await;
8561
8562 _ = view.update(cx, |view, cx| {
8563 view.change_selections(None, cx, |s| {
8564 s.select_display_ranges([
8565 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
8566 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
8567 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
8568 ])
8569 });
8570 view.newline(&Newline, cx);
8571
8572 assert_eq!(
8573 view.buffer().read(cx).read(cx).text(),
8574 concat!(
8575 "{ \n", // Suppress rustfmt
8576 "\n", //
8577 "}\n", //
8578 " x\n", //
8579 " /* \n", //
8580 " \n", //
8581 " */\n", //
8582 "x\n", //
8583 "{{} \n", //
8584 "}\n", //
8585 )
8586 );
8587 });
8588}
8589
8590#[gpui::test]
8591fn test_highlighted_ranges(cx: &mut TestAppContext) {
8592 init_test(cx, |_| {});
8593
8594 let editor = cx.add_window(|cx| {
8595 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
8596 build_editor(buffer.clone(), cx)
8597 });
8598
8599 _ = editor.update(cx, |editor, cx| {
8600 struct Type1;
8601 struct Type2;
8602
8603 let buffer = editor.buffer.read(cx).snapshot(cx);
8604
8605 let anchor_range =
8606 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
8607
8608 editor.highlight_background::<Type1>(
8609 &[
8610 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
8611 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
8612 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
8613 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
8614 ],
8615 |_| Hsla::red(),
8616 cx,
8617 );
8618 editor.highlight_background::<Type2>(
8619 &[
8620 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
8621 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
8622 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
8623 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
8624 ],
8625 |_| Hsla::green(),
8626 cx,
8627 );
8628
8629 let snapshot = editor.snapshot(cx);
8630 let mut highlighted_ranges = editor.background_highlights_in_range(
8631 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
8632 &snapshot,
8633 cx.theme().colors(),
8634 );
8635 // Enforce a consistent ordering based on color without relying on the ordering of the
8636 // highlight's `TypeId` which is non-executor.
8637 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
8638 assert_eq!(
8639 highlighted_ranges,
8640 &[
8641 (
8642 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
8643 Hsla::red(),
8644 ),
8645 (
8646 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
8647 Hsla::red(),
8648 ),
8649 (
8650 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
8651 Hsla::green(),
8652 ),
8653 (
8654 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
8655 Hsla::green(),
8656 ),
8657 ]
8658 );
8659 assert_eq!(
8660 editor.background_highlights_in_range(
8661 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
8662 &snapshot,
8663 cx.theme().colors(),
8664 ),
8665 &[(
8666 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
8667 Hsla::red(),
8668 )]
8669 );
8670 });
8671}
8672
8673#[gpui::test]
8674async fn test_following(cx: &mut gpui::TestAppContext) {
8675 init_test(cx, |_| {});
8676
8677 let fs = FakeFs::new(cx.executor());
8678 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8679
8680 let buffer = project.update(cx, |project, cx| {
8681 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
8682 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
8683 });
8684 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
8685 let follower = cx.update(|cx| {
8686 cx.open_window(
8687 WindowOptions {
8688 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
8689 gpui::Point::new(px(0.), px(0.)),
8690 gpui::Point::new(px(10.), px(80.)),
8691 ))),
8692 ..Default::default()
8693 },
8694 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
8695 )
8696 .unwrap()
8697 });
8698
8699 let is_still_following = Rc::new(RefCell::new(true));
8700 let follower_edit_event_count = Rc::new(RefCell::new(0));
8701 let pending_update = Rc::new(RefCell::new(None));
8702 _ = follower.update(cx, {
8703 let update = pending_update.clone();
8704 let is_still_following = is_still_following.clone();
8705 let follower_edit_event_count = follower_edit_event_count.clone();
8706 |_, cx| {
8707 cx.subscribe(
8708 &leader.root_view(cx).unwrap(),
8709 move |_, leader, event, cx| {
8710 leader
8711 .read(cx)
8712 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
8713 },
8714 )
8715 .detach();
8716
8717 cx.subscribe(
8718 &follower.root_view(cx).unwrap(),
8719 move |_, _, event: &EditorEvent, _cx| {
8720 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
8721 *is_still_following.borrow_mut() = false;
8722 }
8723
8724 if let EditorEvent::BufferEdited = event {
8725 *follower_edit_event_count.borrow_mut() += 1;
8726 }
8727 },
8728 )
8729 .detach();
8730 }
8731 });
8732
8733 // Update the selections only
8734 _ = leader.update(cx, |leader, cx| {
8735 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
8736 });
8737 follower
8738 .update(cx, |follower, cx| {
8739 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8740 })
8741 .unwrap()
8742 .await
8743 .unwrap();
8744 _ = follower.update(cx, |follower, cx| {
8745 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
8746 });
8747 assert_eq!(*is_still_following.borrow(), true);
8748 assert_eq!(*follower_edit_event_count.borrow(), 0);
8749
8750 // Update the scroll position only
8751 _ = leader.update(cx, |leader, cx| {
8752 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
8753 });
8754 follower
8755 .update(cx, |follower, cx| {
8756 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8757 })
8758 .unwrap()
8759 .await
8760 .unwrap();
8761 assert_eq!(
8762 follower
8763 .update(cx, |follower, cx| follower.scroll_position(cx))
8764 .unwrap(),
8765 gpui::Point::new(1.5, 3.5)
8766 );
8767 assert_eq!(*is_still_following.borrow(), true);
8768 assert_eq!(*follower_edit_event_count.borrow(), 0);
8769
8770 // Update the selections and scroll position. The follower's scroll position is updated
8771 // via autoscroll, not via the leader's exact scroll position.
8772 _ = leader.update(cx, |leader, cx| {
8773 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
8774 leader.request_autoscroll(Autoscroll::newest(), cx);
8775 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
8776 });
8777 follower
8778 .update(cx, |follower, cx| {
8779 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8780 })
8781 .unwrap()
8782 .await
8783 .unwrap();
8784 _ = follower.update(cx, |follower, cx| {
8785 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
8786 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
8787 });
8788 assert_eq!(*is_still_following.borrow(), true);
8789
8790 // Creating a pending selection that precedes another selection
8791 _ = leader.update(cx, |leader, cx| {
8792 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
8793 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
8794 });
8795 follower
8796 .update(cx, |follower, cx| {
8797 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8798 })
8799 .unwrap()
8800 .await
8801 .unwrap();
8802 _ = follower.update(cx, |follower, cx| {
8803 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
8804 });
8805 assert_eq!(*is_still_following.borrow(), true);
8806
8807 // Extend the pending selection so that it surrounds another selection
8808 _ = leader.update(cx, |leader, cx| {
8809 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
8810 });
8811 follower
8812 .update(cx, |follower, cx| {
8813 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8814 })
8815 .unwrap()
8816 .await
8817 .unwrap();
8818 _ = follower.update(cx, |follower, cx| {
8819 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
8820 });
8821
8822 // Scrolling locally breaks the follow
8823 _ = follower.update(cx, |follower, cx| {
8824 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
8825 follower.set_scroll_anchor(
8826 ScrollAnchor {
8827 anchor: top_anchor,
8828 offset: gpui::Point::new(0.0, 0.5),
8829 },
8830 cx,
8831 );
8832 });
8833 assert_eq!(*is_still_following.borrow(), false);
8834}
8835
8836#[gpui::test]
8837async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
8838 init_test(cx, |_| {});
8839
8840 let fs = FakeFs::new(cx.executor());
8841 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8842 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8843 let pane = workspace
8844 .update(cx, |workspace, _| workspace.active_pane().clone())
8845 .unwrap();
8846
8847 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
8848
8849 let leader = pane.update(cx, |_, cx| {
8850 let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
8851 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
8852 });
8853
8854 // Start following the editor when it has no excerpts.
8855 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
8856 let follower_1 = cx
8857 .update_window(*workspace.deref(), |_, cx| {
8858 Editor::from_state_proto(
8859 workspace.root_view(cx).unwrap(),
8860 ViewId {
8861 creator: Default::default(),
8862 id: 0,
8863 },
8864 &mut state_message,
8865 cx,
8866 )
8867 })
8868 .unwrap()
8869 .unwrap()
8870 .await
8871 .unwrap();
8872
8873 let update_message = Rc::new(RefCell::new(None));
8874 follower_1.update(cx, {
8875 let update = update_message.clone();
8876 |_, cx| {
8877 cx.subscribe(&leader, move |_, leader, event, cx| {
8878 leader
8879 .read(cx)
8880 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
8881 })
8882 .detach();
8883 }
8884 });
8885
8886 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
8887 (
8888 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
8889 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
8890 )
8891 });
8892
8893 // Insert some excerpts.
8894 _ = leader.update(cx, |leader, cx| {
8895 leader.buffer.update(cx, |multibuffer, cx| {
8896 let excerpt_ids = multibuffer.push_excerpts(
8897 buffer_1.clone(),
8898 [
8899 ExcerptRange {
8900 context: 1..6,
8901 primary: None,
8902 },
8903 ExcerptRange {
8904 context: 12..15,
8905 primary: None,
8906 },
8907 ExcerptRange {
8908 context: 0..3,
8909 primary: None,
8910 },
8911 ],
8912 cx,
8913 );
8914 multibuffer.insert_excerpts_after(
8915 excerpt_ids[0],
8916 buffer_2.clone(),
8917 [
8918 ExcerptRange {
8919 context: 8..12,
8920 primary: None,
8921 },
8922 ExcerptRange {
8923 context: 0..6,
8924 primary: None,
8925 },
8926 ],
8927 cx,
8928 );
8929 });
8930 });
8931
8932 // Apply the update of adding the excerpts.
8933 follower_1
8934 .update(cx, |follower, cx| {
8935 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
8936 })
8937 .await
8938 .unwrap();
8939 assert_eq!(
8940 follower_1.update(cx, |editor, cx| editor.text(cx)),
8941 leader.update(cx, |editor, cx| editor.text(cx))
8942 );
8943 update_message.borrow_mut().take();
8944
8945 // Start following separately after it already has excerpts.
8946 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
8947 let follower_2 = cx
8948 .update_window(*workspace.deref(), |_, cx| {
8949 Editor::from_state_proto(
8950 workspace.root_view(cx).unwrap().clone(),
8951 ViewId {
8952 creator: Default::default(),
8953 id: 0,
8954 },
8955 &mut state_message,
8956 cx,
8957 )
8958 })
8959 .unwrap()
8960 .unwrap()
8961 .await
8962 .unwrap();
8963 assert_eq!(
8964 follower_2.update(cx, |editor, cx| editor.text(cx)),
8965 leader.update(cx, |editor, cx| editor.text(cx))
8966 );
8967
8968 // Remove some excerpts.
8969 _ = leader.update(cx, |leader, cx| {
8970 leader.buffer.update(cx, |multibuffer, cx| {
8971 let excerpt_ids = multibuffer.excerpt_ids();
8972 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
8973 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
8974 });
8975 });
8976
8977 // Apply the update of removing the excerpts.
8978 follower_1
8979 .update(cx, |follower, cx| {
8980 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
8981 })
8982 .await
8983 .unwrap();
8984 follower_2
8985 .update(cx, |follower, cx| {
8986 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
8987 })
8988 .await
8989 .unwrap();
8990 update_message.borrow_mut().take();
8991 assert_eq!(
8992 follower_1.update(cx, |editor, cx| editor.text(cx)),
8993 leader.update(cx, |editor, cx| editor.text(cx))
8994 );
8995}
8996
8997#[gpui::test]
8998async fn go_to_prev_overlapping_diagnostic(
8999 executor: BackgroundExecutor,
9000 cx: &mut gpui::TestAppContext,
9001) {
9002 init_test(cx, |_| {});
9003
9004 let mut cx = EditorTestContext::new(cx).await;
9005 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9006
9007 cx.set_state(indoc! {"
9008 ˇfn func(abc def: i32) -> u32 {
9009 }
9010 "});
9011
9012 _ = cx.update(|cx| {
9013 _ = project.update(cx, |project, cx| {
9014 project
9015 .update_diagnostics(
9016 LanguageServerId(0),
9017 lsp::PublishDiagnosticsParams {
9018 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9019 version: None,
9020 diagnostics: vec![
9021 lsp::Diagnostic {
9022 range: lsp::Range::new(
9023 lsp::Position::new(0, 11),
9024 lsp::Position::new(0, 12),
9025 ),
9026 severity: Some(lsp::DiagnosticSeverity::ERROR),
9027 ..Default::default()
9028 },
9029 lsp::Diagnostic {
9030 range: lsp::Range::new(
9031 lsp::Position::new(0, 12),
9032 lsp::Position::new(0, 15),
9033 ),
9034 severity: Some(lsp::DiagnosticSeverity::ERROR),
9035 ..Default::default()
9036 },
9037 lsp::Diagnostic {
9038 range: lsp::Range::new(
9039 lsp::Position::new(0, 25),
9040 lsp::Position::new(0, 28),
9041 ),
9042 severity: Some(lsp::DiagnosticSeverity::ERROR),
9043 ..Default::default()
9044 },
9045 ],
9046 },
9047 &[],
9048 cx,
9049 )
9050 .unwrap()
9051 });
9052 });
9053
9054 executor.run_until_parked();
9055
9056 cx.update_editor(|editor, cx| {
9057 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9058 });
9059
9060 cx.assert_editor_state(indoc! {"
9061 fn func(abc def: i32) -> ˇu32 {
9062 }
9063 "});
9064
9065 cx.update_editor(|editor, cx| {
9066 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9067 });
9068
9069 cx.assert_editor_state(indoc! {"
9070 fn func(abc ˇdef: i32) -> u32 {
9071 }
9072 "});
9073
9074 cx.update_editor(|editor, cx| {
9075 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9076 });
9077
9078 cx.assert_editor_state(indoc! {"
9079 fn func(abcˇ def: i32) -> u32 {
9080 }
9081 "});
9082
9083 cx.update_editor(|editor, cx| {
9084 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9085 });
9086
9087 cx.assert_editor_state(indoc! {"
9088 fn func(abc def: i32) -> ˇu32 {
9089 }
9090 "});
9091}
9092
9093#[gpui::test]
9094async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9095 init_test(cx, |_| {});
9096
9097 let mut cx = EditorTestContext::new(cx).await;
9098
9099 let diff_base = r#"
9100 use some::mod;
9101
9102 const A: u32 = 42;
9103
9104 fn main() {
9105 println!("hello");
9106
9107 println!("world");
9108 }
9109 "#
9110 .unindent();
9111
9112 // Edits are modified, removed, modified, added
9113 cx.set_state(
9114 &r#"
9115 use some::modified;
9116
9117 ˇ
9118 fn main() {
9119 println!("hello there");
9120
9121 println!("around the");
9122 println!("world");
9123 }
9124 "#
9125 .unindent(),
9126 );
9127
9128 cx.set_diff_base(Some(&diff_base));
9129 executor.run_until_parked();
9130
9131 cx.update_editor(|editor, cx| {
9132 //Wrap around the bottom of the buffer
9133 for _ in 0..3 {
9134 editor.go_to_hunk(&GoToHunk, cx);
9135 }
9136 });
9137
9138 cx.assert_editor_state(
9139 &r#"
9140 ˇuse some::modified;
9141
9142
9143 fn main() {
9144 println!("hello there");
9145
9146 println!("around the");
9147 println!("world");
9148 }
9149 "#
9150 .unindent(),
9151 );
9152
9153 cx.update_editor(|editor, cx| {
9154 //Wrap around the top of the buffer
9155 for _ in 0..2 {
9156 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9157 }
9158 });
9159
9160 cx.assert_editor_state(
9161 &r#"
9162 use some::modified;
9163
9164
9165 fn main() {
9166 ˇ println!("hello there");
9167
9168 println!("around the");
9169 println!("world");
9170 }
9171 "#
9172 .unindent(),
9173 );
9174
9175 cx.update_editor(|editor, cx| {
9176 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9177 });
9178
9179 cx.assert_editor_state(
9180 &r#"
9181 use some::modified;
9182
9183 ˇ
9184 fn main() {
9185 println!("hello there");
9186
9187 println!("around the");
9188 println!("world");
9189 }
9190 "#
9191 .unindent(),
9192 );
9193
9194 cx.update_editor(|editor, cx| {
9195 for _ in 0..3 {
9196 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9197 }
9198 });
9199
9200 cx.assert_editor_state(
9201 &r#"
9202 use some::modified;
9203
9204
9205 fn main() {
9206 ˇ println!("hello there");
9207
9208 println!("around the");
9209 println!("world");
9210 }
9211 "#
9212 .unindent(),
9213 );
9214
9215 cx.update_editor(|editor, cx| {
9216 editor.fold(&Fold, cx);
9217
9218 //Make sure that the fold only gets one hunk
9219 for _ in 0..4 {
9220 editor.go_to_hunk(&GoToHunk, cx);
9221 }
9222 });
9223
9224 cx.assert_editor_state(
9225 &r#"
9226 ˇuse some::modified;
9227
9228
9229 fn main() {
9230 println!("hello there");
9231
9232 println!("around the");
9233 println!("world");
9234 }
9235 "#
9236 .unindent(),
9237 );
9238}
9239
9240#[test]
9241fn test_split_words() {
9242 fn split(text: &str) -> Vec<&str> {
9243 split_words(text).collect()
9244 }
9245
9246 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
9247 assert_eq!(split("hello_world"), &["hello_", "world"]);
9248 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
9249 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
9250 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
9251 assert_eq!(split("helloworld"), &["helloworld"]);
9252
9253 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
9254}
9255
9256#[gpui::test]
9257async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
9258 init_test(cx, |_| {});
9259
9260 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
9261 let mut assert = |before, after| {
9262 let _state_context = cx.set_state(before);
9263 cx.update_editor(|editor, cx| {
9264 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
9265 });
9266 cx.assert_editor_state(after);
9267 };
9268
9269 // Outside bracket jumps to outside of matching bracket
9270 assert("console.logˇ(var);", "console.log(var)ˇ;");
9271 assert("console.log(var)ˇ;", "console.logˇ(var);");
9272
9273 // Inside bracket jumps to inside of matching bracket
9274 assert("console.log(ˇvar);", "console.log(varˇ);");
9275 assert("console.log(varˇ);", "console.log(ˇvar);");
9276
9277 // When outside a bracket and inside, favor jumping to the inside bracket
9278 assert(
9279 "console.log('foo', [1, 2, 3]ˇ);",
9280 "console.log(ˇ'foo', [1, 2, 3]);",
9281 );
9282 assert(
9283 "console.log(ˇ'foo', [1, 2, 3]);",
9284 "console.log('foo', [1, 2, 3]ˇ);",
9285 );
9286
9287 // Bias forward if two options are equally likely
9288 assert(
9289 "let result = curried_fun()ˇ();",
9290 "let result = curried_fun()()ˇ;",
9291 );
9292
9293 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
9294 assert(
9295 indoc! {"
9296 function test() {
9297 console.log('test')ˇ
9298 }"},
9299 indoc! {"
9300 function test() {
9301 console.logˇ('test')
9302 }"},
9303 );
9304}
9305
9306#[gpui::test]
9307async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
9308 init_test(cx, |_| {});
9309
9310 let fs = FakeFs::new(cx.executor());
9311 fs.insert_tree(
9312 "/a",
9313 json!({
9314 "main.rs": "fn main() { let a = 5; }",
9315 "other.rs": "// Test file",
9316 }),
9317 )
9318 .await;
9319 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9320
9321 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9322 language_registry.add(Arc::new(Language::new(
9323 LanguageConfig {
9324 name: "Rust".into(),
9325 matcher: LanguageMatcher {
9326 path_suffixes: vec!["rs".to_string()],
9327 ..Default::default()
9328 },
9329 brackets: BracketPairConfig {
9330 pairs: vec![BracketPair {
9331 start: "{".to_string(),
9332 end: "}".to_string(),
9333 close: true,
9334 surround: true,
9335 newline: true,
9336 }],
9337 disabled_scopes_by_bracket_ix: Vec::new(),
9338 },
9339 ..Default::default()
9340 },
9341 Some(tree_sitter_rust::language()),
9342 )));
9343 let mut fake_servers = language_registry.register_fake_lsp_adapter(
9344 "Rust",
9345 FakeLspAdapter {
9346 capabilities: lsp::ServerCapabilities {
9347 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
9348 first_trigger_character: "{".to_string(),
9349 more_trigger_character: None,
9350 }),
9351 ..Default::default()
9352 },
9353 ..Default::default()
9354 },
9355 );
9356
9357 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9358
9359 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9360
9361 let worktree_id = workspace
9362 .update(cx, |workspace, cx| {
9363 workspace.project().update(cx, |project, cx| {
9364 project.worktrees(cx).next().unwrap().read(cx).id()
9365 })
9366 })
9367 .unwrap();
9368
9369 let buffer = project
9370 .update(cx, |project, cx| {
9371 project.open_local_buffer("/a/main.rs", cx)
9372 })
9373 .await
9374 .unwrap();
9375 cx.executor().run_until_parked();
9376 cx.executor().start_waiting();
9377 let fake_server = fake_servers.next().await.unwrap();
9378 let editor_handle = workspace
9379 .update(cx, |workspace, cx| {
9380 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
9381 })
9382 .unwrap()
9383 .await
9384 .unwrap()
9385 .downcast::<Editor>()
9386 .unwrap();
9387
9388 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
9389 assert_eq!(
9390 params.text_document_position.text_document.uri,
9391 lsp::Url::from_file_path("/a/main.rs").unwrap(),
9392 );
9393 assert_eq!(
9394 params.text_document_position.position,
9395 lsp::Position::new(0, 21),
9396 );
9397
9398 Ok(Some(vec![lsp::TextEdit {
9399 new_text: "]".to_string(),
9400 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
9401 }]))
9402 });
9403
9404 editor_handle.update(cx, |editor, cx| {
9405 editor.focus(cx);
9406 editor.change_selections(None, cx, |s| {
9407 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
9408 });
9409 editor.handle_input("{", cx);
9410 });
9411
9412 cx.executor().run_until_parked();
9413
9414 _ = buffer.update(cx, |buffer, _| {
9415 assert_eq!(
9416 buffer.text(),
9417 "fn main() { let a = {5}; }",
9418 "No extra braces from on type formatting should appear in the buffer"
9419 )
9420 });
9421}
9422
9423#[gpui::test]
9424async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
9425 init_test(cx, |_| {});
9426
9427 let fs = FakeFs::new(cx.executor());
9428 fs.insert_tree(
9429 "/a",
9430 json!({
9431 "main.rs": "fn main() { let a = 5; }",
9432 "other.rs": "// Test file",
9433 }),
9434 )
9435 .await;
9436
9437 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9438
9439 let server_restarts = Arc::new(AtomicUsize::new(0));
9440 let closure_restarts = Arc::clone(&server_restarts);
9441 let language_server_name = "test language server";
9442 let language_name: Arc<str> = "Rust".into();
9443
9444 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9445 language_registry.add(Arc::new(Language::new(
9446 LanguageConfig {
9447 name: Arc::clone(&language_name),
9448 matcher: LanguageMatcher {
9449 path_suffixes: vec!["rs".to_string()],
9450 ..Default::default()
9451 },
9452 ..Default::default()
9453 },
9454 Some(tree_sitter_rust::language()),
9455 )));
9456 let mut fake_servers = language_registry.register_fake_lsp_adapter(
9457 "Rust",
9458 FakeLspAdapter {
9459 name: language_server_name,
9460 initialization_options: Some(json!({
9461 "testOptionValue": true
9462 })),
9463 initializer: Some(Box::new(move |fake_server| {
9464 let task_restarts = Arc::clone(&closure_restarts);
9465 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
9466 task_restarts.fetch_add(1, atomic::Ordering::Release);
9467 futures::future::ready(Ok(()))
9468 });
9469 })),
9470 ..Default::default()
9471 },
9472 );
9473
9474 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9475 let _buffer = project
9476 .update(cx, |project, cx| {
9477 project.open_local_buffer("/a/main.rs", cx)
9478 })
9479 .await
9480 .unwrap();
9481 let _fake_server = fake_servers.next().await.unwrap();
9482 update_test_language_settings(cx, |language_settings| {
9483 language_settings.languages.insert(
9484 Arc::clone(&language_name),
9485 LanguageSettingsContent {
9486 tab_size: NonZeroU32::new(8),
9487 ..Default::default()
9488 },
9489 );
9490 });
9491 cx.executor().run_until_parked();
9492 assert_eq!(
9493 server_restarts.load(atomic::Ordering::Acquire),
9494 0,
9495 "Should not restart LSP server on an unrelated change"
9496 );
9497
9498 update_test_project_settings(cx, |project_settings| {
9499 project_settings.lsp.insert(
9500 "Some other server name".into(),
9501 LspSettings {
9502 binary: None,
9503 settings: None,
9504 initialization_options: Some(json!({
9505 "some other init value": false
9506 })),
9507 },
9508 );
9509 });
9510 cx.executor().run_until_parked();
9511 assert_eq!(
9512 server_restarts.load(atomic::Ordering::Acquire),
9513 0,
9514 "Should not restart LSP server on an unrelated LSP settings change"
9515 );
9516
9517 update_test_project_settings(cx, |project_settings| {
9518 project_settings.lsp.insert(
9519 language_server_name.into(),
9520 LspSettings {
9521 binary: None,
9522 settings: None,
9523 initialization_options: Some(json!({
9524 "anotherInitValue": false
9525 })),
9526 },
9527 );
9528 });
9529 cx.executor().run_until_parked();
9530 assert_eq!(
9531 server_restarts.load(atomic::Ordering::Acquire),
9532 1,
9533 "Should restart LSP server on a related LSP settings change"
9534 );
9535
9536 update_test_project_settings(cx, |project_settings| {
9537 project_settings.lsp.insert(
9538 language_server_name.into(),
9539 LspSettings {
9540 binary: None,
9541 settings: None,
9542 initialization_options: Some(json!({
9543 "anotherInitValue": false
9544 })),
9545 },
9546 );
9547 });
9548 cx.executor().run_until_parked();
9549 assert_eq!(
9550 server_restarts.load(atomic::Ordering::Acquire),
9551 1,
9552 "Should not restart LSP server on a related LSP settings change that is the same"
9553 );
9554
9555 update_test_project_settings(cx, |project_settings| {
9556 project_settings.lsp.insert(
9557 language_server_name.into(),
9558 LspSettings {
9559 binary: None,
9560 settings: None,
9561 initialization_options: None,
9562 },
9563 );
9564 });
9565 cx.executor().run_until_parked();
9566 assert_eq!(
9567 server_restarts.load(atomic::Ordering::Acquire),
9568 2,
9569 "Should restart LSP server on another related LSP settings change"
9570 );
9571}
9572
9573#[gpui::test]
9574async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
9575 init_test(cx, |_| {});
9576
9577 let mut cx = EditorLspTestContext::new_rust(
9578 lsp::ServerCapabilities {
9579 completion_provider: Some(lsp::CompletionOptions {
9580 trigger_characters: Some(vec![".".to_string()]),
9581 resolve_provider: Some(true),
9582 ..Default::default()
9583 }),
9584 ..Default::default()
9585 },
9586 cx,
9587 )
9588 .await;
9589
9590 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9591 cx.simulate_keystroke(".");
9592 let completion_item = lsp::CompletionItem {
9593 label: "some".into(),
9594 kind: Some(lsp::CompletionItemKind::SNIPPET),
9595 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9596 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9597 kind: lsp::MarkupKind::Markdown,
9598 value: "```rust\nSome(2)\n```".to_string(),
9599 })),
9600 deprecated: Some(false),
9601 sort_text: Some("fffffff2".to_string()),
9602 filter_text: Some("some".to_string()),
9603 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9604 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9605 range: lsp::Range {
9606 start: lsp::Position {
9607 line: 0,
9608 character: 22,
9609 },
9610 end: lsp::Position {
9611 line: 0,
9612 character: 22,
9613 },
9614 },
9615 new_text: "Some(2)".to_string(),
9616 })),
9617 additional_text_edits: Some(vec![lsp::TextEdit {
9618 range: lsp::Range {
9619 start: lsp::Position {
9620 line: 0,
9621 character: 20,
9622 },
9623 end: lsp::Position {
9624 line: 0,
9625 character: 22,
9626 },
9627 },
9628 new_text: "".to_string(),
9629 }]),
9630 ..Default::default()
9631 };
9632
9633 let closure_completion_item = completion_item.clone();
9634 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9635 let task_completion_item = closure_completion_item.clone();
9636 async move {
9637 Ok(Some(lsp::CompletionResponse::Array(vec![
9638 task_completion_item,
9639 ])))
9640 }
9641 });
9642
9643 request.next().await;
9644
9645 cx.condition(|editor, _| editor.context_menu_visible())
9646 .await;
9647 let apply_additional_edits = cx.update_editor(|editor, cx| {
9648 editor
9649 .confirm_completion(&ConfirmCompletion::default(), cx)
9650 .unwrap()
9651 });
9652 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
9653
9654 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
9655 let task_completion_item = completion_item.clone();
9656 async move { Ok(task_completion_item) }
9657 })
9658 .next()
9659 .await
9660 .unwrap();
9661 apply_additional_edits.await.unwrap();
9662 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
9663}
9664
9665#[gpui::test]
9666async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
9667 init_test(cx, |_| {});
9668
9669 let mut cx = EditorLspTestContext::new(
9670 Language::new(
9671 LanguageConfig {
9672 matcher: LanguageMatcher {
9673 path_suffixes: vec!["jsx".into()],
9674 ..Default::default()
9675 },
9676 overrides: [(
9677 "element".into(),
9678 LanguageConfigOverride {
9679 word_characters: Override::Set(['-'].into_iter().collect()),
9680 ..Default::default()
9681 },
9682 )]
9683 .into_iter()
9684 .collect(),
9685 ..Default::default()
9686 },
9687 Some(tree_sitter_typescript::language_tsx()),
9688 )
9689 .with_override_query("(jsx_self_closing_element) @element")
9690 .unwrap(),
9691 lsp::ServerCapabilities {
9692 completion_provider: Some(lsp::CompletionOptions {
9693 trigger_characters: Some(vec![":".to_string()]),
9694 ..Default::default()
9695 }),
9696 ..Default::default()
9697 },
9698 cx,
9699 )
9700 .await;
9701
9702 cx.lsp
9703 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9704 Ok(Some(lsp::CompletionResponse::Array(vec![
9705 lsp::CompletionItem {
9706 label: "bg-blue".into(),
9707 ..Default::default()
9708 },
9709 lsp::CompletionItem {
9710 label: "bg-red".into(),
9711 ..Default::default()
9712 },
9713 lsp::CompletionItem {
9714 label: "bg-yellow".into(),
9715 ..Default::default()
9716 },
9717 ])))
9718 });
9719
9720 cx.set_state(r#"<p class="bgˇ" />"#);
9721
9722 // Trigger completion when typing a dash, because the dash is an extra
9723 // word character in the 'element' scope, which contains the cursor.
9724 cx.simulate_keystroke("-");
9725 cx.executor().run_until_parked();
9726 cx.update_editor(|editor, _| {
9727 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9728 assert_eq!(
9729 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9730 &["bg-red", "bg-blue", "bg-yellow"]
9731 );
9732 } else {
9733 panic!("expected completion menu to be open");
9734 }
9735 });
9736
9737 cx.simulate_keystroke("l");
9738 cx.executor().run_until_parked();
9739 cx.update_editor(|editor, _| {
9740 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9741 assert_eq!(
9742 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9743 &["bg-blue", "bg-yellow"]
9744 );
9745 } else {
9746 panic!("expected completion menu to be open");
9747 }
9748 });
9749
9750 // When filtering completions, consider the character after the '-' to
9751 // be the start of a subword.
9752 cx.set_state(r#"<p class="yelˇ" />"#);
9753 cx.simulate_keystroke("l");
9754 cx.executor().run_until_parked();
9755 cx.update_editor(|editor, _| {
9756 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9757 assert_eq!(
9758 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9759 &["bg-yellow"]
9760 );
9761 } else {
9762 panic!("expected completion menu to be open");
9763 }
9764 });
9765}
9766
9767#[gpui::test]
9768async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
9769 init_test(cx, |settings| {
9770 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
9771 FormatterList(vec![Formatter::Prettier].into()),
9772 ))
9773 });
9774
9775 let fs = FakeFs::new(cx.executor());
9776 fs.insert_file("/file.ts", Default::default()).await;
9777
9778 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
9779 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9780
9781 language_registry.add(Arc::new(Language::new(
9782 LanguageConfig {
9783 name: "TypeScript".into(),
9784 matcher: LanguageMatcher {
9785 path_suffixes: vec!["ts".to_string()],
9786 ..Default::default()
9787 },
9788 ..Default::default()
9789 },
9790 Some(tree_sitter_rust::language()),
9791 )));
9792 update_test_language_settings(cx, |settings| {
9793 settings.defaults.prettier = Some(PrettierSettings {
9794 allowed: true,
9795 ..PrettierSettings::default()
9796 });
9797 });
9798
9799 let test_plugin = "test_plugin";
9800 let _ = language_registry.register_fake_lsp_adapter(
9801 "TypeScript",
9802 FakeLspAdapter {
9803 prettier_plugins: vec![test_plugin],
9804 ..Default::default()
9805 },
9806 );
9807
9808 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
9809 let buffer = project
9810 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
9811 .await
9812 .unwrap();
9813
9814 let buffer_text = "one\ntwo\nthree\n";
9815 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9816 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9817 _ = editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
9818
9819 editor
9820 .update(cx, |editor, cx| {
9821 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
9822 })
9823 .unwrap()
9824 .await;
9825 assert_eq!(
9826 editor.update(cx, |editor, cx| editor.text(cx)),
9827 buffer_text.to_string() + prettier_format_suffix,
9828 "Test prettier formatting was not applied to the original buffer text",
9829 );
9830
9831 update_test_language_settings(cx, |settings| {
9832 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
9833 });
9834 let format = editor.update(cx, |editor, cx| {
9835 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
9836 });
9837 format.await.unwrap();
9838 assert_eq!(
9839 editor.update(cx, |editor, cx| editor.text(cx)),
9840 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
9841 "Autoformatting (via test prettier) was not applied to the original buffer text",
9842 );
9843}
9844
9845#[gpui::test]
9846async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
9847 init_test(cx, |_| {});
9848 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9849 let base_text = indoc! {r#"struct Row;
9850struct Row1;
9851struct Row2;
9852
9853struct Row4;
9854struct Row5;
9855struct Row6;
9856
9857struct Row8;
9858struct Row9;
9859struct Row10;"#};
9860
9861 // When addition hunks are not adjacent to carets, no hunk revert is performed
9862 assert_hunk_revert(
9863 indoc! {r#"struct Row;
9864 struct Row1;
9865 struct Row1.1;
9866 struct Row1.2;
9867 struct Row2;ˇ
9868
9869 struct Row4;
9870 struct Row5;
9871 struct Row6;
9872
9873 struct Row8;
9874 ˇstruct Row9;
9875 struct Row9.1;
9876 struct Row9.2;
9877 struct Row9.3;
9878 struct Row10;"#},
9879 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
9880 indoc! {r#"struct Row;
9881 struct Row1;
9882 struct Row1.1;
9883 struct Row1.2;
9884 struct Row2;ˇ
9885
9886 struct Row4;
9887 struct Row5;
9888 struct Row6;
9889
9890 struct Row8;
9891 ˇstruct Row9;
9892 struct Row9.1;
9893 struct Row9.2;
9894 struct Row9.3;
9895 struct Row10;"#},
9896 base_text,
9897 &mut cx,
9898 );
9899 // Same for selections
9900 assert_hunk_revert(
9901 indoc! {r#"struct Row;
9902 struct Row1;
9903 struct Row2;
9904 struct Row2.1;
9905 struct Row2.2;
9906 «ˇ
9907 struct Row4;
9908 struct» Row5;
9909 «struct Row6;
9910 ˇ»
9911 struct Row9.1;
9912 struct Row9.2;
9913 struct Row9.3;
9914 struct Row8;
9915 struct Row9;
9916 struct Row10;"#},
9917 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
9918 indoc! {r#"struct Row;
9919 struct Row1;
9920 struct Row2;
9921 struct Row2.1;
9922 struct Row2.2;
9923 «ˇ
9924 struct Row4;
9925 struct» Row5;
9926 «struct Row6;
9927 ˇ»
9928 struct Row9.1;
9929 struct Row9.2;
9930 struct Row9.3;
9931 struct Row8;
9932 struct Row9;
9933 struct Row10;"#},
9934 base_text,
9935 &mut cx,
9936 );
9937
9938 // When carets and selections intersect the addition hunks, those are reverted.
9939 // Adjacent carets got merged.
9940 assert_hunk_revert(
9941 indoc! {r#"struct Row;
9942 ˇ// something on the top
9943 struct Row1;
9944 struct Row2;
9945 struct Roˇw3.1;
9946 struct Row2.2;
9947 struct Row2.3;ˇ
9948
9949 struct Row4;
9950 struct ˇRow5.1;
9951 struct Row5.2;
9952 struct «Rowˇ»5.3;
9953 struct Row5;
9954 struct Row6;
9955 ˇ
9956 struct Row9.1;
9957 struct «Rowˇ»9.2;
9958 struct «ˇRow»9.3;
9959 struct Row8;
9960 struct Row9;
9961 «ˇ// something on bottom»
9962 struct Row10;"#},
9963 vec![
9964 DiffHunkStatus::Added,
9965 DiffHunkStatus::Added,
9966 DiffHunkStatus::Added,
9967 DiffHunkStatus::Added,
9968 DiffHunkStatus::Added,
9969 ],
9970 indoc! {r#"struct Row;
9971 ˇstruct Row1;
9972 struct Row2;
9973 ˇ
9974 struct Row4;
9975 ˇstruct Row5;
9976 struct Row6;
9977 ˇ
9978 ˇstruct Row8;
9979 struct Row9;
9980 ˇstruct Row10;"#},
9981 base_text,
9982 &mut cx,
9983 );
9984}
9985
9986#[gpui::test]
9987async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
9988 init_test(cx, |_| {});
9989 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9990 let base_text = indoc! {r#"struct Row;
9991struct Row1;
9992struct Row2;
9993
9994struct Row4;
9995struct Row5;
9996struct Row6;
9997
9998struct Row8;
9999struct Row9;
10000struct Row10;"#};
10001
10002 // Modification hunks behave the same as the addition ones.
10003 assert_hunk_revert(
10004 indoc! {r#"struct Row;
10005 struct Row1;
10006 struct Row33;
10007 ˇ
10008 struct Row4;
10009 struct Row5;
10010 struct Row6;
10011 ˇ
10012 struct Row99;
10013 struct Row9;
10014 struct Row10;"#},
10015 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10016 indoc! {r#"struct Row;
10017 struct Row1;
10018 struct Row33;
10019 ˇ
10020 struct Row4;
10021 struct Row5;
10022 struct Row6;
10023 ˇ
10024 struct Row99;
10025 struct Row9;
10026 struct Row10;"#},
10027 base_text,
10028 &mut cx,
10029 );
10030 assert_hunk_revert(
10031 indoc! {r#"struct Row;
10032 struct Row1;
10033 struct Row33;
10034 «ˇ
10035 struct Row4;
10036 struct» Row5;
10037 «struct Row6;
10038 ˇ»
10039 struct Row99;
10040 struct Row9;
10041 struct Row10;"#},
10042 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10043 indoc! {r#"struct Row;
10044 struct Row1;
10045 struct Row33;
10046 «ˇ
10047 struct Row4;
10048 struct» Row5;
10049 «struct Row6;
10050 ˇ»
10051 struct Row99;
10052 struct Row9;
10053 struct Row10;"#},
10054 base_text,
10055 &mut cx,
10056 );
10057
10058 assert_hunk_revert(
10059 indoc! {r#"ˇstruct Row1.1;
10060 struct Row1;
10061 «ˇstr»uct Row22;
10062
10063 struct ˇRow44;
10064 struct Row5;
10065 struct «Rˇ»ow66;ˇ
10066
10067 «struˇ»ct Row88;
10068 struct Row9;
10069 struct Row1011;ˇ"#},
10070 vec![
10071 DiffHunkStatus::Modified,
10072 DiffHunkStatus::Modified,
10073 DiffHunkStatus::Modified,
10074 DiffHunkStatus::Modified,
10075 DiffHunkStatus::Modified,
10076 DiffHunkStatus::Modified,
10077 ],
10078 indoc! {r#"struct Row;
10079 ˇstruct Row1;
10080 struct Row2;
10081 ˇ
10082 struct Row4;
10083 ˇstruct Row5;
10084 struct Row6;
10085 ˇ
10086 struct Row8;
10087 ˇstruct Row9;
10088 struct Row10;ˇ"#},
10089 base_text,
10090 &mut cx,
10091 );
10092}
10093
10094#[gpui::test]
10095async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
10096 init_test(cx, |_| {});
10097 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10098 let base_text = indoc! {r#"struct Row;
10099struct Row1;
10100struct Row2;
10101
10102struct Row4;
10103struct Row5;
10104struct Row6;
10105
10106struct Row8;
10107struct Row9;
10108struct Row10;"#};
10109
10110 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
10111 assert_hunk_revert(
10112 indoc! {r#"struct Row;
10113 struct Row2;
10114
10115 ˇstruct Row4;
10116 struct Row5;
10117 struct Row6;
10118 ˇ
10119 struct Row8;
10120 struct Row10;"#},
10121 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10122 indoc! {r#"struct Row;
10123 struct Row2;
10124
10125 ˇstruct Row4;
10126 struct Row5;
10127 struct Row6;
10128 ˇ
10129 struct Row8;
10130 struct Row10;"#},
10131 base_text,
10132 &mut cx,
10133 );
10134 assert_hunk_revert(
10135 indoc! {r#"struct Row;
10136 struct Row2;
10137
10138 «ˇstruct Row4;
10139 struct» Row5;
10140 «struct Row6;
10141 ˇ»
10142 struct Row8;
10143 struct Row10;"#},
10144 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10145 indoc! {r#"struct Row;
10146 struct Row2;
10147
10148 «ˇstruct Row4;
10149 struct» Row5;
10150 «struct Row6;
10151 ˇ»
10152 struct Row8;
10153 struct Row10;"#},
10154 base_text,
10155 &mut cx,
10156 );
10157
10158 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
10159 assert_hunk_revert(
10160 indoc! {r#"struct Row;
10161 ˇstruct Row2;
10162
10163 struct Row4;
10164 struct Row5;
10165 struct Row6;
10166
10167 struct Row8;ˇ
10168 struct Row10;"#},
10169 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10170 indoc! {r#"struct Row;
10171 struct Row1;
10172 ˇstruct Row2;
10173
10174 struct Row4;
10175 struct Row5;
10176 struct Row6;
10177
10178 struct Row8;ˇ
10179 struct Row9;
10180 struct Row10;"#},
10181 base_text,
10182 &mut cx,
10183 );
10184 assert_hunk_revert(
10185 indoc! {r#"struct Row;
10186 struct Row2«ˇ;
10187 struct Row4;
10188 struct» Row5;
10189 «struct Row6;
10190
10191 struct Row8;ˇ»
10192 struct Row10;"#},
10193 vec![
10194 DiffHunkStatus::Removed,
10195 DiffHunkStatus::Removed,
10196 DiffHunkStatus::Removed,
10197 ],
10198 indoc! {r#"struct Row;
10199 struct Row1;
10200 struct Row2«ˇ;
10201
10202 struct Row4;
10203 struct» Row5;
10204 «struct Row6;
10205
10206 struct Row8;ˇ»
10207 struct Row9;
10208 struct Row10;"#},
10209 base_text,
10210 &mut cx,
10211 );
10212}
10213
10214#[gpui::test]
10215async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
10216 init_test(cx, |_| {});
10217
10218 let cols = 4;
10219 let rows = 10;
10220 let sample_text_1 = sample_text(rows, cols, 'a');
10221 assert_eq!(
10222 sample_text_1,
10223 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10224 );
10225 let sample_text_2 = sample_text(rows, cols, 'l');
10226 assert_eq!(
10227 sample_text_2,
10228 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10229 );
10230 let sample_text_3 = sample_text(rows, cols, 'v');
10231 assert_eq!(
10232 sample_text_3,
10233 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10234 );
10235
10236 fn diff_every_buffer_row(
10237 buffer: &Model<Buffer>,
10238 sample_text: String,
10239 cols: usize,
10240 cx: &mut gpui::TestAppContext,
10241 ) {
10242 // revert first character in each row, creating one large diff hunk per buffer
10243 let is_first_char = |offset: usize| offset % cols == 0;
10244 buffer.update(cx, |buffer, cx| {
10245 buffer.set_text(
10246 sample_text
10247 .chars()
10248 .enumerate()
10249 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
10250 .collect::<String>(),
10251 cx,
10252 );
10253 buffer.set_diff_base(Some(sample_text), cx);
10254 });
10255 cx.executor().run_until_parked();
10256 }
10257
10258 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10259 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10260
10261 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10262 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10263
10264 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10265 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10266
10267 let multibuffer = cx.new_model(|cx| {
10268 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10269 multibuffer.push_excerpts(
10270 buffer_1.clone(),
10271 [
10272 ExcerptRange {
10273 context: Point::new(0, 0)..Point::new(3, 0),
10274 primary: None,
10275 },
10276 ExcerptRange {
10277 context: Point::new(5, 0)..Point::new(7, 0),
10278 primary: None,
10279 },
10280 ExcerptRange {
10281 context: Point::new(9, 0)..Point::new(10, 4),
10282 primary: None,
10283 },
10284 ],
10285 cx,
10286 );
10287 multibuffer.push_excerpts(
10288 buffer_2.clone(),
10289 [
10290 ExcerptRange {
10291 context: Point::new(0, 0)..Point::new(3, 0),
10292 primary: None,
10293 },
10294 ExcerptRange {
10295 context: Point::new(5, 0)..Point::new(7, 0),
10296 primary: None,
10297 },
10298 ExcerptRange {
10299 context: Point::new(9, 0)..Point::new(10, 4),
10300 primary: None,
10301 },
10302 ],
10303 cx,
10304 );
10305 multibuffer.push_excerpts(
10306 buffer_3.clone(),
10307 [
10308 ExcerptRange {
10309 context: Point::new(0, 0)..Point::new(3, 0),
10310 primary: None,
10311 },
10312 ExcerptRange {
10313 context: Point::new(5, 0)..Point::new(7, 0),
10314 primary: None,
10315 },
10316 ExcerptRange {
10317 context: Point::new(9, 0)..Point::new(10, 4),
10318 primary: None,
10319 },
10320 ],
10321 cx,
10322 );
10323 multibuffer
10324 });
10325
10326 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
10327 editor.update(cx, |editor, cx| {
10328 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");
10329 editor.select_all(&SelectAll, cx);
10330 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10331 });
10332 cx.executor().run_until_parked();
10333 // When all ranges are selected, all buffer hunks are reverted.
10334 editor.update(cx, |editor, cx| {
10335 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");
10336 });
10337 buffer_1.update(cx, |buffer, _| {
10338 assert_eq!(buffer.text(), sample_text_1);
10339 });
10340 buffer_2.update(cx, |buffer, _| {
10341 assert_eq!(buffer.text(), sample_text_2);
10342 });
10343 buffer_3.update(cx, |buffer, _| {
10344 assert_eq!(buffer.text(), sample_text_3);
10345 });
10346
10347 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10348 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10349 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10350 editor.update(cx, |editor, cx| {
10351 editor.change_selections(None, cx, |s| {
10352 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
10353 });
10354 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10355 });
10356 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
10357 // but not affect buffer_2 and its related excerpts.
10358 editor.update(cx, |editor, cx| {
10359 assert_eq!(
10360 editor.text(cx),
10361 "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"
10362 );
10363 });
10364 buffer_1.update(cx, |buffer, _| {
10365 assert_eq!(buffer.text(), sample_text_1);
10366 });
10367 buffer_2.update(cx, |buffer, _| {
10368 assert_eq!(
10369 buffer.text(),
10370 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
10371 );
10372 });
10373 buffer_3.update(cx, |buffer, _| {
10374 assert_eq!(
10375 buffer.text(),
10376 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
10377 );
10378 });
10379}
10380
10381#[gpui::test]
10382async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
10383 init_test(cx, |_| {});
10384
10385 let cols = 4;
10386 let rows = 10;
10387 let sample_text_1 = sample_text(rows, cols, 'a');
10388 assert_eq!(
10389 sample_text_1,
10390 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10391 );
10392 let sample_text_2 = sample_text(rows, cols, 'l');
10393 assert_eq!(
10394 sample_text_2,
10395 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10396 );
10397 let sample_text_3 = sample_text(rows, cols, 'v');
10398 assert_eq!(
10399 sample_text_3,
10400 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10401 );
10402
10403 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10404 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10405 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10406
10407 let multi_buffer = cx.new_model(|cx| {
10408 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10409 multibuffer.push_excerpts(
10410 buffer_1.clone(),
10411 [
10412 ExcerptRange {
10413 context: Point::new(0, 0)..Point::new(3, 0),
10414 primary: None,
10415 },
10416 ExcerptRange {
10417 context: Point::new(5, 0)..Point::new(7, 0),
10418 primary: None,
10419 },
10420 ExcerptRange {
10421 context: Point::new(9, 0)..Point::new(10, 4),
10422 primary: None,
10423 },
10424 ],
10425 cx,
10426 );
10427 multibuffer.push_excerpts(
10428 buffer_2.clone(),
10429 [
10430 ExcerptRange {
10431 context: Point::new(0, 0)..Point::new(3, 0),
10432 primary: None,
10433 },
10434 ExcerptRange {
10435 context: Point::new(5, 0)..Point::new(7, 0),
10436 primary: None,
10437 },
10438 ExcerptRange {
10439 context: Point::new(9, 0)..Point::new(10, 4),
10440 primary: None,
10441 },
10442 ],
10443 cx,
10444 );
10445 multibuffer.push_excerpts(
10446 buffer_3.clone(),
10447 [
10448 ExcerptRange {
10449 context: Point::new(0, 0)..Point::new(3, 0),
10450 primary: None,
10451 },
10452 ExcerptRange {
10453 context: Point::new(5, 0)..Point::new(7, 0),
10454 primary: None,
10455 },
10456 ExcerptRange {
10457 context: Point::new(9, 0)..Point::new(10, 4),
10458 primary: None,
10459 },
10460 ],
10461 cx,
10462 );
10463 multibuffer
10464 });
10465
10466 let fs = FakeFs::new(cx.executor());
10467 fs.insert_tree(
10468 "/a",
10469 json!({
10470 "main.rs": sample_text_1,
10471 "other.rs": sample_text_2,
10472 "lib.rs": sample_text_3,
10473 }),
10474 )
10475 .await;
10476 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10477 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10478 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10479 let multi_buffer_editor = cx.new_view(|cx| {
10480 Editor::new(
10481 EditorMode::Full,
10482 multi_buffer,
10483 Some(project.clone()),
10484 true,
10485 cx,
10486 )
10487 });
10488 let multibuffer_item_id = workspace
10489 .update(cx, |workspace, cx| {
10490 assert!(
10491 workspace.active_item(cx).is_none(),
10492 "active item should be None before the first item is added"
10493 );
10494 workspace.add_item_to_active_pane(
10495 Box::new(multi_buffer_editor.clone()),
10496 None,
10497 true,
10498 cx,
10499 );
10500 let active_item = workspace
10501 .active_item(cx)
10502 .expect("should have an active item after adding the multi buffer");
10503 assert!(
10504 !active_item.is_singleton(cx),
10505 "A multi buffer was expected to active after adding"
10506 );
10507 active_item.item_id()
10508 })
10509 .unwrap();
10510 cx.executor().run_until_parked();
10511
10512 multi_buffer_editor.update(cx, |editor, cx| {
10513 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
10514 editor.open_excerpts(&OpenExcerpts, cx);
10515 });
10516 cx.executor().run_until_parked();
10517 let first_item_id = workspace
10518 .update(cx, |workspace, cx| {
10519 let active_item = workspace
10520 .active_item(cx)
10521 .expect("should have an active item after navigating into the 1st buffer");
10522 let first_item_id = active_item.item_id();
10523 assert_ne!(
10524 first_item_id, multibuffer_item_id,
10525 "Should navigate into the 1st buffer and activate it"
10526 );
10527 assert!(
10528 active_item.is_singleton(cx),
10529 "New active item should be a singleton buffer"
10530 );
10531 assert_eq!(
10532 active_item
10533 .act_as::<Editor>(cx)
10534 .expect("should have navigated into an editor for the 1st buffer")
10535 .read(cx)
10536 .text(cx),
10537 sample_text_1
10538 );
10539
10540 workspace
10541 .go_back(workspace.active_pane().downgrade(), cx)
10542 .detach_and_log_err(cx);
10543
10544 first_item_id
10545 })
10546 .unwrap();
10547 cx.executor().run_until_parked();
10548 workspace
10549 .update(cx, |workspace, cx| {
10550 let active_item = workspace
10551 .active_item(cx)
10552 .expect("should have an active item after navigating back");
10553 assert_eq!(
10554 active_item.item_id(),
10555 multibuffer_item_id,
10556 "Should navigate back to the multi buffer"
10557 );
10558 assert!(!active_item.is_singleton(cx));
10559 })
10560 .unwrap();
10561
10562 multi_buffer_editor.update(cx, |editor, cx| {
10563 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
10564 s.select_ranges(Some(39..40))
10565 });
10566 editor.open_excerpts(&OpenExcerpts, cx);
10567 });
10568 cx.executor().run_until_parked();
10569 let second_item_id = workspace
10570 .update(cx, |workspace, cx| {
10571 let active_item = workspace
10572 .active_item(cx)
10573 .expect("should have an active item after navigating into the 2nd buffer");
10574 let second_item_id = active_item.item_id();
10575 assert_ne!(
10576 second_item_id, multibuffer_item_id,
10577 "Should navigate away from the multibuffer"
10578 );
10579 assert_ne!(
10580 second_item_id, first_item_id,
10581 "Should navigate into the 2nd buffer and activate it"
10582 );
10583 assert!(
10584 active_item.is_singleton(cx),
10585 "New active item should be a singleton buffer"
10586 );
10587 assert_eq!(
10588 active_item
10589 .act_as::<Editor>(cx)
10590 .expect("should have navigated into an editor")
10591 .read(cx)
10592 .text(cx),
10593 sample_text_2
10594 );
10595
10596 workspace
10597 .go_back(workspace.active_pane().downgrade(), cx)
10598 .detach_and_log_err(cx);
10599
10600 second_item_id
10601 })
10602 .unwrap();
10603 cx.executor().run_until_parked();
10604 workspace
10605 .update(cx, |workspace, cx| {
10606 let active_item = workspace
10607 .active_item(cx)
10608 .expect("should have an active item after navigating back from the 2nd buffer");
10609 assert_eq!(
10610 active_item.item_id(),
10611 multibuffer_item_id,
10612 "Should navigate back from the 2nd buffer to the multi buffer"
10613 );
10614 assert!(!active_item.is_singleton(cx));
10615 })
10616 .unwrap();
10617
10618 multi_buffer_editor.update(cx, |editor, cx| {
10619 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
10620 s.select_ranges(Some(60..70))
10621 });
10622 editor.open_excerpts(&OpenExcerpts, cx);
10623 });
10624 cx.executor().run_until_parked();
10625 workspace
10626 .update(cx, |workspace, cx| {
10627 let active_item = workspace
10628 .active_item(cx)
10629 .expect("should have an active item after navigating into the 3rd buffer");
10630 let third_item_id = active_item.item_id();
10631 assert_ne!(
10632 third_item_id, multibuffer_item_id,
10633 "Should navigate into the 3rd buffer and activate it"
10634 );
10635 assert_ne!(third_item_id, first_item_id);
10636 assert_ne!(third_item_id, second_item_id);
10637 assert!(
10638 active_item.is_singleton(cx),
10639 "New active item should be a singleton buffer"
10640 );
10641 assert_eq!(
10642 active_item
10643 .act_as::<Editor>(cx)
10644 .expect("should have navigated into an editor")
10645 .read(cx)
10646 .text(cx),
10647 sample_text_3
10648 );
10649
10650 workspace
10651 .go_back(workspace.active_pane().downgrade(), cx)
10652 .detach_and_log_err(cx);
10653 })
10654 .unwrap();
10655 cx.executor().run_until_parked();
10656 workspace
10657 .update(cx, |workspace, cx| {
10658 let active_item = workspace
10659 .active_item(cx)
10660 .expect("should have an active item after navigating back from the 3rd buffer");
10661 assert_eq!(
10662 active_item.item_id(),
10663 multibuffer_item_id,
10664 "Should navigate back from the 3rd buffer to the multi buffer"
10665 );
10666 assert!(!active_item.is_singleton(cx));
10667 })
10668 .unwrap();
10669}
10670
10671#[gpui::test]
10672async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10673 init_test(cx, |_| {});
10674
10675 let mut cx = EditorTestContext::new(cx).await;
10676
10677 let diff_base = r#"
10678 use some::mod;
10679
10680 const A: u32 = 42;
10681
10682 fn main() {
10683 println!("hello");
10684
10685 println!("world");
10686 }
10687 "#
10688 .unindent();
10689
10690 cx.set_state(
10691 &r#"
10692 use some::modified;
10693
10694 ˇ
10695 fn main() {
10696 println!("hello there");
10697
10698 println!("around the");
10699 println!("world");
10700 }
10701 "#
10702 .unindent(),
10703 );
10704
10705 cx.set_diff_base(Some(&diff_base));
10706 executor.run_until_parked();
10707 let unexpanded_hunks = vec![
10708 (
10709 "use some::mod;\n".to_string(),
10710 DiffHunkStatus::Modified,
10711 DisplayRow(0)..DisplayRow(1),
10712 ),
10713 (
10714 "const A: u32 = 42;\n".to_string(),
10715 DiffHunkStatus::Removed,
10716 DisplayRow(2)..DisplayRow(2),
10717 ),
10718 (
10719 " println!(\"hello\");\n".to_string(),
10720 DiffHunkStatus::Modified,
10721 DisplayRow(4)..DisplayRow(5),
10722 ),
10723 (
10724 "".to_string(),
10725 DiffHunkStatus::Added,
10726 DisplayRow(6)..DisplayRow(7),
10727 ),
10728 ];
10729 cx.update_editor(|editor, cx| {
10730 let snapshot = editor.snapshot(cx);
10731 let all_hunks = editor_hunks(editor, &snapshot, cx);
10732 assert_eq!(all_hunks, unexpanded_hunks);
10733 });
10734
10735 cx.update_editor(|editor, cx| {
10736 for _ in 0..4 {
10737 editor.go_to_hunk(&GoToHunk, cx);
10738 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10739 }
10740 });
10741 executor.run_until_parked();
10742 cx.assert_editor_state(
10743 &r#"
10744 use some::modified;
10745
10746 ˇ
10747 fn main() {
10748 println!("hello there");
10749
10750 println!("around the");
10751 println!("world");
10752 }
10753 "#
10754 .unindent(),
10755 );
10756 cx.update_editor(|editor, cx| {
10757 let snapshot = editor.snapshot(cx);
10758 let all_hunks = editor_hunks(editor, &snapshot, cx);
10759 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10760 assert_eq!(
10761 expanded_hunks_background_highlights(editor, cx),
10762 vec![DisplayRow(1)..=DisplayRow(1), DisplayRow(7)..=DisplayRow(7), DisplayRow(9)..=DisplayRow(9)],
10763 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
10764 );
10765 assert_eq!(
10766 all_hunks,
10767 vec![
10768 ("use some::mod;\n".to_string(), DiffHunkStatus::Modified, DisplayRow(1)..DisplayRow(2)),
10769 ("const A: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(4)..DisplayRow(4)),
10770 (" println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(7)..DisplayRow(8)),
10771 ("".to_string(), DiffHunkStatus::Added, DisplayRow(9)..DisplayRow(10)),
10772 ],
10773 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
10774 (from modified and removed hunks)"
10775 );
10776 assert_eq!(
10777 all_hunks, all_expanded_hunks,
10778 "Editor hunks should not change and all be expanded"
10779 );
10780 });
10781
10782 cx.update_editor(|editor, cx| {
10783 editor.cancel(&Cancel, cx);
10784
10785 let snapshot = editor.snapshot(cx);
10786 let all_hunks = editor_hunks(editor, &snapshot, cx);
10787 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
10788 assert_eq!(
10789 expanded_hunks_background_highlights(editor, cx),
10790 Vec::new(),
10791 "After cancelling in editor, no git highlights should be left"
10792 );
10793 assert_eq!(
10794 all_expanded_hunks,
10795 Vec::new(),
10796 "After cancelling in editor, no hunks should be expanded"
10797 );
10798 assert_eq!(
10799 all_hunks, unexpanded_hunks,
10800 "After cancelling in editor, regular hunks' coordinates should get back to normal"
10801 );
10802 });
10803}
10804
10805#[gpui::test]
10806async fn test_toggled_diff_base_change(
10807 executor: BackgroundExecutor,
10808 cx: &mut gpui::TestAppContext,
10809) {
10810 init_test(cx, |_| {});
10811
10812 let mut cx = EditorTestContext::new(cx).await;
10813
10814 let diff_base = r#"
10815 use some::mod1;
10816 use some::mod2;
10817
10818 const A: u32 = 42;
10819 const B: u32 = 42;
10820 const C: u32 = 42;
10821
10822 fn main(ˇ) {
10823 println!("hello");
10824
10825 println!("world");
10826 }
10827 "#
10828 .unindent();
10829
10830 cx.set_state(
10831 &r#"
10832 use some::mod2;
10833
10834 const A: u32 = 42;
10835 const C: u32 = 42;
10836
10837 fn main(ˇ) {
10838 //println!("hello");
10839
10840 println!("world");
10841 //
10842 //
10843 }
10844 "#
10845 .unindent(),
10846 );
10847
10848 cx.set_diff_base(Some(&diff_base));
10849 executor.run_until_parked();
10850 cx.update_editor(|editor, cx| {
10851 let snapshot = editor.snapshot(cx);
10852 let all_hunks = editor_hunks(editor, &snapshot, cx);
10853 assert_eq!(
10854 all_hunks,
10855 vec![
10856 (
10857 "use some::mod1;\n".to_string(),
10858 DiffHunkStatus::Removed,
10859 DisplayRow(0)..DisplayRow(0)
10860 ),
10861 (
10862 "const B: u32 = 42;\n".to_string(),
10863 DiffHunkStatus::Removed,
10864 DisplayRow(3)..DisplayRow(3)
10865 ),
10866 (
10867 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
10868 DiffHunkStatus::Modified,
10869 DisplayRow(5)..DisplayRow(7)
10870 ),
10871 (
10872 "".to_string(),
10873 DiffHunkStatus::Added,
10874 DisplayRow(9)..DisplayRow(11)
10875 ),
10876 ]
10877 );
10878 });
10879
10880 cx.update_editor(|editor, cx| {
10881 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10882 });
10883 executor.run_until_parked();
10884 cx.assert_editor_state(
10885 &r#"
10886 use some::mod2;
10887
10888 const A: u32 = 42;
10889 const C: u32 = 42;
10890
10891 fn main(ˇ) {
10892 //println!("hello");
10893
10894 println!("world");
10895 //
10896 //
10897 }
10898 "#
10899 .unindent(),
10900 );
10901 cx.update_editor(|editor, cx| {
10902 let snapshot = editor.snapshot(cx);
10903 let all_hunks = editor_hunks(editor, &snapshot, cx);
10904 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10905 assert_eq!(
10906 expanded_hunks_background_highlights(editor, cx),
10907 vec![DisplayRow(9)..=DisplayRow(10), DisplayRow(13)..=DisplayRow(14)],
10908 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
10909 );
10910 assert_eq!(
10911 all_hunks,
10912 vec![
10913 ("use some::mod1;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(1)..DisplayRow(1)),
10914 ("const B: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(5)..DisplayRow(5)),
10915 ("fn main(ˇ) {\n println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(9)..DisplayRow(11)),
10916 ("".to_string(), DiffHunkStatus::Added, DisplayRow(13)..DisplayRow(15)),
10917 ],
10918 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
10919 (from modified and removed hunks)"
10920 );
10921 assert_eq!(
10922 all_hunks, all_expanded_hunks,
10923 "Editor hunks should not change and all be expanded"
10924 );
10925 });
10926
10927 cx.set_diff_base(Some("new diff base!"));
10928 executor.run_until_parked();
10929
10930 cx.update_editor(|editor, cx| {
10931 let snapshot = editor.snapshot(cx);
10932 let all_hunks = editor_hunks(editor, &snapshot, cx);
10933 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
10934 assert_eq!(
10935 expanded_hunks_background_highlights(editor, cx),
10936 Vec::new(),
10937 "After diff base is changed, old git highlights should be removed"
10938 );
10939 assert_eq!(
10940 all_expanded_hunks,
10941 Vec::new(),
10942 "After diff base is changed, old git hunk expansions should be removed"
10943 );
10944 assert_eq!(
10945 all_hunks,
10946 vec![(
10947 "new diff base!".to_string(),
10948 DiffHunkStatus::Modified,
10949 DisplayRow(0)..snapshot.display_snapshot.max_point().row()
10950 )],
10951 "After diff base is changed, hunks should update"
10952 );
10953 });
10954}
10955
10956#[gpui::test]
10957async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10958 init_test(cx, |_| {});
10959
10960 let mut cx = EditorTestContext::new(cx).await;
10961
10962 let diff_base = r#"
10963 use some::mod1;
10964 use some::mod2;
10965
10966 const A: u32 = 42;
10967 const B: u32 = 42;
10968 const C: u32 = 42;
10969
10970 fn main(ˇ) {
10971 println!("hello");
10972
10973 println!("world");
10974 }
10975
10976 fn another() {
10977 println!("another");
10978 }
10979
10980 fn another2() {
10981 println!("another2");
10982 }
10983 "#
10984 .unindent();
10985
10986 cx.set_state(
10987 &r#"
10988 «use some::mod2;
10989
10990 const A: u32 = 42;
10991 const C: u32 = 42;
10992
10993 fn main() {
10994 //println!("hello");
10995
10996 println!("world");
10997 //
10998 //ˇ»
10999 }
11000
11001 fn another() {
11002 println!("another");
11003 println!("another");
11004 }
11005
11006 println!("another2");
11007 }
11008 "#
11009 .unindent(),
11010 );
11011
11012 cx.set_diff_base(Some(&diff_base));
11013 executor.run_until_parked();
11014 cx.update_editor(|editor, cx| {
11015 let snapshot = editor.snapshot(cx);
11016 let all_hunks = editor_hunks(editor, &snapshot, cx);
11017 assert_eq!(
11018 all_hunks,
11019 vec![
11020 (
11021 "use some::mod1;\n".to_string(),
11022 DiffHunkStatus::Removed,
11023 DisplayRow(0)..DisplayRow(0)
11024 ),
11025 (
11026 "const B: u32 = 42;\n".to_string(),
11027 DiffHunkStatus::Removed,
11028 DisplayRow(3)..DisplayRow(3)
11029 ),
11030 (
11031 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11032 DiffHunkStatus::Modified,
11033 DisplayRow(5)..DisplayRow(7)
11034 ),
11035 (
11036 "".to_string(),
11037 DiffHunkStatus::Added,
11038 DisplayRow(9)..DisplayRow(11)
11039 ),
11040 (
11041 "".to_string(),
11042 DiffHunkStatus::Added,
11043 DisplayRow(15)..DisplayRow(16)
11044 ),
11045 (
11046 "fn another2() {\n".to_string(),
11047 DiffHunkStatus::Removed,
11048 DisplayRow(18)..DisplayRow(18)
11049 ),
11050 ]
11051 );
11052 });
11053
11054 cx.update_editor(|editor, cx| {
11055 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11056 });
11057 executor.run_until_parked();
11058 cx.assert_editor_state(
11059 &r#"
11060 «use some::mod2;
11061
11062 const A: u32 = 42;
11063 const C: u32 = 42;
11064
11065 fn main() {
11066 //println!("hello");
11067
11068 println!("world");
11069 //
11070 //ˇ»
11071 }
11072
11073 fn another() {
11074 println!("another");
11075 println!("another");
11076 }
11077
11078 println!("another2");
11079 }
11080 "#
11081 .unindent(),
11082 );
11083 cx.update_editor(|editor, cx| {
11084 let snapshot = editor.snapshot(cx);
11085 let all_hunks = editor_hunks(editor, &snapshot, cx);
11086 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11087 assert_eq!(
11088 expanded_hunks_background_highlights(editor, cx),
11089 vec![
11090 DisplayRow(9)..=DisplayRow(10),
11091 DisplayRow(13)..=DisplayRow(14),
11092 DisplayRow(19)..=DisplayRow(19)
11093 ]
11094 );
11095 assert_eq!(
11096 all_hunks,
11097 vec![
11098 (
11099 "use some::mod1;\n".to_string(),
11100 DiffHunkStatus::Removed,
11101 DisplayRow(1)..DisplayRow(1)
11102 ),
11103 (
11104 "const B: u32 = 42;\n".to_string(),
11105 DiffHunkStatus::Removed,
11106 DisplayRow(5)..DisplayRow(5)
11107 ),
11108 (
11109 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11110 DiffHunkStatus::Modified,
11111 DisplayRow(9)..DisplayRow(11)
11112 ),
11113 (
11114 "".to_string(),
11115 DiffHunkStatus::Added,
11116 DisplayRow(13)..DisplayRow(15)
11117 ),
11118 (
11119 "".to_string(),
11120 DiffHunkStatus::Added,
11121 DisplayRow(19)..DisplayRow(20)
11122 ),
11123 (
11124 "fn another2() {\n".to_string(),
11125 DiffHunkStatus::Removed,
11126 DisplayRow(23)..DisplayRow(23)
11127 ),
11128 ],
11129 );
11130 assert_eq!(all_hunks, all_expanded_hunks);
11131 });
11132
11133 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
11134 cx.executor().run_until_parked();
11135 cx.assert_editor_state(
11136 &r#"
11137 «use some::mod2;
11138
11139 const A: u32 = 42;
11140 const C: u32 = 42;
11141
11142 fn main() {
11143 //println!("hello");
11144
11145 println!("world");
11146 //
11147 //ˇ»
11148 }
11149
11150 fn another() {
11151 println!("another");
11152 println!("another");
11153 }
11154
11155 println!("another2");
11156 }
11157 "#
11158 .unindent(),
11159 );
11160 cx.update_editor(|editor, cx| {
11161 let snapshot = editor.snapshot(cx);
11162 let all_hunks = editor_hunks(editor, &snapshot, cx);
11163 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11164 assert_eq!(
11165 expanded_hunks_background_highlights(editor, cx),
11166 vec![DisplayRow(0)..=DisplayRow(0), DisplayRow(5)..=DisplayRow(5)],
11167 "Only one hunk is left not folded, its highlight should be visible"
11168 );
11169 assert_eq!(
11170 all_hunks,
11171 vec![
11172 (
11173 "use some::mod1;\n".to_string(),
11174 DiffHunkStatus::Removed,
11175 DisplayRow(0)..DisplayRow(0)
11176 ),
11177 (
11178 "const B: u32 = 42;\n".to_string(),
11179 DiffHunkStatus::Removed,
11180 DisplayRow(0)..DisplayRow(0)
11181 ),
11182 (
11183 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11184 DiffHunkStatus::Modified,
11185 DisplayRow(0)..DisplayRow(0)
11186 ),
11187 (
11188 "".to_string(),
11189 DiffHunkStatus::Added,
11190 DisplayRow(0)..DisplayRow(1)
11191 ),
11192 (
11193 "".to_string(),
11194 DiffHunkStatus::Added,
11195 DisplayRow(5)..DisplayRow(6)
11196 ),
11197 (
11198 "fn another2() {\n".to_string(),
11199 DiffHunkStatus::Removed,
11200 DisplayRow(9)..DisplayRow(9)
11201 ),
11202 ],
11203 "Hunk list should still return shifted folded hunks"
11204 );
11205 assert_eq!(
11206 all_expanded_hunks,
11207 vec![
11208 (
11209 "".to_string(),
11210 DiffHunkStatus::Added,
11211 DisplayRow(5)..DisplayRow(6)
11212 ),
11213 (
11214 "fn another2() {\n".to_string(),
11215 DiffHunkStatus::Removed,
11216 DisplayRow(9)..DisplayRow(9)
11217 ),
11218 ],
11219 "Only non-folded hunks should be left expanded"
11220 );
11221 });
11222
11223 cx.update_editor(|editor, cx| {
11224 editor.select_all(&SelectAll, cx);
11225 editor.unfold_lines(&UnfoldLines, cx);
11226 });
11227 cx.executor().run_until_parked();
11228 cx.assert_editor_state(
11229 &r#"
11230 «use some::mod2;
11231
11232 const A: u32 = 42;
11233 const C: u32 = 42;
11234
11235 fn main() {
11236 //println!("hello");
11237
11238 println!("world");
11239 //
11240 //
11241 }
11242
11243 fn another() {
11244 println!("another");
11245 println!("another");
11246 }
11247
11248 println!("another2");
11249 }
11250 ˇ»"#
11251 .unindent(),
11252 );
11253 cx.update_editor(|editor, cx| {
11254 let snapshot = editor.snapshot(cx);
11255 let all_hunks = editor_hunks(editor, &snapshot, cx);
11256 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11257 assert_eq!(
11258 expanded_hunks_background_highlights(editor, cx),
11259 vec![
11260 DisplayRow(9)..=DisplayRow(10),
11261 DisplayRow(13)..=DisplayRow(14),
11262 DisplayRow(19)..=DisplayRow(19)
11263 ],
11264 "After unfolding, all hunk diffs should be visible again"
11265 );
11266 assert_eq!(
11267 all_hunks,
11268 vec![
11269 (
11270 "use some::mod1;\n".to_string(),
11271 DiffHunkStatus::Removed,
11272 DisplayRow(1)..DisplayRow(1)
11273 ),
11274 (
11275 "const B: u32 = 42;\n".to_string(),
11276 DiffHunkStatus::Removed,
11277 DisplayRow(5)..DisplayRow(5)
11278 ),
11279 (
11280 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11281 DiffHunkStatus::Modified,
11282 DisplayRow(9)..DisplayRow(11)
11283 ),
11284 (
11285 "".to_string(),
11286 DiffHunkStatus::Added,
11287 DisplayRow(13)..DisplayRow(15)
11288 ),
11289 (
11290 "".to_string(),
11291 DiffHunkStatus::Added,
11292 DisplayRow(19)..DisplayRow(20)
11293 ),
11294 (
11295 "fn another2() {\n".to_string(),
11296 DiffHunkStatus::Removed,
11297 DisplayRow(23)..DisplayRow(23)
11298 ),
11299 ],
11300 );
11301 assert_eq!(all_hunks, all_expanded_hunks);
11302 });
11303}
11304
11305#[gpui::test]
11306async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
11307 init_test(cx, |_| {});
11308
11309 let cols = 4;
11310 let rows = 10;
11311 let sample_text_1 = sample_text(rows, cols, 'a');
11312 assert_eq!(
11313 sample_text_1,
11314 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11315 );
11316 let modified_sample_text_1 = "aaaa\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
11317 let sample_text_2 = sample_text(rows, cols, 'l');
11318 assert_eq!(
11319 sample_text_2,
11320 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11321 );
11322 let modified_sample_text_2 = "llll\nmmmm\n1n1n1n1n1\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
11323 let sample_text_3 = sample_text(rows, cols, 'v');
11324 assert_eq!(
11325 sample_text_3,
11326 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11327 );
11328 let modified_sample_text_3 =
11329 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n@@@@\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
11330 let buffer_1 = cx.new_model(|cx| {
11331 let mut buffer = Buffer::local(modified_sample_text_1.to_string(), cx);
11332 buffer.set_diff_base(Some(sample_text_1.clone()), cx);
11333 buffer
11334 });
11335 let buffer_2 = cx.new_model(|cx| {
11336 let mut buffer = Buffer::local(modified_sample_text_2.to_string(), cx);
11337 buffer.set_diff_base(Some(sample_text_2.clone()), cx);
11338 buffer
11339 });
11340 let buffer_3 = cx.new_model(|cx| {
11341 let mut buffer = Buffer::local(modified_sample_text_3.to_string(), cx);
11342 buffer.set_diff_base(Some(sample_text_3.clone()), cx);
11343 buffer
11344 });
11345
11346 let multi_buffer = cx.new_model(|cx| {
11347 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
11348 multibuffer.push_excerpts(
11349 buffer_1.clone(),
11350 [
11351 ExcerptRange {
11352 context: Point::new(0, 0)..Point::new(3, 0),
11353 primary: None,
11354 },
11355 ExcerptRange {
11356 context: Point::new(5, 0)..Point::new(7, 0),
11357 primary: None,
11358 },
11359 ExcerptRange {
11360 context: Point::new(9, 0)..Point::new(10, 4),
11361 primary: None,
11362 },
11363 ],
11364 cx,
11365 );
11366 multibuffer.push_excerpts(
11367 buffer_2.clone(),
11368 [
11369 ExcerptRange {
11370 context: Point::new(0, 0)..Point::new(3, 0),
11371 primary: None,
11372 },
11373 ExcerptRange {
11374 context: Point::new(5, 0)..Point::new(7, 0),
11375 primary: None,
11376 },
11377 ExcerptRange {
11378 context: Point::new(9, 0)..Point::new(10, 4),
11379 primary: None,
11380 },
11381 ],
11382 cx,
11383 );
11384 multibuffer.push_excerpts(
11385 buffer_3.clone(),
11386 [
11387 ExcerptRange {
11388 context: Point::new(0, 0)..Point::new(3, 0),
11389 primary: None,
11390 },
11391 ExcerptRange {
11392 context: Point::new(5, 0)..Point::new(7, 0),
11393 primary: None,
11394 },
11395 ExcerptRange {
11396 context: Point::new(9, 0)..Point::new(10, 4),
11397 primary: None,
11398 },
11399 ],
11400 cx,
11401 );
11402 multibuffer
11403 });
11404
11405 let fs = FakeFs::new(cx.executor());
11406 fs.insert_tree(
11407 "/a",
11408 json!({
11409 "main.rs": modified_sample_text_1,
11410 "other.rs": modified_sample_text_2,
11411 "lib.rs": modified_sample_text_3,
11412 }),
11413 )
11414 .await;
11415
11416 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11417 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11418 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11419 let multi_buffer_editor = cx.new_view(|cx| {
11420 Editor::new(
11421 EditorMode::Full,
11422 multi_buffer,
11423 Some(project.clone()),
11424 true,
11425 cx,
11426 )
11427 });
11428 cx.executor().run_until_parked();
11429
11430 let expected_all_hunks = vec![
11431 (
11432 "bbbb\n".to_string(),
11433 DiffHunkStatus::Removed,
11434 DisplayRow(4)..DisplayRow(4),
11435 ),
11436 (
11437 "nnnn\n".to_string(),
11438 DiffHunkStatus::Modified,
11439 DisplayRow(21)..DisplayRow(22),
11440 ),
11441 (
11442 "".to_string(),
11443 DiffHunkStatus::Added,
11444 DisplayRow(41)..DisplayRow(42),
11445 ),
11446 ];
11447 let expected_all_hunks_shifted = vec![
11448 (
11449 "bbbb\n".to_string(),
11450 DiffHunkStatus::Removed,
11451 DisplayRow(5)..DisplayRow(5),
11452 ),
11453 (
11454 "nnnn\n".to_string(),
11455 DiffHunkStatus::Modified,
11456 DisplayRow(23)..DisplayRow(24),
11457 ),
11458 (
11459 "".to_string(),
11460 DiffHunkStatus::Added,
11461 DisplayRow(43)..DisplayRow(44),
11462 ),
11463 ];
11464
11465 multi_buffer_editor.update(cx, |editor, cx| {
11466 let snapshot = editor.snapshot(cx);
11467 let all_hunks = editor_hunks(editor, &snapshot, cx);
11468 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11469 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11470 assert_eq!(all_hunks, expected_all_hunks);
11471 assert_eq!(all_expanded_hunks, Vec::new());
11472 });
11473
11474 multi_buffer_editor.update(cx, |editor, cx| {
11475 editor.select_all(&SelectAll, cx);
11476 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11477 });
11478 cx.executor().run_until_parked();
11479 multi_buffer_editor.update(cx, |editor, cx| {
11480 let snapshot = editor.snapshot(cx);
11481 let all_hunks = editor_hunks(editor, &snapshot, cx);
11482 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11483 assert_eq!(
11484 expanded_hunks_background_highlights(editor, cx),
11485 vec![
11486 DisplayRow(23)..=DisplayRow(23),
11487 DisplayRow(43)..=DisplayRow(43)
11488 ],
11489 );
11490 assert_eq!(all_hunks, expected_all_hunks_shifted);
11491 assert_eq!(all_hunks, all_expanded_hunks);
11492 });
11493
11494 multi_buffer_editor.update(cx, |editor, cx| {
11495 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11496 });
11497 cx.executor().run_until_parked();
11498 multi_buffer_editor.update(cx, |editor, cx| {
11499 let snapshot = editor.snapshot(cx);
11500 let all_hunks = editor_hunks(editor, &snapshot, cx);
11501 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11502 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11503 assert_eq!(all_hunks, expected_all_hunks);
11504 assert_eq!(all_expanded_hunks, Vec::new());
11505 });
11506
11507 multi_buffer_editor.update(cx, |editor, cx| {
11508 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11509 });
11510 cx.executor().run_until_parked();
11511 multi_buffer_editor.update(cx, |editor, cx| {
11512 let snapshot = editor.snapshot(cx);
11513 let all_hunks = editor_hunks(editor, &snapshot, cx);
11514 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11515 assert_eq!(
11516 expanded_hunks_background_highlights(editor, cx),
11517 vec![
11518 DisplayRow(23)..=DisplayRow(23),
11519 DisplayRow(43)..=DisplayRow(43)
11520 ],
11521 );
11522 assert_eq!(all_hunks, expected_all_hunks_shifted);
11523 assert_eq!(all_hunks, all_expanded_hunks);
11524 });
11525
11526 multi_buffer_editor.update(cx, |editor, cx| {
11527 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11528 });
11529 cx.executor().run_until_parked();
11530 multi_buffer_editor.update(cx, |editor, cx| {
11531 let snapshot = editor.snapshot(cx);
11532 let all_hunks = editor_hunks(editor, &snapshot, cx);
11533 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11534 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11535 assert_eq!(all_hunks, expected_all_hunks);
11536 assert_eq!(all_expanded_hunks, Vec::new());
11537 });
11538}
11539
11540#[gpui::test]
11541async fn test_edits_around_toggled_additions(
11542 executor: BackgroundExecutor,
11543 cx: &mut gpui::TestAppContext,
11544) {
11545 init_test(cx, |_| {});
11546
11547 let mut cx = EditorTestContext::new(cx).await;
11548
11549 let diff_base = r#"
11550 use some::mod1;
11551 use some::mod2;
11552
11553 const A: u32 = 42;
11554
11555 fn main() {
11556 println!("hello");
11557
11558 println!("world");
11559 }
11560 "#
11561 .unindent();
11562 executor.run_until_parked();
11563 cx.set_state(
11564 &r#"
11565 use some::mod1;
11566 use some::mod2;
11567
11568 const A: u32 = 42;
11569 const B: u32 = 42;
11570 const C: u32 = 42;
11571 ˇ
11572
11573 fn main() {
11574 println!("hello");
11575
11576 println!("world");
11577 }
11578 "#
11579 .unindent(),
11580 );
11581
11582 cx.set_diff_base(Some(&diff_base));
11583 executor.run_until_parked();
11584 cx.update_editor(|editor, cx| {
11585 let snapshot = editor.snapshot(cx);
11586 let all_hunks = editor_hunks(editor, &snapshot, cx);
11587 assert_eq!(
11588 all_hunks,
11589 vec![(
11590 "".to_string(),
11591 DiffHunkStatus::Added,
11592 DisplayRow(4)..DisplayRow(7)
11593 )]
11594 );
11595 });
11596 cx.update_editor(|editor, cx| {
11597 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11598 });
11599 executor.run_until_parked();
11600 cx.assert_editor_state(
11601 &r#"
11602 use some::mod1;
11603 use some::mod2;
11604
11605 const A: u32 = 42;
11606 const B: u32 = 42;
11607 const C: u32 = 42;
11608 ˇ
11609
11610 fn main() {
11611 println!("hello");
11612
11613 println!("world");
11614 }
11615 "#
11616 .unindent(),
11617 );
11618 cx.update_editor(|editor, cx| {
11619 let snapshot = editor.snapshot(cx);
11620 let all_hunks = editor_hunks(editor, &snapshot, cx);
11621 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11622 assert_eq!(
11623 all_hunks,
11624 vec![(
11625 "".to_string(),
11626 DiffHunkStatus::Added,
11627 DisplayRow(4)..DisplayRow(7)
11628 )]
11629 );
11630 assert_eq!(
11631 expanded_hunks_background_highlights(editor, cx),
11632 vec![DisplayRow(4)..=DisplayRow(6)]
11633 );
11634 assert_eq!(all_hunks, all_expanded_hunks);
11635 });
11636
11637 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
11638 executor.run_until_parked();
11639 cx.assert_editor_state(
11640 &r#"
11641 use some::mod1;
11642 use some::mod2;
11643
11644 const A: u32 = 42;
11645 const B: u32 = 42;
11646 const C: u32 = 42;
11647 const D: u32 = 42;
11648 ˇ
11649
11650 fn main() {
11651 println!("hello");
11652
11653 println!("world");
11654 }
11655 "#
11656 .unindent(),
11657 );
11658 cx.update_editor(|editor, cx| {
11659 let snapshot = editor.snapshot(cx);
11660 let all_hunks = editor_hunks(editor, &snapshot, cx);
11661 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11662 assert_eq!(
11663 all_hunks,
11664 vec![(
11665 "".to_string(),
11666 DiffHunkStatus::Added,
11667 DisplayRow(4)..DisplayRow(8)
11668 )]
11669 );
11670 assert_eq!(
11671 expanded_hunks_background_highlights(editor, cx),
11672 vec![DisplayRow(4)..=DisplayRow(6)],
11673 "Edited hunk should have one more line added"
11674 );
11675 assert_eq!(
11676 all_hunks, all_expanded_hunks,
11677 "Expanded hunk should also grow with the addition"
11678 );
11679 });
11680
11681 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
11682 executor.run_until_parked();
11683 cx.assert_editor_state(
11684 &r#"
11685 use some::mod1;
11686 use some::mod2;
11687
11688 const A: u32 = 42;
11689 const B: u32 = 42;
11690 const C: u32 = 42;
11691 const D: u32 = 42;
11692 const E: u32 = 42;
11693 ˇ
11694
11695 fn main() {
11696 println!("hello");
11697
11698 println!("world");
11699 }
11700 "#
11701 .unindent(),
11702 );
11703 cx.update_editor(|editor, cx| {
11704 let snapshot = editor.snapshot(cx);
11705 let all_hunks = editor_hunks(editor, &snapshot, cx);
11706 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11707 assert_eq!(
11708 all_hunks,
11709 vec![(
11710 "".to_string(),
11711 DiffHunkStatus::Added,
11712 DisplayRow(4)..DisplayRow(9)
11713 )]
11714 );
11715 assert_eq!(
11716 expanded_hunks_background_highlights(editor, cx),
11717 vec![DisplayRow(4)..=DisplayRow(6)],
11718 "Edited hunk should have one more line added"
11719 );
11720 assert_eq!(all_hunks, all_expanded_hunks);
11721 });
11722
11723 cx.update_editor(|editor, cx| {
11724 editor.move_up(&MoveUp, cx);
11725 editor.delete_line(&DeleteLine, cx);
11726 });
11727 executor.run_until_parked();
11728 cx.assert_editor_state(
11729 &r#"
11730 use some::mod1;
11731 use some::mod2;
11732
11733 const A: u32 = 42;
11734 const B: u32 = 42;
11735 const C: u32 = 42;
11736 const D: u32 = 42;
11737 ˇ
11738
11739 fn main() {
11740 println!("hello");
11741
11742 println!("world");
11743 }
11744 "#
11745 .unindent(),
11746 );
11747 cx.update_editor(|editor, cx| {
11748 let snapshot = editor.snapshot(cx);
11749 let all_hunks = editor_hunks(editor, &snapshot, cx);
11750 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11751 assert_eq!(
11752 all_hunks,
11753 vec![(
11754 "".to_string(),
11755 DiffHunkStatus::Added,
11756 DisplayRow(4)..DisplayRow(8)
11757 )]
11758 );
11759 assert_eq!(
11760 expanded_hunks_background_highlights(editor, cx),
11761 vec![DisplayRow(4)..=DisplayRow(6)],
11762 "Deleting a line should shrint the hunk"
11763 );
11764 assert_eq!(
11765 all_hunks, all_expanded_hunks,
11766 "Expanded hunk should also shrink with the addition"
11767 );
11768 });
11769
11770 cx.update_editor(|editor, 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 editor.move_up(&MoveUp, cx);
11776 editor.delete_line(&DeleteLine, cx);
11777 });
11778 executor.run_until_parked();
11779 cx.assert_editor_state(
11780 &r#"
11781 use some::mod1;
11782 use some::mod2;
11783
11784 const A: u32 = 42;
11785 ˇ
11786
11787 fn main() {
11788 println!("hello");
11789
11790 println!("world");
11791 }
11792 "#
11793 .unindent(),
11794 );
11795 cx.update_editor(|editor, cx| {
11796 let snapshot = editor.snapshot(cx);
11797 let all_hunks = editor_hunks(editor, &snapshot, cx);
11798 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11799 assert_eq!(
11800 all_hunks,
11801 vec![(
11802 "".to_string(),
11803 DiffHunkStatus::Added,
11804 DisplayRow(5)..DisplayRow(6)
11805 )]
11806 );
11807 assert_eq!(
11808 expanded_hunks_background_highlights(editor, cx),
11809 vec![DisplayRow(5)..=DisplayRow(5)]
11810 );
11811 assert_eq!(all_hunks, all_expanded_hunks);
11812 });
11813
11814 cx.update_editor(|editor, cx| {
11815 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
11816 editor.delete_line(&DeleteLine, cx);
11817 });
11818 executor.run_until_parked();
11819 cx.assert_editor_state(
11820 &r#"
11821 ˇ
11822
11823 fn main() {
11824 println!("hello");
11825
11826 println!("world");
11827 }
11828 "#
11829 .unindent(),
11830 );
11831 cx.update_editor(|editor, cx| {
11832 let snapshot = editor.snapshot(cx);
11833 let all_hunks = editor_hunks(editor, &snapshot, cx);
11834 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11835 assert_eq!(
11836 all_hunks,
11837 vec![
11838 (
11839 "use some::mod1;\nuse some::mod2;\n".to_string(),
11840 DiffHunkStatus::Removed,
11841 DisplayRow(0)..DisplayRow(0)
11842 ),
11843 (
11844 "const A: u32 = 42;\n".to_string(),
11845 DiffHunkStatus::Removed,
11846 DisplayRow(2)..DisplayRow(2)
11847 )
11848 ]
11849 );
11850 assert_eq!(
11851 expanded_hunks_background_highlights(editor, cx),
11852 Vec::new(),
11853 "Should close all stale expanded addition hunks"
11854 );
11855 assert_eq!(
11856 all_expanded_hunks,
11857 vec![(
11858 "const A: u32 = 42;\n".to_string(),
11859 DiffHunkStatus::Removed,
11860 DisplayRow(2)..DisplayRow(2)
11861 )],
11862 "Should open hunks that were adjacent to the stale addition one"
11863 );
11864 });
11865}
11866
11867#[gpui::test]
11868async fn test_edits_around_toggled_deletions(
11869 executor: BackgroundExecutor,
11870 cx: &mut gpui::TestAppContext,
11871) {
11872 init_test(cx, |_| {});
11873
11874 let mut cx = EditorTestContext::new(cx).await;
11875
11876 let diff_base = r#"
11877 use some::mod1;
11878 use some::mod2;
11879
11880 const A: u32 = 42;
11881 const B: u32 = 42;
11882 const C: u32 = 42;
11883
11884
11885 fn main() {
11886 println!("hello");
11887
11888 println!("world");
11889 }
11890 "#
11891 .unindent();
11892 executor.run_until_parked();
11893 cx.set_state(
11894 &r#"
11895 use some::mod1;
11896 use some::mod2;
11897
11898 ˇconst B: u32 = 42;
11899 const C: u32 = 42;
11900
11901
11902 fn main() {
11903 println!("hello");
11904
11905 println!("world");
11906 }
11907 "#
11908 .unindent(),
11909 );
11910
11911 cx.set_diff_base(Some(&diff_base));
11912 executor.run_until_parked();
11913 cx.update_editor(|editor, cx| {
11914 let snapshot = editor.snapshot(cx);
11915 let all_hunks = editor_hunks(editor, &snapshot, cx);
11916 assert_eq!(
11917 all_hunks,
11918 vec![(
11919 "const A: u32 = 42;\n".to_string(),
11920 DiffHunkStatus::Removed,
11921 DisplayRow(3)..DisplayRow(3)
11922 )]
11923 );
11924 });
11925 cx.update_editor(|editor, cx| {
11926 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11927 });
11928 executor.run_until_parked();
11929 cx.assert_editor_state(
11930 &r#"
11931 use some::mod1;
11932 use some::mod2;
11933
11934 ˇconst B: u32 = 42;
11935 const C: u32 = 42;
11936
11937
11938 fn main() {
11939 println!("hello");
11940
11941 println!("world");
11942 }
11943 "#
11944 .unindent(),
11945 );
11946 cx.update_editor(|editor, cx| {
11947 let snapshot = editor.snapshot(cx);
11948 let all_hunks = editor_hunks(editor, &snapshot, cx);
11949 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11950 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11951 assert_eq!(
11952 all_hunks,
11953 vec![(
11954 "const A: u32 = 42;\n".to_string(),
11955 DiffHunkStatus::Removed,
11956 DisplayRow(4)..DisplayRow(4)
11957 )]
11958 );
11959 assert_eq!(all_hunks, all_expanded_hunks);
11960 });
11961
11962 cx.update_editor(|editor, cx| {
11963 editor.delete_line(&DeleteLine, cx);
11964 });
11965 executor.run_until_parked();
11966 cx.assert_editor_state(
11967 &r#"
11968 use some::mod1;
11969 use some::mod2;
11970
11971 ˇconst C: u32 = 42;
11972
11973
11974 fn main() {
11975 println!("hello");
11976
11977 println!("world");
11978 }
11979 "#
11980 .unindent(),
11981 );
11982 cx.update_editor(|editor, cx| {
11983 let snapshot = editor.snapshot(cx);
11984 let all_hunks = editor_hunks(editor, &snapshot, cx);
11985 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11986 assert_eq!(
11987 expanded_hunks_background_highlights(editor, cx),
11988 Vec::new(),
11989 "Deleted hunks do not highlight current editor's background"
11990 );
11991 assert_eq!(
11992 all_hunks,
11993 vec![(
11994 "const A: u32 = 42;\nconst B: u32 = 42;\n".to_string(),
11995 DiffHunkStatus::Removed,
11996 DisplayRow(5)..DisplayRow(5)
11997 )]
11998 );
11999 assert_eq!(all_hunks, all_expanded_hunks);
12000 });
12001
12002 cx.update_editor(|editor, cx| {
12003 editor.delete_line(&DeleteLine, cx);
12004 });
12005 executor.run_until_parked();
12006 cx.assert_editor_state(
12007 &r#"
12008 use some::mod1;
12009 use some::mod2;
12010
12011 ˇ
12012
12013 fn main() {
12014 println!("hello");
12015
12016 println!("world");
12017 }
12018 "#
12019 .unindent(),
12020 );
12021 cx.update_editor(|editor, cx| {
12022 let snapshot = editor.snapshot(cx);
12023 let all_hunks = editor_hunks(editor, &snapshot, cx);
12024 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12025 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
12026 assert_eq!(
12027 all_hunks,
12028 vec![(
12029 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12030 DiffHunkStatus::Removed,
12031 DisplayRow(6)..DisplayRow(6)
12032 )]
12033 );
12034 assert_eq!(all_hunks, all_expanded_hunks);
12035 });
12036
12037 cx.update_editor(|editor, cx| {
12038 editor.handle_input("replacement", cx);
12039 });
12040 executor.run_until_parked();
12041 cx.assert_editor_state(
12042 &r#"
12043 use some::mod1;
12044 use some::mod2;
12045
12046 replacementˇ
12047
12048 fn main() {
12049 println!("hello");
12050
12051 println!("world");
12052 }
12053 "#
12054 .unindent(),
12055 );
12056 cx.update_editor(|editor, cx| {
12057 let snapshot = editor.snapshot(cx);
12058 let all_hunks = editor_hunks(editor, &snapshot, cx);
12059 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12060 assert_eq!(
12061 all_hunks,
12062 vec![(
12063 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n\n".to_string(),
12064 DiffHunkStatus::Modified,
12065 DisplayRow(7)..DisplayRow(8)
12066 )]
12067 );
12068 assert_eq!(
12069 expanded_hunks_background_highlights(editor, cx),
12070 vec![DisplayRow(7)..=DisplayRow(7)],
12071 "Modified expanded hunks should display additions and highlight their background"
12072 );
12073 assert_eq!(all_hunks, all_expanded_hunks);
12074 });
12075}
12076
12077#[gpui::test]
12078async fn test_edits_around_toggled_modifications(
12079 executor: BackgroundExecutor,
12080 cx: &mut gpui::TestAppContext,
12081) {
12082 init_test(cx, |_| {});
12083
12084 let mut cx = EditorTestContext::new(cx).await;
12085
12086 let diff_base = r#"
12087 use some::mod1;
12088 use some::mod2;
12089
12090 const A: u32 = 42;
12091 const B: u32 = 42;
12092 const C: u32 = 42;
12093 const D: u32 = 42;
12094
12095
12096 fn main() {
12097 println!("hello");
12098
12099 println!("world");
12100 }"#
12101 .unindent();
12102 executor.run_until_parked();
12103 cx.set_state(
12104 &r#"
12105 use some::mod1;
12106 use some::mod2;
12107
12108 const A: u32 = 42;
12109 const B: u32 = 42;
12110 const C: u32 = 43ˇ
12111 const D: u32 = 42;
12112
12113
12114 fn main() {
12115 println!("hello");
12116
12117 println!("world");
12118 }"#
12119 .unindent(),
12120 );
12121
12122 cx.set_diff_base(Some(&diff_base));
12123 executor.run_until_parked();
12124 cx.update_editor(|editor, cx| {
12125 let snapshot = editor.snapshot(cx);
12126 let all_hunks = editor_hunks(editor, &snapshot, cx);
12127 assert_eq!(
12128 all_hunks,
12129 vec![(
12130 "const C: u32 = 42;\n".to_string(),
12131 DiffHunkStatus::Modified,
12132 DisplayRow(5)..DisplayRow(6)
12133 )]
12134 );
12135 });
12136 cx.update_editor(|editor, cx| {
12137 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12138 });
12139 executor.run_until_parked();
12140 cx.assert_editor_state(
12141 &r#"
12142 use some::mod1;
12143 use some::mod2;
12144
12145 const A: u32 = 42;
12146 const B: u32 = 42;
12147 const C: u32 = 43ˇ
12148 const D: u32 = 42;
12149
12150
12151 fn main() {
12152 println!("hello");
12153
12154 println!("world");
12155 }"#
12156 .unindent(),
12157 );
12158 cx.update_editor(|editor, cx| {
12159 let snapshot = editor.snapshot(cx);
12160 let all_hunks = editor_hunks(editor, &snapshot, cx);
12161 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12162 assert_eq!(
12163 expanded_hunks_background_highlights(editor, cx),
12164 vec![DisplayRow(6)..=DisplayRow(6)],
12165 );
12166 assert_eq!(
12167 all_hunks,
12168 vec![(
12169 "const C: u32 = 42;\n".to_string(),
12170 DiffHunkStatus::Modified,
12171 DisplayRow(6)..DisplayRow(7)
12172 )]
12173 );
12174 assert_eq!(all_hunks, all_expanded_hunks);
12175 });
12176
12177 cx.update_editor(|editor, cx| {
12178 editor.handle_input("\nnew_line\n", cx);
12179 });
12180 executor.run_until_parked();
12181 cx.assert_editor_state(
12182 &r#"
12183 use some::mod1;
12184 use some::mod2;
12185
12186 const A: u32 = 42;
12187 const B: u32 = 42;
12188 const C: u32 = 43
12189 new_line
12190 ˇ
12191 const D: u32 = 42;
12192
12193
12194 fn main() {
12195 println!("hello");
12196
12197 println!("world");
12198 }"#
12199 .unindent(),
12200 );
12201 cx.update_editor(|editor, cx| {
12202 let snapshot = editor.snapshot(cx);
12203 let all_hunks = editor_hunks(editor, &snapshot, cx);
12204 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12205 assert_eq!(
12206 expanded_hunks_background_highlights(editor, cx),
12207 vec![DisplayRow(6)..=DisplayRow(6)],
12208 "Modified hunk should grow highlighted lines on more text additions"
12209 );
12210 assert_eq!(
12211 all_hunks,
12212 vec![(
12213 "const C: u32 = 42;\n".to_string(),
12214 DiffHunkStatus::Modified,
12215 DisplayRow(6)..DisplayRow(9)
12216 )]
12217 );
12218 assert_eq!(all_hunks, all_expanded_hunks);
12219 });
12220
12221 cx.update_editor(|editor, cx| {
12222 editor.move_up(&MoveUp, cx);
12223 editor.move_up(&MoveUp, cx);
12224 editor.move_up(&MoveUp, cx);
12225 editor.delete_line(&DeleteLine, cx);
12226 });
12227 executor.run_until_parked();
12228 cx.assert_editor_state(
12229 &r#"
12230 use some::mod1;
12231 use some::mod2;
12232
12233 const A: u32 = 42;
12234 ˇconst C: u32 = 43
12235 new_line
12236
12237 const D: u32 = 42;
12238
12239
12240 fn main() {
12241 println!("hello");
12242
12243 println!("world");
12244 }"#
12245 .unindent(),
12246 );
12247 cx.update_editor(|editor, cx| {
12248 let snapshot = editor.snapshot(cx);
12249 let all_hunks = editor_hunks(editor, &snapshot, cx);
12250 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12251 assert_eq!(
12252 expanded_hunks_background_highlights(editor, cx),
12253 vec![DisplayRow(6)..=DisplayRow(8)],
12254 );
12255 assert_eq!(
12256 all_hunks,
12257 vec![(
12258 "const B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12259 DiffHunkStatus::Modified,
12260 DisplayRow(6)..DisplayRow(9)
12261 )],
12262 "Modified hunk should grow deleted lines on text deletions above"
12263 );
12264 assert_eq!(all_hunks, all_expanded_hunks);
12265 });
12266
12267 cx.update_editor(|editor, cx| {
12268 editor.move_up(&MoveUp, cx);
12269 editor.handle_input("v", cx);
12270 });
12271 executor.run_until_parked();
12272 cx.assert_editor_state(
12273 &r#"
12274 use some::mod1;
12275 use some::mod2;
12276
12277 vˇconst A: u32 = 42;
12278 const C: u32 = 43
12279 new_line
12280
12281 const D: u32 = 42;
12282
12283
12284 fn main() {
12285 println!("hello");
12286
12287 println!("world");
12288 }"#
12289 .unindent(),
12290 );
12291 cx.update_editor(|editor, cx| {
12292 let snapshot = editor.snapshot(cx);
12293 let all_hunks = editor_hunks(editor, &snapshot, cx);
12294 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12295 assert_eq!(
12296 expanded_hunks_background_highlights(editor, cx),
12297 vec![DisplayRow(6)..=DisplayRow(9)],
12298 "Modified hunk should grow deleted lines on text modifications above"
12299 );
12300 assert_eq!(
12301 all_hunks,
12302 vec![(
12303 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12304 DiffHunkStatus::Modified,
12305 DisplayRow(6)..DisplayRow(10)
12306 )]
12307 );
12308 assert_eq!(all_hunks, all_expanded_hunks);
12309 });
12310
12311 cx.update_editor(|editor, cx| {
12312 editor.move_down(&MoveDown, cx);
12313 editor.move_down(&MoveDown, cx);
12314 editor.delete_line(&DeleteLine, cx)
12315 });
12316 executor.run_until_parked();
12317 cx.assert_editor_state(
12318 &r#"
12319 use some::mod1;
12320 use some::mod2;
12321
12322 vconst A: u32 = 42;
12323 const C: u32 = 43
12324 ˇ
12325 const D: u32 = 42;
12326
12327
12328 fn main() {
12329 println!("hello");
12330
12331 println!("world");
12332 }"#
12333 .unindent(),
12334 );
12335 cx.update_editor(|editor, cx| {
12336 let snapshot = editor.snapshot(cx);
12337 let all_hunks = editor_hunks(editor, &snapshot, cx);
12338 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12339 assert_eq!(
12340 expanded_hunks_background_highlights(editor, cx),
12341 vec![DisplayRow(6)..=DisplayRow(8)],
12342 "Modified hunk should grow shrink lines on modification lines removal"
12343 );
12344 assert_eq!(
12345 all_hunks,
12346 vec![(
12347 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12348 DiffHunkStatus::Modified,
12349 DisplayRow(6)..DisplayRow(9)
12350 )]
12351 );
12352 assert_eq!(all_hunks, all_expanded_hunks);
12353 });
12354
12355 cx.update_editor(|editor, cx| {
12356 editor.move_up(&MoveUp, cx);
12357 editor.move_up(&MoveUp, cx);
12358 editor.select_down_by_lines(&SelectDownByLines { lines: 4 }, cx);
12359 editor.delete_line(&DeleteLine, cx)
12360 });
12361 executor.run_until_parked();
12362 cx.assert_editor_state(
12363 &r#"
12364 use some::mod1;
12365 use some::mod2;
12366
12367 ˇ
12368
12369 fn main() {
12370 println!("hello");
12371
12372 println!("world");
12373 }"#
12374 .unindent(),
12375 );
12376 cx.update_editor(|editor, cx| {
12377 let snapshot = editor.snapshot(cx);
12378 let all_hunks = editor_hunks(editor, &snapshot, cx);
12379 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12380 assert_eq!(
12381 expanded_hunks_background_highlights(editor, cx),
12382 Vec::new(),
12383 "Modified hunk should turn into a removed one on all modified lines removal"
12384 );
12385 assert_eq!(
12386 all_hunks,
12387 vec![(
12388 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\nconst D: u32 = 42;\n"
12389 .to_string(),
12390 DiffHunkStatus::Removed,
12391 DisplayRow(7)..DisplayRow(7)
12392 )]
12393 );
12394 assert_eq!(all_hunks, all_expanded_hunks);
12395 });
12396}
12397
12398#[gpui::test]
12399async fn test_multiple_expanded_hunks_merge(
12400 executor: BackgroundExecutor,
12401 cx: &mut gpui::TestAppContext,
12402) {
12403 init_test(cx, |_| {});
12404
12405 let mut cx = EditorTestContext::new(cx).await;
12406
12407 let diff_base = r#"
12408 use some::mod1;
12409 use some::mod2;
12410
12411 const A: u32 = 42;
12412 const B: u32 = 42;
12413 const C: u32 = 42;
12414 const D: u32 = 42;
12415
12416
12417 fn main() {
12418 println!("hello");
12419
12420 println!("world");
12421 }"#
12422 .unindent();
12423 executor.run_until_parked();
12424 cx.set_state(
12425 &r#"
12426 use some::mod1;
12427 use some::mod2;
12428
12429 const A: u32 = 42;
12430 const B: u32 = 42;
12431 const C: u32 = 43ˇ
12432 const D: u32 = 42;
12433
12434
12435 fn main() {
12436 println!("hello");
12437
12438 println!("world");
12439 }"#
12440 .unindent(),
12441 );
12442
12443 cx.set_diff_base(Some(&diff_base));
12444 executor.run_until_parked();
12445 cx.update_editor(|editor, cx| {
12446 let snapshot = editor.snapshot(cx);
12447 let all_hunks = editor_hunks(editor, &snapshot, cx);
12448 assert_eq!(
12449 all_hunks,
12450 vec![(
12451 "const C: u32 = 42;\n".to_string(),
12452 DiffHunkStatus::Modified,
12453 DisplayRow(5)..DisplayRow(6)
12454 )]
12455 );
12456 });
12457 cx.update_editor(|editor, cx| {
12458 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12459 });
12460 executor.run_until_parked();
12461 cx.assert_editor_state(
12462 &r#"
12463 use some::mod1;
12464 use some::mod2;
12465
12466 const A: u32 = 42;
12467 const B: u32 = 42;
12468 const C: u32 = 43ˇ
12469 const D: u32 = 42;
12470
12471
12472 fn main() {
12473 println!("hello");
12474
12475 println!("world");
12476 }"#
12477 .unindent(),
12478 );
12479 cx.update_editor(|editor, cx| {
12480 let snapshot = editor.snapshot(cx);
12481 let all_hunks = editor_hunks(editor, &snapshot, cx);
12482 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12483 assert_eq!(
12484 expanded_hunks_background_highlights(editor, cx),
12485 vec![DisplayRow(6)..=DisplayRow(6)],
12486 );
12487 assert_eq!(
12488 all_hunks,
12489 vec![(
12490 "const C: u32 = 42;\n".to_string(),
12491 DiffHunkStatus::Modified,
12492 DisplayRow(6)..DisplayRow(7)
12493 )]
12494 );
12495 assert_eq!(all_hunks, all_expanded_hunks);
12496 });
12497
12498 cx.update_editor(|editor, cx| {
12499 editor.handle_input("\nnew_line\n", cx);
12500 });
12501 executor.run_until_parked();
12502 cx.assert_editor_state(
12503 &r#"
12504 use some::mod1;
12505 use some::mod2;
12506
12507 const A: u32 = 42;
12508 const B: u32 = 42;
12509 const C: u32 = 43
12510 new_line
12511 ˇ
12512 const D: u32 = 42;
12513
12514
12515 fn main() {
12516 println!("hello");
12517
12518 println!("world");
12519 }"#
12520 .unindent(),
12521 );
12522}
12523
12524async fn setup_indent_guides_editor(
12525 text: &str,
12526 cx: &mut gpui::TestAppContext,
12527) -> (BufferId, EditorTestContext) {
12528 init_test(cx, |_| {});
12529
12530 let mut cx = EditorTestContext::new(cx).await;
12531
12532 let buffer_id = cx.update_editor(|editor, cx| {
12533 editor.set_text(text, cx);
12534 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12535 let buffer_id = buffer_ids[0];
12536 buffer_id
12537 });
12538
12539 (buffer_id, cx)
12540}
12541
12542fn assert_indent_guides(
12543 range: Range<u32>,
12544 expected: Vec<IndentGuide>,
12545 active_indices: Option<Vec<usize>>,
12546 cx: &mut EditorTestContext,
12547) {
12548 let indent_guides = cx.update_editor(|editor, cx| {
12549 let snapshot = editor.snapshot(cx).display_snapshot;
12550 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12551 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12552 true,
12553 &snapshot,
12554 cx,
12555 );
12556
12557 indent_guides.sort_by(|a, b| {
12558 a.depth.cmp(&b.depth).then(
12559 a.start_row
12560 .cmp(&b.start_row)
12561 .then(a.end_row.cmp(&b.end_row)),
12562 )
12563 });
12564 indent_guides
12565 });
12566
12567 if let Some(expected) = active_indices {
12568 let active_indices = cx.update_editor(|editor, cx| {
12569 let snapshot = editor.snapshot(cx).display_snapshot;
12570 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
12571 });
12572
12573 assert_eq!(
12574 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
12575 expected,
12576 "Active indent guide indices do not match"
12577 );
12578 }
12579
12580 let expected: Vec<_> = expected
12581 .into_iter()
12582 .map(|guide| MultiBufferIndentGuide {
12583 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
12584 buffer: guide,
12585 })
12586 .collect();
12587
12588 assert_eq!(indent_guides, expected, "Indent guides do not match");
12589}
12590
12591fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
12592 IndentGuide {
12593 buffer_id,
12594 start_row,
12595 end_row,
12596 depth,
12597 tab_size: 4,
12598 settings: IndentGuideSettings {
12599 enabled: true,
12600 line_width: 1,
12601 active_line_width: 1,
12602 ..Default::default()
12603 },
12604 }
12605}
12606
12607#[gpui::test]
12608async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12609 let (buffer_id, mut cx) = setup_indent_guides_editor(
12610 &"
12611 fn main() {
12612 let a = 1;
12613 }"
12614 .unindent(),
12615 cx,
12616 )
12617 .await;
12618
12619 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12620}
12621
12622#[gpui::test]
12623async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
12624 let (buffer_id, mut cx) = setup_indent_guides_editor(
12625 &"
12626 fn main() {
12627 let a = 1;
12628 let b = 2;
12629 }"
12630 .unindent(),
12631 cx,
12632 )
12633 .await;
12634
12635 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
12636}
12637
12638#[gpui::test]
12639async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
12640 let (buffer_id, mut cx) = setup_indent_guides_editor(
12641 &"
12642 fn main() {
12643 let a = 1;
12644 if a == 3 {
12645 let b = 2;
12646 } else {
12647 let c = 3;
12648 }
12649 }"
12650 .unindent(),
12651 cx,
12652 )
12653 .await;
12654
12655 assert_indent_guides(
12656 0..8,
12657 vec![
12658 indent_guide(buffer_id, 1, 6, 0),
12659 indent_guide(buffer_id, 3, 3, 1),
12660 indent_guide(buffer_id, 5, 5, 1),
12661 ],
12662 None,
12663 &mut cx,
12664 );
12665}
12666
12667#[gpui::test]
12668async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
12669 let (buffer_id, mut cx) = setup_indent_guides_editor(
12670 &"
12671 fn main() {
12672 let a = 1;
12673 let b = 2;
12674 let c = 3;
12675 }"
12676 .unindent(),
12677 cx,
12678 )
12679 .await;
12680
12681 assert_indent_guides(
12682 0..5,
12683 vec![
12684 indent_guide(buffer_id, 1, 3, 0),
12685 indent_guide(buffer_id, 2, 2, 1),
12686 ],
12687 None,
12688 &mut cx,
12689 );
12690}
12691
12692#[gpui::test]
12693async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
12694 let (buffer_id, mut cx) = setup_indent_guides_editor(
12695 &"
12696 fn main() {
12697 let a = 1;
12698
12699 let c = 3;
12700 }"
12701 .unindent(),
12702 cx,
12703 )
12704 .await;
12705
12706 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
12707}
12708
12709#[gpui::test]
12710async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
12711 let (buffer_id, mut cx) = setup_indent_guides_editor(
12712 &"
12713 fn main() {
12714 let a = 1;
12715
12716 let c = 3;
12717
12718 if a == 3 {
12719 let b = 2;
12720 } else {
12721 let c = 3;
12722 }
12723 }"
12724 .unindent(),
12725 cx,
12726 )
12727 .await;
12728
12729 assert_indent_guides(
12730 0..11,
12731 vec![
12732 indent_guide(buffer_id, 1, 9, 0),
12733 indent_guide(buffer_id, 6, 6, 1),
12734 indent_guide(buffer_id, 8, 8, 1),
12735 ],
12736 None,
12737 &mut cx,
12738 );
12739}
12740
12741#[gpui::test]
12742async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
12743 let (buffer_id, mut cx) = setup_indent_guides_editor(
12744 &"
12745 fn main() {
12746 let a = 1;
12747
12748 let c = 3;
12749
12750 if a == 3 {
12751 let b = 2;
12752 } else {
12753 let c = 3;
12754 }
12755 }"
12756 .unindent(),
12757 cx,
12758 )
12759 .await;
12760
12761 assert_indent_guides(
12762 1..11,
12763 vec![
12764 indent_guide(buffer_id, 1, 9, 0),
12765 indent_guide(buffer_id, 6, 6, 1),
12766 indent_guide(buffer_id, 8, 8, 1),
12767 ],
12768 None,
12769 &mut cx,
12770 );
12771}
12772
12773#[gpui::test]
12774async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
12775 let (buffer_id, mut cx) = setup_indent_guides_editor(
12776 &"
12777 fn main() {
12778 let a = 1;
12779
12780 let c = 3;
12781
12782 if a == 3 {
12783 let b = 2;
12784 } else {
12785 let c = 3;
12786 }
12787 }"
12788 .unindent(),
12789 cx,
12790 )
12791 .await;
12792
12793 assert_indent_guides(
12794 1..10,
12795 vec![
12796 indent_guide(buffer_id, 1, 9, 0),
12797 indent_guide(buffer_id, 6, 6, 1),
12798 indent_guide(buffer_id, 8, 8, 1),
12799 ],
12800 None,
12801 &mut cx,
12802 );
12803}
12804
12805#[gpui::test]
12806async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
12807 let (buffer_id, mut cx) = setup_indent_guides_editor(
12808 &"
12809 block1
12810 block2
12811 block3
12812 block4
12813 block2
12814 block1
12815 block1"
12816 .unindent(),
12817 cx,
12818 )
12819 .await;
12820
12821 assert_indent_guides(
12822 1..10,
12823 vec![
12824 indent_guide(buffer_id, 1, 4, 0),
12825 indent_guide(buffer_id, 2, 3, 1),
12826 indent_guide(buffer_id, 3, 3, 2),
12827 ],
12828 None,
12829 &mut cx,
12830 );
12831}
12832
12833#[gpui::test]
12834async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
12835 let (buffer_id, mut cx) = setup_indent_guides_editor(
12836 &"
12837 block1
12838 block2
12839 block3
12840
12841 block1
12842 block1"
12843 .unindent(),
12844 cx,
12845 )
12846 .await;
12847
12848 assert_indent_guides(
12849 0..6,
12850 vec![
12851 indent_guide(buffer_id, 1, 2, 0),
12852 indent_guide(buffer_id, 2, 2, 1),
12853 ],
12854 None,
12855 &mut cx,
12856 );
12857}
12858
12859#[gpui::test]
12860async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
12861 let (buffer_id, mut cx) = setup_indent_guides_editor(
12862 &"
12863 block1
12864
12865
12866
12867 block2
12868 "
12869 .unindent(),
12870 cx,
12871 )
12872 .await;
12873
12874 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12875}
12876
12877#[gpui::test]
12878async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
12879 let (buffer_id, mut cx) = setup_indent_guides_editor(
12880 &"
12881 def a:
12882 \tb = 3
12883 \tif True:
12884 \t\tc = 4
12885 \t\td = 5
12886 \tprint(b)
12887 "
12888 .unindent(),
12889 cx,
12890 )
12891 .await;
12892
12893 assert_indent_guides(
12894 0..6,
12895 vec![
12896 indent_guide(buffer_id, 1, 6, 0),
12897 indent_guide(buffer_id, 3, 4, 1),
12898 ],
12899 None,
12900 &mut cx,
12901 );
12902}
12903
12904#[gpui::test]
12905async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12906 let (buffer_id, mut cx) = setup_indent_guides_editor(
12907 &"
12908 fn main() {
12909 let a = 1;
12910 }"
12911 .unindent(),
12912 cx,
12913 )
12914 .await;
12915
12916 cx.update_editor(|editor, cx| {
12917 editor.change_selections(None, cx, |s| {
12918 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12919 });
12920 });
12921
12922 assert_indent_guides(
12923 0..3,
12924 vec![indent_guide(buffer_id, 1, 1, 0)],
12925 Some(vec![0]),
12926 &mut cx,
12927 );
12928}
12929
12930#[gpui::test]
12931async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
12932 let (buffer_id, mut cx) = setup_indent_guides_editor(
12933 &"
12934 fn main() {
12935 if 1 == 2 {
12936 let a = 1;
12937 }
12938 }"
12939 .unindent(),
12940 cx,
12941 )
12942 .await;
12943
12944 cx.update_editor(|editor, cx| {
12945 editor.change_selections(None, cx, |s| {
12946 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12947 });
12948 });
12949
12950 assert_indent_guides(
12951 0..4,
12952 vec![
12953 indent_guide(buffer_id, 1, 3, 0),
12954 indent_guide(buffer_id, 2, 2, 1),
12955 ],
12956 Some(vec![1]),
12957 &mut cx,
12958 );
12959
12960 cx.update_editor(|editor, cx| {
12961 editor.change_selections(None, cx, |s| {
12962 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12963 });
12964 });
12965
12966 assert_indent_guides(
12967 0..4,
12968 vec![
12969 indent_guide(buffer_id, 1, 3, 0),
12970 indent_guide(buffer_id, 2, 2, 1),
12971 ],
12972 Some(vec![1]),
12973 &mut cx,
12974 );
12975
12976 cx.update_editor(|editor, cx| {
12977 editor.change_selections(None, cx, |s| {
12978 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
12979 });
12980 });
12981
12982 assert_indent_guides(
12983 0..4,
12984 vec![
12985 indent_guide(buffer_id, 1, 3, 0),
12986 indent_guide(buffer_id, 2, 2, 1),
12987 ],
12988 Some(vec![0]),
12989 &mut cx,
12990 );
12991}
12992
12993#[gpui::test]
12994async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
12995 let (buffer_id, mut cx) = setup_indent_guides_editor(
12996 &"
12997 fn main() {
12998 let a = 1;
12999
13000 let b = 2;
13001 }"
13002 .unindent(),
13003 cx,
13004 )
13005 .await;
13006
13007 cx.update_editor(|editor, cx| {
13008 editor.change_selections(None, cx, |s| {
13009 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13010 });
13011 });
13012
13013 assert_indent_guides(
13014 0..5,
13015 vec![indent_guide(buffer_id, 1, 3, 0)],
13016 Some(vec![0]),
13017 &mut cx,
13018 );
13019}
13020
13021#[gpui::test]
13022async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13023 let (buffer_id, mut cx) = setup_indent_guides_editor(
13024 &"
13025 def m:
13026 a = 1
13027 pass"
13028 .unindent(),
13029 cx,
13030 )
13031 .await;
13032
13033 cx.update_editor(|editor, cx| {
13034 editor.change_selections(None, cx, |s| {
13035 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13036 });
13037 });
13038
13039 assert_indent_guides(
13040 0..3,
13041 vec![indent_guide(buffer_id, 1, 2, 0)],
13042 Some(vec![0]),
13043 &mut cx,
13044 );
13045}
13046
13047#[gpui::test]
13048fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13049 init_test(cx, |_| {});
13050
13051 let editor = cx.add_window(|cx| {
13052 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13053 build_editor(buffer, cx)
13054 });
13055
13056 let render_args = Arc::new(Mutex::new(None));
13057 let snapshot = editor
13058 .update(cx, |editor, cx| {
13059 let snapshot = editor.buffer().read(cx).snapshot(cx);
13060 let range =
13061 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13062
13063 struct RenderArgs {
13064 row: MultiBufferRow,
13065 folded: bool,
13066 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13067 }
13068
13069 let crease = Crease::new(
13070 range,
13071 FoldPlaceholder::test(),
13072 {
13073 let toggle_callback = render_args.clone();
13074 move |row, folded, callback, _cx| {
13075 *toggle_callback.lock() = Some(RenderArgs {
13076 row,
13077 folded,
13078 callback,
13079 });
13080 div()
13081 }
13082 },
13083 |_row, _folded, _cx| div(),
13084 );
13085
13086 editor.insert_creases(Some(crease), cx);
13087 let snapshot = editor.snapshot(cx);
13088 let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13089 snapshot
13090 })
13091 .unwrap();
13092
13093 let render_args = render_args.lock().take().unwrap();
13094 assert_eq!(render_args.row, MultiBufferRow(1));
13095 assert_eq!(render_args.folded, false);
13096 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13097
13098 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13099 .unwrap();
13100 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13101 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13102
13103 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13104 .unwrap();
13105 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13106 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13107}
13108
13109#[gpui::test]
13110async fn test_input_text(cx: &mut gpui::TestAppContext) {
13111 init_test(cx, |_| {});
13112 let mut cx = EditorTestContext::new(cx).await;
13113
13114 cx.set_state(
13115 &r#"ˇone
13116 two
13117
13118 three
13119 fourˇ
13120 five
13121
13122 siˇx"#
13123 .unindent(),
13124 );
13125
13126 cx.dispatch_action(HandleInput(String::new()));
13127 cx.assert_editor_state(
13128 &r#"ˇone
13129 two
13130
13131 three
13132 fourˇ
13133 five
13134
13135 siˇx"#
13136 .unindent(),
13137 );
13138
13139 cx.dispatch_action(HandleInput("AAAA".to_string()));
13140 cx.assert_editor_state(
13141 &r#"AAAAˇone
13142 two
13143
13144 three
13145 fourAAAAˇ
13146 five
13147
13148 siAAAAˇx"#
13149 .unindent(),
13150 );
13151}
13152
13153#[gpui::test]
13154async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13155 init_test(cx, |_| {});
13156
13157 let mut cx = EditorTestContext::new(cx).await;
13158 cx.set_state(
13159 r#"let foo = 1;
13160let foo = 2;
13161let foo = 3;
13162let fooˇ = 4;
13163let foo = 5;
13164let foo = 6;
13165let foo = 7;
13166let foo = 8;
13167let foo = 9;
13168let foo = 10;
13169let foo = 11;
13170let foo = 12;
13171let foo = 13;
13172let foo = 14;
13173let foo = 15;"#,
13174 );
13175
13176 cx.update_editor(|e, cx| {
13177 assert_eq!(
13178 e.next_scroll_position,
13179 NextScrollCursorCenterTopBottom::Center,
13180 "Default next scroll direction is center",
13181 );
13182
13183 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13184 assert_eq!(
13185 e.next_scroll_position,
13186 NextScrollCursorCenterTopBottom::Top,
13187 "After center, next scroll direction should be top",
13188 );
13189
13190 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13191 assert_eq!(
13192 e.next_scroll_position,
13193 NextScrollCursorCenterTopBottom::Bottom,
13194 "After top, next scroll direction should be bottom",
13195 );
13196
13197 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13198 assert_eq!(
13199 e.next_scroll_position,
13200 NextScrollCursorCenterTopBottom::Center,
13201 "After bottom, scrolling should start over",
13202 );
13203
13204 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13205 assert_eq!(
13206 e.next_scroll_position,
13207 NextScrollCursorCenterTopBottom::Top,
13208 "Scrolling continues if retriggered fast enough"
13209 );
13210 });
13211
13212 cx.executor()
13213 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13214 cx.executor().run_until_parked();
13215 cx.update_editor(|e, _| {
13216 assert_eq!(
13217 e.next_scroll_position,
13218 NextScrollCursorCenterTopBottom::Center,
13219 "If scrolling is not triggered fast enough, it should reset"
13220 );
13221 });
13222}
13223
13224fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13225 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13226 point..point
13227}
13228
13229fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13230 let (text, ranges) = marked_text_ranges(marked_text, true);
13231 assert_eq!(view.text(cx), text);
13232 assert_eq!(
13233 view.selections.ranges(cx),
13234 ranges,
13235 "Assert selections are {}",
13236 marked_text
13237 );
13238}
13239
13240pub fn handle_signature_help_request(
13241 cx: &mut EditorLspTestContext,
13242 mocked_response: lsp::SignatureHelp,
13243) -> impl Future<Output = ()> {
13244 let mut request =
13245 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13246 let mocked_response = mocked_response.clone();
13247 async move { Ok(Some(mocked_response)) }
13248 });
13249
13250 async move {
13251 request.next().await;
13252 }
13253}
13254
13255/// Handle completion request passing a marked string specifying where the completion
13256/// should be triggered from using '|' character, what range should be replaced, and what completions
13257/// should be returned using '<' and '>' to delimit the range
13258pub fn handle_completion_request(
13259 cx: &mut EditorLspTestContext,
13260 marked_string: &str,
13261 completions: Vec<&'static str>,
13262 counter: Arc<AtomicUsize>,
13263) -> impl Future<Output = ()> {
13264 let complete_from_marker: TextRangeMarker = '|'.into();
13265 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13266 let (_, mut marked_ranges) = marked_text_ranges_by(
13267 marked_string,
13268 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13269 );
13270
13271 let complete_from_position =
13272 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13273 let replace_range =
13274 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13275
13276 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13277 let completions = completions.clone();
13278 counter.fetch_add(1, atomic::Ordering::Release);
13279 async move {
13280 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13281 assert_eq!(
13282 params.text_document_position.position,
13283 complete_from_position
13284 );
13285 Ok(Some(lsp::CompletionResponse::Array(
13286 completions
13287 .iter()
13288 .map(|completion_text| lsp::CompletionItem {
13289 label: completion_text.to_string(),
13290 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13291 range: replace_range,
13292 new_text: completion_text.to_string(),
13293 })),
13294 ..Default::default()
13295 })
13296 .collect(),
13297 )))
13298 }
13299 });
13300
13301 async move {
13302 request.next().await;
13303 }
13304}
13305
13306fn handle_resolve_completion_request(
13307 cx: &mut EditorLspTestContext,
13308 edits: Option<Vec<(&'static str, &'static str)>>,
13309) -> impl Future<Output = ()> {
13310 let edits = edits.map(|edits| {
13311 edits
13312 .iter()
13313 .map(|(marked_string, new_text)| {
13314 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13315 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13316 lsp::TextEdit::new(replace_range, new_text.to_string())
13317 })
13318 .collect::<Vec<_>>()
13319 });
13320
13321 let mut request =
13322 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13323 let edits = edits.clone();
13324 async move {
13325 Ok(lsp::CompletionItem {
13326 additional_text_edits: edits,
13327 ..Default::default()
13328 })
13329 }
13330 });
13331
13332 async move {
13333 request.next().await;
13334 }
13335}
13336
13337pub(crate) fn update_test_language_settings(
13338 cx: &mut TestAppContext,
13339 f: impl Fn(&mut AllLanguageSettingsContent),
13340) {
13341 _ = cx.update(|cx| {
13342 SettingsStore::update_global(cx, |store, cx| {
13343 store.update_user_settings::<AllLanguageSettings>(cx, f);
13344 });
13345 });
13346}
13347
13348pub(crate) fn update_test_project_settings(
13349 cx: &mut TestAppContext,
13350 f: impl Fn(&mut ProjectSettings),
13351) {
13352 _ = cx.update(|cx| {
13353 SettingsStore::update_global(cx, |store, cx| {
13354 store.update_user_settings::<ProjectSettings>(cx, f);
13355 });
13356 });
13357}
13358
13359pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
13360 _ = cx.update(|cx| {
13361 assets::Assets.load_test_fonts(cx);
13362 let store = SettingsStore::test(cx);
13363 cx.set_global(store);
13364 theme::init(theme::LoadThemes::JustBase, cx);
13365 release_channel::init(SemanticVersion::default(), cx);
13366 client::init_settings(cx);
13367 language::init(cx);
13368 Project::init_settings(cx);
13369 workspace::init_settings(cx);
13370 crate::init(cx);
13371 });
13372
13373 update_test_language_settings(cx, f);
13374}
13375
13376pub(crate) fn rust_lang() -> Arc<Language> {
13377 Arc::new(Language::new(
13378 LanguageConfig {
13379 name: "Rust".into(),
13380 matcher: LanguageMatcher {
13381 path_suffixes: vec!["rs".to_string()],
13382 ..Default::default()
13383 },
13384 ..Default::default()
13385 },
13386 Some(tree_sitter_rust::language()),
13387 ))
13388}
13389
13390#[track_caller]
13391fn assert_hunk_revert(
13392 not_reverted_text_with_selections: &str,
13393 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
13394 expected_reverted_text_with_selections: &str,
13395 base_text: &str,
13396 cx: &mut EditorLspTestContext,
13397) {
13398 cx.set_state(not_reverted_text_with_selections);
13399 cx.update_editor(|editor, cx| {
13400 editor
13401 .buffer()
13402 .read(cx)
13403 .as_singleton()
13404 .unwrap()
13405 .update(cx, |buffer, cx| {
13406 buffer.set_diff_base(Some(base_text.into()), cx);
13407 });
13408 });
13409 cx.executor().run_until_parked();
13410
13411 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
13412 let snapshot = editor.buffer().read(cx).snapshot(cx);
13413 let reverted_hunk_statuses = snapshot
13414 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
13415 .map(|hunk| hunk_status(&hunk))
13416 .collect::<Vec<_>>();
13417
13418 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
13419 reverted_hunk_statuses
13420 });
13421 cx.executor().run_until_parked();
13422 cx.assert_editor_state(expected_reverted_text_with_selections);
13423 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
13424}