1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_hunks,
6 editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext,
7 expanded_hunks, expanded_hunks_background_highlights, select_ranges,
8 },
9 JoinLines,
10};
11use futures::StreamExt;
12use gpui::{
13 div, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds,
14 WindowOptions,
15};
16use indoc::indoc;
17use language::{
18 language_settings::{
19 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
20 },
21 BracketPairConfig,
22 Capability::ReadWrite,
23 FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher, Override,
24 ParsedMarkdown, Point,
25};
26use language_settings::{Formatter, FormatterList, IndentGuideSettings};
27use multi_buffer::MultiBufferIndentGuide;
28use parking_lot::Mutex;
29use project::FakeFs;
30use project::{
31 lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
32 project_settings::{LspSettings, ProjectSettings},
33};
34use serde_json::{self, json};
35use std::sync::atomic;
36use std::sync::atomic::AtomicUsize;
37use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
38use unindent::Unindent;
39use util::{
40 assert_set_eq,
41 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
42};
43use workspace::{
44 item::{FollowEvent, FollowableItem, Item, ItemHandle},
45 NavigationEntry, ViewId,
46};
47
48#[gpui::test]
49fn test_edit_events(cx: &mut TestAppContext) {
50 init_test(cx, |_| {});
51
52 let buffer = cx.new_model(|cx| {
53 let mut buffer = language::Buffer::local("123456", cx);
54 buffer.set_group_interval(Duration::from_secs(1));
55 buffer
56 });
57
58 let events = Rc::new(RefCell::new(Vec::new()));
59 let editor1 = cx.add_window({
60 let events = events.clone();
61 |cx| {
62 let view = cx.view().clone();
63 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
64 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
65 EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
66 _ => {}
67 })
68 .detach();
69 Editor::for_buffer(buffer.clone(), None, cx)
70 }
71 });
72
73 let editor2 = cx.add_window({
74 let events = events.clone();
75 |cx| {
76 cx.subscribe(
77 &cx.view().clone(),
78 move |_, _, event: &EditorEvent, _| match event {
79 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
80 EditorEvent::BufferEdited => {
81 events.borrow_mut().push(("editor2", "buffer edited"))
82 }
83 _ => {}
84 },
85 )
86 .detach();
87 Editor::for_buffer(buffer.clone(), None, cx)
88 }
89 });
90
91 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
92
93 // Mutating editor 1 will emit an `Edited` event only for that editor.
94 _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
95 assert_eq!(
96 mem::take(&mut *events.borrow_mut()),
97 [
98 ("editor1", "edited"),
99 ("editor1", "buffer edited"),
100 ("editor2", "buffer edited"),
101 ]
102 );
103
104 // Mutating editor 2 will emit an `Edited` event only for that editor.
105 _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
106 assert_eq!(
107 mem::take(&mut *events.borrow_mut()),
108 [
109 ("editor2", "edited"),
110 ("editor1", "buffer edited"),
111 ("editor2", "buffer edited"),
112 ]
113 );
114
115 // Undoing on editor 1 will emit an `Edited` event only for that editor.
116 _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
117 assert_eq!(
118 mem::take(&mut *events.borrow_mut()),
119 [
120 ("editor1", "edited"),
121 ("editor1", "buffer edited"),
122 ("editor2", "buffer edited"),
123 ]
124 );
125
126 // Redoing on editor 1 will emit an `Edited` event only for that editor.
127 _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
128 assert_eq!(
129 mem::take(&mut *events.borrow_mut()),
130 [
131 ("editor1", "edited"),
132 ("editor1", "buffer edited"),
133 ("editor2", "buffer edited"),
134 ]
135 );
136
137 // Undoing on editor 2 will emit an `Edited` event only for that editor.
138 _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
139 assert_eq!(
140 mem::take(&mut *events.borrow_mut()),
141 [
142 ("editor2", "edited"),
143 ("editor1", "buffer edited"),
144 ("editor2", "buffer edited"),
145 ]
146 );
147
148 // Redoing on editor 2 will emit an `Edited` event only for that editor.
149 _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
150 assert_eq!(
151 mem::take(&mut *events.borrow_mut()),
152 [
153 ("editor2", "edited"),
154 ("editor1", "buffer edited"),
155 ("editor2", "buffer edited"),
156 ]
157 );
158
159 // No event is emitted when the mutation is a no-op.
160 _ = editor2.update(cx, |editor, cx| {
161 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
162
163 editor.backspace(&Backspace, cx);
164 });
165 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
166}
167
168#[gpui::test]
169fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
170 init_test(cx, |_| {});
171
172 let mut now = Instant::now();
173 let buffer = cx.new_model(|cx| language::Buffer::local("123456", cx));
174 let group_interval = buffer.update(cx, |buffer, _| buffer.transaction_group_interval());
175 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
176 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
177
178 _ = editor.update(cx, |editor, cx| {
179 editor.start_transaction_at(now, cx);
180 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
181
182 editor.insert("cd", cx);
183 editor.end_transaction_at(now, cx);
184 assert_eq!(editor.text(cx), "12cd56");
185 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
186
187 editor.start_transaction_at(now, cx);
188 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
189 editor.insert("e", cx);
190 editor.end_transaction_at(now, cx);
191 assert_eq!(editor.text(cx), "12cde6");
192 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
193
194 now += group_interval + Duration::from_millis(1);
195 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
196
197 // Simulate an edit in another editor
198 _ = buffer.update(cx, |buffer, cx| {
199 buffer.start_transaction_at(now, cx);
200 buffer.edit([(0..1, "a")], None, cx);
201 buffer.edit([(1..1, "b")], None, cx);
202 buffer.end_transaction_at(now, cx);
203 });
204
205 assert_eq!(editor.text(cx), "ab2cde6");
206 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
207
208 // Last transaction happened past the group interval in a different editor.
209 // Undo it individually and don't restore selections.
210 editor.undo(&Undo, cx);
211 assert_eq!(editor.text(cx), "12cde6");
212 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
213
214 // First two transactions happened within the group interval in this editor.
215 // Undo them together and restore selections.
216 editor.undo(&Undo, cx);
217 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
218 assert_eq!(editor.text(cx), "123456");
219 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
220
221 // Redo the first two transactions together.
222 editor.redo(&Redo, cx);
223 assert_eq!(editor.text(cx), "12cde6");
224 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
225
226 // Redo the last transaction on its own.
227 editor.redo(&Redo, cx);
228 assert_eq!(editor.text(cx), "ab2cde6");
229 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
230
231 // Test empty transactions.
232 editor.start_transaction_at(now, cx);
233 editor.end_transaction_at(now, cx);
234 editor.undo(&Undo, cx);
235 assert_eq!(editor.text(cx), "12cde6");
236 });
237}
238
239#[gpui::test]
240fn test_ime_composition(cx: &mut TestAppContext) {
241 init_test(cx, |_| {});
242
243 let buffer = cx.new_model(|cx| {
244 let mut buffer = language::Buffer::local("abcde", cx);
245 // Ensure automatic grouping doesn't occur.
246 buffer.set_group_interval(Duration::ZERO);
247 buffer
248 });
249
250 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
251 cx.add_window(|cx| {
252 let mut editor = build_editor(buffer.clone(), cx);
253
254 // Start a new IME composition.
255 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
256 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
257 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
258 assert_eq!(editor.text(cx), "äbcde");
259 assert_eq!(
260 editor.marked_text_ranges(cx),
261 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
262 );
263
264 // Finalize IME composition.
265 editor.replace_text_in_range(None, "ā", cx);
266 assert_eq!(editor.text(cx), "ābcde");
267 assert_eq!(editor.marked_text_ranges(cx), None);
268
269 // IME composition edits are grouped and are undone/redone at once.
270 editor.undo(&Default::default(), cx);
271 assert_eq!(editor.text(cx), "abcde");
272 assert_eq!(editor.marked_text_ranges(cx), None);
273 editor.redo(&Default::default(), cx);
274 assert_eq!(editor.text(cx), "ābcde");
275 assert_eq!(editor.marked_text_ranges(cx), None);
276
277 // Start a new IME composition.
278 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
279 assert_eq!(
280 editor.marked_text_ranges(cx),
281 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
282 );
283
284 // Undoing during an IME composition cancels it.
285 editor.undo(&Default::default(), cx);
286 assert_eq!(editor.text(cx), "ābcde");
287 assert_eq!(editor.marked_text_ranges(cx), None);
288
289 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
290 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
291 assert_eq!(editor.text(cx), "ābcdè");
292 assert_eq!(
293 editor.marked_text_ranges(cx),
294 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
295 );
296
297 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
298 editor.replace_text_in_range(Some(4..999), "ę", cx);
299 assert_eq!(editor.text(cx), "ābcdę");
300 assert_eq!(editor.marked_text_ranges(cx), None);
301
302 // Start a new IME composition with multiple cursors.
303 editor.change_selections(None, cx, |s| {
304 s.select_ranges([
305 OffsetUtf16(1)..OffsetUtf16(1),
306 OffsetUtf16(3)..OffsetUtf16(3),
307 OffsetUtf16(5)..OffsetUtf16(5),
308 ])
309 });
310 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
311 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
312 assert_eq!(
313 editor.marked_text_ranges(cx),
314 Some(vec![
315 OffsetUtf16(0)..OffsetUtf16(3),
316 OffsetUtf16(4)..OffsetUtf16(7),
317 OffsetUtf16(8)..OffsetUtf16(11)
318 ])
319 );
320
321 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
322 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
323 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
324 assert_eq!(
325 editor.marked_text_ranges(cx),
326 Some(vec![
327 OffsetUtf16(1)..OffsetUtf16(2),
328 OffsetUtf16(5)..OffsetUtf16(6),
329 OffsetUtf16(9)..OffsetUtf16(10)
330 ])
331 );
332
333 // Finalize IME composition with multiple cursors.
334 editor.replace_text_in_range(Some(9..10), "2", cx);
335 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
336 assert_eq!(editor.marked_text_ranges(cx), None);
337
338 editor
339 });
340}
341
342#[gpui::test]
343fn test_selection_with_mouse(cx: &mut TestAppContext) {
344 init_test(cx, |_| {});
345
346 let editor = cx.add_window(|cx| {
347 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
348 build_editor(buffer, cx)
349 });
350
351 _ = editor.update(cx, |view, cx| {
352 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
353 });
354 assert_eq!(
355 editor
356 .update(cx, |view, cx| view.selections.display_ranges(cx))
357 .unwrap(),
358 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
359 );
360
361 _ = editor.update(cx, |view, cx| {
362 view.update_selection(
363 DisplayPoint::new(DisplayRow(3), 3),
364 0,
365 gpui::Point::<f32>::default(),
366 cx,
367 );
368 });
369
370 assert_eq!(
371 editor
372 .update(cx, |view, cx| view.selections.display_ranges(cx))
373 .unwrap(),
374 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
375 );
376
377 _ = editor.update(cx, |view, cx| {
378 view.update_selection(
379 DisplayPoint::new(DisplayRow(1), 1),
380 0,
381 gpui::Point::<f32>::default(),
382 cx,
383 );
384 });
385
386 assert_eq!(
387 editor
388 .update(cx, |view, cx| view.selections.display_ranges(cx))
389 .unwrap(),
390 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
391 );
392
393 _ = editor.update(cx, |view, cx| {
394 view.end_selection(cx);
395 view.update_selection(
396 DisplayPoint::new(DisplayRow(3), 3),
397 0,
398 gpui::Point::<f32>::default(),
399 cx,
400 );
401 });
402
403 assert_eq!(
404 editor
405 .update(cx, |view, cx| view.selections.display_ranges(cx))
406 .unwrap(),
407 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
408 );
409
410 _ = editor.update(cx, |view, cx| {
411 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
412 view.update_selection(
413 DisplayPoint::new(DisplayRow(0), 0),
414 0,
415 gpui::Point::<f32>::default(),
416 cx,
417 );
418 });
419
420 assert_eq!(
421 editor
422 .update(cx, |view, cx| view.selections.display_ranges(cx))
423 .unwrap(),
424 [
425 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
426 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
427 ]
428 );
429
430 _ = editor.update(cx, |view, cx| {
431 view.end_selection(cx);
432 });
433
434 assert_eq!(
435 editor
436 .update(cx, |view, cx| view.selections.display_ranges(cx))
437 .unwrap(),
438 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
439 );
440}
441
442#[gpui::test]
443fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
444 init_test(cx, |_| {});
445
446 let editor = cx.add_window(|cx| {
447 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
448 build_editor(buffer, cx)
449 });
450
451 _ = editor.update(cx, |view, cx| {
452 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
453 });
454
455 _ = editor.update(cx, |view, cx| {
456 view.end_selection(cx);
457 });
458
459 _ = editor.update(cx, |view, cx| {
460 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
461 });
462
463 _ = editor.update(cx, |view, cx| {
464 view.end_selection(cx);
465 });
466
467 assert_eq!(
468 editor
469 .update(cx, |view, cx| view.selections.display_ranges(cx))
470 .unwrap(),
471 [
472 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
473 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
474 ]
475 );
476
477 _ = editor.update(cx, |view, cx| {
478 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
479 });
480
481 _ = editor.update(cx, |view, cx| {
482 view.end_selection(cx);
483 });
484
485 assert_eq!(
486 editor
487 .update(cx, |view, cx| view.selections.display_ranges(cx))
488 .unwrap(),
489 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
490 );
491}
492
493#[gpui::test]
494fn test_canceling_pending_selection(cx: &mut TestAppContext) {
495 init_test(cx, |_| {});
496
497 let view = cx.add_window(|cx| {
498 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
499 build_editor(buffer, cx)
500 });
501
502 _ = view.update(cx, |view, cx| {
503 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
504 assert_eq!(
505 view.selections.display_ranges(cx),
506 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
507 );
508 });
509
510 _ = view.update(cx, |view, cx| {
511 view.update_selection(
512 DisplayPoint::new(DisplayRow(3), 3),
513 0,
514 gpui::Point::<f32>::default(),
515 cx,
516 );
517 assert_eq!(
518 view.selections.display_ranges(cx),
519 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
520 );
521 });
522
523 _ = view.update(cx, |view, cx| {
524 view.cancel(&Cancel, cx);
525 view.update_selection(
526 DisplayPoint::new(DisplayRow(1), 1),
527 0,
528 gpui::Point::<f32>::default(),
529 cx,
530 );
531 assert_eq!(
532 view.selections.display_ranges(cx),
533 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
534 );
535 });
536}
537
538#[gpui::test]
539fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
540 init_test(cx, |_| {});
541
542 let view = cx.add_window(|cx| {
543 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
544 build_editor(buffer, cx)
545 });
546
547 _ = view.update(cx, |view, cx| {
548 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
549 assert_eq!(
550 view.selections.display_ranges(cx),
551 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
552 );
553
554 view.move_down(&Default::default(), cx);
555 assert_eq!(
556 view.selections.display_ranges(cx),
557 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
558 );
559
560 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
561 assert_eq!(
562 view.selections.display_ranges(cx),
563 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
564 );
565
566 view.move_up(&Default::default(), cx);
567 assert_eq!(
568 view.selections.display_ranges(cx),
569 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
570 );
571 });
572}
573
574#[gpui::test]
575fn test_clone(cx: &mut TestAppContext) {
576 init_test(cx, |_| {});
577
578 let (text, selection_ranges) = marked_text_ranges(
579 indoc! {"
580 one
581 two
582 threeˇ
583 four
584 fiveˇ
585 "},
586 true,
587 );
588
589 let editor = cx.add_window(|cx| {
590 let buffer = MultiBuffer::build_simple(&text, cx);
591 build_editor(buffer, cx)
592 });
593
594 _ = editor.update(cx, |editor, cx| {
595 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
596 editor.fold_ranges(
597 [
598 (Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
599 (Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
600 ],
601 true,
602 cx,
603 );
604 });
605
606 let cloned_editor = editor
607 .update(cx, |editor, cx| {
608 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
609 })
610 .unwrap()
611 .unwrap();
612
613 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
614 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
615
616 assert_eq!(
617 cloned_editor
618 .update(cx, |e, cx| e.display_text(cx))
619 .unwrap(),
620 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
621 );
622 assert_eq!(
623 cloned_snapshot
624 .folds_in_range(0..text.len())
625 .collect::<Vec<_>>(),
626 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
627 );
628 assert_set_eq!(
629 cloned_editor
630 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
631 .unwrap(),
632 editor
633 .update(cx, |editor, cx| editor.selections.ranges(cx))
634 .unwrap()
635 );
636 assert_set_eq!(
637 cloned_editor
638 .update(cx, |e, cx| e.selections.display_ranges(cx))
639 .unwrap(),
640 editor
641 .update(cx, |e, cx| e.selections.display_ranges(cx))
642 .unwrap()
643 );
644}
645
646#[gpui::test]
647async fn test_navigation_history(cx: &mut TestAppContext) {
648 init_test(cx, |_| {});
649
650 use workspace::item::Item;
651
652 let fs = FakeFs::new(cx.executor());
653 let project = Project::test(fs, [], cx).await;
654 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
655 let pane = workspace
656 .update(cx, |workspace, _| workspace.active_pane().clone())
657 .unwrap();
658
659 _ = workspace.update(cx, |_v, cx| {
660 cx.new_view(|cx| {
661 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
662 let mut editor = build_editor(buffer.clone(), cx);
663 let handle = cx.view();
664 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
665
666 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
667 editor.nav_history.as_mut().unwrap().pop_backward(cx)
668 }
669
670 // Move the cursor a small distance.
671 // Nothing is added to the navigation history.
672 editor.change_selections(None, cx, |s| {
673 s.select_display_ranges([
674 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
675 ])
676 });
677 editor.change_selections(None, cx, |s| {
678 s.select_display_ranges([
679 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
680 ])
681 });
682 assert!(pop_history(&mut editor, cx).is_none());
683
684 // Move the cursor a large distance.
685 // The history can jump back to the previous position.
686 editor.change_selections(None, cx, |s| {
687 s.select_display_ranges([
688 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
689 ])
690 });
691 let nav_entry = pop_history(&mut editor, cx).unwrap();
692 editor.navigate(nav_entry.data.unwrap(), cx);
693 assert_eq!(nav_entry.item.id(), cx.entity_id());
694 assert_eq!(
695 editor.selections.display_ranges(cx),
696 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
697 );
698 assert!(pop_history(&mut editor, cx).is_none());
699
700 // Move the cursor a small distance via the mouse.
701 // Nothing is added to the navigation history.
702 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
703 editor.end_selection(cx);
704 assert_eq!(
705 editor.selections.display_ranges(cx),
706 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
707 );
708 assert!(pop_history(&mut editor, cx).is_none());
709
710 // Move the cursor a large distance via the mouse.
711 // The history can jump back to the previous position.
712 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
713 editor.end_selection(cx);
714 assert_eq!(
715 editor.selections.display_ranges(cx),
716 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
717 );
718 let nav_entry = pop_history(&mut editor, cx).unwrap();
719 editor.navigate(nav_entry.data.unwrap(), cx);
720 assert_eq!(nav_entry.item.id(), cx.entity_id());
721 assert_eq!(
722 editor.selections.display_ranges(cx),
723 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
724 );
725 assert!(pop_history(&mut editor, cx).is_none());
726
727 // Set scroll position to check later
728 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
729 let original_scroll_position = editor.scroll_manager.anchor();
730
731 // Jump to the end of the document and adjust scroll
732 editor.move_to_end(&MoveToEnd, cx);
733 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
734 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
735
736 let nav_entry = pop_history(&mut editor, cx).unwrap();
737 editor.navigate(nav_entry.data.unwrap(), cx);
738 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
739
740 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
741 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
742 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
743 let invalid_point = Point::new(9999, 0);
744 editor.navigate(
745 Box::new(NavigationData {
746 cursor_anchor: invalid_anchor,
747 cursor_position: invalid_point,
748 scroll_anchor: ScrollAnchor {
749 anchor: invalid_anchor,
750 offset: Default::default(),
751 },
752 scroll_top_row: invalid_point.row,
753 }),
754 cx,
755 );
756 assert_eq!(
757 editor.selections.display_ranges(cx),
758 &[editor.max_point(cx)..editor.max_point(cx)]
759 );
760 assert_eq!(
761 editor.scroll_position(cx),
762 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
763 );
764
765 editor
766 })
767 });
768}
769
770#[gpui::test]
771fn test_cancel(cx: &mut TestAppContext) {
772 init_test(cx, |_| {});
773
774 let view = cx.add_window(|cx| {
775 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
776 build_editor(buffer, cx)
777 });
778
779 _ = view.update(cx, |view, cx| {
780 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
781 view.update_selection(
782 DisplayPoint::new(DisplayRow(1), 1),
783 0,
784 gpui::Point::<f32>::default(),
785 cx,
786 );
787 view.end_selection(cx);
788
789 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
790 view.update_selection(
791 DisplayPoint::new(DisplayRow(0), 3),
792 0,
793 gpui::Point::<f32>::default(),
794 cx,
795 );
796 view.end_selection(cx);
797 assert_eq!(
798 view.selections.display_ranges(cx),
799 [
800 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
801 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
802 ]
803 );
804 });
805
806 _ = view.update(cx, |view, cx| {
807 view.cancel(&Cancel, cx);
808 assert_eq!(
809 view.selections.display_ranges(cx),
810 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
811 );
812 });
813
814 _ = view.update(cx, |view, cx| {
815 view.cancel(&Cancel, cx);
816 assert_eq!(
817 view.selections.display_ranges(cx),
818 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
819 );
820 });
821}
822
823#[gpui::test]
824fn test_fold_action(cx: &mut TestAppContext) {
825 init_test(cx, |_| {});
826
827 let view = cx.add_window(|cx| {
828 let buffer = MultiBuffer::build_simple(
829 &"
830 impl Foo {
831 // Hello!
832
833 fn a() {
834 1
835 }
836
837 fn b() {
838 2
839 }
840
841 fn c() {
842 3
843 }
844 }
845 "
846 .unindent(),
847 cx,
848 );
849 build_editor(buffer.clone(), cx)
850 });
851
852 _ = view.update(cx, |view, cx| {
853 view.change_selections(None, cx, |s| {
854 s.select_display_ranges([
855 DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(12), 0)
856 ]);
857 });
858 view.fold(&Fold, cx);
859 assert_eq!(
860 view.display_text(cx),
861 "
862 impl Foo {
863 // Hello!
864
865 fn a() {
866 1
867 }
868
869 fn b() {⋯
870 }
871
872 fn c() {⋯
873 }
874 }
875 "
876 .unindent(),
877 );
878
879 view.fold(&Fold, cx);
880 assert_eq!(
881 view.display_text(cx),
882 "
883 impl Foo {⋯
884 }
885 "
886 .unindent(),
887 );
888
889 view.unfold_lines(&UnfoldLines, cx);
890 assert_eq!(
891 view.display_text(cx),
892 "
893 impl Foo {
894 // Hello!
895
896 fn a() {
897 1
898 }
899
900 fn b() {⋯
901 }
902
903 fn c() {⋯
904 }
905 }
906 "
907 .unindent(),
908 );
909
910 view.unfold_lines(&UnfoldLines, cx);
911 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
912 });
913}
914
915#[gpui::test]
916fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
917 init_test(cx, |_| {});
918
919 let view = cx.add_window(|cx| {
920 let buffer = MultiBuffer::build_simple(
921 &"
922 class Foo:
923 # Hello!
924
925 def a():
926 print(1)
927
928 def b():
929 print(2)
930
931 def c():
932 print(3)
933 "
934 .unindent(),
935 cx,
936 );
937 build_editor(buffer.clone(), cx)
938 });
939
940 _ = view.update(cx, |view, cx| {
941 view.change_selections(None, cx, |s| {
942 s.select_display_ranges([
943 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(10), 0)
944 ]);
945 });
946 view.fold(&Fold, cx);
947 assert_eq!(
948 view.display_text(cx),
949 "
950 class Foo:
951 # Hello!
952
953 def a():
954 print(1)
955
956 def b():⋯
957
958 def c():⋯
959 "
960 .unindent(),
961 );
962
963 view.fold(&Fold, cx);
964 assert_eq!(
965 view.display_text(cx),
966 "
967 class Foo:⋯
968 "
969 .unindent(),
970 );
971
972 view.unfold_lines(&UnfoldLines, cx);
973 assert_eq!(
974 view.display_text(cx),
975 "
976 class Foo:
977 # Hello!
978
979 def a():
980 print(1)
981
982 def b():⋯
983
984 def c():⋯
985 "
986 .unindent(),
987 );
988
989 view.unfold_lines(&UnfoldLines, cx);
990 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
991 });
992}
993
994#[gpui::test]
995fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
996 init_test(cx, |_| {});
997
998 let view = cx.add_window(|cx| {
999 let buffer = MultiBuffer::build_simple(
1000 &"
1001 class Foo:
1002 # Hello!
1003
1004 def a():
1005 print(1)
1006
1007 def b():
1008 print(2)
1009
1010
1011 def c():
1012 print(3)
1013
1014
1015 "
1016 .unindent(),
1017 cx,
1018 );
1019 build_editor(buffer.clone(), cx)
1020 });
1021
1022 _ = view.update(cx, |view, cx| {
1023 view.change_selections(None, cx, |s| {
1024 s.select_display_ranges([
1025 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(11), 0)
1026 ]);
1027 });
1028 view.fold(&Fold, cx);
1029 assert_eq!(
1030 view.display_text(cx),
1031 "
1032 class Foo:
1033 # Hello!
1034
1035 def a():
1036 print(1)
1037
1038 def b():⋯
1039
1040
1041 def c():⋯
1042
1043
1044 "
1045 .unindent(),
1046 );
1047
1048 view.fold(&Fold, cx);
1049 assert_eq!(
1050 view.display_text(cx),
1051 "
1052 class Foo:⋯
1053
1054
1055 "
1056 .unindent(),
1057 );
1058
1059 view.unfold_lines(&UnfoldLines, cx);
1060 assert_eq!(
1061 view.display_text(cx),
1062 "
1063 class Foo:
1064 # Hello!
1065
1066 def a():
1067 print(1)
1068
1069 def b():⋯
1070
1071
1072 def c():⋯
1073
1074
1075 "
1076 .unindent(),
1077 );
1078
1079 view.unfold_lines(&UnfoldLines, cx);
1080 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1081 });
1082}
1083
1084#[gpui::test]
1085fn test_move_cursor(cx: &mut TestAppContext) {
1086 init_test(cx, |_| {});
1087
1088 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1089 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1090
1091 _ = buffer.update(cx, |buffer, cx| {
1092 buffer.edit(
1093 vec![
1094 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1095 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1096 ],
1097 None,
1098 cx,
1099 );
1100 });
1101 _ = view.update(cx, |view, cx| {
1102 assert_eq!(
1103 view.selections.display_ranges(cx),
1104 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1105 );
1106
1107 view.move_down(&MoveDown, cx);
1108 assert_eq!(
1109 view.selections.display_ranges(cx),
1110 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1111 );
1112
1113 view.move_right(&MoveRight, cx);
1114 assert_eq!(
1115 view.selections.display_ranges(cx),
1116 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1117 );
1118
1119 view.move_left(&MoveLeft, cx);
1120 assert_eq!(
1121 view.selections.display_ranges(cx),
1122 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1123 );
1124
1125 view.move_up(&MoveUp, cx);
1126 assert_eq!(
1127 view.selections.display_ranges(cx),
1128 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1129 );
1130
1131 view.move_to_end(&MoveToEnd, cx);
1132 assert_eq!(
1133 view.selections.display_ranges(cx),
1134 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1135 );
1136
1137 view.move_to_beginning(&MoveToBeginning, cx);
1138 assert_eq!(
1139 view.selections.display_ranges(cx),
1140 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1141 );
1142
1143 view.change_selections(None, cx, |s| {
1144 s.select_display_ranges([
1145 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1146 ]);
1147 });
1148 view.select_to_beginning(&SelectToBeginning, cx);
1149 assert_eq!(
1150 view.selections.display_ranges(cx),
1151 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1152 );
1153
1154 view.select_to_end(&SelectToEnd, cx);
1155 assert_eq!(
1156 view.selections.display_ranges(cx),
1157 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1158 );
1159 });
1160}
1161
1162// TODO: Re-enable this test
1163#[cfg(target_os = "macos")]
1164#[gpui::test]
1165fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1166 init_test(cx, |_| {});
1167
1168 let view = cx.add_window(|cx| {
1169 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1170 build_editor(buffer.clone(), cx)
1171 });
1172
1173 assert_eq!('ⓐ'.len_utf8(), 3);
1174 assert_eq!('α'.len_utf8(), 2);
1175
1176 _ = view.update(cx, |view, cx| {
1177 view.fold_ranges(
1178 vec![
1179 (Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1180 (Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1181 (Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1182 ],
1183 true,
1184 cx,
1185 );
1186 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1187
1188 view.move_right(&MoveRight, cx);
1189 assert_eq!(
1190 view.selections.display_ranges(cx),
1191 &[empty_range(0, "ⓐ".len())]
1192 );
1193 view.move_right(&MoveRight, cx);
1194 assert_eq!(
1195 view.selections.display_ranges(cx),
1196 &[empty_range(0, "ⓐⓑ".len())]
1197 );
1198 view.move_right(&MoveRight, cx);
1199 assert_eq!(
1200 view.selections.display_ranges(cx),
1201 &[empty_range(0, "ⓐⓑ⋯".len())]
1202 );
1203
1204 view.move_down(&MoveDown, cx);
1205 assert_eq!(
1206 view.selections.display_ranges(cx),
1207 &[empty_range(1, "ab⋯e".len())]
1208 );
1209 view.move_left(&MoveLeft, cx);
1210 assert_eq!(
1211 view.selections.display_ranges(cx),
1212 &[empty_range(1, "ab⋯".len())]
1213 );
1214 view.move_left(&MoveLeft, cx);
1215 assert_eq!(
1216 view.selections.display_ranges(cx),
1217 &[empty_range(1, "ab".len())]
1218 );
1219 view.move_left(&MoveLeft, cx);
1220 assert_eq!(
1221 view.selections.display_ranges(cx),
1222 &[empty_range(1, "a".len())]
1223 );
1224
1225 view.move_down(&MoveDown, cx);
1226 assert_eq!(
1227 view.selections.display_ranges(cx),
1228 &[empty_range(2, "α".len())]
1229 );
1230 view.move_right(&MoveRight, cx);
1231 assert_eq!(
1232 view.selections.display_ranges(cx),
1233 &[empty_range(2, "αβ".len())]
1234 );
1235 view.move_right(&MoveRight, cx);
1236 assert_eq!(
1237 view.selections.display_ranges(cx),
1238 &[empty_range(2, "αβ⋯".len())]
1239 );
1240 view.move_right(&MoveRight, cx);
1241 assert_eq!(
1242 view.selections.display_ranges(cx),
1243 &[empty_range(2, "αβ⋯ε".len())]
1244 );
1245
1246 view.move_up(&MoveUp, cx);
1247 assert_eq!(
1248 view.selections.display_ranges(cx),
1249 &[empty_range(1, "ab⋯e".len())]
1250 );
1251 view.move_down(&MoveDown, cx);
1252 assert_eq!(
1253 view.selections.display_ranges(cx),
1254 &[empty_range(2, "αβ⋯ε".len())]
1255 );
1256 view.move_up(&MoveUp, cx);
1257 assert_eq!(
1258 view.selections.display_ranges(cx),
1259 &[empty_range(1, "ab⋯e".len())]
1260 );
1261
1262 view.move_up(&MoveUp, cx);
1263 assert_eq!(
1264 view.selections.display_ranges(cx),
1265 &[empty_range(0, "ⓐⓑ".len())]
1266 );
1267 view.move_left(&MoveLeft, cx);
1268 assert_eq!(
1269 view.selections.display_ranges(cx),
1270 &[empty_range(0, "ⓐ".len())]
1271 );
1272 view.move_left(&MoveLeft, cx);
1273 assert_eq!(
1274 view.selections.display_ranges(cx),
1275 &[empty_range(0, "".len())]
1276 );
1277 });
1278}
1279
1280#[gpui::test]
1281fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1282 init_test(cx, |_| {});
1283
1284 let view = cx.add_window(|cx| {
1285 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1286 build_editor(buffer.clone(), cx)
1287 });
1288 _ = view.update(cx, |view, cx| {
1289 view.change_selections(None, cx, |s| {
1290 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1291 });
1292 view.move_down(&MoveDown, cx);
1293 assert_eq!(
1294 view.selections.display_ranges(cx),
1295 &[empty_range(1, "abcd".len())]
1296 );
1297
1298 view.move_down(&MoveDown, cx);
1299 assert_eq!(
1300 view.selections.display_ranges(cx),
1301 &[empty_range(2, "αβγ".len())]
1302 );
1303
1304 view.move_down(&MoveDown, cx);
1305 assert_eq!(
1306 view.selections.display_ranges(cx),
1307 &[empty_range(3, "abcd".len())]
1308 );
1309
1310 view.move_down(&MoveDown, cx);
1311 assert_eq!(
1312 view.selections.display_ranges(cx),
1313 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1314 );
1315
1316 view.move_up(&MoveUp, cx);
1317 assert_eq!(
1318 view.selections.display_ranges(cx),
1319 &[empty_range(3, "abcd".len())]
1320 );
1321
1322 view.move_up(&MoveUp, cx);
1323 assert_eq!(
1324 view.selections.display_ranges(cx),
1325 &[empty_range(2, "αβγ".len())]
1326 );
1327 });
1328}
1329
1330#[gpui::test]
1331fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1332 init_test(cx, |_| {});
1333 let move_to_beg = MoveToBeginningOfLine {
1334 stop_at_soft_wraps: true,
1335 };
1336
1337 let move_to_end = MoveToEndOfLine {
1338 stop_at_soft_wraps: true,
1339 };
1340
1341 let view = cx.add_window(|cx| {
1342 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1343 build_editor(buffer, cx)
1344 });
1345 _ = view.update(cx, |view, cx| {
1346 view.change_selections(None, cx, |s| {
1347 s.select_display_ranges([
1348 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1349 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1350 ]);
1351 });
1352 });
1353
1354 _ = view.update(cx, |view, cx| {
1355 view.move_to_beginning_of_line(&move_to_beg, cx);
1356 assert_eq!(
1357 view.selections.display_ranges(cx),
1358 &[
1359 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1360 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1361 ]
1362 );
1363 });
1364
1365 _ = view.update(cx, |view, cx| {
1366 view.move_to_beginning_of_line(&move_to_beg, cx);
1367 assert_eq!(
1368 view.selections.display_ranges(cx),
1369 &[
1370 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1371 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1372 ]
1373 );
1374 });
1375
1376 _ = view.update(cx, |view, cx| {
1377 view.move_to_beginning_of_line(&move_to_beg, cx);
1378 assert_eq!(
1379 view.selections.display_ranges(cx),
1380 &[
1381 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1382 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1383 ]
1384 );
1385 });
1386
1387 _ = view.update(cx, |view, cx| {
1388 view.move_to_end_of_line(&move_to_end, cx);
1389 assert_eq!(
1390 view.selections.display_ranges(cx),
1391 &[
1392 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1393 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1394 ]
1395 );
1396 });
1397
1398 // Moving to the end of line again is a no-op.
1399 _ = view.update(cx, |view, cx| {
1400 view.move_to_end_of_line(&move_to_end, cx);
1401 assert_eq!(
1402 view.selections.display_ranges(cx),
1403 &[
1404 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1405 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1406 ]
1407 );
1408 });
1409
1410 _ = view.update(cx, |view, cx| {
1411 view.move_left(&MoveLeft, cx);
1412 view.select_to_beginning_of_line(
1413 &SelectToBeginningOfLine {
1414 stop_at_soft_wraps: true,
1415 },
1416 cx,
1417 );
1418 assert_eq!(
1419 view.selections.display_ranges(cx),
1420 &[
1421 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1422 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1423 ]
1424 );
1425 });
1426
1427 _ = view.update(cx, |view, cx| {
1428 view.select_to_beginning_of_line(
1429 &SelectToBeginningOfLine {
1430 stop_at_soft_wraps: true,
1431 },
1432 cx,
1433 );
1434 assert_eq!(
1435 view.selections.display_ranges(cx),
1436 &[
1437 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1438 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1439 ]
1440 );
1441 });
1442
1443 _ = view.update(cx, |view, cx| {
1444 view.select_to_beginning_of_line(
1445 &SelectToBeginningOfLine {
1446 stop_at_soft_wraps: true,
1447 },
1448 cx,
1449 );
1450 assert_eq!(
1451 view.selections.display_ranges(cx),
1452 &[
1453 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1454 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1455 ]
1456 );
1457 });
1458
1459 _ = view.update(cx, |view, cx| {
1460 view.select_to_end_of_line(
1461 &SelectToEndOfLine {
1462 stop_at_soft_wraps: true,
1463 },
1464 cx,
1465 );
1466 assert_eq!(
1467 view.selections.display_ranges(cx),
1468 &[
1469 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1470 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1471 ]
1472 );
1473 });
1474
1475 _ = view.update(cx, |view, cx| {
1476 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1477 assert_eq!(view.display_text(cx), "ab\n de");
1478 assert_eq!(
1479 view.selections.display_ranges(cx),
1480 &[
1481 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1482 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1483 ]
1484 );
1485 });
1486
1487 _ = view.update(cx, |view, cx| {
1488 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1489 assert_eq!(view.display_text(cx), "\n");
1490 assert_eq!(
1491 view.selections.display_ranges(cx),
1492 &[
1493 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1494 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1495 ]
1496 );
1497 });
1498}
1499
1500#[gpui::test]
1501fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1502 init_test(cx, |_| {});
1503 let move_to_beg = MoveToBeginningOfLine {
1504 stop_at_soft_wraps: false,
1505 };
1506
1507 let move_to_end = MoveToEndOfLine {
1508 stop_at_soft_wraps: false,
1509 };
1510
1511 let view = cx.add_window(|cx| {
1512 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1513 build_editor(buffer, cx)
1514 });
1515
1516 _ = view.update(cx, |view, cx| {
1517 view.set_wrap_width(Some(140.0.into()), cx);
1518
1519 // We expect the following lines after wrapping
1520 // ```
1521 // thequickbrownfox
1522 // jumpedoverthelazydo
1523 // gs
1524 // ```
1525 // The final `gs` was soft-wrapped onto a new line.
1526 assert_eq!(
1527 "thequickbrownfox\njumpedoverthelaz\nydogs",
1528 view.display_text(cx),
1529 );
1530
1531 // First, let's assert behavior on the first line, that was not soft-wrapped.
1532 // Start the cursor at the `k` on the first line
1533 view.change_selections(None, cx, |s| {
1534 s.select_display_ranges([
1535 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1536 ]);
1537 });
1538
1539 // Moving to the beginning of the line should put us at the beginning of the line.
1540 view.move_to_beginning_of_line(&move_to_beg, cx);
1541 assert_eq!(
1542 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1543 view.selections.display_ranges(cx)
1544 );
1545
1546 // Moving to the end of the line should put us at the end of the line.
1547 view.move_to_end_of_line(&move_to_end, cx);
1548 assert_eq!(
1549 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1550 view.selections.display_ranges(cx)
1551 );
1552
1553 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1554 // Start the cursor at the last line (`y` that was wrapped to a new line)
1555 view.change_selections(None, cx, |s| {
1556 s.select_display_ranges([
1557 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1558 ]);
1559 });
1560
1561 // Moving to the beginning of the line should put us at the start of the second line of
1562 // display text, i.e., the `j`.
1563 view.move_to_beginning_of_line(&move_to_beg, cx);
1564 assert_eq!(
1565 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1566 view.selections.display_ranges(cx)
1567 );
1568
1569 // Moving to the beginning of the line again should be a no-op.
1570 view.move_to_beginning_of_line(&move_to_beg, cx);
1571 assert_eq!(
1572 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1573 view.selections.display_ranges(cx)
1574 );
1575
1576 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1577 // next display line.
1578 view.move_to_end_of_line(&move_to_end, cx);
1579 assert_eq!(
1580 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1581 view.selections.display_ranges(cx)
1582 );
1583
1584 // Moving to the end of the line again should be a no-op.
1585 view.move_to_end_of_line(&move_to_end, cx);
1586 assert_eq!(
1587 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1588 view.selections.display_ranges(cx)
1589 );
1590 });
1591}
1592
1593#[gpui::test]
1594fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1595 init_test(cx, |_| {});
1596
1597 let view = cx.add_window(|cx| {
1598 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1599 build_editor(buffer, cx)
1600 });
1601 _ = view.update(cx, |view, cx| {
1602 view.change_selections(None, cx, |s| {
1603 s.select_display_ranges([
1604 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1605 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1606 ])
1607 });
1608
1609 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1610 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1611
1612 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1613 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1614
1615 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1616 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1617
1618 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1619 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1620
1621 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1622 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1623
1624 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1625 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1626
1627 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1628 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1629
1630 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1631 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1632
1633 view.move_right(&MoveRight, cx);
1634 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1635 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1636
1637 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1638 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1639
1640 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1641 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1642 });
1643}
1644
1645#[gpui::test]
1646fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1647 init_test(cx, |_| {});
1648
1649 let view = cx.add_window(|cx| {
1650 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1651 build_editor(buffer, cx)
1652 });
1653
1654 _ = view.update(cx, |view, cx| {
1655 view.set_wrap_width(Some(140.0.into()), cx);
1656 assert_eq!(
1657 view.display_text(cx),
1658 "use one::{\n two::three::\n four::five\n};"
1659 );
1660
1661 view.change_selections(None, cx, |s| {
1662 s.select_display_ranges([
1663 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1664 ]);
1665 });
1666
1667 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1668 assert_eq!(
1669 view.selections.display_ranges(cx),
1670 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1671 );
1672
1673 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1674 assert_eq!(
1675 view.selections.display_ranges(cx),
1676 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1677 );
1678
1679 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1680 assert_eq!(
1681 view.selections.display_ranges(cx),
1682 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1683 );
1684
1685 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1686 assert_eq!(
1687 view.selections.display_ranges(cx),
1688 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1689 );
1690
1691 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1692 assert_eq!(
1693 view.selections.display_ranges(cx),
1694 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1695 );
1696
1697 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1698 assert_eq!(
1699 view.selections.display_ranges(cx),
1700 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1701 );
1702 });
1703}
1704
1705#[gpui::test]
1706async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1707 init_test(cx, |_| {});
1708 let mut cx = EditorTestContext::new(cx).await;
1709
1710 let line_height = cx.editor(|editor, cx| {
1711 editor
1712 .style()
1713 .unwrap()
1714 .text
1715 .line_height_in_pixels(cx.rem_size())
1716 });
1717 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1718
1719 cx.set_state(
1720 &r#"ˇone
1721 two
1722
1723 three
1724 fourˇ
1725 five
1726
1727 six"#
1728 .unindent(),
1729 );
1730
1731 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1732 cx.assert_editor_state(
1733 &r#"one
1734 two
1735 ˇ
1736 three
1737 four
1738 five
1739 ˇ
1740 six"#
1741 .unindent(),
1742 );
1743
1744 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1745 cx.assert_editor_state(
1746 &r#"one
1747 two
1748
1749 three
1750 four
1751 five
1752 ˇ
1753 sixˇ"#
1754 .unindent(),
1755 );
1756
1757 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1758 cx.assert_editor_state(
1759 &r#"one
1760 two
1761
1762 three
1763 four
1764 five
1765
1766 sixˇ"#
1767 .unindent(),
1768 );
1769
1770 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1771 cx.assert_editor_state(
1772 &r#"one
1773 two
1774
1775 three
1776 four
1777 five
1778 ˇ
1779 six"#
1780 .unindent(),
1781 );
1782
1783 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1784 cx.assert_editor_state(
1785 &r#"one
1786 two
1787 ˇ
1788 three
1789 four
1790 five
1791
1792 six"#
1793 .unindent(),
1794 );
1795
1796 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1797 cx.assert_editor_state(
1798 &r#"ˇone
1799 two
1800
1801 three
1802 four
1803 five
1804
1805 six"#
1806 .unindent(),
1807 );
1808}
1809
1810#[gpui::test]
1811async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1812 init_test(cx, |_| {});
1813 let mut cx = EditorTestContext::new(cx).await;
1814 let line_height = cx.editor(|editor, cx| {
1815 editor
1816 .style()
1817 .unwrap()
1818 .text
1819 .line_height_in_pixels(cx.rem_size())
1820 });
1821 let window = cx.window;
1822 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1823
1824 cx.set_state(
1825 &r#"ˇone
1826 two
1827 three
1828 four
1829 five
1830 six
1831 seven
1832 eight
1833 nine
1834 ten
1835 "#,
1836 );
1837
1838 cx.update_editor(|editor, cx| {
1839 assert_eq!(
1840 editor.snapshot(cx).scroll_position(),
1841 gpui::Point::new(0., 0.)
1842 );
1843 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1844 assert_eq!(
1845 editor.snapshot(cx).scroll_position(),
1846 gpui::Point::new(0., 3.)
1847 );
1848 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1849 assert_eq!(
1850 editor.snapshot(cx).scroll_position(),
1851 gpui::Point::new(0., 6.)
1852 );
1853 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1854 assert_eq!(
1855 editor.snapshot(cx).scroll_position(),
1856 gpui::Point::new(0., 3.)
1857 );
1858
1859 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1860 assert_eq!(
1861 editor.snapshot(cx).scroll_position(),
1862 gpui::Point::new(0., 1.)
1863 );
1864 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
1865 assert_eq!(
1866 editor.snapshot(cx).scroll_position(),
1867 gpui::Point::new(0., 3.)
1868 );
1869 });
1870}
1871
1872#[gpui::test]
1873async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
1874 init_test(cx, |_| {});
1875 let mut cx = EditorTestContext::new(cx).await;
1876
1877 let line_height = cx.update_editor(|editor, cx| {
1878 editor.set_vertical_scroll_margin(2, cx);
1879 editor
1880 .style()
1881 .unwrap()
1882 .text
1883 .line_height_in_pixels(cx.rem_size())
1884 });
1885 let window = cx.window;
1886 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
1887
1888 cx.set_state(
1889 &r#"ˇone
1890 two
1891 three
1892 four
1893 five
1894 six
1895 seven
1896 eight
1897 nine
1898 ten
1899 "#,
1900 );
1901 cx.update_editor(|editor, cx| {
1902 assert_eq!(
1903 editor.snapshot(cx).scroll_position(),
1904 gpui::Point::new(0., 0.0)
1905 );
1906 });
1907
1908 // Add a cursor below the visible area. Since both cursors cannot fit
1909 // on screen, the editor autoscrolls to reveal the newest cursor, and
1910 // allows the vertical scroll margin below that cursor.
1911 cx.update_editor(|editor, cx| {
1912 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1913 selections.select_ranges([
1914 Point::new(0, 0)..Point::new(0, 0),
1915 Point::new(6, 0)..Point::new(6, 0),
1916 ]);
1917 })
1918 });
1919 cx.update_editor(|editor, cx| {
1920 assert_eq!(
1921 editor.snapshot(cx).scroll_position(),
1922 gpui::Point::new(0., 3.0)
1923 );
1924 });
1925
1926 // Move down. The editor cursor scrolls down to track the newest cursor.
1927 cx.update_editor(|editor, cx| {
1928 editor.move_down(&Default::default(), cx);
1929 });
1930 cx.update_editor(|editor, cx| {
1931 assert_eq!(
1932 editor.snapshot(cx).scroll_position(),
1933 gpui::Point::new(0., 4.0)
1934 );
1935 });
1936
1937 // Add a cursor above the visible area. Since both cursors fit on screen,
1938 // the editor scrolls to show both.
1939 cx.update_editor(|editor, cx| {
1940 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1941 selections.select_ranges([
1942 Point::new(1, 0)..Point::new(1, 0),
1943 Point::new(6, 0)..Point::new(6, 0),
1944 ]);
1945 })
1946 });
1947 cx.update_editor(|editor, cx| {
1948 assert_eq!(
1949 editor.snapshot(cx).scroll_position(),
1950 gpui::Point::new(0., 1.0)
1951 );
1952 });
1953}
1954
1955#[gpui::test]
1956async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
1957 init_test(cx, |_| {});
1958 let mut cx = EditorTestContext::new(cx).await;
1959
1960 let line_height = cx.editor(|editor, cx| {
1961 editor
1962 .style()
1963 .unwrap()
1964 .text
1965 .line_height_in_pixels(cx.rem_size())
1966 });
1967 let window = cx.window;
1968 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
1969 cx.set_state(
1970 &r#"
1971 ˇone
1972 two
1973 threeˇ
1974 four
1975 five
1976 six
1977 seven
1978 eight
1979 nine
1980 ten
1981 "#
1982 .unindent(),
1983 );
1984
1985 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1986 cx.assert_editor_state(
1987 &r#"
1988 one
1989 two
1990 three
1991 ˇfour
1992 five
1993 sixˇ
1994 seven
1995 eight
1996 nine
1997 ten
1998 "#
1999 .unindent(),
2000 );
2001
2002 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2003 cx.assert_editor_state(
2004 &r#"
2005 one
2006 two
2007 three
2008 four
2009 five
2010 six
2011 ˇseven
2012 eight
2013 nineˇ
2014 ten
2015 "#
2016 .unindent(),
2017 );
2018
2019 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2020 cx.assert_editor_state(
2021 &r#"
2022 one
2023 two
2024 three
2025 ˇfour
2026 five
2027 sixˇ
2028 seven
2029 eight
2030 nine
2031 ten
2032 "#
2033 .unindent(),
2034 );
2035
2036 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2037 cx.assert_editor_state(
2038 &r#"
2039 ˇone
2040 two
2041 threeˇ
2042 four
2043 five
2044 six
2045 seven
2046 eight
2047 nine
2048 ten
2049 "#
2050 .unindent(),
2051 );
2052
2053 // Test select collapsing
2054 cx.update_editor(|editor, cx| {
2055 editor.move_page_down(&MovePageDown::default(), cx);
2056 editor.move_page_down(&MovePageDown::default(), cx);
2057 editor.move_page_down(&MovePageDown::default(), cx);
2058 });
2059 cx.assert_editor_state(
2060 &r#"
2061 one
2062 two
2063 three
2064 four
2065 five
2066 six
2067 seven
2068 eight
2069 nine
2070 ˇten
2071 ˇ"#
2072 .unindent(),
2073 );
2074}
2075
2076#[gpui::test]
2077async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2078 init_test(cx, |_| {});
2079 let mut cx = EditorTestContext::new(cx).await;
2080 cx.set_state("one «two threeˇ» four");
2081 cx.update_editor(|editor, cx| {
2082 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2083 assert_eq!(editor.text(cx), " four");
2084 });
2085}
2086
2087#[gpui::test]
2088fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2089 init_test(cx, |_| {});
2090
2091 let view = cx.add_window(|cx| {
2092 let buffer = MultiBuffer::build_simple("one two three four", cx);
2093 build_editor(buffer.clone(), cx)
2094 });
2095
2096 _ = view.update(cx, |view, cx| {
2097 view.change_selections(None, cx, |s| {
2098 s.select_display_ranges([
2099 // an empty selection - the preceding word fragment is deleted
2100 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2101 // characters selected - they are deleted
2102 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2103 ])
2104 });
2105 view.delete_to_previous_word_start(&DeleteToPreviousWordStart, cx);
2106 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2107 });
2108
2109 _ = view.update(cx, |view, cx| {
2110 view.change_selections(None, cx, |s| {
2111 s.select_display_ranges([
2112 // an empty selection - the following word fragment is deleted
2113 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2114 // characters selected - they are deleted
2115 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2116 ])
2117 });
2118 view.delete_to_next_word_end(&DeleteToNextWordEnd, cx);
2119 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2120 });
2121}
2122
2123#[gpui::test]
2124fn test_newline(cx: &mut TestAppContext) {
2125 init_test(cx, |_| {});
2126
2127 let view = cx.add_window(|cx| {
2128 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2129 build_editor(buffer.clone(), cx)
2130 });
2131
2132 _ = view.update(cx, |view, cx| {
2133 view.change_selections(None, cx, |s| {
2134 s.select_display_ranges([
2135 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2136 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2137 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2138 ])
2139 });
2140
2141 view.newline(&Newline, cx);
2142 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2143 });
2144}
2145
2146#[gpui::test]
2147fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2148 init_test(cx, |_| {});
2149
2150 let editor = cx.add_window(|cx| {
2151 let buffer = MultiBuffer::build_simple(
2152 "
2153 a
2154 b(
2155 X
2156 )
2157 c(
2158 X
2159 )
2160 "
2161 .unindent()
2162 .as_str(),
2163 cx,
2164 );
2165 let mut editor = build_editor(buffer.clone(), cx);
2166 editor.change_selections(None, cx, |s| {
2167 s.select_ranges([
2168 Point::new(2, 4)..Point::new(2, 5),
2169 Point::new(5, 4)..Point::new(5, 5),
2170 ])
2171 });
2172 editor
2173 });
2174
2175 _ = editor.update(cx, |editor, cx| {
2176 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2177 editor.buffer.update(cx, |buffer, cx| {
2178 buffer.edit(
2179 [
2180 (Point::new(1, 2)..Point::new(3, 0), ""),
2181 (Point::new(4, 2)..Point::new(6, 0), ""),
2182 ],
2183 None,
2184 cx,
2185 );
2186 assert_eq!(
2187 buffer.read(cx).text(),
2188 "
2189 a
2190 b()
2191 c()
2192 "
2193 .unindent()
2194 );
2195 });
2196 assert_eq!(
2197 editor.selections.ranges(cx),
2198 &[
2199 Point::new(1, 2)..Point::new(1, 2),
2200 Point::new(2, 2)..Point::new(2, 2),
2201 ],
2202 );
2203
2204 editor.newline(&Newline, cx);
2205 assert_eq!(
2206 editor.text(cx),
2207 "
2208 a
2209 b(
2210 )
2211 c(
2212 )
2213 "
2214 .unindent()
2215 );
2216
2217 // The selections are moved after the inserted newlines
2218 assert_eq!(
2219 editor.selections.ranges(cx),
2220 &[
2221 Point::new(2, 0)..Point::new(2, 0),
2222 Point::new(4, 0)..Point::new(4, 0),
2223 ],
2224 );
2225 });
2226}
2227
2228#[gpui::test]
2229async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2230 init_test(cx, |settings| {
2231 settings.defaults.tab_size = NonZeroU32::new(4)
2232 });
2233
2234 let language = Arc::new(
2235 Language::new(
2236 LanguageConfig::default(),
2237 Some(tree_sitter_rust::language()),
2238 )
2239 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2240 .unwrap(),
2241 );
2242
2243 let mut cx = EditorTestContext::new(cx).await;
2244 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2245 cx.set_state(indoc! {"
2246 const a: ˇA = (
2247 (ˇ
2248 «const_functionˇ»(ˇ),
2249 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2250 )ˇ
2251 ˇ);ˇ
2252 "});
2253
2254 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2255 cx.assert_editor_state(indoc! {"
2256 ˇ
2257 const a: A = (
2258 ˇ
2259 (
2260 ˇ
2261 ˇ
2262 const_function(),
2263 ˇ
2264 ˇ
2265 ˇ
2266 ˇ
2267 something_else,
2268 ˇ
2269 )
2270 ˇ
2271 ˇ
2272 );
2273 "});
2274}
2275
2276#[gpui::test]
2277async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2278 init_test(cx, |settings| {
2279 settings.defaults.tab_size = NonZeroU32::new(4)
2280 });
2281
2282 let language = Arc::new(
2283 Language::new(
2284 LanguageConfig::default(),
2285 Some(tree_sitter_rust::language()),
2286 )
2287 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2288 .unwrap(),
2289 );
2290
2291 let mut cx = EditorTestContext::new(cx).await;
2292 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2293 cx.set_state(indoc! {"
2294 const a: ˇA = (
2295 (ˇ
2296 «const_functionˇ»(ˇ),
2297 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2298 )ˇ
2299 ˇ);ˇ
2300 "});
2301
2302 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2303 cx.assert_editor_state(indoc! {"
2304 const a: A = (
2305 ˇ
2306 (
2307 ˇ
2308 const_function(),
2309 ˇ
2310 ˇ
2311 something_else,
2312 ˇ
2313 ˇ
2314 ˇ
2315 ˇ
2316 )
2317 ˇ
2318 );
2319 ˇ
2320 ˇ
2321 "});
2322}
2323
2324#[gpui::test]
2325async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2326 init_test(cx, |settings| {
2327 settings.defaults.tab_size = NonZeroU32::new(4)
2328 });
2329
2330 let language = Arc::new(Language::new(
2331 LanguageConfig {
2332 line_comments: vec!["//".into()],
2333 ..LanguageConfig::default()
2334 },
2335 None,
2336 ));
2337 {
2338 let mut cx = EditorTestContext::new(cx).await;
2339 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2340 cx.set_state(indoc! {"
2341 // Fooˇ
2342 "});
2343
2344 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2345 cx.assert_editor_state(indoc! {"
2346 // Foo
2347 //ˇ
2348 "});
2349 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2350 cx.set_state(indoc! {"
2351 ˇ// Foo
2352 "});
2353 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2354 cx.assert_editor_state(indoc! {"
2355
2356 ˇ// Foo
2357 "});
2358 }
2359 // Ensure that comment continuations can be disabled.
2360 update_test_language_settings(cx, |settings| {
2361 settings.defaults.extend_comment_on_newline = Some(false);
2362 });
2363 let mut cx = EditorTestContext::new(cx).await;
2364 cx.set_state(indoc! {"
2365 // Fooˇ
2366 "});
2367 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2368 cx.assert_editor_state(indoc! {"
2369 // Foo
2370 ˇ
2371 "});
2372}
2373
2374#[gpui::test]
2375fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2376 init_test(cx, |_| {});
2377
2378 let editor = cx.add_window(|cx| {
2379 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2380 let mut editor = build_editor(buffer.clone(), cx);
2381 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2382 editor
2383 });
2384
2385 _ = editor.update(cx, |editor, cx| {
2386 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2387 editor.buffer.update(cx, |buffer, cx| {
2388 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2389 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2390 });
2391 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2392
2393 editor.insert("Z", cx);
2394 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2395
2396 // The selections are moved after the inserted characters
2397 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2398 });
2399}
2400
2401#[gpui::test]
2402async fn test_tab(cx: &mut gpui::TestAppContext) {
2403 init_test(cx, |settings| {
2404 settings.defaults.tab_size = NonZeroU32::new(3)
2405 });
2406
2407 let mut cx = EditorTestContext::new(cx).await;
2408 cx.set_state(indoc! {"
2409 ˇabˇc
2410 ˇ🏀ˇ🏀ˇefg
2411 dˇ
2412 "});
2413 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2414 cx.assert_editor_state(indoc! {"
2415 ˇab ˇc
2416 ˇ🏀 ˇ🏀 ˇefg
2417 d ˇ
2418 "});
2419
2420 cx.set_state(indoc! {"
2421 a
2422 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2423 "});
2424 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2425 cx.assert_editor_state(indoc! {"
2426 a
2427 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2428 "});
2429}
2430
2431#[gpui::test]
2432async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2433 init_test(cx, |_| {});
2434
2435 let mut cx = EditorTestContext::new(cx).await;
2436 let language = Arc::new(
2437 Language::new(
2438 LanguageConfig::default(),
2439 Some(tree_sitter_rust::language()),
2440 )
2441 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2442 .unwrap(),
2443 );
2444 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2445
2446 // cursors that are already at the suggested indent level insert
2447 // a soft tab. cursors that are to the left of the suggested indent
2448 // auto-indent their line.
2449 cx.set_state(indoc! {"
2450 ˇ
2451 const a: B = (
2452 c(
2453 d(
2454 ˇ
2455 )
2456 ˇ
2457 ˇ )
2458 );
2459 "});
2460 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2461 cx.assert_editor_state(indoc! {"
2462 ˇ
2463 const a: B = (
2464 c(
2465 d(
2466 ˇ
2467 )
2468 ˇ
2469 ˇ)
2470 );
2471 "});
2472
2473 // handle auto-indent when there are multiple cursors on the same line
2474 cx.set_state(indoc! {"
2475 const a: B = (
2476 c(
2477 ˇ ˇ
2478 ˇ )
2479 );
2480 "});
2481 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2482 cx.assert_editor_state(indoc! {"
2483 const a: B = (
2484 c(
2485 ˇ
2486 ˇ)
2487 );
2488 "});
2489}
2490
2491#[gpui::test]
2492async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2493 init_test(cx, |settings| {
2494 settings.defaults.tab_size = NonZeroU32::new(4)
2495 });
2496
2497 let language = Arc::new(
2498 Language::new(
2499 LanguageConfig::default(),
2500 Some(tree_sitter_rust::language()),
2501 )
2502 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2503 .unwrap(),
2504 );
2505
2506 let mut cx = EditorTestContext::new(cx).await;
2507 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2508 cx.set_state(indoc! {"
2509 fn a() {
2510 if b {
2511 \t ˇc
2512 }
2513 }
2514 "});
2515
2516 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2517 cx.assert_editor_state(indoc! {"
2518 fn a() {
2519 if b {
2520 ˇc
2521 }
2522 }
2523 "});
2524}
2525
2526#[gpui::test]
2527async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2528 init_test(cx, |settings| {
2529 settings.defaults.tab_size = NonZeroU32::new(4);
2530 });
2531
2532 let mut cx = EditorTestContext::new(cx).await;
2533
2534 cx.set_state(indoc! {"
2535 «oneˇ» «twoˇ»
2536 three
2537 four
2538 "});
2539 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2540 cx.assert_editor_state(indoc! {"
2541 «oneˇ» «twoˇ»
2542 three
2543 four
2544 "});
2545
2546 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2547 cx.assert_editor_state(indoc! {"
2548 «oneˇ» «twoˇ»
2549 three
2550 four
2551 "});
2552
2553 // select across line ending
2554 cx.set_state(indoc! {"
2555 one two
2556 t«hree
2557 ˇ» four
2558 "});
2559 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2560 cx.assert_editor_state(indoc! {"
2561 one two
2562 t«hree
2563 ˇ» four
2564 "});
2565
2566 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2567 cx.assert_editor_state(indoc! {"
2568 one two
2569 t«hree
2570 ˇ» four
2571 "});
2572
2573 // Ensure that indenting/outdenting works when the cursor is at column 0.
2574 cx.set_state(indoc! {"
2575 one two
2576 ˇthree
2577 four
2578 "});
2579 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2580 cx.assert_editor_state(indoc! {"
2581 one two
2582 ˇthree
2583 four
2584 "});
2585
2586 cx.set_state(indoc! {"
2587 one two
2588 ˇ three
2589 four
2590 "});
2591 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2592 cx.assert_editor_state(indoc! {"
2593 one two
2594 ˇthree
2595 four
2596 "});
2597}
2598
2599#[gpui::test]
2600async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2601 init_test(cx, |settings| {
2602 settings.defaults.hard_tabs = Some(true);
2603 });
2604
2605 let mut cx = EditorTestContext::new(cx).await;
2606
2607 // select two ranges on one line
2608 cx.set_state(indoc! {"
2609 «oneˇ» «twoˇ»
2610 three
2611 four
2612 "});
2613 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2614 cx.assert_editor_state(indoc! {"
2615 \t«oneˇ» «twoˇ»
2616 three
2617 four
2618 "});
2619 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2620 cx.assert_editor_state(indoc! {"
2621 \t\t«oneˇ» «twoˇ»
2622 three
2623 four
2624 "});
2625 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2626 cx.assert_editor_state(indoc! {"
2627 \t«oneˇ» «twoˇ»
2628 three
2629 four
2630 "});
2631 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2632 cx.assert_editor_state(indoc! {"
2633 «oneˇ» «twoˇ»
2634 three
2635 four
2636 "});
2637
2638 // select across a line ending
2639 cx.set_state(indoc! {"
2640 one two
2641 t«hree
2642 ˇ»four
2643 "});
2644 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2645 cx.assert_editor_state(indoc! {"
2646 one two
2647 \tt«hree
2648 ˇ»four
2649 "});
2650 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2651 cx.assert_editor_state(indoc! {"
2652 one two
2653 \t\tt«hree
2654 ˇ»four
2655 "});
2656 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2657 cx.assert_editor_state(indoc! {"
2658 one two
2659 \tt«hree
2660 ˇ»four
2661 "});
2662 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2663 cx.assert_editor_state(indoc! {"
2664 one two
2665 t«hree
2666 ˇ»four
2667 "});
2668
2669 // Ensure that indenting/outdenting works when the cursor is at column 0.
2670 cx.set_state(indoc! {"
2671 one two
2672 ˇthree
2673 four
2674 "});
2675 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2676 cx.assert_editor_state(indoc! {"
2677 one two
2678 ˇthree
2679 four
2680 "});
2681 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2682 cx.assert_editor_state(indoc! {"
2683 one two
2684 \tˇthree
2685 four
2686 "});
2687 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2688 cx.assert_editor_state(indoc! {"
2689 one two
2690 ˇthree
2691 four
2692 "});
2693}
2694
2695#[gpui::test]
2696fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2697 init_test(cx, |settings| {
2698 settings.languages.extend([
2699 (
2700 "TOML".into(),
2701 LanguageSettingsContent {
2702 tab_size: NonZeroU32::new(2),
2703 ..Default::default()
2704 },
2705 ),
2706 (
2707 "Rust".into(),
2708 LanguageSettingsContent {
2709 tab_size: NonZeroU32::new(4),
2710 ..Default::default()
2711 },
2712 ),
2713 ]);
2714 });
2715
2716 let toml_language = Arc::new(Language::new(
2717 LanguageConfig {
2718 name: "TOML".into(),
2719 ..Default::default()
2720 },
2721 None,
2722 ));
2723 let rust_language = Arc::new(Language::new(
2724 LanguageConfig {
2725 name: "Rust".into(),
2726 ..Default::default()
2727 },
2728 None,
2729 ));
2730
2731 let toml_buffer =
2732 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2733 let rust_buffer = cx.new_model(|cx| {
2734 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2735 });
2736 let multibuffer = cx.new_model(|cx| {
2737 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
2738 multibuffer.push_excerpts(
2739 toml_buffer.clone(),
2740 [ExcerptRange {
2741 context: Point::new(0, 0)..Point::new(2, 0),
2742 primary: None,
2743 }],
2744 cx,
2745 );
2746 multibuffer.push_excerpts(
2747 rust_buffer.clone(),
2748 [ExcerptRange {
2749 context: Point::new(0, 0)..Point::new(1, 0),
2750 primary: None,
2751 }],
2752 cx,
2753 );
2754 multibuffer
2755 });
2756
2757 cx.add_window(|cx| {
2758 let mut editor = build_editor(multibuffer, cx);
2759
2760 assert_eq!(
2761 editor.text(cx),
2762 indoc! {"
2763 a = 1
2764 b = 2
2765
2766 const c: usize = 3;
2767 "}
2768 );
2769
2770 select_ranges(
2771 &mut editor,
2772 indoc! {"
2773 «aˇ» = 1
2774 b = 2
2775
2776 «const c:ˇ» usize = 3;
2777 "},
2778 cx,
2779 );
2780
2781 editor.tab(&Tab, cx);
2782 assert_text_with_selections(
2783 &mut editor,
2784 indoc! {"
2785 «aˇ» = 1
2786 b = 2
2787
2788 «const c:ˇ» usize = 3;
2789 "},
2790 cx,
2791 );
2792 editor.tab_prev(&TabPrev, cx);
2793 assert_text_with_selections(
2794 &mut editor,
2795 indoc! {"
2796 «aˇ» = 1
2797 b = 2
2798
2799 «const c:ˇ» usize = 3;
2800 "},
2801 cx,
2802 );
2803
2804 editor
2805 });
2806}
2807
2808#[gpui::test]
2809async fn test_backspace(cx: &mut gpui::TestAppContext) {
2810 init_test(cx, |_| {});
2811
2812 let mut cx = EditorTestContext::new(cx).await;
2813
2814 // Basic backspace
2815 cx.set_state(indoc! {"
2816 onˇe two three
2817 fou«rˇ» five six
2818 seven «ˇeight nine
2819 »ten
2820 "});
2821 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2822 cx.assert_editor_state(indoc! {"
2823 oˇe two three
2824 fouˇ five six
2825 seven ˇten
2826 "});
2827
2828 // Test backspace inside and around indents
2829 cx.set_state(indoc! {"
2830 zero
2831 ˇone
2832 ˇtwo
2833 ˇ ˇ ˇ three
2834 ˇ ˇ four
2835 "});
2836 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2837 cx.assert_editor_state(indoc! {"
2838 zero
2839 ˇone
2840 ˇtwo
2841 ˇ threeˇ four
2842 "});
2843
2844 // Test backspace with line_mode set to true
2845 cx.update_editor(|e, _| e.selections.line_mode = true);
2846 cx.set_state(indoc! {"
2847 The ˇquick ˇbrown
2848 fox jumps over
2849 the lazy dog
2850 ˇThe qu«ick bˇ»rown"});
2851 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2852 cx.assert_editor_state(indoc! {"
2853 ˇfox jumps over
2854 the lazy dogˇ"});
2855}
2856
2857#[gpui::test]
2858async fn test_delete(cx: &mut gpui::TestAppContext) {
2859 init_test(cx, |_| {});
2860
2861 let mut cx = EditorTestContext::new(cx).await;
2862 cx.set_state(indoc! {"
2863 onˇe two three
2864 fou«rˇ» five six
2865 seven «ˇeight nine
2866 »ten
2867 "});
2868 cx.update_editor(|e, cx| e.delete(&Delete, cx));
2869 cx.assert_editor_state(indoc! {"
2870 onˇ two three
2871 fouˇ five six
2872 seven ˇten
2873 "});
2874
2875 // Test backspace with line_mode set to true
2876 cx.update_editor(|e, _| e.selections.line_mode = true);
2877 cx.set_state(indoc! {"
2878 The ˇquick ˇbrown
2879 fox «ˇjum»ps over
2880 the lazy dog
2881 ˇThe qu«ick bˇ»rown"});
2882 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2883 cx.assert_editor_state("ˇthe lazy dogˇ");
2884}
2885
2886#[gpui::test]
2887fn test_delete_line(cx: &mut TestAppContext) {
2888 init_test(cx, |_| {});
2889
2890 let view = cx.add_window(|cx| {
2891 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2892 build_editor(buffer, cx)
2893 });
2894 _ = view.update(cx, |view, cx| {
2895 view.change_selections(None, cx, |s| {
2896 s.select_display_ranges([
2897 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
2898 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
2899 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
2900 ])
2901 });
2902 view.delete_line(&DeleteLine, cx);
2903 assert_eq!(view.display_text(cx), "ghi");
2904 assert_eq!(
2905 view.selections.display_ranges(cx),
2906 vec![
2907 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
2908 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
2909 ]
2910 );
2911 });
2912
2913 let view = cx.add_window(|cx| {
2914 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2915 build_editor(buffer, cx)
2916 });
2917 _ = view.update(cx, |view, cx| {
2918 view.change_selections(None, cx, |s| {
2919 s.select_display_ranges([
2920 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
2921 ])
2922 });
2923 view.delete_line(&DeleteLine, cx);
2924 assert_eq!(view.display_text(cx), "ghi\n");
2925 assert_eq!(
2926 view.selections.display_ranges(cx),
2927 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
2928 );
2929 });
2930}
2931
2932#[gpui::test]
2933fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
2934 init_test(cx, |_| {});
2935
2936 cx.add_window(|cx| {
2937 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
2938 let mut editor = build_editor(buffer.clone(), cx);
2939 let buffer = buffer.read(cx).as_singleton().unwrap();
2940
2941 assert_eq!(
2942 editor.selections.ranges::<Point>(cx),
2943 &[Point::new(0, 0)..Point::new(0, 0)]
2944 );
2945
2946 // When on single line, replace newline at end by space
2947 editor.join_lines(&JoinLines, cx);
2948 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
2949 assert_eq!(
2950 editor.selections.ranges::<Point>(cx),
2951 &[Point::new(0, 3)..Point::new(0, 3)]
2952 );
2953
2954 // When multiple lines are selected, remove newlines that are spanned by the selection
2955 editor.change_selections(None, cx, |s| {
2956 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
2957 });
2958 editor.join_lines(&JoinLines, cx);
2959 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
2960 assert_eq!(
2961 editor.selections.ranges::<Point>(cx),
2962 &[Point::new(0, 11)..Point::new(0, 11)]
2963 );
2964
2965 // Undo should be transactional
2966 editor.undo(&Undo, cx);
2967 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
2968 assert_eq!(
2969 editor.selections.ranges::<Point>(cx),
2970 &[Point::new(0, 5)..Point::new(2, 2)]
2971 );
2972
2973 // When joining an empty line don't insert a space
2974 editor.change_selections(None, cx, |s| {
2975 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
2976 });
2977 editor.join_lines(&JoinLines, cx);
2978 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
2979 assert_eq!(
2980 editor.selections.ranges::<Point>(cx),
2981 [Point::new(2, 3)..Point::new(2, 3)]
2982 );
2983
2984 // We can remove trailing newlines
2985 editor.join_lines(&JoinLines, cx);
2986 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
2987 assert_eq!(
2988 editor.selections.ranges::<Point>(cx),
2989 [Point::new(2, 3)..Point::new(2, 3)]
2990 );
2991
2992 // We don't blow up on the last line
2993 editor.join_lines(&JoinLines, cx);
2994 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
2995 assert_eq!(
2996 editor.selections.ranges::<Point>(cx),
2997 [Point::new(2, 3)..Point::new(2, 3)]
2998 );
2999
3000 // reset to test indentation
3001 editor.buffer.update(cx, |buffer, cx| {
3002 buffer.edit(
3003 [
3004 (Point::new(1, 0)..Point::new(1, 2), " "),
3005 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3006 ],
3007 None,
3008 cx,
3009 )
3010 });
3011
3012 // We remove any leading spaces
3013 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3014 editor.change_selections(None, cx, |s| {
3015 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3016 });
3017 editor.join_lines(&JoinLines, cx);
3018 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3019
3020 // We don't insert a space for a line containing only spaces
3021 editor.join_lines(&JoinLines, cx);
3022 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3023
3024 // We ignore any leading tabs
3025 editor.join_lines(&JoinLines, cx);
3026 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3027
3028 editor
3029 });
3030}
3031
3032#[gpui::test]
3033fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3034 init_test(cx, |_| {});
3035
3036 cx.add_window(|cx| {
3037 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3038 let mut editor = build_editor(buffer.clone(), cx);
3039 let buffer = buffer.read(cx).as_singleton().unwrap();
3040
3041 editor.change_selections(None, cx, |s| {
3042 s.select_ranges([
3043 Point::new(0, 2)..Point::new(1, 1),
3044 Point::new(1, 2)..Point::new(1, 2),
3045 Point::new(3, 1)..Point::new(3, 2),
3046 ])
3047 });
3048
3049 editor.join_lines(&JoinLines, cx);
3050 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3051
3052 assert_eq!(
3053 editor.selections.ranges::<Point>(cx),
3054 [
3055 Point::new(0, 7)..Point::new(0, 7),
3056 Point::new(1, 3)..Point::new(1, 3)
3057 ]
3058 );
3059 editor
3060 });
3061}
3062
3063#[gpui::test]
3064async fn test_join_lines_with_git_diff_base(
3065 executor: BackgroundExecutor,
3066 cx: &mut gpui::TestAppContext,
3067) {
3068 init_test(cx, |_| {});
3069
3070 let mut cx = EditorTestContext::new(cx).await;
3071
3072 let diff_base = r#"
3073 Line 0
3074 Line 1
3075 Line 2
3076 Line 3
3077 "#
3078 .unindent();
3079
3080 cx.set_state(
3081 &r#"
3082 ˇLine 0
3083 Line 1
3084 Line 2
3085 Line 3
3086 "#
3087 .unindent(),
3088 );
3089
3090 cx.set_diff_base(Some(&diff_base));
3091 executor.run_until_parked();
3092
3093 // Join lines
3094 cx.update_editor(|editor, cx| {
3095 editor.join_lines(&JoinLines, cx);
3096 });
3097 executor.run_until_parked();
3098
3099 cx.assert_editor_state(
3100 &r#"
3101 Line 0ˇ Line 1
3102 Line 2
3103 Line 3
3104 "#
3105 .unindent(),
3106 );
3107 // Join again
3108 cx.update_editor(|editor, cx| {
3109 editor.join_lines(&JoinLines, cx);
3110 });
3111 executor.run_until_parked();
3112
3113 cx.assert_editor_state(
3114 &r#"
3115 Line 0 Line 1ˇ Line 2
3116 Line 3
3117 "#
3118 .unindent(),
3119 );
3120}
3121
3122#[gpui::test]
3123async fn test_custom_newlines_cause_no_false_positive_diffs(
3124 executor: BackgroundExecutor,
3125 cx: &mut gpui::TestAppContext,
3126) {
3127 init_test(cx, |_| {});
3128 let mut cx = EditorTestContext::new(cx).await;
3129 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3130 cx.set_diff_base(Some("Line 0\r\nLine 1\r\nLine 2\r\nLine 3"));
3131 executor.run_until_parked();
3132
3133 cx.update_editor(|editor, cx| {
3134 assert_eq!(
3135 editor
3136 .buffer()
3137 .read(cx)
3138 .snapshot(cx)
3139 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
3140 .collect::<Vec<_>>(),
3141 Vec::new(),
3142 "Should not have any diffs for files with custom newlines"
3143 );
3144 });
3145}
3146
3147#[gpui::test]
3148async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3149 init_test(cx, |_| {});
3150
3151 let mut cx = EditorTestContext::new(cx).await;
3152
3153 // Test sort_lines_case_insensitive()
3154 cx.set_state(indoc! {"
3155 «z
3156 y
3157 x
3158 Z
3159 Y
3160 Xˇ»
3161 "});
3162 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3163 cx.assert_editor_state(indoc! {"
3164 «x
3165 X
3166 y
3167 Y
3168 z
3169 Zˇ»
3170 "});
3171
3172 // Test reverse_lines()
3173 cx.set_state(indoc! {"
3174 «5
3175 4
3176 3
3177 2
3178 1ˇ»
3179 "});
3180 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3181 cx.assert_editor_state(indoc! {"
3182 «1
3183 2
3184 3
3185 4
3186 5ˇ»
3187 "});
3188
3189 // Skip testing shuffle_line()
3190
3191 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3192 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3193
3194 // Don't manipulate when cursor is on single line, but expand the selection
3195 cx.set_state(indoc! {"
3196 ddˇdd
3197 ccc
3198 bb
3199 a
3200 "});
3201 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3202 cx.assert_editor_state(indoc! {"
3203 «ddddˇ»
3204 ccc
3205 bb
3206 a
3207 "});
3208
3209 // Basic manipulate case
3210 // Start selection moves to column 0
3211 // End of selection shrinks to fit shorter line
3212 cx.set_state(indoc! {"
3213 dd«d
3214 ccc
3215 bb
3216 aaaaaˇ»
3217 "});
3218 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3219 cx.assert_editor_state(indoc! {"
3220 «aaaaa
3221 bb
3222 ccc
3223 dddˇ»
3224 "});
3225
3226 // Manipulate case with newlines
3227 cx.set_state(indoc! {"
3228 dd«d
3229 ccc
3230
3231 bb
3232 aaaaa
3233
3234 ˇ»
3235 "});
3236 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3237 cx.assert_editor_state(indoc! {"
3238 «
3239
3240 aaaaa
3241 bb
3242 ccc
3243 dddˇ»
3244
3245 "});
3246
3247 // Adding new line
3248 cx.set_state(indoc! {"
3249 aa«a
3250 bbˇ»b
3251 "});
3252 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3253 cx.assert_editor_state(indoc! {"
3254 «aaa
3255 bbb
3256 added_lineˇ»
3257 "});
3258
3259 // Removing line
3260 cx.set_state(indoc! {"
3261 aa«a
3262 bbbˇ»
3263 "});
3264 cx.update_editor(|e, cx| {
3265 e.manipulate_lines(cx, |lines| {
3266 lines.pop();
3267 })
3268 });
3269 cx.assert_editor_state(indoc! {"
3270 «aaaˇ»
3271 "});
3272
3273 // Removing all lines
3274 cx.set_state(indoc! {"
3275 aa«a
3276 bbbˇ»
3277 "});
3278 cx.update_editor(|e, cx| {
3279 e.manipulate_lines(cx, |lines| {
3280 lines.drain(..);
3281 })
3282 });
3283 cx.assert_editor_state(indoc! {"
3284 ˇ
3285 "});
3286}
3287
3288#[gpui::test]
3289async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3290 init_test(cx, |_| {});
3291
3292 let mut cx = EditorTestContext::new(cx).await;
3293
3294 // Consider continuous selection as single selection
3295 cx.set_state(indoc! {"
3296 Aaa«aa
3297 cˇ»c«c
3298 bb
3299 aaaˇ»aa
3300 "});
3301 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3302 cx.assert_editor_state(indoc! {"
3303 «Aaaaa
3304 ccc
3305 bb
3306 aaaaaˇ»
3307 "});
3308
3309 cx.set_state(indoc! {"
3310 Aaa«aa
3311 cˇ»c«c
3312 bb
3313 aaaˇ»aa
3314 "});
3315 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3316 cx.assert_editor_state(indoc! {"
3317 «Aaaaa
3318 ccc
3319 bbˇ»
3320 "});
3321
3322 // Consider non continuous selection as distinct dedup operations
3323 cx.set_state(indoc! {"
3324 «aaaaa
3325 bb
3326 aaaaa
3327 aaaaaˇ»
3328
3329 aaa«aaˇ»
3330 "});
3331 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3332 cx.assert_editor_state(indoc! {"
3333 «aaaaa
3334 bbˇ»
3335
3336 «aaaaaˇ»
3337 "});
3338}
3339
3340#[gpui::test]
3341async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3342 init_test(cx, |_| {});
3343
3344 let mut cx = EditorTestContext::new(cx).await;
3345
3346 cx.set_state(indoc! {"
3347 «Aaa
3348 aAa
3349 Aaaˇ»
3350 "});
3351 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3352 cx.assert_editor_state(indoc! {"
3353 «Aaa
3354 aAaˇ»
3355 "});
3356
3357 cx.set_state(indoc! {"
3358 «Aaa
3359 aAa
3360 aaAˇ»
3361 "});
3362 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3363 cx.assert_editor_state(indoc! {"
3364 «Aaaˇ»
3365 "});
3366}
3367
3368#[gpui::test]
3369async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3370 init_test(cx, |_| {});
3371
3372 let mut cx = EditorTestContext::new(cx).await;
3373
3374 // Manipulate with multiple selections on a single line
3375 cx.set_state(indoc! {"
3376 dd«dd
3377 cˇ»c«c
3378 bb
3379 aaaˇ»aa
3380 "});
3381 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3382 cx.assert_editor_state(indoc! {"
3383 «aaaaa
3384 bb
3385 ccc
3386 ddddˇ»
3387 "});
3388
3389 // Manipulate with multiple disjoin selections
3390 cx.set_state(indoc! {"
3391 5«
3392 4
3393 3
3394 2
3395 1ˇ»
3396
3397 dd«dd
3398 ccc
3399 bb
3400 aaaˇ»aa
3401 "});
3402 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3403 cx.assert_editor_state(indoc! {"
3404 «1
3405 2
3406 3
3407 4
3408 5ˇ»
3409
3410 «aaaaa
3411 bb
3412 ccc
3413 ddddˇ»
3414 "});
3415
3416 // Adding lines on each selection
3417 cx.set_state(indoc! {"
3418 2«
3419 1ˇ»
3420
3421 bb«bb
3422 aaaˇ»aa
3423 "});
3424 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3425 cx.assert_editor_state(indoc! {"
3426 «2
3427 1
3428 added lineˇ»
3429
3430 «bbbb
3431 aaaaa
3432 added lineˇ»
3433 "});
3434
3435 // Removing lines on each selection
3436 cx.set_state(indoc! {"
3437 2«
3438 1ˇ»
3439
3440 bb«bb
3441 aaaˇ»aa
3442 "});
3443 cx.update_editor(|e, cx| {
3444 e.manipulate_lines(cx, |lines| {
3445 lines.pop();
3446 })
3447 });
3448 cx.assert_editor_state(indoc! {"
3449 «2ˇ»
3450
3451 «bbbbˇ»
3452 "});
3453}
3454
3455#[gpui::test]
3456async fn test_manipulate_text(cx: &mut TestAppContext) {
3457 init_test(cx, |_| {});
3458
3459 let mut cx = EditorTestContext::new(cx).await;
3460
3461 // Test convert_to_upper_case()
3462 cx.set_state(indoc! {"
3463 «hello worldˇ»
3464 "});
3465 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3466 cx.assert_editor_state(indoc! {"
3467 «HELLO WORLDˇ»
3468 "});
3469
3470 // Test convert_to_lower_case()
3471 cx.set_state(indoc! {"
3472 «HELLO WORLDˇ»
3473 "});
3474 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3475 cx.assert_editor_state(indoc! {"
3476 «hello worldˇ»
3477 "});
3478
3479 // Test multiple line, single selection case
3480 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3481 cx.set_state(indoc! {"
3482 «The quick brown
3483 fox jumps over
3484 the lazy dogˇ»
3485 "});
3486 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3487 cx.assert_editor_state(indoc! {"
3488 «The Quick Brown
3489 Fox Jumps Over
3490 The Lazy Dogˇ»
3491 "});
3492
3493 // Test multiple line, single selection case
3494 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3495 cx.set_state(indoc! {"
3496 «The quick brown
3497 fox jumps over
3498 the lazy dogˇ»
3499 "});
3500 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3501 cx.assert_editor_state(indoc! {"
3502 «TheQuickBrown
3503 FoxJumpsOver
3504 TheLazyDogˇ»
3505 "});
3506
3507 // From here on out, test more complex cases of manipulate_text()
3508
3509 // Test no selection case - should affect words cursors are in
3510 // Cursor at beginning, middle, and end of word
3511 cx.set_state(indoc! {"
3512 ˇhello big beauˇtiful worldˇ
3513 "});
3514 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3515 cx.assert_editor_state(indoc! {"
3516 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3517 "});
3518
3519 // Test multiple selections on a single line and across multiple lines
3520 cx.set_state(indoc! {"
3521 «Theˇ» quick «brown
3522 foxˇ» jumps «overˇ»
3523 the «lazyˇ» dog
3524 "});
3525 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3526 cx.assert_editor_state(indoc! {"
3527 «THEˇ» quick «BROWN
3528 FOXˇ» jumps «OVERˇ»
3529 the «LAZYˇ» dog
3530 "});
3531
3532 // Test case where text length grows
3533 cx.set_state(indoc! {"
3534 «tschüߡ»
3535 "});
3536 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3537 cx.assert_editor_state(indoc! {"
3538 «TSCHÜSSˇ»
3539 "});
3540
3541 // Test to make sure we don't crash when text shrinks
3542 cx.set_state(indoc! {"
3543 aaa_bbbˇ
3544 "});
3545 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3546 cx.assert_editor_state(indoc! {"
3547 «aaaBbbˇ»
3548 "});
3549
3550 // Test to make sure we all aware of the fact that each word can grow and shrink
3551 // Final selections should be aware of this fact
3552 cx.set_state(indoc! {"
3553 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3554 "});
3555 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3556 cx.assert_editor_state(indoc! {"
3557 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3558 "});
3559
3560 cx.set_state(indoc! {"
3561 «hElLo, WoRld!ˇ»
3562 "});
3563 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3564 cx.assert_editor_state(indoc! {"
3565 «HeLlO, wOrLD!ˇ»
3566 "});
3567}
3568
3569#[gpui::test]
3570fn test_duplicate_line(cx: &mut TestAppContext) {
3571 init_test(cx, |_| {});
3572
3573 let view = cx.add_window(|cx| {
3574 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3575 build_editor(buffer, cx)
3576 });
3577 _ = view.update(cx, |view, cx| {
3578 view.change_selections(None, cx, |s| {
3579 s.select_display_ranges([
3580 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3581 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3582 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3583 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3584 ])
3585 });
3586 view.duplicate_line_down(&DuplicateLineDown, cx);
3587 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3588 assert_eq!(
3589 view.selections.display_ranges(cx),
3590 vec![
3591 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3592 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3593 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3594 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3595 ]
3596 );
3597 });
3598
3599 let view = cx.add_window(|cx| {
3600 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3601 build_editor(buffer, cx)
3602 });
3603 _ = view.update(cx, |view, cx| {
3604 view.change_selections(None, cx, |s| {
3605 s.select_display_ranges([
3606 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3607 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3608 ])
3609 });
3610 view.duplicate_line_down(&DuplicateLineDown, cx);
3611 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3612 assert_eq!(
3613 view.selections.display_ranges(cx),
3614 vec![
3615 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3616 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3617 ]
3618 );
3619 });
3620
3621 // With `move_upwards` the selections stay in place, except for
3622 // the lines inserted above them
3623 let view = cx.add_window(|cx| {
3624 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3625 build_editor(buffer, cx)
3626 });
3627 _ = view.update(cx, |view, cx| {
3628 view.change_selections(None, cx, |s| {
3629 s.select_display_ranges([
3630 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3631 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3632 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3633 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3634 ])
3635 });
3636 view.duplicate_line_up(&DuplicateLineUp, cx);
3637 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3638 assert_eq!(
3639 view.selections.display_ranges(cx),
3640 vec![
3641 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3642 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3643 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3644 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3645 ]
3646 );
3647 });
3648
3649 let view = cx.add_window(|cx| {
3650 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3651 build_editor(buffer, cx)
3652 });
3653 _ = view.update(cx, |view, cx| {
3654 view.change_selections(None, cx, |s| {
3655 s.select_display_ranges([
3656 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3657 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3658 ])
3659 });
3660 view.duplicate_line_up(&DuplicateLineUp, cx);
3661 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3662 assert_eq!(
3663 view.selections.display_ranges(cx),
3664 vec![
3665 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3666 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3667 ]
3668 );
3669 });
3670}
3671
3672#[gpui::test]
3673fn test_move_line_up_down(cx: &mut TestAppContext) {
3674 init_test(cx, |_| {});
3675
3676 let view = cx.add_window(|cx| {
3677 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3678 build_editor(buffer, cx)
3679 });
3680 _ = view.update(cx, |view, cx| {
3681 view.fold_ranges(
3682 vec![
3683 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3684 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3685 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3686 ],
3687 true,
3688 cx,
3689 );
3690 view.change_selections(None, cx, |s| {
3691 s.select_display_ranges([
3692 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3693 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3694 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3695 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3696 ])
3697 });
3698 assert_eq!(
3699 view.display_text(cx),
3700 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3701 );
3702
3703 view.move_line_up(&MoveLineUp, cx);
3704 assert_eq!(
3705 view.display_text(cx),
3706 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3707 );
3708 assert_eq!(
3709 view.selections.display_ranges(cx),
3710 vec![
3711 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3712 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3713 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3714 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3715 ]
3716 );
3717 });
3718
3719 _ = view.update(cx, |view, cx| {
3720 view.move_line_down(&MoveLineDown, cx);
3721 assert_eq!(
3722 view.display_text(cx),
3723 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3724 );
3725 assert_eq!(
3726 view.selections.display_ranges(cx),
3727 vec![
3728 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3729 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3730 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3731 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3732 ]
3733 );
3734 });
3735
3736 _ = view.update(cx, |view, cx| {
3737 view.move_line_down(&MoveLineDown, cx);
3738 assert_eq!(
3739 view.display_text(cx),
3740 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3741 );
3742 assert_eq!(
3743 view.selections.display_ranges(cx),
3744 vec![
3745 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3746 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3747 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3748 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3749 ]
3750 );
3751 });
3752
3753 _ = view.update(cx, |view, cx| {
3754 view.move_line_up(&MoveLineUp, cx);
3755 assert_eq!(
3756 view.display_text(cx),
3757 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3758 );
3759 assert_eq!(
3760 view.selections.display_ranges(cx),
3761 vec![
3762 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3763 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3764 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3765 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3766 ]
3767 );
3768 });
3769}
3770
3771#[gpui::test]
3772fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3773 init_test(cx, |_| {});
3774
3775 let editor = cx.add_window(|cx| {
3776 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3777 build_editor(buffer, cx)
3778 });
3779 _ = editor.update(cx, |editor, cx| {
3780 let snapshot = editor.buffer.read(cx).snapshot(cx);
3781 editor.insert_blocks(
3782 [BlockProperties {
3783 style: BlockStyle::Fixed,
3784 position: snapshot.anchor_after(Point::new(2, 0)),
3785 disposition: BlockDisposition::Below,
3786 height: 1,
3787 render: Box::new(|_| div().into_any()),
3788 }],
3789 Some(Autoscroll::fit()),
3790 cx,
3791 );
3792 editor.change_selections(None, cx, |s| {
3793 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
3794 });
3795 editor.move_line_down(&MoveLineDown, cx);
3796 });
3797}
3798
3799#[gpui::test]
3800fn test_transpose(cx: &mut TestAppContext) {
3801 init_test(cx, |_| {});
3802
3803 _ = cx.add_window(|cx| {
3804 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
3805 editor.set_style(EditorStyle::default(), cx);
3806 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
3807 editor.transpose(&Default::default(), cx);
3808 assert_eq!(editor.text(cx), "bac");
3809 assert_eq!(editor.selections.ranges(cx), [2..2]);
3810
3811 editor.transpose(&Default::default(), cx);
3812 assert_eq!(editor.text(cx), "bca");
3813 assert_eq!(editor.selections.ranges(cx), [3..3]);
3814
3815 editor.transpose(&Default::default(), cx);
3816 assert_eq!(editor.text(cx), "bac");
3817 assert_eq!(editor.selections.ranges(cx), [3..3]);
3818
3819 editor
3820 });
3821
3822 _ = cx.add_window(|cx| {
3823 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3824 editor.set_style(EditorStyle::default(), cx);
3825 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
3826 editor.transpose(&Default::default(), cx);
3827 assert_eq!(editor.text(cx), "acb\nde");
3828 assert_eq!(editor.selections.ranges(cx), [3..3]);
3829
3830 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3831 editor.transpose(&Default::default(), cx);
3832 assert_eq!(editor.text(cx), "acbd\ne");
3833 assert_eq!(editor.selections.ranges(cx), [5..5]);
3834
3835 editor.transpose(&Default::default(), cx);
3836 assert_eq!(editor.text(cx), "acbde\n");
3837 assert_eq!(editor.selections.ranges(cx), [6..6]);
3838
3839 editor.transpose(&Default::default(), cx);
3840 assert_eq!(editor.text(cx), "acbd\ne");
3841 assert_eq!(editor.selections.ranges(cx), [6..6]);
3842
3843 editor
3844 });
3845
3846 _ = cx.add_window(|cx| {
3847 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3848 editor.set_style(EditorStyle::default(), cx);
3849 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
3850 editor.transpose(&Default::default(), cx);
3851 assert_eq!(editor.text(cx), "bacd\ne");
3852 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
3853
3854 editor.transpose(&Default::default(), cx);
3855 assert_eq!(editor.text(cx), "bcade\n");
3856 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
3857
3858 editor.transpose(&Default::default(), cx);
3859 assert_eq!(editor.text(cx), "bcda\ne");
3860 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3861
3862 editor.transpose(&Default::default(), cx);
3863 assert_eq!(editor.text(cx), "bcade\n");
3864 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3865
3866 editor.transpose(&Default::default(), cx);
3867 assert_eq!(editor.text(cx), "bcaed\n");
3868 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
3869
3870 editor
3871 });
3872
3873 _ = cx.add_window(|cx| {
3874 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
3875 editor.set_style(EditorStyle::default(), cx);
3876 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3877 editor.transpose(&Default::default(), cx);
3878 assert_eq!(editor.text(cx), "🏀🍐✋");
3879 assert_eq!(editor.selections.ranges(cx), [8..8]);
3880
3881 editor.transpose(&Default::default(), cx);
3882 assert_eq!(editor.text(cx), "🏀✋🍐");
3883 assert_eq!(editor.selections.ranges(cx), [11..11]);
3884
3885 editor.transpose(&Default::default(), cx);
3886 assert_eq!(editor.text(cx), "🏀🍐✋");
3887 assert_eq!(editor.selections.ranges(cx), [11..11]);
3888
3889 editor
3890 });
3891}
3892
3893#[gpui::test]
3894async fn test_clipboard(cx: &mut gpui::TestAppContext) {
3895 init_test(cx, |_| {});
3896
3897 let mut cx = EditorTestContext::new(cx).await;
3898
3899 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
3900 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3901 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
3902
3903 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
3904 cx.set_state("two ˇfour ˇsix ˇ");
3905 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3906 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
3907
3908 // Paste again but with only two cursors. Since the number of cursors doesn't
3909 // match the number of slices in the clipboard, the entire clipboard text
3910 // is pasted at each cursor.
3911 cx.set_state("ˇtwo one✅ four three six five ˇ");
3912 cx.update_editor(|e, cx| {
3913 e.handle_input("( ", cx);
3914 e.paste(&Paste, cx);
3915 e.handle_input(") ", cx);
3916 });
3917 cx.assert_editor_state(
3918 &([
3919 "( one✅ ",
3920 "three ",
3921 "five ) ˇtwo one✅ four three six five ( one✅ ",
3922 "three ",
3923 "five ) ˇ",
3924 ]
3925 .join("\n")),
3926 );
3927
3928 // Cut with three selections, one of which is full-line.
3929 cx.set_state(indoc! {"
3930 1«2ˇ»3
3931 4ˇ567
3932 «8ˇ»9"});
3933 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3934 cx.assert_editor_state(indoc! {"
3935 1ˇ3
3936 ˇ9"});
3937
3938 // Paste with three selections, noticing how the copied selection that was full-line
3939 // gets inserted before the second cursor.
3940 cx.set_state(indoc! {"
3941 1ˇ3
3942 9ˇ
3943 «oˇ»ne"});
3944 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3945 cx.assert_editor_state(indoc! {"
3946 12ˇ3
3947 4567
3948 9ˇ
3949 8ˇne"});
3950
3951 // Copy with a single cursor only, which writes the whole line into the clipboard.
3952 cx.set_state(indoc! {"
3953 The quick brown
3954 fox juˇmps over
3955 the lazy dog"});
3956 cx.update_editor(|e, cx| e.copy(&Copy, cx));
3957 assert_eq!(
3958 cx.read_from_clipboard().map(|item| item.text().to_owned()),
3959 Some("fox jumps over\n".to_owned())
3960 );
3961
3962 // Paste with three selections, noticing how the copied full-line selection is inserted
3963 // before the empty selections but replaces the selection that is non-empty.
3964 cx.set_state(indoc! {"
3965 Tˇhe quick brown
3966 «foˇ»x jumps over
3967 tˇhe lazy dog"});
3968 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3969 cx.assert_editor_state(indoc! {"
3970 fox jumps over
3971 Tˇhe quick brown
3972 fox jumps over
3973 ˇx jumps over
3974 fox jumps over
3975 tˇhe lazy dog"});
3976}
3977
3978#[gpui::test]
3979async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
3980 init_test(cx, |_| {});
3981
3982 let mut cx = EditorTestContext::new(cx).await;
3983 let language = Arc::new(Language::new(
3984 LanguageConfig::default(),
3985 Some(tree_sitter_rust::language()),
3986 ));
3987 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
3988
3989 // Cut an indented block, without the leading whitespace.
3990 cx.set_state(indoc! {"
3991 const a: B = (
3992 c(),
3993 «d(
3994 e,
3995 f
3996 )ˇ»
3997 );
3998 "});
3999 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4000 cx.assert_editor_state(indoc! {"
4001 const a: B = (
4002 c(),
4003 ˇ
4004 );
4005 "});
4006
4007 // Paste it at the same position.
4008 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4009 cx.assert_editor_state(indoc! {"
4010 const a: B = (
4011 c(),
4012 d(
4013 e,
4014 f
4015 )ˇ
4016 );
4017 "});
4018
4019 // Paste it at a line with a lower indent level.
4020 cx.set_state(indoc! {"
4021 ˇ
4022 const a: B = (
4023 c(),
4024 );
4025 "});
4026 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4027 cx.assert_editor_state(indoc! {"
4028 d(
4029 e,
4030 f
4031 )ˇ
4032 const a: B = (
4033 c(),
4034 );
4035 "});
4036
4037 // Cut an indented block, with the leading whitespace.
4038 cx.set_state(indoc! {"
4039 const a: B = (
4040 c(),
4041 « d(
4042 e,
4043 f
4044 )
4045 ˇ»);
4046 "});
4047 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4048 cx.assert_editor_state(indoc! {"
4049 const a: B = (
4050 c(),
4051 ˇ);
4052 "});
4053
4054 // Paste it at the same position.
4055 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4056 cx.assert_editor_state(indoc! {"
4057 const a: B = (
4058 c(),
4059 d(
4060 e,
4061 f
4062 )
4063 ˇ);
4064 "});
4065
4066 // Paste it at a line with a higher indent level.
4067 cx.set_state(indoc! {"
4068 const a: B = (
4069 c(),
4070 d(
4071 e,
4072 fˇ
4073 )
4074 );
4075 "});
4076 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4077 cx.assert_editor_state(indoc! {"
4078 const a: B = (
4079 c(),
4080 d(
4081 e,
4082 f d(
4083 e,
4084 f
4085 )
4086 ˇ
4087 )
4088 );
4089 "});
4090}
4091
4092#[gpui::test]
4093fn test_select_all(cx: &mut TestAppContext) {
4094 init_test(cx, |_| {});
4095
4096 let view = cx.add_window(|cx| {
4097 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4098 build_editor(buffer, cx)
4099 });
4100 _ = view.update(cx, |view, cx| {
4101 view.select_all(&SelectAll, cx);
4102 assert_eq!(
4103 view.selections.display_ranges(cx),
4104 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4105 );
4106 });
4107}
4108
4109#[gpui::test]
4110fn test_select_line(cx: &mut TestAppContext) {
4111 init_test(cx, |_| {});
4112
4113 let view = cx.add_window(|cx| {
4114 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4115 build_editor(buffer, cx)
4116 });
4117 _ = view.update(cx, |view, cx| {
4118 view.change_selections(None, cx, |s| {
4119 s.select_display_ranges([
4120 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4121 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4122 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4123 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4124 ])
4125 });
4126 view.select_line(&SelectLine, cx);
4127 assert_eq!(
4128 view.selections.display_ranges(cx),
4129 vec![
4130 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4131 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4132 ]
4133 );
4134 });
4135
4136 _ = view.update(cx, |view, cx| {
4137 view.select_line(&SelectLine, cx);
4138 assert_eq!(
4139 view.selections.display_ranges(cx),
4140 vec![
4141 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4142 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4143 ]
4144 );
4145 });
4146
4147 _ = view.update(cx, |view, cx| {
4148 view.select_line(&SelectLine, cx);
4149 assert_eq!(
4150 view.selections.display_ranges(cx),
4151 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4152 );
4153 });
4154}
4155
4156#[gpui::test]
4157fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4158 init_test(cx, |_| {});
4159
4160 let view = cx.add_window(|cx| {
4161 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4162 build_editor(buffer, cx)
4163 });
4164 _ = view.update(cx, |view, cx| {
4165 view.fold_ranges(
4166 vec![
4167 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4168 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4169 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4170 ],
4171 true,
4172 cx,
4173 );
4174 view.change_selections(None, cx, |s| {
4175 s.select_display_ranges([
4176 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4177 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4178 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4179 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4180 ])
4181 });
4182 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4183 });
4184
4185 _ = view.update(cx, |view, cx| {
4186 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4187 assert_eq!(
4188 view.display_text(cx),
4189 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4190 );
4191 assert_eq!(
4192 view.selections.display_ranges(cx),
4193 [
4194 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4195 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4196 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4197 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4198 ]
4199 );
4200 });
4201
4202 _ = view.update(cx, |view, cx| {
4203 view.change_selections(None, cx, |s| {
4204 s.select_display_ranges([
4205 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4206 ])
4207 });
4208 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4209 assert_eq!(
4210 view.display_text(cx),
4211 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4212 );
4213 assert_eq!(
4214 view.selections.display_ranges(cx),
4215 [
4216 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4217 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4218 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4219 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4220 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4221 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4222 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4223 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4224 ]
4225 );
4226 });
4227}
4228
4229#[gpui::test]
4230async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4231 init_test(cx, |_| {});
4232
4233 let mut cx = EditorTestContext::new(cx).await;
4234
4235 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4236 cx.set_state(indoc!(
4237 r#"abc
4238 defˇghi
4239
4240 jk
4241 nlmo
4242 "#
4243 ));
4244
4245 cx.update_editor(|editor, cx| {
4246 editor.add_selection_above(&Default::default(), cx);
4247 });
4248
4249 cx.assert_editor_state(indoc!(
4250 r#"abcˇ
4251 defˇghi
4252
4253 jk
4254 nlmo
4255 "#
4256 ));
4257
4258 cx.update_editor(|editor, cx| {
4259 editor.add_selection_above(&Default::default(), cx);
4260 });
4261
4262 cx.assert_editor_state(indoc!(
4263 r#"abcˇ
4264 defˇghi
4265
4266 jk
4267 nlmo
4268 "#
4269 ));
4270
4271 cx.update_editor(|view, cx| {
4272 view.add_selection_below(&Default::default(), cx);
4273 });
4274
4275 cx.assert_editor_state(indoc!(
4276 r#"abc
4277 defˇghi
4278
4279 jk
4280 nlmo
4281 "#
4282 ));
4283
4284 cx.update_editor(|view, cx| {
4285 view.undo_selection(&Default::default(), cx);
4286 });
4287
4288 cx.assert_editor_state(indoc!(
4289 r#"abcˇ
4290 defˇghi
4291
4292 jk
4293 nlmo
4294 "#
4295 ));
4296
4297 cx.update_editor(|view, cx| {
4298 view.redo_selection(&Default::default(), cx);
4299 });
4300
4301 cx.assert_editor_state(indoc!(
4302 r#"abc
4303 defˇghi
4304
4305 jk
4306 nlmo
4307 "#
4308 ));
4309
4310 cx.update_editor(|view, cx| {
4311 view.add_selection_below(&Default::default(), cx);
4312 });
4313
4314 cx.assert_editor_state(indoc!(
4315 r#"abc
4316 defˇghi
4317
4318 jk
4319 nlmˇo
4320 "#
4321 ));
4322
4323 cx.update_editor(|view, cx| {
4324 view.add_selection_below(&Default::default(), cx);
4325 });
4326
4327 cx.assert_editor_state(indoc!(
4328 r#"abc
4329 defˇghi
4330
4331 jk
4332 nlmˇo
4333 "#
4334 ));
4335
4336 // change selections
4337 cx.set_state(indoc!(
4338 r#"abc
4339 def«ˇg»hi
4340
4341 jk
4342 nlmo
4343 "#
4344 ));
4345
4346 cx.update_editor(|view, cx| {
4347 view.add_selection_below(&Default::default(), cx);
4348 });
4349
4350 cx.assert_editor_state(indoc!(
4351 r#"abc
4352 def«ˇg»hi
4353
4354 jk
4355 nlm«ˇo»
4356 "#
4357 ));
4358
4359 cx.update_editor(|view, cx| {
4360 view.add_selection_below(&Default::default(), cx);
4361 });
4362
4363 cx.assert_editor_state(indoc!(
4364 r#"abc
4365 def«ˇg»hi
4366
4367 jk
4368 nlm«ˇo»
4369 "#
4370 ));
4371
4372 cx.update_editor(|view, cx| {
4373 view.add_selection_above(&Default::default(), cx);
4374 });
4375
4376 cx.assert_editor_state(indoc!(
4377 r#"abc
4378 def«ˇg»hi
4379
4380 jk
4381 nlmo
4382 "#
4383 ));
4384
4385 cx.update_editor(|view, cx| {
4386 view.add_selection_above(&Default::default(), cx);
4387 });
4388
4389 cx.assert_editor_state(indoc!(
4390 r#"abc
4391 def«ˇg»hi
4392
4393 jk
4394 nlmo
4395 "#
4396 ));
4397
4398 // Change selections again
4399 cx.set_state(indoc!(
4400 r#"a«bc
4401 defgˇ»hi
4402
4403 jk
4404 nlmo
4405 "#
4406 ));
4407
4408 cx.update_editor(|view, cx| {
4409 view.add_selection_below(&Default::default(), cx);
4410 });
4411
4412 cx.assert_editor_state(indoc!(
4413 r#"a«bcˇ»
4414 d«efgˇ»hi
4415
4416 j«kˇ»
4417 nlmo
4418 "#
4419 ));
4420
4421 cx.update_editor(|view, cx| {
4422 view.add_selection_below(&Default::default(), cx);
4423 });
4424 cx.assert_editor_state(indoc!(
4425 r#"a«bcˇ»
4426 d«efgˇ»hi
4427
4428 j«kˇ»
4429 n«lmoˇ»
4430 "#
4431 ));
4432 cx.update_editor(|view, cx| {
4433 view.add_selection_above(&Default::default(), cx);
4434 });
4435
4436 cx.assert_editor_state(indoc!(
4437 r#"a«bcˇ»
4438 d«efgˇ»hi
4439
4440 j«kˇ»
4441 nlmo
4442 "#
4443 ));
4444
4445 // Change selections again
4446 cx.set_state(indoc!(
4447 r#"abc
4448 d«ˇefghi
4449
4450 jk
4451 nlm»o
4452 "#
4453 ));
4454
4455 cx.update_editor(|view, cx| {
4456 view.add_selection_above(&Default::default(), cx);
4457 });
4458
4459 cx.assert_editor_state(indoc!(
4460 r#"a«ˇbc»
4461 d«ˇef»ghi
4462
4463 j«ˇk»
4464 n«ˇlm»o
4465 "#
4466 ));
4467
4468 cx.update_editor(|view, cx| {
4469 view.add_selection_below(&Default::default(), cx);
4470 });
4471
4472 cx.assert_editor_state(indoc!(
4473 r#"abc
4474 d«ˇef»ghi
4475
4476 j«ˇk»
4477 n«ˇlm»o
4478 "#
4479 ));
4480}
4481
4482#[gpui::test]
4483async fn test_select_next(cx: &mut gpui::TestAppContext) {
4484 init_test(cx, |_| {});
4485
4486 let mut cx = EditorTestContext::new(cx).await;
4487 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4488
4489 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4490 .unwrap();
4491 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4492
4493 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4494 .unwrap();
4495 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4496
4497 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4498 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4499
4500 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4501 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4502
4503 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4504 .unwrap();
4505 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4506
4507 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4508 .unwrap();
4509 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4510}
4511
4512#[gpui::test]
4513async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
4514 init_test(cx, |_| {});
4515
4516 let mut cx = EditorTestContext::new(cx).await;
4517 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4518
4519 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
4520 .unwrap();
4521 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4522}
4523
4524#[gpui::test]
4525async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4526 init_test(cx, |_| {});
4527
4528 let mut cx = EditorTestContext::new(cx).await;
4529 cx.set_state(
4530 r#"let foo = 2;
4531lˇet foo = 2;
4532let fooˇ = 2;
4533let foo = 2;
4534let foo = ˇ2;"#,
4535 );
4536
4537 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4538 .unwrap();
4539 cx.assert_editor_state(
4540 r#"let foo = 2;
4541«letˇ» foo = 2;
4542let «fooˇ» = 2;
4543let foo = 2;
4544let foo = «2ˇ»;"#,
4545 );
4546
4547 // noop for multiple selections with different contents
4548 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4549 .unwrap();
4550 cx.assert_editor_state(
4551 r#"let foo = 2;
4552«letˇ» foo = 2;
4553let «fooˇ» = 2;
4554let foo = 2;
4555let foo = «2ˇ»;"#,
4556 );
4557}
4558
4559#[gpui::test]
4560async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
4561 init_test(cx, |_| {});
4562
4563 let mut cx = EditorTestContext::new_multibuffer(
4564 cx,
4565 [
4566 &indoc! {
4567 "aaa\n«bbb\nccc\n»ddd"
4568 },
4569 &indoc! {
4570 "aaa\n«bbb\nccc\n»ddd"
4571 },
4572 ],
4573 );
4574
4575 cx.assert_editor_state(indoc! {"
4576 ˇbbb
4577 ccc
4578
4579 bbb
4580 ccc
4581 "});
4582 cx.dispatch_action(SelectPrevious::default());
4583 cx.assert_editor_state(indoc! {"
4584 «bbbˇ»
4585 ccc
4586
4587 bbb
4588 ccc
4589 "});
4590 cx.dispatch_action(SelectPrevious::default());
4591 cx.assert_editor_state(indoc! {"
4592 «bbbˇ»
4593 ccc
4594
4595 «bbbˇ»
4596 ccc
4597 "});
4598}
4599
4600#[gpui::test]
4601async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
4602 init_test(cx, |_| {});
4603
4604 let mut cx = EditorTestContext::new(cx).await;
4605 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4606
4607 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4608 .unwrap();
4609 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4610
4611 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4612 .unwrap();
4613 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4614
4615 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4616 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4617
4618 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4619 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4620
4621 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4622 .unwrap();
4623 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
4624
4625 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4626 .unwrap();
4627 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
4628
4629 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4630 .unwrap();
4631 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
4632}
4633
4634#[gpui::test]
4635async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4636 init_test(cx, |_| {});
4637
4638 let mut cx = EditorTestContext::new(cx).await;
4639 cx.set_state(
4640 r#"let foo = 2;
4641lˇet foo = 2;
4642let fooˇ = 2;
4643let foo = 2;
4644let foo = ˇ2;"#,
4645 );
4646
4647 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4648 .unwrap();
4649 cx.assert_editor_state(
4650 r#"let foo = 2;
4651«letˇ» foo = 2;
4652let «fooˇ» = 2;
4653let foo = 2;
4654let foo = «2ˇ»;"#,
4655 );
4656
4657 // noop for multiple selections with different contents
4658 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4659 .unwrap();
4660 cx.assert_editor_state(
4661 r#"let foo = 2;
4662«letˇ» foo = 2;
4663let «fooˇ» = 2;
4664let foo = 2;
4665let foo = «2ˇ»;"#,
4666 );
4667}
4668
4669#[gpui::test]
4670async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
4671 init_test(cx, |_| {});
4672
4673 let mut cx = EditorTestContext::new(cx).await;
4674 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
4675
4676 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4677 .unwrap();
4678 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
4679
4680 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4681 .unwrap();
4682 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
4683
4684 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4685 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
4686
4687 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4688 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
4689
4690 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4691 .unwrap();
4692 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
4693
4694 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4695 .unwrap();
4696 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
4697}
4698
4699#[gpui::test]
4700async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
4701 init_test(cx, |_| {});
4702
4703 let language = Arc::new(Language::new(
4704 LanguageConfig::default(),
4705 Some(tree_sitter_rust::language()),
4706 ));
4707
4708 let text = r#"
4709 use mod1::mod2::{mod3, mod4};
4710
4711 fn fn_1(param1: bool, param2: &str) {
4712 let var1 = "text";
4713 }
4714 "#
4715 .unindent();
4716
4717 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
4718 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4719 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4720
4721 view.condition::<crate::EditorEvent>(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4722 .await;
4723
4724 _ = view.update(cx, |view, cx| {
4725 view.change_selections(None, cx, |s| {
4726 s.select_display_ranges([
4727 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
4728 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
4729 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
4730 ]);
4731 });
4732 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4733 });
4734 assert_eq!(
4735 view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
4736 &[
4737 DisplayPoint::new(DisplayRow(0), 23)..DisplayPoint::new(DisplayRow(0), 27),
4738 DisplayPoint::new(DisplayRow(2), 35)..DisplayPoint::new(DisplayRow(2), 7),
4739 DisplayPoint::new(DisplayRow(3), 15)..DisplayPoint::new(DisplayRow(3), 21),
4740 ]
4741 );
4742
4743 _ = view.update(cx, |view, cx| {
4744 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4745 });
4746 assert_eq!(
4747 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4748 &[
4749 DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 28),
4750 DisplayPoint::new(DisplayRow(4), 1)..DisplayPoint::new(DisplayRow(2), 0),
4751 ]
4752 );
4753
4754 _ = view.update(cx, |view, cx| {
4755 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4756 });
4757 assert_eq!(
4758 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4759 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
4760 );
4761
4762 // Trying to expand the selected syntax node one more time has no effect.
4763 _ = view.update(cx, |view, cx| {
4764 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4765 });
4766 assert_eq!(
4767 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4768 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
4769 );
4770
4771 _ = view.update(cx, |view, cx| {
4772 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4773 });
4774 assert_eq!(
4775 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4776 &[
4777 DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 28),
4778 DisplayPoint::new(DisplayRow(4), 1)..DisplayPoint::new(DisplayRow(2), 0),
4779 ]
4780 );
4781
4782 _ = view.update(cx, |view, cx| {
4783 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4784 });
4785 assert_eq!(
4786 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4787 &[
4788 DisplayPoint::new(DisplayRow(0), 23)..DisplayPoint::new(DisplayRow(0), 27),
4789 DisplayPoint::new(DisplayRow(2), 35)..DisplayPoint::new(DisplayRow(2), 7),
4790 DisplayPoint::new(DisplayRow(3), 15)..DisplayPoint::new(DisplayRow(3), 21),
4791 ]
4792 );
4793
4794 _ = view.update(cx, |view, cx| {
4795 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4796 });
4797 assert_eq!(
4798 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4799 &[
4800 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
4801 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
4802 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
4803 ]
4804 );
4805
4806 // Trying to shrink the selected syntax node one more time has no effect.
4807 _ = view.update(cx, |view, cx| {
4808 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4809 });
4810 assert_eq!(
4811 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4812 &[
4813 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
4814 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
4815 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
4816 ]
4817 );
4818
4819 // Ensure that we keep expanding the selection if the larger selection starts or ends within
4820 // a fold.
4821 _ = view.update(cx, |view, cx| {
4822 view.fold_ranges(
4823 vec![
4824 (
4825 Point::new(0, 21)..Point::new(0, 24),
4826 FoldPlaceholder::test(),
4827 ),
4828 (
4829 Point::new(3, 20)..Point::new(3, 22),
4830 FoldPlaceholder::test(),
4831 ),
4832 ],
4833 true,
4834 cx,
4835 );
4836 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4837 });
4838 assert_eq!(
4839 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4840 &[
4841 DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 28),
4842 DisplayPoint::new(DisplayRow(2), 35)..DisplayPoint::new(DisplayRow(2), 7),
4843 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(3), 23),
4844 ]
4845 );
4846}
4847
4848#[gpui::test]
4849async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
4850 init_test(cx, |_| {});
4851
4852 let language = Arc::new(
4853 Language::new(
4854 LanguageConfig {
4855 brackets: BracketPairConfig {
4856 pairs: vec![
4857 BracketPair {
4858 start: "{".to_string(),
4859 end: "}".to_string(),
4860 close: false,
4861 surround: false,
4862 newline: true,
4863 },
4864 BracketPair {
4865 start: "(".to_string(),
4866 end: ")".to_string(),
4867 close: false,
4868 surround: false,
4869 newline: true,
4870 },
4871 ],
4872 ..Default::default()
4873 },
4874 ..Default::default()
4875 },
4876 Some(tree_sitter_rust::language()),
4877 )
4878 .with_indents_query(
4879 r#"
4880 (_ "(" ")" @end) @indent
4881 (_ "{" "}" @end) @indent
4882 "#,
4883 )
4884 .unwrap(),
4885 );
4886
4887 let text = "fn a() {}";
4888
4889 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
4890 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4891 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4892 editor
4893 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
4894 .await;
4895
4896 _ = editor.update(cx, |editor, cx| {
4897 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
4898 editor.newline(&Newline, cx);
4899 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
4900 assert_eq!(
4901 editor.selections.ranges(cx),
4902 &[
4903 Point::new(1, 4)..Point::new(1, 4),
4904 Point::new(3, 4)..Point::new(3, 4),
4905 Point::new(5, 0)..Point::new(5, 0)
4906 ]
4907 );
4908 });
4909}
4910
4911#[gpui::test]
4912async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
4913 init_test(cx, |_| {});
4914
4915 let mut cx = EditorTestContext::new(cx).await;
4916
4917 let language = Arc::new(Language::new(
4918 LanguageConfig {
4919 brackets: BracketPairConfig {
4920 pairs: vec![
4921 BracketPair {
4922 start: "{".to_string(),
4923 end: "}".to_string(),
4924 close: true,
4925 surround: true,
4926 newline: true,
4927 },
4928 BracketPair {
4929 start: "(".to_string(),
4930 end: ")".to_string(),
4931 close: true,
4932 surround: true,
4933 newline: true,
4934 },
4935 BracketPair {
4936 start: "/*".to_string(),
4937 end: " */".to_string(),
4938 close: true,
4939 surround: true,
4940 newline: true,
4941 },
4942 BracketPair {
4943 start: "[".to_string(),
4944 end: "]".to_string(),
4945 close: false,
4946 surround: false,
4947 newline: true,
4948 },
4949 BracketPair {
4950 start: "\"".to_string(),
4951 end: "\"".to_string(),
4952 close: true,
4953 surround: true,
4954 newline: false,
4955 },
4956 BracketPair {
4957 start: "<".to_string(),
4958 end: ">".to_string(),
4959 close: false,
4960 surround: true,
4961 newline: true,
4962 },
4963 ],
4964 ..Default::default()
4965 },
4966 autoclose_before: "})]".to_string(),
4967 ..Default::default()
4968 },
4969 Some(tree_sitter_rust::language()),
4970 ));
4971
4972 cx.language_registry().add(language.clone());
4973 cx.update_buffer(|buffer, cx| {
4974 buffer.set_language(Some(language), cx);
4975 });
4976
4977 cx.set_state(
4978 &r#"
4979 🏀ˇ
4980 εˇ
4981 ❤️ˇ
4982 "#
4983 .unindent(),
4984 );
4985
4986 // autoclose multiple nested brackets at multiple cursors
4987 cx.update_editor(|view, cx| {
4988 view.handle_input("{", cx);
4989 view.handle_input("{", cx);
4990 view.handle_input("{", cx);
4991 });
4992 cx.assert_editor_state(
4993 &"
4994 🏀{{{ˇ}}}
4995 ε{{{ˇ}}}
4996 ❤️{{{ˇ}}}
4997 "
4998 .unindent(),
4999 );
5000
5001 // insert a different closing bracket
5002 cx.update_editor(|view, cx| {
5003 view.handle_input(")", cx);
5004 });
5005 cx.assert_editor_state(
5006 &"
5007 🏀{{{)ˇ}}}
5008 ε{{{)ˇ}}}
5009 ❤️{{{)ˇ}}}
5010 "
5011 .unindent(),
5012 );
5013
5014 // skip over the auto-closed brackets when typing a closing bracket
5015 cx.update_editor(|view, cx| {
5016 view.move_right(&MoveRight, cx);
5017 view.handle_input("}", cx);
5018 view.handle_input("}", cx);
5019 view.handle_input("}", cx);
5020 });
5021 cx.assert_editor_state(
5022 &"
5023 🏀{{{)}}}}ˇ
5024 ε{{{)}}}}ˇ
5025 ❤️{{{)}}}}ˇ
5026 "
5027 .unindent(),
5028 );
5029
5030 // autoclose multi-character pairs
5031 cx.set_state(
5032 &"
5033 ˇ
5034 ˇ
5035 "
5036 .unindent(),
5037 );
5038 cx.update_editor(|view, cx| {
5039 view.handle_input("/", cx);
5040 view.handle_input("*", cx);
5041 });
5042 cx.assert_editor_state(
5043 &"
5044 /*ˇ */
5045 /*ˇ */
5046 "
5047 .unindent(),
5048 );
5049
5050 // one cursor autocloses a multi-character pair, one cursor
5051 // does not autoclose.
5052 cx.set_state(
5053 &"
5054 /ˇ
5055 ˇ
5056 "
5057 .unindent(),
5058 );
5059 cx.update_editor(|view, cx| view.handle_input("*", cx));
5060 cx.assert_editor_state(
5061 &"
5062 /*ˇ */
5063 *ˇ
5064 "
5065 .unindent(),
5066 );
5067
5068 // Don't autoclose if the next character isn't whitespace and isn't
5069 // listed in the language's "autoclose_before" section.
5070 cx.set_state("ˇa b");
5071 cx.update_editor(|view, cx| view.handle_input("{", cx));
5072 cx.assert_editor_state("{ˇa b");
5073
5074 // Don't autoclose if `close` is false for the bracket pair
5075 cx.set_state("ˇ");
5076 cx.update_editor(|view, cx| view.handle_input("[", cx));
5077 cx.assert_editor_state("[ˇ");
5078
5079 // Surround with brackets if text is selected
5080 cx.set_state("«aˇ» b");
5081 cx.update_editor(|view, cx| view.handle_input("{", cx));
5082 cx.assert_editor_state("{«aˇ»} b");
5083
5084 // Autclose pair where the start and end characters are the same
5085 cx.set_state("aˇ");
5086 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5087 cx.assert_editor_state("a\"ˇ\"");
5088 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5089 cx.assert_editor_state("a\"\"ˇ");
5090
5091 // Don't autoclose pair if autoclose is disabled
5092 cx.set_state("ˇ");
5093 cx.update_editor(|view, cx| view.handle_input("<", cx));
5094 cx.assert_editor_state("<ˇ");
5095
5096 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5097 cx.set_state("«aˇ» b");
5098 cx.update_editor(|view, cx| view.handle_input("<", cx));
5099 cx.assert_editor_state("<«aˇ»> b");
5100}
5101
5102#[gpui::test]
5103async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5104 init_test(cx, |settings| {
5105 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5106 });
5107
5108 let mut cx = EditorTestContext::new(cx).await;
5109
5110 let language = Arc::new(Language::new(
5111 LanguageConfig {
5112 brackets: BracketPairConfig {
5113 pairs: vec![
5114 BracketPair {
5115 start: "{".to_string(),
5116 end: "}".to_string(),
5117 close: true,
5118 surround: true,
5119 newline: true,
5120 },
5121 BracketPair {
5122 start: "(".to_string(),
5123 end: ")".to_string(),
5124 close: true,
5125 surround: true,
5126 newline: true,
5127 },
5128 BracketPair {
5129 start: "[".to_string(),
5130 end: "]".to_string(),
5131 close: false,
5132 surround: false,
5133 newline: true,
5134 },
5135 ],
5136 ..Default::default()
5137 },
5138 autoclose_before: "})]".to_string(),
5139 ..Default::default()
5140 },
5141 Some(tree_sitter_rust::language()),
5142 ));
5143
5144 cx.language_registry().add(language.clone());
5145 cx.update_buffer(|buffer, cx| {
5146 buffer.set_language(Some(language), cx);
5147 });
5148
5149 cx.set_state(
5150 &"
5151 ˇ
5152 ˇ
5153 ˇ
5154 "
5155 .unindent(),
5156 );
5157
5158 // ensure only matching closing brackets are skipped over
5159 cx.update_editor(|view, cx| {
5160 view.handle_input("}", cx);
5161 view.move_left(&MoveLeft, cx);
5162 view.handle_input(")", cx);
5163 view.move_left(&MoveLeft, cx);
5164 });
5165 cx.assert_editor_state(
5166 &"
5167 ˇ)}
5168 ˇ)}
5169 ˇ)}
5170 "
5171 .unindent(),
5172 );
5173
5174 // skip-over closing brackets at multiple cursors
5175 cx.update_editor(|view, cx| {
5176 view.handle_input(")", cx);
5177 view.handle_input("}", cx);
5178 });
5179 cx.assert_editor_state(
5180 &"
5181 )}ˇ
5182 )}ˇ
5183 )}ˇ
5184 "
5185 .unindent(),
5186 );
5187
5188 // ignore non-close brackets
5189 cx.update_editor(|view, cx| {
5190 view.handle_input("]", cx);
5191 view.move_left(&MoveLeft, cx);
5192 view.handle_input("]", cx);
5193 });
5194 cx.assert_editor_state(
5195 &"
5196 )}]ˇ]
5197 )}]ˇ]
5198 )}]ˇ]
5199 "
5200 .unindent(),
5201 );
5202}
5203
5204#[gpui::test]
5205async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5206 init_test(cx, |_| {});
5207
5208 let mut cx = EditorTestContext::new(cx).await;
5209
5210 let html_language = Arc::new(
5211 Language::new(
5212 LanguageConfig {
5213 name: "HTML".into(),
5214 brackets: BracketPairConfig {
5215 pairs: vec![
5216 BracketPair {
5217 start: "<".into(),
5218 end: ">".into(),
5219 close: true,
5220 ..Default::default()
5221 },
5222 BracketPair {
5223 start: "{".into(),
5224 end: "}".into(),
5225 close: true,
5226 ..Default::default()
5227 },
5228 BracketPair {
5229 start: "(".into(),
5230 end: ")".into(),
5231 close: true,
5232 ..Default::default()
5233 },
5234 ],
5235 ..Default::default()
5236 },
5237 autoclose_before: "})]>".into(),
5238 ..Default::default()
5239 },
5240 Some(tree_sitter_html::language()),
5241 )
5242 .with_injection_query(
5243 r#"
5244 (script_element
5245 (raw_text) @content
5246 (#set! "language" "javascript"))
5247 "#,
5248 )
5249 .unwrap(),
5250 );
5251
5252 let javascript_language = Arc::new(Language::new(
5253 LanguageConfig {
5254 name: "JavaScript".into(),
5255 brackets: BracketPairConfig {
5256 pairs: vec![
5257 BracketPair {
5258 start: "/*".into(),
5259 end: " */".into(),
5260 close: true,
5261 ..Default::default()
5262 },
5263 BracketPair {
5264 start: "{".into(),
5265 end: "}".into(),
5266 close: true,
5267 ..Default::default()
5268 },
5269 BracketPair {
5270 start: "(".into(),
5271 end: ")".into(),
5272 close: true,
5273 ..Default::default()
5274 },
5275 ],
5276 ..Default::default()
5277 },
5278 autoclose_before: "})]>".into(),
5279 ..Default::default()
5280 },
5281 Some(tree_sitter_typescript::language_tsx()),
5282 ));
5283
5284 cx.language_registry().add(html_language.clone());
5285 cx.language_registry().add(javascript_language.clone());
5286
5287 cx.update_buffer(|buffer, cx| {
5288 buffer.set_language(Some(html_language), cx);
5289 });
5290
5291 cx.set_state(
5292 &r#"
5293 <body>ˇ
5294 <script>
5295 var x = 1;ˇ
5296 </script>
5297 </body>ˇ
5298 "#
5299 .unindent(),
5300 );
5301
5302 // Precondition: different languages are active at different locations.
5303 cx.update_editor(|editor, cx| {
5304 let snapshot = editor.snapshot(cx);
5305 let cursors = editor.selections.ranges::<usize>(cx);
5306 let languages = cursors
5307 .iter()
5308 .map(|c| snapshot.language_at(c.start).unwrap().name())
5309 .collect::<Vec<_>>();
5310 assert_eq!(
5311 languages,
5312 &["HTML".into(), "JavaScript".into(), "HTML".into()]
5313 );
5314 });
5315
5316 // Angle brackets autoclose in HTML, but not JavaScript.
5317 cx.update_editor(|editor, cx| {
5318 editor.handle_input("<", cx);
5319 editor.handle_input("a", cx);
5320 });
5321 cx.assert_editor_state(
5322 &r#"
5323 <body><aˇ>
5324 <script>
5325 var x = 1;<aˇ
5326 </script>
5327 </body><aˇ>
5328 "#
5329 .unindent(),
5330 );
5331
5332 // Curly braces and parens autoclose in both HTML and JavaScript.
5333 cx.update_editor(|editor, cx| {
5334 editor.handle_input(" b=", cx);
5335 editor.handle_input("{", cx);
5336 editor.handle_input("c", cx);
5337 editor.handle_input("(", cx);
5338 });
5339 cx.assert_editor_state(
5340 &r#"
5341 <body><a b={c(ˇ)}>
5342 <script>
5343 var x = 1;<a b={c(ˇ)}
5344 </script>
5345 </body><a b={c(ˇ)}>
5346 "#
5347 .unindent(),
5348 );
5349
5350 // Brackets that were already autoclosed are skipped.
5351 cx.update_editor(|editor, cx| {
5352 editor.handle_input(")", cx);
5353 editor.handle_input("d", cx);
5354 editor.handle_input("}", cx);
5355 });
5356 cx.assert_editor_state(
5357 &r#"
5358 <body><a b={c()d}ˇ>
5359 <script>
5360 var x = 1;<a b={c()d}ˇ
5361 </script>
5362 </body><a b={c()d}ˇ>
5363 "#
5364 .unindent(),
5365 );
5366 cx.update_editor(|editor, cx| {
5367 editor.handle_input(">", cx);
5368 });
5369 cx.assert_editor_state(
5370 &r#"
5371 <body><a b={c()d}>ˇ
5372 <script>
5373 var x = 1;<a b={c()d}>ˇ
5374 </script>
5375 </body><a b={c()d}>ˇ
5376 "#
5377 .unindent(),
5378 );
5379
5380 // Reset
5381 cx.set_state(
5382 &r#"
5383 <body>ˇ
5384 <script>
5385 var x = 1;ˇ
5386 </script>
5387 </body>ˇ
5388 "#
5389 .unindent(),
5390 );
5391
5392 cx.update_editor(|editor, cx| {
5393 editor.handle_input("<", cx);
5394 });
5395 cx.assert_editor_state(
5396 &r#"
5397 <body><ˇ>
5398 <script>
5399 var x = 1;<ˇ
5400 </script>
5401 </body><ˇ>
5402 "#
5403 .unindent(),
5404 );
5405
5406 // When backspacing, the closing angle brackets are removed.
5407 cx.update_editor(|editor, cx| {
5408 editor.backspace(&Backspace, cx);
5409 });
5410 cx.assert_editor_state(
5411 &r#"
5412 <body>ˇ
5413 <script>
5414 var x = 1;ˇ
5415 </script>
5416 </body>ˇ
5417 "#
5418 .unindent(),
5419 );
5420
5421 // Block comments autoclose in JavaScript, but not HTML.
5422 cx.update_editor(|editor, cx| {
5423 editor.handle_input("/", cx);
5424 editor.handle_input("*", cx);
5425 });
5426 cx.assert_editor_state(
5427 &r#"
5428 <body>/*ˇ
5429 <script>
5430 var x = 1;/*ˇ */
5431 </script>
5432 </body>/*ˇ
5433 "#
5434 .unindent(),
5435 );
5436}
5437
5438#[gpui::test]
5439async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
5440 init_test(cx, |_| {});
5441
5442 let mut cx = EditorTestContext::new(cx).await;
5443
5444 let rust_language = Arc::new(
5445 Language::new(
5446 LanguageConfig {
5447 name: "Rust".into(),
5448 brackets: serde_json::from_value(json!([
5449 { "start": "{", "end": "}", "close": true, "newline": true },
5450 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
5451 ]))
5452 .unwrap(),
5453 autoclose_before: "})]>".into(),
5454 ..Default::default()
5455 },
5456 Some(tree_sitter_rust::language()),
5457 )
5458 .with_override_query("(string_literal) @string")
5459 .unwrap(),
5460 );
5461
5462 cx.language_registry().add(rust_language.clone());
5463 cx.update_buffer(|buffer, cx| {
5464 buffer.set_language(Some(rust_language), cx);
5465 });
5466
5467 cx.set_state(
5468 &r#"
5469 let x = ˇ
5470 "#
5471 .unindent(),
5472 );
5473
5474 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
5475 cx.update_editor(|editor, cx| {
5476 editor.handle_input("\"", cx);
5477 });
5478 cx.assert_editor_state(
5479 &r#"
5480 let x = "ˇ"
5481 "#
5482 .unindent(),
5483 );
5484
5485 // Inserting another quotation mark. The cursor moves across the existing
5486 // automatically-inserted quotation mark.
5487 cx.update_editor(|editor, cx| {
5488 editor.handle_input("\"", cx);
5489 });
5490 cx.assert_editor_state(
5491 &r#"
5492 let x = ""ˇ
5493 "#
5494 .unindent(),
5495 );
5496
5497 // Reset
5498 cx.set_state(
5499 &r#"
5500 let x = ˇ
5501 "#
5502 .unindent(),
5503 );
5504
5505 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
5506 cx.update_editor(|editor, cx| {
5507 editor.handle_input("\"", cx);
5508 editor.handle_input(" ", cx);
5509 editor.move_left(&Default::default(), cx);
5510 editor.handle_input("\\", cx);
5511 editor.handle_input("\"", cx);
5512 });
5513 cx.assert_editor_state(
5514 &r#"
5515 let x = "\"ˇ "
5516 "#
5517 .unindent(),
5518 );
5519
5520 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
5521 // mark. Nothing is inserted.
5522 cx.update_editor(|editor, cx| {
5523 editor.move_right(&Default::default(), cx);
5524 editor.handle_input("\"", cx);
5525 });
5526 cx.assert_editor_state(
5527 &r#"
5528 let x = "\" "ˇ
5529 "#
5530 .unindent(),
5531 );
5532}
5533
5534#[gpui::test]
5535async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
5536 init_test(cx, |_| {});
5537
5538 let language = Arc::new(Language::new(
5539 LanguageConfig {
5540 brackets: BracketPairConfig {
5541 pairs: vec![
5542 BracketPair {
5543 start: "{".to_string(),
5544 end: "}".to_string(),
5545 close: true,
5546 surround: true,
5547 newline: true,
5548 },
5549 BracketPair {
5550 start: "/* ".to_string(),
5551 end: "*/".to_string(),
5552 close: true,
5553 surround: true,
5554 ..Default::default()
5555 },
5556 ],
5557 ..Default::default()
5558 },
5559 ..Default::default()
5560 },
5561 Some(tree_sitter_rust::language()),
5562 ));
5563
5564 let text = r#"
5565 a
5566 b
5567 c
5568 "#
5569 .unindent();
5570
5571 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5572 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5573 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5574 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5575 .await;
5576
5577 _ = view.update(cx, |view, cx| {
5578 view.change_selections(None, cx, |s| {
5579 s.select_display_ranges([
5580 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5581 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5582 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
5583 ])
5584 });
5585
5586 view.handle_input("{", cx);
5587 view.handle_input("{", cx);
5588 view.handle_input("{", cx);
5589 assert_eq!(
5590 view.text(cx),
5591 "
5592 {{{a}}}
5593 {{{b}}}
5594 {{{c}}}
5595 "
5596 .unindent()
5597 );
5598 assert_eq!(
5599 view.selections.display_ranges(cx),
5600 [
5601 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
5602 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
5603 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
5604 ]
5605 );
5606
5607 view.undo(&Undo, cx);
5608 view.undo(&Undo, cx);
5609 view.undo(&Undo, cx);
5610 assert_eq!(
5611 view.text(cx),
5612 "
5613 a
5614 b
5615 c
5616 "
5617 .unindent()
5618 );
5619 assert_eq!(
5620 view.selections.display_ranges(cx),
5621 [
5622 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5623 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5624 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
5625 ]
5626 );
5627
5628 // Ensure inserting the first character of a multi-byte bracket pair
5629 // doesn't surround the selections with the bracket.
5630 view.handle_input("/", cx);
5631 assert_eq!(
5632 view.text(cx),
5633 "
5634 /
5635 /
5636 /
5637 "
5638 .unindent()
5639 );
5640 assert_eq!(
5641 view.selections.display_ranges(cx),
5642 [
5643 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
5644 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
5645 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
5646 ]
5647 );
5648
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 last 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}
5690
5691#[gpui::test]
5692async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
5693 init_test(cx, |_| {});
5694
5695 let language = Arc::new(Language::new(
5696 LanguageConfig {
5697 brackets: BracketPairConfig {
5698 pairs: vec![BracketPair {
5699 start: "{".to_string(),
5700 end: "}".to_string(),
5701 close: true,
5702 surround: true,
5703 newline: true,
5704 }],
5705 ..Default::default()
5706 },
5707 autoclose_before: "}".to_string(),
5708 ..Default::default()
5709 },
5710 Some(tree_sitter_rust::language()),
5711 ));
5712
5713 let text = r#"
5714 a
5715 b
5716 c
5717 "#
5718 .unindent();
5719
5720 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5721 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5722 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5723 editor
5724 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5725 .await;
5726
5727 _ = editor.update(cx, |editor, cx| {
5728 editor.change_selections(None, cx, |s| {
5729 s.select_ranges([
5730 Point::new(0, 1)..Point::new(0, 1),
5731 Point::new(1, 1)..Point::new(1, 1),
5732 Point::new(2, 1)..Point::new(2, 1),
5733 ])
5734 });
5735
5736 editor.handle_input("{", cx);
5737 editor.handle_input("{", cx);
5738 editor.handle_input("_", cx);
5739 assert_eq!(
5740 editor.text(cx),
5741 "
5742 a{{_}}
5743 b{{_}}
5744 c{{_}}
5745 "
5746 .unindent()
5747 );
5748 assert_eq!(
5749 editor.selections.ranges::<Point>(cx),
5750 [
5751 Point::new(0, 4)..Point::new(0, 4),
5752 Point::new(1, 4)..Point::new(1, 4),
5753 Point::new(2, 4)..Point::new(2, 4)
5754 ]
5755 );
5756
5757 editor.backspace(&Default::default(), cx);
5758 editor.backspace(&Default::default(), cx);
5759 assert_eq!(
5760 editor.text(cx),
5761 "
5762 a{}
5763 b{}
5764 c{}
5765 "
5766 .unindent()
5767 );
5768 assert_eq!(
5769 editor.selections.ranges::<Point>(cx),
5770 [
5771 Point::new(0, 2)..Point::new(0, 2),
5772 Point::new(1, 2)..Point::new(1, 2),
5773 Point::new(2, 2)..Point::new(2, 2)
5774 ]
5775 );
5776
5777 editor.delete_to_previous_word_start(&Default::default(), cx);
5778 assert_eq!(
5779 editor.text(cx),
5780 "
5781 a
5782 b
5783 c
5784 "
5785 .unindent()
5786 );
5787 assert_eq!(
5788 editor.selections.ranges::<Point>(cx),
5789 [
5790 Point::new(0, 1)..Point::new(0, 1),
5791 Point::new(1, 1)..Point::new(1, 1),
5792 Point::new(2, 1)..Point::new(2, 1)
5793 ]
5794 );
5795 });
5796}
5797
5798#[gpui::test]
5799async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
5800 init_test(cx, |settings| {
5801 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5802 });
5803
5804 let mut cx = EditorTestContext::new(cx).await;
5805
5806 let language = Arc::new(Language::new(
5807 LanguageConfig {
5808 brackets: BracketPairConfig {
5809 pairs: vec![
5810 BracketPair {
5811 start: "{".to_string(),
5812 end: "}".to_string(),
5813 close: true,
5814 surround: true,
5815 newline: true,
5816 },
5817 BracketPair {
5818 start: "(".to_string(),
5819 end: ")".to_string(),
5820 close: true,
5821 surround: true,
5822 newline: true,
5823 },
5824 BracketPair {
5825 start: "[".to_string(),
5826 end: "]".to_string(),
5827 close: false,
5828 surround: true,
5829 newline: true,
5830 },
5831 ],
5832 ..Default::default()
5833 },
5834 autoclose_before: "})]".to_string(),
5835 ..Default::default()
5836 },
5837 Some(tree_sitter_rust::language()),
5838 ));
5839
5840 cx.language_registry().add(language.clone());
5841 cx.update_buffer(|buffer, cx| {
5842 buffer.set_language(Some(language), cx);
5843 });
5844
5845 cx.set_state(
5846 &"
5847 {(ˇ)}
5848 [[ˇ]]
5849 {(ˇ)}
5850 "
5851 .unindent(),
5852 );
5853
5854 cx.update_editor(|view, cx| {
5855 view.backspace(&Default::default(), cx);
5856 view.backspace(&Default::default(), cx);
5857 });
5858
5859 cx.assert_editor_state(
5860 &"
5861 ˇ
5862 ˇ]]
5863 ˇ
5864 "
5865 .unindent(),
5866 );
5867
5868 cx.update_editor(|view, cx| {
5869 view.handle_input("{", cx);
5870 view.handle_input("{", cx);
5871 view.move_right(&MoveRight, cx);
5872 view.move_right(&MoveRight, cx);
5873 view.move_left(&MoveLeft, cx);
5874 view.move_left(&MoveLeft, cx);
5875 view.backspace(&Default::default(), cx);
5876 });
5877
5878 cx.assert_editor_state(
5879 &"
5880 {ˇ}
5881 {ˇ}]]
5882 {ˇ}
5883 "
5884 .unindent(),
5885 );
5886
5887 cx.update_editor(|view, cx| {
5888 view.backspace(&Default::default(), cx);
5889 });
5890
5891 cx.assert_editor_state(
5892 &"
5893 ˇ
5894 ˇ]]
5895 ˇ
5896 "
5897 .unindent(),
5898 );
5899}
5900
5901#[gpui::test]
5902async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
5903 init_test(cx, |_| {});
5904
5905 let language = Arc::new(Language::new(
5906 LanguageConfig::default(),
5907 Some(tree_sitter_rust::language()),
5908 ));
5909
5910 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
5911 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5912 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5913 editor
5914 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5915 .await;
5916
5917 _ = editor.update(cx, |editor, cx| {
5918 editor.set_auto_replace_emoji_shortcode(true);
5919
5920 editor.handle_input("Hello ", cx);
5921 editor.handle_input(":wave", cx);
5922 assert_eq!(editor.text(cx), "Hello :wave".unindent());
5923
5924 editor.handle_input(":", cx);
5925 assert_eq!(editor.text(cx), "Hello 👋".unindent());
5926
5927 editor.handle_input(" :smile", cx);
5928 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
5929
5930 editor.handle_input(":", cx);
5931 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
5932
5933 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
5934 editor.handle_input(":wave", cx);
5935 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
5936
5937 editor.handle_input(":", cx);
5938 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
5939
5940 editor.handle_input(":1", cx);
5941 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
5942
5943 editor.handle_input(":", cx);
5944 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
5945
5946 // Ensure shortcode does not get replaced when it is part of a word
5947 editor.handle_input(" Test:wave", cx);
5948 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
5949
5950 editor.handle_input(":", cx);
5951 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
5952
5953 editor.set_auto_replace_emoji_shortcode(false);
5954
5955 // Ensure shortcode does not get replaced when auto replace is off
5956 editor.handle_input(" :wave", cx);
5957 assert_eq!(
5958 editor.text(cx),
5959 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
5960 );
5961
5962 editor.handle_input(":", cx);
5963 assert_eq!(
5964 editor.text(cx),
5965 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
5966 );
5967 });
5968}
5969
5970#[gpui::test]
5971async fn test_snippets(cx: &mut gpui::TestAppContext) {
5972 init_test(cx, |_| {});
5973
5974 let (text, insertion_ranges) = marked_text_ranges(
5975 indoc! {"
5976 a.ˇ b
5977 a.ˇ b
5978 a.ˇ b
5979 "},
5980 false,
5981 );
5982
5983 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
5984 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5985
5986 _ = editor.update(cx, |editor, cx| {
5987 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
5988
5989 editor
5990 .insert_snippet(&insertion_ranges, snippet, cx)
5991 .unwrap();
5992
5993 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
5994 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
5995 assert_eq!(editor.text(cx), expected_text);
5996 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
5997 }
5998
5999 assert(
6000 editor,
6001 cx,
6002 indoc! {"
6003 a.f(«one», two, «three») b
6004 a.f(«one», two, «three») b
6005 a.f(«one», two, «three») b
6006 "},
6007 );
6008
6009 // Can't move earlier than the first tab stop
6010 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6011 assert(
6012 editor,
6013 cx,
6014 indoc! {"
6015 a.f(«one», two, «three») b
6016 a.f(«one», two, «three») b
6017 a.f(«one», two, «three») b
6018 "},
6019 );
6020
6021 assert!(editor.move_to_next_snippet_tabstop(cx));
6022 assert(
6023 editor,
6024 cx,
6025 indoc! {"
6026 a.f(one, «two», three) b
6027 a.f(one, «two», three) b
6028 a.f(one, «two», three) b
6029 "},
6030 );
6031
6032 editor.move_to_prev_snippet_tabstop(cx);
6033 assert(
6034 editor,
6035 cx,
6036 indoc! {"
6037 a.f(«one», two, «three») b
6038 a.f(«one», two, «three») b
6039 a.f(«one», two, «three») b
6040 "},
6041 );
6042
6043 assert!(editor.move_to_next_snippet_tabstop(cx));
6044 assert(
6045 editor,
6046 cx,
6047 indoc! {"
6048 a.f(one, «two», three) b
6049 a.f(one, «two», three) b
6050 a.f(one, «two», three) b
6051 "},
6052 );
6053 assert!(editor.move_to_next_snippet_tabstop(cx));
6054 assert(
6055 editor,
6056 cx,
6057 indoc! {"
6058 a.f(one, two, three)ˇ b
6059 a.f(one, two, three)ˇ b
6060 a.f(one, two, three)ˇ b
6061 "},
6062 );
6063
6064 // As soon as the last tab stop is reached, snippet state is gone
6065 editor.move_to_prev_snippet_tabstop(cx);
6066 assert(
6067 editor,
6068 cx,
6069 indoc! {"
6070 a.f(one, two, three)ˇ b
6071 a.f(one, two, three)ˇ b
6072 a.f(one, two, three)ˇ b
6073 "},
6074 );
6075 });
6076}
6077
6078#[gpui::test]
6079async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6080 init_test(cx, |_| {});
6081
6082 let fs = FakeFs::new(cx.executor());
6083 fs.insert_file("/file.rs", Default::default()).await;
6084
6085 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6086
6087 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6088 language_registry.add(rust_lang());
6089 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6090 "Rust",
6091 FakeLspAdapter {
6092 capabilities: lsp::ServerCapabilities {
6093 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6094 ..Default::default()
6095 },
6096 ..Default::default()
6097 },
6098 );
6099
6100 let buffer = project
6101 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6102 .await
6103 .unwrap();
6104
6105 cx.executor().start_waiting();
6106 let fake_server = fake_servers.next().await.unwrap();
6107
6108 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6109 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6110 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6111 assert!(cx.read(|cx| editor.is_dirty(cx)));
6112
6113 let save = editor
6114 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6115 .unwrap();
6116 fake_server
6117 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6118 assert_eq!(
6119 params.text_document.uri,
6120 lsp::Url::from_file_path("/file.rs").unwrap()
6121 );
6122 assert_eq!(params.options.tab_size, 4);
6123 Ok(Some(vec![lsp::TextEdit::new(
6124 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6125 ", ".to_string(),
6126 )]))
6127 })
6128 .next()
6129 .await;
6130 cx.executor().start_waiting();
6131 save.await;
6132
6133 assert_eq!(
6134 editor.update(cx, |editor, cx| editor.text(cx)),
6135 "one, two\nthree\n"
6136 );
6137 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6138
6139 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6140 assert!(cx.read(|cx| editor.is_dirty(cx)));
6141
6142 // Ensure we can still save even if formatting hangs.
6143 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6144 assert_eq!(
6145 params.text_document.uri,
6146 lsp::Url::from_file_path("/file.rs").unwrap()
6147 );
6148 futures::future::pending::<()>().await;
6149 unreachable!()
6150 });
6151 let save = editor
6152 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6153 .unwrap();
6154 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6155 cx.executor().start_waiting();
6156 save.await;
6157 assert_eq!(
6158 editor.update(cx, |editor, cx| editor.text(cx)),
6159 "one\ntwo\nthree\n"
6160 );
6161 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6162
6163 // For non-dirty buffer, no formatting request should be sent
6164 let save = editor
6165 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6166 .unwrap();
6167 let _pending_format_request = fake_server
6168 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6169 panic!("Should not be invoked on non-dirty buffer");
6170 })
6171 .next();
6172 cx.executor().start_waiting();
6173 save.await;
6174
6175 // Set rust language override and assert overridden tabsize is sent to language server
6176 update_test_language_settings(cx, |settings| {
6177 settings.languages.insert(
6178 "Rust".into(),
6179 LanguageSettingsContent {
6180 tab_size: NonZeroU32::new(8),
6181 ..Default::default()
6182 },
6183 );
6184 });
6185
6186 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6187 assert!(cx.read(|cx| editor.is_dirty(cx)));
6188 let save = editor
6189 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6190 .unwrap();
6191 fake_server
6192 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6193 assert_eq!(
6194 params.text_document.uri,
6195 lsp::Url::from_file_path("/file.rs").unwrap()
6196 );
6197 assert_eq!(params.options.tab_size, 8);
6198 Ok(Some(vec![]))
6199 })
6200 .next()
6201 .await;
6202 cx.executor().start_waiting();
6203 save.await;
6204}
6205
6206#[gpui::test]
6207async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6208 init_test(cx, |_| {});
6209
6210 let cols = 4;
6211 let rows = 10;
6212 let sample_text_1 = sample_text(rows, cols, 'a');
6213 assert_eq!(
6214 sample_text_1,
6215 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6216 );
6217 let sample_text_2 = sample_text(rows, cols, 'l');
6218 assert_eq!(
6219 sample_text_2,
6220 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6221 );
6222 let sample_text_3 = sample_text(rows, cols, 'v');
6223 assert_eq!(
6224 sample_text_3,
6225 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6226 );
6227
6228 let fs = FakeFs::new(cx.executor());
6229 fs.insert_tree(
6230 "/a",
6231 json!({
6232 "main.rs": sample_text_1,
6233 "other.rs": sample_text_2,
6234 "lib.rs": sample_text_3,
6235 }),
6236 )
6237 .await;
6238
6239 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6240 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6241 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6242
6243 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6244 language_registry.add(rust_lang());
6245 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6246 "Rust",
6247 FakeLspAdapter {
6248 capabilities: lsp::ServerCapabilities {
6249 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6250 ..Default::default()
6251 },
6252 ..Default::default()
6253 },
6254 );
6255
6256 let worktree = project.update(cx, |project, cx| {
6257 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
6258 assert_eq!(worktrees.len(), 1);
6259 worktrees.pop().unwrap()
6260 });
6261 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6262
6263 let buffer_1 = project
6264 .update(cx, |project, cx| {
6265 project.open_buffer((worktree_id, "main.rs"), cx)
6266 })
6267 .await
6268 .unwrap();
6269 let buffer_2 = project
6270 .update(cx, |project, cx| {
6271 project.open_buffer((worktree_id, "other.rs"), cx)
6272 })
6273 .await
6274 .unwrap();
6275 let buffer_3 = project
6276 .update(cx, |project, cx| {
6277 project.open_buffer((worktree_id, "lib.rs"), cx)
6278 })
6279 .await
6280 .unwrap();
6281
6282 let multi_buffer = cx.new_model(|cx| {
6283 let mut multi_buffer = MultiBuffer::new(0, ReadWrite);
6284 multi_buffer.push_excerpts(
6285 buffer_1.clone(),
6286 [
6287 ExcerptRange {
6288 context: Point::new(0, 0)..Point::new(3, 0),
6289 primary: None,
6290 },
6291 ExcerptRange {
6292 context: Point::new(5, 0)..Point::new(7, 0),
6293 primary: None,
6294 },
6295 ExcerptRange {
6296 context: Point::new(9, 0)..Point::new(10, 4),
6297 primary: None,
6298 },
6299 ],
6300 cx,
6301 );
6302 multi_buffer.push_excerpts(
6303 buffer_2.clone(),
6304 [
6305 ExcerptRange {
6306 context: Point::new(0, 0)..Point::new(3, 0),
6307 primary: None,
6308 },
6309 ExcerptRange {
6310 context: Point::new(5, 0)..Point::new(7, 0),
6311 primary: None,
6312 },
6313 ExcerptRange {
6314 context: Point::new(9, 0)..Point::new(10, 4),
6315 primary: None,
6316 },
6317 ],
6318 cx,
6319 );
6320 multi_buffer.push_excerpts(
6321 buffer_3.clone(),
6322 [
6323 ExcerptRange {
6324 context: Point::new(0, 0)..Point::new(3, 0),
6325 primary: None,
6326 },
6327 ExcerptRange {
6328 context: Point::new(5, 0)..Point::new(7, 0),
6329 primary: None,
6330 },
6331 ExcerptRange {
6332 context: Point::new(9, 0)..Point::new(10, 4),
6333 primary: None,
6334 },
6335 ],
6336 cx,
6337 );
6338 multi_buffer
6339 });
6340 let multi_buffer_editor = cx.new_view(|cx| {
6341 Editor::new(
6342 EditorMode::Full,
6343 multi_buffer,
6344 Some(project.clone()),
6345 true,
6346 cx,
6347 )
6348 });
6349
6350 multi_buffer_editor.update(cx, |editor, cx| {
6351 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
6352 editor.insert("|one|two|three|", cx);
6353 });
6354 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6355 multi_buffer_editor.update(cx, |editor, cx| {
6356 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
6357 s.select_ranges(Some(60..70))
6358 });
6359 editor.insert("|four|five|six|", cx);
6360 });
6361 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6362
6363 // First two buffers should be edited, but not the third one.
6364 assert_eq!(
6365 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6366 "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}",
6367 );
6368 buffer_1.update(cx, |buffer, _| {
6369 assert!(buffer.is_dirty());
6370 assert_eq!(
6371 buffer.text(),
6372 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
6373 )
6374 });
6375 buffer_2.update(cx, |buffer, _| {
6376 assert!(buffer.is_dirty());
6377 assert_eq!(
6378 buffer.text(),
6379 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
6380 )
6381 });
6382 buffer_3.update(cx, |buffer, _| {
6383 assert!(!buffer.is_dirty());
6384 assert_eq!(buffer.text(), sample_text_3,)
6385 });
6386
6387 cx.executor().start_waiting();
6388 let save = multi_buffer_editor
6389 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6390 .unwrap();
6391
6392 let fake_server = fake_servers.next().await.unwrap();
6393 fake_server
6394 .server
6395 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6396 Ok(Some(vec![lsp::TextEdit::new(
6397 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6398 format!("[{} formatted]", params.text_document.uri),
6399 )]))
6400 })
6401 .detach();
6402 save.await;
6403
6404 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
6405 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
6406 assert_eq!(
6407 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6408 "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}",
6409 );
6410 buffer_1.update(cx, |buffer, _| {
6411 assert!(!buffer.is_dirty());
6412 assert_eq!(
6413 buffer.text(),
6414 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
6415 )
6416 });
6417 buffer_2.update(cx, |buffer, _| {
6418 assert!(!buffer.is_dirty());
6419 assert_eq!(
6420 buffer.text(),
6421 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
6422 )
6423 });
6424 buffer_3.update(cx, |buffer, _| {
6425 assert!(!buffer.is_dirty());
6426 assert_eq!(buffer.text(), sample_text_3,)
6427 });
6428}
6429
6430#[gpui::test]
6431async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
6432 init_test(cx, |_| {});
6433
6434 let fs = FakeFs::new(cx.executor());
6435 fs.insert_file("/file.rs", Default::default()).await;
6436
6437 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6438
6439 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6440 language_registry.add(rust_lang());
6441 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6442 "Rust",
6443 FakeLspAdapter {
6444 capabilities: lsp::ServerCapabilities {
6445 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
6446 ..Default::default()
6447 },
6448 ..Default::default()
6449 },
6450 );
6451
6452 let buffer = project
6453 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6454 .await
6455 .unwrap();
6456
6457 cx.executor().start_waiting();
6458 let fake_server = fake_servers.next().await.unwrap();
6459
6460 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6461 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6462 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6463 assert!(cx.read(|cx| editor.is_dirty(cx)));
6464
6465 let save = editor
6466 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6467 .unwrap();
6468 fake_server
6469 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6470 assert_eq!(
6471 params.text_document.uri,
6472 lsp::Url::from_file_path("/file.rs").unwrap()
6473 );
6474 assert_eq!(params.options.tab_size, 4);
6475 Ok(Some(vec![lsp::TextEdit::new(
6476 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6477 ", ".to_string(),
6478 )]))
6479 })
6480 .next()
6481 .await;
6482 cx.executor().start_waiting();
6483 save.await;
6484 assert_eq!(
6485 editor.update(cx, |editor, cx| editor.text(cx)),
6486 "one, two\nthree\n"
6487 );
6488 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6489
6490 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6491 assert!(cx.read(|cx| editor.is_dirty(cx)));
6492
6493 // Ensure we can still save even if formatting hangs.
6494 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
6495 move |params, _| async move {
6496 assert_eq!(
6497 params.text_document.uri,
6498 lsp::Url::from_file_path("/file.rs").unwrap()
6499 );
6500 futures::future::pending::<()>().await;
6501 unreachable!()
6502 },
6503 );
6504 let save = editor
6505 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6506 .unwrap();
6507 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6508 cx.executor().start_waiting();
6509 save.await;
6510 assert_eq!(
6511 editor.update(cx, |editor, cx| editor.text(cx)),
6512 "one\ntwo\nthree\n"
6513 );
6514 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6515
6516 // For non-dirty buffer, no formatting request should be sent
6517 let save = editor
6518 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6519 .unwrap();
6520 let _pending_format_request = fake_server
6521 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6522 panic!("Should not be invoked on non-dirty buffer");
6523 })
6524 .next();
6525 cx.executor().start_waiting();
6526 save.await;
6527
6528 // Set Rust language override and assert overridden tabsize is sent to language server
6529 update_test_language_settings(cx, |settings| {
6530 settings.languages.insert(
6531 "Rust".into(),
6532 LanguageSettingsContent {
6533 tab_size: NonZeroU32::new(8),
6534 ..Default::default()
6535 },
6536 );
6537 });
6538
6539 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6540 assert!(cx.read(|cx| editor.is_dirty(cx)));
6541 let save = editor
6542 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6543 .unwrap();
6544 fake_server
6545 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6546 assert_eq!(
6547 params.text_document.uri,
6548 lsp::Url::from_file_path("/file.rs").unwrap()
6549 );
6550 assert_eq!(params.options.tab_size, 8);
6551 Ok(Some(vec![]))
6552 })
6553 .next()
6554 .await;
6555 cx.executor().start_waiting();
6556 save.await;
6557}
6558
6559#[gpui::test]
6560async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
6561 init_test(cx, |settings| {
6562 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
6563 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
6564 ))
6565 });
6566
6567 let fs = FakeFs::new(cx.executor());
6568 fs.insert_file("/file.rs", Default::default()).await;
6569
6570 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6571
6572 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6573 language_registry.add(Arc::new(Language::new(
6574 LanguageConfig {
6575 name: "Rust".into(),
6576 matcher: LanguageMatcher {
6577 path_suffixes: vec!["rs".to_string()],
6578 ..Default::default()
6579 },
6580 ..LanguageConfig::default()
6581 },
6582 Some(tree_sitter_rust::language()),
6583 )));
6584 update_test_language_settings(cx, |settings| {
6585 // Enable Prettier formatting for the same buffer, and ensure
6586 // LSP is called instead of Prettier.
6587 settings.defaults.prettier = Some(PrettierSettings {
6588 allowed: true,
6589 ..PrettierSettings::default()
6590 });
6591 });
6592 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6593 "Rust",
6594 FakeLspAdapter {
6595 capabilities: lsp::ServerCapabilities {
6596 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6597 ..Default::default()
6598 },
6599 ..Default::default()
6600 },
6601 );
6602
6603 let buffer = project
6604 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6605 .await
6606 .unwrap();
6607
6608 cx.executor().start_waiting();
6609 let fake_server = fake_servers.next().await.unwrap();
6610
6611 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6612 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6613 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6614
6615 let format = editor
6616 .update(cx, |editor, cx| {
6617 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
6618 })
6619 .unwrap();
6620 fake_server
6621 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6622 assert_eq!(
6623 params.text_document.uri,
6624 lsp::Url::from_file_path("/file.rs").unwrap()
6625 );
6626 assert_eq!(params.options.tab_size, 4);
6627 Ok(Some(vec![lsp::TextEdit::new(
6628 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6629 ", ".to_string(),
6630 )]))
6631 })
6632 .next()
6633 .await;
6634 cx.executor().start_waiting();
6635 format.await;
6636 assert_eq!(
6637 editor.update(cx, |editor, cx| editor.text(cx)),
6638 "one, two\nthree\n"
6639 );
6640
6641 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6642 // Ensure we don't lock if formatting hangs.
6643 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6644 assert_eq!(
6645 params.text_document.uri,
6646 lsp::Url::from_file_path("/file.rs").unwrap()
6647 );
6648 futures::future::pending::<()>().await;
6649 unreachable!()
6650 });
6651 let format = editor
6652 .update(cx, |editor, cx| {
6653 editor.perform_format(project, FormatTrigger::Manual, cx)
6654 })
6655 .unwrap();
6656 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6657 cx.executor().start_waiting();
6658 format.await;
6659 assert_eq!(
6660 editor.update(cx, |editor, cx| editor.text(cx)),
6661 "one\ntwo\nthree\n"
6662 );
6663}
6664
6665#[gpui::test]
6666async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
6667 init_test(cx, |_| {});
6668
6669 let mut cx = EditorLspTestContext::new_rust(
6670 lsp::ServerCapabilities {
6671 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6672 ..Default::default()
6673 },
6674 cx,
6675 )
6676 .await;
6677
6678 cx.set_state(indoc! {"
6679 one.twoˇ
6680 "});
6681
6682 // The format request takes a long time. When it completes, it inserts
6683 // a newline and an indent before the `.`
6684 cx.lsp
6685 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
6686 let executor = cx.background_executor().clone();
6687 async move {
6688 executor.timer(Duration::from_millis(100)).await;
6689 Ok(Some(vec![lsp::TextEdit {
6690 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
6691 new_text: "\n ".into(),
6692 }]))
6693 }
6694 });
6695
6696 // Submit a format request.
6697 let format_1 = cx
6698 .update_editor(|editor, cx| editor.format(&Format, cx))
6699 .unwrap();
6700 cx.executor().run_until_parked();
6701
6702 // Submit a second format request.
6703 let format_2 = cx
6704 .update_editor(|editor, cx| editor.format(&Format, cx))
6705 .unwrap();
6706 cx.executor().run_until_parked();
6707
6708 // Wait for both format requests to complete
6709 cx.executor().advance_clock(Duration::from_millis(200));
6710 cx.executor().start_waiting();
6711 format_1.await.unwrap();
6712 cx.executor().start_waiting();
6713 format_2.await.unwrap();
6714
6715 // The formatting edits only happens once.
6716 cx.assert_editor_state(indoc! {"
6717 one
6718 .twoˇ
6719 "});
6720}
6721
6722#[gpui::test]
6723async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
6724 init_test(cx, |settings| {
6725 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
6726 });
6727
6728 let mut cx = EditorLspTestContext::new_rust(
6729 lsp::ServerCapabilities {
6730 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6731 ..Default::default()
6732 },
6733 cx,
6734 )
6735 .await;
6736
6737 // Set up a buffer white some trailing whitespace and no trailing newline.
6738 cx.set_state(
6739 &[
6740 "one ", //
6741 "twoˇ", //
6742 "three ", //
6743 "four", //
6744 ]
6745 .join("\n"),
6746 );
6747
6748 // Submit a format request.
6749 let format = cx
6750 .update_editor(|editor, cx| editor.format(&Format, cx))
6751 .unwrap();
6752
6753 // Record which buffer changes have been sent to the language server
6754 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
6755 cx.lsp
6756 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
6757 let buffer_changes = buffer_changes.clone();
6758 move |params, _| {
6759 buffer_changes.lock().extend(
6760 params
6761 .content_changes
6762 .into_iter()
6763 .map(|e| (e.range.unwrap(), e.text)),
6764 );
6765 }
6766 });
6767
6768 // Handle formatting requests to the language server.
6769 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
6770 let buffer_changes = buffer_changes.clone();
6771 move |_, _| {
6772 // When formatting is requested, trailing whitespace has already been stripped,
6773 // and the trailing newline has already been added.
6774 assert_eq!(
6775 &buffer_changes.lock()[1..],
6776 &[
6777 (
6778 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
6779 "".into()
6780 ),
6781 (
6782 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
6783 "".into()
6784 ),
6785 (
6786 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
6787 "\n".into()
6788 ),
6789 ]
6790 );
6791
6792 // Insert blank lines between each line of the buffer.
6793 async move {
6794 Ok(Some(vec![
6795 lsp::TextEdit {
6796 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
6797 new_text: "\n".into(),
6798 },
6799 lsp::TextEdit {
6800 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
6801 new_text: "\n".into(),
6802 },
6803 ]))
6804 }
6805 }
6806 });
6807
6808 // After formatting the buffer, the trailing whitespace is stripped,
6809 // a newline is appended, and the edits provided by the language server
6810 // have been applied.
6811 format.await.unwrap();
6812 cx.assert_editor_state(
6813 &[
6814 "one", //
6815 "", //
6816 "twoˇ", //
6817 "", //
6818 "three", //
6819 "four", //
6820 "", //
6821 ]
6822 .join("\n"),
6823 );
6824
6825 // Undoing the formatting undoes the trailing whitespace removal, the
6826 // trailing newline, and the LSP edits.
6827 cx.update_buffer(|buffer, cx| buffer.undo(cx));
6828 cx.assert_editor_state(
6829 &[
6830 "one ", //
6831 "twoˇ", //
6832 "three ", //
6833 "four", //
6834 ]
6835 .join("\n"),
6836 );
6837}
6838
6839#[gpui::test]
6840async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
6841 cx: &mut gpui::TestAppContext,
6842) {
6843 init_test(cx, |_| {});
6844
6845 cx.update(|cx| {
6846 cx.update_global::<SettingsStore, _>(|settings, cx| {
6847 settings.update_user_settings::<EditorSettings>(cx, |settings| {
6848 settings.auto_signature_help = Some(true);
6849 });
6850 });
6851 });
6852
6853 let mut cx = EditorLspTestContext::new_rust(
6854 lsp::ServerCapabilities {
6855 signature_help_provider: Some(lsp::SignatureHelpOptions {
6856 ..Default::default()
6857 }),
6858 ..Default::default()
6859 },
6860 cx,
6861 )
6862 .await;
6863
6864 let language = Language::new(
6865 LanguageConfig {
6866 name: "Rust".into(),
6867 brackets: BracketPairConfig {
6868 pairs: vec![
6869 BracketPair {
6870 start: "{".to_string(),
6871 end: "}".to_string(),
6872 close: true,
6873 surround: true,
6874 newline: true,
6875 },
6876 BracketPair {
6877 start: "(".to_string(),
6878 end: ")".to_string(),
6879 close: true,
6880 surround: true,
6881 newline: true,
6882 },
6883 BracketPair {
6884 start: "/*".to_string(),
6885 end: " */".to_string(),
6886 close: true,
6887 surround: true,
6888 newline: true,
6889 },
6890 BracketPair {
6891 start: "[".to_string(),
6892 end: "]".to_string(),
6893 close: false,
6894 surround: false,
6895 newline: true,
6896 },
6897 BracketPair {
6898 start: "\"".to_string(),
6899 end: "\"".to_string(),
6900 close: true,
6901 surround: true,
6902 newline: false,
6903 },
6904 BracketPair {
6905 start: "<".to_string(),
6906 end: ">".to_string(),
6907 close: false,
6908 surround: true,
6909 newline: true,
6910 },
6911 ],
6912 ..Default::default()
6913 },
6914 autoclose_before: "})]".to_string(),
6915 ..Default::default()
6916 },
6917 Some(tree_sitter_rust::language()),
6918 );
6919 let language = Arc::new(language);
6920
6921 cx.language_registry().add(language.clone());
6922 cx.update_buffer(|buffer, cx| {
6923 buffer.set_language(Some(language), cx);
6924 });
6925
6926 cx.set_state(
6927 &r#"
6928 fn main() {
6929 sampleˇ
6930 }
6931 "#
6932 .unindent(),
6933 );
6934
6935 cx.update_editor(|view, cx| {
6936 view.handle_input("(", cx);
6937 });
6938 cx.assert_editor_state(
6939 &"
6940 fn main() {
6941 sample(ˇ)
6942 }
6943 "
6944 .unindent(),
6945 );
6946
6947 let mocked_response = lsp::SignatureHelp {
6948 signatures: vec![lsp::SignatureInformation {
6949 label: "fn sample(param1: u8, param2: u8)".to_string(),
6950 documentation: None,
6951 parameters: Some(vec![
6952 lsp::ParameterInformation {
6953 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
6954 documentation: None,
6955 },
6956 lsp::ParameterInformation {
6957 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
6958 documentation: None,
6959 },
6960 ]),
6961 active_parameter: None,
6962 }],
6963 active_signature: Some(0),
6964 active_parameter: Some(0),
6965 };
6966 handle_signature_help_request(&mut cx, mocked_response).await;
6967
6968 cx.condition(|editor, _| editor.signature_help_state.is_shown())
6969 .await;
6970
6971 cx.editor(|editor, _| {
6972 let signature_help_state = editor.signature_help_state.popover().cloned();
6973 assert!(signature_help_state.is_some());
6974 let ParsedMarkdown {
6975 text, highlights, ..
6976 } = signature_help_state.unwrap().parsed_content;
6977 assert_eq!(text, "param1: u8, param2: u8");
6978 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
6979 });
6980}
6981
6982#[gpui::test]
6983async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
6984 init_test(cx, |_| {});
6985
6986 cx.update(|cx| {
6987 cx.update_global::<SettingsStore, _>(|settings, cx| {
6988 settings.update_user_settings::<EditorSettings>(cx, |settings| {
6989 settings.auto_signature_help = Some(false);
6990 settings.show_signature_help_after_edits = Some(false);
6991 });
6992 });
6993 });
6994
6995 let mut cx = EditorLspTestContext::new_rust(
6996 lsp::ServerCapabilities {
6997 signature_help_provider: Some(lsp::SignatureHelpOptions {
6998 ..Default::default()
6999 }),
7000 ..Default::default()
7001 },
7002 cx,
7003 )
7004 .await;
7005
7006 let language = Language::new(
7007 LanguageConfig {
7008 name: "Rust".into(),
7009 brackets: BracketPairConfig {
7010 pairs: vec![
7011 BracketPair {
7012 start: "{".to_string(),
7013 end: "}".to_string(),
7014 close: true,
7015 surround: true,
7016 newline: true,
7017 },
7018 BracketPair {
7019 start: "(".to_string(),
7020 end: ")".to_string(),
7021 close: true,
7022 surround: true,
7023 newline: true,
7024 },
7025 BracketPair {
7026 start: "/*".to_string(),
7027 end: " */".to_string(),
7028 close: true,
7029 surround: true,
7030 newline: true,
7031 },
7032 BracketPair {
7033 start: "[".to_string(),
7034 end: "]".to_string(),
7035 close: false,
7036 surround: false,
7037 newline: true,
7038 },
7039 BracketPair {
7040 start: "\"".to_string(),
7041 end: "\"".to_string(),
7042 close: true,
7043 surround: true,
7044 newline: false,
7045 },
7046 BracketPair {
7047 start: "<".to_string(),
7048 end: ">".to_string(),
7049 close: false,
7050 surround: true,
7051 newline: true,
7052 },
7053 ],
7054 ..Default::default()
7055 },
7056 autoclose_before: "})]".to_string(),
7057 ..Default::default()
7058 },
7059 Some(tree_sitter_rust::language()),
7060 );
7061 let language = Arc::new(language);
7062
7063 cx.language_registry().add(language.clone());
7064 cx.update_buffer(|buffer, cx| {
7065 buffer.set_language(Some(language), cx);
7066 });
7067
7068 // Ensure that signature_help is not called when no signature help is enabled.
7069 cx.set_state(
7070 &r#"
7071 fn main() {
7072 sampleˇ
7073 }
7074 "#
7075 .unindent(),
7076 );
7077 cx.update_editor(|view, cx| {
7078 view.handle_input("(", cx);
7079 });
7080 cx.assert_editor_state(
7081 &"
7082 fn main() {
7083 sample(ˇ)
7084 }
7085 "
7086 .unindent(),
7087 );
7088 cx.editor(|editor, _| {
7089 assert!(editor.signature_help_state.task().is_none());
7090 });
7091
7092 let mocked_response = lsp::SignatureHelp {
7093 signatures: vec![lsp::SignatureInformation {
7094 label: "fn sample(param1: u8, param2: u8)".to_string(),
7095 documentation: None,
7096 parameters: Some(vec![
7097 lsp::ParameterInformation {
7098 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7099 documentation: None,
7100 },
7101 lsp::ParameterInformation {
7102 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7103 documentation: None,
7104 },
7105 ]),
7106 active_parameter: None,
7107 }],
7108 active_signature: Some(0),
7109 active_parameter: Some(0),
7110 };
7111
7112 // Ensure that signature_help is called when enabled afte edits
7113 cx.update(|cx| {
7114 cx.update_global::<SettingsStore, _>(|settings, cx| {
7115 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7116 settings.auto_signature_help = Some(false);
7117 settings.show_signature_help_after_edits = Some(true);
7118 });
7119 });
7120 });
7121 cx.set_state(
7122 &r#"
7123 fn main() {
7124 sampleˇ
7125 }
7126 "#
7127 .unindent(),
7128 );
7129 cx.update_editor(|view, cx| {
7130 view.handle_input("(", cx);
7131 });
7132 cx.assert_editor_state(
7133 &"
7134 fn main() {
7135 sample(ˇ)
7136 }
7137 "
7138 .unindent(),
7139 );
7140 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7141 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7142 .await;
7143 cx.update_editor(|editor, _| {
7144 let signature_help_state = editor.signature_help_state.popover().cloned();
7145 assert!(signature_help_state.is_some());
7146 let ParsedMarkdown {
7147 text, highlights, ..
7148 } = signature_help_state.unwrap().parsed_content;
7149 assert_eq!(text, "param1: u8, param2: u8");
7150 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7151 editor.signature_help_state = SignatureHelpState::default();
7152 });
7153
7154 // Ensure that signature_help is called when auto signature help override is enabled
7155 cx.update(|cx| {
7156 cx.update_global::<SettingsStore, _>(|settings, cx| {
7157 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7158 settings.auto_signature_help = Some(true);
7159 settings.show_signature_help_after_edits = Some(false);
7160 });
7161 });
7162 });
7163 cx.set_state(
7164 &r#"
7165 fn main() {
7166 sampleˇ
7167 }
7168 "#
7169 .unindent(),
7170 );
7171 cx.update_editor(|view, cx| {
7172 view.handle_input("(", cx);
7173 });
7174 cx.assert_editor_state(
7175 &"
7176 fn main() {
7177 sample(ˇ)
7178 }
7179 "
7180 .unindent(),
7181 );
7182 handle_signature_help_request(&mut cx, mocked_response).await;
7183 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7184 .await;
7185 cx.editor(|editor, _| {
7186 let signature_help_state = editor.signature_help_state.popover().cloned();
7187 assert!(signature_help_state.is_some());
7188 let ParsedMarkdown {
7189 text, highlights, ..
7190 } = signature_help_state.unwrap().parsed_content;
7191 assert_eq!(text, "param1: u8, param2: u8");
7192 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7193 });
7194}
7195
7196#[gpui::test]
7197async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7198 init_test(cx, |_| {});
7199 cx.update(|cx| {
7200 cx.update_global::<SettingsStore, _>(|settings, cx| {
7201 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7202 settings.auto_signature_help = Some(true);
7203 });
7204 });
7205 });
7206
7207 let mut cx = EditorLspTestContext::new_rust(
7208 lsp::ServerCapabilities {
7209 signature_help_provider: Some(lsp::SignatureHelpOptions {
7210 ..Default::default()
7211 }),
7212 ..Default::default()
7213 },
7214 cx,
7215 )
7216 .await;
7217
7218 // A test that directly calls `show_signature_help`
7219 cx.update_editor(|editor, cx| {
7220 editor.show_signature_help(&ShowSignatureHelp, cx);
7221 });
7222
7223 let mocked_response = lsp::SignatureHelp {
7224 signatures: vec![lsp::SignatureInformation {
7225 label: "fn sample(param1: u8, param2: u8)".to_string(),
7226 documentation: None,
7227 parameters: Some(vec![
7228 lsp::ParameterInformation {
7229 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7230 documentation: None,
7231 },
7232 lsp::ParameterInformation {
7233 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7234 documentation: None,
7235 },
7236 ]),
7237 active_parameter: None,
7238 }],
7239 active_signature: Some(0),
7240 active_parameter: Some(0),
7241 };
7242 handle_signature_help_request(&mut cx, mocked_response).await;
7243
7244 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7245 .await;
7246
7247 cx.editor(|editor, _| {
7248 let signature_help_state = editor.signature_help_state.popover().cloned();
7249 assert!(signature_help_state.is_some());
7250 let ParsedMarkdown {
7251 text, highlights, ..
7252 } = signature_help_state.unwrap().parsed_content;
7253 assert_eq!(text, "param1: u8, param2: u8");
7254 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7255 });
7256
7257 // When exiting outside from inside the brackets, `signature_help` is closed.
7258 cx.set_state(indoc! {"
7259 fn main() {
7260 sample(ˇ);
7261 }
7262
7263 fn sample(param1: u8, param2: u8) {}
7264 "});
7265
7266 cx.update_editor(|editor, cx| {
7267 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
7268 });
7269
7270 let mocked_response = lsp::SignatureHelp {
7271 signatures: Vec::new(),
7272 active_signature: None,
7273 active_parameter: None,
7274 };
7275 handle_signature_help_request(&mut cx, mocked_response).await;
7276
7277 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7278 .await;
7279
7280 cx.editor(|editor, _| {
7281 assert!(!editor.signature_help_state.is_shown());
7282 });
7283
7284 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
7285 cx.set_state(indoc! {"
7286 fn main() {
7287 sample(ˇ);
7288 }
7289
7290 fn sample(param1: u8, param2: u8) {}
7291 "});
7292
7293 let mocked_response = lsp::SignatureHelp {
7294 signatures: vec![lsp::SignatureInformation {
7295 label: "fn sample(param1: u8, param2: u8)".to_string(),
7296 documentation: None,
7297 parameters: Some(vec![
7298 lsp::ParameterInformation {
7299 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7300 documentation: None,
7301 },
7302 lsp::ParameterInformation {
7303 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7304 documentation: None,
7305 },
7306 ]),
7307 active_parameter: None,
7308 }],
7309 active_signature: Some(0),
7310 active_parameter: Some(0),
7311 };
7312 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7313 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7314 .await;
7315 cx.editor(|editor, _| {
7316 assert!(editor.signature_help_state.is_shown());
7317 });
7318
7319 // Restore the popover with more parameter input
7320 cx.set_state(indoc! {"
7321 fn main() {
7322 sample(param1, param2ˇ);
7323 }
7324
7325 fn sample(param1: u8, param2: u8) {}
7326 "});
7327
7328 let mocked_response = lsp::SignatureHelp {
7329 signatures: vec![lsp::SignatureInformation {
7330 label: "fn sample(param1: u8, param2: u8)".to_string(),
7331 documentation: None,
7332 parameters: Some(vec![
7333 lsp::ParameterInformation {
7334 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7335 documentation: None,
7336 },
7337 lsp::ParameterInformation {
7338 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7339 documentation: None,
7340 },
7341 ]),
7342 active_parameter: None,
7343 }],
7344 active_signature: Some(0),
7345 active_parameter: Some(1),
7346 };
7347 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7348 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7349 .await;
7350
7351 // When selecting a range, the popover is gone.
7352 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
7353 cx.update_editor(|editor, cx| {
7354 editor.change_selections(None, cx, |s| {
7355 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7356 })
7357 });
7358 cx.assert_editor_state(indoc! {"
7359 fn main() {
7360 sample(param1, «ˇparam2»);
7361 }
7362
7363 fn sample(param1: u8, param2: u8) {}
7364 "});
7365 cx.editor(|editor, _| {
7366 assert!(!editor.signature_help_state.is_shown());
7367 });
7368
7369 // When unselecting again, the popover is back if within the brackets.
7370 cx.update_editor(|editor, cx| {
7371 editor.change_selections(None, cx, |s| {
7372 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7373 })
7374 });
7375 cx.assert_editor_state(indoc! {"
7376 fn main() {
7377 sample(param1, ˇparam2);
7378 }
7379
7380 fn sample(param1: u8, param2: u8) {}
7381 "});
7382 handle_signature_help_request(&mut cx, mocked_response).await;
7383 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7384 .await;
7385 cx.editor(|editor, _| {
7386 assert!(editor.signature_help_state.is_shown());
7387 });
7388
7389 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
7390 cx.update_editor(|editor, cx| {
7391 editor.change_selections(None, cx, |s| {
7392 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
7393 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7394 })
7395 });
7396 cx.assert_editor_state(indoc! {"
7397 fn main() {
7398 sample(param1, ˇparam2);
7399 }
7400
7401 fn sample(param1: u8, param2: u8) {}
7402 "});
7403
7404 let mocked_response = lsp::SignatureHelp {
7405 signatures: vec![lsp::SignatureInformation {
7406 label: "fn sample(param1: u8, param2: u8)".to_string(),
7407 documentation: None,
7408 parameters: Some(vec![
7409 lsp::ParameterInformation {
7410 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7411 documentation: None,
7412 },
7413 lsp::ParameterInformation {
7414 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7415 documentation: None,
7416 },
7417 ]),
7418 active_parameter: None,
7419 }],
7420 active_signature: Some(0),
7421 active_parameter: Some(1),
7422 };
7423 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7424 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7425 .await;
7426 cx.update_editor(|editor, cx| {
7427 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
7428 });
7429 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7430 .await;
7431 cx.update_editor(|editor, cx| {
7432 editor.change_selections(None, cx, |s| {
7433 s.select_ranges(Some(Point::new(1, 25)..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 cx.update_editor(|editor, cx| {
7444 editor.change_selections(None, cx, |s| {
7445 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7446 })
7447 });
7448 cx.assert_editor_state(indoc! {"
7449 fn main() {
7450 sample(param1, ˇparam2);
7451 }
7452
7453 fn sample(param1: u8, param2: u8) {}
7454 "});
7455 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
7456 .await;
7457}
7458
7459#[gpui::test]
7460async fn test_completion(cx: &mut gpui::TestAppContext) {
7461 init_test(cx, |_| {});
7462
7463 let mut cx = EditorLspTestContext::new_rust(
7464 lsp::ServerCapabilities {
7465 completion_provider: Some(lsp::CompletionOptions {
7466 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7467 resolve_provider: Some(true),
7468 ..Default::default()
7469 }),
7470 ..Default::default()
7471 },
7472 cx,
7473 )
7474 .await;
7475 let counter = Arc::new(AtomicUsize::new(0));
7476
7477 cx.set_state(indoc! {"
7478 oneˇ
7479 two
7480 three
7481 "});
7482 cx.simulate_keystroke(".");
7483 handle_completion_request(
7484 &mut cx,
7485 indoc! {"
7486 one.|<>
7487 two
7488 three
7489 "},
7490 vec!["first_completion", "second_completion"],
7491 counter.clone(),
7492 )
7493 .await;
7494 cx.condition(|editor, _| editor.context_menu_visible())
7495 .await;
7496 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
7497
7498 let apply_additional_edits = cx.update_editor(|editor, cx| {
7499 editor.context_menu_next(&Default::default(), cx);
7500 editor
7501 .confirm_completion(&ConfirmCompletion::default(), cx)
7502 .unwrap()
7503 });
7504 cx.assert_editor_state(indoc! {"
7505 one.second_completionˇ
7506 two
7507 three
7508 "});
7509
7510 handle_resolve_completion_request(
7511 &mut cx,
7512 Some(vec![
7513 (
7514 //This overlaps with the primary completion edit which is
7515 //misbehavior from the LSP spec, test that we filter it out
7516 indoc! {"
7517 one.second_ˇcompletion
7518 two
7519 threeˇ
7520 "},
7521 "overlapping additional edit",
7522 ),
7523 (
7524 indoc! {"
7525 one.second_completion
7526 two
7527 threeˇ
7528 "},
7529 "\nadditional edit",
7530 ),
7531 ]),
7532 )
7533 .await;
7534 apply_additional_edits.await.unwrap();
7535 cx.assert_editor_state(indoc! {"
7536 one.second_completionˇ
7537 two
7538 three
7539 additional edit
7540 "});
7541
7542 cx.set_state(indoc! {"
7543 one.second_completion
7544 twoˇ
7545 threeˇ
7546 additional edit
7547 "});
7548 cx.simulate_keystroke(" ");
7549 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7550 cx.simulate_keystroke("s");
7551 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7552
7553 cx.assert_editor_state(indoc! {"
7554 one.second_completion
7555 two sˇ
7556 three sˇ
7557 additional edit
7558 "});
7559 handle_completion_request(
7560 &mut cx,
7561 indoc! {"
7562 one.second_completion
7563 two s
7564 three <s|>
7565 additional edit
7566 "},
7567 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
7568 counter.clone(),
7569 )
7570 .await;
7571 cx.condition(|editor, _| editor.context_menu_visible())
7572 .await;
7573 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
7574
7575 cx.simulate_keystroke("i");
7576
7577 handle_completion_request(
7578 &mut cx,
7579 indoc! {"
7580 one.second_completion
7581 two si
7582 three <si|>
7583 additional edit
7584 "},
7585 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
7586 counter.clone(),
7587 )
7588 .await;
7589 cx.condition(|editor, _| editor.context_menu_visible())
7590 .await;
7591 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
7592
7593 let apply_additional_edits = cx.update_editor(|editor, cx| {
7594 editor
7595 .confirm_completion(&ConfirmCompletion::default(), cx)
7596 .unwrap()
7597 });
7598 cx.assert_editor_state(indoc! {"
7599 one.second_completion
7600 two sixth_completionˇ
7601 three sixth_completionˇ
7602 additional edit
7603 "});
7604
7605 handle_resolve_completion_request(&mut cx, None).await;
7606 apply_additional_edits.await.unwrap();
7607
7608 _ = cx.update(|cx| {
7609 cx.update_global::<SettingsStore, _>(|settings, cx| {
7610 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7611 settings.show_completions_on_input = Some(false);
7612 });
7613 })
7614 });
7615 cx.set_state("editorˇ");
7616 cx.simulate_keystroke(".");
7617 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7618 cx.simulate_keystroke("c");
7619 cx.simulate_keystroke("l");
7620 cx.simulate_keystroke("o");
7621 cx.assert_editor_state("editor.cloˇ");
7622 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7623 cx.update_editor(|editor, cx| {
7624 editor.show_completions(&ShowCompletions { trigger: None }, cx);
7625 });
7626 handle_completion_request(
7627 &mut cx,
7628 "editor.<clo|>",
7629 vec!["close", "clobber"],
7630 counter.clone(),
7631 )
7632 .await;
7633 cx.condition(|editor, _| editor.context_menu_visible())
7634 .await;
7635 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
7636
7637 let apply_additional_edits = cx.update_editor(|editor, cx| {
7638 editor
7639 .confirm_completion(&ConfirmCompletion::default(), cx)
7640 .unwrap()
7641 });
7642 cx.assert_editor_state("editor.closeˇ");
7643 handle_resolve_completion_request(&mut cx, None).await;
7644 apply_additional_edits.await.unwrap();
7645}
7646
7647#[gpui::test]
7648async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
7649 init_test(cx, |_| {});
7650 let mut cx = EditorLspTestContext::new_rust(
7651 lsp::ServerCapabilities {
7652 completion_provider: Some(lsp::CompletionOptions {
7653 trigger_characters: Some(vec![".".to_string()]),
7654 ..Default::default()
7655 }),
7656 ..Default::default()
7657 },
7658 cx,
7659 )
7660 .await;
7661 cx.lsp
7662 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
7663 Ok(Some(lsp::CompletionResponse::Array(vec![
7664 lsp::CompletionItem {
7665 label: "first".into(),
7666 ..Default::default()
7667 },
7668 lsp::CompletionItem {
7669 label: "last".into(),
7670 ..Default::default()
7671 },
7672 ])))
7673 });
7674 cx.set_state("variableˇ");
7675 cx.simulate_keystroke(".");
7676 cx.executor().run_until_parked();
7677
7678 cx.update_editor(|editor, _| {
7679 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7680 assert_eq!(
7681 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
7682 &["first", "last"]
7683 );
7684 } else {
7685 panic!("expected completion menu to be open");
7686 }
7687 });
7688
7689 cx.update_editor(|editor, cx| {
7690 editor.move_page_down(&MovePageDown::default(), cx);
7691 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7692 assert!(
7693 menu.selected_item == 1,
7694 "expected PageDown to select the last item from the context menu"
7695 );
7696 } else {
7697 panic!("expected completion menu to stay open after PageDown");
7698 }
7699 });
7700
7701 cx.update_editor(|editor, cx| {
7702 editor.move_page_up(&MovePageUp::default(), cx);
7703 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7704 assert!(
7705 menu.selected_item == 0,
7706 "expected PageUp to select the first item from the context menu"
7707 );
7708 } else {
7709 panic!("expected completion menu to stay open after PageUp");
7710 }
7711 });
7712}
7713
7714#[gpui::test]
7715async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
7716 init_test(cx, |_| {});
7717
7718 let mut cx = EditorLspTestContext::new_rust(
7719 lsp::ServerCapabilities {
7720 completion_provider: Some(lsp::CompletionOptions {
7721 trigger_characters: Some(vec![".".to_string()]),
7722 resolve_provider: Some(true),
7723 ..Default::default()
7724 }),
7725 ..Default::default()
7726 },
7727 cx,
7728 )
7729 .await;
7730
7731 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
7732 cx.simulate_keystroke(".");
7733 let completion_item = lsp::CompletionItem {
7734 label: "Some".into(),
7735 kind: Some(lsp::CompletionItemKind::SNIPPET),
7736 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
7737 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
7738 kind: lsp::MarkupKind::Markdown,
7739 value: "```rust\nSome(2)\n```".to_string(),
7740 })),
7741 deprecated: Some(false),
7742 sort_text: Some("Some".to_string()),
7743 filter_text: Some("Some".to_string()),
7744 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
7745 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7746 range: lsp::Range {
7747 start: lsp::Position {
7748 line: 0,
7749 character: 22,
7750 },
7751 end: lsp::Position {
7752 line: 0,
7753 character: 22,
7754 },
7755 },
7756 new_text: "Some(2)".to_string(),
7757 })),
7758 additional_text_edits: Some(vec![lsp::TextEdit {
7759 range: lsp::Range {
7760 start: lsp::Position {
7761 line: 0,
7762 character: 20,
7763 },
7764 end: lsp::Position {
7765 line: 0,
7766 character: 22,
7767 },
7768 },
7769 new_text: "".to_string(),
7770 }]),
7771 ..Default::default()
7772 };
7773
7774 let closure_completion_item = completion_item.clone();
7775 let counter = Arc::new(AtomicUsize::new(0));
7776 let counter_clone = counter.clone();
7777 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
7778 let task_completion_item = closure_completion_item.clone();
7779 counter_clone.fetch_add(1, atomic::Ordering::Release);
7780 async move {
7781 Ok(Some(lsp::CompletionResponse::Array(vec![
7782 task_completion_item,
7783 ])))
7784 }
7785 });
7786
7787 cx.condition(|editor, _| editor.context_menu_visible())
7788 .await;
7789 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
7790 assert!(request.next().await.is_some());
7791 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
7792
7793 cx.simulate_keystroke("S");
7794 cx.simulate_keystroke("o");
7795 cx.simulate_keystroke("m");
7796 cx.condition(|editor, _| editor.context_menu_visible())
7797 .await;
7798 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
7799 assert!(request.next().await.is_some());
7800 assert!(request.next().await.is_some());
7801 assert!(request.next().await.is_some());
7802 request.close();
7803 assert!(request.next().await.is_none());
7804 assert_eq!(
7805 counter.load(atomic::Ordering::Acquire),
7806 4,
7807 "With the completions menu open, only one LSP request should happen per input"
7808 );
7809}
7810
7811#[gpui::test]
7812async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
7813 init_test(cx, |_| {});
7814 let mut cx = EditorTestContext::new(cx).await;
7815 let language = Arc::new(Language::new(
7816 LanguageConfig {
7817 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
7818 ..Default::default()
7819 },
7820 Some(tree_sitter_rust::language()),
7821 ));
7822 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
7823
7824 // If multiple selections intersect a line, the line is only toggled once.
7825 cx.set_state(indoc! {"
7826 fn a() {
7827 «//b();
7828 ˇ»// «c();
7829 //ˇ» d();
7830 }
7831 "});
7832
7833 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7834
7835 cx.assert_editor_state(indoc! {"
7836 fn a() {
7837 «b();
7838 c();
7839 ˇ» d();
7840 }
7841 "});
7842
7843 // The comment prefix is inserted at the same column for every line in a
7844 // selection.
7845 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7846
7847 cx.assert_editor_state(indoc! {"
7848 fn a() {
7849 // «b();
7850 // c();
7851 ˇ»// d();
7852 }
7853 "});
7854
7855 // If a selection ends at the beginning of a line, that line is not toggled.
7856 cx.set_selections_state(indoc! {"
7857 fn a() {
7858 // b();
7859 «// c();
7860 ˇ» // d();
7861 }
7862 "});
7863
7864 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7865
7866 cx.assert_editor_state(indoc! {"
7867 fn a() {
7868 // b();
7869 «c();
7870 ˇ» // d();
7871 }
7872 "});
7873
7874 // If a selection span a single line and is empty, the line is toggled.
7875 cx.set_state(indoc! {"
7876 fn a() {
7877 a();
7878 b();
7879 ˇ
7880 }
7881 "});
7882
7883 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7884
7885 cx.assert_editor_state(indoc! {"
7886 fn a() {
7887 a();
7888 b();
7889 //•ˇ
7890 }
7891 "});
7892
7893 // If a selection span multiple lines, empty lines are not toggled.
7894 cx.set_state(indoc! {"
7895 fn a() {
7896 «a();
7897
7898 c();ˇ»
7899 }
7900 "});
7901
7902 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7903
7904 cx.assert_editor_state(indoc! {"
7905 fn a() {
7906 // «a();
7907
7908 // c();ˇ»
7909 }
7910 "});
7911
7912 // If a selection includes multiple comment prefixes, all lines are uncommented.
7913 cx.set_state(indoc! {"
7914 fn a() {
7915 «// a();
7916 /// b();
7917 //! c();ˇ»
7918 }
7919 "});
7920
7921 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7922
7923 cx.assert_editor_state(indoc! {"
7924 fn a() {
7925 «a();
7926 b();
7927 c();ˇ»
7928 }
7929 "});
7930}
7931
7932#[gpui::test]
7933async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
7934 init_test(cx, |_| {});
7935
7936 let language = Arc::new(Language::new(
7937 LanguageConfig {
7938 line_comments: vec!["// ".into()],
7939 ..Default::default()
7940 },
7941 Some(tree_sitter_rust::language()),
7942 ));
7943
7944 let mut cx = EditorTestContext::new(cx).await;
7945
7946 cx.language_registry().add(language.clone());
7947 cx.update_buffer(|buffer, cx| {
7948 buffer.set_language(Some(language), cx);
7949 });
7950
7951 let toggle_comments = &ToggleComments {
7952 advance_downwards: true,
7953 };
7954
7955 // Single cursor on one line -> advance
7956 // Cursor moves horizontally 3 characters as well on non-blank line
7957 cx.set_state(indoc!(
7958 "fn a() {
7959 ˇdog();
7960 cat();
7961 }"
7962 ));
7963 cx.update_editor(|editor, cx| {
7964 editor.toggle_comments(toggle_comments, cx);
7965 });
7966 cx.assert_editor_state(indoc!(
7967 "fn a() {
7968 // dog();
7969 catˇ();
7970 }"
7971 ));
7972
7973 // Single selection on one line -> don't advance
7974 cx.set_state(indoc!(
7975 "fn a() {
7976 «dog()ˇ»;
7977 cat();
7978 }"
7979 ));
7980 cx.update_editor(|editor, cx| {
7981 editor.toggle_comments(toggle_comments, cx);
7982 });
7983 cx.assert_editor_state(indoc!(
7984 "fn a() {
7985 // «dog()ˇ»;
7986 cat();
7987 }"
7988 ));
7989
7990 // Multiple cursors on one line -> advance
7991 cx.set_state(indoc!(
7992 "fn a() {
7993 ˇdˇog();
7994 cat();
7995 }"
7996 ));
7997 cx.update_editor(|editor, cx| {
7998 editor.toggle_comments(toggle_comments, cx);
7999 });
8000 cx.assert_editor_state(indoc!(
8001 "fn a() {
8002 // dog();
8003 catˇ(ˇ);
8004 }"
8005 ));
8006
8007 // Multiple cursors on one line, with selection -> don't advance
8008 cx.set_state(indoc!(
8009 "fn a() {
8010 ˇdˇog«()ˇ»;
8011 cat();
8012 }"
8013 ));
8014 cx.update_editor(|editor, cx| {
8015 editor.toggle_comments(toggle_comments, cx);
8016 });
8017 cx.assert_editor_state(indoc!(
8018 "fn a() {
8019 // ˇdˇog«()ˇ»;
8020 cat();
8021 }"
8022 ));
8023
8024 // Single cursor on one line -> advance
8025 // Cursor moves to column 0 on blank line
8026 cx.set_state(indoc!(
8027 "fn a() {
8028 ˇdog();
8029
8030 cat();
8031 }"
8032 ));
8033 cx.update_editor(|editor, cx| {
8034 editor.toggle_comments(toggle_comments, cx);
8035 });
8036 cx.assert_editor_state(indoc!(
8037 "fn a() {
8038 // dog();
8039 ˇ
8040 cat();
8041 }"
8042 ));
8043
8044 // Single cursor on one line -> advance
8045 // Cursor starts and ends at column 0
8046 cx.set_state(indoc!(
8047 "fn a() {
8048 ˇ dog();
8049 cat();
8050 }"
8051 ));
8052 cx.update_editor(|editor, cx| {
8053 editor.toggle_comments(toggle_comments, cx);
8054 });
8055 cx.assert_editor_state(indoc!(
8056 "fn a() {
8057 // dog();
8058 ˇ cat();
8059 }"
8060 ));
8061}
8062
8063#[gpui::test]
8064async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
8065 init_test(cx, |_| {});
8066
8067 let mut cx = EditorTestContext::new(cx).await;
8068
8069 let html_language = Arc::new(
8070 Language::new(
8071 LanguageConfig {
8072 name: "HTML".into(),
8073 block_comment: Some(("<!-- ".into(), " -->".into())),
8074 ..Default::default()
8075 },
8076 Some(tree_sitter_html::language()),
8077 )
8078 .with_injection_query(
8079 r#"
8080 (script_element
8081 (raw_text) @content
8082 (#set! "language" "javascript"))
8083 "#,
8084 )
8085 .unwrap(),
8086 );
8087
8088 let javascript_language = Arc::new(Language::new(
8089 LanguageConfig {
8090 name: "JavaScript".into(),
8091 line_comments: vec!["// ".into()],
8092 ..Default::default()
8093 },
8094 Some(tree_sitter_typescript::language_tsx()),
8095 ));
8096
8097 cx.language_registry().add(html_language.clone());
8098 cx.language_registry().add(javascript_language.clone());
8099 cx.update_buffer(|buffer, cx| {
8100 buffer.set_language(Some(html_language), cx);
8101 });
8102
8103 // Toggle comments for empty selections
8104 cx.set_state(
8105 &r#"
8106 <p>A</p>ˇ
8107 <p>B</p>ˇ
8108 <p>C</p>ˇ
8109 "#
8110 .unindent(),
8111 );
8112 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8113 cx.assert_editor_state(
8114 &r#"
8115 <!-- <p>A</p>ˇ -->
8116 <!-- <p>B</p>ˇ -->
8117 <!-- <p>C</p>ˇ -->
8118 "#
8119 .unindent(),
8120 );
8121 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8122 cx.assert_editor_state(
8123 &r#"
8124 <p>A</p>ˇ
8125 <p>B</p>ˇ
8126 <p>C</p>ˇ
8127 "#
8128 .unindent(),
8129 );
8130
8131 // Toggle comments for mixture of empty and non-empty selections, where
8132 // multiple selections occupy a given line.
8133 cx.set_state(
8134 &r#"
8135 <p>A«</p>
8136 <p>ˇ»B</p>ˇ
8137 <p>C«</p>
8138 <p>ˇ»D</p>ˇ
8139 "#
8140 .unindent(),
8141 );
8142
8143 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8144 cx.assert_editor_state(
8145 &r#"
8146 <!-- <p>A«</p>
8147 <p>ˇ»B</p>ˇ -->
8148 <!-- <p>C«</p>
8149 <p>ˇ»D</p>ˇ -->
8150 "#
8151 .unindent(),
8152 );
8153 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8154 cx.assert_editor_state(
8155 &r#"
8156 <p>A«</p>
8157 <p>ˇ»B</p>ˇ
8158 <p>C«</p>
8159 <p>ˇ»D</p>ˇ
8160 "#
8161 .unindent(),
8162 );
8163
8164 // Toggle comments when different languages are active for different
8165 // selections.
8166 cx.set_state(
8167 &r#"
8168 ˇ<script>
8169 ˇvar x = new Y();
8170 ˇ</script>
8171 "#
8172 .unindent(),
8173 );
8174 cx.executor().run_until_parked();
8175 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8176 cx.assert_editor_state(
8177 &r#"
8178 <!-- ˇ<script> -->
8179 // ˇvar x = new Y();
8180 <!-- ˇ</script> -->
8181 "#
8182 .unindent(),
8183 );
8184}
8185
8186#[gpui::test]
8187fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
8188 init_test(cx, |_| {});
8189
8190 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8191 let multibuffer = cx.new_model(|cx| {
8192 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8193 multibuffer.push_excerpts(
8194 buffer.clone(),
8195 [
8196 ExcerptRange {
8197 context: Point::new(0, 0)..Point::new(0, 4),
8198 primary: None,
8199 },
8200 ExcerptRange {
8201 context: Point::new(1, 0)..Point::new(1, 4),
8202 primary: None,
8203 },
8204 ],
8205 cx,
8206 );
8207 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
8208 multibuffer
8209 });
8210
8211 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8212 _ = view.update(cx, |view, cx| {
8213 assert_eq!(view.text(cx), "aaaa\nbbbb");
8214 view.change_selections(None, cx, |s| {
8215 s.select_ranges([
8216 Point::new(0, 0)..Point::new(0, 0),
8217 Point::new(1, 0)..Point::new(1, 0),
8218 ])
8219 });
8220
8221 view.handle_input("X", cx);
8222 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
8223 assert_eq!(
8224 view.selections.ranges(cx),
8225 [
8226 Point::new(0, 1)..Point::new(0, 1),
8227 Point::new(1, 1)..Point::new(1, 1),
8228 ]
8229 );
8230
8231 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
8232 view.change_selections(None, cx, |s| {
8233 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
8234 });
8235 view.backspace(&Default::default(), cx);
8236 assert_eq!(view.text(cx), "Xa\nbbb");
8237 assert_eq!(
8238 view.selections.ranges(cx),
8239 [Point::new(1, 0)..Point::new(1, 0)]
8240 );
8241
8242 view.change_selections(None, cx, |s| {
8243 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
8244 });
8245 view.backspace(&Default::default(), cx);
8246 assert_eq!(view.text(cx), "X\nbb");
8247 assert_eq!(
8248 view.selections.ranges(cx),
8249 [Point::new(0, 1)..Point::new(0, 1)]
8250 );
8251 });
8252}
8253
8254#[gpui::test]
8255fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
8256 init_test(cx, |_| {});
8257
8258 let markers = vec![('[', ']').into(), ('(', ')').into()];
8259 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
8260 indoc! {"
8261 [aaaa
8262 (bbbb]
8263 cccc)",
8264 },
8265 markers.clone(),
8266 );
8267 let excerpt_ranges = markers.into_iter().map(|marker| {
8268 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
8269 ExcerptRange {
8270 context,
8271 primary: None,
8272 }
8273 });
8274 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
8275 let multibuffer = cx.new_model(|cx| {
8276 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8277 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
8278 multibuffer
8279 });
8280
8281 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8282 _ = view.update(cx, |view, cx| {
8283 let (expected_text, selection_ranges) = marked_text_ranges(
8284 indoc! {"
8285 aaaa
8286 bˇbbb
8287 bˇbbˇb
8288 cccc"
8289 },
8290 true,
8291 );
8292 assert_eq!(view.text(cx), expected_text);
8293 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
8294
8295 view.handle_input("X", cx);
8296
8297 let (expected_text, expected_selections) = marked_text_ranges(
8298 indoc! {"
8299 aaaa
8300 bXˇbbXb
8301 bXˇbbXˇb
8302 cccc"
8303 },
8304 false,
8305 );
8306 assert_eq!(view.text(cx), expected_text);
8307 assert_eq!(view.selections.ranges(cx), expected_selections);
8308
8309 view.newline(&Newline, cx);
8310 let (expected_text, expected_selections) = marked_text_ranges(
8311 indoc! {"
8312 aaaa
8313 bX
8314 ˇbbX
8315 b
8316 bX
8317 ˇbbX
8318 ˇb
8319 cccc"
8320 },
8321 false,
8322 );
8323 assert_eq!(view.text(cx), expected_text);
8324 assert_eq!(view.selections.ranges(cx), expected_selections);
8325 });
8326}
8327
8328#[gpui::test]
8329fn test_refresh_selections(cx: &mut TestAppContext) {
8330 init_test(cx, |_| {});
8331
8332 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8333 let mut excerpt1_id = None;
8334 let multibuffer = cx.new_model(|cx| {
8335 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8336 excerpt1_id = multibuffer
8337 .push_excerpts(
8338 buffer.clone(),
8339 [
8340 ExcerptRange {
8341 context: Point::new(0, 0)..Point::new(1, 4),
8342 primary: None,
8343 },
8344 ExcerptRange {
8345 context: Point::new(1, 0)..Point::new(2, 4),
8346 primary: None,
8347 },
8348 ],
8349 cx,
8350 )
8351 .into_iter()
8352 .next();
8353 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8354 multibuffer
8355 });
8356
8357 let editor = cx.add_window(|cx| {
8358 let mut editor = build_editor(multibuffer.clone(), cx);
8359 let snapshot = editor.snapshot(cx);
8360 editor.change_selections(None, cx, |s| {
8361 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
8362 });
8363 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
8364 assert_eq!(
8365 editor.selections.ranges(cx),
8366 [
8367 Point::new(1, 3)..Point::new(1, 3),
8368 Point::new(2, 1)..Point::new(2, 1),
8369 ]
8370 );
8371 editor
8372 });
8373
8374 // Refreshing selections is a no-op when excerpts haven't changed.
8375 _ = editor.update(cx, |editor, cx| {
8376 editor.change_selections(None, cx, |s| s.refresh());
8377 assert_eq!(
8378 editor.selections.ranges(cx),
8379 [
8380 Point::new(1, 3)..Point::new(1, 3),
8381 Point::new(2, 1)..Point::new(2, 1),
8382 ]
8383 );
8384 });
8385
8386 _ = multibuffer.update(cx, |multibuffer, cx| {
8387 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8388 });
8389 _ = editor.update(cx, |editor, cx| {
8390 // Removing an excerpt causes the first selection to become degenerate.
8391 assert_eq!(
8392 editor.selections.ranges(cx),
8393 [
8394 Point::new(0, 0)..Point::new(0, 0),
8395 Point::new(0, 1)..Point::new(0, 1)
8396 ]
8397 );
8398
8399 // Refreshing selections will relocate the first selection to the original buffer
8400 // location.
8401 editor.change_selections(None, cx, |s| s.refresh());
8402 assert_eq!(
8403 editor.selections.ranges(cx),
8404 [
8405 Point::new(0, 1)..Point::new(0, 1),
8406 Point::new(0, 3)..Point::new(0, 3)
8407 ]
8408 );
8409 assert!(editor.selections.pending_anchor().is_some());
8410 });
8411}
8412
8413#[gpui::test]
8414fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
8415 init_test(cx, |_| {});
8416
8417 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8418 let mut excerpt1_id = None;
8419 let multibuffer = cx.new_model(|cx| {
8420 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8421 excerpt1_id = multibuffer
8422 .push_excerpts(
8423 buffer.clone(),
8424 [
8425 ExcerptRange {
8426 context: Point::new(0, 0)..Point::new(1, 4),
8427 primary: None,
8428 },
8429 ExcerptRange {
8430 context: Point::new(1, 0)..Point::new(2, 4),
8431 primary: None,
8432 },
8433 ],
8434 cx,
8435 )
8436 .into_iter()
8437 .next();
8438 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8439 multibuffer
8440 });
8441
8442 let editor = cx.add_window(|cx| {
8443 let mut editor = build_editor(multibuffer.clone(), cx);
8444 let snapshot = editor.snapshot(cx);
8445 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
8446 assert_eq!(
8447 editor.selections.ranges(cx),
8448 [Point::new(1, 3)..Point::new(1, 3)]
8449 );
8450 editor
8451 });
8452
8453 _ = multibuffer.update(cx, |multibuffer, cx| {
8454 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8455 });
8456 _ = editor.update(cx, |editor, cx| {
8457 assert_eq!(
8458 editor.selections.ranges(cx),
8459 [Point::new(0, 0)..Point::new(0, 0)]
8460 );
8461
8462 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
8463 editor.change_selections(None, cx, |s| s.refresh());
8464 assert_eq!(
8465 editor.selections.ranges(cx),
8466 [Point::new(0, 3)..Point::new(0, 3)]
8467 );
8468 assert!(editor.selections.pending_anchor().is_some());
8469 });
8470}
8471
8472#[gpui::test]
8473async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
8474 init_test(cx, |_| {});
8475
8476 let language = Arc::new(
8477 Language::new(
8478 LanguageConfig {
8479 brackets: BracketPairConfig {
8480 pairs: vec![
8481 BracketPair {
8482 start: "{".to_string(),
8483 end: "}".to_string(),
8484 close: true,
8485 surround: true,
8486 newline: true,
8487 },
8488 BracketPair {
8489 start: "/* ".to_string(),
8490 end: " */".to_string(),
8491 close: true,
8492 surround: true,
8493 newline: true,
8494 },
8495 ],
8496 ..Default::default()
8497 },
8498 ..Default::default()
8499 },
8500 Some(tree_sitter_rust::language()),
8501 )
8502 .with_indents_query("")
8503 .unwrap(),
8504 );
8505
8506 let text = concat!(
8507 "{ }\n", //
8508 " x\n", //
8509 " /* */\n", //
8510 "x\n", //
8511 "{{} }\n", //
8512 );
8513
8514 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
8515 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
8516 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
8517 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
8518 .await;
8519
8520 _ = view.update(cx, |view, cx| {
8521 view.change_selections(None, cx, |s| {
8522 s.select_display_ranges([
8523 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
8524 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
8525 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
8526 ])
8527 });
8528 view.newline(&Newline, cx);
8529
8530 assert_eq!(
8531 view.buffer().read(cx).read(cx).text(),
8532 concat!(
8533 "{ \n", // Suppress rustfmt
8534 "\n", //
8535 "}\n", //
8536 " x\n", //
8537 " /* \n", //
8538 " \n", //
8539 " */\n", //
8540 "x\n", //
8541 "{{} \n", //
8542 "}\n", //
8543 )
8544 );
8545 });
8546}
8547
8548#[gpui::test]
8549fn test_highlighted_ranges(cx: &mut TestAppContext) {
8550 init_test(cx, |_| {});
8551
8552 let editor = cx.add_window(|cx| {
8553 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
8554 build_editor(buffer.clone(), cx)
8555 });
8556
8557 _ = editor.update(cx, |editor, cx| {
8558 struct Type1;
8559 struct Type2;
8560
8561 let buffer = editor.buffer.read(cx).snapshot(cx);
8562
8563 let anchor_range =
8564 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
8565
8566 editor.highlight_background::<Type1>(
8567 &[
8568 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
8569 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
8570 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
8571 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
8572 ],
8573 |_| Hsla::red(),
8574 cx,
8575 );
8576 editor.highlight_background::<Type2>(
8577 &[
8578 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
8579 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
8580 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
8581 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
8582 ],
8583 |_| Hsla::green(),
8584 cx,
8585 );
8586
8587 let snapshot = editor.snapshot(cx);
8588 let mut highlighted_ranges = editor.background_highlights_in_range(
8589 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
8590 &snapshot,
8591 cx.theme().colors(),
8592 );
8593 // Enforce a consistent ordering based on color without relying on the ordering of the
8594 // highlight's `TypeId` which is non-executor.
8595 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
8596 assert_eq!(
8597 highlighted_ranges,
8598 &[
8599 (
8600 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
8601 Hsla::red(),
8602 ),
8603 (
8604 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
8605 Hsla::red(),
8606 ),
8607 (
8608 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
8609 Hsla::green(),
8610 ),
8611 (
8612 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
8613 Hsla::green(),
8614 ),
8615 ]
8616 );
8617 assert_eq!(
8618 editor.background_highlights_in_range(
8619 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
8620 &snapshot,
8621 cx.theme().colors(),
8622 ),
8623 &[(
8624 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
8625 Hsla::red(),
8626 )]
8627 );
8628 });
8629}
8630
8631#[gpui::test]
8632async fn test_following(cx: &mut gpui::TestAppContext) {
8633 init_test(cx, |_| {});
8634
8635 let fs = FakeFs::new(cx.executor());
8636 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8637
8638 let buffer = project.update(cx, |project, cx| {
8639 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
8640 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
8641 });
8642 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
8643 let follower = cx.update(|cx| {
8644 cx.open_window(
8645 WindowOptions {
8646 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
8647 gpui::Point::new(px(0.), px(0.)),
8648 gpui::Point::new(px(10.), px(80.)),
8649 ))),
8650 ..Default::default()
8651 },
8652 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
8653 )
8654 .unwrap()
8655 });
8656
8657 let is_still_following = Rc::new(RefCell::new(true));
8658 let follower_edit_event_count = Rc::new(RefCell::new(0));
8659 let pending_update = Rc::new(RefCell::new(None));
8660 _ = follower.update(cx, {
8661 let update = pending_update.clone();
8662 let is_still_following = is_still_following.clone();
8663 let follower_edit_event_count = follower_edit_event_count.clone();
8664 |_, cx| {
8665 cx.subscribe(
8666 &leader.root_view(cx).unwrap(),
8667 move |_, leader, event, cx| {
8668 leader
8669 .read(cx)
8670 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
8671 },
8672 )
8673 .detach();
8674
8675 cx.subscribe(
8676 &follower.root_view(cx).unwrap(),
8677 move |_, _, event: &EditorEvent, _cx| {
8678 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
8679 *is_still_following.borrow_mut() = false;
8680 }
8681
8682 if let EditorEvent::BufferEdited = event {
8683 *follower_edit_event_count.borrow_mut() += 1;
8684 }
8685 },
8686 )
8687 .detach();
8688 }
8689 });
8690
8691 // Update the selections only
8692 _ = leader.update(cx, |leader, cx| {
8693 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
8694 });
8695 follower
8696 .update(cx, |follower, cx| {
8697 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8698 })
8699 .unwrap()
8700 .await
8701 .unwrap();
8702 _ = follower.update(cx, |follower, cx| {
8703 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
8704 });
8705 assert_eq!(*is_still_following.borrow(), true);
8706 assert_eq!(*follower_edit_event_count.borrow(), 0);
8707
8708 // Update the scroll position only
8709 _ = leader.update(cx, |leader, cx| {
8710 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
8711 });
8712 follower
8713 .update(cx, |follower, cx| {
8714 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8715 })
8716 .unwrap()
8717 .await
8718 .unwrap();
8719 assert_eq!(
8720 follower
8721 .update(cx, |follower, cx| follower.scroll_position(cx))
8722 .unwrap(),
8723 gpui::Point::new(1.5, 3.5)
8724 );
8725 assert_eq!(*is_still_following.borrow(), true);
8726 assert_eq!(*follower_edit_event_count.borrow(), 0);
8727
8728 // Update the selections and scroll position. The follower's scroll position is updated
8729 // via autoscroll, not via the leader's exact scroll position.
8730 _ = leader.update(cx, |leader, cx| {
8731 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
8732 leader.request_autoscroll(Autoscroll::newest(), cx);
8733 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
8734 });
8735 follower
8736 .update(cx, |follower, cx| {
8737 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8738 })
8739 .unwrap()
8740 .await
8741 .unwrap();
8742 _ = follower.update(cx, |follower, cx| {
8743 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
8744 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
8745 });
8746 assert_eq!(*is_still_following.borrow(), true);
8747
8748 // Creating a pending selection that precedes another selection
8749 _ = leader.update(cx, |leader, cx| {
8750 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
8751 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
8752 });
8753 follower
8754 .update(cx, |follower, cx| {
8755 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8756 })
8757 .unwrap()
8758 .await
8759 .unwrap();
8760 _ = follower.update(cx, |follower, cx| {
8761 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
8762 });
8763 assert_eq!(*is_still_following.borrow(), true);
8764
8765 // Extend the pending selection so that it surrounds another selection
8766 _ = leader.update(cx, |leader, cx| {
8767 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
8768 });
8769 follower
8770 .update(cx, |follower, cx| {
8771 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8772 })
8773 .unwrap()
8774 .await
8775 .unwrap();
8776 _ = follower.update(cx, |follower, cx| {
8777 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
8778 });
8779
8780 // Scrolling locally breaks the follow
8781 _ = follower.update(cx, |follower, cx| {
8782 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
8783 follower.set_scroll_anchor(
8784 ScrollAnchor {
8785 anchor: top_anchor,
8786 offset: gpui::Point::new(0.0, 0.5),
8787 },
8788 cx,
8789 );
8790 });
8791 assert_eq!(*is_still_following.borrow(), false);
8792}
8793
8794#[gpui::test]
8795async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
8796 init_test(cx, |_| {});
8797
8798 let fs = FakeFs::new(cx.executor());
8799 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8800 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8801 let pane = workspace
8802 .update(cx, |workspace, _| workspace.active_pane().clone())
8803 .unwrap();
8804
8805 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
8806
8807 let leader = pane.update(cx, |_, cx| {
8808 let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
8809 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
8810 });
8811
8812 // Start following the editor when it has no excerpts.
8813 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
8814 let follower_1 = cx
8815 .update_window(*workspace.deref(), |_, cx| {
8816 Editor::from_state_proto(
8817 workspace.root_view(cx).unwrap(),
8818 ViewId {
8819 creator: Default::default(),
8820 id: 0,
8821 },
8822 &mut state_message,
8823 cx,
8824 )
8825 })
8826 .unwrap()
8827 .unwrap()
8828 .await
8829 .unwrap();
8830
8831 let update_message = Rc::new(RefCell::new(None));
8832 follower_1.update(cx, {
8833 let update = update_message.clone();
8834 |_, cx| {
8835 cx.subscribe(&leader, move |_, leader, event, cx| {
8836 leader
8837 .read(cx)
8838 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
8839 })
8840 .detach();
8841 }
8842 });
8843
8844 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
8845 (
8846 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
8847 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
8848 )
8849 });
8850
8851 // Insert some excerpts.
8852 _ = leader.update(cx, |leader, cx| {
8853 leader.buffer.update(cx, |multibuffer, cx| {
8854 let excerpt_ids = multibuffer.push_excerpts(
8855 buffer_1.clone(),
8856 [
8857 ExcerptRange {
8858 context: 1..6,
8859 primary: None,
8860 },
8861 ExcerptRange {
8862 context: 12..15,
8863 primary: None,
8864 },
8865 ExcerptRange {
8866 context: 0..3,
8867 primary: None,
8868 },
8869 ],
8870 cx,
8871 );
8872 multibuffer.insert_excerpts_after(
8873 excerpt_ids[0],
8874 buffer_2.clone(),
8875 [
8876 ExcerptRange {
8877 context: 8..12,
8878 primary: None,
8879 },
8880 ExcerptRange {
8881 context: 0..6,
8882 primary: None,
8883 },
8884 ],
8885 cx,
8886 );
8887 });
8888 });
8889
8890 // Apply the update of adding the excerpts.
8891 follower_1
8892 .update(cx, |follower, cx| {
8893 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
8894 })
8895 .await
8896 .unwrap();
8897 assert_eq!(
8898 follower_1.update(cx, |editor, cx| editor.text(cx)),
8899 leader.update(cx, |editor, cx| editor.text(cx))
8900 );
8901 update_message.borrow_mut().take();
8902
8903 // Start following separately after it already has excerpts.
8904 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
8905 let follower_2 = cx
8906 .update_window(*workspace.deref(), |_, cx| {
8907 Editor::from_state_proto(
8908 workspace.root_view(cx).unwrap().clone(),
8909 ViewId {
8910 creator: Default::default(),
8911 id: 0,
8912 },
8913 &mut state_message,
8914 cx,
8915 )
8916 })
8917 .unwrap()
8918 .unwrap()
8919 .await
8920 .unwrap();
8921 assert_eq!(
8922 follower_2.update(cx, |editor, cx| editor.text(cx)),
8923 leader.update(cx, |editor, cx| editor.text(cx))
8924 );
8925
8926 // Remove some excerpts.
8927 _ = leader.update(cx, |leader, cx| {
8928 leader.buffer.update(cx, |multibuffer, cx| {
8929 let excerpt_ids = multibuffer.excerpt_ids();
8930 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
8931 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
8932 });
8933 });
8934
8935 // Apply the update of removing the excerpts.
8936 follower_1
8937 .update(cx, |follower, cx| {
8938 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
8939 })
8940 .await
8941 .unwrap();
8942 follower_2
8943 .update(cx, |follower, cx| {
8944 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
8945 })
8946 .await
8947 .unwrap();
8948 update_message.borrow_mut().take();
8949 assert_eq!(
8950 follower_1.update(cx, |editor, cx| editor.text(cx)),
8951 leader.update(cx, |editor, cx| editor.text(cx))
8952 );
8953}
8954
8955#[gpui::test]
8956async fn go_to_prev_overlapping_diagnostic(
8957 executor: BackgroundExecutor,
8958 cx: &mut gpui::TestAppContext,
8959) {
8960 init_test(cx, |_| {});
8961
8962 let mut cx = EditorTestContext::new(cx).await;
8963 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
8964
8965 cx.set_state(indoc! {"
8966 ˇfn func(abc def: i32) -> u32 {
8967 }
8968 "});
8969
8970 _ = cx.update(|cx| {
8971 _ = project.update(cx, |project, cx| {
8972 project
8973 .update_diagnostics(
8974 LanguageServerId(0),
8975 lsp::PublishDiagnosticsParams {
8976 uri: lsp::Url::from_file_path("/root/file").unwrap(),
8977 version: None,
8978 diagnostics: vec![
8979 lsp::Diagnostic {
8980 range: lsp::Range::new(
8981 lsp::Position::new(0, 11),
8982 lsp::Position::new(0, 12),
8983 ),
8984 severity: Some(lsp::DiagnosticSeverity::ERROR),
8985 ..Default::default()
8986 },
8987 lsp::Diagnostic {
8988 range: lsp::Range::new(
8989 lsp::Position::new(0, 12),
8990 lsp::Position::new(0, 15),
8991 ),
8992 severity: Some(lsp::DiagnosticSeverity::ERROR),
8993 ..Default::default()
8994 },
8995 lsp::Diagnostic {
8996 range: lsp::Range::new(
8997 lsp::Position::new(0, 25),
8998 lsp::Position::new(0, 28),
8999 ),
9000 severity: Some(lsp::DiagnosticSeverity::ERROR),
9001 ..Default::default()
9002 },
9003 ],
9004 },
9005 &[],
9006 cx,
9007 )
9008 .unwrap()
9009 });
9010 });
9011
9012 executor.run_until_parked();
9013
9014 cx.update_editor(|editor, cx| {
9015 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9016 });
9017
9018 cx.assert_editor_state(indoc! {"
9019 fn func(abc def: i32) -> ˇu32 {
9020 }
9021 "});
9022
9023 cx.update_editor(|editor, cx| {
9024 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9025 });
9026
9027 cx.assert_editor_state(indoc! {"
9028 fn func(abc ˇdef: i32) -> u32 {
9029 }
9030 "});
9031
9032 cx.update_editor(|editor, cx| {
9033 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9034 });
9035
9036 cx.assert_editor_state(indoc! {"
9037 fn func(abcˇ def: i32) -> u32 {
9038 }
9039 "});
9040
9041 cx.update_editor(|editor, cx| {
9042 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9043 });
9044
9045 cx.assert_editor_state(indoc! {"
9046 fn func(abc def: i32) -> ˇu32 {
9047 }
9048 "});
9049}
9050
9051#[gpui::test]
9052async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9053 init_test(cx, |_| {});
9054
9055 let mut cx = EditorTestContext::new(cx).await;
9056
9057 let diff_base = r#"
9058 use some::mod;
9059
9060 const A: u32 = 42;
9061
9062 fn main() {
9063 println!("hello");
9064
9065 println!("world");
9066 }
9067 "#
9068 .unindent();
9069
9070 // Edits are modified, removed, modified, added
9071 cx.set_state(
9072 &r#"
9073 use some::modified;
9074
9075 ˇ
9076 fn main() {
9077 println!("hello there");
9078
9079 println!("around the");
9080 println!("world");
9081 }
9082 "#
9083 .unindent(),
9084 );
9085
9086 cx.set_diff_base(Some(&diff_base));
9087 executor.run_until_parked();
9088
9089 cx.update_editor(|editor, cx| {
9090 //Wrap around the bottom of the buffer
9091 for _ in 0..3 {
9092 editor.go_to_hunk(&GoToHunk, cx);
9093 }
9094 });
9095
9096 cx.assert_editor_state(
9097 &r#"
9098 ˇuse some::modified;
9099
9100
9101 fn main() {
9102 println!("hello there");
9103
9104 println!("around the");
9105 println!("world");
9106 }
9107 "#
9108 .unindent(),
9109 );
9110
9111 cx.update_editor(|editor, cx| {
9112 //Wrap around the top of the buffer
9113 for _ in 0..2 {
9114 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9115 }
9116 });
9117
9118 cx.assert_editor_state(
9119 &r#"
9120 use some::modified;
9121
9122
9123 fn main() {
9124 ˇ println!("hello there");
9125
9126 println!("around the");
9127 println!("world");
9128 }
9129 "#
9130 .unindent(),
9131 );
9132
9133 cx.update_editor(|editor, cx| {
9134 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9135 });
9136
9137 cx.assert_editor_state(
9138 &r#"
9139 use some::modified;
9140
9141 ˇ
9142 fn main() {
9143 println!("hello there");
9144
9145 println!("around the");
9146 println!("world");
9147 }
9148 "#
9149 .unindent(),
9150 );
9151
9152 cx.update_editor(|editor, cx| {
9153 for _ in 0..3 {
9154 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9155 }
9156 });
9157
9158 cx.assert_editor_state(
9159 &r#"
9160 use some::modified;
9161
9162
9163 fn main() {
9164 ˇ println!("hello there");
9165
9166 println!("around the");
9167 println!("world");
9168 }
9169 "#
9170 .unindent(),
9171 );
9172
9173 cx.update_editor(|editor, cx| {
9174 editor.fold(&Fold, cx);
9175
9176 //Make sure that the fold only gets one hunk
9177 for _ in 0..4 {
9178 editor.go_to_hunk(&GoToHunk, cx);
9179 }
9180 });
9181
9182 cx.assert_editor_state(
9183 &r#"
9184 ˇuse some::modified;
9185
9186
9187 fn main() {
9188 println!("hello there");
9189
9190 println!("around the");
9191 println!("world");
9192 }
9193 "#
9194 .unindent(),
9195 );
9196}
9197
9198#[test]
9199fn test_split_words() {
9200 fn split(text: &str) -> Vec<&str> {
9201 split_words(text).collect()
9202 }
9203
9204 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
9205 assert_eq!(split("hello_world"), &["hello_", "world"]);
9206 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
9207 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
9208 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
9209 assert_eq!(split("helloworld"), &["helloworld"]);
9210
9211 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
9212}
9213
9214#[gpui::test]
9215async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
9216 init_test(cx, |_| {});
9217
9218 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
9219 let mut assert = |before, after| {
9220 let _state_context = cx.set_state(before);
9221 cx.update_editor(|editor, cx| {
9222 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
9223 });
9224 cx.assert_editor_state(after);
9225 };
9226
9227 // Outside bracket jumps to outside of matching bracket
9228 assert("console.logˇ(var);", "console.log(var)ˇ;");
9229 assert("console.log(var)ˇ;", "console.logˇ(var);");
9230
9231 // Inside bracket jumps to inside of matching bracket
9232 assert("console.log(ˇvar);", "console.log(varˇ);");
9233 assert("console.log(varˇ);", "console.log(ˇvar);");
9234
9235 // When outside a bracket and inside, favor jumping to the inside bracket
9236 assert(
9237 "console.log('foo', [1, 2, 3]ˇ);",
9238 "console.log(ˇ'foo', [1, 2, 3]);",
9239 );
9240 assert(
9241 "console.log(ˇ'foo', [1, 2, 3]);",
9242 "console.log('foo', [1, 2, 3]ˇ);",
9243 );
9244
9245 // Bias forward if two options are equally likely
9246 assert(
9247 "let result = curried_fun()ˇ();",
9248 "let result = curried_fun()()ˇ;",
9249 );
9250
9251 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
9252 assert(
9253 indoc! {"
9254 function test() {
9255 console.log('test')ˇ
9256 }"},
9257 indoc! {"
9258 function test() {
9259 console.logˇ('test')
9260 }"},
9261 );
9262}
9263
9264#[gpui::test]
9265async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
9266 init_test(cx, |_| {});
9267
9268 let fs = FakeFs::new(cx.executor());
9269 fs.insert_tree(
9270 "/a",
9271 json!({
9272 "main.rs": "fn main() { let a = 5; }",
9273 "other.rs": "// Test file",
9274 }),
9275 )
9276 .await;
9277 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9278
9279 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9280 language_registry.add(Arc::new(Language::new(
9281 LanguageConfig {
9282 name: "Rust".into(),
9283 matcher: LanguageMatcher {
9284 path_suffixes: vec!["rs".to_string()],
9285 ..Default::default()
9286 },
9287 brackets: BracketPairConfig {
9288 pairs: vec![BracketPair {
9289 start: "{".to_string(),
9290 end: "}".to_string(),
9291 close: true,
9292 surround: true,
9293 newline: true,
9294 }],
9295 disabled_scopes_by_bracket_ix: Vec::new(),
9296 },
9297 ..Default::default()
9298 },
9299 Some(tree_sitter_rust::language()),
9300 )));
9301 let mut fake_servers = language_registry.register_fake_lsp_adapter(
9302 "Rust",
9303 FakeLspAdapter {
9304 capabilities: lsp::ServerCapabilities {
9305 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
9306 first_trigger_character: "{".to_string(),
9307 more_trigger_character: None,
9308 }),
9309 ..Default::default()
9310 },
9311 ..Default::default()
9312 },
9313 );
9314
9315 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9316
9317 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9318
9319 let worktree_id = workspace
9320 .update(cx, |workspace, cx| {
9321 workspace.project().update(cx, |project, cx| {
9322 project.worktrees(cx).next().unwrap().read(cx).id()
9323 })
9324 })
9325 .unwrap();
9326
9327 let buffer = project
9328 .update(cx, |project, cx| {
9329 project.open_local_buffer("/a/main.rs", cx)
9330 })
9331 .await
9332 .unwrap();
9333 cx.executor().run_until_parked();
9334 cx.executor().start_waiting();
9335 let fake_server = fake_servers.next().await.unwrap();
9336 let editor_handle = workspace
9337 .update(cx, |workspace, cx| {
9338 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
9339 })
9340 .unwrap()
9341 .await
9342 .unwrap()
9343 .downcast::<Editor>()
9344 .unwrap();
9345
9346 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
9347 assert_eq!(
9348 params.text_document_position.text_document.uri,
9349 lsp::Url::from_file_path("/a/main.rs").unwrap(),
9350 );
9351 assert_eq!(
9352 params.text_document_position.position,
9353 lsp::Position::new(0, 21),
9354 );
9355
9356 Ok(Some(vec![lsp::TextEdit {
9357 new_text: "]".to_string(),
9358 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
9359 }]))
9360 });
9361
9362 editor_handle.update(cx, |editor, cx| {
9363 editor.focus(cx);
9364 editor.change_selections(None, cx, |s| {
9365 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
9366 });
9367 editor.handle_input("{", cx);
9368 });
9369
9370 cx.executor().run_until_parked();
9371
9372 _ = buffer.update(cx, |buffer, _| {
9373 assert_eq!(
9374 buffer.text(),
9375 "fn main() { let a = {5}; }",
9376 "No extra braces from on type formatting should appear in the buffer"
9377 )
9378 });
9379}
9380
9381#[gpui::test]
9382async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
9383 init_test(cx, |_| {});
9384
9385 let fs = FakeFs::new(cx.executor());
9386 fs.insert_tree(
9387 "/a",
9388 json!({
9389 "main.rs": "fn main() { let a = 5; }",
9390 "other.rs": "// Test file",
9391 }),
9392 )
9393 .await;
9394
9395 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9396
9397 let server_restarts = Arc::new(AtomicUsize::new(0));
9398 let closure_restarts = Arc::clone(&server_restarts);
9399 let language_server_name = "test language server";
9400 let language_name: Arc<str> = "Rust".into();
9401
9402 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9403 language_registry.add(Arc::new(Language::new(
9404 LanguageConfig {
9405 name: Arc::clone(&language_name),
9406 matcher: LanguageMatcher {
9407 path_suffixes: vec!["rs".to_string()],
9408 ..Default::default()
9409 },
9410 ..Default::default()
9411 },
9412 Some(tree_sitter_rust::language()),
9413 )));
9414 let mut fake_servers = language_registry.register_fake_lsp_adapter(
9415 "Rust",
9416 FakeLspAdapter {
9417 name: language_server_name,
9418 initialization_options: Some(json!({
9419 "testOptionValue": true
9420 })),
9421 initializer: Some(Box::new(move |fake_server| {
9422 let task_restarts = Arc::clone(&closure_restarts);
9423 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
9424 task_restarts.fetch_add(1, atomic::Ordering::Release);
9425 futures::future::ready(Ok(()))
9426 });
9427 })),
9428 ..Default::default()
9429 },
9430 );
9431
9432 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9433 let _buffer = project
9434 .update(cx, |project, cx| {
9435 project.open_local_buffer("/a/main.rs", cx)
9436 })
9437 .await
9438 .unwrap();
9439 let _fake_server = fake_servers.next().await.unwrap();
9440 update_test_language_settings(cx, |language_settings| {
9441 language_settings.languages.insert(
9442 Arc::clone(&language_name),
9443 LanguageSettingsContent {
9444 tab_size: NonZeroU32::new(8),
9445 ..Default::default()
9446 },
9447 );
9448 });
9449 cx.executor().run_until_parked();
9450 assert_eq!(
9451 server_restarts.load(atomic::Ordering::Acquire),
9452 0,
9453 "Should not restart LSP server on an unrelated change"
9454 );
9455
9456 update_test_project_settings(cx, |project_settings| {
9457 project_settings.lsp.insert(
9458 "Some other server name".into(),
9459 LspSettings {
9460 binary: None,
9461 settings: None,
9462 initialization_options: Some(json!({
9463 "some other init value": false
9464 })),
9465 },
9466 );
9467 });
9468 cx.executor().run_until_parked();
9469 assert_eq!(
9470 server_restarts.load(atomic::Ordering::Acquire),
9471 0,
9472 "Should not restart LSP server on an unrelated LSP settings change"
9473 );
9474
9475 update_test_project_settings(cx, |project_settings| {
9476 project_settings.lsp.insert(
9477 language_server_name.into(),
9478 LspSettings {
9479 binary: None,
9480 settings: None,
9481 initialization_options: Some(json!({
9482 "anotherInitValue": false
9483 })),
9484 },
9485 );
9486 });
9487 cx.executor().run_until_parked();
9488 assert_eq!(
9489 server_restarts.load(atomic::Ordering::Acquire),
9490 1,
9491 "Should restart LSP server on a related LSP settings change"
9492 );
9493
9494 update_test_project_settings(cx, |project_settings| {
9495 project_settings.lsp.insert(
9496 language_server_name.into(),
9497 LspSettings {
9498 binary: None,
9499 settings: None,
9500 initialization_options: Some(json!({
9501 "anotherInitValue": false
9502 })),
9503 },
9504 );
9505 });
9506 cx.executor().run_until_parked();
9507 assert_eq!(
9508 server_restarts.load(atomic::Ordering::Acquire),
9509 1,
9510 "Should not restart LSP server on a related LSP settings change that is the same"
9511 );
9512
9513 update_test_project_settings(cx, |project_settings| {
9514 project_settings.lsp.insert(
9515 language_server_name.into(),
9516 LspSettings {
9517 binary: None,
9518 settings: None,
9519 initialization_options: None,
9520 },
9521 );
9522 });
9523 cx.executor().run_until_parked();
9524 assert_eq!(
9525 server_restarts.load(atomic::Ordering::Acquire),
9526 2,
9527 "Should restart LSP server on another related LSP settings change"
9528 );
9529}
9530
9531#[gpui::test]
9532async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
9533 init_test(cx, |_| {});
9534
9535 let mut cx = EditorLspTestContext::new_rust(
9536 lsp::ServerCapabilities {
9537 completion_provider: Some(lsp::CompletionOptions {
9538 trigger_characters: Some(vec![".".to_string()]),
9539 resolve_provider: Some(true),
9540 ..Default::default()
9541 }),
9542 ..Default::default()
9543 },
9544 cx,
9545 )
9546 .await;
9547
9548 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9549 cx.simulate_keystroke(".");
9550 let completion_item = lsp::CompletionItem {
9551 label: "some".into(),
9552 kind: Some(lsp::CompletionItemKind::SNIPPET),
9553 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9554 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9555 kind: lsp::MarkupKind::Markdown,
9556 value: "```rust\nSome(2)\n```".to_string(),
9557 })),
9558 deprecated: Some(false),
9559 sort_text: Some("fffffff2".to_string()),
9560 filter_text: Some("some".to_string()),
9561 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9562 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9563 range: lsp::Range {
9564 start: lsp::Position {
9565 line: 0,
9566 character: 22,
9567 },
9568 end: lsp::Position {
9569 line: 0,
9570 character: 22,
9571 },
9572 },
9573 new_text: "Some(2)".to_string(),
9574 })),
9575 additional_text_edits: Some(vec![lsp::TextEdit {
9576 range: lsp::Range {
9577 start: lsp::Position {
9578 line: 0,
9579 character: 20,
9580 },
9581 end: lsp::Position {
9582 line: 0,
9583 character: 22,
9584 },
9585 },
9586 new_text: "".to_string(),
9587 }]),
9588 ..Default::default()
9589 };
9590
9591 let closure_completion_item = completion_item.clone();
9592 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9593 let task_completion_item = closure_completion_item.clone();
9594 async move {
9595 Ok(Some(lsp::CompletionResponse::Array(vec![
9596 task_completion_item,
9597 ])))
9598 }
9599 });
9600
9601 request.next().await;
9602
9603 cx.condition(|editor, _| editor.context_menu_visible())
9604 .await;
9605 let apply_additional_edits = cx.update_editor(|editor, cx| {
9606 editor
9607 .confirm_completion(&ConfirmCompletion::default(), cx)
9608 .unwrap()
9609 });
9610 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
9611
9612 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
9613 let task_completion_item = completion_item.clone();
9614 async move { Ok(task_completion_item) }
9615 })
9616 .next()
9617 .await
9618 .unwrap();
9619 apply_additional_edits.await.unwrap();
9620 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
9621}
9622
9623#[gpui::test]
9624async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
9625 init_test(cx, |_| {});
9626
9627 let mut cx = EditorLspTestContext::new(
9628 Language::new(
9629 LanguageConfig {
9630 matcher: LanguageMatcher {
9631 path_suffixes: vec!["jsx".into()],
9632 ..Default::default()
9633 },
9634 overrides: [(
9635 "element".into(),
9636 LanguageConfigOverride {
9637 word_characters: Override::Set(['-'].into_iter().collect()),
9638 ..Default::default()
9639 },
9640 )]
9641 .into_iter()
9642 .collect(),
9643 ..Default::default()
9644 },
9645 Some(tree_sitter_typescript::language_tsx()),
9646 )
9647 .with_override_query("(jsx_self_closing_element) @element")
9648 .unwrap(),
9649 lsp::ServerCapabilities {
9650 completion_provider: Some(lsp::CompletionOptions {
9651 trigger_characters: Some(vec![":".to_string()]),
9652 ..Default::default()
9653 }),
9654 ..Default::default()
9655 },
9656 cx,
9657 )
9658 .await;
9659
9660 cx.lsp
9661 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9662 Ok(Some(lsp::CompletionResponse::Array(vec![
9663 lsp::CompletionItem {
9664 label: "bg-blue".into(),
9665 ..Default::default()
9666 },
9667 lsp::CompletionItem {
9668 label: "bg-red".into(),
9669 ..Default::default()
9670 },
9671 lsp::CompletionItem {
9672 label: "bg-yellow".into(),
9673 ..Default::default()
9674 },
9675 ])))
9676 });
9677
9678 cx.set_state(r#"<p class="bgˇ" />"#);
9679
9680 // Trigger completion when typing a dash, because the dash is an extra
9681 // word character in the 'element' scope, which contains the cursor.
9682 cx.simulate_keystroke("-");
9683 cx.executor().run_until_parked();
9684 cx.update_editor(|editor, _| {
9685 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9686 assert_eq!(
9687 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9688 &["bg-red", "bg-blue", "bg-yellow"]
9689 );
9690 } else {
9691 panic!("expected completion menu to be open");
9692 }
9693 });
9694
9695 cx.simulate_keystroke("l");
9696 cx.executor().run_until_parked();
9697 cx.update_editor(|editor, _| {
9698 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9699 assert_eq!(
9700 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9701 &["bg-blue", "bg-yellow"]
9702 );
9703 } else {
9704 panic!("expected completion menu to be open");
9705 }
9706 });
9707
9708 // When filtering completions, consider the character after the '-' to
9709 // be the start of a subword.
9710 cx.set_state(r#"<p class="yelˇ" />"#);
9711 cx.simulate_keystroke("l");
9712 cx.executor().run_until_parked();
9713 cx.update_editor(|editor, _| {
9714 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9715 assert_eq!(
9716 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9717 &["bg-yellow"]
9718 );
9719 } else {
9720 panic!("expected completion menu to be open");
9721 }
9722 });
9723}
9724
9725#[gpui::test]
9726async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
9727 init_test(cx, |settings| {
9728 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
9729 FormatterList(vec![Formatter::Prettier].into()),
9730 ))
9731 });
9732
9733 let fs = FakeFs::new(cx.executor());
9734 fs.insert_file("/file.ts", Default::default()).await;
9735
9736 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
9737 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9738
9739 language_registry.add(Arc::new(Language::new(
9740 LanguageConfig {
9741 name: "TypeScript".into(),
9742 matcher: LanguageMatcher {
9743 path_suffixes: vec!["ts".to_string()],
9744 ..Default::default()
9745 },
9746 ..Default::default()
9747 },
9748 Some(tree_sitter_rust::language()),
9749 )));
9750 update_test_language_settings(cx, |settings| {
9751 settings.defaults.prettier = Some(PrettierSettings {
9752 allowed: true,
9753 ..PrettierSettings::default()
9754 });
9755 });
9756
9757 let test_plugin = "test_plugin";
9758 let _ = language_registry.register_fake_lsp_adapter(
9759 "TypeScript",
9760 FakeLspAdapter {
9761 prettier_plugins: vec![test_plugin],
9762 ..Default::default()
9763 },
9764 );
9765
9766 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
9767 let buffer = project
9768 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
9769 .await
9770 .unwrap();
9771
9772 let buffer_text = "one\ntwo\nthree\n";
9773 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9774 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9775 _ = editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
9776
9777 editor
9778 .update(cx, |editor, cx| {
9779 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
9780 })
9781 .unwrap()
9782 .await;
9783 assert_eq!(
9784 editor.update(cx, |editor, cx| editor.text(cx)),
9785 buffer_text.to_string() + prettier_format_suffix,
9786 "Test prettier formatting was not applied to the original buffer text",
9787 );
9788
9789 update_test_language_settings(cx, |settings| {
9790 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
9791 });
9792 let format = editor.update(cx, |editor, cx| {
9793 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
9794 });
9795 format.await.unwrap();
9796 assert_eq!(
9797 editor.update(cx, |editor, cx| editor.text(cx)),
9798 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
9799 "Autoformatting (via test prettier) was not applied to the original buffer text",
9800 );
9801}
9802
9803#[gpui::test]
9804async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
9805 init_test(cx, |_| {});
9806 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9807 let base_text = indoc! {r#"struct Row;
9808struct Row1;
9809struct Row2;
9810
9811struct Row4;
9812struct Row5;
9813struct Row6;
9814
9815struct Row8;
9816struct Row9;
9817struct Row10;"#};
9818
9819 // When addition hunks are not adjacent to carets, no hunk revert is performed
9820 assert_hunk_revert(
9821 indoc! {r#"struct Row;
9822 struct Row1;
9823 struct Row1.1;
9824 struct Row1.2;
9825 struct Row2;ˇ
9826
9827 struct Row4;
9828 struct Row5;
9829 struct Row6;
9830
9831 struct Row8;
9832 ˇstruct Row9;
9833 struct Row9.1;
9834 struct Row9.2;
9835 struct Row9.3;
9836 struct Row10;"#},
9837 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
9838 indoc! {r#"struct Row;
9839 struct Row1;
9840 struct Row1.1;
9841 struct Row1.2;
9842 struct Row2;ˇ
9843
9844 struct Row4;
9845 struct Row5;
9846 struct Row6;
9847
9848 struct Row8;
9849 ˇstruct Row9;
9850 struct Row9.1;
9851 struct Row9.2;
9852 struct Row9.3;
9853 struct Row10;"#},
9854 base_text,
9855 &mut cx,
9856 );
9857 // Same for selections
9858 assert_hunk_revert(
9859 indoc! {r#"struct Row;
9860 struct Row1;
9861 struct Row2;
9862 struct Row2.1;
9863 struct Row2.2;
9864 «ˇ
9865 struct Row4;
9866 struct» Row5;
9867 «struct Row6;
9868 ˇ»
9869 struct Row9.1;
9870 struct Row9.2;
9871 struct Row9.3;
9872 struct Row8;
9873 struct Row9;
9874 struct Row10;"#},
9875 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
9876 indoc! {r#"struct Row;
9877 struct Row1;
9878 struct Row2;
9879 struct Row2.1;
9880 struct Row2.2;
9881 «ˇ
9882 struct Row4;
9883 struct» Row5;
9884 «struct Row6;
9885 ˇ»
9886 struct Row9.1;
9887 struct Row9.2;
9888 struct Row9.3;
9889 struct Row8;
9890 struct Row9;
9891 struct Row10;"#},
9892 base_text,
9893 &mut cx,
9894 );
9895
9896 // When carets and selections intersect the addition hunks, those are reverted.
9897 // Adjacent carets got merged.
9898 assert_hunk_revert(
9899 indoc! {r#"struct Row;
9900 ˇ// something on the top
9901 struct Row1;
9902 struct Row2;
9903 struct Roˇw3.1;
9904 struct Row2.2;
9905 struct Row2.3;ˇ
9906
9907 struct Row4;
9908 struct ˇRow5.1;
9909 struct Row5.2;
9910 struct «Rowˇ»5.3;
9911 struct Row5;
9912 struct Row6;
9913 ˇ
9914 struct Row9.1;
9915 struct «Rowˇ»9.2;
9916 struct «ˇRow»9.3;
9917 struct Row8;
9918 struct Row9;
9919 «ˇ// something on bottom»
9920 struct Row10;"#},
9921 vec![
9922 DiffHunkStatus::Added,
9923 DiffHunkStatus::Added,
9924 DiffHunkStatus::Added,
9925 DiffHunkStatus::Added,
9926 DiffHunkStatus::Added,
9927 ],
9928 indoc! {r#"struct Row;
9929 ˇstruct Row1;
9930 struct Row2;
9931 ˇ
9932 struct Row4;
9933 ˇstruct Row5;
9934 struct Row6;
9935 ˇ
9936 ˇstruct Row8;
9937 struct Row9;
9938 ˇstruct Row10;"#},
9939 base_text,
9940 &mut cx,
9941 );
9942}
9943
9944#[gpui::test]
9945async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
9946 init_test(cx, |_| {});
9947 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9948 let base_text = indoc! {r#"struct Row;
9949struct Row1;
9950struct Row2;
9951
9952struct Row4;
9953struct Row5;
9954struct Row6;
9955
9956struct Row8;
9957struct Row9;
9958struct Row10;"#};
9959
9960 // Modification hunks behave the same as the addition ones.
9961 assert_hunk_revert(
9962 indoc! {r#"struct Row;
9963 struct Row1;
9964 struct Row33;
9965 ˇ
9966 struct Row4;
9967 struct Row5;
9968 struct Row6;
9969 ˇ
9970 struct Row99;
9971 struct Row9;
9972 struct Row10;"#},
9973 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
9974 indoc! {r#"struct Row;
9975 struct Row1;
9976 struct Row33;
9977 ˇ
9978 struct Row4;
9979 struct Row5;
9980 struct Row6;
9981 ˇ
9982 struct Row99;
9983 struct Row9;
9984 struct Row10;"#},
9985 base_text,
9986 &mut cx,
9987 );
9988 assert_hunk_revert(
9989 indoc! {r#"struct Row;
9990 struct Row1;
9991 struct Row33;
9992 «ˇ
9993 struct Row4;
9994 struct» Row5;
9995 «struct Row6;
9996 ˇ»
9997 struct Row99;
9998 struct Row9;
9999 struct Row10;"#},
10000 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10001 indoc! {r#"struct Row;
10002 struct Row1;
10003 struct Row33;
10004 «ˇ
10005 struct Row4;
10006 struct» Row5;
10007 «struct Row6;
10008 ˇ»
10009 struct Row99;
10010 struct Row9;
10011 struct Row10;"#},
10012 base_text,
10013 &mut cx,
10014 );
10015
10016 assert_hunk_revert(
10017 indoc! {r#"ˇstruct Row1.1;
10018 struct Row1;
10019 «ˇstr»uct Row22;
10020
10021 struct ˇRow44;
10022 struct Row5;
10023 struct «Rˇ»ow66;ˇ
10024
10025 «struˇ»ct Row88;
10026 struct Row9;
10027 struct Row1011;ˇ"#},
10028 vec![
10029 DiffHunkStatus::Modified,
10030 DiffHunkStatus::Modified,
10031 DiffHunkStatus::Modified,
10032 DiffHunkStatus::Modified,
10033 DiffHunkStatus::Modified,
10034 DiffHunkStatus::Modified,
10035 ],
10036 indoc! {r#"struct Row;
10037 ˇstruct Row1;
10038 struct Row2;
10039 ˇ
10040 struct Row4;
10041 ˇstruct Row5;
10042 struct Row6;
10043 ˇ
10044 struct Row8;
10045 ˇstruct Row9;
10046 struct Row10;ˇ"#},
10047 base_text,
10048 &mut cx,
10049 );
10050}
10051
10052#[gpui::test]
10053async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
10054 init_test(cx, |_| {});
10055 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10056 let base_text = indoc! {r#"struct Row;
10057struct Row1;
10058struct Row2;
10059
10060struct Row4;
10061struct Row5;
10062struct Row6;
10063
10064struct Row8;
10065struct Row9;
10066struct Row10;"#};
10067
10068 // Deletion hunks trigger with carets on ajacent rows, so carets and selections have to stay farther to avoid the revert
10069 assert_hunk_revert(
10070 indoc! {r#"struct Row;
10071 struct Row2;
10072
10073 ˇstruct Row4;
10074 struct Row5;
10075 struct Row6;
10076 ˇ
10077 struct Row8;
10078 struct Row10;"#},
10079 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10080 indoc! {r#"struct Row;
10081 struct Row2;
10082
10083 ˇstruct Row4;
10084 struct Row5;
10085 struct Row6;
10086 ˇ
10087 struct Row8;
10088 struct Row10;"#},
10089 base_text,
10090 &mut cx,
10091 );
10092 assert_hunk_revert(
10093 indoc! {r#"struct Row;
10094 struct Row2;
10095
10096 «ˇstruct Row4;
10097 struct» Row5;
10098 «struct Row6;
10099 ˇ»
10100 struct Row8;
10101 struct Row10;"#},
10102 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10103 indoc! {r#"struct Row;
10104 struct Row2;
10105
10106 «ˇstruct Row4;
10107 struct» Row5;
10108 «struct Row6;
10109 ˇ»
10110 struct Row8;
10111 struct Row10;"#},
10112 base_text,
10113 &mut cx,
10114 );
10115
10116 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
10117 assert_hunk_revert(
10118 indoc! {r#"struct Row;
10119 ˇstruct Row2;
10120
10121 struct Row4;
10122 struct Row5;
10123 struct Row6;
10124
10125 struct Row8;ˇ
10126 struct Row10;"#},
10127 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10128 indoc! {r#"struct Row;
10129 struct Row1;
10130 ˇstruct Row2;
10131
10132 struct Row4;
10133 struct Row5;
10134 struct Row6;
10135
10136 struct Row8;ˇ
10137 struct Row9;
10138 struct Row10;"#},
10139 base_text,
10140 &mut cx,
10141 );
10142 assert_hunk_revert(
10143 indoc! {r#"struct Row;
10144 struct Row2«ˇ;
10145 struct Row4;
10146 struct» Row5;
10147 «struct Row6;
10148
10149 struct Row8;ˇ»
10150 struct Row10;"#},
10151 vec![
10152 DiffHunkStatus::Removed,
10153 DiffHunkStatus::Removed,
10154 DiffHunkStatus::Removed,
10155 ],
10156 indoc! {r#"struct Row;
10157 struct Row1;
10158 struct Row2«ˇ;
10159
10160 struct Row4;
10161 struct» Row5;
10162 «struct Row6;
10163
10164 struct Row8;ˇ»
10165 struct Row9;
10166 struct Row10;"#},
10167 base_text,
10168 &mut cx,
10169 );
10170}
10171
10172#[gpui::test]
10173async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
10174 init_test(cx, |_| {});
10175
10176 let cols = 4;
10177 let rows = 10;
10178 let sample_text_1 = sample_text(rows, cols, 'a');
10179 assert_eq!(
10180 sample_text_1,
10181 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10182 );
10183 let sample_text_2 = sample_text(rows, cols, 'l');
10184 assert_eq!(
10185 sample_text_2,
10186 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10187 );
10188 let sample_text_3 = sample_text(rows, cols, 'v');
10189 assert_eq!(
10190 sample_text_3,
10191 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10192 );
10193
10194 fn diff_every_buffer_row(
10195 buffer: &Model<Buffer>,
10196 sample_text: String,
10197 cols: usize,
10198 cx: &mut gpui::TestAppContext,
10199 ) {
10200 // revert first character in each row, creating one large diff hunk per buffer
10201 let is_first_char = |offset: usize| offset % cols == 0;
10202 buffer.update(cx, |buffer, cx| {
10203 buffer.set_text(
10204 sample_text
10205 .chars()
10206 .enumerate()
10207 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
10208 .collect::<String>(),
10209 cx,
10210 );
10211 buffer.set_diff_base(Some(sample_text), cx);
10212 });
10213 cx.executor().run_until_parked();
10214 }
10215
10216 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10217 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10218
10219 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10220 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10221
10222 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10223 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10224
10225 let multibuffer = cx.new_model(|cx| {
10226 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10227 multibuffer.push_excerpts(
10228 buffer_1.clone(),
10229 [
10230 ExcerptRange {
10231 context: Point::new(0, 0)..Point::new(3, 0),
10232 primary: None,
10233 },
10234 ExcerptRange {
10235 context: Point::new(5, 0)..Point::new(7, 0),
10236 primary: None,
10237 },
10238 ExcerptRange {
10239 context: Point::new(9, 0)..Point::new(10, 4),
10240 primary: None,
10241 },
10242 ],
10243 cx,
10244 );
10245 multibuffer.push_excerpts(
10246 buffer_2.clone(),
10247 [
10248 ExcerptRange {
10249 context: Point::new(0, 0)..Point::new(3, 0),
10250 primary: None,
10251 },
10252 ExcerptRange {
10253 context: Point::new(5, 0)..Point::new(7, 0),
10254 primary: None,
10255 },
10256 ExcerptRange {
10257 context: Point::new(9, 0)..Point::new(10, 4),
10258 primary: None,
10259 },
10260 ],
10261 cx,
10262 );
10263 multibuffer.push_excerpts(
10264 buffer_3.clone(),
10265 [
10266 ExcerptRange {
10267 context: Point::new(0, 0)..Point::new(3, 0),
10268 primary: None,
10269 },
10270 ExcerptRange {
10271 context: Point::new(5, 0)..Point::new(7, 0),
10272 primary: None,
10273 },
10274 ExcerptRange {
10275 context: Point::new(9, 0)..Point::new(10, 4),
10276 primary: None,
10277 },
10278 ],
10279 cx,
10280 );
10281 multibuffer
10282 });
10283
10284 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
10285 editor.update(cx, |editor, cx| {
10286 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");
10287 editor.select_all(&SelectAll, cx);
10288 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10289 });
10290 cx.executor().run_until_parked();
10291 // When all ranges are selected, all buffer hunks are reverted.
10292 editor.update(cx, |editor, cx| {
10293 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");
10294 });
10295 buffer_1.update(cx, |buffer, _| {
10296 assert_eq!(buffer.text(), sample_text_1);
10297 });
10298 buffer_2.update(cx, |buffer, _| {
10299 assert_eq!(buffer.text(), sample_text_2);
10300 });
10301 buffer_3.update(cx, |buffer, _| {
10302 assert_eq!(buffer.text(), sample_text_3);
10303 });
10304
10305 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10306 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10307 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10308 editor.update(cx, |editor, cx| {
10309 editor.change_selections(None, cx, |s| {
10310 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
10311 });
10312 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10313 });
10314 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
10315 // but not affect buffer_2 and its related excerpts.
10316 editor.update(cx, |editor, cx| {
10317 assert_eq!(
10318 editor.text(cx),
10319 "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"
10320 );
10321 });
10322 buffer_1.update(cx, |buffer, _| {
10323 assert_eq!(buffer.text(), sample_text_1);
10324 });
10325 buffer_2.update(cx, |buffer, _| {
10326 assert_eq!(
10327 buffer.text(),
10328 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
10329 );
10330 });
10331 buffer_3.update(cx, |buffer, _| {
10332 assert_eq!(
10333 buffer.text(),
10334 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
10335 );
10336 });
10337}
10338
10339#[gpui::test]
10340async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
10341 init_test(cx, |_| {});
10342
10343 let cols = 4;
10344 let rows = 10;
10345 let sample_text_1 = sample_text(rows, cols, 'a');
10346 assert_eq!(
10347 sample_text_1,
10348 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10349 );
10350 let sample_text_2 = sample_text(rows, cols, 'l');
10351 assert_eq!(
10352 sample_text_2,
10353 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10354 );
10355 let sample_text_3 = sample_text(rows, cols, 'v');
10356 assert_eq!(
10357 sample_text_3,
10358 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10359 );
10360
10361 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10362 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10363 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10364
10365 let multi_buffer = cx.new_model(|cx| {
10366 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10367 multibuffer.push_excerpts(
10368 buffer_1.clone(),
10369 [
10370 ExcerptRange {
10371 context: Point::new(0, 0)..Point::new(3, 0),
10372 primary: None,
10373 },
10374 ExcerptRange {
10375 context: Point::new(5, 0)..Point::new(7, 0),
10376 primary: None,
10377 },
10378 ExcerptRange {
10379 context: Point::new(9, 0)..Point::new(10, 4),
10380 primary: None,
10381 },
10382 ],
10383 cx,
10384 );
10385 multibuffer.push_excerpts(
10386 buffer_2.clone(),
10387 [
10388 ExcerptRange {
10389 context: Point::new(0, 0)..Point::new(3, 0),
10390 primary: None,
10391 },
10392 ExcerptRange {
10393 context: Point::new(5, 0)..Point::new(7, 0),
10394 primary: None,
10395 },
10396 ExcerptRange {
10397 context: Point::new(9, 0)..Point::new(10, 4),
10398 primary: None,
10399 },
10400 ],
10401 cx,
10402 );
10403 multibuffer.push_excerpts(
10404 buffer_3.clone(),
10405 [
10406 ExcerptRange {
10407 context: Point::new(0, 0)..Point::new(3, 0),
10408 primary: None,
10409 },
10410 ExcerptRange {
10411 context: Point::new(5, 0)..Point::new(7, 0),
10412 primary: None,
10413 },
10414 ExcerptRange {
10415 context: Point::new(9, 0)..Point::new(10, 4),
10416 primary: None,
10417 },
10418 ],
10419 cx,
10420 );
10421 multibuffer
10422 });
10423
10424 let fs = FakeFs::new(cx.executor());
10425 fs.insert_tree(
10426 "/a",
10427 json!({
10428 "main.rs": sample_text_1,
10429 "other.rs": sample_text_2,
10430 "lib.rs": sample_text_3,
10431 }),
10432 )
10433 .await;
10434 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10435 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10436 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10437 let multi_buffer_editor = cx.new_view(|cx| {
10438 Editor::new(
10439 EditorMode::Full,
10440 multi_buffer,
10441 Some(project.clone()),
10442 true,
10443 cx,
10444 )
10445 });
10446 let multibuffer_item_id = workspace
10447 .update(cx, |workspace, cx| {
10448 assert!(
10449 workspace.active_item(cx).is_none(),
10450 "active item should be None before the first item is added"
10451 );
10452 workspace.add_item_to_active_pane(
10453 Box::new(multi_buffer_editor.clone()),
10454 None,
10455 true,
10456 cx,
10457 );
10458 let active_item = workspace
10459 .active_item(cx)
10460 .expect("should have an active item after adding the multi buffer");
10461 assert!(
10462 !active_item.is_singleton(cx),
10463 "A multi buffer was expected to active after adding"
10464 );
10465 active_item.item_id()
10466 })
10467 .unwrap();
10468 cx.executor().run_until_parked();
10469
10470 multi_buffer_editor.update(cx, |editor, cx| {
10471 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
10472 editor.open_excerpts(&OpenExcerpts, cx);
10473 });
10474 cx.executor().run_until_parked();
10475 let first_item_id = workspace
10476 .update(cx, |workspace, cx| {
10477 let active_item = workspace
10478 .active_item(cx)
10479 .expect("should have an active item after navigating into the 1st buffer");
10480 let first_item_id = active_item.item_id();
10481 assert_ne!(
10482 first_item_id, multibuffer_item_id,
10483 "Should navigate into the 1st buffer and activate it"
10484 );
10485 assert!(
10486 active_item.is_singleton(cx),
10487 "New active item should be a singleton buffer"
10488 );
10489 assert_eq!(
10490 active_item
10491 .act_as::<Editor>(cx)
10492 .expect("should have navigated into an editor for the 1st buffer")
10493 .read(cx)
10494 .text(cx),
10495 sample_text_1
10496 );
10497
10498 workspace
10499 .go_back(workspace.active_pane().downgrade(), cx)
10500 .detach_and_log_err(cx);
10501
10502 first_item_id
10503 })
10504 .unwrap();
10505 cx.executor().run_until_parked();
10506 workspace
10507 .update(cx, |workspace, cx| {
10508 let active_item = workspace
10509 .active_item(cx)
10510 .expect("should have an active item after navigating back");
10511 assert_eq!(
10512 active_item.item_id(),
10513 multibuffer_item_id,
10514 "Should navigate back to the multi buffer"
10515 );
10516 assert!(!active_item.is_singleton(cx));
10517 })
10518 .unwrap();
10519
10520 multi_buffer_editor.update(cx, |editor, cx| {
10521 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
10522 s.select_ranges(Some(39..40))
10523 });
10524 editor.open_excerpts(&OpenExcerpts, cx);
10525 });
10526 cx.executor().run_until_parked();
10527 let second_item_id = workspace
10528 .update(cx, |workspace, cx| {
10529 let active_item = workspace
10530 .active_item(cx)
10531 .expect("should have an active item after navigating into the 2nd buffer");
10532 let second_item_id = active_item.item_id();
10533 assert_ne!(
10534 second_item_id, multibuffer_item_id,
10535 "Should navigate away from the multibuffer"
10536 );
10537 assert_ne!(
10538 second_item_id, first_item_id,
10539 "Should navigate into the 2nd buffer and activate it"
10540 );
10541 assert!(
10542 active_item.is_singleton(cx),
10543 "New active item should be a singleton buffer"
10544 );
10545 assert_eq!(
10546 active_item
10547 .act_as::<Editor>(cx)
10548 .expect("should have navigated into an editor")
10549 .read(cx)
10550 .text(cx),
10551 sample_text_2
10552 );
10553
10554 workspace
10555 .go_back(workspace.active_pane().downgrade(), cx)
10556 .detach_and_log_err(cx);
10557
10558 second_item_id
10559 })
10560 .unwrap();
10561 cx.executor().run_until_parked();
10562 workspace
10563 .update(cx, |workspace, cx| {
10564 let active_item = workspace
10565 .active_item(cx)
10566 .expect("should have an active item after navigating back from the 2nd buffer");
10567 assert_eq!(
10568 active_item.item_id(),
10569 multibuffer_item_id,
10570 "Should navigate back from the 2nd buffer to the multi buffer"
10571 );
10572 assert!(!active_item.is_singleton(cx));
10573 })
10574 .unwrap();
10575
10576 multi_buffer_editor.update(cx, |editor, cx| {
10577 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
10578 s.select_ranges(Some(60..70))
10579 });
10580 editor.open_excerpts(&OpenExcerpts, cx);
10581 });
10582 cx.executor().run_until_parked();
10583 workspace
10584 .update(cx, |workspace, cx| {
10585 let active_item = workspace
10586 .active_item(cx)
10587 .expect("should have an active item after navigating into the 3rd buffer");
10588 let third_item_id = active_item.item_id();
10589 assert_ne!(
10590 third_item_id, multibuffer_item_id,
10591 "Should navigate into the 3rd buffer and activate it"
10592 );
10593 assert_ne!(third_item_id, first_item_id);
10594 assert_ne!(third_item_id, second_item_id);
10595 assert!(
10596 active_item.is_singleton(cx),
10597 "New active item should be a singleton buffer"
10598 );
10599 assert_eq!(
10600 active_item
10601 .act_as::<Editor>(cx)
10602 .expect("should have navigated into an editor")
10603 .read(cx)
10604 .text(cx),
10605 sample_text_3
10606 );
10607
10608 workspace
10609 .go_back(workspace.active_pane().downgrade(), cx)
10610 .detach_and_log_err(cx);
10611 })
10612 .unwrap();
10613 cx.executor().run_until_parked();
10614 workspace
10615 .update(cx, |workspace, cx| {
10616 let active_item = workspace
10617 .active_item(cx)
10618 .expect("should have an active item after navigating back from the 3rd buffer");
10619 assert_eq!(
10620 active_item.item_id(),
10621 multibuffer_item_id,
10622 "Should navigate back from the 3rd buffer to the multi buffer"
10623 );
10624 assert!(!active_item.is_singleton(cx));
10625 })
10626 .unwrap();
10627}
10628
10629#[gpui::test]
10630async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10631 init_test(cx, |_| {});
10632
10633 let mut cx = EditorTestContext::new(cx).await;
10634
10635 let diff_base = r#"
10636 use some::mod;
10637
10638 const A: u32 = 42;
10639
10640 fn main() {
10641 println!("hello");
10642
10643 println!("world");
10644 }
10645 "#
10646 .unindent();
10647
10648 cx.set_state(
10649 &r#"
10650 use some::modified;
10651
10652 ˇ
10653 fn main() {
10654 println!("hello there");
10655
10656 println!("around the");
10657 println!("world");
10658 }
10659 "#
10660 .unindent(),
10661 );
10662
10663 cx.set_diff_base(Some(&diff_base));
10664 executor.run_until_parked();
10665 let unexpanded_hunks = vec![
10666 (
10667 "use some::mod;\n".to_string(),
10668 DiffHunkStatus::Modified,
10669 DisplayRow(0)..DisplayRow(1),
10670 ),
10671 (
10672 "const A: u32 = 42;\n".to_string(),
10673 DiffHunkStatus::Removed,
10674 DisplayRow(2)..DisplayRow(2),
10675 ),
10676 (
10677 " println!(\"hello\");\n".to_string(),
10678 DiffHunkStatus::Modified,
10679 DisplayRow(4)..DisplayRow(5),
10680 ),
10681 (
10682 "".to_string(),
10683 DiffHunkStatus::Added,
10684 DisplayRow(6)..DisplayRow(7),
10685 ),
10686 ];
10687 cx.update_editor(|editor, cx| {
10688 let snapshot = editor.snapshot(cx);
10689 let all_hunks = editor_hunks(editor, &snapshot, cx);
10690 assert_eq!(all_hunks, unexpanded_hunks);
10691 });
10692
10693 cx.update_editor(|editor, cx| {
10694 for _ in 0..4 {
10695 editor.go_to_hunk(&GoToHunk, cx);
10696 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10697 }
10698 });
10699 executor.run_until_parked();
10700 cx.assert_editor_state(
10701 &r#"
10702 use some::modified;
10703
10704 ˇ
10705 fn main() {
10706 println!("hello there");
10707
10708 println!("around the");
10709 println!("world");
10710 }
10711 "#
10712 .unindent(),
10713 );
10714 cx.update_editor(|editor, cx| {
10715 let snapshot = editor.snapshot(cx);
10716 let all_hunks = editor_hunks(editor, &snapshot, cx);
10717 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10718 assert_eq!(
10719 expanded_hunks_background_highlights(editor, cx),
10720 vec![DisplayRow(1)..=DisplayRow(1), DisplayRow(7)..=DisplayRow(7), DisplayRow(9)..=DisplayRow(9)],
10721 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
10722 );
10723 assert_eq!(
10724 all_hunks,
10725 vec![
10726 ("use some::mod;\n".to_string(), DiffHunkStatus::Modified, DisplayRow(1)..DisplayRow(2)),
10727 ("const A: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(4)..DisplayRow(4)),
10728 (" println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(7)..DisplayRow(8)),
10729 ("".to_string(), DiffHunkStatus::Added, DisplayRow(9)..DisplayRow(10)),
10730 ],
10731 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
10732 (from modified and removed hunks)"
10733 );
10734 assert_eq!(
10735 all_hunks, all_expanded_hunks,
10736 "Editor hunks should not change and all be expanded"
10737 );
10738 });
10739
10740 cx.update_editor(|editor, cx| {
10741 editor.cancel(&Cancel, cx);
10742
10743 let snapshot = editor.snapshot(cx);
10744 let all_hunks = editor_hunks(editor, &snapshot, cx);
10745 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
10746 assert_eq!(
10747 expanded_hunks_background_highlights(editor, cx),
10748 Vec::new(),
10749 "After cancelling in editor, no git highlights should be left"
10750 );
10751 assert_eq!(
10752 all_expanded_hunks,
10753 Vec::new(),
10754 "After cancelling in editor, no hunks should be expanded"
10755 );
10756 assert_eq!(
10757 all_hunks, unexpanded_hunks,
10758 "After cancelling in editor, regular hunks' coordinates should get back to normal"
10759 );
10760 });
10761}
10762
10763#[gpui::test]
10764async fn test_toggled_diff_base_change(
10765 executor: BackgroundExecutor,
10766 cx: &mut gpui::TestAppContext,
10767) {
10768 init_test(cx, |_| {});
10769
10770 let mut cx = EditorTestContext::new(cx).await;
10771
10772 let diff_base = r#"
10773 use some::mod1;
10774 use some::mod2;
10775
10776 const A: u32 = 42;
10777 const B: u32 = 42;
10778 const C: u32 = 42;
10779
10780 fn main(ˇ) {
10781 println!("hello");
10782
10783 println!("world");
10784 }
10785 "#
10786 .unindent();
10787
10788 cx.set_state(
10789 &r#"
10790 use some::mod2;
10791
10792 const A: u32 = 42;
10793 const C: u32 = 42;
10794
10795 fn main(ˇ) {
10796 //println!("hello");
10797
10798 println!("world");
10799 //
10800 //
10801 }
10802 "#
10803 .unindent(),
10804 );
10805
10806 cx.set_diff_base(Some(&diff_base));
10807 executor.run_until_parked();
10808 cx.update_editor(|editor, cx| {
10809 let snapshot = editor.snapshot(cx);
10810 let all_hunks = editor_hunks(editor, &snapshot, cx);
10811 assert_eq!(
10812 all_hunks,
10813 vec![
10814 (
10815 "use some::mod1;\n".to_string(),
10816 DiffHunkStatus::Removed,
10817 DisplayRow(0)..DisplayRow(0)
10818 ),
10819 (
10820 "const B: u32 = 42;\n".to_string(),
10821 DiffHunkStatus::Removed,
10822 DisplayRow(3)..DisplayRow(3)
10823 ),
10824 (
10825 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
10826 DiffHunkStatus::Modified,
10827 DisplayRow(5)..DisplayRow(7)
10828 ),
10829 (
10830 "".to_string(),
10831 DiffHunkStatus::Added,
10832 DisplayRow(9)..DisplayRow(11)
10833 ),
10834 ]
10835 );
10836 });
10837
10838 cx.update_editor(|editor, cx| {
10839 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10840 });
10841 executor.run_until_parked();
10842 cx.assert_editor_state(
10843 &r#"
10844 use some::mod2;
10845
10846 const A: u32 = 42;
10847 const C: u32 = 42;
10848
10849 fn main(ˇ) {
10850 //println!("hello");
10851
10852 println!("world");
10853 //
10854 //
10855 }
10856 "#
10857 .unindent(),
10858 );
10859 cx.update_editor(|editor, cx| {
10860 let snapshot = editor.snapshot(cx);
10861 let all_hunks = editor_hunks(editor, &snapshot, cx);
10862 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10863 assert_eq!(
10864 expanded_hunks_background_highlights(editor, cx),
10865 vec![DisplayRow(9)..=DisplayRow(10), DisplayRow(13)..=DisplayRow(14)],
10866 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
10867 );
10868 assert_eq!(
10869 all_hunks,
10870 vec![
10871 ("use some::mod1;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(1)..DisplayRow(1)),
10872 ("const B: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(5)..DisplayRow(5)),
10873 ("fn main(ˇ) {\n println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(9)..DisplayRow(11)),
10874 ("".to_string(), DiffHunkStatus::Added, DisplayRow(13)..DisplayRow(15)),
10875 ],
10876 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
10877 (from modified and removed hunks)"
10878 );
10879 assert_eq!(
10880 all_hunks, all_expanded_hunks,
10881 "Editor hunks should not change and all be expanded"
10882 );
10883 });
10884
10885 cx.set_diff_base(Some("new diff base!"));
10886 executor.run_until_parked();
10887
10888 cx.update_editor(|editor, cx| {
10889 let snapshot = editor.snapshot(cx);
10890 let all_hunks = editor_hunks(editor, &snapshot, cx);
10891 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
10892 assert_eq!(
10893 expanded_hunks_background_highlights(editor, cx),
10894 Vec::new(),
10895 "After diff base is changed, old git highlights should be removed"
10896 );
10897 assert_eq!(
10898 all_expanded_hunks,
10899 Vec::new(),
10900 "After diff base is changed, old git hunk expansions should be removed"
10901 );
10902 assert_eq!(
10903 all_hunks,
10904 vec![(
10905 "new diff base!".to_string(),
10906 DiffHunkStatus::Modified,
10907 DisplayRow(0)..snapshot.display_snapshot.max_point().row()
10908 )],
10909 "After diff base is changed, hunks should update"
10910 );
10911 });
10912}
10913
10914#[gpui::test]
10915async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10916 init_test(cx, |_| {});
10917
10918 let mut cx = EditorTestContext::new(cx).await;
10919
10920 let diff_base = r#"
10921 use some::mod1;
10922 use some::mod2;
10923
10924 const A: u32 = 42;
10925 const B: u32 = 42;
10926 const C: u32 = 42;
10927
10928 fn main(ˇ) {
10929 println!("hello");
10930
10931 println!("world");
10932 }
10933
10934 fn another() {
10935 println!("another");
10936 }
10937
10938 fn another2() {
10939 println!("another2");
10940 }
10941 "#
10942 .unindent();
10943
10944 cx.set_state(
10945 &r#"
10946 «use some::mod2;
10947
10948 const A: u32 = 42;
10949 const C: u32 = 42;
10950
10951 fn main() {
10952 //println!("hello");
10953
10954 println!("world");
10955 //
10956 //ˇ»
10957 }
10958
10959 fn another() {
10960 println!("another");
10961 println!("another");
10962 }
10963
10964 println!("another2");
10965 }
10966 "#
10967 .unindent(),
10968 );
10969
10970 cx.set_diff_base(Some(&diff_base));
10971 executor.run_until_parked();
10972 cx.update_editor(|editor, cx| {
10973 let snapshot = editor.snapshot(cx);
10974 let all_hunks = editor_hunks(editor, &snapshot, cx);
10975 assert_eq!(
10976 all_hunks,
10977 vec![
10978 (
10979 "use some::mod1;\n".to_string(),
10980 DiffHunkStatus::Removed,
10981 DisplayRow(0)..DisplayRow(0)
10982 ),
10983 (
10984 "const B: u32 = 42;\n".to_string(),
10985 DiffHunkStatus::Removed,
10986 DisplayRow(3)..DisplayRow(3)
10987 ),
10988 (
10989 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
10990 DiffHunkStatus::Modified,
10991 DisplayRow(5)..DisplayRow(7)
10992 ),
10993 (
10994 "".to_string(),
10995 DiffHunkStatus::Added,
10996 DisplayRow(9)..DisplayRow(11)
10997 ),
10998 (
10999 "".to_string(),
11000 DiffHunkStatus::Added,
11001 DisplayRow(15)..DisplayRow(16)
11002 ),
11003 (
11004 "fn another2() {\n".to_string(),
11005 DiffHunkStatus::Removed,
11006 DisplayRow(18)..DisplayRow(18)
11007 ),
11008 ]
11009 );
11010 });
11011
11012 cx.update_editor(|editor, cx| {
11013 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11014 });
11015 executor.run_until_parked();
11016 cx.assert_editor_state(
11017 &r#"
11018 «use some::mod2;
11019
11020 const A: u32 = 42;
11021 const C: u32 = 42;
11022
11023 fn main() {
11024 //println!("hello");
11025
11026 println!("world");
11027 //
11028 //ˇ»
11029 }
11030
11031 fn another() {
11032 println!("another");
11033 println!("another");
11034 }
11035
11036 println!("another2");
11037 }
11038 "#
11039 .unindent(),
11040 );
11041 cx.update_editor(|editor, cx| {
11042 let snapshot = editor.snapshot(cx);
11043 let all_hunks = editor_hunks(editor, &snapshot, cx);
11044 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11045 assert_eq!(
11046 expanded_hunks_background_highlights(editor, cx),
11047 vec![
11048 DisplayRow(9)..=DisplayRow(10),
11049 DisplayRow(13)..=DisplayRow(14),
11050 DisplayRow(19)..=DisplayRow(19)
11051 ]
11052 );
11053 assert_eq!(
11054 all_hunks,
11055 vec![
11056 (
11057 "use some::mod1;\n".to_string(),
11058 DiffHunkStatus::Removed,
11059 DisplayRow(1)..DisplayRow(1)
11060 ),
11061 (
11062 "const B: u32 = 42;\n".to_string(),
11063 DiffHunkStatus::Removed,
11064 DisplayRow(5)..DisplayRow(5)
11065 ),
11066 (
11067 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11068 DiffHunkStatus::Modified,
11069 DisplayRow(9)..DisplayRow(11)
11070 ),
11071 (
11072 "".to_string(),
11073 DiffHunkStatus::Added,
11074 DisplayRow(13)..DisplayRow(15)
11075 ),
11076 (
11077 "".to_string(),
11078 DiffHunkStatus::Added,
11079 DisplayRow(19)..DisplayRow(20)
11080 ),
11081 (
11082 "fn another2() {\n".to_string(),
11083 DiffHunkStatus::Removed,
11084 DisplayRow(23)..DisplayRow(23)
11085 ),
11086 ],
11087 );
11088 assert_eq!(all_hunks, all_expanded_hunks);
11089 });
11090
11091 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
11092 cx.executor().run_until_parked();
11093 cx.assert_editor_state(
11094 &r#"
11095 «use some::mod2;
11096
11097 const A: u32 = 42;
11098 const C: u32 = 42;
11099
11100 fn main() {
11101 //println!("hello");
11102
11103 println!("world");
11104 //
11105 //ˇ»
11106 }
11107
11108 fn another() {
11109 println!("another");
11110 println!("another");
11111 }
11112
11113 println!("another2");
11114 }
11115 "#
11116 .unindent(),
11117 );
11118 cx.update_editor(|editor, cx| {
11119 let snapshot = editor.snapshot(cx);
11120 let all_hunks = editor_hunks(editor, &snapshot, cx);
11121 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11122 assert_eq!(
11123 expanded_hunks_background_highlights(editor, cx),
11124 vec![DisplayRow(0)..=DisplayRow(0), DisplayRow(5)..=DisplayRow(5)],
11125 "Only one hunk is left not folded, its highlight should be visible"
11126 );
11127 assert_eq!(
11128 all_hunks,
11129 vec![
11130 (
11131 "use some::mod1;\n".to_string(),
11132 DiffHunkStatus::Removed,
11133 DisplayRow(0)..DisplayRow(0)
11134 ),
11135 (
11136 "const B: u32 = 42;\n".to_string(),
11137 DiffHunkStatus::Removed,
11138 DisplayRow(0)..DisplayRow(0)
11139 ),
11140 (
11141 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11142 DiffHunkStatus::Modified,
11143 DisplayRow(0)..DisplayRow(0)
11144 ),
11145 (
11146 "".to_string(),
11147 DiffHunkStatus::Added,
11148 DisplayRow(0)..DisplayRow(1)
11149 ),
11150 (
11151 "".to_string(),
11152 DiffHunkStatus::Added,
11153 DisplayRow(5)..DisplayRow(6)
11154 ),
11155 (
11156 "fn another2() {\n".to_string(),
11157 DiffHunkStatus::Removed,
11158 DisplayRow(9)..DisplayRow(9)
11159 ),
11160 ],
11161 "Hunk list should still return shifted folded hunks"
11162 );
11163 assert_eq!(
11164 all_expanded_hunks,
11165 vec![
11166 (
11167 "".to_string(),
11168 DiffHunkStatus::Added,
11169 DisplayRow(5)..DisplayRow(6)
11170 ),
11171 (
11172 "fn another2() {\n".to_string(),
11173 DiffHunkStatus::Removed,
11174 DisplayRow(9)..DisplayRow(9)
11175 ),
11176 ],
11177 "Only non-folded hunks should be left expanded"
11178 );
11179 });
11180
11181 cx.update_editor(|editor, cx| {
11182 editor.select_all(&SelectAll, cx);
11183 editor.unfold_lines(&UnfoldLines, cx);
11184 });
11185 cx.executor().run_until_parked();
11186 cx.assert_editor_state(
11187 &r#"
11188 «use some::mod2;
11189
11190 const A: u32 = 42;
11191 const C: u32 = 42;
11192
11193 fn main() {
11194 //println!("hello");
11195
11196 println!("world");
11197 //
11198 //
11199 }
11200
11201 fn another() {
11202 println!("another");
11203 println!("another");
11204 }
11205
11206 println!("another2");
11207 }
11208 ˇ»"#
11209 .unindent(),
11210 );
11211 cx.update_editor(|editor, cx| {
11212 let snapshot = editor.snapshot(cx);
11213 let all_hunks = editor_hunks(editor, &snapshot, cx);
11214 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11215 assert_eq!(
11216 expanded_hunks_background_highlights(editor, cx),
11217 vec![
11218 DisplayRow(9)..=DisplayRow(10),
11219 DisplayRow(13)..=DisplayRow(14),
11220 DisplayRow(19)..=DisplayRow(19)
11221 ],
11222 "After unfolding, all hunk diffs should be visible again"
11223 );
11224 assert_eq!(
11225 all_hunks,
11226 vec![
11227 (
11228 "use some::mod1;\n".to_string(),
11229 DiffHunkStatus::Removed,
11230 DisplayRow(1)..DisplayRow(1)
11231 ),
11232 (
11233 "const B: u32 = 42;\n".to_string(),
11234 DiffHunkStatus::Removed,
11235 DisplayRow(5)..DisplayRow(5)
11236 ),
11237 (
11238 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11239 DiffHunkStatus::Modified,
11240 DisplayRow(9)..DisplayRow(11)
11241 ),
11242 (
11243 "".to_string(),
11244 DiffHunkStatus::Added,
11245 DisplayRow(13)..DisplayRow(15)
11246 ),
11247 (
11248 "".to_string(),
11249 DiffHunkStatus::Added,
11250 DisplayRow(19)..DisplayRow(20)
11251 ),
11252 (
11253 "fn another2() {\n".to_string(),
11254 DiffHunkStatus::Removed,
11255 DisplayRow(23)..DisplayRow(23)
11256 ),
11257 ],
11258 );
11259 assert_eq!(all_hunks, all_expanded_hunks);
11260 });
11261}
11262
11263#[gpui::test]
11264async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
11265 init_test(cx, |_| {});
11266
11267 let cols = 4;
11268 let rows = 10;
11269 let sample_text_1 = sample_text(rows, cols, 'a');
11270 assert_eq!(
11271 sample_text_1,
11272 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11273 );
11274 let modified_sample_text_1 = "aaaa\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
11275 let sample_text_2 = sample_text(rows, cols, 'l');
11276 assert_eq!(
11277 sample_text_2,
11278 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11279 );
11280 let modified_sample_text_2 = "llll\nmmmm\n1n1n1n1n1\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
11281 let sample_text_3 = sample_text(rows, cols, 'v');
11282 assert_eq!(
11283 sample_text_3,
11284 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11285 );
11286 let modified_sample_text_3 =
11287 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n@@@@\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
11288 let buffer_1 = cx.new_model(|cx| {
11289 let mut buffer = Buffer::local(modified_sample_text_1.to_string(), cx);
11290 buffer.set_diff_base(Some(sample_text_1.clone()), cx);
11291 buffer
11292 });
11293 let buffer_2 = cx.new_model(|cx| {
11294 let mut buffer = Buffer::local(modified_sample_text_2.to_string(), cx);
11295 buffer.set_diff_base(Some(sample_text_2.clone()), cx);
11296 buffer
11297 });
11298 let buffer_3 = cx.new_model(|cx| {
11299 let mut buffer = Buffer::local(modified_sample_text_3.to_string(), cx);
11300 buffer.set_diff_base(Some(sample_text_3.clone()), cx);
11301 buffer
11302 });
11303
11304 let multi_buffer = cx.new_model(|cx| {
11305 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
11306 multibuffer.push_excerpts(
11307 buffer_1.clone(),
11308 [
11309 ExcerptRange {
11310 context: Point::new(0, 0)..Point::new(3, 0),
11311 primary: None,
11312 },
11313 ExcerptRange {
11314 context: Point::new(5, 0)..Point::new(7, 0),
11315 primary: None,
11316 },
11317 ExcerptRange {
11318 context: Point::new(9, 0)..Point::new(10, 4),
11319 primary: None,
11320 },
11321 ],
11322 cx,
11323 );
11324 multibuffer.push_excerpts(
11325 buffer_2.clone(),
11326 [
11327 ExcerptRange {
11328 context: Point::new(0, 0)..Point::new(3, 0),
11329 primary: None,
11330 },
11331 ExcerptRange {
11332 context: Point::new(5, 0)..Point::new(7, 0),
11333 primary: None,
11334 },
11335 ExcerptRange {
11336 context: Point::new(9, 0)..Point::new(10, 4),
11337 primary: None,
11338 },
11339 ],
11340 cx,
11341 );
11342 multibuffer.push_excerpts(
11343 buffer_3.clone(),
11344 [
11345 ExcerptRange {
11346 context: Point::new(0, 0)..Point::new(3, 0),
11347 primary: None,
11348 },
11349 ExcerptRange {
11350 context: Point::new(5, 0)..Point::new(7, 0),
11351 primary: None,
11352 },
11353 ExcerptRange {
11354 context: Point::new(9, 0)..Point::new(10, 4),
11355 primary: None,
11356 },
11357 ],
11358 cx,
11359 );
11360 multibuffer
11361 });
11362
11363 let fs = FakeFs::new(cx.executor());
11364 fs.insert_tree(
11365 "/a",
11366 json!({
11367 "main.rs": modified_sample_text_1,
11368 "other.rs": modified_sample_text_2,
11369 "lib.rs": modified_sample_text_3,
11370 }),
11371 )
11372 .await;
11373
11374 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11375 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11376 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11377 let multi_buffer_editor = cx.new_view(|cx| {
11378 Editor::new(
11379 EditorMode::Full,
11380 multi_buffer,
11381 Some(project.clone()),
11382 true,
11383 cx,
11384 )
11385 });
11386 cx.executor().run_until_parked();
11387
11388 let expected_all_hunks = vec![
11389 (
11390 "bbbb\n".to_string(),
11391 DiffHunkStatus::Removed,
11392 DisplayRow(4)..DisplayRow(4),
11393 ),
11394 (
11395 "nnnn\n".to_string(),
11396 DiffHunkStatus::Modified,
11397 DisplayRow(21)..DisplayRow(22),
11398 ),
11399 (
11400 "".to_string(),
11401 DiffHunkStatus::Added,
11402 DisplayRow(41)..DisplayRow(42),
11403 ),
11404 ];
11405 let expected_all_hunks_shifted = vec![
11406 (
11407 "bbbb\n".to_string(),
11408 DiffHunkStatus::Removed,
11409 DisplayRow(5)..DisplayRow(5),
11410 ),
11411 (
11412 "nnnn\n".to_string(),
11413 DiffHunkStatus::Modified,
11414 DisplayRow(23)..DisplayRow(24),
11415 ),
11416 (
11417 "".to_string(),
11418 DiffHunkStatus::Added,
11419 DisplayRow(43)..DisplayRow(44),
11420 ),
11421 ];
11422
11423 multi_buffer_editor.update(cx, |editor, cx| {
11424 let snapshot = editor.snapshot(cx);
11425 let all_hunks = editor_hunks(editor, &snapshot, cx);
11426 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11427 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11428 assert_eq!(all_hunks, expected_all_hunks);
11429 assert_eq!(all_expanded_hunks, Vec::new());
11430 });
11431
11432 multi_buffer_editor.update(cx, |editor, cx| {
11433 editor.select_all(&SelectAll, cx);
11434 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11435 });
11436 cx.executor().run_until_parked();
11437 multi_buffer_editor.update(cx, |editor, cx| {
11438 let snapshot = editor.snapshot(cx);
11439 let all_hunks = editor_hunks(editor, &snapshot, cx);
11440 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11441 assert_eq!(
11442 expanded_hunks_background_highlights(editor, cx),
11443 vec![
11444 DisplayRow(23)..=DisplayRow(23),
11445 DisplayRow(43)..=DisplayRow(43)
11446 ],
11447 );
11448 assert_eq!(all_hunks, expected_all_hunks_shifted);
11449 assert_eq!(all_hunks, all_expanded_hunks);
11450 });
11451
11452 multi_buffer_editor.update(cx, |editor, cx| {
11453 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11454 });
11455 cx.executor().run_until_parked();
11456 multi_buffer_editor.update(cx, |editor, cx| {
11457 let snapshot = editor.snapshot(cx);
11458 let all_hunks = editor_hunks(editor, &snapshot, cx);
11459 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11460 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11461 assert_eq!(all_hunks, expected_all_hunks);
11462 assert_eq!(all_expanded_hunks, Vec::new());
11463 });
11464
11465 multi_buffer_editor.update(cx, |editor, cx| {
11466 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11467 });
11468 cx.executor().run_until_parked();
11469 multi_buffer_editor.update(cx, |editor, cx| {
11470 let snapshot = editor.snapshot(cx);
11471 let all_hunks = editor_hunks(editor, &snapshot, cx);
11472 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11473 assert_eq!(
11474 expanded_hunks_background_highlights(editor, cx),
11475 vec![
11476 DisplayRow(23)..=DisplayRow(23),
11477 DisplayRow(43)..=DisplayRow(43)
11478 ],
11479 );
11480 assert_eq!(all_hunks, expected_all_hunks_shifted);
11481 assert_eq!(all_hunks, all_expanded_hunks);
11482 });
11483
11484 multi_buffer_editor.update(cx, |editor, cx| {
11485 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11486 });
11487 cx.executor().run_until_parked();
11488 multi_buffer_editor.update(cx, |editor, cx| {
11489 let snapshot = editor.snapshot(cx);
11490 let all_hunks = editor_hunks(editor, &snapshot, cx);
11491 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11492 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11493 assert_eq!(all_hunks, expected_all_hunks);
11494 assert_eq!(all_expanded_hunks, Vec::new());
11495 });
11496}
11497
11498#[gpui::test]
11499async fn test_edits_around_toggled_additions(
11500 executor: BackgroundExecutor,
11501 cx: &mut gpui::TestAppContext,
11502) {
11503 init_test(cx, |_| {});
11504
11505 let mut cx = EditorTestContext::new(cx).await;
11506
11507 let diff_base = r#"
11508 use some::mod1;
11509 use some::mod2;
11510
11511 const A: u32 = 42;
11512
11513 fn main() {
11514 println!("hello");
11515
11516 println!("world");
11517 }
11518 "#
11519 .unindent();
11520 executor.run_until_parked();
11521 cx.set_state(
11522 &r#"
11523 use some::mod1;
11524 use some::mod2;
11525
11526 const A: u32 = 42;
11527 const B: u32 = 42;
11528 const C: u32 = 42;
11529 ˇ
11530
11531 fn main() {
11532 println!("hello");
11533
11534 println!("world");
11535 }
11536 "#
11537 .unindent(),
11538 );
11539
11540 cx.set_diff_base(Some(&diff_base));
11541 executor.run_until_parked();
11542 cx.update_editor(|editor, cx| {
11543 let snapshot = editor.snapshot(cx);
11544 let all_hunks = editor_hunks(editor, &snapshot, cx);
11545 assert_eq!(
11546 all_hunks,
11547 vec![(
11548 "".to_string(),
11549 DiffHunkStatus::Added,
11550 DisplayRow(4)..DisplayRow(7)
11551 )]
11552 );
11553 });
11554 cx.update_editor(|editor, cx| {
11555 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11556 });
11557 executor.run_until_parked();
11558 cx.assert_editor_state(
11559 &r#"
11560 use some::mod1;
11561 use some::mod2;
11562
11563 const A: u32 = 42;
11564 const B: u32 = 42;
11565 const C: u32 = 42;
11566 ˇ
11567
11568 fn main() {
11569 println!("hello");
11570
11571 println!("world");
11572 }
11573 "#
11574 .unindent(),
11575 );
11576 cx.update_editor(|editor, cx| {
11577 let snapshot = editor.snapshot(cx);
11578 let all_hunks = editor_hunks(editor, &snapshot, cx);
11579 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11580 assert_eq!(
11581 all_hunks,
11582 vec![(
11583 "".to_string(),
11584 DiffHunkStatus::Added,
11585 DisplayRow(4)..DisplayRow(7)
11586 )]
11587 );
11588 assert_eq!(
11589 expanded_hunks_background_highlights(editor, cx),
11590 vec![DisplayRow(4)..=DisplayRow(6)]
11591 );
11592 assert_eq!(all_hunks, all_expanded_hunks);
11593 });
11594
11595 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
11596 executor.run_until_parked();
11597 cx.assert_editor_state(
11598 &r#"
11599 use some::mod1;
11600 use some::mod2;
11601
11602 const A: u32 = 42;
11603 const B: u32 = 42;
11604 const C: u32 = 42;
11605 const D: u32 = 42;
11606 ˇ
11607
11608 fn main() {
11609 println!("hello");
11610
11611 println!("world");
11612 }
11613 "#
11614 .unindent(),
11615 );
11616 cx.update_editor(|editor, cx| {
11617 let snapshot = editor.snapshot(cx);
11618 let all_hunks = editor_hunks(editor, &snapshot, cx);
11619 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11620 assert_eq!(
11621 all_hunks,
11622 vec![(
11623 "".to_string(),
11624 DiffHunkStatus::Added,
11625 DisplayRow(4)..DisplayRow(8)
11626 )]
11627 );
11628 assert_eq!(
11629 expanded_hunks_background_highlights(editor, cx),
11630 vec![DisplayRow(4)..=DisplayRow(6)],
11631 "Edited hunk should have one more line added"
11632 );
11633 assert_eq!(
11634 all_hunks, all_expanded_hunks,
11635 "Expanded hunk should also grow with the addition"
11636 );
11637 });
11638
11639 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
11640 executor.run_until_parked();
11641 cx.assert_editor_state(
11642 &r#"
11643 use some::mod1;
11644 use some::mod2;
11645
11646 const A: u32 = 42;
11647 const B: u32 = 42;
11648 const C: u32 = 42;
11649 const D: u32 = 42;
11650 const E: u32 = 42;
11651 ˇ
11652
11653 fn main() {
11654 println!("hello");
11655
11656 println!("world");
11657 }
11658 "#
11659 .unindent(),
11660 );
11661 cx.update_editor(|editor, cx| {
11662 let snapshot = editor.snapshot(cx);
11663 let all_hunks = editor_hunks(editor, &snapshot, cx);
11664 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11665 assert_eq!(
11666 all_hunks,
11667 vec![(
11668 "".to_string(),
11669 DiffHunkStatus::Added,
11670 DisplayRow(4)..DisplayRow(9)
11671 )]
11672 );
11673 assert_eq!(
11674 expanded_hunks_background_highlights(editor, cx),
11675 vec![DisplayRow(4)..=DisplayRow(6)],
11676 "Edited hunk should have one more line added"
11677 );
11678 assert_eq!(all_hunks, all_expanded_hunks);
11679 });
11680
11681 cx.update_editor(|editor, cx| {
11682 editor.move_up(&MoveUp, cx);
11683 editor.delete_line(&DeleteLine, cx);
11684 });
11685 executor.run_until_parked();
11686 cx.assert_editor_state(
11687 &r#"
11688 use some::mod1;
11689 use some::mod2;
11690
11691 const A: u32 = 42;
11692 const B: u32 = 42;
11693 const C: u32 = 42;
11694 const D: u32 = 42;
11695 ˇ
11696
11697 fn main() {
11698 println!("hello");
11699
11700 println!("world");
11701 }
11702 "#
11703 .unindent(),
11704 );
11705 cx.update_editor(|editor, cx| {
11706 let snapshot = editor.snapshot(cx);
11707 let all_hunks = editor_hunks(editor, &snapshot, cx);
11708 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11709 assert_eq!(
11710 all_hunks,
11711 vec![(
11712 "".to_string(),
11713 DiffHunkStatus::Added,
11714 DisplayRow(4)..DisplayRow(8)
11715 )]
11716 );
11717 assert_eq!(
11718 expanded_hunks_background_highlights(editor, cx),
11719 vec![DisplayRow(4)..=DisplayRow(6)],
11720 "Deleting a line should shrint the hunk"
11721 );
11722 assert_eq!(
11723 all_hunks, all_expanded_hunks,
11724 "Expanded hunk should also shrink with the addition"
11725 );
11726 });
11727
11728 cx.update_editor(|editor, cx| {
11729 editor.move_up(&MoveUp, cx);
11730 editor.delete_line(&DeleteLine, cx);
11731 editor.move_up(&MoveUp, cx);
11732 editor.delete_line(&DeleteLine, cx);
11733 editor.move_up(&MoveUp, cx);
11734 editor.delete_line(&DeleteLine, cx);
11735 });
11736 executor.run_until_parked();
11737 cx.assert_editor_state(
11738 &r#"
11739 use some::mod1;
11740 use some::mod2;
11741
11742 const A: u32 = 42;
11743 ˇ
11744
11745 fn main() {
11746 println!("hello");
11747
11748 println!("world");
11749 }
11750 "#
11751 .unindent(),
11752 );
11753 cx.update_editor(|editor, cx| {
11754 let snapshot = editor.snapshot(cx);
11755 let all_hunks = editor_hunks(editor, &snapshot, cx);
11756 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11757 assert_eq!(
11758 all_hunks,
11759 vec![(
11760 "".to_string(),
11761 DiffHunkStatus::Added,
11762 DisplayRow(5)..DisplayRow(6)
11763 )]
11764 );
11765 assert_eq!(
11766 expanded_hunks_background_highlights(editor, cx),
11767 vec![DisplayRow(5)..=DisplayRow(5)]
11768 );
11769 assert_eq!(all_hunks, all_expanded_hunks);
11770 });
11771
11772 cx.update_editor(|editor, cx| {
11773 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
11774 editor.delete_line(&DeleteLine, cx);
11775 });
11776 executor.run_until_parked();
11777 cx.assert_editor_state(
11778 &r#"
11779 ˇ
11780
11781 fn main() {
11782 println!("hello");
11783
11784 println!("world");
11785 }
11786 "#
11787 .unindent(),
11788 );
11789 cx.update_editor(|editor, cx| {
11790 let snapshot = editor.snapshot(cx);
11791 let all_hunks = editor_hunks(editor, &snapshot, cx);
11792 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11793 assert_eq!(
11794 all_hunks,
11795 vec![
11796 (
11797 "use some::mod1;\nuse some::mod2;\n".to_string(),
11798 DiffHunkStatus::Removed,
11799 DisplayRow(0)..DisplayRow(0)
11800 ),
11801 (
11802 "const A: u32 = 42;\n".to_string(),
11803 DiffHunkStatus::Removed,
11804 DisplayRow(2)..DisplayRow(2)
11805 )
11806 ]
11807 );
11808 assert_eq!(
11809 expanded_hunks_background_highlights(editor, cx),
11810 Vec::new(),
11811 "Should close all stale expanded addition hunks"
11812 );
11813 assert_eq!(
11814 all_expanded_hunks,
11815 vec![(
11816 "const A: u32 = 42;\n".to_string(),
11817 DiffHunkStatus::Removed,
11818 DisplayRow(2)..DisplayRow(2)
11819 )],
11820 "Should open hunks that were adjacent to the stale addition one"
11821 );
11822 });
11823}
11824
11825#[gpui::test]
11826async fn test_edits_around_toggled_deletions(
11827 executor: BackgroundExecutor,
11828 cx: &mut gpui::TestAppContext,
11829) {
11830 init_test(cx, |_| {});
11831
11832 let mut cx = EditorTestContext::new(cx).await;
11833
11834 let diff_base = r#"
11835 use some::mod1;
11836 use some::mod2;
11837
11838 const A: u32 = 42;
11839 const B: u32 = 42;
11840 const C: u32 = 42;
11841
11842
11843 fn main() {
11844 println!("hello");
11845
11846 println!("world");
11847 }
11848 "#
11849 .unindent();
11850 executor.run_until_parked();
11851 cx.set_state(
11852 &r#"
11853 use some::mod1;
11854 use some::mod2;
11855
11856 ˇconst B: u32 = 42;
11857 const C: u32 = 42;
11858
11859
11860 fn main() {
11861 println!("hello");
11862
11863 println!("world");
11864 }
11865 "#
11866 .unindent(),
11867 );
11868
11869 cx.set_diff_base(Some(&diff_base));
11870 executor.run_until_parked();
11871 cx.update_editor(|editor, cx| {
11872 let snapshot = editor.snapshot(cx);
11873 let all_hunks = editor_hunks(editor, &snapshot, cx);
11874 assert_eq!(
11875 all_hunks,
11876 vec![(
11877 "const A: u32 = 42;\n".to_string(),
11878 DiffHunkStatus::Removed,
11879 DisplayRow(3)..DisplayRow(3)
11880 )]
11881 );
11882 });
11883 cx.update_editor(|editor, cx| {
11884 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11885 });
11886 executor.run_until_parked();
11887 cx.assert_editor_state(
11888 &r#"
11889 use some::mod1;
11890 use some::mod2;
11891
11892 ˇconst B: u32 = 42;
11893 const C: u32 = 42;
11894
11895
11896 fn main() {
11897 println!("hello");
11898
11899 println!("world");
11900 }
11901 "#
11902 .unindent(),
11903 );
11904 cx.update_editor(|editor, cx| {
11905 let snapshot = editor.snapshot(cx);
11906 let all_hunks = editor_hunks(editor, &snapshot, cx);
11907 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11908 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11909 assert_eq!(
11910 all_hunks,
11911 vec![(
11912 "const A: u32 = 42;\n".to_string(),
11913 DiffHunkStatus::Removed,
11914 DisplayRow(4)..DisplayRow(4)
11915 )]
11916 );
11917 assert_eq!(all_hunks, all_expanded_hunks);
11918 });
11919
11920 cx.update_editor(|editor, cx| {
11921 editor.delete_line(&DeleteLine, cx);
11922 });
11923 executor.run_until_parked();
11924 cx.assert_editor_state(
11925 &r#"
11926 use some::mod1;
11927 use some::mod2;
11928
11929 ˇconst C: u32 = 42;
11930
11931
11932 fn main() {
11933 println!("hello");
11934
11935 println!("world");
11936 }
11937 "#
11938 .unindent(),
11939 );
11940 cx.update_editor(|editor, cx| {
11941 let snapshot = editor.snapshot(cx);
11942 let all_hunks = editor_hunks(editor, &snapshot, cx);
11943 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11944 assert_eq!(
11945 expanded_hunks_background_highlights(editor, cx),
11946 Vec::new(),
11947 "Deleted hunks do not highlight current editor's background"
11948 );
11949 assert_eq!(
11950 all_hunks,
11951 vec![(
11952 "const A: u32 = 42;\nconst B: u32 = 42;\n".to_string(),
11953 DiffHunkStatus::Removed,
11954 DisplayRow(5)..DisplayRow(5)
11955 )]
11956 );
11957 assert_eq!(all_hunks, all_expanded_hunks);
11958 });
11959
11960 cx.update_editor(|editor, cx| {
11961 editor.delete_line(&DeleteLine, cx);
11962 });
11963 executor.run_until_parked();
11964 cx.assert_editor_state(
11965 &r#"
11966 use some::mod1;
11967 use some::mod2;
11968
11969 ˇ
11970
11971 fn main() {
11972 println!("hello");
11973
11974 println!("world");
11975 }
11976 "#
11977 .unindent(),
11978 );
11979 cx.update_editor(|editor, cx| {
11980 let snapshot = editor.snapshot(cx);
11981 let all_hunks = editor_hunks(editor, &snapshot, cx);
11982 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11983 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11984 assert_eq!(
11985 all_hunks,
11986 vec![(
11987 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
11988 DiffHunkStatus::Removed,
11989 DisplayRow(6)..DisplayRow(6)
11990 )]
11991 );
11992 assert_eq!(all_hunks, all_expanded_hunks);
11993 });
11994
11995 cx.update_editor(|editor, cx| {
11996 editor.handle_input("replacement", cx);
11997 });
11998 executor.run_until_parked();
11999 cx.assert_editor_state(
12000 &r#"
12001 use some::mod1;
12002 use some::mod2;
12003
12004 replacementˇ
12005
12006 fn main() {
12007 println!("hello");
12008
12009 println!("world");
12010 }
12011 "#
12012 .unindent(),
12013 );
12014 cx.update_editor(|editor, cx| {
12015 let snapshot = editor.snapshot(cx);
12016 let all_hunks = editor_hunks(editor, &snapshot, cx);
12017 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12018 assert_eq!(
12019 all_hunks,
12020 vec![(
12021 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n\n".to_string(),
12022 DiffHunkStatus::Modified,
12023 DisplayRow(7)..DisplayRow(8)
12024 )]
12025 );
12026 assert_eq!(
12027 expanded_hunks_background_highlights(editor, cx),
12028 vec![DisplayRow(7)..=DisplayRow(7)],
12029 "Modified expanded hunks should display additions and highlight their background"
12030 );
12031 assert_eq!(all_hunks, all_expanded_hunks);
12032 });
12033}
12034
12035#[gpui::test]
12036async fn test_edits_around_toggled_modifications(
12037 executor: BackgroundExecutor,
12038 cx: &mut gpui::TestAppContext,
12039) {
12040 init_test(cx, |_| {});
12041
12042 let mut cx = EditorTestContext::new(cx).await;
12043
12044 let diff_base = r#"
12045 use some::mod1;
12046 use some::mod2;
12047
12048 const A: u32 = 42;
12049 const B: u32 = 42;
12050 const C: u32 = 42;
12051 const D: u32 = 42;
12052
12053
12054 fn main() {
12055 println!("hello");
12056
12057 println!("world");
12058 }"#
12059 .unindent();
12060 executor.run_until_parked();
12061 cx.set_state(
12062 &r#"
12063 use some::mod1;
12064 use some::mod2;
12065
12066 const A: u32 = 42;
12067 const B: u32 = 42;
12068 const C: u32 = 43ˇ
12069 const D: u32 = 42;
12070
12071
12072 fn main() {
12073 println!("hello");
12074
12075 println!("world");
12076 }"#
12077 .unindent(),
12078 );
12079
12080 cx.set_diff_base(Some(&diff_base));
12081 executor.run_until_parked();
12082 cx.update_editor(|editor, cx| {
12083 let snapshot = editor.snapshot(cx);
12084 let all_hunks = editor_hunks(editor, &snapshot, cx);
12085 assert_eq!(
12086 all_hunks,
12087 vec![(
12088 "const C: u32 = 42;\n".to_string(),
12089 DiffHunkStatus::Modified,
12090 DisplayRow(5)..DisplayRow(6)
12091 )]
12092 );
12093 });
12094 cx.update_editor(|editor, cx| {
12095 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12096 });
12097 executor.run_until_parked();
12098 cx.assert_editor_state(
12099 &r#"
12100 use some::mod1;
12101 use some::mod2;
12102
12103 const A: u32 = 42;
12104 const B: u32 = 42;
12105 const C: u32 = 43ˇ
12106 const D: u32 = 42;
12107
12108
12109 fn main() {
12110 println!("hello");
12111
12112 println!("world");
12113 }"#
12114 .unindent(),
12115 );
12116 cx.update_editor(|editor, cx| {
12117 let snapshot = editor.snapshot(cx);
12118 let all_hunks = editor_hunks(editor, &snapshot, cx);
12119 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12120 assert_eq!(
12121 expanded_hunks_background_highlights(editor, cx),
12122 vec![DisplayRow(6)..=DisplayRow(6)],
12123 );
12124 assert_eq!(
12125 all_hunks,
12126 vec![(
12127 "const C: u32 = 42;\n".to_string(),
12128 DiffHunkStatus::Modified,
12129 DisplayRow(6)..DisplayRow(7)
12130 )]
12131 );
12132 assert_eq!(all_hunks, all_expanded_hunks);
12133 });
12134
12135 cx.update_editor(|editor, cx| {
12136 editor.handle_input("\nnew_line\n", cx);
12137 });
12138 executor.run_until_parked();
12139 cx.assert_editor_state(
12140 &r#"
12141 use some::mod1;
12142 use some::mod2;
12143
12144 const A: u32 = 42;
12145 const B: u32 = 42;
12146 const C: u32 = 43
12147 new_line
12148 ˇ
12149 const D: u32 = 42;
12150
12151
12152 fn main() {
12153 println!("hello");
12154
12155 println!("world");
12156 }"#
12157 .unindent(),
12158 );
12159 cx.update_editor(|editor, cx| {
12160 let snapshot = editor.snapshot(cx);
12161 let all_hunks = editor_hunks(editor, &snapshot, cx);
12162 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12163 assert_eq!(
12164 expanded_hunks_background_highlights(editor, cx),
12165 vec![DisplayRow(6)..=DisplayRow(6)],
12166 "Modified hunk should grow highlighted lines on more text additions"
12167 );
12168 assert_eq!(
12169 all_hunks,
12170 vec![(
12171 "const C: u32 = 42;\n".to_string(),
12172 DiffHunkStatus::Modified,
12173 DisplayRow(6)..DisplayRow(9)
12174 )]
12175 );
12176 assert_eq!(all_hunks, all_expanded_hunks);
12177 });
12178
12179 cx.update_editor(|editor, cx| {
12180 editor.move_up(&MoveUp, cx);
12181 editor.move_up(&MoveUp, cx);
12182 editor.move_up(&MoveUp, cx);
12183 editor.delete_line(&DeleteLine, cx);
12184 });
12185 executor.run_until_parked();
12186 cx.assert_editor_state(
12187 &r#"
12188 use some::mod1;
12189 use some::mod2;
12190
12191 const A: u32 = 42;
12192 ˇconst C: u32 = 43
12193 new_line
12194
12195 const D: u32 = 42;
12196
12197
12198 fn main() {
12199 println!("hello");
12200
12201 println!("world");
12202 }"#
12203 .unindent(),
12204 );
12205 cx.update_editor(|editor, cx| {
12206 let snapshot = editor.snapshot(cx);
12207 let all_hunks = editor_hunks(editor, &snapshot, cx);
12208 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12209 assert_eq!(
12210 expanded_hunks_background_highlights(editor, cx),
12211 vec![DisplayRow(6)..=DisplayRow(8)],
12212 );
12213 assert_eq!(
12214 all_hunks,
12215 vec![(
12216 "const B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12217 DiffHunkStatus::Modified,
12218 DisplayRow(6)..DisplayRow(9)
12219 )],
12220 "Modified hunk should grow deleted lines on text deletions above"
12221 );
12222 assert_eq!(all_hunks, all_expanded_hunks);
12223 });
12224
12225 cx.update_editor(|editor, cx| {
12226 editor.move_up(&MoveUp, cx);
12227 editor.handle_input("v", cx);
12228 });
12229 executor.run_until_parked();
12230 cx.assert_editor_state(
12231 &r#"
12232 use some::mod1;
12233 use some::mod2;
12234
12235 vˇconst A: u32 = 42;
12236 const C: u32 = 43
12237 new_line
12238
12239 const D: u32 = 42;
12240
12241
12242 fn main() {
12243 println!("hello");
12244
12245 println!("world");
12246 }"#
12247 .unindent(),
12248 );
12249 cx.update_editor(|editor, cx| {
12250 let snapshot = editor.snapshot(cx);
12251 let all_hunks = editor_hunks(editor, &snapshot, cx);
12252 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12253 assert_eq!(
12254 expanded_hunks_background_highlights(editor, cx),
12255 vec![DisplayRow(6)..=DisplayRow(9)],
12256 "Modified hunk should grow deleted lines on text modifications above"
12257 );
12258 assert_eq!(
12259 all_hunks,
12260 vec![(
12261 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12262 DiffHunkStatus::Modified,
12263 DisplayRow(6)..DisplayRow(10)
12264 )]
12265 );
12266 assert_eq!(all_hunks, all_expanded_hunks);
12267 });
12268
12269 cx.update_editor(|editor, cx| {
12270 editor.move_down(&MoveDown, cx);
12271 editor.move_down(&MoveDown, cx);
12272 editor.delete_line(&DeleteLine, cx)
12273 });
12274 executor.run_until_parked();
12275 cx.assert_editor_state(
12276 &r#"
12277 use some::mod1;
12278 use some::mod2;
12279
12280 vconst A: u32 = 42;
12281 const C: u32 = 43
12282 ˇ
12283 const D: u32 = 42;
12284
12285
12286 fn main() {
12287 println!("hello");
12288
12289 println!("world");
12290 }"#
12291 .unindent(),
12292 );
12293 cx.update_editor(|editor, cx| {
12294 let snapshot = editor.snapshot(cx);
12295 let all_hunks = editor_hunks(editor, &snapshot, cx);
12296 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12297 assert_eq!(
12298 expanded_hunks_background_highlights(editor, cx),
12299 vec![DisplayRow(6)..=DisplayRow(8)],
12300 "Modified hunk should grow shrink lines on modification lines removal"
12301 );
12302 assert_eq!(
12303 all_hunks,
12304 vec![(
12305 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12306 DiffHunkStatus::Modified,
12307 DisplayRow(6)..DisplayRow(9)
12308 )]
12309 );
12310 assert_eq!(all_hunks, all_expanded_hunks);
12311 });
12312
12313 cx.update_editor(|editor, cx| {
12314 editor.move_up(&MoveUp, cx);
12315 editor.move_up(&MoveUp, cx);
12316 editor.select_down_by_lines(&SelectDownByLines { lines: 4 }, cx);
12317 editor.delete_line(&DeleteLine, cx)
12318 });
12319 executor.run_until_parked();
12320 cx.assert_editor_state(
12321 &r#"
12322 use some::mod1;
12323 use some::mod2;
12324
12325 ˇ
12326
12327 fn main() {
12328 println!("hello");
12329
12330 println!("world");
12331 }"#
12332 .unindent(),
12333 );
12334 cx.update_editor(|editor, cx| {
12335 let snapshot = editor.snapshot(cx);
12336 let all_hunks = editor_hunks(editor, &snapshot, cx);
12337 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12338 assert_eq!(
12339 expanded_hunks_background_highlights(editor, cx),
12340 Vec::new(),
12341 "Modified hunk should turn into a removed one on all modified lines removal"
12342 );
12343 assert_eq!(
12344 all_hunks,
12345 vec![(
12346 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\nconst D: u32 = 42;\n"
12347 .to_string(),
12348 DiffHunkStatus::Removed,
12349 DisplayRow(7)..DisplayRow(7)
12350 )]
12351 );
12352 assert_eq!(all_hunks, all_expanded_hunks);
12353 });
12354}
12355
12356#[gpui::test]
12357async fn test_multiple_expanded_hunks_merge(
12358 executor: BackgroundExecutor,
12359 cx: &mut gpui::TestAppContext,
12360) {
12361 init_test(cx, |_| {});
12362
12363 let mut cx = EditorTestContext::new(cx).await;
12364
12365 let diff_base = r#"
12366 use some::mod1;
12367 use some::mod2;
12368
12369 const A: u32 = 42;
12370 const B: u32 = 42;
12371 const C: u32 = 42;
12372 const D: u32 = 42;
12373
12374
12375 fn main() {
12376 println!("hello");
12377
12378 println!("world");
12379 }"#
12380 .unindent();
12381 executor.run_until_parked();
12382 cx.set_state(
12383 &r#"
12384 use some::mod1;
12385 use some::mod2;
12386
12387 const A: u32 = 42;
12388 const B: u32 = 42;
12389 const C: u32 = 43ˇ
12390 const D: u32 = 42;
12391
12392
12393 fn main() {
12394 println!("hello");
12395
12396 println!("world");
12397 }"#
12398 .unindent(),
12399 );
12400
12401 cx.set_diff_base(Some(&diff_base));
12402 executor.run_until_parked();
12403 cx.update_editor(|editor, cx| {
12404 let snapshot = editor.snapshot(cx);
12405 let all_hunks = editor_hunks(editor, &snapshot, cx);
12406 assert_eq!(
12407 all_hunks,
12408 vec![(
12409 "const C: u32 = 42;\n".to_string(),
12410 DiffHunkStatus::Modified,
12411 DisplayRow(5)..DisplayRow(6)
12412 )]
12413 );
12414 });
12415 cx.update_editor(|editor, cx| {
12416 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12417 });
12418 executor.run_until_parked();
12419 cx.assert_editor_state(
12420 &r#"
12421 use some::mod1;
12422 use some::mod2;
12423
12424 const A: u32 = 42;
12425 const B: u32 = 42;
12426 const C: u32 = 43ˇ
12427 const D: u32 = 42;
12428
12429
12430 fn main() {
12431 println!("hello");
12432
12433 println!("world");
12434 }"#
12435 .unindent(),
12436 );
12437 cx.update_editor(|editor, cx| {
12438 let snapshot = editor.snapshot(cx);
12439 let all_hunks = editor_hunks(editor, &snapshot, cx);
12440 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12441 assert_eq!(
12442 expanded_hunks_background_highlights(editor, cx),
12443 vec![DisplayRow(6)..=DisplayRow(6)],
12444 );
12445 assert_eq!(
12446 all_hunks,
12447 vec![(
12448 "const C: u32 = 42;\n".to_string(),
12449 DiffHunkStatus::Modified,
12450 DisplayRow(6)..DisplayRow(7)
12451 )]
12452 );
12453 assert_eq!(all_hunks, all_expanded_hunks);
12454 });
12455
12456 cx.update_editor(|editor, cx| {
12457 editor.handle_input("\nnew_line\n", cx);
12458 });
12459 executor.run_until_parked();
12460 cx.assert_editor_state(
12461 &r#"
12462 use some::mod1;
12463 use some::mod2;
12464
12465 const A: u32 = 42;
12466 const B: u32 = 42;
12467 const C: u32 = 43
12468 new_line
12469 ˇ
12470 const D: u32 = 42;
12471
12472
12473 fn main() {
12474 println!("hello");
12475
12476 println!("world");
12477 }"#
12478 .unindent(),
12479 );
12480}
12481
12482async fn setup_indent_guides_editor(
12483 text: &str,
12484 cx: &mut gpui::TestAppContext,
12485) -> (BufferId, EditorTestContext) {
12486 init_test(cx, |_| {});
12487
12488 let mut cx = EditorTestContext::new(cx).await;
12489
12490 let buffer_id = cx.update_editor(|editor, cx| {
12491 editor.set_text(text, cx);
12492 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12493 let buffer_id = buffer_ids[0];
12494 buffer_id
12495 });
12496
12497 (buffer_id, cx)
12498}
12499
12500fn assert_indent_guides(
12501 range: Range<u32>,
12502 expected: Vec<IndentGuide>,
12503 active_indices: Option<Vec<usize>>,
12504 cx: &mut EditorTestContext,
12505) {
12506 let indent_guides = cx.update_editor(|editor, cx| {
12507 let snapshot = editor.snapshot(cx).display_snapshot;
12508 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12509 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12510 true,
12511 &snapshot,
12512 cx,
12513 );
12514
12515 indent_guides.sort_by(|a, b| {
12516 a.depth.cmp(&b.depth).then(
12517 a.start_row
12518 .cmp(&b.start_row)
12519 .then(a.end_row.cmp(&b.end_row)),
12520 )
12521 });
12522 indent_guides
12523 });
12524
12525 if let Some(expected) = active_indices {
12526 let active_indices = cx.update_editor(|editor, cx| {
12527 let snapshot = editor.snapshot(cx).display_snapshot;
12528 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
12529 });
12530
12531 assert_eq!(
12532 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
12533 expected,
12534 "Active indent guide indices do not match"
12535 );
12536 }
12537
12538 let expected: Vec<_> = expected
12539 .into_iter()
12540 .map(|guide| MultiBufferIndentGuide {
12541 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
12542 buffer: guide,
12543 })
12544 .collect();
12545
12546 assert_eq!(indent_guides, expected, "Indent guides do not match");
12547}
12548
12549fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
12550 IndentGuide {
12551 buffer_id,
12552 start_row,
12553 end_row,
12554 depth,
12555 tab_size: 4,
12556 settings: IndentGuideSettings {
12557 enabled: true,
12558 line_width: 1,
12559 active_line_width: 1,
12560 ..Default::default()
12561 },
12562 }
12563}
12564
12565#[gpui::test]
12566async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12567 let (buffer_id, mut cx) = setup_indent_guides_editor(
12568 &"
12569 fn main() {
12570 let a = 1;
12571 }"
12572 .unindent(),
12573 cx,
12574 )
12575 .await;
12576
12577 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12578}
12579
12580#[gpui::test]
12581async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
12582 let (buffer_id, mut cx) = setup_indent_guides_editor(
12583 &"
12584 fn main() {
12585 let a = 1;
12586 let b = 2;
12587 }"
12588 .unindent(),
12589 cx,
12590 )
12591 .await;
12592
12593 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
12594}
12595
12596#[gpui::test]
12597async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
12598 let (buffer_id, mut cx) = setup_indent_guides_editor(
12599 &"
12600 fn main() {
12601 let a = 1;
12602 if a == 3 {
12603 let b = 2;
12604 } else {
12605 let c = 3;
12606 }
12607 }"
12608 .unindent(),
12609 cx,
12610 )
12611 .await;
12612
12613 assert_indent_guides(
12614 0..8,
12615 vec![
12616 indent_guide(buffer_id, 1, 6, 0),
12617 indent_guide(buffer_id, 3, 3, 1),
12618 indent_guide(buffer_id, 5, 5, 1),
12619 ],
12620 None,
12621 &mut cx,
12622 );
12623}
12624
12625#[gpui::test]
12626async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
12627 let (buffer_id, mut cx) = setup_indent_guides_editor(
12628 &"
12629 fn main() {
12630 let a = 1;
12631 let b = 2;
12632 let c = 3;
12633 }"
12634 .unindent(),
12635 cx,
12636 )
12637 .await;
12638
12639 assert_indent_guides(
12640 0..5,
12641 vec![
12642 indent_guide(buffer_id, 1, 3, 0),
12643 indent_guide(buffer_id, 2, 2, 1),
12644 ],
12645 None,
12646 &mut cx,
12647 );
12648}
12649
12650#[gpui::test]
12651async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
12652 let (buffer_id, mut cx) = setup_indent_guides_editor(
12653 &"
12654 fn main() {
12655 let a = 1;
12656
12657 let c = 3;
12658 }"
12659 .unindent(),
12660 cx,
12661 )
12662 .await;
12663
12664 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
12665}
12666
12667#[gpui::test]
12668async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
12669 let (buffer_id, mut cx) = setup_indent_guides_editor(
12670 &"
12671 fn main() {
12672 let a = 1;
12673
12674 let c = 3;
12675
12676 if a == 3 {
12677 let b = 2;
12678 } else {
12679 let c = 3;
12680 }
12681 }"
12682 .unindent(),
12683 cx,
12684 )
12685 .await;
12686
12687 assert_indent_guides(
12688 0..11,
12689 vec![
12690 indent_guide(buffer_id, 1, 9, 0),
12691 indent_guide(buffer_id, 6, 6, 1),
12692 indent_guide(buffer_id, 8, 8, 1),
12693 ],
12694 None,
12695 &mut cx,
12696 );
12697}
12698
12699#[gpui::test]
12700async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
12701 let (buffer_id, mut cx) = setup_indent_guides_editor(
12702 &"
12703 fn main() {
12704 let a = 1;
12705
12706 let c = 3;
12707
12708 if a == 3 {
12709 let b = 2;
12710 } else {
12711 let c = 3;
12712 }
12713 }"
12714 .unindent(),
12715 cx,
12716 )
12717 .await;
12718
12719 assert_indent_guides(
12720 1..11,
12721 vec![
12722 indent_guide(buffer_id, 1, 9, 0),
12723 indent_guide(buffer_id, 6, 6, 1),
12724 indent_guide(buffer_id, 8, 8, 1),
12725 ],
12726 None,
12727 &mut cx,
12728 );
12729}
12730
12731#[gpui::test]
12732async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
12733 let (buffer_id, mut cx) = setup_indent_guides_editor(
12734 &"
12735 fn main() {
12736 let a = 1;
12737
12738 let c = 3;
12739
12740 if a == 3 {
12741 let b = 2;
12742 } else {
12743 let c = 3;
12744 }
12745 }"
12746 .unindent(),
12747 cx,
12748 )
12749 .await;
12750
12751 assert_indent_guides(
12752 1..10,
12753 vec![
12754 indent_guide(buffer_id, 1, 9, 0),
12755 indent_guide(buffer_id, 6, 6, 1),
12756 indent_guide(buffer_id, 8, 8, 1),
12757 ],
12758 None,
12759 &mut cx,
12760 );
12761}
12762
12763#[gpui::test]
12764async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
12765 let (buffer_id, mut cx) = setup_indent_guides_editor(
12766 &"
12767 block1
12768 block2
12769 block3
12770 block4
12771 block2
12772 block1
12773 block1"
12774 .unindent(),
12775 cx,
12776 )
12777 .await;
12778
12779 assert_indent_guides(
12780 1..10,
12781 vec![
12782 indent_guide(buffer_id, 1, 4, 0),
12783 indent_guide(buffer_id, 2, 3, 1),
12784 indent_guide(buffer_id, 3, 3, 2),
12785 ],
12786 None,
12787 &mut cx,
12788 );
12789}
12790
12791#[gpui::test]
12792async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
12793 let (buffer_id, mut cx) = setup_indent_guides_editor(
12794 &"
12795 block1
12796 block2
12797 block3
12798
12799 block1
12800 block1"
12801 .unindent(),
12802 cx,
12803 )
12804 .await;
12805
12806 assert_indent_guides(
12807 0..6,
12808 vec![
12809 indent_guide(buffer_id, 1, 2, 0),
12810 indent_guide(buffer_id, 2, 2, 1),
12811 ],
12812 None,
12813 &mut cx,
12814 );
12815}
12816
12817#[gpui::test]
12818async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
12819 let (buffer_id, mut cx) = setup_indent_guides_editor(
12820 &"
12821 block1
12822
12823
12824
12825 block2
12826 "
12827 .unindent(),
12828 cx,
12829 )
12830 .await;
12831
12832 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12833}
12834
12835#[gpui::test]
12836async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
12837 let (buffer_id, mut cx) = setup_indent_guides_editor(
12838 &"
12839 def a:
12840 \tb = 3
12841 \tif True:
12842 \t\tc = 4
12843 \t\td = 5
12844 \tprint(b)
12845 "
12846 .unindent(),
12847 cx,
12848 )
12849 .await;
12850
12851 assert_indent_guides(
12852 0..6,
12853 vec![
12854 indent_guide(buffer_id, 1, 6, 0),
12855 indent_guide(buffer_id, 3, 4, 1),
12856 ],
12857 None,
12858 &mut cx,
12859 );
12860}
12861
12862#[gpui::test]
12863async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12864 let (buffer_id, mut cx) = setup_indent_guides_editor(
12865 &"
12866 fn main() {
12867 let a = 1;
12868 }"
12869 .unindent(),
12870 cx,
12871 )
12872 .await;
12873
12874 cx.update_editor(|editor, cx| {
12875 editor.change_selections(None, cx, |s| {
12876 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12877 });
12878 });
12879
12880 assert_indent_guides(
12881 0..3,
12882 vec![indent_guide(buffer_id, 1, 1, 0)],
12883 Some(vec![0]),
12884 &mut cx,
12885 );
12886}
12887
12888#[gpui::test]
12889async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
12890 let (buffer_id, mut cx) = setup_indent_guides_editor(
12891 &"
12892 fn main() {
12893 if 1 == 2 {
12894 let a = 1;
12895 }
12896 }"
12897 .unindent(),
12898 cx,
12899 )
12900 .await;
12901
12902 cx.update_editor(|editor, cx| {
12903 editor.change_selections(None, cx, |s| {
12904 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12905 });
12906 });
12907
12908 assert_indent_guides(
12909 0..4,
12910 vec![
12911 indent_guide(buffer_id, 1, 3, 0),
12912 indent_guide(buffer_id, 2, 2, 1),
12913 ],
12914 Some(vec![1]),
12915 &mut cx,
12916 );
12917
12918 cx.update_editor(|editor, cx| {
12919 editor.change_selections(None, cx, |s| {
12920 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12921 });
12922 });
12923
12924 assert_indent_guides(
12925 0..4,
12926 vec![
12927 indent_guide(buffer_id, 1, 3, 0),
12928 indent_guide(buffer_id, 2, 2, 1),
12929 ],
12930 Some(vec![1]),
12931 &mut cx,
12932 );
12933
12934 cx.update_editor(|editor, cx| {
12935 editor.change_selections(None, cx, |s| {
12936 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
12937 });
12938 });
12939
12940 assert_indent_guides(
12941 0..4,
12942 vec![
12943 indent_guide(buffer_id, 1, 3, 0),
12944 indent_guide(buffer_id, 2, 2, 1),
12945 ],
12946 Some(vec![0]),
12947 &mut cx,
12948 );
12949}
12950
12951#[gpui::test]
12952async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
12953 let (buffer_id, mut cx) = setup_indent_guides_editor(
12954 &"
12955 fn main() {
12956 let a = 1;
12957
12958 let b = 2;
12959 }"
12960 .unindent(),
12961 cx,
12962 )
12963 .await;
12964
12965 cx.update_editor(|editor, cx| {
12966 editor.change_selections(None, cx, |s| {
12967 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12968 });
12969 });
12970
12971 assert_indent_guides(
12972 0..5,
12973 vec![indent_guide(buffer_id, 1, 3, 0)],
12974 Some(vec![0]),
12975 &mut cx,
12976 );
12977}
12978
12979#[gpui::test]
12980async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
12981 let (buffer_id, mut cx) = setup_indent_guides_editor(
12982 &"
12983 def m:
12984 a = 1
12985 pass"
12986 .unindent(),
12987 cx,
12988 )
12989 .await;
12990
12991 cx.update_editor(|editor, cx| {
12992 editor.change_selections(None, cx, |s| {
12993 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12994 });
12995 });
12996
12997 assert_indent_guides(
12998 0..3,
12999 vec![indent_guide(buffer_id, 1, 2, 0)],
13000 Some(vec![0]),
13001 &mut cx,
13002 );
13003}
13004
13005#[gpui::test]
13006fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13007 init_test(cx, |_| {});
13008
13009 let editor = cx.add_window(|cx| {
13010 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13011 build_editor(buffer, cx)
13012 });
13013
13014 let render_args = Arc::new(Mutex::new(None));
13015 let snapshot = editor
13016 .update(cx, |editor, cx| {
13017 let snapshot = editor.buffer().read(cx).snapshot(cx);
13018 let range =
13019 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13020
13021 struct RenderArgs {
13022 row: MultiBufferRow,
13023 folded: bool,
13024 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13025 }
13026
13027 let crease = Crease::new(
13028 range,
13029 FoldPlaceholder::test(),
13030 {
13031 let toggle_callback = render_args.clone();
13032 move |row, folded, callback, _cx| {
13033 *toggle_callback.lock() = Some(RenderArgs {
13034 row,
13035 folded,
13036 callback,
13037 });
13038 div()
13039 }
13040 },
13041 |_row, _folded, _cx| div(),
13042 );
13043
13044 editor.insert_creases(Some(crease), cx);
13045 let snapshot = editor.snapshot(cx);
13046 let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13047 snapshot
13048 })
13049 .unwrap();
13050
13051 let render_args = render_args.lock().take().unwrap();
13052 assert_eq!(render_args.row, MultiBufferRow(1));
13053 assert_eq!(render_args.folded, false);
13054 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13055
13056 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13057 .unwrap();
13058 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13059 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13060
13061 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13062 .unwrap();
13063 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13064 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13065}
13066
13067fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13068 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13069 point..point
13070}
13071
13072fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13073 let (text, ranges) = marked_text_ranges(marked_text, true);
13074 assert_eq!(view.text(cx), text);
13075 assert_eq!(
13076 view.selections.ranges(cx),
13077 ranges,
13078 "Assert selections are {}",
13079 marked_text
13080 );
13081}
13082
13083pub fn handle_signature_help_request(
13084 cx: &mut EditorLspTestContext,
13085 mocked_response: lsp::SignatureHelp,
13086) -> impl Future<Output = ()> {
13087 let mut request =
13088 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13089 let mocked_response = mocked_response.clone();
13090 async move { Ok(Some(mocked_response)) }
13091 });
13092
13093 async move {
13094 request.next().await;
13095 }
13096}
13097
13098/// Handle completion request passing a marked string specifying where the completion
13099/// should be triggered from using '|' character, what range should be replaced, and what completions
13100/// should be returned using '<' and '>' to delimit the range
13101pub fn handle_completion_request(
13102 cx: &mut EditorLspTestContext,
13103 marked_string: &str,
13104 completions: Vec<&'static str>,
13105 counter: Arc<AtomicUsize>,
13106) -> impl Future<Output = ()> {
13107 let complete_from_marker: TextRangeMarker = '|'.into();
13108 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13109 let (_, mut marked_ranges) = marked_text_ranges_by(
13110 marked_string,
13111 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13112 );
13113
13114 let complete_from_position =
13115 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13116 let replace_range =
13117 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13118
13119 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13120 let completions = completions.clone();
13121 counter.fetch_add(1, atomic::Ordering::Release);
13122 async move {
13123 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13124 assert_eq!(
13125 params.text_document_position.position,
13126 complete_from_position
13127 );
13128 Ok(Some(lsp::CompletionResponse::Array(
13129 completions
13130 .iter()
13131 .map(|completion_text| lsp::CompletionItem {
13132 label: completion_text.to_string(),
13133 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13134 range: replace_range,
13135 new_text: completion_text.to_string(),
13136 })),
13137 ..Default::default()
13138 })
13139 .collect(),
13140 )))
13141 }
13142 });
13143
13144 async move {
13145 request.next().await;
13146 }
13147}
13148
13149fn handle_resolve_completion_request(
13150 cx: &mut EditorLspTestContext,
13151 edits: Option<Vec<(&'static str, &'static str)>>,
13152) -> impl Future<Output = ()> {
13153 let edits = edits.map(|edits| {
13154 edits
13155 .iter()
13156 .map(|(marked_string, new_text)| {
13157 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13158 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13159 lsp::TextEdit::new(replace_range, new_text.to_string())
13160 })
13161 .collect::<Vec<_>>()
13162 });
13163
13164 let mut request =
13165 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13166 let edits = edits.clone();
13167 async move {
13168 Ok(lsp::CompletionItem {
13169 additional_text_edits: edits,
13170 ..Default::default()
13171 })
13172 }
13173 });
13174
13175 async move {
13176 request.next().await;
13177 }
13178}
13179
13180pub(crate) fn update_test_language_settings(
13181 cx: &mut TestAppContext,
13182 f: impl Fn(&mut AllLanguageSettingsContent),
13183) {
13184 _ = cx.update(|cx| {
13185 SettingsStore::update_global(cx, |store, cx| {
13186 store.update_user_settings::<AllLanguageSettings>(cx, f);
13187 });
13188 });
13189}
13190
13191pub(crate) fn update_test_project_settings(
13192 cx: &mut TestAppContext,
13193 f: impl Fn(&mut ProjectSettings),
13194) {
13195 _ = cx.update(|cx| {
13196 SettingsStore::update_global(cx, |store, cx| {
13197 store.update_user_settings::<ProjectSettings>(cx, f);
13198 });
13199 });
13200}
13201
13202pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
13203 _ = cx.update(|cx| {
13204 assets::Assets.load_test_fonts(cx);
13205 let store = SettingsStore::test(cx);
13206 cx.set_global(store);
13207 theme::init(theme::LoadThemes::JustBase, cx);
13208 release_channel::init(SemanticVersion::default(), cx);
13209 client::init_settings(cx);
13210 language::init(cx);
13211 Project::init_settings(cx);
13212 workspace::init_settings(cx);
13213 crate::init(cx);
13214 });
13215
13216 update_test_language_settings(cx, f);
13217}
13218
13219pub(crate) fn rust_lang() -> Arc<Language> {
13220 Arc::new(Language::new(
13221 LanguageConfig {
13222 name: "Rust".into(),
13223 matcher: LanguageMatcher {
13224 path_suffixes: vec!["rs".to_string()],
13225 ..Default::default()
13226 },
13227 ..Default::default()
13228 },
13229 Some(tree_sitter_rust::language()),
13230 ))
13231}
13232
13233#[track_caller]
13234fn assert_hunk_revert(
13235 not_reverted_text_with_selections: &str,
13236 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
13237 expected_reverted_text_with_selections: &str,
13238 base_text: &str,
13239 cx: &mut EditorLspTestContext,
13240) {
13241 cx.set_state(not_reverted_text_with_selections);
13242 cx.update_editor(|editor, cx| {
13243 editor
13244 .buffer()
13245 .read(cx)
13246 .as_singleton()
13247 .unwrap()
13248 .update(cx, |buffer, cx| {
13249 buffer.set_diff_base(Some(base_text.into()), cx);
13250 });
13251 });
13252 cx.executor().run_until_parked();
13253
13254 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
13255 let snapshot = editor.buffer().read(cx).snapshot(cx);
13256 let reverted_hunk_statuses = snapshot
13257 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
13258 .map(|hunk| hunk_status(&hunk))
13259 .collect::<Vec<_>>();
13260
13261 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
13262 reverted_hunk_statuses
13263 });
13264 cx.executor().run_until_parked();
13265 cx.assert_editor_state(expected_reverted_text_with_selections);
13266 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
13267}