1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_hunks,
6 editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext,
7 expanded_hunks, expanded_hunks_background_highlights, select_ranges,
8 },
9 JoinLines,
10};
11use futures::StreamExt;
12use gpui::{
13 div, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds,
14 WindowOptions,
15};
16use indoc::indoc;
17use language::{
18 language_settings::{
19 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
20 },
21 BracketPairConfig,
22 Capability::ReadWrite,
23 FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher, Override,
24 ParsedMarkdown, Point,
25};
26use language_settings::{Formatter, FormatterList, IndentGuideSettings};
27use multi_buffer::MultiBufferIndentGuide;
28use parking_lot::Mutex;
29use project::FakeFs;
30use project::{
31 lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
32 project_settings::{LspSettings, ProjectSettings},
33};
34use serde_json::{self, json};
35use std::sync::atomic;
36use std::sync::atomic::AtomicUsize;
37use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
38use unindent::Unindent;
39use util::{
40 assert_set_eq,
41 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
42};
43use workspace::{
44 item::{FollowEvent, FollowableItem, Item, ItemHandle},
45 NavigationEntry, ViewId,
46};
47
48#[gpui::test]
49fn test_edit_events(cx: &mut TestAppContext) {
50 init_test(cx, |_| {});
51
52 let buffer = cx.new_model(|cx| {
53 let mut buffer = language::Buffer::local("123456", cx);
54 buffer.set_group_interval(Duration::from_secs(1));
55 buffer
56 });
57
58 let events = Rc::new(RefCell::new(Vec::new()));
59 let editor1 = cx.add_window({
60 let events = events.clone();
61 |cx| {
62 let view = cx.view().clone();
63 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
64 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
65 EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
66 _ => {}
67 })
68 .detach();
69 Editor::for_buffer(buffer.clone(), None, cx)
70 }
71 });
72
73 let editor2 = cx.add_window({
74 let events = events.clone();
75 |cx| {
76 cx.subscribe(
77 &cx.view().clone(),
78 move |_, _, event: &EditorEvent, _| match event {
79 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
80 EditorEvent::BufferEdited => {
81 events.borrow_mut().push(("editor2", "buffer edited"))
82 }
83 _ => {}
84 },
85 )
86 .detach();
87 Editor::for_buffer(buffer.clone(), None, cx)
88 }
89 });
90
91 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
92
93 // Mutating editor 1 will emit an `Edited` event only for that editor.
94 _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
95 assert_eq!(
96 mem::take(&mut *events.borrow_mut()),
97 [
98 ("editor1", "edited"),
99 ("editor1", "buffer edited"),
100 ("editor2", "buffer edited"),
101 ]
102 );
103
104 // Mutating editor 2 will emit an `Edited` event only for that editor.
105 _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
106 assert_eq!(
107 mem::take(&mut *events.borrow_mut()),
108 [
109 ("editor2", "edited"),
110 ("editor1", "buffer edited"),
111 ("editor2", "buffer edited"),
112 ]
113 );
114
115 // Undoing on editor 1 will emit an `Edited` event only for that editor.
116 _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
117 assert_eq!(
118 mem::take(&mut *events.borrow_mut()),
119 [
120 ("editor1", "edited"),
121 ("editor1", "buffer edited"),
122 ("editor2", "buffer edited"),
123 ]
124 );
125
126 // Redoing on editor 1 will emit an `Edited` event only for that editor.
127 _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
128 assert_eq!(
129 mem::take(&mut *events.borrow_mut()),
130 [
131 ("editor1", "edited"),
132 ("editor1", "buffer edited"),
133 ("editor2", "buffer edited"),
134 ]
135 );
136
137 // Undoing on editor 2 will emit an `Edited` event only for that editor.
138 _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
139 assert_eq!(
140 mem::take(&mut *events.borrow_mut()),
141 [
142 ("editor2", "edited"),
143 ("editor1", "buffer edited"),
144 ("editor2", "buffer edited"),
145 ]
146 );
147
148 // Redoing on editor 2 will emit an `Edited` event only for that editor.
149 _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
150 assert_eq!(
151 mem::take(&mut *events.borrow_mut()),
152 [
153 ("editor2", "edited"),
154 ("editor1", "buffer edited"),
155 ("editor2", "buffer edited"),
156 ]
157 );
158
159 // No event is emitted when the mutation is a no-op.
160 _ = editor2.update(cx, |editor, cx| {
161 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
162
163 editor.backspace(&Backspace, cx);
164 });
165 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
166}
167
168#[gpui::test]
169fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
170 init_test(cx, |_| {});
171
172 let mut now = Instant::now();
173 let buffer = cx.new_model(|cx| language::Buffer::local("123456", cx));
174 let group_interval = buffer.update(cx, |buffer, _| buffer.transaction_group_interval());
175 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
176 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
177
178 _ = editor.update(cx, |editor, cx| {
179 editor.start_transaction_at(now, cx);
180 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
181
182 editor.insert("cd", cx);
183 editor.end_transaction_at(now, cx);
184 assert_eq!(editor.text(cx), "12cd56");
185 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
186
187 editor.start_transaction_at(now, cx);
188 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
189 editor.insert("e", cx);
190 editor.end_transaction_at(now, cx);
191 assert_eq!(editor.text(cx), "12cde6");
192 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
193
194 now += group_interval + Duration::from_millis(1);
195 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
196
197 // Simulate an edit in another editor
198 _ = buffer.update(cx, |buffer, cx| {
199 buffer.start_transaction_at(now, cx);
200 buffer.edit([(0..1, "a")], None, cx);
201 buffer.edit([(1..1, "b")], None, cx);
202 buffer.end_transaction_at(now, cx);
203 });
204
205 assert_eq!(editor.text(cx), "ab2cde6");
206 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
207
208 // Last transaction happened past the group interval in a different editor.
209 // Undo it individually and don't restore selections.
210 editor.undo(&Undo, cx);
211 assert_eq!(editor.text(cx), "12cde6");
212 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
213
214 // First two transactions happened within the group interval in this editor.
215 // Undo them together and restore selections.
216 editor.undo(&Undo, cx);
217 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
218 assert_eq!(editor.text(cx), "123456");
219 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
220
221 // Redo the first two transactions together.
222 editor.redo(&Redo, cx);
223 assert_eq!(editor.text(cx), "12cde6");
224 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
225
226 // Redo the last transaction on its own.
227 editor.redo(&Redo, cx);
228 assert_eq!(editor.text(cx), "ab2cde6");
229 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
230
231 // Test empty transactions.
232 editor.start_transaction_at(now, cx);
233 editor.end_transaction_at(now, cx);
234 editor.undo(&Undo, cx);
235 assert_eq!(editor.text(cx), "12cde6");
236 });
237}
238
239#[gpui::test]
240fn test_ime_composition(cx: &mut TestAppContext) {
241 init_test(cx, |_| {});
242
243 let buffer = cx.new_model(|cx| {
244 let mut buffer = language::Buffer::local("abcde", cx);
245 // Ensure automatic grouping doesn't occur.
246 buffer.set_group_interval(Duration::ZERO);
247 buffer
248 });
249
250 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
251 cx.add_window(|cx| {
252 let mut editor = build_editor(buffer.clone(), cx);
253
254 // Start a new IME composition.
255 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
256 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
257 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
258 assert_eq!(editor.text(cx), "äbcde");
259 assert_eq!(
260 editor.marked_text_ranges(cx),
261 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
262 );
263
264 // Finalize IME composition.
265 editor.replace_text_in_range(None, "ā", cx);
266 assert_eq!(editor.text(cx), "ābcde");
267 assert_eq!(editor.marked_text_ranges(cx), None);
268
269 // IME composition edits are grouped and are undone/redone at once.
270 editor.undo(&Default::default(), cx);
271 assert_eq!(editor.text(cx), "abcde");
272 assert_eq!(editor.marked_text_ranges(cx), None);
273 editor.redo(&Default::default(), cx);
274 assert_eq!(editor.text(cx), "ābcde");
275 assert_eq!(editor.marked_text_ranges(cx), None);
276
277 // Start a new IME composition.
278 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
279 assert_eq!(
280 editor.marked_text_ranges(cx),
281 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
282 );
283
284 // Undoing during an IME composition cancels it.
285 editor.undo(&Default::default(), cx);
286 assert_eq!(editor.text(cx), "ābcde");
287 assert_eq!(editor.marked_text_ranges(cx), None);
288
289 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
290 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
291 assert_eq!(editor.text(cx), "ābcdè");
292 assert_eq!(
293 editor.marked_text_ranges(cx),
294 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
295 );
296
297 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
298 editor.replace_text_in_range(Some(4..999), "ę", cx);
299 assert_eq!(editor.text(cx), "ābcdę");
300 assert_eq!(editor.marked_text_ranges(cx), None);
301
302 // Start a new IME composition with multiple cursors.
303 editor.change_selections(None, cx, |s| {
304 s.select_ranges([
305 OffsetUtf16(1)..OffsetUtf16(1),
306 OffsetUtf16(3)..OffsetUtf16(3),
307 OffsetUtf16(5)..OffsetUtf16(5),
308 ])
309 });
310 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
311 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
312 assert_eq!(
313 editor.marked_text_ranges(cx),
314 Some(vec![
315 OffsetUtf16(0)..OffsetUtf16(3),
316 OffsetUtf16(4)..OffsetUtf16(7),
317 OffsetUtf16(8)..OffsetUtf16(11)
318 ])
319 );
320
321 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
322 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
323 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
324 assert_eq!(
325 editor.marked_text_ranges(cx),
326 Some(vec![
327 OffsetUtf16(1)..OffsetUtf16(2),
328 OffsetUtf16(5)..OffsetUtf16(6),
329 OffsetUtf16(9)..OffsetUtf16(10)
330 ])
331 );
332
333 // Finalize IME composition with multiple cursors.
334 editor.replace_text_in_range(Some(9..10), "2", cx);
335 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
336 assert_eq!(editor.marked_text_ranges(cx), None);
337
338 editor
339 });
340}
341
342#[gpui::test]
343fn test_selection_with_mouse(cx: &mut TestAppContext) {
344 init_test(cx, |_| {});
345
346 let editor = cx.add_window(|cx| {
347 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
348 build_editor(buffer, cx)
349 });
350
351 _ = editor.update(cx, |view, cx| {
352 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
353 });
354 assert_eq!(
355 editor
356 .update(cx, |view, cx| view.selections.display_ranges(cx))
357 .unwrap(),
358 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
359 );
360
361 _ = editor.update(cx, |view, cx| {
362 view.update_selection(
363 DisplayPoint::new(DisplayRow(3), 3),
364 0,
365 gpui::Point::<f32>::default(),
366 cx,
367 );
368 });
369
370 assert_eq!(
371 editor
372 .update(cx, |view, cx| view.selections.display_ranges(cx))
373 .unwrap(),
374 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
375 );
376
377 _ = editor.update(cx, |view, cx| {
378 view.update_selection(
379 DisplayPoint::new(DisplayRow(1), 1),
380 0,
381 gpui::Point::<f32>::default(),
382 cx,
383 );
384 });
385
386 assert_eq!(
387 editor
388 .update(cx, |view, cx| view.selections.display_ranges(cx))
389 .unwrap(),
390 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
391 );
392
393 _ = editor.update(cx, |view, cx| {
394 view.end_selection(cx);
395 view.update_selection(
396 DisplayPoint::new(DisplayRow(3), 3),
397 0,
398 gpui::Point::<f32>::default(),
399 cx,
400 );
401 });
402
403 assert_eq!(
404 editor
405 .update(cx, |view, cx| view.selections.display_ranges(cx))
406 .unwrap(),
407 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
408 );
409
410 _ = editor.update(cx, |view, cx| {
411 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
412 view.update_selection(
413 DisplayPoint::new(DisplayRow(0), 0),
414 0,
415 gpui::Point::<f32>::default(),
416 cx,
417 );
418 });
419
420 assert_eq!(
421 editor
422 .update(cx, |view, cx| view.selections.display_ranges(cx))
423 .unwrap(),
424 [
425 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
426 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
427 ]
428 );
429
430 _ = editor.update(cx, |view, cx| {
431 view.end_selection(cx);
432 });
433
434 assert_eq!(
435 editor
436 .update(cx, |view, cx| view.selections.display_ranges(cx))
437 .unwrap(),
438 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
439 );
440}
441
442#[gpui::test]
443fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
444 init_test(cx, |_| {});
445
446 let editor = cx.add_window(|cx| {
447 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
448 build_editor(buffer, cx)
449 });
450
451 _ = editor.update(cx, |view, cx| {
452 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
453 });
454
455 _ = editor.update(cx, |view, cx| {
456 view.end_selection(cx);
457 });
458
459 _ = editor.update(cx, |view, cx| {
460 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
461 });
462
463 _ = editor.update(cx, |view, cx| {
464 view.end_selection(cx);
465 });
466
467 assert_eq!(
468 editor
469 .update(cx, |view, cx| view.selections.display_ranges(cx))
470 .unwrap(),
471 [
472 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
473 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
474 ]
475 );
476
477 _ = editor.update(cx, |view, cx| {
478 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
479 });
480
481 _ = editor.update(cx, |view, cx| {
482 view.end_selection(cx);
483 });
484
485 assert_eq!(
486 editor
487 .update(cx, |view, cx| view.selections.display_ranges(cx))
488 .unwrap(),
489 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
490 );
491}
492
493#[gpui::test]
494fn test_canceling_pending_selection(cx: &mut TestAppContext) {
495 init_test(cx, |_| {});
496
497 let view = cx.add_window(|cx| {
498 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
499 build_editor(buffer, cx)
500 });
501
502 _ = view.update(cx, |view, cx| {
503 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
504 assert_eq!(
505 view.selections.display_ranges(cx),
506 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
507 );
508 });
509
510 _ = view.update(cx, |view, cx| {
511 view.update_selection(
512 DisplayPoint::new(DisplayRow(3), 3),
513 0,
514 gpui::Point::<f32>::default(),
515 cx,
516 );
517 assert_eq!(
518 view.selections.display_ranges(cx),
519 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
520 );
521 });
522
523 _ = view.update(cx, |view, cx| {
524 view.cancel(&Cancel, cx);
525 view.update_selection(
526 DisplayPoint::new(DisplayRow(1), 1),
527 0,
528 gpui::Point::<f32>::default(),
529 cx,
530 );
531 assert_eq!(
532 view.selections.display_ranges(cx),
533 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
534 );
535 });
536}
537
538#[gpui::test]
539fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
540 init_test(cx, |_| {});
541
542 let view = cx.add_window(|cx| {
543 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
544 build_editor(buffer, cx)
545 });
546
547 _ = view.update(cx, |view, cx| {
548 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
549 assert_eq!(
550 view.selections.display_ranges(cx),
551 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
552 );
553
554 view.move_down(&Default::default(), cx);
555 assert_eq!(
556 view.selections.display_ranges(cx),
557 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
558 );
559
560 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
561 assert_eq!(
562 view.selections.display_ranges(cx),
563 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
564 );
565
566 view.move_up(&Default::default(), cx);
567 assert_eq!(
568 view.selections.display_ranges(cx),
569 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
570 );
571 });
572}
573
574#[gpui::test]
575fn test_clone(cx: &mut TestAppContext) {
576 init_test(cx, |_| {});
577
578 let (text, selection_ranges) = marked_text_ranges(
579 indoc! {"
580 one
581 two
582 threeˇ
583 four
584 fiveˇ
585 "},
586 true,
587 );
588
589 let editor = cx.add_window(|cx| {
590 let buffer = MultiBuffer::build_simple(&text, cx);
591 build_editor(buffer, cx)
592 });
593
594 _ = editor.update(cx, |editor, cx| {
595 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
596 editor.fold_ranges(
597 [
598 (Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
599 (Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
600 ],
601 true,
602 cx,
603 );
604 });
605
606 let cloned_editor = editor
607 .update(cx, |editor, cx| {
608 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
609 })
610 .unwrap()
611 .unwrap();
612
613 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
614 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
615
616 assert_eq!(
617 cloned_editor
618 .update(cx, |e, cx| e.display_text(cx))
619 .unwrap(),
620 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
621 );
622 assert_eq!(
623 cloned_snapshot
624 .folds_in_range(0..text.len())
625 .collect::<Vec<_>>(),
626 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
627 );
628 assert_set_eq!(
629 cloned_editor
630 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
631 .unwrap(),
632 editor
633 .update(cx, |editor, cx| editor.selections.ranges(cx))
634 .unwrap()
635 );
636 assert_set_eq!(
637 cloned_editor
638 .update(cx, |e, cx| e.selections.display_ranges(cx))
639 .unwrap(),
640 editor
641 .update(cx, |e, cx| e.selections.display_ranges(cx))
642 .unwrap()
643 );
644}
645
646#[gpui::test]
647async fn test_navigation_history(cx: &mut TestAppContext) {
648 init_test(cx, |_| {});
649
650 use workspace::item::Item;
651
652 let fs = FakeFs::new(cx.executor());
653 let project = Project::test(fs, [], cx).await;
654 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
655 let pane = workspace
656 .update(cx, |workspace, _| workspace.active_pane().clone())
657 .unwrap();
658
659 _ = workspace.update(cx, |_v, cx| {
660 cx.new_view(|cx| {
661 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
662 let mut editor = build_editor(buffer.clone(), cx);
663 let handle = cx.view();
664 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
665
666 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
667 editor.nav_history.as_mut().unwrap().pop_backward(cx)
668 }
669
670 // Move the cursor a small distance.
671 // Nothing is added to the navigation history.
672 editor.change_selections(None, cx, |s| {
673 s.select_display_ranges([
674 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
675 ])
676 });
677 editor.change_selections(None, cx, |s| {
678 s.select_display_ranges([
679 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
680 ])
681 });
682 assert!(pop_history(&mut editor, cx).is_none());
683
684 // Move the cursor a large distance.
685 // The history can jump back to the previous position.
686 editor.change_selections(None, cx, |s| {
687 s.select_display_ranges([
688 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
689 ])
690 });
691 let nav_entry = pop_history(&mut editor, cx).unwrap();
692 editor.navigate(nav_entry.data.unwrap(), cx);
693 assert_eq!(nav_entry.item.id(), cx.entity_id());
694 assert_eq!(
695 editor.selections.display_ranges(cx),
696 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
697 );
698 assert!(pop_history(&mut editor, cx).is_none());
699
700 // Move the cursor a small distance via the mouse.
701 // Nothing is added to the navigation history.
702 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
703 editor.end_selection(cx);
704 assert_eq!(
705 editor.selections.display_ranges(cx),
706 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
707 );
708 assert!(pop_history(&mut editor, cx).is_none());
709
710 // Move the cursor a large distance via the mouse.
711 // The history can jump back to the previous position.
712 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
713 editor.end_selection(cx);
714 assert_eq!(
715 editor.selections.display_ranges(cx),
716 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
717 );
718 let nav_entry = pop_history(&mut editor, cx).unwrap();
719 editor.navigate(nav_entry.data.unwrap(), cx);
720 assert_eq!(nav_entry.item.id(), cx.entity_id());
721 assert_eq!(
722 editor.selections.display_ranges(cx),
723 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
724 );
725 assert!(pop_history(&mut editor, cx).is_none());
726
727 // Set scroll position to check later
728 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
729 let original_scroll_position = editor.scroll_manager.anchor();
730
731 // Jump to the end of the document and adjust scroll
732 editor.move_to_end(&MoveToEnd, cx);
733 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
734 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
735
736 let nav_entry = pop_history(&mut editor, cx).unwrap();
737 editor.navigate(nav_entry.data.unwrap(), cx);
738 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
739
740 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
741 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
742 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
743 let invalid_point = Point::new(9999, 0);
744 editor.navigate(
745 Box::new(NavigationData {
746 cursor_anchor: invalid_anchor,
747 cursor_position: invalid_point,
748 scroll_anchor: ScrollAnchor {
749 anchor: invalid_anchor,
750 offset: Default::default(),
751 },
752 scroll_top_row: invalid_point.row,
753 }),
754 cx,
755 );
756 assert_eq!(
757 editor.selections.display_ranges(cx),
758 &[editor.max_point(cx)..editor.max_point(cx)]
759 );
760 assert_eq!(
761 editor.scroll_position(cx),
762 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
763 );
764
765 editor
766 })
767 });
768}
769
770#[gpui::test]
771fn test_cancel(cx: &mut TestAppContext) {
772 init_test(cx, |_| {});
773
774 let view = cx.add_window(|cx| {
775 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
776 build_editor(buffer, cx)
777 });
778
779 _ = view.update(cx, |view, cx| {
780 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
781 view.update_selection(
782 DisplayPoint::new(DisplayRow(1), 1),
783 0,
784 gpui::Point::<f32>::default(),
785 cx,
786 );
787 view.end_selection(cx);
788
789 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
790 view.update_selection(
791 DisplayPoint::new(DisplayRow(0), 3),
792 0,
793 gpui::Point::<f32>::default(),
794 cx,
795 );
796 view.end_selection(cx);
797 assert_eq!(
798 view.selections.display_ranges(cx),
799 [
800 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
801 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
802 ]
803 );
804 });
805
806 _ = view.update(cx, |view, cx| {
807 view.cancel(&Cancel, cx);
808 assert_eq!(
809 view.selections.display_ranges(cx),
810 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
811 );
812 });
813
814 _ = view.update(cx, |view, cx| {
815 view.cancel(&Cancel, cx);
816 assert_eq!(
817 view.selections.display_ranges(cx),
818 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
819 );
820 });
821}
822
823#[gpui::test]
824fn test_fold_action(cx: &mut TestAppContext) {
825 init_test(cx, |_| {});
826
827 let view = cx.add_window(|cx| {
828 let buffer = MultiBuffer::build_simple(
829 &"
830 impl Foo {
831 // Hello!
832
833 fn a() {
834 1
835 }
836
837 fn b() {
838 2
839 }
840
841 fn c() {
842 3
843 }
844 }
845 "
846 .unindent(),
847 cx,
848 );
849 build_editor(buffer.clone(), cx)
850 });
851
852 _ = view.update(cx, |view, cx| {
853 view.change_selections(None, cx, |s| {
854 s.select_display_ranges([
855 DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(12), 0)
856 ]);
857 });
858 view.fold(&Fold, cx);
859 assert_eq!(
860 view.display_text(cx),
861 "
862 impl Foo {
863 // Hello!
864
865 fn a() {
866 1
867 }
868
869 fn b() {⋯
870 }
871
872 fn c() {⋯
873 }
874 }
875 "
876 .unindent(),
877 );
878
879 view.fold(&Fold, cx);
880 assert_eq!(
881 view.display_text(cx),
882 "
883 impl Foo {⋯
884 }
885 "
886 .unindent(),
887 );
888
889 view.unfold_lines(&UnfoldLines, cx);
890 assert_eq!(
891 view.display_text(cx),
892 "
893 impl Foo {
894 // Hello!
895
896 fn a() {
897 1
898 }
899
900 fn b() {⋯
901 }
902
903 fn c() {⋯
904 }
905 }
906 "
907 .unindent(),
908 );
909
910 view.unfold_lines(&UnfoldLines, cx);
911 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
912 });
913}
914
915#[gpui::test]
916fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
917 init_test(cx, |_| {});
918
919 let view = cx.add_window(|cx| {
920 let buffer = MultiBuffer::build_simple(
921 &"
922 class Foo:
923 # Hello!
924
925 def a():
926 print(1)
927
928 def b():
929 print(2)
930
931 def c():
932 print(3)
933 "
934 .unindent(),
935 cx,
936 );
937 build_editor(buffer.clone(), cx)
938 });
939
940 _ = view.update(cx, |view, cx| {
941 view.change_selections(None, cx, |s| {
942 s.select_display_ranges([
943 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(10), 0)
944 ]);
945 });
946 view.fold(&Fold, cx);
947 assert_eq!(
948 view.display_text(cx),
949 "
950 class Foo:
951 # Hello!
952
953 def a():
954 print(1)
955
956 def b():⋯
957
958 def c():⋯
959 "
960 .unindent(),
961 );
962
963 view.fold(&Fold, cx);
964 assert_eq!(
965 view.display_text(cx),
966 "
967 class Foo:⋯
968 "
969 .unindent(),
970 );
971
972 view.unfold_lines(&UnfoldLines, cx);
973 assert_eq!(
974 view.display_text(cx),
975 "
976 class Foo:
977 # Hello!
978
979 def a():
980 print(1)
981
982 def b():⋯
983
984 def c():⋯
985 "
986 .unindent(),
987 );
988
989 view.unfold_lines(&UnfoldLines, cx);
990 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
991 });
992}
993
994#[gpui::test]
995fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
996 init_test(cx, |_| {});
997
998 let view = cx.add_window(|cx| {
999 let buffer = MultiBuffer::build_simple(
1000 &"
1001 class Foo:
1002 # Hello!
1003
1004 def a():
1005 print(1)
1006
1007 def b():
1008 print(2)
1009
1010
1011 def c():
1012 print(3)
1013
1014
1015 "
1016 .unindent(),
1017 cx,
1018 );
1019 build_editor(buffer.clone(), cx)
1020 });
1021
1022 _ = view.update(cx, |view, cx| {
1023 view.change_selections(None, cx, |s| {
1024 s.select_display_ranges([
1025 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(11), 0)
1026 ]);
1027 });
1028 view.fold(&Fold, cx);
1029 assert_eq!(
1030 view.display_text(cx),
1031 "
1032 class Foo:
1033 # Hello!
1034
1035 def a():
1036 print(1)
1037
1038 def b():⋯
1039
1040
1041 def c():⋯
1042
1043
1044 "
1045 .unindent(),
1046 );
1047
1048 view.fold(&Fold, cx);
1049 assert_eq!(
1050 view.display_text(cx),
1051 "
1052 class Foo:⋯
1053
1054
1055 "
1056 .unindent(),
1057 );
1058
1059 view.unfold_lines(&UnfoldLines, cx);
1060 assert_eq!(
1061 view.display_text(cx),
1062 "
1063 class Foo:
1064 # Hello!
1065
1066 def a():
1067 print(1)
1068
1069 def b():⋯
1070
1071
1072 def c():⋯
1073
1074
1075 "
1076 .unindent(),
1077 );
1078
1079 view.unfold_lines(&UnfoldLines, cx);
1080 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1081 });
1082}
1083
1084#[gpui::test]
1085fn test_move_cursor(cx: &mut TestAppContext) {
1086 init_test(cx, |_| {});
1087
1088 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1089 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1090
1091 _ = buffer.update(cx, |buffer, cx| {
1092 buffer.edit(
1093 vec![
1094 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1095 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1096 ],
1097 None,
1098 cx,
1099 );
1100 });
1101 _ = view.update(cx, |view, cx| {
1102 assert_eq!(
1103 view.selections.display_ranges(cx),
1104 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1105 );
1106
1107 view.move_down(&MoveDown, cx);
1108 assert_eq!(
1109 view.selections.display_ranges(cx),
1110 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1111 );
1112
1113 view.move_right(&MoveRight, cx);
1114 assert_eq!(
1115 view.selections.display_ranges(cx),
1116 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1117 );
1118
1119 view.move_left(&MoveLeft, cx);
1120 assert_eq!(
1121 view.selections.display_ranges(cx),
1122 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1123 );
1124
1125 view.move_up(&MoveUp, cx);
1126 assert_eq!(
1127 view.selections.display_ranges(cx),
1128 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1129 );
1130
1131 view.move_to_end(&MoveToEnd, cx);
1132 assert_eq!(
1133 view.selections.display_ranges(cx),
1134 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1135 );
1136
1137 view.move_to_beginning(&MoveToBeginning, cx);
1138 assert_eq!(
1139 view.selections.display_ranges(cx),
1140 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1141 );
1142
1143 view.change_selections(None, cx, |s| {
1144 s.select_display_ranges([
1145 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1146 ]);
1147 });
1148 view.select_to_beginning(&SelectToBeginning, cx);
1149 assert_eq!(
1150 view.selections.display_ranges(cx),
1151 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1152 );
1153
1154 view.select_to_end(&SelectToEnd, cx);
1155 assert_eq!(
1156 view.selections.display_ranges(cx),
1157 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1158 );
1159 });
1160}
1161
1162// TODO: Re-enable this test
1163#[cfg(target_os = "macos")]
1164#[gpui::test]
1165fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1166 init_test(cx, |_| {});
1167
1168 let view = cx.add_window(|cx| {
1169 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1170 build_editor(buffer.clone(), cx)
1171 });
1172
1173 assert_eq!('ⓐ'.len_utf8(), 3);
1174 assert_eq!('α'.len_utf8(), 2);
1175
1176 _ = view.update(cx, |view, cx| {
1177 view.fold_ranges(
1178 vec![
1179 (Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1180 (Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1181 (Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1182 ],
1183 true,
1184 cx,
1185 );
1186 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1187
1188 view.move_right(&MoveRight, cx);
1189 assert_eq!(
1190 view.selections.display_ranges(cx),
1191 &[empty_range(0, "ⓐ".len())]
1192 );
1193 view.move_right(&MoveRight, cx);
1194 assert_eq!(
1195 view.selections.display_ranges(cx),
1196 &[empty_range(0, "ⓐⓑ".len())]
1197 );
1198 view.move_right(&MoveRight, cx);
1199 assert_eq!(
1200 view.selections.display_ranges(cx),
1201 &[empty_range(0, "ⓐⓑ⋯".len())]
1202 );
1203
1204 view.move_down(&MoveDown, cx);
1205 assert_eq!(
1206 view.selections.display_ranges(cx),
1207 &[empty_range(1, "ab⋯e".len())]
1208 );
1209 view.move_left(&MoveLeft, cx);
1210 assert_eq!(
1211 view.selections.display_ranges(cx),
1212 &[empty_range(1, "ab⋯".len())]
1213 );
1214 view.move_left(&MoveLeft, cx);
1215 assert_eq!(
1216 view.selections.display_ranges(cx),
1217 &[empty_range(1, "ab".len())]
1218 );
1219 view.move_left(&MoveLeft, cx);
1220 assert_eq!(
1221 view.selections.display_ranges(cx),
1222 &[empty_range(1, "a".len())]
1223 );
1224
1225 view.move_down(&MoveDown, cx);
1226 assert_eq!(
1227 view.selections.display_ranges(cx),
1228 &[empty_range(2, "α".len())]
1229 );
1230 view.move_right(&MoveRight, cx);
1231 assert_eq!(
1232 view.selections.display_ranges(cx),
1233 &[empty_range(2, "αβ".len())]
1234 );
1235 view.move_right(&MoveRight, cx);
1236 assert_eq!(
1237 view.selections.display_ranges(cx),
1238 &[empty_range(2, "αβ⋯".len())]
1239 );
1240 view.move_right(&MoveRight, cx);
1241 assert_eq!(
1242 view.selections.display_ranges(cx),
1243 &[empty_range(2, "αβ⋯ε".len())]
1244 );
1245
1246 view.move_up(&MoveUp, cx);
1247 assert_eq!(
1248 view.selections.display_ranges(cx),
1249 &[empty_range(1, "ab⋯e".len())]
1250 );
1251 view.move_down(&MoveDown, cx);
1252 assert_eq!(
1253 view.selections.display_ranges(cx),
1254 &[empty_range(2, "αβ⋯ε".len())]
1255 );
1256 view.move_up(&MoveUp, cx);
1257 assert_eq!(
1258 view.selections.display_ranges(cx),
1259 &[empty_range(1, "ab⋯e".len())]
1260 );
1261
1262 view.move_up(&MoveUp, cx);
1263 assert_eq!(
1264 view.selections.display_ranges(cx),
1265 &[empty_range(0, "ⓐⓑ".len())]
1266 );
1267 view.move_left(&MoveLeft, cx);
1268 assert_eq!(
1269 view.selections.display_ranges(cx),
1270 &[empty_range(0, "ⓐ".len())]
1271 );
1272 view.move_left(&MoveLeft, cx);
1273 assert_eq!(
1274 view.selections.display_ranges(cx),
1275 &[empty_range(0, "".len())]
1276 );
1277 });
1278}
1279
1280#[gpui::test]
1281fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1282 init_test(cx, |_| {});
1283
1284 let view = cx.add_window(|cx| {
1285 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1286 build_editor(buffer.clone(), cx)
1287 });
1288 _ = view.update(cx, |view, cx| {
1289 view.change_selections(None, cx, |s| {
1290 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1291 });
1292 view.move_down(&MoveDown, cx);
1293 assert_eq!(
1294 view.selections.display_ranges(cx),
1295 &[empty_range(1, "abcd".len())]
1296 );
1297
1298 view.move_down(&MoveDown, cx);
1299 assert_eq!(
1300 view.selections.display_ranges(cx),
1301 &[empty_range(2, "αβγ".len())]
1302 );
1303
1304 view.move_down(&MoveDown, cx);
1305 assert_eq!(
1306 view.selections.display_ranges(cx),
1307 &[empty_range(3, "abcd".len())]
1308 );
1309
1310 view.move_down(&MoveDown, cx);
1311 assert_eq!(
1312 view.selections.display_ranges(cx),
1313 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1314 );
1315
1316 view.move_up(&MoveUp, cx);
1317 assert_eq!(
1318 view.selections.display_ranges(cx),
1319 &[empty_range(3, "abcd".len())]
1320 );
1321
1322 view.move_up(&MoveUp, cx);
1323 assert_eq!(
1324 view.selections.display_ranges(cx),
1325 &[empty_range(2, "αβγ".len())]
1326 );
1327 });
1328}
1329
1330#[gpui::test]
1331fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1332 init_test(cx, |_| {});
1333 let move_to_beg = MoveToBeginningOfLine {
1334 stop_at_soft_wraps: true,
1335 };
1336
1337 let move_to_end = MoveToEndOfLine {
1338 stop_at_soft_wraps: true,
1339 };
1340
1341 let view = cx.add_window(|cx| {
1342 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1343 build_editor(buffer, cx)
1344 });
1345 _ = view.update(cx, |view, cx| {
1346 view.change_selections(None, cx, |s| {
1347 s.select_display_ranges([
1348 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1349 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1350 ]);
1351 });
1352 });
1353
1354 _ = view.update(cx, |view, cx| {
1355 view.move_to_beginning_of_line(&move_to_beg, cx);
1356 assert_eq!(
1357 view.selections.display_ranges(cx),
1358 &[
1359 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1360 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1361 ]
1362 );
1363 });
1364
1365 _ = view.update(cx, |view, cx| {
1366 view.move_to_beginning_of_line(&move_to_beg, cx);
1367 assert_eq!(
1368 view.selections.display_ranges(cx),
1369 &[
1370 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1371 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1372 ]
1373 );
1374 });
1375
1376 _ = view.update(cx, |view, cx| {
1377 view.move_to_beginning_of_line(&move_to_beg, cx);
1378 assert_eq!(
1379 view.selections.display_ranges(cx),
1380 &[
1381 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1382 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1383 ]
1384 );
1385 });
1386
1387 _ = view.update(cx, |view, cx| {
1388 view.move_to_end_of_line(&move_to_end, cx);
1389 assert_eq!(
1390 view.selections.display_ranges(cx),
1391 &[
1392 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1393 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1394 ]
1395 );
1396 });
1397
1398 // Moving to the end of line again is a no-op.
1399 _ = view.update(cx, |view, cx| {
1400 view.move_to_end_of_line(&move_to_end, cx);
1401 assert_eq!(
1402 view.selections.display_ranges(cx),
1403 &[
1404 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1405 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1406 ]
1407 );
1408 });
1409
1410 _ = view.update(cx, |view, cx| {
1411 view.move_left(&MoveLeft, cx);
1412 view.select_to_beginning_of_line(
1413 &SelectToBeginningOfLine {
1414 stop_at_soft_wraps: true,
1415 },
1416 cx,
1417 );
1418 assert_eq!(
1419 view.selections.display_ranges(cx),
1420 &[
1421 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1422 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1423 ]
1424 );
1425 });
1426
1427 _ = view.update(cx, |view, cx| {
1428 view.select_to_beginning_of_line(
1429 &SelectToBeginningOfLine {
1430 stop_at_soft_wraps: true,
1431 },
1432 cx,
1433 );
1434 assert_eq!(
1435 view.selections.display_ranges(cx),
1436 &[
1437 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1438 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1439 ]
1440 );
1441 });
1442
1443 _ = view.update(cx, |view, cx| {
1444 view.select_to_beginning_of_line(
1445 &SelectToBeginningOfLine {
1446 stop_at_soft_wraps: true,
1447 },
1448 cx,
1449 );
1450 assert_eq!(
1451 view.selections.display_ranges(cx),
1452 &[
1453 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1454 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1455 ]
1456 );
1457 });
1458
1459 _ = view.update(cx, |view, cx| {
1460 view.select_to_end_of_line(
1461 &SelectToEndOfLine {
1462 stop_at_soft_wraps: true,
1463 },
1464 cx,
1465 );
1466 assert_eq!(
1467 view.selections.display_ranges(cx),
1468 &[
1469 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1470 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1471 ]
1472 );
1473 });
1474
1475 _ = view.update(cx, |view, cx| {
1476 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1477 assert_eq!(view.display_text(cx), "ab\n de");
1478 assert_eq!(
1479 view.selections.display_ranges(cx),
1480 &[
1481 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1482 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1483 ]
1484 );
1485 });
1486
1487 _ = view.update(cx, |view, cx| {
1488 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1489 assert_eq!(view.display_text(cx), "\n");
1490 assert_eq!(
1491 view.selections.display_ranges(cx),
1492 &[
1493 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1494 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1495 ]
1496 );
1497 });
1498}
1499
1500#[gpui::test]
1501fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1502 init_test(cx, |_| {});
1503 let move_to_beg = MoveToBeginningOfLine {
1504 stop_at_soft_wraps: false,
1505 };
1506
1507 let move_to_end = MoveToEndOfLine {
1508 stop_at_soft_wraps: false,
1509 };
1510
1511 let view = cx.add_window(|cx| {
1512 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1513 build_editor(buffer, cx)
1514 });
1515
1516 _ = view.update(cx, |view, cx| {
1517 view.set_wrap_width(Some(140.0.into()), cx);
1518
1519 // We expect the following lines after wrapping
1520 // ```
1521 // thequickbrownfox
1522 // jumpedoverthelazydo
1523 // gs
1524 // ```
1525 // The final `gs` was soft-wrapped onto a new line.
1526 assert_eq!(
1527 "thequickbrownfox\njumpedoverthelaz\nydogs",
1528 view.display_text(cx),
1529 );
1530
1531 // First, let's assert behavior on the first line, that was not soft-wrapped.
1532 // Start the cursor at the `k` on the first line
1533 view.change_selections(None, cx, |s| {
1534 s.select_display_ranges([
1535 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1536 ]);
1537 });
1538
1539 // Moving to the beginning of the line should put us at the beginning of the line.
1540 view.move_to_beginning_of_line(&move_to_beg, cx);
1541 assert_eq!(
1542 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1543 view.selections.display_ranges(cx)
1544 );
1545
1546 // Moving to the end of the line should put us at the end of the line.
1547 view.move_to_end_of_line(&move_to_end, cx);
1548 assert_eq!(
1549 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1550 view.selections.display_ranges(cx)
1551 );
1552
1553 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1554 // Start the cursor at the last line (`y` that was wrapped to a new line)
1555 view.change_selections(None, cx, |s| {
1556 s.select_display_ranges([
1557 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1558 ]);
1559 });
1560
1561 // Moving to the beginning of the line should put us at the start of the second line of
1562 // display text, i.e., the `j`.
1563 view.move_to_beginning_of_line(&move_to_beg, cx);
1564 assert_eq!(
1565 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1566 view.selections.display_ranges(cx)
1567 );
1568
1569 // Moving to the beginning of the line again should be a no-op.
1570 view.move_to_beginning_of_line(&move_to_beg, cx);
1571 assert_eq!(
1572 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1573 view.selections.display_ranges(cx)
1574 );
1575
1576 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1577 // next display line.
1578 view.move_to_end_of_line(&move_to_end, cx);
1579 assert_eq!(
1580 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1581 view.selections.display_ranges(cx)
1582 );
1583
1584 // Moving to the end of the line again should be a no-op.
1585 view.move_to_end_of_line(&move_to_end, cx);
1586 assert_eq!(
1587 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1588 view.selections.display_ranges(cx)
1589 );
1590 });
1591}
1592
1593#[gpui::test]
1594fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1595 init_test(cx, |_| {});
1596
1597 let view = cx.add_window(|cx| {
1598 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1599 build_editor(buffer, cx)
1600 });
1601 _ = view.update(cx, |view, cx| {
1602 view.change_selections(None, cx, |s| {
1603 s.select_display_ranges([
1604 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1605 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1606 ])
1607 });
1608
1609 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1610 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1611
1612 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1613 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1614
1615 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1616 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1617
1618 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1619 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1620
1621 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1622 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1623
1624 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1625 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1626
1627 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1628 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1629
1630 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1631 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1632
1633 view.move_right(&MoveRight, cx);
1634 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1635 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1636
1637 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1638 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1639
1640 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1641 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1642 });
1643}
1644
1645#[gpui::test]
1646fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1647 init_test(cx, |_| {});
1648
1649 let view = cx.add_window(|cx| {
1650 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1651 build_editor(buffer, cx)
1652 });
1653
1654 _ = view.update(cx, |view, cx| {
1655 view.set_wrap_width(Some(140.0.into()), cx);
1656 assert_eq!(
1657 view.display_text(cx),
1658 "use one::{\n two::three::\n four::five\n};"
1659 );
1660
1661 view.change_selections(None, cx, |s| {
1662 s.select_display_ranges([
1663 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1664 ]);
1665 });
1666
1667 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1668 assert_eq!(
1669 view.selections.display_ranges(cx),
1670 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1671 );
1672
1673 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1674 assert_eq!(
1675 view.selections.display_ranges(cx),
1676 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1677 );
1678
1679 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1680 assert_eq!(
1681 view.selections.display_ranges(cx),
1682 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1683 );
1684
1685 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1686 assert_eq!(
1687 view.selections.display_ranges(cx),
1688 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1689 );
1690
1691 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1692 assert_eq!(
1693 view.selections.display_ranges(cx),
1694 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1695 );
1696
1697 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1698 assert_eq!(
1699 view.selections.display_ranges(cx),
1700 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1701 );
1702 });
1703}
1704
1705#[gpui::test]
1706async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1707 init_test(cx, |_| {});
1708 let mut cx = EditorTestContext::new(cx).await;
1709
1710 let line_height = cx.editor(|editor, cx| {
1711 editor
1712 .style()
1713 .unwrap()
1714 .text
1715 .line_height_in_pixels(cx.rem_size())
1716 });
1717 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1718
1719 cx.set_state(
1720 &r#"ˇone
1721 two
1722
1723 three
1724 fourˇ
1725 five
1726
1727 six"#
1728 .unindent(),
1729 );
1730
1731 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1732 cx.assert_editor_state(
1733 &r#"one
1734 two
1735 ˇ
1736 three
1737 four
1738 five
1739 ˇ
1740 six"#
1741 .unindent(),
1742 );
1743
1744 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1745 cx.assert_editor_state(
1746 &r#"one
1747 two
1748
1749 three
1750 four
1751 five
1752 ˇ
1753 sixˇ"#
1754 .unindent(),
1755 );
1756
1757 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1758 cx.assert_editor_state(
1759 &r#"one
1760 two
1761
1762 three
1763 four
1764 five
1765
1766 sixˇ"#
1767 .unindent(),
1768 );
1769
1770 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1771 cx.assert_editor_state(
1772 &r#"one
1773 two
1774
1775 three
1776 four
1777 five
1778 ˇ
1779 six"#
1780 .unindent(),
1781 );
1782
1783 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1784 cx.assert_editor_state(
1785 &r#"one
1786 two
1787 ˇ
1788 three
1789 four
1790 five
1791
1792 six"#
1793 .unindent(),
1794 );
1795
1796 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1797 cx.assert_editor_state(
1798 &r#"ˇone
1799 two
1800
1801 three
1802 four
1803 five
1804
1805 six"#
1806 .unindent(),
1807 );
1808}
1809
1810#[gpui::test]
1811async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1812 init_test(cx, |_| {});
1813 let mut cx = EditorTestContext::new(cx).await;
1814 let line_height = cx.editor(|editor, cx| {
1815 editor
1816 .style()
1817 .unwrap()
1818 .text
1819 .line_height_in_pixels(cx.rem_size())
1820 });
1821 let window = cx.window;
1822 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1823
1824 cx.set_state(
1825 &r#"ˇone
1826 two
1827 three
1828 four
1829 five
1830 six
1831 seven
1832 eight
1833 nine
1834 ten
1835 "#,
1836 );
1837
1838 cx.update_editor(|editor, cx| {
1839 assert_eq!(
1840 editor.snapshot(cx).scroll_position(),
1841 gpui::Point::new(0., 0.)
1842 );
1843 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1844 assert_eq!(
1845 editor.snapshot(cx).scroll_position(),
1846 gpui::Point::new(0., 3.)
1847 );
1848 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1849 assert_eq!(
1850 editor.snapshot(cx).scroll_position(),
1851 gpui::Point::new(0., 6.)
1852 );
1853 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1854 assert_eq!(
1855 editor.snapshot(cx).scroll_position(),
1856 gpui::Point::new(0., 3.)
1857 );
1858
1859 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1860 assert_eq!(
1861 editor.snapshot(cx).scroll_position(),
1862 gpui::Point::new(0., 1.)
1863 );
1864 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
1865 assert_eq!(
1866 editor.snapshot(cx).scroll_position(),
1867 gpui::Point::new(0., 3.)
1868 );
1869 });
1870}
1871
1872#[gpui::test]
1873async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
1874 init_test(cx, |_| {});
1875 let mut cx = EditorTestContext::new(cx).await;
1876
1877 let line_height = cx.update_editor(|editor, cx| {
1878 editor.set_vertical_scroll_margin(2, cx);
1879 editor
1880 .style()
1881 .unwrap()
1882 .text
1883 .line_height_in_pixels(cx.rem_size())
1884 });
1885 let window = cx.window;
1886 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
1887
1888 cx.set_state(
1889 &r#"ˇone
1890 two
1891 three
1892 four
1893 five
1894 six
1895 seven
1896 eight
1897 nine
1898 ten
1899 "#,
1900 );
1901 cx.update_editor(|editor, cx| {
1902 assert_eq!(
1903 editor.snapshot(cx).scroll_position(),
1904 gpui::Point::new(0., 0.0)
1905 );
1906 });
1907
1908 // Add a cursor below the visible area. Since both cursors cannot fit
1909 // on screen, the editor autoscrolls to reveal the newest cursor, and
1910 // allows the vertical scroll margin below that cursor.
1911 cx.update_editor(|editor, cx| {
1912 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1913 selections.select_ranges([
1914 Point::new(0, 0)..Point::new(0, 0),
1915 Point::new(6, 0)..Point::new(6, 0),
1916 ]);
1917 })
1918 });
1919 cx.update_editor(|editor, cx| {
1920 assert_eq!(
1921 editor.snapshot(cx).scroll_position(),
1922 gpui::Point::new(0., 3.0)
1923 );
1924 });
1925
1926 // Move down. The editor cursor scrolls down to track the newest cursor.
1927 cx.update_editor(|editor, cx| {
1928 editor.move_down(&Default::default(), cx);
1929 });
1930 cx.update_editor(|editor, cx| {
1931 assert_eq!(
1932 editor.snapshot(cx).scroll_position(),
1933 gpui::Point::new(0., 4.0)
1934 );
1935 });
1936
1937 // Add a cursor above the visible area. Since both cursors fit on screen,
1938 // the editor scrolls to show both.
1939 cx.update_editor(|editor, cx| {
1940 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1941 selections.select_ranges([
1942 Point::new(1, 0)..Point::new(1, 0),
1943 Point::new(6, 0)..Point::new(6, 0),
1944 ]);
1945 })
1946 });
1947 cx.update_editor(|editor, cx| {
1948 assert_eq!(
1949 editor.snapshot(cx).scroll_position(),
1950 gpui::Point::new(0., 1.0)
1951 );
1952 });
1953}
1954
1955#[gpui::test]
1956async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
1957 init_test(cx, |_| {});
1958 let mut cx = EditorTestContext::new(cx).await;
1959
1960 let line_height = cx.editor(|editor, cx| {
1961 editor
1962 .style()
1963 .unwrap()
1964 .text
1965 .line_height_in_pixels(cx.rem_size())
1966 });
1967 let window = cx.window;
1968 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
1969 cx.set_state(
1970 &r#"
1971 ˇone
1972 two
1973 threeˇ
1974 four
1975 five
1976 six
1977 seven
1978 eight
1979 nine
1980 ten
1981 "#
1982 .unindent(),
1983 );
1984
1985 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1986 cx.assert_editor_state(
1987 &r#"
1988 one
1989 two
1990 three
1991 ˇfour
1992 five
1993 sixˇ
1994 seven
1995 eight
1996 nine
1997 ten
1998 "#
1999 .unindent(),
2000 );
2001
2002 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2003 cx.assert_editor_state(
2004 &r#"
2005 one
2006 two
2007 three
2008 four
2009 five
2010 six
2011 ˇseven
2012 eight
2013 nineˇ
2014 ten
2015 "#
2016 .unindent(),
2017 );
2018
2019 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2020 cx.assert_editor_state(
2021 &r#"
2022 one
2023 two
2024 three
2025 ˇfour
2026 five
2027 sixˇ
2028 seven
2029 eight
2030 nine
2031 ten
2032 "#
2033 .unindent(),
2034 );
2035
2036 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2037 cx.assert_editor_state(
2038 &r#"
2039 ˇone
2040 two
2041 threeˇ
2042 four
2043 five
2044 six
2045 seven
2046 eight
2047 nine
2048 ten
2049 "#
2050 .unindent(),
2051 );
2052
2053 // Test select collapsing
2054 cx.update_editor(|editor, cx| {
2055 editor.move_page_down(&MovePageDown::default(), cx);
2056 editor.move_page_down(&MovePageDown::default(), cx);
2057 editor.move_page_down(&MovePageDown::default(), cx);
2058 });
2059 cx.assert_editor_state(
2060 &r#"
2061 one
2062 two
2063 three
2064 four
2065 five
2066 six
2067 seven
2068 eight
2069 nine
2070 ˇten
2071 ˇ"#
2072 .unindent(),
2073 );
2074}
2075
2076#[gpui::test]
2077async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2078 init_test(cx, |_| {});
2079 let mut cx = EditorTestContext::new(cx).await;
2080 cx.set_state("one «two threeˇ» four");
2081 cx.update_editor(|editor, cx| {
2082 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2083 assert_eq!(editor.text(cx), " four");
2084 });
2085}
2086
2087#[gpui::test]
2088fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2089 init_test(cx, |_| {});
2090
2091 let view = cx.add_window(|cx| {
2092 let buffer = MultiBuffer::build_simple("one two three four", cx);
2093 build_editor(buffer.clone(), cx)
2094 });
2095
2096 _ = view.update(cx, |view, cx| {
2097 view.change_selections(None, cx, |s| {
2098 s.select_display_ranges([
2099 // an empty selection - the preceding word fragment is deleted
2100 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2101 // characters selected - they are deleted
2102 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2103 ])
2104 });
2105 view.delete_to_previous_word_start(&DeleteToPreviousWordStart, cx);
2106 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2107 });
2108
2109 _ = view.update(cx, |view, cx| {
2110 view.change_selections(None, cx, |s| {
2111 s.select_display_ranges([
2112 // an empty selection - the following word fragment is deleted
2113 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2114 // characters selected - they are deleted
2115 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2116 ])
2117 });
2118 view.delete_to_next_word_end(&DeleteToNextWordEnd, cx);
2119 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2120 });
2121}
2122
2123#[gpui::test]
2124fn test_newline(cx: &mut TestAppContext) {
2125 init_test(cx, |_| {});
2126
2127 let view = cx.add_window(|cx| {
2128 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2129 build_editor(buffer.clone(), cx)
2130 });
2131
2132 _ = view.update(cx, |view, cx| {
2133 view.change_selections(None, cx, |s| {
2134 s.select_display_ranges([
2135 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2136 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2137 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2138 ])
2139 });
2140
2141 view.newline(&Newline, cx);
2142 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2143 });
2144}
2145
2146#[gpui::test]
2147fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2148 init_test(cx, |_| {});
2149
2150 let editor = cx.add_window(|cx| {
2151 let buffer = MultiBuffer::build_simple(
2152 "
2153 a
2154 b(
2155 X
2156 )
2157 c(
2158 X
2159 )
2160 "
2161 .unindent()
2162 .as_str(),
2163 cx,
2164 );
2165 let mut editor = build_editor(buffer.clone(), cx);
2166 editor.change_selections(None, cx, |s| {
2167 s.select_ranges([
2168 Point::new(2, 4)..Point::new(2, 5),
2169 Point::new(5, 4)..Point::new(5, 5),
2170 ])
2171 });
2172 editor
2173 });
2174
2175 _ = editor.update(cx, |editor, cx| {
2176 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2177 editor.buffer.update(cx, |buffer, cx| {
2178 buffer.edit(
2179 [
2180 (Point::new(1, 2)..Point::new(3, 0), ""),
2181 (Point::new(4, 2)..Point::new(6, 0), ""),
2182 ],
2183 None,
2184 cx,
2185 );
2186 assert_eq!(
2187 buffer.read(cx).text(),
2188 "
2189 a
2190 b()
2191 c()
2192 "
2193 .unindent()
2194 );
2195 });
2196 assert_eq!(
2197 editor.selections.ranges(cx),
2198 &[
2199 Point::new(1, 2)..Point::new(1, 2),
2200 Point::new(2, 2)..Point::new(2, 2),
2201 ],
2202 );
2203
2204 editor.newline(&Newline, cx);
2205 assert_eq!(
2206 editor.text(cx),
2207 "
2208 a
2209 b(
2210 )
2211 c(
2212 )
2213 "
2214 .unindent()
2215 );
2216
2217 // The selections are moved after the inserted newlines
2218 assert_eq!(
2219 editor.selections.ranges(cx),
2220 &[
2221 Point::new(2, 0)..Point::new(2, 0),
2222 Point::new(4, 0)..Point::new(4, 0),
2223 ],
2224 );
2225 });
2226}
2227
2228#[gpui::test]
2229async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2230 init_test(cx, |settings| {
2231 settings.defaults.tab_size = NonZeroU32::new(4)
2232 });
2233
2234 let language = Arc::new(
2235 Language::new(
2236 LanguageConfig::default(),
2237 Some(tree_sitter_rust::language()),
2238 )
2239 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2240 .unwrap(),
2241 );
2242
2243 let mut cx = EditorTestContext::new(cx).await;
2244 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2245 cx.set_state(indoc! {"
2246 const a: ˇA = (
2247 (ˇ
2248 «const_functionˇ»(ˇ),
2249 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2250 )ˇ
2251 ˇ);ˇ
2252 "});
2253
2254 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2255 cx.assert_editor_state(indoc! {"
2256 ˇ
2257 const a: A = (
2258 ˇ
2259 (
2260 ˇ
2261 ˇ
2262 const_function(),
2263 ˇ
2264 ˇ
2265 ˇ
2266 ˇ
2267 something_else,
2268 ˇ
2269 )
2270 ˇ
2271 ˇ
2272 );
2273 "});
2274}
2275
2276#[gpui::test]
2277async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2278 init_test(cx, |settings| {
2279 settings.defaults.tab_size = NonZeroU32::new(4)
2280 });
2281
2282 let language = Arc::new(
2283 Language::new(
2284 LanguageConfig::default(),
2285 Some(tree_sitter_rust::language()),
2286 )
2287 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2288 .unwrap(),
2289 );
2290
2291 let mut cx = EditorTestContext::new(cx).await;
2292 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2293 cx.set_state(indoc! {"
2294 const a: ˇA = (
2295 (ˇ
2296 «const_functionˇ»(ˇ),
2297 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2298 )ˇ
2299 ˇ);ˇ
2300 "});
2301
2302 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2303 cx.assert_editor_state(indoc! {"
2304 const a: A = (
2305 ˇ
2306 (
2307 ˇ
2308 const_function(),
2309 ˇ
2310 ˇ
2311 something_else,
2312 ˇ
2313 ˇ
2314 ˇ
2315 ˇ
2316 )
2317 ˇ
2318 );
2319 ˇ
2320 ˇ
2321 "});
2322}
2323
2324#[gpui::test]
2325async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2326 init_test(cx, |settings| {
2327 settings.defaults.tab_size = NonZeroU32::new(4)
2328 });
2329
2330 let language = Arc::new(Language::new(
2331 LanguageConfig {
2332 line_comments: vec!["//".into()],
2333 ..LanguageConfig::default()
2334 },
2335 None,
2336 ));
2337 {
2338 let mut cx = EditorTestContext::new(cx).await;
2339 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2340 cx.set_state(indoc! {"
2341 // Fooˇ
2342 "});
2343
2344 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2345 cx.assert_editor_state(indoc! {"
2346 // Foo
2347 //ˇ
2348 "});
2349 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2350 cx.set_state(indoc! {"
2351 ˇ// Foo
2352 "});
2353 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2354 cx.assert_editor_state(indoc! {"
2355
2356 ˇ// Foo
2357 "});
2358 }
2359 // Ensure that comment continuations can be disabled.
2360 update_test_language_settings(cx, |settings| {
2361 settings.defaults.extend_comment_on_newline = Some(false);
2362 });
2363 let mut cx = EditorTestContext::new(cx).await;
2364 cx.set_state(indoc! {"
2365 // Fooˇ
2366 "});
2367 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2368 cx.assert_editor_state(indoc! {"
2369 // Foo
2370 ˇ
2371 "});
2372}
2373
2374#[gpui::test]
2375fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2376 init_test(cx, |_| {});
2377
2378 let editor = cx.add_window(|cx| {
2379 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2380 let mut editor = build_editor(buffer.clone(), cx);
2381 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2382 editor
2383 });
2384
2385 _ = editor.update(cx, |editor, cx| {
2386 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2387 editor.buffer.update(cx, |buffer, cx| {
2388 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2389 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2390 });
2391 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2392
2393 editor.insert("Z", cx);
2394 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2395
2396 // The selections are moved after the inserted characters
2397 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2398 });
2399}
2400
2401#[gpui::test]
2402async fn test_tab(cx: &mut gpui::TestAppContext) {
2403 init_test(cx, |settings| {
2404 settings.defaults.tab_size = NonZeroU32::new(3)
2405 });
2406
2407 let mut cx = EditorTestContext::new(cx).await;
2408 cx.set_state(indoc! {"
2409 ˇabˇc
2410 ˇ🏀ˇ🏀ˇefg
2411 dˇ
2412 "});
2413 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2414 cx.assert_editor_state(indoc! {"
2415 ˇab ˇc
2416 ˇ🏀 ˇ🏀 ˇefg
2417 d ˇ
2418 "});
2419
2420 cx.set_state(indoc! {"
2421 a
2422 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2423 "});
2424 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2425 cx.assert_editor_state(indoc! {"
2426 a
2427 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2428 "});
2429}
2430
2431#[gpui::test]
2432async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2433 init_test(cx, |_| {});
2434
2435 let mut cx = EditorTestContext::new(cx).await;
2436 let language = Arc::new(
2437 Language::new(
2438 LanguageConfig::default(),
2439 Some(tree_sitter_rust::language()),
2440 )
2441 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2442 .unwrap(),
2443 );
2444 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2445
2446 // cursors that are already at the suggested indent level insert
2447 // a soft tab. cursors that are to the left of the suggested indent
2448 // auto-indent their line.
2449 cx.set_state(indoc! {"
2450 ˇ
2451 const a: B = (
2452 c(
2453 d(
2454 ˇ
2455 )
2456 ˇ
2457 ˇ )
2458 );
2459 "});
2460 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2461 cx.assert_editor_state(indoc! {"
2462 ˇ
2463 const a: B = (
2464 c(
2465 d(
2466 ˇ
2467 )
2468 ˇ
2469 ˇ)
2470 );
2471 "});
2472
2473 // handle auto-indent when there are multiple cursors on the same line
2474 cx.set_state(indoc! {"
2475 const a: B = (
2476 c(
2477 ˇ ˇ
2478 ˇ )
2479 );
2480 "});
2481 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2482 cx.assert_editor_state(indoc! {"
2483 const a: B = (
2484 c(
2485 ˇ
2486 ˇ)
2487 );
2488 "});
2489}
2490
2491#[gpui::test]
2492async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2493 init_test(cx, |settings| {
2494 settings.defaults.tab_size = NonZeroU32::new(4)
2495 });
2496
2497 let language = Arc::new(
2498 Language::new(
2499 LanguageConfig::default(),
2500 Some(tree_sitter_rust::language()),
2501 )
2502 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2503 .unwrap(),
2504 );
2505
2506 let mut cx = EditorTestContext::new(cx).await;
2507 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2508 cx.set_state(indoc! {"
2509 fn a() {
2510 if b {
2511 \t ˇc
2512 }
2513 }
2514 "});
2515
2516 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2517 cx.assert_editor_state(indoc! {"
2518 fn a() {
2519 if b {
2520 ˇc
2521 }
2522 }
2523 "});
2524}
2525
2526#[gpui::test]
2527async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2528 init_test(cx, |settings| {
2529 settings.defaults.tab_size = NonZeroU32::new(4);
2530 });
2531
2532 let mut cx = EditorTestContext::new(cx).await;
2533
2534 cx.set_state(indoc! {"
2535 «oneˇ» «twoˇ»
2536 three
2537 four
2538 "});
2539 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2540 cx.assert_editor_state(indoc! {"
2541 «oneˇ» «twoˇ»
2542 three
2543 four
2544 "});
2545
2546 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2547 cx.assert_editor_state(indoc! {"
2548 «oneˇ» «twoˇ»
2549 three
2550 four
2551 "});
2552
2553 // select across line ending
2554 cx.set_state(indoc! {"
2555 one two
2556 t«hree
2557 ˇ» four
2558 "});
2559 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2560 cx.assert_editor_state(indoc! {"
2561 one two
2562 t«hree
2563 ˇ» four
2564 "});
2565
2566 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2567 cx.assert_editor_state(indoc! {"
2568 one two
2569 t«hree
2570 ˇ» four
2571 "});
2572
2573 // Ensure that indenting/outdenting works when the cursor is at column 0.
2574 cx.set_state(indoc! {"
2575 one two
2576 ˇthree
2577 four
2578 "});
2579 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2580 cx.assert_editor_state(indoc! {"
2581 one two
2582 ˇthree
2583 four
2584 "});
2585
2586 cx.set_state(indoc! {"
2587 one two
2588 ˇ three
2589 four
2590 "});
2591 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2592 cx.assert_editor_state(indoc! {"
2593 one two
2594 ˇthree
2595 four
2596 "});
2597}
2598
2599#[gpui::test]
2600async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2601 init_test(cx, |settings| {
2602 settings.defaults.hard_tabs = Some(true);
2603 });
2604
2605 let mut cx = EditorTestContext::new(cx).await;
2606
2607 // select two ranges on one line
2608 cx.set_state(indoc! {"
2609 «oneˇ» «twoˇ»
2610 three
2611 four
2612 "});
2613 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2614 cx.assert_editor_state(indoc! {"
2615 \t«oneˇ» «twoˇ»
2616 three
2617 four
2618 "});
2619 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2620 cx.assert_editor_state(indoc! {"
2621 \t\t«oneˇ» «twoˇ»
2622 three
2623 four
2624 "});
2625 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2626 cx.assert_editor_state(indoc! {"
2627 \t«oneˇ» «twoˇ»
2628 three
2629 four
2630 "});
2631 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2632 cx.assert_editor_state(indoc! {"
2633 «oneˇ» «twoˇ»
2634 three
2635 four
2636 "});
2637
2638 // select across a line ending
2639 cx.set_state(indoc! {"
2640 one two
2641 t«hree
2642 ˇ»four
2643 "});
2644 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2645 cx.assert_editor_state(indoc! {"
2646 one two
2647 \tt«hree
2648 ˇ»four
2649 "});
2650 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2651 cx.assert_editor_state(indoc! {"
2652 one two
2653 \t\tt«hree
2654 ˇ»four
2655 "});
2656 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2657 cx.assert_editor_state(indoc! {"
2658 one two
2659 \tt«hree
2660 ˇ»four
2661 "});
2662 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2663 cx.assert_editor_state(indoc! {"
2664 one two
2665 t«hree
2666 ˇ»four
2667 "});
2668
2669 // Ensure that indenting/outdenting works when the cursor is at column 0.
2670 cx.set_state(indoc! {"
2671 one two
2672 ˇthree
2673 four
2674 "});
2675 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2676 cx.assert_editor_state(indoc! {"
2677 one two
2678 ˇthree
2679 four
2680 "});
2681 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2682 cx.assert_editor_state(indoc! {"
2683 one two
2684 \tˇthree
2685 four
2686 "});
2687 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2688 cx.assert_editor_state(indoc! {"
2689 one two
2690 ˇthree
2691 four
2692 "});
2693}
2694
2695#[gpui::test]
2696fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2697 init_test(cx, |settings| {
2698 settings.languages.extend([
2699 (
2700 "TOML".into(),
2701 LanguageSettingsContent {
2702 tab_size: NonZeroU32::new(2),
2703 ..Default::default()
2704 },
2705 ),
2706 (
2707 "Rust".into(),
2708 LanguageSettingsContent {
2709 tab_size: NonZeroU32::new(4),
2710 ..Default::default()
2711 },
2712 ),
2713 ]);
2714 });
2715
2716 let toml_language = Arc::new(Language::new(
2717 LanguageConfig {
2718 name: "TOML".into(),
2719 ..Default::default()
2720 },
2721 None,
2722 ));
2723 let rust_language = Arc::new(Language::new(
2724 LanguageConfig {
2725 name: "Rust".into(),
2726 ..Default::default()
2727 },
2728 None,
2729 ));
2730
2731 let toml_buffer =
2732 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2733 let rust_buffer = cx.new_model(|cx| {
2734 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2735 });
2736 let multibuffer = cx.new_model(|cx| {
2737 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
2738 multibuffer.push_excerpts(
2739 toml_buffer.clone(),
2740 [ExcerptRange {
2741 context: Point::new(0, 0)..Point::new(2, 0),
2742 primary: None,
2743 }],
2744 cx,
2745 );
2746 multibuffer.push_excerpts(
2747 rust_buffer.clone(),
2748 [ExcerptRange {
2749 context: Point::new(0, 0)..Point::new(1, 0),
2750 primary: None,
2751 }],
2752 cx,
2753 );
2754 multibuffer
2755 });
2756
2757 cx.add_window(|cx| {
2758 let mut editor = build_editor(multibuffer, cx);
2759
2760 assert_eq!(
2761 editor.text(cx),
2762 indoc! {"
2763 a = 1
2764 b = 2
2765
2766 const c: usize = 3;
2767 "}
2768 );
2769
2770 select_ranges(
2771 &mut editor,
2772 indoc! {"
2773 «aˇ» = 1
2774 b = 2
2775
2776 «const c:ˇ» usize = 3;
2777 "},
2778 cx,
2779 );
2780
2781 editor.tab(&Tab, cx);
2782 assert_text_with_selections(
2783 &mut editor,
2784 indoc! {"
2785 «aˇ» = 1
2786 b = 2
2787
2788 «const c:ˇ» usize = 3;
2789 "},
2790 cx,
2791 );
2792 editor.tab_prev(&TabPrev, cx);
2793 assert_text_with_selections(
2794 &mut editor,
2795 indoc! {"
2796 «aˇ» = 1
2797 b = 2
2798
2799 «const c:ˇ» usize = 3;
2800 "},
2801 cx,
2802 );
2803
2804 editor
2805 });
2806}
2807
2808#[gpui::test]
2809async fn test_backspace(cx: &mut gpui::TestAppContext) {
2810 init_test(cx, |_| {});
2811
2812 let mut cx = EditorTestContext::new(cx).await;
2813
2814 // Basic backspace
2815 cx.set_state(indoc! {"
2816 onˇe two three
2817 fou«rˇ» five six
2818 seven «ˇeight nine
2819 »ten
2820 "});
2821 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2822 cx.assert_editor_state(indoc! {"
2823 oˇe two three
2824 fouˇ five six
2825 seven ˇten
2826 "});
2827
2828 // Test backspace inside and around indents
2829 cx.set_state(indoc! {"
2830 zero
2831 ˇone
2832 ˇtwo
2833 ˇ ˇ ˇ three
2834 ˇ ˇ four
2835 "});
2836 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2837 cx.assert_editor_state(indoc! {"
2838 zero
2839 ˇone
2840 ˇtwo
2841 ˇ threeˇ four
2842 "});
2843
2844 // Test backspace with line_mode set to true
2845 cx.update_editor(|e, _| e.selections.line_mode = true);
2846 cx.set_state(indoc! {"
2847 The ˇquick ˇbrown
2848 fox jumps over
2849 the lazy dog
2850 ˇThe qu«ick bˇ»rown"});
2851 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2852 cx.assert_editor_state(indoc! {"
2853 ˇfox jumps over
2854 the lazy dogˇ"});
2855}
2856
2857#[gpui::test]
2858async fn test_delete(cx: &mut gpui::TestAppContext) {
2859 init_test(cx, |_| {});
2860
2861 let mut cx = EditorTestContext::new(cx).await;
2862 cx.set_state(indoc! {"
2863 onˇe two three
2864 fou«rˇ» five six
2865 seven «ˇeight nine
2866 »ten
2867 "});
2868 cx.update_editor(|e, cx| e.delete(&Delete, cx));
2869 cx.assert_editor_state(indoc! {"
2870 onˇ two three
2871 fouˇ five six
2872 seven ˇten
2873 "});
2874
2875 // Test backspace with line_mode set to true
2876 cx.update_editor(|e, _| e.selections.line_mode = true);
2877 cx.set_state(indoc! {"
2878 The ˇquick ˇbrown
2879 fox «ˇjum»ps over
2880 the lazy dog
2881 ˇThe qu«ick bˇ»rown"});
2882 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2883 cx.assert_editor_state("ˇthe lazy dogˇ");
2884}
2885
2886#[gpui::test]
2887fn test_delete_line(cx: &mut TestAppContext) {
2888 init_test(cx, |_| {});
2889
2890 let view = cx.add_window(|cx| {
2891 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2892 build_editor(buffer, cx)
2893 });
2894 _ = view.update(cx, |view, cx| {
2895 view.change_selections(None, cx, |s| {
2896 s.select_display_ranges([
2897 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
2898 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
2899 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
2900 ])
2901 });
2902 view.delete_line(&DeleteLine, cx);
2903 assert_eq!(view.display_text(cx), "ghi");
2904 assert_eq!(
2905 view.selections.display_ranges(cx),
2906 vec![
2907 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
2908 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
2909 ]
2910 );
2911 });
2912
2913 let view = cx.add_window(|cx| {
2914 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2915 build_editor(buffer, cx)
2916 });
2917 _ = view.update(cx, |view, cx| {
2918 view.change_selections(None, cx, |s| {
2919 s.select_display_ranges([
2920 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
2921 ])
2922 });
2923 view.delete_line(&DeleteLine, cx);
2924 assert_eq!(view.display_text(cx), "ghi\n");
2925 assert_eq!(
2926 view.selections.display_ranges(cx),
2927 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
2928 );
2929 });
2930}
2931
2932#[gpui::test]
2933fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
2934 init_test(cx, |_| {});
2935
2936 cx.add_window(|cx| {
2937 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
2938 let mut editor = build_editor(buffer.clone(), cx);
2939 let buffer = buffer.read(cx).as_singleton().unwrap();
2940
2941 assert_eq!(
2942 editor.selections.ranges::<Point>(cx),
2943 &[Point::new(0, 0)..Point::new(0, 0)]
2944 );
2945
2946 // When on single line, replace newline at end by space
2947 editor.join_lines(&JoinLines, cx);
2948 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
2949 assert_eq!(
2950 editor.selections.ranges::<Point>(cx),
2951 &[Point::new(0, 3)..Point::new(0, 3)]
2952 );
2953
2954 // When multiple lines are selected, remove newlines that are spanned by the selection
2955 editor.change_selections(None, cx, |s| {
2956 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
2957 });
2958 editor.join_lines(&JoinLines, cx);
2959 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
2960 assert_eq!(
2961 editor.selections.ranges::<Point>(cx),
2962 &[Point::new(0, 11)..Point::new(0, 11)]
2963 );
2964
2965 // Undo should be transactional
2966 editor.undo(&Undo, cx);
2967 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
2968 assert_eq!(
2969 editor.selections.ranges::<Point>(cx),
2970 &[Point::new(0, 5)..Point::new(2, 2)]
2971 );
2972
2973 // When joining an empty line don't insert a space
2974 editor.change_selections(None, cx, |s| {
2975 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
2976 });
2977 editor.join_lines(&JoinLines, cx);
2978 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
2979 assert_eq!(
2980 editor.selections.ranges::<Point>(cx),
2981 [Point::new(2, 3)..Point::new(2, 3)]
2982 );
2983
2984 // We can remove trailing newlines
2985 editor.join_lines(&JoinLines, cx);
2986 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
2987 assert_eq!(
2988 editor.selections.ranges::<Point>(cx),
2989 [Point::new(2, 3)..Point::new(2, 3)]
2990 );
2991
2992 // We don't blow up on the last line
2993 editor.join_lines(&JoinLines, cx);
2994 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
2995 assert_eq!(
2996 editor.selections.ranges::<Point>(cx),
2997 [Point::new(2, 3)..Point::new(2, 3)]
2998 );
2999
3000 // reset to test indentation
3001 editor.buffer.update(cx, |buffer, cx| {
3002 buffer.edit(
3003 [
3004 (Point::new(1, 0)..Point::new(1, 2), " "),
3005 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3006 ],
3007 None,
3008 cx,
3009 )
3010 });
3011
3012 // We remove any leading spaces
3013 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3014 editor.change_selections(None, cx, |s| {
3015 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3016 });
3017 editor.join_lines(&JoinLines, cx);
3018 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3019
3020 // We don't insert a space for a line containing only spaces
3021 editor.join_lines(&JoinLines, cx);
3022 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3023
3024 // We ignore any leading tabs
3025 editor.join_lines(&JoinLines, cx);
3026 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3027
3028 editor
3029 });
3030}
3031
3032#[gpui::test]
3033fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3034 init_test(cx, |_| {});
3035
3036 cx.add_window(|cx| {
3037 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3038 let mut editor = build_editor(buffer.clone(), cx);
3039 let buffer = buffer.read(cx).as_singleton().unwrap();
3040
3041 editor.change_selections(None, cx, |s| {
3042 s.select_ranges([
3043 Point::new(0, 2)..Point::new(1, 1),
3044 Point::new(1, 2)..Point::new(1, 2),
3045 Point::new(3, 1)..Point::new(3, 2),
3046 ])
3047 });
3048
3049 editor.join_lines(&JoinLines, cx);
3050 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3051
3052 assert_eq!(
3053 editor.selections.ranges::<Point>(cx),
3054 [
3055 Point::new(0, 7)..Point::new(0, 7),
3056 Point::new(1, 3)..Point::new(1, 3)
3057 ]
3058 );
3059 editor
3060 });
3061}
3062
3063#[gpui::test]
3064async fn test_join_lines_with_git_diff_base(
3065 executor: BackgroundExecutor,
3066 cx: &mut gpui::TestAppContext,
3067) {
3068 init_test(cx, |_| {});
3069
3070 let mut cx = EditorTestContext::new(cx).await;
3071
3072 let diff_base = r#"
3073 Line 0
3074 Line 1
3075 Line 2
3076 Line 3
3077 "#
3078 .unindent();
3079
3080 cx.set_state(
3081 &r#"
3082 ˇLine 0
3083 Line 1
3084 Line 2
3085 Line 3
3086 "#
3087 .unindent(),
3088 );
3089
3090 cx.set_diff_base(Some(&diff_base));
3091 executor.run_until_parked();
3092
3093 // Join lines
3094 cx.update_editor(|editor, cx| {
3095 editor.join_lines(&JoinLines, cx);
3096 });
3097 executor.run_until_parked();
3098
3099 cx.assert_editor_state(
3100 &r#"
3101 Line 0ˇ Line 1
3102 Line 2
3103 Line 3
3104 "#
3105 .unindent(),
3106 );
3107 // Join again
3108 cx.update_editor(|editor, cx| {
3109 editor.join_lines(&JoinLines, cx);
3110 });
3111 executor.run_until_parked();
3112
3113 cx.assert_editor_state(
3114 &r#"
3115 Line 0 Line 1ˇ Line 2
3116 Line 3
3117 "#
3118 .unindent(),
3119 );
3120}
3121
3122#[gpui::test]
3123async fn test_custom_newlines_cause_no_false_positive_diffs(
3124 executor: BackgroundExecutor,
3125 cx: &mut gpui::TestAppContext,
3126) {
3127 init_test(cx, |_| {});
3128 let mut cx = EditorTestContext::new(cx).await;
3129 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3130 cx.set_diff_base(Some("Line 0\r\nLine 1\r\nLine 2\r\nLine 3"));
3131 executor.run_until_parked();
3132
3133 cx.update_editor(|editor, cx| {
3134 assert_eq!(
3135 editor
3136 .buffer()
3137 .read(cx)
3138 .snapshot(cx)
3139 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
3140 .collect::<Vec<_>>(),
3141 Vec::new(),
3142 "Should not have any diffs for files with custom newlines"
3143 );
3144 });
3145}
3146
3147#[gpui::test]
3148async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3149 init_test(cx, |_| {});
3150
3151 let mut cx = EditorTestContext::new(cx).await;
3152
3153 // Test sort_lines_case_insensitive()
3154 cx.set_state(indoc! {"
3155 «z
3156 y
3157 x
3158 Z
3159 Y
3160 Xˇ»
3161 "});
3162 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3163 cx.assert_editor_state(indoc! {"
3164 «x
3165 X
3166 y
3167 Y
3168 z
3169 Zˇ»
3170 "});
3171
3172 // Test reverse_lines()
3173 cx.set_state(indoc! {"
3174 «5
3175 4
3176 3
3177 2
3178 1ˇ»
3179 "});
3180 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3181 cx.assert_editor_state(indoc! {"
3182 «1
3183 2
3184 3
3185 4
3186 5ˇ»
3187 "});
3188
3189 // Skip testing shuffle_line()
3190
3191 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3192 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3193
3194 // Don't manipulate when cursor is on single line, but expand the selection
3195 cx.set_state(indoc! {"
3196 ddˇdd
3197 ccc
3198 bb
3199 a
3200 "});
3201 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3202 cx.assert_editor_state(indoc! {"
3203 «ddddˇ»
3204 ccc
3205 bb
3206 a
3207 "});
3208
3209 // Basic manipulate case
3210 // Start selection moves to column 0
3211 // End of selection shrinks to fit shorter line
3212 cx.set_state(indoc! {"
3213 dd«d
3214 ccc
3215 bb
3216 aaaaaˇ»
3217 "});
3218 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3219 cx.assert_editor_state(indoc! {"
3220 «aaaaa
3221 bb
3222 ccc
3223 dddˇ»
3224 "});
3225
3226 // Manipulate case with newlines
3227 cx.set_state(indoc! {"
3228 dd«d
3229 ccc
3230
3231 bb
3232 aaaaa
3233
3234 ˇ»
3235 "});
3236 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3237 cx.assert_editor_state(indoc! {"
3238 «
3239
3240 aaaaa
3241 bb
3242 ccc
3243 dddˇ»
3244
3245 "});
3246
3247 // Adding new line
3248 cx.set_state(indoc! {"
3249 aa«a
3250 bbˇ»b
3251 "});
3252 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3253 cx.assert_editor_state(indoc! {"
3254 «aaa
3255 bbb
3256 added_lineˇ»
3257 "});
3258
3259 // Removing line
3260 cx.set_state(indoc! {"
3261 aa«a
3262 bbbˇ»
3263 "});
3264 cx.update_editor(|e, cx| {
3265 e.manipulate_lines(cx, |lines| {
3266 lines.pop();
3267 })
3268 });
3269 cx.assert_editor_state(indoc! {"
3270 «aaaˇ»
3271 "});
3272
3273 // Removing all lines
3274 cx.set_state(indoc! {"
3275 aa«a
3276 bbbˇ»
3277 "});
3278 cx.update_editor(|e, cx| {
3279 e.manipulate_lines(cx, |lines| {
3280 lines.drain(..);
3281 })
3282 });
3283 cx.assert_editor_state(indoc! {"
3284 ˇ
3285 "});
3286}
3287
3288#[gpui::test]
3289async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3290 init_test(cx, |_| {});
3291
3292 let mut cx = EditorTestContext::new(cx).await;
3293
3294 // Consider continuous selection as single selection
3295 cx.set_state(indoc! {"
3296 Aaa«aa
3297 cˇ»c«c
3298 bb
3299 aaaˇ»aa
3300 "});
3301 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3302 cx.assert_editor_state(indoc! {"
3303 «Aaaaa
3304 ccc
3305 bb
3306 aaaaaˇ»
3307 "});
3308
3309 cx.set_state(indoc! {"
3310 Aaa«aa
3311 cˇ»c«c
3312 bb
3313 aaaˇ»aa
3314 "});
3315 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3316 cx.assert_editor_state(indoc! {"
3317 «Aaaaa
3318 ccc
3319 bbˇ»
3320 "});
3321
3322 // Consider non continuous selection as distinct dedup operations
3323 cx.set_state(indoc! {"
3324 «aaaaa
3325 bb
3326 aaaaa
3327 aaaaaˇ»
3328
3329 aaa«aaˇ»
3330 "});
3331 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3332 cx.assert_editor_state(indoc! {"
3333 «aaaaa
3334 bbˇ»
3335
3336 «aaaaaˇ»
3337 "});
3338}
3339
3340#[gpui::test]
3341async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3342 init_test(cx, |_| {});
3343
3344 let mut cx = EditorTestContext::new(cx).await;
3345
3346 cx.set_state(indoc! {"
3347 «Aaa
3348 aAa
3349 Aaaˇ»
3350 "});
3351 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3352 cx.assert_editor_state(indoc! {"
3353 «Aaa
3354 aAaˇ»
3355 "});
3356
3357 cx.set_state(indoc! {"
3358 «Aaa
3359 aAa
3360 aaAˇ»
3361 "});
3362 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3363 cx.assert_editor_state(indoc! {"
3364 «Aaaˇ»
3365 "});
3366}
3367
3368#[gpui::test]
3369async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3370 init_test(cx, |_| {});
3371
3372 let mut cx = EditorTestContext::new(cx).await;
3373
3374 // Manipulate with multiple selections on a single line
3375 cx.set_state(indoc! {"
3376 dd«dd
3377 cˇ»c«c
3378 bb
3379 aaaˇ»aa
3380 "});
3381 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3382 cx.assert_editor_state(indoc! {"
3383 «aaaaa
3384 bb
3385 ccc
3386 ddddˇ»
3387 "});
3388
3389 // Manipulate with multiple disjoin selections
3390 cx.set_state(indoc! {"
3391 5«
3392 4
3393 3
3394 2
3395 1ˇ»
3396
3397 dd«dd
3398 ccc
3399 bb
3400 aaaˇ»aa
3401 "});
3402 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3403 cx.assert_editor_state(indoc! {"
3404 «1
3405 2
3406 3
3407 4
3408 5ˇ»
3409
3410 «aaaaa
3411 bb
3412 ccc
3413 ddddˇ»
3414 "});
3415
3416 // Adding lines on each selection
3417 cx.set_state(indoc! {"
3418 2«
3419 1ˇ»
3420
3421 bb«bb
3422 aaaˇ»aa
3423 "});
3424 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3425 cx.assert_editor_state(indoc! {"
3426 «2
3427 1
3428 added lineˇ»
3429
3430 «bbbb
3431 aaaaa
3432 added lineˇ»
3433 "});
3434
3435 // Removing lines on each selection
3436 cx.set_state(indoc! {"
3437 2«
3438 1ˇ»
3439
3440 bb«bb
3441 aaaˇ»aa
3442 "});
3443 cx.update_editor(|e, cx| {
3444 e.manipulate_lines(cx, |lines| {
3445 lines.pop();
3446 })
3447 });
3448 cx.assert_editor_state(indoc! {"
3449 «2ˇ»
3450
3451 «bbbbˇ»
3452 "});
3453}
3454
3455#[gpui::test]
3456async fn test_manipulate_text(cx: &mut TestAppContext) {
3457 init_test(cx, |_| {});
3458
3459 let mut cx = EditorTestContext::new(cx).await;
3460
3461 // Test convert_to_upper_case()
3462 cx.set_state(indoc! {"
3463 «hello worldˇ»
3464 "});
3465 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3466 cx.assert_editor_state(indoc! {"
3467 «HELLO WORLDˇ»
3468 "});
3469
3470 // Test convert_to_lower_case()
3471 cx.set_state(indoc! {"
3472 «HELLO WORLDˇ»
3473 "});
3474 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3475 cx.assert_editor_state(indoc! {"
3476 «hello worldˇ»
3477 "});
3478
3479 // Test multiple line, single selection case
3480 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3481 cx.set_state(indoc! {"
3482 «The quick brown
3483 fox jumps over
3484 the lazy dogˇ»
3485 "});
3486 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3487 cx.assert_editor_state(indoc! {"
3488 «The Quick Brown
3489 Fox Jumps Over
3490 The Lazy Dogˇ»
3491 "});
3492
3493 // Test multiple line, single selection case
3494 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3495 cx.set_state(indoc! {"
3496 «The quick brown
3497 fox jumps over
3498 the lazy dogˇ»
3499 "});
3500 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3501 cx.assert_editor_state(indoc! {"
3502 «TheQuickBrown
3503 FoxJumpsOver
3504 TheLazyDogˇ»
3505 "});
3506
3507 // From here on out, test more complex cases of manipulate_text()
3508
3509 // Test no selection case - should affect words cursors are in
3510 // Cursor at beginning, middle, and end of word
3511 cx.set_state(indoc! {"
3512 ˇhello big beauˇtiful worldˇ
3513 "});
3514 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3515 cx.assert_editor_state(indoc! {"
3516 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3517 "});
3518
3519 // Test multiple selections on a single line and across multiple lines
3520 cx.set_state(indoc! {"
3521 «Theˇ» quick «brown
3522 foxˇ» jumps «overˇ»
3523 the «lazyˇ» dog
3524 "});
3525 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3526 cx.assert_editor_state(indoc! {"
3527 «THEˇ» quick «BROWN
3528 FOXˇ» jumps «OVERˇ»
3529 the «LAZYˇ» dog
3530 "});
3531
3532 // Test case where text length grows
3533 cx.set_state(indoc! {"
3534 «tschüߡ»
3535 "});
3536 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3537 cx.assert_editor_state(indoc! {"
3538 «TSCHÜSSˇ»
3539 "});
3540
3541 // Test to make sure we don't crash when text shrinks
3542 cx.set_state(indoc! {"
3543 aaa_bbbˇ
3544 "});
3545 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3546 cx.assert_editor_state(indoc! {"
3547 «aaaBbbˇ»
3548 "});
3549
3550 // Test to make sure we all aware of the fact that each word can grow and shrink
3551 // Final selections should be aware of this fact
3552 cx.set_state(indoc! {"
3553 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3554 "});
3555 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3556 cx.assert_editor_state(indoc! {"
3557 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3558 "});
3559
3560 cx.set_state(indoc! {"
3561 «hElLo, WoRld!ˇ»
3562 "});
3563 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3564 cx.assert_editor_state(indoc! {"
3565 «HeLlO, wOrLD!ˇ»
3566 "});
3567}
3568
3569#[gpui::test]
3570fn test_duplicate_line(cx: &mut TestAppContext) {
3571 init_test(cx, |_| {});
3572
3573 let view = cx.add_window(|cx| {
3574 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3575 build_editor(buffer, cx)
3576 });
3577 _ = view.update(cx, |view, cx| {
3578 view.change_selections(None, cx, |s| {
3579 s.select_display_ranges([
3580 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3581 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3582 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3583 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3584 ])
3585 });
3586 view.duplicate_line_down(&DuplicateLineDown, cx);
3587 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3588 assert_eq!(
3589 view.selections.display_ranges(cx),
3590 vec![
3591 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3592 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3593 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3594 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3595 ]
3596 );
3597 });
3598
3599 let view = cx.add_window(|cx| {
3600 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3601 build_editor(buffer, cx)
3602 });
3603 _ = view.update(cx, |view, cx| {
3604 view.change_selections(None, cx, |s| {
3605 s.select_display_ranges([
3606 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3607 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3608 ])
3609 });
3610 view.duplicate_line_down(&DuplicateLineDown, cx);
3611 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3612 assert_eq!(
3613 view.selections.display_ranges(cx),
3614 vec![
3615 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3616 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3617 ]
3618 );
3619 });
3620
3621 // With `move_upwards` the selections stay in place, except for
3622 // the lines inserted above them
3623 let view = cx.add_window(|cx| {
3624 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3625 build_editor(buffer, cx)
3626 });
3627 _ = view.update(cx, |view, cx| {
3628 view.change_selections(None, cx, |s| {
3629 s.select_display_ranges([
3630 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3631 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3632 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3633 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3634 ])
3635 });
3636 view.duplicate_line_up(&DuplicateLineUp, cx);
3637 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3638 assert_eq!(
3639 view.selections.display_ranges(cx),
3640 vec![
3641 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3642 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3643 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3644 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3645 ]
3646 );
3647 });
3648
3649 let view = cx.add_window(|cx| {
3650 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3651 build_editor(buffer, cx)
3652 });
3653 _ = view.update(cx, |view, cx| {
3654 view.change_selections(None, cx, |s| {
3655 s.select_display_ranges([
3656 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3657 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3658 ])
3659 });
3660 view.duplicate_line_up(&DuplicateLineUp, cx);
3661 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3662 assert_eq!(
3663 view.selections.display_ranges(cx),
3664 vec![
3665 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3666 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3667 ]
3668 );
3669 });
3670}
3671
3672#[gpui::test]
3673fn test_move_line_up_down(cx: &mut TestAppContext) {
3674 init_test(cx, |_| {});
3675
3676 let view = cx.add_window(|cx| {
3677 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3678 build_editor(buffer, cx)
3679 });
3680 _ = view.update(cx, |view, cx| {
3681 view.fold_ranges(
3682 vec![
3683 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3684 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3685 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3686 ],
3687 true,
3688 cx,
3689 );
3690 view.change_selections(None, cx, |s| {
3691 s.select_display_ranges([
3692 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3693 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3694 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3695 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3696 ])
3697 });
3698 assert_eq!(
3699 view.display_text(cx),
3700 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3701 );
3702
3703 view.move_line_up(&MoveLineUp, cx);
3704 assert_eq!(
3705 view.display_text(cx),
3706 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3707 );
3708 assert_eq!(
3709 view.selections.display_ranges(cx),
3710 vec![
3711 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3712 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3713 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3714 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3715 ]
3716 );
3717 });
3718
3719 _ = view.update(cx, |view, cx| {
3720 view.move_line_down(&MoveLineDown, cx);
3721 assert_eq!(
3722 view.display_text(cx),
3723 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3724 );
3725 assert_eq!(
3726 view.selections.display_ranges(cx),
3727 vec![
3728 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3729 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3730 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3731 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3732 ]
3733 );
3734 });
3735
3736 _ = view.update(cx, |view, cx| {
3737 view.move_line_down(&MoveLineDown, cx);
3738 assert_eq!(
3739 view.display_text(cx),
3740 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3741 );
3742 assert_eq!(
3743 view.selections.display_ranges(cx),
3744 vec![
3745 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3746 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3747 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3748 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3749 ]
3750 );
3751 });
3752
3753 _ = view.update(cx, |view, cx| {
3754 view.move_line_up(&MoveLineUp, cx);
3755 assert_eq!(
3756 view.display_text(cx),
3757 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3758 );
3759 assert_eq!(
3760 view.selections.display_ranges(cx),
3761 vec![
3762 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3763 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3764 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3765 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3766 ]
3767 );
3768 });
3769}
3770
3771#[gpui::test]
3772fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3773 init_test(cx, |_| {});
3774
3775 let editor = cx.add_window(|cx| {
3776 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3777 build_editor(buffer, cx)
3778 });
3779 _ = editor.update(cx, |editor, cx| {
3780 let snapshot = editor.buffer.read(cx).snapshot(cx);
3781 editor.insert_blocks(
3782 [BlockProperties {
3783 style: BlockStyle::Fixed,
3784 position: snapshot.anchor_after(Point::new(2, 0)),
3785 disposition: BlockDisposition::Below,
3786 height: 1,
3787 render: Box::new(|_| div().into_any()),
3788 priority: 0,
3789 }],
3790 Some(Autoscroll::fit()),
3791 cx,
3792 );
3793 editor.change_selections(None, cx, |s| {
3794 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
3795 });
3796 editor.move_line_down(&MoveLineDown, cx);
3797 });
3798}
3799
3800#[gpui::test]
3801fn test_transpose(cx: &mut TestAppContext) {
3802 init_test(cx, |_| {});
3803
3804 _ = cx.add_window(|cx| {
3805 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
3806 editor.set_style(EditorStyle::default(), cx);
3807 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
3808 editor.transpose(&Default::default(), cx);
3809 assert_eq!(editor.text(cx), "bac");
3810 assert_eq!(editor.selections.ranges(cx), [2..2]);
3811
3812 editor.transpose(&Default::default(), cx);
3813 assert_eq!(editor.text(cx), "bca");
3814 assert_eq!(editor.selections.ranges(cx), [3..3]);
3815
3816 editor.transpose(&Default::default(), cx);
3817 assert_eq!(editor.text(cx), "bac");
3818 assert_eq!(editor.selections.ranges(cx), [3..3]);
3819
3820 editor
3821 });
3822
3823 _ = cx.add_window(|cx| {
3824 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3825 editor.set_style(EditorStyle::default(), cx);
3826 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
3827 editor.transpose(&Default::default(), cx);
3828 assert_eq!(editor.text(cx), "acb\nde");
3829 assert_eq!(editor.selections.ranges(cx), [3..3]);
3830
3831 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3832 editor.transpose(&Default::default(), cx);
3833 assert_eq!(editor.text(cx), "acbd\ne");
3834 assert_eq!(editor.selections.ranges(cx), [5..5]);
3835
3836 editor.transpose(&Default::default(), cx);
3837 assert_eq!(editor.text(cx), "acbde\n");
3838 assert_eq!(editor.selections.ranges(cx), [6..6]);
3839
3840 editor.transpose(&Default::default(), cx);
3841 assert_eq!(editor.text(cx), "acbd\ne");
3842 assert_eq!(editor.selections.ranges(cx), [6..6]);
3843
3844 editor
3845 });
3846
3847 _ = cx.add_window(|cx| {
3848 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3849 editor.set_style(EditorStyle::default(), cx);
3850 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
3851 editor.transpose(&Default::default(), cx);
3852 assert_eq!(editor.text(cx), "bacd\ne");
3853 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
3854
3855 editor.transpose(&Default::default(), cx);
3856 assert_eq!(editor.text(cx), "bcade\n");
3857 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
3858
3859 editor.transpose(&Default::default(), cx);
3860 assert_eq!(editor.text(cx), "bcda\ne");
3861 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3862
3863 editor.transpose(&Default::default(), cx);
3864 assert_eq!(editor.text(cx), "bcade\n");
3865 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3866
3867 editor.transpose(&Default::default(), cx);
3868 assert_eq!(editor.text(cx), "bcaed\n");
3869 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
3870
3871 editor
3872 });
3873
3874 _ = cx.add_window(|cx| {
3875 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
3876 editor.set_style(EditorStyle::default(), cx);
3877 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3878 editor.transpose(&Default::default(), cx);
3879 assert_eq!(editor.text(cx), "🏀🍐✋");
3880 assert_eq!(editor.selections.ranges(cx), [8..8]);
3881
3882 editor.transpose(&Default::default(), cx);
3883 assert_eq!(editor.text(cx), "🏀✋🍐");
3884 assert_eq!(editor.selections.ranges(cx), [11..11]);
3885
3886 editor.transpose(&Default::default(), cx);
3887 assert_eq!(editor.text(cx), "🏀🍐✋");
3888 assert_eq!(editor.selections.ranges(cx), [11..11]);
3889
3890 editor
3891 });
3892}
3893
3894#[gpui::test]
3895async fn test_clipboard(cx: &mut gpui::TestAppContext) {
3896 init_test(cx, |_| {});
3897
3898 let mut cx = EditorTestContext::new(cx).await;
3899
3900 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
3901 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3902 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
3903
3904 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
3905 cx.set_state("two ˇfour ˇsix ˇ");
3906 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3907 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
3908
3909 // Paste again but with only two cursors. Since the number of cursors doesn't
3910 // match the number of slices in the clipboard, the entire clipboard text
3911 // is pasted at each cursor.
3912 cx.set_state("ˇtwo one✅ four three six five ˇ");
3913 cx.update_editor(|e, cx| {
3914 e.handle_input("( ", cx);
3915 e.paste(&Paste, cx);
3916 e.handle_input(") ", cx);
3917 });
3918 cx.assert_editor_state(
3919 &([
3920 "( one✅ ",
3921 "three ",
3922 "five ) ˇtwo one✅ four three six five ( one✅ ",
3923 "three ",
3924 "five ) ˇ",
3925 ]
3926 .join("\n")),
3927 );
3928
3929 // Cut with three selections, one of which is full-line.
3930 cx.set_state(indoc! {"
3931 1«2ˇ»3
3932 4ˇ567
3933 «8ˇ»9"});
3934 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3935 cx.assert_editor_state(indoc! {"
3936 1ˇ3
3937 ˇ9"});
3938
3939 // Paste with three selections, noticing how the copied selection that was full-line
3940 // gets inserted before the second cursor.
3941 cx.set_state(indoc! {"
3942 1ˇ3
3943 9ˇ
3944 «oˇ»ne"});
3945 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3946 cx.assert_editor_state(indoc! {"
3947 12ˇ3
3948 4567
3949 9ˇ
3950 8ˇne"});
3951
3952 // Copy with a single cursor only, which writes the whole line into the clipboard.
3953 cx.set_state(indoc! {"
3954 The quick brown
3955 fox juˇmps over
3956 the lazy dog"});
3957 cx.update_editor(|e, cx| e.copy(&Copy, cx));
3958 assert_eq!(
3959 cx.read_from_clipboard().map(|item| item.text().to_owned()),
3960 Some("fox jumps over\n".to_owned())
3961 );
3962
3963 // Paste with three selections, noticing how the copied full-line selection is inserted
3964 // before the empty selections but replaces the selection that is non-empty.
3965 cx.set_state(indoc! {"
3966 Tˇhe quick brown
3967 «foˇ»x jumps over
3968 tˇhe lazy dog"});
3969 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3970 cx.assert_editor_state(indoc! {"
3971 fox jumps over
3972 Tˇhe quick brown
3973 fox jumps over
3974 ˇx jumps over
3975 fox jumps over
3976 tˇhe lazy dog"});
3977}
3978
3979#[gpui::test]
3980async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
3981 init_test(cx, |_| {});
3982
3983 let mut cx = EditorTestContext::new(cx).await;
3984 let language = Arc::new(Language::new(
3985 LanguageConfig::default(),
3986 Some(tree_sitter_rust::language()),
3987 ));
3988 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
3989
3990 // Cut an indented block, without the leading whitespace.
3991 cx.set_state(indoc! {"
3992 const a: B = (
3993 c(),
3994 «d(
3995 e,
3996 f
3997 )ˇ»
3998 );
3999 "});
4000 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4001 cx.assert_editor_state(indoc! {"
4002 const a: B = (
4003 c(),
4004 ˇ
4005 );
4006 "});
4007
4008 // Paste it at the same position.
4009 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4010 cx.assert_editor_state(indoc! {"
4011 const a: B = (
4012 c(),
4013 d(
4014 e,
4015 f
4016 )ˇ
4017 );
4018 "});
4019
4020 // Paste it at a line with a lower indent level.
4021 cx.set_state(indoc! {"
4022 ˇ
4023 const a: B = (
4024 c(),
4025 );
4026 "});
4027 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4028 cx.assert_editor_state(indoc! {"
4029 d(
4030 e,
4031 f
4032 )ˇ
4033 const a: B = (
4034 c(),
4035 );
4036 "});
4037
4038 // Cut an indented block, with the leading whitespace.
4039 cx.set_state(indoc! {"
4040 const a: B = (
4041 c(),
4042 « d(
4043 e,
4044 f
4045 )
4046 ˇ»);
4047 "});
4048 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4049 cx.assert_editor_state(indoc! {"
4050 const a: B = (
4051 c(),
4052 ˇ);
4053 "});
4054
4055 // Paste it at the same position.
4056 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4057 cx.assert_editor_state(indoc! {"
4058 const a: B = (
4059 c(),
4060 d(
4061 e,
4062 f
4063 )
4064 ˇ);
4065 "});
4066
4067 // Paste it at a line with a higher indent level.
4068 cx.set_state(indoc! {"
4069 const a: B = (
4070 c(),
4071 d(
4072 e,
4073 fˇ
4074 )
4075 );
4076 "});
4077 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4078 cx.assert_editor_state(indoc! {"
4079 const a: B = (
4080 c(),
4081 d(
4082 e,
4083 f d(
4084 e,
4085 f
4086 )
4087 ˇ
4088 )
4089 );
4090 "});
4091}
4092
4093#[gpui::test]
4094fn test_select_all(cx: &mut TestAppContext) {
4095 init_test(cx, |_| {});
4096
4097 let view = cx.add_window(|cx| {
4098 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4099 build_editor(buffer, cx)
4100 });
4101 _ = view.update(cx, |view, cx| {
4102 view.select_all(&SelectAll, cx);
4103 assert_eq!(
4104 view.selections.display_ranges(cx),
4105 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4106 );
4107 });
4108}
4109
4110#[gpui::test]
4111fn test_select_line(cx: &mut TestAppContext) {
4112 init_test(cx, |_| {});
4113
4114 let view = cx.add_window(|cx| {
4115 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4116 build_editor(buffer, cx)
4117 });
4118 _ = view.update(cx, |view, cx| {
4119 view.change_selections(None, cx, |s| {
4120 s.select_display_ranges([
4121 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4122 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4123 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4124 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4125 ])
4126 });
4127 view.select_line(&SelectLine, cx);
4128 assert_eq!(
4129 view.selections.display_ranges(cx),
4130 vec![
4131 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4132 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4133 ]
4134 );
4135 });
4136
4137 _ = view.update(cx, |view, cx| {
4138 view.select_line(&SelectLine, cx);
4139 assert_eq!(
4140 view.selections.display_ranges(cx),
4141 vec![
4142 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4143 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4144 ]
4145 );
4146 });
4147
4148 _ = view.update(cx, |view, cx| {
4149 view.select_line(&SelectLine, cx);
4150 assert_eq!(
4151 view.selections.display_ranges(cx),
4152 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4153 );
4154 });
4155}
4156
4157#[gpui::test]
4158fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4159 init_test(cx, |_| {});
4160
4161 let view = cx.add_window(|cx| {
4162 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4163 build_editor(buffer, cx)
4164 });
4165 _ = view.update(cx, |view, cx| {
4166 view.fold_ranges(
4167 vec![
4168 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4169 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4170 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4171 ],
4172 true,
4173 cx,
4174 );
4175 view.change_selections(None, cx, |s| {
4176 s.select_display_ranges([
4177 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4178 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4179 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4180 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4181 ])
4182 });
4183 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4184 });
4185
4186 _ = view.update(cx, |view, cx| {
4187 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4188 assert_eq!(
4189 view.display_text(cx),
4190 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4191 );
4192 assert_eq!(
4193 view.selections.display_ranges(cx),
4194 [
4195 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4196 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4197 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4198 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4199 ]
4200 );
4201 });
4202
4203 _ = view.update(cx, |view, cx| {
4204 view.change_selections(None, cx, |s| {
4205 s.select_display_ranges([
4206 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4207 ])
4208 });
4209 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4210 assert_eq!(
4211 view.display_text(cx),
4212 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4213 );
4214 assert_eq!(
4215 view.selections.display_ranges(cx),
4216 [
4217 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4218 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4219 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4220 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4221 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4222 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4223 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4224 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4225 ]
4226 );
4227 });
4228}
4229
4230#[gpui::test]
4231async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4232 init_test(cx, |_| {});
4233
4234 let mut cx = EditorTestContext::new(cx).await;
4235
4236 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4237 cx.set_state(indoc!(
4238 r#"abc
4239 defˇghi
4240
4241 jk
4242 nlmo
4243 "#
4244 ));
4245
4246 cx.update_editor(|editor, cx| {
4247 editor.add_selection_above(&Default::default(), cx);
4248 });
4249
4250 cx.assert_editor_state(indoc!(
4251 r#"abcˇ
4252 defˇghi
4253
4254 jk
4255 nlmo
4256 "#
4257 ));
4258
4259 cx.update_editor(|editor, cx| {
4260 editor.add_selection_above(&Default::default(), cx);
4261 });
4262
4263 cx.assert_editor_state(indoc!(
4264 r#"abcˇ
4265 defˇghi
4266
4267 jk
4268 nlmo
4269 "#
4270 ));
4271
4272 cx.update_editor(|view, cx| {
4273 view.add_selection_below(&Default::default(), cx);
4274 });
4275
4276 cx.assert_editor_state(indoc!(
4277 r#"abc
4278 defˇghi
4279
4280 jk
4281 nlmo
4282 "#
4283 ));
4284
4285 cx.update_editor(|view, cx| {
4286 view.undo_selection(&Default::default(), cx);
4287 });
4288
4289 cx.assert_editor_state(indoc!(
4290 r#"abcˇ
4291 defˇghi
4292
4293 jk
4294 nlmo
4295 "#
4296 ));
4297
4298 cx.update_editor(|view, cx| {
4299 view.redo_selection(&Default::default(), cx);
4300 });
4301
4302 cx.assert_editor_state(indoc!(
4303 r#"abc
4304 defˇghi
4305
4306 jk
4307 nlmo
4308 "#
4309 ));
4310
4311 cx.update_editor(|view, cx| {
4312 view.add_selection_below(&Default::default(), cx);
4313 });
4314
4315 cx.assert_editor_state(indoc!(
4316 r#"abc
4317 defˇghi
4318
4319 jk
4320 nlmˇo
4321 "#
4322 ));
4323
4324 cx.update_editor(|view, cx| {
4325 view.add_selection_below(&Default::default(), cx);
4326 });
4327
4328 cx.assert_editor_state(indoc!(
4329 r#"abc
4330 defˇghi
4331
4332 jk
4333 nlmˇo
4334 "#
4335 ));
4336
4337 // change selections
4338 cx.set_state(indoc!(
4339 r#"abc
4340 def«ˇg»hi
4341
4342 jk
4343 nlmo
4344 "#
4345 ));
4346
4347 cx.update_editor(|view, cx| {
4348 view.add_selection_below(&Default::default(), cx);
4349 });
4350
4351 cx.assert_editor_state(indoc!(
4352 r#"abc
4353 def«ˇg»hi
4354
4355 jk
4356 nlm«ˇo»
4357 "#
4358 ));
4359
4360 cx.update_editor(|view, cx| {
4361 view.add_selection_below(&Default::default(), cx);
4362 });
4363
4364 cx.assert_editor_state(indoc!(
4365 r#"abc
4366 def«ˇg»hi
4367
4368 jk
4369 nlm«ˇo»
4370 "#
4371 ));
4372
4373 cx.update_editor(|view, cx| {
4374 view.add_selection_above(&Default::default(), cx);
4375 });
4376
4377 cx.assert_editor_state(indoc!(
4378 r#"abc
4379 def«ˇg»hi
4380
4381 jk
4382 nlmo
4383 "#
4384 ));
4385
4386 cx.update_editor(|view, cx| {
4387 view.add_selection_above(&Default::default(), cx);
4388 });
4389
4390 cx.assert_editor_state(indoc!(
4391 r#"abc
4392 def«ˇg»hi
4393
4394 jk
4395 nlmo
4396 "#
4397 ));
4398
4399 // Change selections again
4400 cx.set_state(indoc!(
4401 r#"a«bc
4402 defgˇ»hi
4403
4404 jk
4405 nlmo
4406 "#
4407 ));
4408
4409 cx.update_editor(|view, cx| {
4410 view.add_selection_below(&Default::default(), cx);
4411 });
4412
4413 cx.assert_editor_state(indoc!(
4414 r#"a«bcˇ»
4415 d«efgˇ»hi
4416
4417 j«kˇ»
4418 nlmo
4419 "#
4420 ));
4421
4422 cx.update_editor(|view, cx| {
4423 view.add_selection_below(&Default::default(), cx);
4424 });
4425 cx.assert_editor_state(indoc!(
4426 r#"a«bcˇ»
4427 d«efgˇ»hi
4428
4429 j«kˇ»
4430 n«lmoˇ»
4431 "#
4432 ));
4433 cx.update_editor(|view, cx| {
4434 view.add_selection_above(&Default::default(), cx);
4435 });
4436
4437 cx.assert_editor_state(indoc!(
4438 r#"a«bcˇ»
4439 d«efgˇ»hi
4440
4441 j«kˇ»
4442 nlmo
4443 "#
4444 ));
4445
4446 // Change selections again
4447 cx.set_state(indoc!(
4448 r#"abc
4449 d«ˇefghi
4450
4451 jk
4452 nlm»o
4453 "#
4454 ));
4455
4456 cx.update_editor(|view, cx| {
4457 view.add_selection_above(&Default::default(), cx);
4458 });
4459
4460 cx.assert_editor_state(indoc!(
4461 r#"a«ˇbc»
4462 d«ˇef»ghi
4463
4464 j«ˇk»
4465 n«ˇlm»o
4466 "#
4467 ));
4468
4469 cx.update_editor(|view, cx| {
4470 view.add_selection_below(&Default::default(), cx);
4471 });
4472
4473 cx.assert_editor_state(indoc!(
4474 r#"abc
4475 d«ˇef»ghi
4476
4477 j«ˇk»
4478 n«ˇlm»o
4479 "#
4480 ));
4481}
4482
4483#[gpui::test]
4484async fn test_select_next(cx: &mut gpui::TestAppContext) {
4485 init_test(cx, |_| {});
4486
4487 let mut cx = EditorTestContext::new(cx).await;
4488 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4489
4490 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4491 .unwrap();
4492 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4493
4494 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4495 .unwrap();
4496 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4497
4498 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4499 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4500
4501 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4502 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4503
4504 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4505 .unwrap();
4506 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4507
4508 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4509 .unwrap();
4510 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4511}
4512
4513#[gpui::test]
4514async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
4515 init_test(cx, |_| {});
4516
4517 let mut cx = EditorTestContext::new(cx).await;
4518 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4519
4520 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
4521 .unwrap();
4522 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4523}
4524
4525#[gpui::test]
4526async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4527 init_test(cx, |_| {});
4528
4529 let mut cx = EditorTestContext::new(cx).await;
4530 cx.set_state(
4531 r#"let foo = 2;
4532lˇet foo = 2;
4533let fooˇ = 2;
4534let foo = 2;
4535let foo = ˇ2;"#,
4536 );
4537
4538 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4539 .unwrap();
4540 cx.assert_editor_state(
4541 r#"let foo = 2;
4542«letˇ» foo = 2;
4543let «fooˇ» = 2;
4544let foo = 2;
4545let foo = «2ˇ»;"#,
4546 );
4547
4548 // noop for multiple selections with different contents
4549 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4550 .unwrap();
4551 cx.assert_editor_state(
4552 r#"let foo = 2;
4553«letˇ» foo = 2;
4554let «fooˇ» = 2;
4555let foo = 2;
4556let foo = «2ˇ»;"#,
4557 );
4558}
4559
4560#[gpui::test]
4561async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
4562 init_test(cx, |_| {});
4563
4564 let mut cx = EditorTestContext::new_multibuffer(
4565 cx,
4566 [
4567 &indoc! {
4568 "aaa\n«bbb\nccc\n»ddd"
4569 },
4570 &indoc! {
4571 "aaa\n«bbb\nccc\n»ddd"
4572 },
4573 ],
4574 );
4575
4576 cx.assert_editor_state(indoc! {"
4577 ˇbbb
4578 ccc
4579
4580 bbb
4581 ccc
4582 "});
4583 cx.dispatch_action(SelectPrevious::default());
4584 cx.assert_editor_state(indoc! {"
4585 «bbbˇ»
4586 ccc
4587
4588 bbb
4589 ccc
4590 "});
4591 cx.dispatch_action(SelectPrevious::default());
4592 cx.assert_editor_state(indoc! {"
4593 «bbbˇ»
4594 ccc
4595
4596 «bbbˇ»
4597 ccc
4598 "});
4599}
4600
4601#[gpui::test]
4602async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
4603 init_test(cx, |_| {});
4604
4605 let mut cx = EditorTestContext::new(cx).await;
4606 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4607
4608 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4609 .unwrap();
4610 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4611
4612 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4613 .unwrap();
4614 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4615
4616 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4617 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4618
4619 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4620 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4621
4622 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4623 .unwrap();
4624 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
4625
4626 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4627 .unwrap();
4628 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
4629
4630 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4631 .unwrap();
4632 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
4633}
4634
4635#[gpui::test]
4636async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4637 init_test(cx, |_| {});
4638
4639 let mut cx = EditorTestContext::new(cx).await;
4640 cx.set_state(
4641 r#"let foo = 2;
4642lˇet foo = 2;
4643let fooˇ = 2;
4644let foo = 2;
4645let foo = ˇ2;"#,
4646 );
4647
4648 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4649 .unwrap();
4650 cx.assert_editor_state(
4651 r#"let foo = 2;
4652«letˇ» foo = 2;
4653let «fooˇ» = 2;
4654let foo = 2;
4655let foo = «2ˇ»;"#,
4656 );
4657
4658 // noop for multiple selections with different contents
4659 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4660 .unwrap();
4661 cx.assert_editor_state(
4662 r#"let foo = 2;
4663«letˇ» foo = 2;
4664let «fooˇ» = 2;
4665let foo = 2;
4666let foo = «2ˇ»;"#,
4667 );
4668}
4669
4670#[gpui::test]
4671async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
4672 init_test(cx, |_| {});
4673
4674 let mut cx = EditorTestContext::new(cx).await;
4675 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
4676
4677 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4678 .unwrap();
4679 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
4680
4681 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4682 .unwrap();
4683 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
4684
4685 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4686 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
4687
4688 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4689 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
4690
4691 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4692 .unwrap();
4693 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
4694
4695 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4696 .unwrap();
4697 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
4698}
4699
4700#[gpui::test]
4701async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
4702 init_test(cx, |_| {});
4703
4704 let language = Arc::new(Language::new(
4705 LanguageConfig::default(),
4706 Some(tree_sitter_rust::language()),
4707 ));
4708
4709 let text = r#"
4710 use mod1::mod2::{mod3, mod4};
4711
4712 fn fn_1(param1: bool, param2: &str) {
4713 let var1 = "text";
4714 }
4715 "#
4716 .unindent();
4717
4718 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
4719 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4720 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4721
4722 editor
4723 .condition::<crate::EditorEvent>(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4724 .await;
4725
4726 editor.update(cx, |view, cx| {
4727 view.change_selections(None, cx, |s| {
4728 s.select_display_ranges([
4729 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
4730 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
4731 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
4732 ]);
4733 });
4734 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4735 });
4736 editor.update(cx, |editor, cx| {
4737 assert_text_with_selections(
4738 editor,
4739 indoc! {r#"
4740 use mod1::mod2::{mod3, «mod4ˇ»};
4741
4742 fn fn_1«ˇ(param1: bool, param2: &str)» {
4743 let var1 = "«textˇ»";
4744 }
4745 "#},
4746 cx,
4747 );
4748 });
4749
4750 editor.update(cx, |view, cx| {
4751 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4752 });
4753 editor.update(cx, |editor, cx| {
4754 assert_text_with_selections(
4755 editor,
4756 indoc! {r#"
4757 use mod1::mod2::«{mod3, mod4}ˇ»;
4758
4759 «ˇfn fn_1(param1: bool, param2: &str) {
4760 let var1 = "text";
4761 }»
4762 "#},
4763 cx,
4764 );
4765 });
4766
4767 editor.update(cx, |view, cx| {
4768 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4769 });
4770 assert_eq!(
4771 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
4772 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
4773 );
4774
4775 // Trying to expand the selected syntax node one more time has no effect.
4776 editor.update(cx, |view, cx| {
4777 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4778 });
4779 assert_eq!(
4780 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
4781 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
4782 );
4783
4784 editor.update(cx, |view, cx| {
4785 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4786 });
4787 editor.update(cx, |editor, cx| {
4788 assert_text_with_selections(
4789 editor,
4790 indoc! {r#"
4791 use mod1::mod2::«{mod3, mod4}ˇ»;
4792
4793 «ˇfn fn_1(param1: bool, param2: &str) {
4794 let var1 = "text";
4795 }»
4796 "#},
4797 cx,
4798 );
4799 });
4800
4801 editor.update(cx, |view, cx| {
4802 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4803 });
4804 editor.update(cx, |editor, cx| {
4805 assert_text_with_selections(
4806 editor,
4807 indoc! {r#"
4808 use mod1::mod2::{mod3, «mod4ˇ»};
4809
4810 fn fn_1«ˇ(param1: bool, param2: &str)» {
4811 let var1 = "«textˇ»";
4812 }
4813 "#},
4814 cx,
4815 );
4816 });
4817
4818 editor.update(cx, |view, cx| {
4819 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4820 });
4821 editor.update(cx, |editor, cx| {
4822 assert_text_with_selections(
4823 editor,
4824 indoc! {r#"
4825 use mod1::mod2::{mod3, mo«ˇ»d4};
4826
4827 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
4828 let var1 = "te«ˇ»xt";
4829 }
4830 "#},
4831 cx,
4832 );
4833 });
4834
4835 // Trying to shrink the selected syntax node one more time has no effect.
4836 editor.update(cx, |view, cx| {
4837 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4838 });
4839 editor.update(cx, |editor, cx| {
4840 assert_text_with_selections(
4841 editor,
4842 indoc! {r#"
4843 use mod1::mod2::{mod3, mo«ˇ»d4};
4844
4845 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
4846 let var1 = "te«ˇ»xt";
4847 }
4848 "#},
4849 cx,
4850 );
4851 });
4852
4853 // Ensure that we keep expanding the selection if the larger selection starts or ends within
4854 // a fold.
4855 editor.update(cx, |view, cx| {
4856 view.fold_ranges(
4857 vec![
4858 (
4859 Point::new(0, 21)..Point::new(0, 24),
4860 FoldPlaceholder::test(),
4861 ),
4862 (
4863 Point::new(3, 20)..Point::new(3, 22),
4864 FoldPlaceholder::test(),
4865 ),
4866 ],
4867 true,
4868 cx,
4869 );
4870 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4871 });
4872 editor.update(cx, |editor, cx| {
4873 assert_text_with_selections(
4874 editor,
4875 indoc! {r#"
4876 use mod1::mod2::«{mod3, mod4}ˇ»;
4877
4878 fn fn_1«ˇ(param1: bool, param2: &str)» {
4879 «let var1 = "text";ˇ»
4880 }
4881 "#},
4882 cx,
4883 );
4884 });
4885}
4886
4887#[gpui::test]
4888async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
4889 init_test(cx, |_| {});
4890
4891 let language = Arc::new(
4892 Language::new(
4893 LanguageConfig {
4894 brackets: BracketPairConfig {
4895 pairs: vec![
4896 BracketPair {
4897 start: "{".to_string(),
4898 end: "}".to_string(),
4899 close: false,
4900 surround: false,
4901 newline: true,
4902 },
4903 BracketPair {
4904 start: "(".to_string(),
4905 end: ")".to_string(),
4906 close: false,
4907 surround: false,
4908 newline: true,
4909 },
4910 ],
4911 ..Default::default()
4912 },
4913 ..Default::default()
4914 },
4915 Some(tree_sitter_rust::language()),
4916 )
4917 .with_indents_query(
4918 r#"
4919 (_ "(" ")" @end) @indent
4920 (_ "{" "}" @end) @indent
4921 "#,
4922 )
4923 .unwrap(),
4924 );
4925
4926 let text = "fn a() {}";
4927
4928 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
4929 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4930 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4931 editor
4932 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
4933 .await;
4934
4935 _ = editor.update(cx, |editor, cx| {
4936 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
4937 editor.newline(&Newline, cx);
4938 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
4939 assert_eq!(
4940 editor.selections.ranges(cx),
4941 &[
4942 Point::new(1, 4)..Point::new(1, 4),
4943 Point::new(3, 4)..Point::new(3, 4),
4944 Point::new(5, 0)..Point::new(5, 0)
4945 ]
4946 );
4947 });
4948}
4949
4950#[gpui::test]
4951async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
4952 init_test(cx, |_| {});
4953
4954 let mut cx = EditorTestContext::new(cx).await;
4955
4956 let language = Arc::new(Language::new(
4957 LanguageConfig {
4958 brackets: BracketPairConfig {
4959 pairs: vec![
4960 BracketPair {
4961 start: "{".to_string(),
4962 end: "}".to_string(),
4963 close: true,
4964 surround: true,
4965 newline: true,
4966 },
4967 BracketPair {
4968 start: "(".to_string(),
4969 end: ")".to_string(),
4970 close: true,
4971 surround: true,
4972 newline: true,
4973 },
4974 BracketPair {
4975 start: "/*".to_string(),
4976 end: " */".to_string(),
4977 close: true,
4978 surround: true,
4979 newline: true,
4980 },
4981 BracketPair {
4982 start: "[".to_string(),
4983 end: "]".to_string(),
4984 close: false,
4985 surround: false,
4986 newline: true,
4987 },
4988 BracketPair {
4989 start: "\"".to_string(),
4990 end: "\"".to_string(),
4991 close: true,
4992 surround: true,
4993 newline: false,
4994 },
4995 BracketPair {
4996 start: "<".to_string(),
4997 end: ">".to_string(),
4998 close: false,
4999 surround: true,
5000 newline: true,
5001 },
5002 ],
5003 ..Default::default()
5004 },
5005 autoclose_before: "})]".to_string(),
5006 ..Default::default()
5007 },
5008 Some(tree_sitter_rust::language()),
5009 ));
5010
5011 cx.language_registry().add(language.clone());
5012 cx.update_buffer(|buffer, cx| {
5013 buffer.set_language(Some(language), cx);
5014 });
5015
5016 cx.set_state(
5017 &r#"
5018 🏀ˇ
5019 εˇ
5020 ❤️ˇ
5021 "#
5022 .unindent(),
5023 );
5024
5025 // autoclose multiple nested brackets at multiple cursors
5026 cx.update_editor(|view, cx| {
5027 view.handle_input("{", cx);
5028 view.handle_input("{", cx);
5029 view.handle_input("{", cx);
5030 });
5031 cx.assert_editor_state(
5032 &"
5033 🏀{{{ˇ}}}
5034 ε{{{ˇ}}}
5035 ❤️{{{ˇ}}}
5036 "
5037 .unindent(),
5038 );
5039
5040 // insert a different closing bracket
5041 cx.update_editor(|view, cx| {
5042 view.handle_input(")", cx);
5043 });
5044 cx.assert_editor_state(
5045 &"
5046 🏀{{{)ˇ}}}
5047 ε{{{)ˇ}}}
5048 ❤️{{{)ˇ}}}
5049 "
5050 .unindent(),
5051 );
5052
5053 // skip over the auto-closed brackets when typing a closing bracket
5054 cx.update_editor(|view, cx| {
5055 view.move_right(&MoveRight, cx);
5056 view.handle_input("}", cx);
5057 view.handle_input("}", cx);
5058 view.handle_input("}", cx);
5059 });
5060 cx.assert_editor_state(
5061 &"
5062 🏀{{{)}}}}ˇ
5063 ε{{{)}}}}ˇ
5064 ❤️{{{)}}}}ˇ
5065 "
5066 .unindent(),
5067 );
5068
5069 // autoclose multi-character pairs
5070 cx.set_state(
5071 &"
5072 ˇ
5073 ˇ
5074 "
5075 .unindent(),
5076 );
5077 cx.update_editor(|view, cx| {
5078 view.handle_input("/", cx);
5079 view.handle_input("*", cx);
5080 });
5081 cx.assert_editor_state(
5082 &"
5083 /*ˇ */
5084 /*ˇ */
5085 "
5086 .unindent(),
5087 );
5088
5089 // one cursor autocloses a multi-character pair, one cursor
5090 // does not autoclose.
5091 cx.set_state(
5092 &"
5093 /ˇ
5094 ˇ
5095 "
5096 .unindent(),
5097 );
5098 cx.update_editor(|view, cx| view.handle_input("*", cx));
5099 cx.assert_editor_state(
5100 &"
5101 /*ˇ */
5102 *ˇ
5103 "
5104 .unindent(),
5105 );
5106
5107 // Don't autoclose if the next character isn't whitespace and isn't
5108 // listed in the language's "autoclose_before" section.
5109 cx.set_state("ˇa b");
5110 cx.update_editor(|view, cx| view.handle_input("{", cx));
5111 cx.assert_editor_state("{ˇa b");
5112
5113 // Don't autoclose if `close` is false for the bracket pair
5114 cx.set_state("ˇ");
5115 cx.update_editor(|view, cx| view.handle_input("[", cx));
5116 cx.assert_editor_state("[ˇ");
5117
5118 // Surround with brackets if text is selected
5119 cx.set_state("«aˇ» b");
5120 cx.update_editor(|view, cx| view.handle_input("{", cx));
5121 cx.assert_editor_state("{«aˇ»} b");
5122
5123 // Autclose pair where the start and end characters are the same
5124 cx.set_state("aˇ");
5125 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5126 cx.assert_editor_state("a\"ˇ\"");
5127 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5128 cx.assert_editor_state("a\"\"ˇ");
5129
5130 // Don't autoclose pair if autoclose is disabled
5131 cx.set_state("ˇ");
5132 cx.update_editor(|view, cx| view.handle_input("<", cx));
5133 cx.assert_editor_state("<ˇ");
5134
5135 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5136 cx.set_state("«aˇ» b");
5137 cx.update_editor(|view, cx| view.handle_input("<", cx));
5138 cx.assert_editor_state("<«aˇ»> b");
5139}
5140
5141#[gpui::test]
5142async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5143 init_test(cx, |settings| {
5144 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5145 });
5146
5147 let mut cx = EditorTestContext::new(cx).await;
5148
5149 let language = Arc::new(Language::new(
5150 LanguageConfig {
5151 brackets: BracketPairConfig {
5152 pairs: vec![
5153 BracketPair {
5154 start: "{".to_string(),
5155 end: "}".to_string(),
5156 close: true,
5157 surround: true,
5158 newline: true,
5159 },
5160 BracketPair {
5161 start: "(".to_string(),
5162 end: ")".to_string(),
5163 close: true,
5164 surround: true,
5165 newline: true,
5166 },
5167 BracketPair {
5168 start: "[".to_string(),
5169 end: "]".to_string(),
5170 close: false,
5171 surround: false,
5172 newline: true,
5173 },
5174 ],
5175 ..Default::default()
5176 },
5177 autoclose_before: "})]".to_string(),
5178 ..Default::default()
5179 },
5180 Some(tree_sitter_rust::language()),
5181 ));
5182
5183 cx.language_registry().add(language.clone());
5184 cx.update_buffer(|buffer, cx| {
5185 buffer.set_language(Some(language), cx);
5186 });
5187
5188 cx.set_state(
5189 &"
5190 ˇ
5191 ˇ
5192 ˇ
5193 "
5194 .unindent(),
5195 );
5196
5197 // ensure only matching closing brackets are skipped over
5198 cx.update_editor(|view, cx| {
5199 view.handle_input("}", cx);
5200 view.move_left(&MoveLeft, cx);
5201 view.handle_input(")", cx);
5202 view.move_left(&MoveLeft, cx);
5203 });
5204 cx.assert_editor_state(
5205 &"
5206 ˇ)}
5207 ˇ)}
5208 ˇ)}
5209 "
5210 .unindent(),
5211 );
5212
5213 // skip-over closing brackets at multiple cursors
5214 cx.update_editor(|view, cx| {
5215 view.handle_input(")", cx);
5216 view.handle_input("}", cx);
5217 });
5218 cx.assert_editor_state(
5219 &"
5220 )}ˇ
5221 )}ˇ
5222 )}ˇ
5223 "
5224 .unindent(),
5225 );
5226
5227 // ignore non-close brackets
5228 cx.update_editor(|view, cx| {
5229 view.handle_input("]", cx);
5230 view.move_left(&MoveLeft, cx);
5231 view.handle_input("]", cx);
5232 });
5233 cx.assert_editor_state(
5234 &"
5235 )}]ˇ]
5236 )}]ˇ]
5237 )}]ˇ]
5238 "
5239 .unindent(),
5240 );
5241}
5242
5243#[gpui::test]
5244async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5245 init_test(cx, |_| {});
5246
5247 let mut cx = EditorTestContext::new(cx).await;
5248
5249 let html_language = Arc::new(
5250 Language::new(
5251 LanguageConfig {
5252 name: "HTML".into(),
5253 brackets: BracketPairConfig {
5254 pairs: vec![
5255 BracketPair {
5256 start: "<".into(),
5257 end: ">".into(),
5258 close: true,
5259 ..Default::default()
5260 },
5261 BracketPair {
5262 start: "{".into(),
5263 end: "}".into(),
5264 close: true,
5265 ..Default::default()
5266 },
5267 BracketPair {
5268 start: "(".into(),
5269 end: ")".into(),
5270 close: true,
5271 ..Default::default()
5272 },
5273 ],
5274 ..Default::default()
5275 },
5276 autoclose_before: "})]>".into(),
5277 ..Default::default()
5278 },
5279 Some(tree_sitter_html::language()),
5280 )
5281 .with_injection_query(
5282 r#"
5283 (script_element
5284 (raw_text) @content
5285 (#set! "language" "javascript"))
5286 "#,
5287 )
5288 .unwrap(),
5289 );
5290
5291 let javascript_language = Arc::new(Language::new(
5292 LanguageConfig {
5293 name: "JavaScript".into(),
5294 brackets: BracketPairConfig {
5295 pairs: vec![
5296 BracketPair {
5297 start: "/*".into(),
5298 end: " */".into(),
5299 close: true,
5300 ..Default::default()
5301 },
5302 BracketPair {
5303 start: "{".into(),
5304 end: "}".into(),
5305 close: true,
5306 ..Default::default()
5307 },
5308 BracketPair {
5309 start: "(".into(),
5310 end: ")".into(),
5311 close: true,
5312 ..Default::default()
5313 },
5314 ],
5315 ..Default::default()
5316 },
5317 autoclose_before: "})]>".into(),
5318 ..Default::default()
5319 },
5320 Some(tree_sitter_typescript::language_tsx()),
5321 ));
5322
5323 cx.language_registry().add(html_language.clone());
5324 cx.language_registry().add(javascript_language.clone());
5325
5326 cx.update_buffer(|buffer, cx| {
5327 buffer.set_language(Some(html_language), cx);
5328 });
5329
5330 cx.set_state(
5331 &r#"
5332 <body>ˇ
5333 <script>
5334 var x = 1;ˇ
5335 </script>
5336 </body>ˇ
5337 "#
5338 .unindent(),
5339 );
5340
5341 // Precondition: different languages are active at different locations.
5342 cx.update_editor(|editor, cx| {
5343 let snapshot = editor.snapshot(cx);
5344 let cursors = editor.selections.ranges::<usize>(cx);
5345 let languages = cursors
5346 .iter()
5347 .map(|c| snapshot.language_at(c.start).unwrap().name())
5348 .collect::<Vec<_>>();
5349 assert_eq!(
5350 languages,
5351 &["HTML".into(), "JavaScript".into(), "HTML".into()]
5352 );
5353 });
5354
5355 // Angle brackets autoclose in HTML, but not JavaScript.
5356 cx.update_editor(|editor, cx| {
5357 editor.handle_input("<", cx);
5358 editor.handle_input("a", cx);
5359 });
5360 cx.assert_editor_state(
5361 &r#"
5362 <body><aˇ>
5363 <script>
5364 var x = 1;<aˇ
5365 </script>
5366 </body><aˇ>
5367 "#
5368 .unindent(),
5369 );
5370
5371 // Curly braces and parens autoclose in both HTML and JavaScript.
5372 cx.update_editor(|editor, cx| {
5373 editor.handle_input(" b=", cx);
5374 editor.handle_input("{", cx);
5375 editor.handle_input("c", cx);
5376 editor.handle_input("(", cx);
5377 });
5378 cx.assert_editor_state(
5379 &r#"
5380 <body><a b={c(ˇ)}>
5381 <script>
5382 var x = 1;<a b={c(ˇ)}
5383 </script>
5384 </body><a b={c(ˇ)}>
5385 "#
5386 .unindent(),
5387 );
5388
5389 // Brackets that were already autoclosed are skipped.
5390 cx.update_editor(|editor, cx| {
5391 editor.handle_input(")", cx);
5392 editor.handle_input("d", cx);
5393 editor.handle_input("}", cx);
5394 });
5395 cx.assert_editor_state(
5396 &r#"
5397 <body><a b={c()d}ˇ>
5398 <script>
5399 var x = 1;<a b={c()d}ˇ
5400 </script>
5401 </body><a b={c()d}ˇ>
5402 "#
5403 .unindent(),
5404 );
5405 cx.update_editor(|editor, cx| {
5406 editor.handle_input(">", cx);
5407 });
5408 cx.assert_editor_state(
5409 &r#"
5410 <body><a b={c()d}>ˇ
5411 <script>
5412 var x = 1;<a b={c()d}>ˇ
5413 </script>
5414 </body><a b={c()d}>ˇ
5415 "#
5416 .unindent(),
5417 );
5418
5419 // Reset
5420 cx.set_state(
5421 &r#"
5422 <body>ˇ
5423 <script>
5424 var x = 1;ˇ
5425 </script>
5426 </body>ˇ
5427 "#
5428 .unindent(),
5429 );
5430
5431 cx.update_editor(|editor, cx| {
5432 editor.handle_input("<", cx);
5433 });
5434 cx.assert_editor_state(
5435 &r#"
5436 <body><ˇ>
5437 <script>
5438 var x = 1;<ˇ
5439 </script>
5440 </body><ˇ>
5441 "#
5442 .unindent(),
5443 );
5444
5445 // When backspacing, the closing angle brackets are removed.
5446 cx.update_editor(|editor, cx| {
5447 editor.backspace(&Backspace, cx);
5448 });
5449 cx.assert_editor_state(
5450 &r#"
5451 <body>ˇ
5452 <script>
5453 var x = 1;ˇ
5454 </script>
5455 </body>ˇ
5456 "#
5457 .unindent(),
5458 );
5459
5460 // Block comments autoclose in JavaScript, but not HTML.
5461 cx.update_editor(|editor, cx| {
5462 editor.handle_input("/", cx);
5463 editor.handle_input("*", cx);
5464 });
5465 cx.assert_editor_state(
5466 &r#"
5467 <body>/*ˇ
5468 <script>
5469 var x = 1;/*ˇ */
5470 </script>
5471 </body>/*ˇ
5472 "#
5473 .unindent(),
5474 );
5475}
5476
5477#[gpui::test]
5478async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
5479 init_test(cx, |_| {});
5480
5481 let mut cx = EditorTestContext::new(cx).await;
5482
5483 let rust_language = Arc::new(
5484 Language::new(
5485 LanguageConfig {
5486 name: "Rust".into(),
5487 brackets: serde_json::from_value(json!([
5488 { "start": "{", "end": "}", "close": true, "newline": true },
5489 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
5490 ]))
5491 .unwrap(),
5492 autoclose_before: "})]>".into(),
5493 ..Default::default()
5494 },
5495 Some(tree_sitter_rust::language()),
5496 )
5497 .with_override_query("(string_literal) @string")
5498 .unwrap(),
5499 );
5500
5501 cx.language_registry().add(rust_language.clone());
5502 cx.update_buffer(|buffer, cx| {
5503 buffer.set_language(Some(rust_language), cx);
5504 });
5505
5506 cx.set_state(
5507 &r#"
5508 let x = ˇ
5509 "#
5510 .unindent(),
5511 );
5512
5513 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
5514 cx.update_editor(|editor, cx| {
5515 editor.handle_input("\"", cx);
5516 });
5517 cx.assert_editor_state(
5518 &r#"
5519 let x = "ˇ"
5520 "#
5521 .unindent(),
5522 );
5523
5524 // Inserting another quotation mark. The cursor moves across the existing
5525 // automatically-inserted quotation mark.
5526 cx.update_editor(|editor, cx| {
5527 editor.handle_input("\"", cx);
5528 });
5529 cx.assert_editor_state(
5530 &r#"
5531 let x = ""ˇ
5532 "#
5533 .unindent(),
5534 );
5535
5536 // Reset
5537 cx.set_state(
5538 &r#"
5539 let x = ˇ
5540 "#
5541 .unindent(),
5542 );
5543
5544 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
5545 cx.update_editor(|editor, cx| {
5546 editor.handle_input("\"", cx);
5547 editor.handle_input(" ", cx);
5548 editor.move_left(&Default::default(), cx);
5549 editor.handle_input("\\", cx);
5550 editor.handle_input("\"", cx);
5551 });
5552 cx.assert_editor_state(
5553 &r#"
5554 let x = "\"ˇ "
5555 "#
5556 .unindent(),
5557 );
5558
5559 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
5560 // mark. Nothing is inserted.
5561 cx.update_editor(|editor, cx| {
5562 editor.move_right(&Default::default(), cx);
5563 editor.handle_input("\"", cx);
5564 });
5565 cx.assert_editor_state(
5566 &r#"
5567 let x = "\" "ˇ
5568 "#
5569 .unindent(),
5570 );
5571}
5572
5573#[gpui::test]
5574async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
5575 init_test(cx, |_| {});
5576
5577 let language = Arc::new(Language::new(
5578 LanguageConfig {
5579 brackets: BracketPairConfig {
5580 pairs: vec![
5581 BracketPair {
5582 start: "{".to_string(),
5583 end: "}".to_string(),
5584 close: true,
5585 surround: true,
5586 newline: true,
5587 },
5588 BracketPair {
5589 start: "/* ".to_string(),
5590 end: "*/".to_string(),
5591 close: true,
5592 surround: true,
5593 ..Default::default()
5594 },
5595 ],
5596 ..Default::default()
5597 },
5598 ..Default::default()
5599 },
5600 Some(tree_sitter_rust::language()),
5601 ));
5602
5603 let text = r#"
5604 a
5605 b
5606 c
5607 "#
5608 .unindent();
5609
5610 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5611 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5612 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5613 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5614 .await;
5615
5616 _ = view.update(cx, |view, cx| {
5617 view.change_selections(None, cx, |s| {
5618 s.select_display_ranges([
5619 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5620 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5621 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
5622 ])
5623 });
5624
5625 view.handle_input("{", cx);
5626 view.handle_input("{", cx);
5627 view.handle_input("{", cx);
5628 assert_eq!(
5629 view.text(cx),
5630 "
5631 {{{a}}}
5632 {{{b}}}
5633 {{{c}}}
5634 "
5635 .unindent()
5636 );
5637 assert_eq!(
5638 view.selections.display_ranges(cx),
5639 [
5640 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
5641 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
5642 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
5643 ]
5644 );
5645
5646 view.undo(&Undo, cx);
5647 view.undo(&Undo, cx);
5648 view.undo(&Undo, cx);
5649 assert_eq!(
5650 view.text(cx),
5651 "
5652 a
5653 b
5654 c
5655 "
5656 .unindent()
5657 );
5658 assert_eq!(
5659 view.selections.display_ranges(cx),
5660 [
5661 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5662 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5663 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
5664 ]
5665 );
5666
5667 // Ensure inserting the first character of a multi-byte bracket pair
5668 // doesn't surround the selections with the bracket.
5669 view.handle_input("/", cx);
5670 assert_eq!(
5671 view.text(cx),
5672 "
5673 /
5674 /
5675 /
5676 "
5677 .unindent()
5678 );
5679 assert_eq!(
5680 view.selections.display_ranges(cx),
5681 [
5682 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
5683 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
5684 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
5685 ]
5686 );
5687
5688 view.undo(&Undo, cx);
5689 assert_eq!(
5690 view.text(cx),
5691 "
5692 a
5693 b
5694 c
5695 "
5696 .unindent()
5697 );
5698 assert_eq!(
5699 view.selections.display_ranges(cx),
5700 [
5701 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5702 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5703 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
5704 ]
5705 );
5706
5707 // Ensure inserting the last character of a multi-byte bracket pair
5708 // doesn't surround the selections with the bracket.
5709 view.handle_input("*", cx);
5710 assert_eq!(
5711 view.text(cx),
5712 "
5713 *
5714 *
5715 *
5716 "
5717 .unindent()
5718 );
5719 assert_eq!(
5720 view.selections.display_ranges(cx),
5721 [
5722 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
5723 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
5724 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
5725 ]
5726 );
5727 });
5728}
5729
5730#[gpui::test]
5731async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
5732 init_test(cx, |_| {});
5733
5734 let language = Arc::new(Language::new(
5735 LanguageConfig {
5736 brackets: BracketPairConfig {
5737 pairs: vec![BracketPair {
5738 start: "{".to_string(),
5739 end: "}".to_string(),
5740 close: true,
5741 surround: true,
5742 newline: true,
5743 }],
5744 ..Default::default()
5745 },
5746 autoclose_before: "}".to_string(),
5747 ..Default::default()
5748 },
5749 Some(tree_sitter_rust::language()),
5750 ));
5751
5752 let text = r#"
5753 a
5754 b
5755 c
5756 "#
5757 .unindent();
5758
5759 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5760 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5761 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5762 editor
5763 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5764 .await;
5765
5766 _ = editor.update(cx, |editor, cx| {
5767 editor.change_selections(None, cx, |s| {
5768 s.select_ranges([
5769 Point::new(0, 1)..Point::new(0, 1),
5770 Point::new(1, 1)..Point::new(1, 1),
5771 Point::new(2, 1)..Point::new(2, 1),
5772 ])
5773 });
5774
5775 editor.handle_input("{", cx);
5776 editor.handle_input("{", cx);
5777 editor.handle_input("_", 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, 4)..Point::new(0, 4),
5791 Point::new(1, 4)..Point::new(1, 4),
5792 Point::new(2, 4)..Point::new(2, 4)
5793 ]
5794 );
5795
5796 editor.backspace(&Default::default(), cx);
5797 editor.backspace(&Default::default(), cx);
5798 assert_eq!(
5799 editor.text(cx),
5800 "
5801 a{}
5802 b{}
5803 c{}
5804 "
5805 .unindent()
5806 );
5807 assert_eq!(
5808 editor.selections.ranges::<Point>(cx),
5809 [
5810 Point::new(0, 2)..Point::new(0, 2),
5811 Point::new(1, 2)..Point::new(1, 2),
5812 Point::new(2, 2)..Point::new(2, 2)
5813 ]
5814 );
5815
5816 editor.delete_to_previous_word_start(&Default::default(), cx);
5817 assert_eq!(
5818 editor.text(cx),
5819 "
5820 a
5821 b
5822 c
5823 "
5824 .unindent()
5825 );
5826 assert_eq!(
5827 editor.selections.ranges::<Point>(cx),
5828 [
5829 Point::new(0, 1)..Point::new(0, 1),
5830 Point::new(1, 1)..Point::new(1, 1),
5831 Point::new(2, 1)..Point::new(2, 1)
5832 ]
5833 );
5834 });
5835}
5836
5837#[gpui::test]
5838async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
5839 init_test(cx, |settings| {
5840 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5841 });
5842
5843 let mut cx = EditorTestContext::new(cx).await;
5844
5845 let language = Arc::new(Language::new(
5846 LanguageConfig {
5847 brackets: BracketPairConfig {
5848 pairs: vec![
5849 BracketPair {
5850 start: "{".to_string(),
5851 end: "}".to_string(),
5852 close: true,
5853 surround: true,
5854 newline: true,
5855 },
5856 BracketPair {
5857 start: "(".to_string(),
5858 end: ")".to_string(),
5859 close: true,
5860 surround: true,
5861 newline: true,
5862 },
5863 BracketPair {
5864 start: "[".to_string(),
5865 end: "]".to_string(),
5866 close: false,
5867 surround: true,
5868 newline: true,
5869 },
5870 ],
5871 ..Default::default()
5872 },
5873 autoclose_before: "})]".to_string(),
5874 ..Default::default()
5875 },
5876 Some(tree_sitter_rust::language()),
5877 ));
5878
5879 cx.language_registry().add(language.clone());
5880 cx.update_buffer(|buffer, cx| {
5881 buffer.set_language(Some(language), cx);
5882 });
5883
5884 cx.set_state(
5885 &"
5886 {(ˇ)}
5887 [[ˇ]]
5888 {(ˇ)}
5889 "
5890 .unindent(),
5891 );
5892
5893 cx.update_editor(|view, cx| {
5894 view.backspace(&Default::default(), cx);
5895 view.backspace(&Default::default(), cx);
5896 });
5897
5898 cx.assert_editor_state(
5899 &"
5900 ˇ
5901 ˇ]]
5902 ˇ
5903 "
5904 .unindent(),
5905 );
5906
5907 cx.update_editor(|view, cx| {
5908 view.handle_input("{", cx);
5909 view.handle_input("{", cx);
5910 view.move_right(&MoveRight, cx);
5911 view.move_right(&MoveRight, cx);
5912 view.move_left(&MoveLeft, cx);
5913 view.move_left(&MoveLeft, cx);
5914 view.backspace(&Default::default(), cx);
5915 });
5916
5917 cx.assert_editor_state(
5918 &"
5919 {ˇ}
5920 {ˇ}]]
5921 {ˇ}
5922 "
5923 .unindent(),
5924 );
5925
5926 cx.update_editor(|view, cx| {
5927 view.backspace(&Default::default(), cx);
5928 });
5929
5930 cx.assert_editor_state(
5931 &"
5932 ˇ
5933 ˇ]]
5934 ˇ
5935 "
5936 .unindent(),
5937 );
5938}
5939
5940#[gpui::test]
5941async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
5942 init_test(cx, |_| {});
5943
5944 let language = Arc::new(Language::new(
5945 LanguageConfig::default(),
5946 Some(tree_sitter_rust::language()),
5947 ));
5948
5949 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
5950 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5951 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5952 editor
5953 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5954 .await;
5955
5956 _ = editor.update(cx, |editor, cx| {
5957 editor.set_auto_replace_emoji_shortcode(true);
5958
5959 editor.handle_input("Hello ", cx);
5960 editor.handle_input(":wave", cx);
5961 assert_eq!(editor.text(cx), "Hello :wave".unindent());
5962
5963 editor.handle_input(":", cx);
5964 assert_eq!(editor.text(cx), "Hello 👋".unindent());
5965
5966 editor.handle_input(" :smile", cx);
5967 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
5968
5969 editor.handle_input(":", cx);
5970 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
5971
5972 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
5973 editor.handle_input(":wave", cx);
5974 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
5975
5976 editor.handle_input(":", cx);
5977 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
5978
5979 editor.handle_input(":1", cx);
5980 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
5981
5982 editor.handle_input(":", cx);
5983 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
5984
5985 // Ensure shortcode does not get replaced when it is part of a word
5986 editor.handle_input(" Test:wave", cx);
5987 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
5988
5989 editor.handle_input(":", cx);
5990 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
5991
5992 editor.set_auto_replace_emoji_shortcode(false);
5993
5994 // Ensure shortcode does not get replaced when auto replace is off
5995 editor.handle_input(" :wave", cx);
5996 assert_eq!(
5997 editor.text(cx),
5998 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
5999 );
6000
6001 editor.handle_input(":", cx);
6002 assert_eq!(
6003 editor.text(cx),
6004 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6005 );
6006 });
6007}
6008
6009#[gpui::test]
6010async fn test_snippets(cx: &mut gpui::TestAppContext) {
6011 init_test(cx, |_| {});
6012
6013 let (text, insertion_ranges) = marked_text_ranges(
6014 indoc! {"
6015 a.ˇ b
6016 a.ˇ b
6017 a.ˇ b
6018 "},
6019 false,
6020 );
6021
6022 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6023 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6024
6025 _ = editor.update(cx, |editor, cx| {
6026 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6027
6028 editor
6029 .insert_snippet(&insertion_ranges, snippet, cx)
6030 .unwrap();
6031
6032 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6033 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6034 assert_eq!(editor.text(cx), expected_text);
6035 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6036 }
6037
6038 assert(
6039 editor,
6040 cx,
6041 indoc! {"
6042 a.f(«one», two, «three») b
6043 a.f(«one», two, «three») b
6044 a.f(«one», two, «three») b
6045 "},
6046 );
6047
6048 // Can't move earlier than the first tab stop
6049 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6050 assert(
6051 editor,
6052 cx,
6053 indoc! {"
6054 a.f(«one», two, «three») b
6055 a.f(«one», two, «three») b
6056 a.f(«one», two, «three») b
6057 "},
6058 );
6059
6060 assert!(editor.move_to_next_snippet_tabstop(cx));
6061 assert(
6062 editor,
6063 cx,
6064 indoc! {"
6065 a.f(one, «two», three) b
6066 a.f(one, «two», three) b
6067 a.f(one, «two», three) b
6068 "},
6069 );
6070
6071 editor.move_to_prev_snippet_tabstop(cx);
6072 assert(
6073 editor,
6074 cx,
6075 indoc! {"
6076 a.f(«one», two, «three») b
6077 a.f(«one», two, «three») b
6078 a.f(«one», two, «three») b
6079 "},
6080 );
6081
6082 assert!(editor.move_to_next_snippet_tabstop(cx));
6083 assert(
6084 editor,
6085 cx,
6086 indoc! {"
6087 a.f(one, «two», three) b
6088 a.f(one, «two», three) b
6089 a.f(one, «two», three) b
6090 "},
6091 );
6092 assert!(editor.move_to_next_snippet_tabstop(cx));
6093 assert(
6094 editor,
6095 cx,
6096 indoc! {"
6097 a.f(one, two, three)ˇ b
6098 a.f(one, two, three)ˇ b
6099 a.f(one, two, three)ˇ b
6100 "},
6101 );
6102
6103 // As soon as the last tab stop is reached, snippet state is gone
6104 editor.move_to_prev_snippet_tabstop(cx);
6105 assert(
6106 editor,
6107 cx,
6108 indoc! {"
6109 a.f(one, two, three)ˇ b
6110 a.f(one, two, three)ˇ b
6111 a.f(one, two, three)ˇ b
6112 "},
6113 );
6114 });
6115}
6116
6117#[gpui::test]
6118async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6119 init_test(cx, |_| {});
6120
6121 let fs = FakeFs::new(cx.executor());
6122 fs.insert_file("/file.rs", Default::default()).await;
6123
6124 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6125
6126 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6127 language_registry.add(rust_lang());
6128 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6129 "Rust",
6130 FakeLspAdapter {
6131 capabilities: lsp::ServerCapabilities {
6132 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6133 ..Default::default()
6134 },
6135 ..Default::default()
6136 },
6137 );
6138
6139 let buffer = project
6140 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6141 .await
6142 .unwrap();
6143
6144 cx.executor().start_waiting();
6145 let fake_server = fake_servers.next().await.unwrap();
6146
6147 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6148 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6149 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6150 assert!(cx.read(|cx| editor.is_dirty(cx)));
6151
6152 let save = editor
6153 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6154 .unwrap();
6155 fake_server
6156 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6157 assert_eq!(
6158 params.text_document.uri,
6159 lsp::Url::from_file_path("/file.rs").unwrap()
6160 );
6161 assert_eq!(params.options.tab_size, 4);
6162 Ok(Some(vec![lsp::TextEdit::new(
6163 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6164 ", ".to_string(),
6165 )]))
6166 })
6167 .next()
6168 .await;
6169 cx.executor().start_waiting();
6170 save.await;
6171
6172 assert_eq!(
6173 editor.update(cx, |editor, cx| editor.text(cx)),
6174 "one, two\nthree\n"
6175 );
6176 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6177
6178 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6179 assert!(cx.read(|cx| editor.is_dirty(cx)));
6180
6181 // Ensure we can still save even if formatting hangs.
6182 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6183 assert_eq!(
6184 params.text_document.uri,
6185 lsp::Url::from_file_path("/file.rs").unwrap()
6186 );
6187 futures::future::pending::<()>().await;
6188 unreachable!()
6189 });
6190 let save = editor
6191 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6192 .unwrap();
6193 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6194 cx.executor().start_waiting();
6195 save.await;
6196 assert_eq!(
6197 editor.update(cx, |editor, cx| editor.text(cx)),
6198 "one\ntwo\nthree\n"
6199 );
6200 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6201
6202 // For non-dirty buffer, no formatting request should be sent
6203 let save = editor
6204 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6205 .unwrap();
6206 let _pending_format_request = fake_server
6207 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6208 panic!("Should not be invoked on non-dirty buffer");
6209 })
6210 .next();
6211 cx.executor().start_waiting();
6212 save.await;
6213
6214 // Set rust language override and assert overridden tabsize is sent to language server
6215 update_test_language_settings(cx, |settings| {
6216 settings.languages.insert(
6217 "Rust".into(),
6218 LanguageSettingsContent {
6219 tab_size: NonZeroU32::new(8),
6220 ..Default::default()
6221 },
6222 );
6223 });
6224
6225 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6226 assert!(cx.read(|cx| editor.is_dirty(cx)));
6227 let save = editor
6228 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6229 .unwrap();
6230 fake_server
6231 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6232 assert_eq!(
6233 params.text_document.uri,
6234 lsp::Url::from_file_path("/file.rs").unwrap()
6235 );
6236 assert_eq!(params.options.tab_size, 8);
6237 Ok(Some(vec![]))
6238 })
6239 .next()
6240 .await;
6241 cx.executor().start_waiting();
6242 save.await;
6243}
6244
6245#[gpui::test]
6246async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6247 init_test(cx, |_| {});
6248
6249 let cols = 4;
6250 let rows = 10;
6251 let sample_text_1 = sample_text(rows, cols, 'a');
6252 assert_eq!(
6253 sample_text_1,
6254 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6255 );
6256 let sample_text_2 = sample_text(rows, cols, 'l');
6257 assert_eq!(
6258 sample_text_2,
6259 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6260 );
6261 let sample_text_3 = sample_text(rows, cols, 'v');
6262 assert_eq!(
6263 sample_text_3,
6264 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6265 );
6266
6267 let fs = FakeFs::new(cx.executor());
6268 fs.insert_tree(
6269 "/a",
6270 json!({
6271 "main.rs": sample_text_1,
6272 "other.rs": sample_text_2,
6273 "lib.rs": sample_text_3,
6274 }),
6275 )
6276 .await;
6277
6278 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6279 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6280 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6281
6282 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6283 language_registry.add(rust_lang());
6284 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6285 "Rust",
6286 FakeLspAdapter {
6287 capabilities: lsp::ServerCapabilities {
6288 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6289 ..Default::default()
6290 },
6291 ..Default::default()
6292 },
6293 );
6294
6295 let worktree = project.update(cx, |project, cx| {
6296 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
6297 assert_eq!(worktrees.len(), 1);
6298 worktrees.pop().unwrap()
6299 });
6300 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6301
6302 let buffer_1 = project
6303 .update(cx, |project, cx| {
6304 project.open_buffer((worktree_id, "main.rs"), cx)
6305 })
6306 .await
6307 .unwrap();
6308 let buffer_2 = project
6309 .update(cx, |project, cx| {
6310 project.open_buffer((worktree_id, "other.rs"), cx)
6311 })
6312 .await
6313 .unwrap();
6314 let buffer_3 = project
6315 .update(cx, |project, cx| {
6316 project.open_buffer((worktree_id, "lib.rs"), cx)
6317 })
6318 .await
6319 .unwrap();
6320
6321 let multi_buffer = cx.new_model(|cx| {
6322 let mut multi_buffer = MultiBuffer::new(0, ReadWrite);
6323 multi_buffer.push_excerpts(
6324 buffer_1.clone(),
6325 [
6326 ExcerptRange {
6327 context: Point::new(0, 0)..Point::new(3, 0),
6328 primary: None,
6329 },
6330 ExcerptRange {
6331 context: Point::new(5, 0)..Point::new(7, 0),
6332 primary: None,
6333 },
6334 ExcerptRange {
6335 context: Point::new(9, 0)..Point::new(10, 4),
6336 primary: None,
6337 },
6338 ],
6339 cx,
6340 );
6341 multi_buffer.push_excerpts(
6342 buffer_2.clone(),
6343 [
6344 ExcerptRange {
6345 context: Point::new(0, 0)..Point::new(3, 0),
6346 primary: None,
6347 },
6348 ExcerptRange {
6349 context: Point::new(5, 0)..Point::new(7, 0),
6350 primary: None,
6351 },
6352 ExcerptRange {
6353 context: Point::new(9, 0)..Point::new(10, 4),
6354 primary: None,
6355 },
6356 ],
6357 cx,
6358 );
6359 multi_buffer.push_excerpts(
6360 buffer_3.clone(),
6361 [
6362 ExcerptRange {
6363 context: Point::new(0, 0)..Point::new(3, 0),
6364 primary: None,
6365 },
6366 ExcerptRange {
6367 context: Point::new(5, 0)..Point::new(7, 0),
6368 primary: None,
6369 },
6370 ExcerptRange {
6371 context: Point::new(9, 0)..Point::new(10, 4),
6372 primary: None,
6373 },
6374 ],
6375 cx,
6376 );
6377 multi_buffer
6378 });
6379 let multi_buffer_editor = cx.new_view(|cx| {
6380 Editor::new(
6381 EditorMode::Full,
6382 multi_buffer,
6383 Some(project.clone()),
6384 true,
6385 cx,
6386 )
6387 });
6388
6389 multi_buffer_editor.update(cx, |editor, cx| {
6390 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
6391 editor.insert("|one|two|three|", cx);
6392 });
6393 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6394 multi_buffer_editor.update(cx, |editor, cx| {
6395 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
6396 s.select_ranges(Some(60..70))
6397 });
6398 editor.insert("|four|five|six|", cx);
6399 });
6400 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6401
6402 // First two buffers should be edited, but not the third one.
6403 assert_eq!(
6404 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6405 "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}",
6406 );
6407 buffer_1.update(cx, |buffer, _| {
6408 assert!(buffer.is_dirty());
6409 assert_eq!(
6410 buffer.text(),
6411 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
6412 )
6413 });
6414 buffer_2.update(cx, |buffer, _| {
6415 assert!(buffer.is_dirty());
6416 assert_eq!(
6417 buffer.text(),
6418 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
6419 )
6420 });
6421 buffer_3.update(cx, |buffer, _| {
6422 assert!(!buffer.is_dirty());
6423 assert_eq!(buffer.text(), sample_text_3,)
6424 });
6425
6426 cx.executor().start_waiting();
6427 let save = multi_buffer_editor
6428 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6429 .unwrap();
6430
6431 let fake_server = fake_servers.next().await.unwrap();
6432 fake_server
6433 .server
6434 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6435 Ok(Some(vec![lsp::TextEdit::new(
6436 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6437 format!("[{} formatted]", params.text_document.uri),
6438 )]))
6439 })
6440 .detach();
6441 save.await;
6442
6443 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
6444 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
6445 assert_eq!(
6446 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6447 "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}",
6448 );
6449 buffer_1.update(cx, |buffer, _| {
6450 assert!(!buffer.is_dirty());
6451 assert_eq!(
6452 buffer.text(),
6453 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
6454 )
6455 });
6456 buffer_2.update(cx, |buffer, _| {
6457 assert!(!buffer.is_dirty());
6458 assert_eq!(
6459 buffer.text(),
6460 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
6461 )
6462 });
6463 buffer_3.update(cx, |buffer, _| {
6464 assert!(!buffer.is_dirty());
6465 assert_eq!(buffer.text(), sample_text_3,)
6466 });
6467}
6468
6469#[gpui::test]
6470async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
6471 init_test(cx, |_| {});
6472
6473 let fs = FakeFs::new(cx.executor());
6474 fs.insert_file("/file.rs", Default::default()).await;
6475
6476 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6477
6478 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6479 language_registry.add(rust_lang());
6480 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6481 "Rust",
6482 FakeLspAdapter {
6483 capabilities: lsp::ServerCapabilities {
6484 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
6485 ..Default::default()
6486 },
6487 ..Default::default()
6488 },
6489 );
6490
6491 let buffer = project
6492 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6493 .await
6494 .unwrap();
6495
6496 cx.executor().start_waiting();
6497 let fake_server = fake_servers.next().await.unwrap();
6498
6499 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6500 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6501 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6502 assert!(cx.read(|cx| editor.is_dirty(cx)));
6503
6504 let save = editor
6505 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6506 .unwrap();
6507 fake_server
6508 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6509 assert_eq!(
6510 params.text_document.uri,
6511 lsp::Url::from_file_path("/file.rs").unwrap()
6512 );
6513 assert_eq!(params.options.tab_size, 4);
6514 Ok(Some(vec![lsp::TextEdit::new(
6515 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6516 ", ".to_string(),
6517 )]))
6518 })
6519 .next()
6520 .await;
6521 cx.executor().start_waiting();
6522 save.await;
6523 assert_eq!(
6524 editor.update(cx, |editor, cx| editor.text(cx)),
6525 "one, two\nthree\n"
6526 );
6527 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6528
6529 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6530 assert!(cx.read(|cx| editor.is_dirty(cx)));
6531
6532 // Ensure we can still save even if formatting hangs.
6533 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
6534 move |params, _| async move {
6535 assert_eq!(
6536 params.text_document.uri,
6537 lsp::Url::from_file_path("/file.rs").unwrap()
6538 );
6539 futures::future::pending::<()>().await;
6540 unreachable!()
6541 },
6542 );
6543 let save = editor
6544 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6545 .unwrap();
6546 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6547 cx.executor().start_waiting();
6548 save.await;
6549 assert_eq!(
6550 editor.update(cx, |editor, cx| editor.text(cx)),
6551 "one\ntwo\nthree\n"
6552 );
6553 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6554
6555 // For non-dirty buffer, no formatting request should be sent
6556 let save = editor
6557 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6558 .unwrap();
6559 let _pending_format_request = fake_server
6560 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6561 panic!("Should not be invoked on non-dirty buffer");
6562 })
6563 .next();
6564 cx.executor().start_waiting();
6565 save.await;
6566
6567 // Set Rust language override and assert overridden tabsize is sent to language server
6568 update_test_language_settings(cx, |settings| {
6569 settings.languages.insert(
6570 "Rust".into(),
6571 LanguageSettingsContent {
6572 tab_size: NonZeroU32::new(8),
6573 ..Default::default()
6574 },
6575 );
6576 });
6577
6578 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6579 assert!(cx.read(|cx| editor.is_dirty(cx)));
6580 let save = editor
6581 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6582 .unwrap();
6583 fake_server
6584 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6585 assert_eq!(
6586 params.text_document.uri,
6587 lsp::Url::from_file_path("/file.rs").unwrap()
6588 );
6589 assert_eq!(params.options.tab_size, 8);
6590 Ok(Some(vec![]))
6591 })
6592 .next()
6593 .await;
6594 cx.executor().start_waiting();
6595 save.await;
6596}
6597
6598#[gpui::test]
6599async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
6600 init_test(cx, |settings| {
6601 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
6602 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
6603 ))
6604 });
6605
6606 let fs = FakeFs::new(cx.executor());
6607 fs.insert_file("/file.rs", Default::default()).await;
6608
6609 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6610
6611 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6612 language_registry.add(Arc::new(Language::new(
6613 LanguageConfig {
6614 name: "Rust".into(),
6615 matcher: LanguageMatcher {
6616 path_suffixes: vec!["rs".to_string()],
6617 ..Default::default()
6618 },
6619 ..LanguageConfig::default()
6620 },
6621 Some(tree_sitter_rust::language()),
6622 )));
6623 update_test_language_settings(cx, |settings| {
6624 // Enable Prettier formatting for the same buffer, and ensure
6625 // LSP is called instead of Prettier.
6626 settings.defaults.prettier = Some(PrettierSettings {
6627 allowed: true,
6628 ..PrettierSettings::default()
6629 });
6630 });
6631 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6632 "Rust",
6633 FakeLspAdapter {
6634 capabilities: lsp::ServerCapabilities {
6635 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6636 ..Default::default()
6637 },
6638 ..Default::default()
6639 },
6640 );
6641
6642 let buffer = project
6643 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6644 .await
6645 .unwrap();
6646
6647 cx.executor().start_waiting();
6648 let fake_server = fake_servers.next().await.unwrap();
6649
6650 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6651 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6652 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6653
6654 let format = editor
6655 .update(cx, |editor, cx| {
6656 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
6657 })
6658 .unwrap();
6659 fake_server
6660 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6661 assert_eq!(
6662 params.text_document.uri,
6663 lsp::Url::from_file_path("/file.rs").unwrap()
6664 );
6665 assert_eq!(params.options.tab_size, 4);
6666 Ok(Some(vec![lsp::TextEdit::new(
6667 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6668 ", ".to_string(),
6669 )]))
6670 })
6671 .next()
6672 .await;
6673 cx.executor().start_waiting();
6674 format.await;
6675 assert_eq!(
6676 editor.update(cx, |editor, cx| editor.text(cx)),
6677 "one, two\nthree\n"
6678 );
6679
6680 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6681 // Ensure we don't lock if formatting hangs.
6682 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6683 assert_eq!(
6684 params.text_document.uri,
6685 lsp::Url::from_file_path("/file.rs").unwrap()
6686 );
6687 futures::future::pending::<()>().await;
6688 unreachable!()
6689 });
6690 let format = editor
6691 .update(cx, |editor, cx| {
6692 editor.perform_format(project, FormatTrigger::Manual, cx)
6693 })
6694 .unwrap();
6695 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6696 cx.executor().start_waiting();
6697 format.await;
6698 assert_eq!(
6699 editor.update(cx, |editor, cx| editor.text(cx)),
6700 "one\ntwo\nthree\n"
6701 );
6702}
6703
6704#[gpui::test]
6705async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
6706 init_test(cx, |_| {});
6707
6708 let mut cx = EditorLspTestContext::new_rust(
6709 lsp::ServerCapabilities {
6710 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6711 ..Default::default()
6712 },
6713 cx,
6714 )
6715 .await;
6716
6717 cx.set_state(indoc! {"
6718 one.twoˇ
6719 "});
6720
6721 // The format request takes a long time. When it completes, it inserts
6722 // a newline and an indent before the `.`
6723 cx.lsp
6724 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
6725 let executor = cx.background_executor().clone();
6726 async move {
6727 executor.timer(Duration::from_millis(100)).await;
6728 Ok(Some(vec![lsp::TextEdit {
6729 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
6730 new_text: "\n ".into(),
6731 }]))
6732 }
6733 });
6734
6735 // Submit a format request.
6736 let format_1 = cx
6737 .update_editor(|editor, cx| editor.format(&Format, cx))
6738 .unwrap();
6739 cx.executor().run_until_parked();
6740
6741 // Submit a second format request.
6742 let format_2 = cx
6743 .update_editor(|editor, cx| editor.format(&Format, cx))
6744 .unwrap();
6745 cx.executor().run_until_parked();
6746
6747 // Wait for both format requests to complete
6748 cx.executor().advance_clock(Duration::from_millis(200));
6749 cx.executor().start_waiting();
6750 format_1.await.unwrap();
6751 cx.executor().start_waiting();
6752 format_2.await.unwrap();
6753
6754 // The formatting edits only happens once.
6755 cx.assert_editor_state(indoc! {"
6756 one
6757 .twoˇ
6758 "});
6759}
6760
6761#[gpui::test]
6762async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
6763 init_test(cx, |settings| {
6764 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
6765 });
6766
6767 let mut cx = EditorLspTestContext::new_rust(
6768 lsp::ServerCapabilities {
6769 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6770 ..Default::default()
6771 },
6772 cx,
6773 )
6774 .await;
6775
6776 // Set up a buffer white some trailing whitespace and no trailing newline.
6777 cx.set_state(
6778 &[
6779 "one ", //
6780 "twoˇ", //
6781 "three ", //
6782 "four", //
6783 ]
6784 .join("\n"),
6785 );
6786
6787 // Submit a format request.
6788 let format = cx
6789 .update_editor(|editor, cx| editor.format(&Format, cx))
6790 .unwrap();
6791
6792 // Record which buffer changes have been sent to the language server
6793 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
6794 cx.lsp
6795 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
6796 let buffer_changes = buffer_changes.clone();
6797 move |params, _| {
6798 buffer_changes.lock().extend(
6799 params
6800 .content_changes
6801 .into_iter()
6802 .map(|e| (e.range.unwrap(), e.text)),
6803 );
6804 }
6805 });
6806
6807 // Handle formatting requests to the language server.
6808 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
6809 let buffer_changes = buffer_changes.clone();
6810 move |_, _| {
6811 // When formatting is requested, trailing whitespace has already been stripped,
6812 // and the trailing newline has already been added.
6813 assert_eq!(
6814 &buffer_changes.lock()[1..],
6815 &[
6816 (
6817 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
6818 "".into()
6819 ),
6820 (
6821 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
6822 "".into()
6823 ),
6824 (
6825 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
6826 "\n".into()
6827 ),
6828 ]
6829 );
6830
6831 // Insert blank lines between each line of the buffer.
6832 async move {
6833 Ok(Some(vec![
6834 lsp::TextEdit {
6835 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
6836 new_text: "\n".into(),
6837 },
6838 lsp::TextEdit {
6839 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
6840 new_text: "\n".into(),
6841 },
6842 ]))
6843 }
6844 }
6845 });
6846
6847 // After formatting the buffer, the trailing whitespace is stripped,
6848 // a newline is appended, and the edits provided by the language server
6849 // have been applied.
6850 format.await.unwrap();
6851 cx.assert_editor_state(
6852 &[
6853 "one", //
6854 "", //
6855 "twoˇ", //
6856 "", //
6857 "three", //
6858 "four", //
6859 "", //
6860 ]
6861 .join("\n"),
6862 );
6863
6864 // Undoing the formatting undoes the trailing whitespace removal, the
6865 // trailing newline, and the LSP edits.
6866 cx.update_buffer(|buffer, cx| buffer.undo(cx));
6867 cx.assert_editor_state(
6868 &[
6869 "one ", //
6870 "twoˇ", //
6871 "three ", //
6872 "four", //
6873 ]
6874 .join("\n"),
6875 );
6876}
6877
6878#[gpui::test]
6879async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
6880 cx: &mut gpui::TestAppContext,
6881) {
6882 init_test(cx, |_| {});
6883
6884 cx.update(|cx| {
6885 cx.update_global::<SettingsStore, _>(|settings, cx| {
6886 settings.update_user_settings::<EditorSettings>(cx, |settings| {
6887 settings.auto_signature_help = Some(true);
6888 });
6889 });
6890 });
6891
6892 let mut cx = EditorLspTestContext::new_rust(
6893 lsp::ServerCapabilities {
6894 signature_help_provider: Some(lsp::SignatureHelpOptions {
6895 ..Default::default()
6896 }),
6897 ..Default::default()
6898 },
6899 cx,
6900 )
6901 .await;
6902
6903 let language = Language::new(
6904 LanguageConfig {
6905 name: "Rust".into(),
6906 brackets: BracketPairConfig {
6907 pairs: vec![
6908 BracketPair {
6909 start: "{".to_string(),
6910 end: "}".to_string(),
6911 close: true,
6912 surround: true,
6913 newline: true,
6914 },
6915 BracketPair {
6916 start: "(".to_string(),
6917 end: ")".to_string(),
6918 close: true,
6919 surround: true,
6920 newline: true,
6921 },
6922 BracketPair {
6923 start: "/*".to_string(),
6924 end: " */".to_string(),
6925 close: true,
6926 surround: true,
6927 newline: true,
6928 },
6929 BracketPair {
6930 start: "[".to_string(),
6931 end: "]".to_string(),
6932 close: false,
6933 surround: false,
6934 newline: true,
6935 },
6936 BracketPair {
6937 start: "\"".to_string(),
6938 end: "\"".to_string(),
6939 close: true,
6940 surround: true,
6941 newline: false,
6942 },
6943 BracketPair {
6944 start: "<".to_string(),
6945 end: ">".to_string(),
6946 close: false,
6947 surround: true,
6948 newline: true,
6949 },
6950 ],
6951 ..Default::default()
6952 },
6953 autoclose_before: "})]".to_string(),
6954 ..Default::default()
6955 },
6956 Some(tree_sitter_rust::language()),
6957 );
6958 let language = Arc::new(language);
6959
6960 cx.language_registry().add(language.clone());
6961 cx.update_buffer(|buffer, cx| {
6962 buffer.set_language(Some(language), cx);
6963 });
6964
6965 cx.set_state(
6966 &r#"
6967 fn main() {
6968 sampleˇ
6969 }
6970 "#
6971 .unindent(),
6972 );
6973
6974 cx.update_editor(|view, cx| {
6975 view.handle_input("(", cx);
6976 });
6977 cx.assert_editor_state(
6978 &"
6979 fn main() {
6980 sample(ˇ)
6981 }
6982 "
6983 .unindent(),
6984 );
6985
6986 let mocked_response = lsp::SignatureHelp {
6987 signatures: vec![lsp::SignatureInformation {
6988 label: "fn sample(param1: u8, param2: u8)".to_string(),
6989 documentation: None,
6990 parameters: Some(vec![
6991 lsp::ParameterInformation {
6992 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
6993 documentation: None,
6994 },
6995 lsp::ParameterInformation {
6996 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
6997 documentation: None,
6998 },
6999 ]),
7000 active_parameter: None,
7001 }],
7002 active_signature: Some(0),
7003 active_parameter: Some(0),
7004 };
7005 handle_signature_help_request(&mut cx, mocked_response).await;
7006
7007 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7008 .await;
7009
7010 cx.editor(|editor, _| {
7011 let signature_help_state = editor.signature_help_state.popover().cloned();
7012 assert!(signature_help_state.is_some());
7013 let ParsedMarkdown {
7014 text, highlights, ..
7015 } = signature_help_state.unwrap().parsed_content;
7016 assert_eq!(text, "param1: u8, param2: u8");
7017 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7018 });
7019}
7020
7021#[gpui::test]
7022async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7023 init_test(cx, |_| {});
7024
7025 cx.update(|cx| {
7026 cx.update_global::<SettingsStore, _>(|settings, cx| {
7027 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7028 settings.auto_signature_help = Some(false);
7029 settings.show_signature_help_after_edits = Some(false);
7030 });
7031 });
7032 });
7033
7034 let mut cx = EditorLspTestContext::new_rust(
7035 lsp::ServerCapabilities {
7036 signature_help_provider: Some(lsp::SignatureHelpOptions {
7037 ..Default::default()
7038 }),
7039 ..Default::default()
7040 },
7041 cx,
7042 )
7043 .await;
7044
7045 let language = Language::new(
7046 LanguageConfig {
7047 name: "Rust".into(),
7048 brackets: BracketPairConfig {
7049 pairs: vec![
7050 BracketPair {
7051 start: "{".to_string(),
7052 end: "}".to_string(),
7053 close: true,
7054 surround: true,
7055 newline: true,
7056 },
7057 BracketPair {
7058 start: "(".to_string(),
7059 end: ")".to_string(),
7060 close: true,
7061 surround: true,
7062 newline: true,
7063 },
7064 BracketPair {
7065 start: "/*".to_string(),
7066 end: " */".to_string(),
7067 close: true,
7068 surround: true,
7069 newline: true,
7070 },
7071 BracketPair {
7072 start: "[".to_string(),
7073 end: "]".to_string(),
7074 close: false,
7075 surround: false,
7076 newline: true,
7077 },
7078 BracketPair {
7079 start: "\"".to_string(),
7080 end: "\"".to_string(),
7081 close: true,
7082 surround: true,
7083 newline: false,
7084 },
7085 BracketPair {
7086 start: "<".to_string(),
7087 end: ">".to_string(),
7088 close: false,
7089 surround: true,
7090 newline: true,
7091 },
7092 ],
7093 ..Default::default()
7094 },
7095 autoclose_before: "})]".to_string(),
7096 ..Default::default()
7097 },
7098 Some(tree_sitter_rust::language()),
7099 );
7100 let language = Arc::new(language);
7101
7102 cx.language_registry().add(language.clone());
7103 cx.update_buffer(|buffer, cx| {
7104 buffer.set_language(Some(language), cx);
7105 });
7106
7107 // Ensure that signature_help is not called when no signature help is enabled.
7108 cx.set_state(
7109 &r#"
7110 fn main() {
7111 sampleˇ
7112 }
7113 "#
7114 .unindent(),
7115 );
7116 cx.update_editor(|view, cx| {
7117 view.handle_input("(", cx);
7118 });
7119 cx.assert_editor_state(
7120 &"
7121 fn main() {
7122 sample(ˇ)
7123 }
7124 "
7125 .unindent(),
7126 );
7127 cx.editor(|editor, _| {
7128 assert!(editor.signature_help_state.task().is_none());
7129 });
7130
7131 let mocked_response = lsp::SignatureHelp {
7132 signatures: vec![lsp::SignatureInformation {
7133 label: "fn sample(param1: u8, param2: u8)".to_string(),
7134 documentation: None,
7135 parameters: Some(vec![
7136 lsp::ParameterInformation {
7137 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7138 documentation: None,
7139 },
7140 lsp::ParameterInformation {
7141 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7142 documentation: None,
7143 },
7144 ]),
7145 active_parameter: None,
7146 }],
7147 active_signature: Some(0),
7148 active_parameter: Some(0),
7149 };
7150
7151 // Ensure that signature_help is called when enabled afte edits
7152 cx.update(|cx| {
7153 cx.update_global::<SettingsStore, _>(|settings, cx| {
7154 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7155 settings.auto_signature_help = Some(false);
7156 settings.show_signature_help_after_edits = Some(true);
7157 });
7158 });
7159 });
7160 cx.set_state(
7161 &r#"
7162 fn main() {
7163 sampleˇ
7164 }
7165 "#
7166 .unindent(),
7167 );
7168 cx.update_editor(|view, cx| {
7169 view.handle_input("(", cx);
7170 });
7171 cx.assert_editor_state(
7172 &"
7173 fn main() {
7174 sample(ˇ)
7175 }
7176 "
7177 .unindent(),
7178 );
7179 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7180 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7181 .await;
7182 cx.update_editor(|editor, _| {
7183 let signature_help_state = editor.signature_help_state.popover().cloned();
7184 assert!(signature_help_state.is_some());
7185 let ParsedMarkdown {
7186 text, highlights, ..
7187 } = signature_help_state.unwrap().parsed_content;
7188 assert_eq!(text, "param1: u8, param2: u8");
7189 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7190 editor.signature_help_state = SignatureHelpState::default();
7191 });
7192
7193 // Ensure that signature_help is called when auto signature help override is enabled
7194 cx.update(|cx| {
7195 cx.update_global::<SettingsStore, _>(|settings, cx| {
7196 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7197 settings.auto_signature_help = Some(true);
7198 settings.show_signature_help_after_edits = Some(false);
7199 });
7200 });
7201 });
7202 cx.set_state(
7203 &r#"
7204 fn main() {
7205 sampleˇ
7206 }
7207 "#
7208 .unindent(),
7209 );
7210 cx.update_editor(|view, cx| {
7211 view.handle_input("(", cx);
7212 });
7213 cx.assert_editor_state(
7214 &"
7215 fn main() {
7216 sample(ˇ)
7217 }
7218 "
7219 .unindent(),
7220 );
7221 handle_signature_help_request(&mut cx, mocked_response).await;
7222 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7223 .await;
7224 cx.editor(|editor, _| {
7225 let signature_help_state = editor.signature_help_state.popover().cloned();
7226 assert!(signature_help_state.is_some());
7227 let ParsedMarkdown {
7228 text, highlights, ..
7229 } = signature_help_state.unwrap().parsed_content;
7230 assert_eq!(text, "param1: u8, param2: u8");
7231 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7232 });
7233}
7234
7235#[gpui::test]
7236async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7237 init_test(cx, |_| {});
7238 cx.update(|cx| {
7239 cx.update_global::<SettingsStore, _>(|settings, cx| {
7240 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7241 settings.auto_signature_help = Some(true);
7242 });
7243 });
7244 });
7245
7246 let mut cx = EditorLspTestContext::new_rust(
7247 lsp::ServerCapabilities {
7248 signature_help_provider: Some(lsp::SignatureHelpOptions {
7249 ..Default::default()
7250 }),
7251 ..Default::default()
7252 },
7253 cx,
7254 )
7255 .await;
7256
7257 // A test that directly calls `show_signature_help`
7258 cx.update_editor(|editor, cx| {
7259 editor.show_signature_help(&ShowSignatureHelp, cx);
7260 });
7261
7262 let mocked_response = lsp::SignatureHelp {
7263 signatures: vec![lsp::SignatureInformation {
7264 label: "fn sample(param1: u8, param2: u8)".to_string(),
7265 documentation: None,
7266 parameters: Some(vec![
7267 lsp::ParameterInformation {
7268 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7269 documentation: None,
7270 },
7271 lsp::ParameterInformation {
7272 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7273 documentation: None,
7274 },
7275 ]),
7276 active_parameter: None,
7277 }],
7278 active_signature: Some(0),
7279 active_parameter: Some(0),
7280 };
7281 handle_signature_help_request(&mut cx, mocked_response).await;
7282
7283 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7284 .await;
7285
7286 cx.editor(|editor, _| {
7287 let signature_help_state = editor.signature_help_state.popover().cloned();
7288 assert!(signature_help_state.is_some());
7289 let ParsedMarkdown {
7290 text, highlights, ..
7291 } = signature_help_state.unwrap().parsed_content;
7292 assert_eq!(text, "param1: u8, param2: u8");
7293 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7294 });
7295
7296 // When exiting outside from inside the brackets, `signature_help` is closed.
7297 cx.set_state(indoc! {"
7298 fn main() {
7299 sample(ˇ);
7300 }
7301
7302 fn sample(param1: u8, param2: u8) {}
7303 "});
7304
7305 cx.update_editor(|editor, cx| {
7306 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
7307 });
7308
7309 let mocked_response = lsp::SignatureHelp {
7310 signatures: Vec::new(),
7311 active_signature: None,
7312 active_parameter: None,
7313 };
7314 handle_signature_help_request(&mut cx, mocked_response).await;
7315
7316 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7317 .await;
7318
7319 cx.editor(|editor, _| {
7320 assert!(!editor.signature_help_state.is_shown());
7321 });
7322
7323 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
7324 cx.set_state(indoc! {"
7325 fn main() {
7326 sample(ˇ);
7327 }
7328
7329 fn sample(param1: u8, param2: u8) {}
7330 "});
7331
7332 let mocked_response = lsp::SignatureHelp {
7333 signatures: vec![lsp::SignatureInformation {
7334 label: "fn sample(param1: u8, param2: u8)".to_string(),
7335 documentation: None,
7336 parameters: Some(vec![
7337 lsp::ParameterInformation {
7338 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7339 documentation: None,
7340 },
7341 lsp::ParameterInformation {
7342 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7343 documentation: None,
7344 },
7345 ]),
7346 active_parameter: None,
7347 }],
7348 active_signature: Some(0),
7349 active_parameter: Some(0),
7350 };
7351 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7352 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7353 .await;
7354 cx.editor(|editor, _| {
7355 assert!(editor.signature_help_state.is_shown());
7356 });
7357
7358 // Restore the popover with more parameter input
7359 cx.set_state(indoc! {"
7360 fn main() {
7361 sample(param1, param2ˇ);
7362 }
7363
7364 fn sample(param1: u8, param2: u8) {}
7365 "});
7366
7367 let mocked_response = lsp::SignatureHelp {
7368 signatures: vec![lsp::SignatureInformation {
7369 label: "fn sample(param1: u8, param2: u8)".to_string(),
7370 documentation: None,
7371 parameters: Some(vec![
7372 lsp::ParameterInformation {
7373 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7374 documentation: None,
7375 },
7376 lsp::ParameterInformation {
7377 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7378 documentation: None,
7379 },
7380 ]),
7381 active_parameter: None,
7382 }],
7383 active_signature: Some(0),
7384 active_parameter: Some(1),
7385 };
7386 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7387 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7388 .await;
7389
7390 // When selecting a range, the popover is gone.
7391 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
7392 cx.update_editor(|editor, cx| {
7393 editor.change_selections(None, cx, |s| {
7394 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7395 })
7396 });
7397 cx.assert_editor_state(indoc! {"
7398 fn main() {
7399 sample(param1, «ˇparam2»);
7400 }
7401
7402 fn sample(param1: u8, param2: u8) {}
7403 "});
7404 cx.editor(|editor, _| {
7405 assert!(!editor.signature_help_state.is_shown());
7406 });
7407
7408 // When unselecting again, the popover is back if within the brackets.
7409 cx.update_editor(|editor, cx| {
7410 editor.change_selections(None, cx, |s| {
7411 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7412 })
7413 });
7414 cx.assert_editor_state(indoc! {"
7415 fn main() {
7416 sample(param1, ˇparam2);
7417 }
7418
7419 fn sample(param1: u8, param2: u8) {}
7420 "});
7421 handle_signature_help_request(&mut cx, mocked_response).await;
7422 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7423 .await;
7424 cx.editor(|editor, _| {
7425 assert!(editor.signature_help_state.is_shown());
7426 });
7427
7428 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
7429 cx.update_editor(|editor, cx| {
7430 editor.change_selections(None, cx, |s| {
7431 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
7432 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7433 })
7434 });
7435 cx.assert_editor_state(indoc! {"
7436 fn main() {
7437 sample(param1, ˇparam2);
7438 }
7439
7440 fn sample(param1: u8, param2: u8) {}
7441 "});
7442
7443 let mocked_response = lsp::SignatureHelp {
7444 signatures: vec![lsp::SignatureInformation {
7445 label: "fn sample(param1: u8, param2: u8)".to_string(),
7446 documentation: None,
7447 parameters: Some(vec![
7448 lsp::ParameterInformation {
7449 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7450 documentation: None,
7451 },
7452 lsp::ParameterInformation {
7453 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7454 documentation: None,
7455 },
7456 ]),
7457 active_parameter: None,
7458 }],
7459 active_signature: Some(0),
7460 active_parameter: Some(1),
7461 };
7462 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7463 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7464 .await;
7465 cx.update_editor(|editor, cx| {
7466 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
7467 });
7468 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7469 .await;
7470 cx.update_editor(|editor, cx| {
7471 editor.change_selections(None, cx, |s| {
7472 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7473 })
7474 });
7475 cx.assert_editor_state(indoc! {"
7476 fn main() {
7477 sample(param1, «ˇparam2»);
7478 }
7479
7480 fn sample(param1: u8, param2: u8) {}
7481 "});
7482 cx.update_editor(|editor, cx| {
7483 editor.change_selections(None, cx, |s| {
7484 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
7485 })
7486 });
7487 cx.assert_editor_state(indoc! {"
7488 fn main() {
7489 sample(param1, ˇparam2);
7490 }
7491
7492 fn sample(param1: u8, param2: u8) {}
7493 "});
7494 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
7495 .await;
7496}
7497
7498#[gpui::test]
7499async fn test_completion(cx: &mut gpui::TestAppContext) {
7500 init_test(cx, |_| {});
7501
7502 let mut cx = EditorLspTestContext::new_rust(
7503 lsp::ServerCapabilities {
7504 completion_provider: Some(lsp::CompletionOptions {
7505 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
7506 resolve_provider: Some(true),
7507 ..Default::default()
7508 }),
7509 ..Default::default()
7510 },
7511 cx,
7512 )
7513 .await;
7514 let counter = Arc::new(AtomicUsize::new(0));
7515
7516 cx.set_state(indoc! {"
7517 oneˇ
7518 two
7519 three
7520 "});
7521 cx.simulate_keystroke(".");
7522 handle_completion_request(
7523 &mut cx,
7524 indoc! {"
7525 one.|<>
7526 two
7527 three
7528 "},
7529 vec!["first_completion", "second_completion"],
7530 counter.clone(),
7531 )
7532 .await;
7533 cx.condition(|editor, _| editor.context_menu_visible())
7534 .await;
7535 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
7536
7537 let apply_additional_edits = cx.update_editor(|editor, cx| {
7538 editor.context_menu_next(&Default::default(), cx);
7539 editor
7540 .confirm_completion(&ConfirmCompletion::default(), cx)
7541 .unwrap()
7542 });
7543 cx.assert_editor_state(indoc! {"
7544 one.second_completionˇ
7545 two
7546 three
7547 "});
7548
7549 handle_resolve_completion_request(
7550 &mut cx,
7551 Some(vec![
7552 (
7553 //This overlaps with the primary completion edit which is
7554 //misbehavior from the LSP spec, test that we filter it out
7555 indoc! {"
7556 one.second_ˇcompletion
7557 two
7558 threeˇ
7559 "},
7560 "overlapping additional edit",
7561 ),
7562 (
7563 indoc! {"
7564 one.second_completion
7565 two
7566 threeˇ
7567 "},
7568 "\nadditional edit",
7569 ),
7570 ]),
7571 )
7572 .await;
7573 apply_additional_edits.await.unwrap();
7574 cx.assert_editor_state(indoc! {"
7575 one.second_completionˇ
7576 two
7577 three
7578 additional edit
7579 "});
7580
7581 cx.set_state(indoc! {"
7582 one.second_completion
7583 twoˇ
7584 threeˇ
7585 additional edit
7586 "});
7587 cx.simulate_keystroke(" ");
7588 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7589 cx.simulate_keystroke("s");
7590 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7591
7592 cx.assert_editor_state(indoc! {"
7593 one.second_completion
7594 two sˇ
7595 three sˇ
7596 additional edit
7597 "});
7598 handle_completion_request(
7599 &mut cx,
7600 indoc! {"
7601 one.second_completion
7602 two s
7603 three <s|>
7604 additional edit
7605 "},
7606 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
7607 counter.clone(),
7608 )
7609 .await;
7610 cx.condition(|editor, _| editor.context_menu_visible())
7611 .await;
7612 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
7613
7614 cx.simulate_keystroke("i");
7615
7616 handle_completion_request(
7617 &mut cx,
7618 indoc! {"
7619 one.second_completion
7620 two si
7621 three <si|>
7622 additional edit
7623 "},
7624 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
7625 counter.clone(),
7626 )
7627 .await;
7628 cx.condition(|editor, _| editor.context_menu_visible())
7629 .await;
7630 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
7631
7632 let apply_additional_edits = cx.update_editor(|editor, cx| {
7633 editor
7634 .confirm_completion(&ConfirmCompletion::default(), cx)
7635 .unwrap()
7636 });
7637 cx.assert_editor_state(indoc! {"
7638 one.second_completion
7639 two sixth_completionˇ
7640 three sixth_completionˇ
7641 additional edit
7642 "});
7643
7644 handle_resolve_completion_request(&mut cx, None).await;
7645 apply_additional_edits.await.unwrap();
7646
7647 _ = cx.update(|cx| {
7648 cx.update_global::<SettingsStore, _>(|settings, cx| {
7649 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7650 settings.show_completions_on_input = Some(false);
7651 });
7652 })
7653 });
7654 cx.set_state("editorˇ");
7655 cx.simulate_keystroke(".");
7656 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7657 cx.simulate_keystroke("c");
7658 cx.simulate_keystroke("l");
7659 cx.simulate_keystroke("o");
7660 cx.assert_editor_state("editor.cloˇ");
7661 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
7662 cx.update_editor(|editor, cx| {
7663 editor.show_completions(&ShowCompletions { trigger: None }, cx);
7664 });
7665 handle_completion_request(
7666 &mut cx,
7667 "editor.<clo|>",
7668 vec!["close", "clobber"],
7669 counter.clone(),
7670 )
7671 .await;
7672 cx.condition(|editor, _| editor.context_menu_visible())
7673 .await;
7674 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
7675
7676 let apply_additional_edits = cx.update_editor(|editor, cx| {
7677 editor
7678 .confirm_completion(&ConfirmCompletion::default(), cx)
7679 .unwrap()
7680 });
7681 cx.assert_editor_state("editor.closeˇ");
7682 handle_resolve_completion_request(&mut cx, None).await;
7683 apply_additional_edits.await.unwrap();
7684}
7685
7686#[gpui::test]
7687async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
7688 init_test(cx, |_| {});
7689 let mut cx = EditorLspTestContext::new_rust(
7690 lsp::ServerCapabilities {
7691 completion_provider: Some(lsp::CompletionOptions {
7692 trigger_characters: Some(vec![".".to_string()]),
7693 ..Default::default()
7694 }),
7695 ..Default::default()
7696 },
7697 cx,
7698 )
7699 .await;
7700 cx.lsp
7701 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
7702 Ok(Some(lsp::CompletionResponse::Array(vec![
7703 lsp::CompletionItem {
7704 label: "first".into(),
7705 ..Default::default()
7706 },
7707 lsp::CompletionItem {
7708 label: "last".into(),
7709 ..Default::default()
7710 },
7711 ])))
7712 });
7713 cx.set_state("variableˇ");
7714 cx.simulate_keystroke(".");
7715 cx.executor().run_until_parked();
7716
7717 cx.update_editor(|editor, _| {
7718 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7719 assert_eq!(
7720 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
7721 &["first", "last"]
7722 );
7723 } else {
7724 panic!("expected completion menu to be open");
7725 }
7726 });
7727
7728 cx.update_editor(|editor, cx| {
7729 editor.move_page_down(&MovePageDown::default(), cx);
7730 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7731 assert!(
7732 menu.selected_item == 1,
7733 "expected PageDown to select the last item from the context menu"
7734 );
7735 } else {
7736 panic!("expected completion menu to stay open after PageDown");
7737 }
7738 });
7739
7740 cx.update_editor(|editor, cx| {
7741 editor.move_page_up(&MovePageUp::default(), cx);
7742 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7743 assert!(
7744 menu.selected_item == 0,
7745 "expected PageUp to select the first item from the context menu"
7746 );
7747 } else {
7748 panic!("expected completion menu to stay open after PageUp");
7749 }
7750 });
7751}
7752
7753#[gpui::test]
7754async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
7755 init_test(cx, |_| {});
7756
7757 let mut cx = EditorLspTestContext::new_rust(
7758 lsp::ServerCapabilities {
7759 completion_provider: Some(lsp::CompletionOptions {
7760 trigger_characters: Some(vec![".".to_string()]),
7761 resolve_provider: Some(true),
7762 ..Default::default()
7763 }),
7764 ..Default::default()
7765 },
7766 cx,
7767 )
7768 .await;
7769
7770 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
7771 cx.simulate_keystroke(".");
7772 let completion_item = lsp::CompletionItem {
7773 label: "Some".into(),
7774 kind: Some(lsp::CompletionItemKind::SNIPPET),
7775 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
7776 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
7777 kind: lsp::MarkupKind::Markdown,
7778 value: "```rust\nSome(2)\n```".to_string(),
7779 })),
7780 deprecated: Some(false),
7781 sort_text: Some("Some".to_string()),
7782 filter_text: Some("Some".to_string()),
7783 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
7784 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7785 range: lsp::Range {
7786 start: lsp::Position {
7787 line: 0,
7788 character: 22,
7789 },
7790 end: lsp::Position {
7791 line: 0,
7792 character: 22,
7793 },
7794 },
7795 new_text: "Some(2)".to_string(),
7796 })),
7797 additional_text_edits: Some(vec![lsp::TextEdit {
7798 range: lsp::Range {
7799 start: lsp::Position {
7800 line: 0,
7801 character: 20,
7802 },
7803 end: lsp::Position {
7804 line: 0,
7805 character: 22,
7806 },
7807 },
7808 new_text: "".to_string(),
7809 }]),
7810 ..Default::default()
7811 };
7812
7813 let closure_completion_item = completion_item.clone();
7814 let counter = Arc::new(AtomicUsize::new(0));
7815 let counter_clone = counter.clone();
7816 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
7817 let task_completion_item = closure_completion_item.clone();
7818 counter_clone.fetch_add(1, atomic::Ordering::Release);
7819 async move {
7820 Ok(Some(lsp::CompletionResponse::Array(vec![
7821 task_completion_item,
7822 ])))
7823 }
7824 });
7825
7826 cx.condition(|editor, _| editor.context_menu_visible())
7827 .await;
7828 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
7829 assert!(request.next().await.is_some());
7830 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
7831
7832 cx.simulate_keystroke("S");
7833 cx.simulate_keystroke("o");
7834 cx.simulate_keystroke("m");
7835 cx.condition(|editor, _| editor.context_menu_visible())
7836 .await;
7837 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
7838 assert!(request.next().await.is_some());
7839 assert!(request.next().await.is_some());
7840 assert!(request.next().await.is_some());
7841 request.close();
7842 assert!(request.next().await.is_none());
7843 assert_eq!(
7844 counter.load(atomic::Ordering::Acquire),
7845 4,
7846 "With the completions menu open, only one LSP request should happen per input"
7847 );
7848}
7849
7850#[gpui::test]
7851async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
7852 init_test(cx, |_| {});
7853 let mut cx = EditorTestContext::new(cx).await;
7854 let language = Arc::new(Language::new(
7855 LanguageConfig {
7856 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
7857 ..Default::default()
7858 },
7859 Some(tree_sitter_rust::language()),
7860 ));
7861 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
7862
7863 // If multiple selections intersect a line, the line is only toggled once.
7864 cx.set_state(indoc! {"
7865 fn a() {
7866 «//b();
7867 ˇ»// «c();
7868 //ˇ» d();
7869 }
7870 "});
7871
7872 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7873
7874 cx.assert_editor_state(indoc! {"
7875 fn a() {
7876 «b();
7877 c();
7878 ˇ» d();
7879 }
7880 "});
7881
7882 // The comment prefix is inserted at the same column for every line in a
7883 // selection.
7884 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7885
7886 cx.assert_editor_state(indoc! {"
7887 fn a() {
7888 // «b();
7889 // c();
7890 ˇ»// d();
7891 }
7892 "});
7893
7894 // If a selection ends at the beginning of a line, that line is not toggled.
7895 cx.set_selections_state(indoc! {"
7896 fn a() {
7897 // b();
7898 «// c();
7899 ˇ» // d();
7900 }
7901 "});
7902
7903 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7904
7905 cx.assert_editor_state(indoc! {"
7906 fn a() {
7907 // b();
7908 «c();
7909 ˇ» // d();
7910 }
7911 "});
7912
7913 // If a selection span a single line and is empty, the line is toggled.
7914 cx.set_state(indoc! {"
7915 fn a() {
7916 a();
7917 b();
7918 ˇ
7919 }
7920 "});
7921
7922 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7923
7924 cx.assert_editor_state(indoc! {"
7925 fn a() {
7926 a();
7927 b();
7928 //•ˇ
7929 }
7930 "});
7931
7932 // If a selection span multiple lines, empty lines are not toggled.
7933 cx.set_state(indoc! {"
7934 fn a() {
7935 «a();
7936
7937 c();ˇ»
7938 }
7939 "});
7940
7941 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7942
7943 cx.assert_editor_state(indoc! {"
7944 fn a() {
7945 // «a();
7946
7947 // c();ˇ»
7948 }
7949 "});
7950
7951 // If a selection includes multiple comment prefixes, all lines are uncommented.
7952 cx.set_state(indoc! {"
7953 fn a() {
7954 «// a();
7955 /// b();
7956 //! c();ˇ»
7957 }
7958 "});
7959
7960 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7961
7962 cx.assert_editor_state(indoc! {"
7963 fn a() {
7964 «a();
7965 b();
7966 c();ˇ»
7967 }
7968 "});
7969}
7970
7971#[gpui::test]
7972async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
7973 init_test(cx, |_| {});
7974
7975 let language = Arc::new(Language::new(
7976 LanguageConfig {
7977 line_comments: vec!["// ".into()],
7978 ..Default::default()
7979 },
7980 Some(tree_sitter_rust::language()),
7981 ));
7982
7983 let mut cx = EditorTestContext::new(cx).await;
7984
7985 cx.language_registry().add(language.clone());
7986 cx.update_buffer(|buffer, cx| {
7987 buffer.set_language(Some(language), cx);
7988 });
7989
7990 let toggle_comments = &ToggleComments {
7991 advance_downwards: true,
7992 };
7993
7994 // Single cursor on one line -> advance
7995 // Cursor moves horizontally 3 characters as well on non-blank line
7996 cx.set_state(indoc!(
7997 "fn a() {
7998 ˇdog();
7999 cat();
8000 }"
8001 ));
8002 cx.update_editor(|editor, cx| {
8003 editor.toggle_comments(toggle_comments, cx);
8004 });
8005 cx.assert_editor_state(indoc!(
8006 "fn a() {
8007 // dog();
8008 catˇ();
8009 }"
8010 ));
8011
8012 // Single selection on one line -> don't advance
8013 cx.set_state(indoc!(
8014 "fn a() {
8015 «dog()ˇ»;
8016 cat();
8017 }"
8018 ));
8019 cx.update_editor(|editor, cx| {
8020 editor.toggle_comments(toggle_comments, cx);
8021 });
8022 cx.assert_editor_state(indoc!(
8023 "fn a() {
8024 // «dog()ˇ»;
8025 cat();
8026 }"
8027 ));
8028
8029 // Multiple cursors on one line -> advance
8030 cx.set_state(indoc!(
8031 "fn a() {
8032 ˇdˇog();
8033 cat();
8034 }"
8035 ));
8036 cx.update_editor(|editor, cx| {
8037 editor.toggle_comments(toggle_comments, cx);
8038 });
8039 cx.assert_editor_state(indoc!(
8040 "fn a() {
8041 // dog();
8042 catˇ(ˇ);
8043 }"
8044 ));
8045
8046 // Multiple cursors on one line, with selection -> don't advance
8047 cx.set_state(indoc!(
8048 "fn a() {
8049 ˇdˇog«()ˇ»;
8050 cat();
8051 }"
8052 ));
8053 cx.update_editor(|editor, cx| {
8054 editor.toggle_comments(toggle_comments, cx);
8055 });
8056 cx.assert_editor_state(indoc!(
8057 "fn a() {
8058 // ˇdˇog«()ˇ»;
8059 cat();
8060 }"
8061 ));
8062
8063 // Single cursor on one line -> advance
8064 // Cursor moves to column 0 on blank line
8065 cx.set_state(indoc!(
8066 "fn a() {
8067 ˇdog();
8068
8069 cat();
8070 }"
8071 ));
8072 cx.update_editor(|editor, cx| {
8073 editor.toggle_comments(toggle_comments, cx);
8074 });
8075 cx.assert_editor_state(indoc!(
8076 "fn a() {
8077 // dog();
8078 ˇ
8079 cat();
8080 }"
8081 ));
8082
8083 // Single cursor on one line -> advance
8084 // Cursor starts and ends at column 0
8085 cx.set_state(indoc!(
8086 "fn a() {
8087 ˇ dog();
8088 cat();
8089 }"
8090 ));
8091 cx.update_editor(|editor, cx| {
8092 editor.toggle_comments(toggle_comments, cx);
8093 });
8094 cx.assert_editor_state(indoc!(
8095 "fn a() {
8096 // dog();
8097 ˇ cat();
8098 }"
8099 ));
8100}
8101
8102#[gpui::test]
8103async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
8104 init_test(cx, |_| {});
8105
8106 let mut cx = EditorTestContext::new(cx).await;
8107
8108 let html_language = Arc::new(
8109 Language::new(
8110 LanguageConfig {
8111 name: "HTML".into(),
8112 block_comment: Some(("<!-- ".into(), " -->".into())),
8113 ..Default::default()
8114 },
8115 Some(tree_sitter_html::language()),
8116 )
8117 .with_injection_query(
8118 r#"
8119 (script_element
8120 (raw_text) @content
8121 (#set! "language" "javascript"))
8122 "#,
8123 )
8124 .unwrap(),
8125 );
8126
8127 let javascript_language = Arc::new(Language::new(
8128 LanguageConfig {
8129 name: "JavaScript".into(),
8130 line_comments: vec!["// ".into()],
8131 ..Default::default()
8132 },
8133 Some(tree_sitter_typescript::language_tsx()),
8134 ));
8135
8136 cx.language_registry().add(html_language.clone());
8137 cx.language_registry().add(javascript_language.clone());
8138 cx.update_buffer(|buffer, cx| {
8139 buffer.set_language(Some(html_language), cx);
8140 });
8141
8142 // Toggle comments for empty selections
8143 cx.set_state(
8144 &r#"
8145 <p>A</p>ˇ
8146 <p>B</p>ˇ
8147 <p>C</p>ˇ
8148 "#
8149 .unindent(),
8150 );
8151 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8152 cx.assert_editor_state(
8153 &r#"
8154 <!-- <p>A</p>ˇ -->
8155 <!-- <p>B</p>ˇ -->
8156 <!-- <p>C</p>ˇ -->
8157 "#
8158 .unindent(),
8159 );
8160 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8161 cx.assert_editor_state(
8162 &r#"
8163 <p>A</p>ˇ
8164 <p>B</p>ˇ
8165 <p>C</p>ˇ
8166 "#
8167 .unindent(),
8168 );
8169
8170 // Toggle comments for mixture of empty and non-empty selections, where
8171 // multiple selections occupy a given line.
8172 cx.set_state(
8173 &r#"
8174 <p>A«</p>
8175 <p>ˇ»B</p>ˇ
8176 <p>C«</p>
8177 <p>ˇ»D</p>ˇ
8178 "#
8179 .unindent(),
8180 );
8181
8182 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8183 cx.assert_editor_state(
8184 &r#"
8185 <!-- <p>A«</p>
8186 <p>ˇ»B</p>ˇ -->
8187 <!-- <p>C«</p>
8188 <p>ˇ»D</p>ˇ -->
8189 "#
8190 .unindent(),
8191 );
8192 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8193 cx.assert_editor_state(
8194 &r#"
8195 <p>A«</p>
8196 <p>ˇ»B</p>ˇ
8197 <p>C«</p>
8198 <p>ˇ»D</p>ˇ
8199 "#
8200 .unindent(),
8201 );
8202
8203 // Toggle comments when different languages are active for different
8204 // selections.
8205 cx.set_state(
8206 &r#"
8207 ˇ<script>
8208 ˇvar x = new Y();
8209 ˇ</script>
8210 "#
8211 .unindent(),
8212 );
8213 cx.executor().run_until_parked();
8214 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8215 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
8216 // Uncommenting and commenting from this position brings in even more wrong artifacts.
8217 cx.assert_editor_state(
8218 &r#"
8219 <!-- ˇ<script> -->
8220 // ˇvar x = new Y();
8221 // ˇ</script>
8222 "#
8223 .unindent(),
8224 );
8225}
8226
8227#[gpui::test]
8228fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
8229 init_test(cx, |_| {});
8230
8231 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8232 let multibuffer = cx.new_model(|cx| {
8233 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8234 multibuffer.push_excerpts(
8235 buffer.clone(),
8236 [
8237 ExcerptRange {
8238 context: Point::new(0, 0)..Point::new(0, 4),
8239 primary: None,
8240 },
8241 ExcerptRange {
8242 context: Point::new(1, 0)..Point::new(1, 4),
8243 primary: None,
8244 },
8245 ],
8246 cx,
8247 );
8248 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
8249 multibuffer
8250 });
8251
8252 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8253 _ = view.update(cx, |view, cx| {
8254 assert_eq!(view.text(cx), "aaaa\nbbbb");
8255 view.change_selections(None, cx, |s| {
8256 s.select_ranges([
8257 Point::new(0, 0)..Point::new(0, 0),
8258 Point::new(1, 0)..Point::new(1, 0),
8259 ])
8260 });
8261
8262 view.handle_input("X", cx);
8263 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
8264 assert_eq!(
8265 view.selections.ranges(cx),
8266 [
8267 Point::new(0, 1)..Point::new(0, 1),
8268 Point::new(1, 1)..Point::new(1, 1),
8269 ]
8270 );
8271
8272 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
8273 view.change_selections(None, cx, |s| {
8274 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
8275 });
8276 view.backspace(&Default::default(), cx);
8277 assert_eq!(view.text(cx), "Xa\nbbb");
8278 assert_eq!(
8279 view.selections.ranges(cx),
8280 [Point::new(1, 0)..Point::new(1, 0)]
8281 );
8282
8283 view.change_selections(None, cx, |s| {
8284 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
8285 });
8286 view.backspace(&Default::default(), cx);
8287 assert_eq!(view.text(cx), "X\nbb");
8288 assert_eq!(
8289 view.selections.ranges(cx),
8290 [Point::new(0, 1)..Point::new(0, 1)]
8291 );
8292 });
8293}
8294
8295#[gpui::test]
8296fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
8297 init_test(cx, |_| {});
8298
8299 let markers = vec![('[', ']').into(), ('(', ')').into()];
8300 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
8301 indoc! {"
8302 [aaaa
8303 (bbbb]
8304 cccc)",
8305 },
8306 markers.clone(),
8307 );
8308 let excerpt_ranges = markers.into_iter().map(|marker| {
8309 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
8310 ExcerptRange {
8311 context,
8312 primary: None,
8313 }
8314 });
8315 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
8316 let multibuffer = cx.new_model(|cx| {
8317 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8318 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
8319 multibuffer
8320 });
8321
8322 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
8323 _ = view.update(cx, |view, cx| {
8324 let (expected_text, selection_ranges) = marked_text_ranges(
8325 indoc! {"
8326 aaaa
8327 bˇbbb
8328 bˇbbˇb
8329 cccc"
8330 },
8331 true,
8332 );
8333 assert_eq!(view.text(cx), expected_text);
8334 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
8335
8336 view.handle_input("X", cx);
8337
8338 let (expected_text, expected_selections) = marked_text_ranges(
8339 indoc! {"
8340 aaaa
8341 bXˇbbXb
8342 bXˇbbXˇb
8343 cccc"
8344 },
8345 false,
8346 );
8347 assert_eq!(view.text(cx), expected_text);
8348 assert_eq!(view.selections.ranges(cx), expected_selections);
8349
8350 view.newline(&Newline, cx);
8351 let (expected_text, expected_selections) = marked_text_ranges(
8352 indoc! {"
8353 aaaa
8354 bX
8355 ˇbbX
8356 b
8357 bX
8358 ˇbbX
8359 ˇb
8360 cccc"
8361 },
8362 false,
8363 );
8364 assert_eq!(view.text(cx), expected_text);
8365 assert_eq!(view.selections.ranges(cx), expected_selections);
8366 });
8367}
8368
8369#[gpui::test]
8370fn test_refresh_selections(cx: &mut TestAppContext) {
8371 init_test(cx, |_| {});
8372
8373 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8374 let mut excerpt1_id = None;
8375 let multibuffer = cx.new_model(|cx| {
8376 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8377 excerpt1_id = multibuffer
8378 .push_excerpts(
8379 buffer.clone(),
8380 [
8381 ExcerptRange {
8382 context: Point::new(0, 0)..Point::new(1, 4),
8383 primary: None,
8384 },
8385 ExcerptRange {
8386 context: Point::new(1, 0)..Point::new(2, 4),
8387 primary: None,
8388 },
8389 ],
8390 cx,
8391 )
8392 .into_iter()
8393 .next();
8394 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8395 multibuffer
8396 });
8397
8398 let editor = cx.add_window(|cx| {
8399 let mut editor = build_editor(multibuffer.clone(), cx);
8400 let snapshot = editor.snapshot(cx);
8401 editor.change_selections(None, cx, |s| {
8402 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
8403 });
8404 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
8405 assert_eq!(
8406 editor.selections.ranges(cx),
8407 [
8408 Point::new(1, 3)..Point::new(1, 3),
8409 Point::new(2, 1)..Point::new(2, 1),
8410 ]
8411 );
8412 editor
8413 });
8414
8415 // Refreshing selections is a no-op when excerpts haven't changed.
8416 _ = editor.update(cx, |editor, cx| {
8417 editor.change_selections(None, cx, |s| s.refresh());
8418 assert_eq!(
8419 editor.selections.ranges(cx),
8420 [
8421 Point::new(1, 3)..Point::new(1, 3),
8422 Point::new(2, 1)..Point::new(2, 1),
8423 ]
8424 );
8425 });
8426
8427 _ = multibuffer.update(cx, |multibuffer, cx| {
8428 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8429 });
8430 _ = editor.update(cx, |editor, cx| {
8431 // Removing an excerpt causes the first selection to become degenerate.
8432 assert_eq!(
8433 editor.selections.ranges(cx),
8434 [
8435 Point::new(0, 0)..Point::new(0, 0),
8436 Point::new(0, 1)..Point::new(0, 1)
8437 ]
8438 );
8439
8440 // Refreshing selections will relocate the first selection to the original buffer
8441 // location.
8442 editor.change_selections(None, cx, |s| s.refresh());
8443 assert_eq!(
8444 editor.selections.ranges(cx),
8445 [
8446 Point::new(0, 1)..Point::new(0, 1),
8447 Point::new(0, 3)..Point::new(0, 3)
8448 ]
8449 );
8450 assert!(editor.selections.pending_anchor().is_some());
8451 });
8452}
8453
8454#[gpui::test]
8455fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
8456 init_test(cx, |_| {});
8457
8458 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
8459 let mut excerpt1_id = None;
8460 let multibuffer = cx.new_model(|cx| {
8461 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
8462 excerpt1_id = multibuffer
8463 .push_excerpts(
8464 buffer.clone(),
8465 [
8466 ExcerptRange {
8467 context: Point::new(0, 0)..Point::new(1, 4),
8468 primary: None,
8469 },
8470 ExcerptRange {
8471 context: Point::new(1, 0)..Point::new(2, 4),
8472 primary: None,
8473 },
8474 ],
8475 cx,
8476 )
8477 .into_iter()
8478 .next();
8479 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
8480 multibuffer
8481 });
8482
8483 let editor = cx.add_window(|cx| {
8484 let mut editor = build_editor(multibuffer.clone(), cx);
8485 let snapshot = editor.snapshot(cx);
8486 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
8487 assert_eq!(
8488 editor.selections.ranges(cx),
8489 [Point::new(1, 3)..Point::new(1, 3)]
8490 );
8491 editor
8492 });
8493
8494 _ = multibuffer.update(cx, |multibuffer, cx| {
8495 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
8496 });
8497 _ = editor.update(cx, |editor, cx| {
8498 assert_eq!(
8499 editor.selections.ranges(cx),
8500 [Point::new(0, 0)..Point::new(0, 0)]
8501 );
8502
8503 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
8504 editor.change_selections(None, cx, |s| s.refresh());
8505 assert_eq!(
8506 editor.selections.ranges(cx),
8507 [Point::new(0, 3)..Point::new(0, 3)]
8508 );
8509 assert!(editor.selections.pending_anchor().is_some());
8510 });
8511}
8512
8513#[gpui::test]
8514async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
8515 init_test(cx, |_| {});
8516
8517 let language = Arc::new(
8518 Language::new(
8519 LanguageConfig {
8520 brackets: BracketPairConfig {
8521 pairs: vec![
8522 BracketPair {
8523 start: "{".to_string(),
8524 end: "}".to_string(),
8525 close: true,
8526 surround: true,
8527 newline: true,
8528 },
8529 BracketPair {
8530 start: "/* ".to_string(),
8531 end: " */".to_string(),
8532 close: true,
8533 surround: true,
8534 newline: true,
8535 },
8536 ],
8537 ..Default::default()
8538 },
8539 ..Default::default()
8540 },
8541 Some(tree_sitter_rust::language()),
8542 )
8543 .with_indents_query("")
8544 .unwrap(),
8545 );
8546
8547 let text = concat!(
8548 "{ }\n", //
8549 " x\n", //
8550 " /* */\n", //
8551 "x\n", //
8552 "{{} }\n", //
8553 );
8554
8555 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
8556 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
8557 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
8558 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
8559 .await;
8560
8561 _ = view.update(cx, |view, cx| {
8562 view.change_selections(None, cx, |s| {
8563 s.select_display_ranges([
8564 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
8565 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
8566 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
8567 ])
8568 });
8569 view.newline(&Newline, cx);
8570
8571 assert_eq!(
8572 view.buffer().read(cx).read(cx).text(),
8573 concat!(
8574 "{ \n", // Suppress rustfmt
8575 "\n", //
8576 "}\n", //
8577 " x\n", //
8578 " /* \n", //
8579 " \n", //
8580 " */\n", //
8581 "x\n", //
8582 "{{} \n", //
8583 "}\n", //
8584 )
8585 );
8586 });
8587}
8588
8589#[gpui::test]
8590fn test_highlighted_ranges(cx: &mut TestAppContext) {
8591 init_test(cx, |_| {});
8592
8593 let editor = cx.add_window(|cx| {
8594 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
8595 build_editor(buffer.clone(), cx)
8596 });
8597
8598 _ = editor.update(cx, |editor, cx| {
8599 struct Type1;
8600 struct Type2;
8601
8602 let buffer = editor.buffer.read(cx).snapshot(cx);
8603
8604 let anchor_range =
8605 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
8606
8607 editor.highlight_background::<Type1>(
8608 &[
8609 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
8610 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
8611 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
8612 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
8613 ],
8614 |_| Hsla::red(),
8615 cx,
8616 );
8617 editor.highlight_background::<Type2>(
8618 &[
8619 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
8620 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
8621 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
8622 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
8623 ],
8624 |_| Hsla::green(),
8625 cx,
8626 );
8627
8628 let snapshot = editor.snapshot(cx);
8629 let mut highlighted_ranges = editor.background_highlights_in_range(
8630 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
8631 &snapshot,
8632 cx.theme().colors(),
8633 );
8634 // Enforce a consistent ordering based on color without relying on the ordering of the
8635 // highlight's `TypeId` which is non-executor.
8636 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
8637 assert_eq!(
8638 highlighted_ranges,
8639 &[
8640 (
8641 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
8642 Hsla::red(),
8643 ),
8644 (
8645 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
8646 Hsla::red(),
8647 ),
8648 (
8649 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
8650 Hsla::green(),
8651 ),
8652 (
8653 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
8654 Hsla::green(),
8655 ),
8656 ]
8657 );
8658 assert_eq!(
8659 editor.background_highlights_in_range(
8660 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
8661 &snapshot,
8662 cx.theme().colors(),
8663 ),
8664 &[(
8665 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
8666 Hsla::red(),
8667 )]
8668 );
8669 });
8670}
8671
8672#[gpui::test]
8673async fn test_following(cx: &mut gpui::TestAppContext) {
8674 init_test(cx, |_| {});
8675
8676 let fs = FakeFs::new(cx.executor());
8677 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8678
8679 let buffer = project.update(cx, |project, cx| {
8680 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
8681 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
8682 });
8683 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
8684 let follower = cx.update(|cx| {
8685 cx.open_window(
8686 WindowOptions {
8687 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
8688 gpui::Point::new(px(0.), px(0.)),
8689 gpui::Point::new(px(10.), px(80.)),
8690 ))),
8691 ..Default::default()
8692 },
8693 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
8694 )
8695 .unwrap()
8696 });
8697
8698 let is_still_following = Rc::new(RefCell::new(true));
8699 let follower_edit_event_count = Rc::new(RefCell::new(0));
8700 let pending_update = Rc::new(RefCell::new(None));
8701 _ = follower.update(cx, {
8702 let update = pending_update.clone();
8703 let is_still_following = is_still_following.clone();
8704 let follower_edit_event_count = follower_edit_event_count.clone();
8705 |_, cx| {
8706 cx.subscribe(
8707 &leader.root_view(cx).unwrap(),
8708 move |_, leader, event, cx| {
8709 leader
8710 .read(cx)
8711 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
8712 },
8713 )
8714 .detach();
8715
8716 cx.subscribe(
8717 &follower.root_view(cx).unwrap(),
8718 move |_, _, event: &EditorEvent, _cx| {
8719 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
8720 *is_still_following.borrow_mut() = false;
8721 }
8722
8723 if let EditorEvent::BufferEdited = event {
8724 *follower_edit_event_count.borrow_mut() += 1;
8725 }
8726 },
8727 )
8728 .detach();
8729 }
8730 });
8731
8732 // Update the selections only
8733 _ = leader.update(cx, |leader, cx| {
8734 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
8735 });
8736 follower
8737 .update(cx, |follower, cx| {
8738 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8739 })
8740 .unwrap()
8741 .await
8742 .unwrap();
8743 _ = follower.update(cx, |follower, cx| {
8744 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
8745 });
8746 assert_eq!(*is_still_following.borrow(), true);
8747 assert_eq!(*follower_edit_event_count.borrow(), 0);
8748
8749 // Update the scroll position only
8750 _ = leader.update(cx, |leader, cx| {
8751 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), 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 assert_eq!(
8761 follower
8762 .update(cx, |follower, cx| follower.scroll_position(cx))
8763 .unwrap(),
8764 gpui::Point::new(1.5, 3.5)
8765 );
8766 assert_eq!(*is_still_following.borrow(), true);
8767 assert_eq!(*follower_edit_event_count.borrow(), 0);
8768
8769 // Update the selections and scroll position. The follower's scroll position is updated
8770 // via autoscroll, not via the leader's exact scroll position.
8771 _ = leader.update(cx, |leader, cx| {
8772 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
8773 leader.request_autoscroll(Autoscroll::newest(), cx);
8774 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
8775 });
8776 follower
8777 .update(cx, |follower, cx| {
8778 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8779 })
8780 .unwrap()
8781 .await
8782 .unwrap();
8783 _ = follower.update(cx, |follower, cx| {
8784 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
8785 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
8786 });
8787 assert_eq!(*is_still_following.borrow(), true);
8788
8789 // Creating a pending selection that precedes another selection
8790 _ = leader.update(cx, |leader, cx| {
8791 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
8792 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
8793 });
8794 follower
8795 .update(cx, |follower, cx| {
8796 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8797 })
8798 .unwrap()
8799 .await
8800 .unwrap();
8801 _ = follower.update(cx, |follower, cx| {
8802 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
8803 });
8804 assert_eq!(*is_still_following.borrow(), true);
8805
8806 // Extend the pending selection so that it surrounds another selection
8807 _ = leader.update(cx, |leader, cx| {
8808 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
8809 });
8810 follower
8811 .update(cx, |follower, cx| {
8812 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8813 })
8814 .unwrap()
8815 .await
8816 .unwrap();
8817 _ = follower.update(cx, |follower, cx| {
8818 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
8819 });
8820
8821 // Scrolling locally breaks the follow
8822 _ = follower.update(cx, |follower, cx| {
8823 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
8824 follower.set_scroll_anchor(
8825 ScrollAnchor {
8826 anchor: top_anchor,
8827 offset: gpui::Point::new(0.0, 0.5),
8828 },
8829 cx,
8830 );
8831 });
8832 assert_eq!(*is_still_following.borrow(), false);
8833}
8834
8835#[gpui::test]
8836async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
8837 init_test(cx, |_| {});
8838
8839 let fs = FakeFs::new(cx.executor());
8840 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8841 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8842 let pane = workspace
8843 .update(cx, |workspace, _| workspace.active_pane().clone())
8844 .unwrap();
8845
8846 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
8847
8848 let leader = pane.update(cx, |_, cx| {
8849 let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
8850 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
8851 });
8852
8853 // Start following the editor when it has no excerpts.
8854 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
8855 let follower_1 = cx
8856 .update_window(*workspace.deref(), |_, cx| {
8857 Editor::from_state_proto(
8858 workspace.root_view(cx).unwrap(),
8859 ViewId {
8860 creator: Default::default(),
8861 id: 0,
8862 },
8863 &mut state_message,
8864 cx,
8865 )
8866 })
8867 .unwrap()
8868 .unwrap()
8869 .await
8870 .unwrap();
8871
8872 let update_message = Rc::new(RefCell::new(None));
8873 follower_1.update(cx, {
8874 let update = update_message.clone();
8875 |_, cx| {
8876 cx.subscribe(&leader, move |_, leader, event, cx| {
8877 leader
8878 .read(cx)
8879 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
8880 })
8881 .detach();
8882 }
8883 });
8884
8885 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
8886 (
8887 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
8888 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
8889 )
8890 });
8891
8892 // Insert some excerpts.
8893 _ = leader.update(cx, |leader, cx| {
8894 leader.buffer.update(cx, |multibuffer, cx| {
8895 let excerpt_ids = multibuffer.push_excerpts(
8896 buffer_1.clone(),
8897 [
8898 ExcerptRange {
8899 context: 1..6,
8900 primary: None,
8901 },
8902 ExcerptRange {
8903 context: 12..15,
8904 primary: None,
8905 },
8906 ExcerptRange {
8907 context: 0..3,
8908 primary: None,
8909 },
8910 ],
8911 cx,
8912 );
8913 multibuffer.insert_excerpts_after(
8914 excerpt_ids[0],
8915 buffer_2.clone(),
8916 [
8917 ExcerptRange {
8918 context: 8..12,
8919 primary: None,
8920 },
8921 ExcerptRange {
8922 context: 0..6,
8923 primary: None,
8924 },
8925 ],
8926 cx,
8927 );
8928 });
8929 });
8930
8931 // Apply the update of adding the excerpts.
8932 follower_1
8933 .update(cx, |follower, cx| {
8934 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
8935 })
8936 .await
8937 .unwrap();
8938 assert_eq!(
8939 follower_1.update(cx, |editor, cx| editor.text(cx)),
8940 leader.update(cx, |editor, cx| editor.text(cx))
8941 );
8942 update_message.borrow_mut().take();
8943
8944 // Start following separately after it already has excerpts.
8945 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
8946 let follower_2 = cx
8947 .update_window(*workspace.deref(), |_, cx| {
8948 Editor::from_state_proto(
8949 workspace.root_view(cx).unwrap().clone(),
8950 ViewId {
8951 creator: Default::default(),
8952 id: 0,
8953 },
8954 &mut state_message,
8955 cx,
8956 )
8957 })
8958 .unwrap()
8959 .unwrap()
8960 .await
8961 .unwrap();
8962 assert_eq!(
8963 follower_2.update(cx, |editor, cx| editor.text(cx)),
8964 leader.update(cx, |editor, cx| editor.text(cx))
8965 );
8966
8967 // Remove some excerpts.
8968 _ = leader.update(cx, |leader, cx| {
8969 leader.buffer.update(cx, |multibuffer, cx| {
8970 let excerpt_ids = multibuffer.excerpt_ids();
8971 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
8972 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
8973 });
8974 });
8975
8976 // Apply the update of removing the excerpts.
8977 follower_1
8978 .update(cx, |follower, cx| {
8979 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
8980 })
8981 .await
8982 .unwrap();
8983 follower_2
8984 .update(cx, |follower, cx| {
8985 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
8986 })
8987 .await
8988 .unwrap();
8989 update_message.borrow_mut().take();
8990 assert_eq!(
8991 follower_1.update(cx, |editor, cx| editor.text(cx)),
8992 leader.update(cx, |editor, cx| editor.text(cx))
8993 );
8994}
8995
8996#[gpui::test]
8997async fn go_to_prev_overlapping_diagnostic(
8998 executor: BackgroundExecutor,
8999 cx: &mut gpui::TestAppContext,
9000) {
9001 init_test(cx, |_| {});
9002
9003 let mut cx = EditorTestContext::new(cx).await;
9004 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9005
9006 cx.set_state(indoc! {"
9007 ˇfn func(abc def: i32) -> u32 {
9008 }
9009 "});
9010
9011 _ = cx.update(|cx| {
9012 _ = project.update(cx, |project, cx| {
9013 project
9014 .update_diagnostics(
9015 LanguageServerId(0),
9016 lsp::PublishDiagnosticsParams {
9017 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9018 version: None,
9019 diagnostics: vec![
9020 lsp::Diagnostic {
9021 range: lsp::Range::new(
9022 lsp::Position::new(0, 11),
9023 lsp::Position::new(0, 12),
9024 ),
9025 severity: Some(lsp::DiagnosticSeverity::ERROR),
9026 ..Default::default()
9027 },
9028 lsp::Diagnostic {
9029 range: lsp::Range::new(
9030 lsp::Position::new(0, 12),
9031 lsp::Position::new(0, 15),
9032 ),
9033 severity: Some(lsp::DiagnosticSeverity::ERROR),
9034 ..Default::default()
9035 },
9036 lsp::Diagnostic {
9037 range: lsp::Range::new(
9038 lsp::Position::new(0, 25),
9039 lsp::Position::new(0, 28),
9040 ),
9041 severity: Some(lsp::DiagnosticSeverity::ERROR),
9042 ..Default::default()
9043 },
9044 ],
9045 },
9046 &[],
9047 cx,
9048 )
9049 .unwrap()
9050 });
9051 });
9052
9053 executor.run_until_parked();
9054
9055 cx.update_editor(|editor, cx| {
9056 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9057 });
9058
9059 cx.assert_editor_state(indoc! {"
9060 fn func(abc def: i32) -> ˇu32 {
9061 }
9062 "});
9063
9064 cx.update_editor(|editor, cx| {
9065 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9066 });
9067
9068 cx.assert_editor_state(indoc! {"
9069 fn func(abc ˇdef: i32) -> u32 {
9070 }
9071 "});
9072
9073 cx.update_editor(|editor, cx| {
9074 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9075 });
9076
9077 cx.assert_editor_state(indoc! {"
9078 fn func(abcˇ def: i32) -> u32 {
9079 }
9080 "});
9081
9082 cx.update_editor(|editor, cx| {
9083 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9084 });
9085
9086 cx.assert_editor_state(indoc! {"
9087 fn func(abc def: i32) -> ˇu32 {
9088 }
9089 "});
9090}
9091
9092#[gpui::test]
9093async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9094 init_test(cx, |_| {});
9095
9096 let mut cx = EditorTestContext::new(cx).await;
9097
9098 let diff_base = r#"
9099 use some::mod;
9100
9101 const A: u32 = 42;
9102
9103 fn main() {
9104 println!("hello");
9105
9106 println!("world");
9107 }
9108 "#
9109 .unindent();
9110
9111 // Edits are modified, removed, modified, added
9112 cx.set_state(
9113 &r#"
9114 use some::modified;
9115
9116 ˇ
9117 fn main() {
9118 println!("hello there");
9119
9120 println!("around the");
9121 println!("world");
9122 }
9123 "#
9124 .unindent(),
9125 );
9126
9127 cx.set_diff_base(Some(&diff_base));
9128 executor.run_until_parked();
9129
9130 cx.update_editor(|editor, cx| {
9131 //Wrap around the bottom of the buffer
9132 for _ in 0..3 {
9133 editor.go_to_hunk(&GoToHunk, cx);
9134 }
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 //Wrap around the top of the buffer
9154 for _ in 0..2 {
9155 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9156 }
9157 });
9158
9159 cx.assert_editor_state(
9160 &r#"
9161 use some::modified;
9162
9163
9164 fn main() {
9165 ˇ println!("hello there");
9166
9167 println!("around the");
9168 println!("world");
9169 }
9170 "#
9171 .unindent(),
9172 );
9173
9174 cx.update_editor(|editor, cx| {
9175 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9176 });
9177
9178 cx.assert_editor_state(
9179 &r#"
9180 use some::modified;
9181
9182 ˇ
9183 fn main() {
9184 println!("hello there");
9185
9186 println!("around the");
9187 println!("world");
9188 }
9189 "#
9190 .unindent(),
9191 );
9192
9193 cx.update_editor(|editor, cx| {
9194 for _ in 0..3 {
9195 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
9196 }
9197 });
9198
9199 cx.assert_editor_state(
9200 &r#"
9201 use some::modified;
9202
9203
9204 fn main() {
9205 ˇ println!("hello there");
9206
9207 println!("around the");
9208 println!("world");
9209 }
9210 "#
9211 .unindent(),
9212 );
9213
9214 cx.update_editor(|editor, cx| {
9215 editor.fold(&Fold, cx);
9216
9217 //Make sure that the fold only gets one hunk
9218 for _ in 0..4 {
9219 editor.go_to_hunk(&GoToHunk, cx);
9220 }
9221 });
9222
9223 cx.assert_editor_state(
9224 &r#"
9225 ˇuse some::modified;
9226
9227
9228 fn main() {
9229 println!("hello there");
9230
9231 println!("around the");
9232 println!("world");
9233 }
9234 "#
9235 .unindent(),
9236 );
9237}
9238
9239#[test]
9240fn test_split_words() {
9241 fn split(text: &str) -> Vec<&str> {
9242 split_words(text).collect()
9243 }
9244
9245 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
9246 assert_eq!(split("hello_world"), &["hello_", "world"]);
9247 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
9248 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
9249 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
9250 assert_eq!(split("helloworld"), &["helloworld"]);
9251
9252 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
9253}
9254
9255#[gpui::test]
9256async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
9257 init_test(cx, |_| {});
9258
9259 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
9260 let mut assert = |before, after| {
9261 let _state_context = cx.set_state(before);
9262 cx.update_editor(|editor, cx| {
9263 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
9264 });
9265 cx.assert_editor_state(after);
9266 };
9267
9268 // Outside bracket jumps to outside of matching bracket
9269 assert("console.logˇ(var);", "console.log(var)ˇ;");
9270 assert("console.log(var)ˇ;", "console.logˇ(var);");
9271
9272 // Inside bracket jumps to inside of matching bracket
9273 assert("console.log(ˇvar);", "console.log(varˇ);");
9274 assert("console.log(varˇ);", "console.log(ˇvar);");
9275
9276 // When outside a bracket and inside, favor jumping to the inside bracket
9277 assert(
9278 "console.log('foo', [1, 2, 3]ˇ);",
9279 "console.log(ˇ'foo', [1, 2, 3]);",
9280 );
9281 assert(
9282 "console.log(ˇ'foo', [1, 2, 3]);",
9283 "console.log('foo', [1, 2, 3]ˇ);",
9284 );
9285
9286 // Bias forward if two options are equally likely
9287 assert(
9288 "let result = curried_fun()ˇ();",
9289 "let result = curried_fun()()ˇ;",
9290 );
9291
9292 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
9293 assert(
9294 indoc! {"
9295 function test() {
9296 console.log('test')ˇ
9297 }"},
9298 indoc! {"
9299 function test() {
9300 console.logˇ('test')
9301 }"},
9302 );
9303}
9304
9305#[gpui::test]
9306async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
9307 init_test(cx, |_| {});
9308
9309 let fs = FakeFs::new(cx.executor());
9310 fs.insert_tree(
9311 "/a",
9312 json!({
9313 "main.rs": "fn main() { let a = 5; }",
9314 "other.rs": "// Test file",
9315 }),
9316 )
9317 .await;
9318 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9319
9320 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9321 language_registry.add(Arc::new(Language::new(
9322 LanguageConfig {
9323 name: "Rust".into(),
9324 matcher: LanguageMatcher {
9325 path_suffixes: vec!["rs".to_string()],
9326 ..Default::default()
9327 },
9328 brackets: BracketPairConfig {
9329 pairs: vec![BracketPair {
9330 start: "{".to_string(),
9331 end: "}".to_string(),
9332 close: true,
9333 surround: true,
9334 newline: true,
9335 }],
9336 disabled_scopes_by_bracket_ix: Vec::new(),
9337 },
9338 ..Default::default()
9339 },
9340 Some(tree_sitter_rust::language()),
9341 )));
9342 let mut fake_servers = language_registry.register_fake_lsp_adapter(
9343 "Rust",
9344 FakeLspAdapter {
9345 capabilities: lsp::ServerCapabilities {
9346 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
9347 first_trigger_character: "{".to_string(),
9348 more_trigger_character: None,
9349 }),
9350 ..Default::default()
9351 },
9352 ..Default::default()
9353 },
9354 );
9355
9356 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9357
9358 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9359
9360 let worktree_id = workspace
9361 .update(cx, |workspace, cx| {
9362 workspace.project().update(cx, |project, cx| {
9363 project.worktrees(cx).next().unwrap().read(cx).id()
9364 })
9365 })
9366 .unwrap();
9367
9368 let buffer = project
9369 .update(cx, |project, cx| {
9370 project.open_local_buffer("/a/main.rs", cx)
9371 })
9372 .await
9373 .unwrap();
9374 cx.executor().run_until_parked();
9375 cx.executor().start_waiting();
9376 let fake_server = fake_servers.next().await.unwrap();
9377 let editor_handle = workspace
9378 .update(cx, |workspace, cx| {
9379 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
9380 })
9381 .unwrap()
9382 .await
9383 .unwrap()
9384 .downcast::<Editor>()
9385 .unwrap();
9386
9387 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
9388 assert_eq!(
9389 params.text_document_position.text_document.uri,
9390 lsp::Url::from_file_path("/a/main.rs").unwrap(),
9391 );
9392 assert_eq!(
9393 params.text_document_position.position,
9394 lsp::Position::new(0, 21),
9395 );
9396
9397 Ok(Some(vec![lsp::TextEdit {
9398 new_text: "]".to_string(),
9399 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
9400 }]))
9401 });
9402
9403 editor_handle.update(cx, |editor, cx| {
9404 editor.focus(cx);
9405 editor.change_selections(None, cx, |s| {
9406 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
9407 });
9408 editor.handle_input("{", cx);
9409 });
9410
9411 cx.executor().run_until_parked();
9412
9413 _ = buffer.update(cx, |buffer, _| {
9414 assert_eq!(
9415 buffer.text(),
9416 "fn main() { let a = {5}; }",
9417 "No extra braces from on type formatting should appear in the buffer"
9418 )
9419 });
9420}
9421
9422#[gpui::test]
9423async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
9424 init_test(cx, |_| {});
9425
9426 let fs = FakeFs::new(cx.executor());
9427 fs.insert_tree(
9428 "/a",
9429 json!({
9430 "main.rs": "fn main() { let a = 5; }",
9431 "other.rs": "// Test file",
9432 }),
9433 )
9434 .await;
9435
9436 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9437
9438 let server_restarts = Arc::new(AtomicUsize::new(0));
9439 let closure_restarts = Arc::clone(&server_restarts);
9440 let language_server_name = "test language server";
9441 let language_name: Arc<str> = "Rust".into();
9442
9443 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9444 language_registry.add(Arc::new(Language::new(
9445 LanguageConfig {
9446 name: Arc::clone(&language_name),
9447 matcher: LanguageMatcher {
9448 path_suffixes: vec!["rs".to_string()],
9449 ..Default::default()
9450 },
9451 ..Default::default()
9452 },
9453 Some(tree_sitter_rust::language()),
9454 )));
9455 let mut fake_servers = language_registry.register_fake_lsp_adapter(
9456 "Rust",
9457 FakeLspAdapter {
9458 name: language_server_name,
9459 initialization_options: Some(json!({
9460 "testOptionValue": true
9461 })),
9462 initializer: Some(Box::new(move |fake_server| {
9463 let task_restarts = Arc::clone(&closure_restarts);
9464 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
9465 task_restarts.fetch_add(1, atomic::Ordering::Release);
9466 futures::future::ready(Ok(()))
9467 });
9468 })),
9469 ..Default::default()
9470 },
9471 );
9472
9473 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9474 let _buffer = project
9475 .update(cx, |project, cx| {
9476 project.open_local_buffer("/a/main.rs", cx)
9477 })
9478 .await
9479 .unwrap();
9480 let _fake_server = fake_servers.next().await.unwrap();
9481 update_test_language_settings(cx, |language_settings| {
9482 language_settings.languages.insert(
9483 Arc::clone(&language_name),
9484 LanguageSettingsContent {
9485 tab_size: NonZeroU32::new(8),
9486 ..Default::default()
9487 },
9488 );
9489 });
9490 cx.executor().run_until_parked();
9491 assert_eq!(
9492 server_restarts.load(atomic::Ordering::Acquire),
9493 0,
9494 "Should not restart LSP server on an unrelated change"
9495 );
9496
9497 update_test_project_settings(cx, |project_settings| {
9498 project_settings.lsp.insert(
9499 "Some other server name".into(),
9500 LspSettings {
9501 binary: None,
9502 settings: None,
9503 initialization_options: Some(json!({
9504 "some other init value": false
9505 })),
9506 },
9507 );
9508 });
9509 cx.executor().run_until_parked();
9510 assert_eq!(
9511 server_restarts.load(atomic::Ordering::Acquire),
9512 0,
9513 "Should not restart LSP server on an unrelated LSP settings change"
9514 );
9515
9516 update_test_project_settings(cx, |project_settings| {
9517 project_settings.lsp.insert(
9518 language_server_name.into(),
9519 LspSettings {
9520 binary: None,
9521 settings: None,
9522 initialization_options: Some(json!({
9523 "anotherInitValue": false
9524 })),
9525 },
9526 );
9527 });
9528 cx.executor().run_until_parked();
9529 assert_eq!(
9530 server_restarts.load(atomic::Ordering::Acquire),
9531 1,
9532 "Should restart LSP server on a related LSP settings change"
9533 );
9534
9535 update_test_project_settings(cx, |project_settings| {
9536 project_settings.lsp.insert(
9537 language_server_name.into(),
9538 LspSettings {
9539 binary: None,
9540 settings: None,
9541 initialization_options: Some(json!({
9542 "anotherInitValue": false
9543 })),
9544 },
9545 );
9546 });
9547 cx.executor().run_until_parked();
9548 assert_eq!(
9549 server_restarts.load(atomic::Ordering::Acquire),
9550 1,
9551 "Should not restart LSP server on a related LSP settings change that is the same"
9552 );
9553
9554 update_test_project_settings(cx, |project_settings| {
9555 project_settings.lsp.insert(
9556 language_server_name.into(),
9557 LspSettings {
9558 binary: None,
9559 settings: None,
9560 initialization_options: None,
9561 },
9562 );
9563 });
9564 cx.executor().run_until_parked();
9565 assert_eq!(
9566 server_restarts.load(atomic::Ordering::Acquire),
9567 2,
9568 "Should restart LSP server on another related LSP settings change"
9569 );
9570}
9571
9572#[gpui::test]
9573async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
9574 init_test(cx, |_| {});
9575
9576 let mut cx = EditorLspTestContext::new_rust(
9577 lsp::ServerCapabilities {
9578 completion_provider: Some(lsp::CompletionOptions {
9579 trigger_characters: Some(vec![".".to_string()]),
9580 resolve_provider: Some(true),
9581 ..Default::default()
9582 }),
9583 ..Default::default()
9584 },
9585 cx,
9586 )
9587 .await;
9588
9589 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9590 cx.simulate_keystroke(".");
9591 let completion_item = lsp::CompletionItem {
9592 label: "some".into(),
9593 kind: Some(lsp::CompletionItemKind::SNIPPET),
9594 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9595 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9596 kind: lsp::MarkupKind::Markdown,
9597 value: "```rust\nSome(2)\n```".to_string(),
9598 })),
9599 deprecated: Some(false),
9600 sort_text: Some("fffffff2".to_string()),
9601 filter_text: Some("some".to_string()),
9602 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9603 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9604 range: lsp::Range {
9605 start: lsp::Position {
9606 line: 0,
9607 character: 22,
9608 },
9609 end: lsp::Position {
9610 line: 0,
9611 character: 22,
9612 },
9613 },
9614 new_text: "Some(2)".to_string(),
9615 })),
9616 additional_text_edits: Some(vec![lsp::TextEdit {
9617 range: lsp::Range {
9618 start: lsp::Position {
9619 line: 0,
9620 character: 20,
9621 },
9622 end: lsp::Position {
9623 line: 0,
9624 character: 22,
9625 },
9626 },
9627 new_text: "".to_string(),
9628 }]),
9629 ..Default::default()
9630 };
9631
9632 let closure_completion_item = completion_item.clone();
9633 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9634 let task_completion_item = closure_completion_item.clone();
9635 async move {
9636 Ok(Some(lsp::CompletionResponse::Array(vec![
9637 task_completion_item,
9638 ])))
9639 }
9640 });
9641
9642 request.next().await;
9643
9644 cx.condition(|editor, _| editor.context_menu_visible())
9645 .await;
9646 let apply_additional_edits = cx.update_editor(|editor, cx| {
9647 editor
9648 .confirm_completion(&ConfirmCompletion::default(), cx)
9649 .unwrap()
9650 });
9651 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
9652
9653 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
9654 let task_completion_item = completion_item.clone();
9655 async move { Ok(task_completion_item) }
9656 })
9657 .next()
9658 .await
9659 .unwrap();
9660 apply_additional_edits.await.unwrap();
9661 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
9662}
9663
9664#[gpui::test]
9665async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
9666 init_test(cx, |_| {});
9667
9668 let mut cx = EditorLspTestContext::new(
9669 Language::new(
9670 LanguageConfig {
9671 matcher: LanguageMatcher {
9672 path_suffixes: vec!["jsx".into()],
9673 ..Default::default()
9674 },
9675 overrides: [(
9676 "element".into(),
9677 LanguageConfigOverride {
9678 word_characters: Override::Set(['-'].into_iter().collect()),
9679 ..Default::default()
9680 },
9681 )]
9682 .into_iter()
9683 .collect(),
9684 ..Default::default()
9685 },
9686 Some(tree_sitter_typescript::language_tsx()),
9687 )
9688 .with_override_query("(jsx_self_closing_element) @element")
9689 .unwrap(),
9690 lsp::ServerCapabilities {
9691 completion_provider: Some(lsp::CompletionOptions {
9692 trigger_characters: Some(vec![":".to_string()]),
9693 ..Default::default()
9694 }),
9695 ..Default::default()
9696 },
9697 cx,
9698 )
9699 .await;
9700
9701 cx.lsp
9702 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9703 Ok(Some(lsp::CompletionResponse::Array(vec![
9704 lsp::CompletionItem {
9705 label: "bg-blue".into(),
9706 ..Default::default()
9707 },
9708 lsp::CompletionItem {
9709 label: "bg-red".into(),
9710 ..Default::default()
9711 },
9712 lsp::CompletionItem {
9713 label: "bg-yellow".into(),
9714 ..Default::default()
9715 },
9716 ])))
9717 });
9718
9719 cx.set_state(r#"<p class="bgˇ" />"#);
9720
9721 // Trigger completion when typing a dash, because the dash is an extra
9722 // word character in the 'element' scope, which contains the cursor.
9723 cx.simulate_keystroke("-");
9724 cx.executor().run_until_parked();
9725 cx.update_editor(|editor, _| {
9726 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9727 assert_eq!(
9728 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9729 &["bg-red", "bg-blue", "bg-yellow"]
9730 );
9731 } else {
9732 panic!("expected completion menu to be open");
9733 }
9734 });
9735
9736 cx.simulate_keystroke("l");
9737 cx.executor().run_until_parked();
9738 cx.update_editor(|editor, _| {
9739 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9740 assert_eq!(
9741 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9742 &["bg-blue", "bg-yellow"]
9743 );
9744 } else {
9745 panic!("expected completion menu to be open");
9746 }
9747 });
9748
9749 // When filtering completions, consider the character after the '-' to
9750 // be the start of a subword.
9751 cx.set_state(r#"<p class="yelˇ" />"#);
9752 cx.simulate_keystroke("l");
9753 cx.executor().run_until_parked();
9754 cx.update_editor(|editor, _| {
9755 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9756 assert_eq!(
9757 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9758 &["bg-yellow"]
9759 );
9760 } else {
9761 panic!("expected completion menu to be open");
9762 }
9763 });
9764}
9765
9766#[gpui::test]
9767async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
9768 init_test(cx, |settings| {
9769 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
9770 FormatterList(vec![Formatter::Prettier].into()),
9771 ))
9772 });
9773
9774 let fs = FakeFs::new(cx.executor());
9775 fs.insert_file("/file.ts", Default::default()).await;
9776
9777 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
9778 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9779
9780 language_registry.add(Arc::new(Language::new(
9781 LanguageConfig {
9782 name: "TypeScript".into(),
9783 matcher: LanguageMatcher {
9784 path_suffixes: vec!["ts".to_string()],
9785 ..Default::default()
9786 },
9787 ..Default::default()
9788 },
9789 Some(tree_sitter_rust::language()),
9790 )));
9791 update_test_language_settings(cx, |settings| {
9792 settings.defaults.prettier = Some(PrettierSettings {
9793 allowed: true,
9794 ..PrettierSettings::default()
9795 });
9796 });
9797
9798 let test_plugin = "test_plugin";
9799 let _ = language_registry.register_fake_lsp_adapter(
9800 "TypeScript",
9801 FakeLspAdapter {
9802 prettier_plugins: vec![test_plugin],
9803 ..Default::default()
9804 },
9805 );
9806
9807 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
9808 let buffer = project
9809 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
9810 .await
9811 .unwrap();
9812
9813 let buffer_text = "one\ntwo\nthree\n";
9814 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9815 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9816 _ = editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
9817
9818 editor
9819 .update(cx, |editor, cx| {
9820 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
9821 })
9822 .unwrap()
9823 .await;
9824 assert_eq!(
9825 editor.update(cx, |editor, cx| editor.text(cx)),
9826 buffer_text.to_string() + prettier_format_suffix,
9827 "Test prettier formatting was not applied to the original buffer text",
9828 );
9829
9830 update_test_language_settings(cx, |settings| {
9831 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
9832 });
9833 let format = editor.update(cx, |editor, cx| {
9834 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
9835 });
9836 format.await.unwrap();
9837 assert_eq!(
9838 editor.update(cx, |editor, cx| editor.text(cx)),
9839 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
9840 "Autoformatting (via test prettier) was not applied to the original buffer text",
9841 );
9842}
9843
9844#[gpui::test]
9845async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
9846 init_test(cx, |_| {});
9847 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9848 let base_text = indoc! {r#"struct Row;
9849struct Row1;
9850struct Row2;
9851
9852struct Row4;
9853struct Row5;
9854struct Row6;
9855
9856struct Row8;
9857struct Row9;
9858struct Row10;"#};
9859
9860 // When addition hunks are not adjacent to carets, no hunk revert is performed
9861 assert_hunk_revert(
9862 indoc! {r#"struct Row;
9863 struct Row1;
9864 struct Row1.1;
9865 struct Row1.2;
9866 struct Row2;ˇ
9867
9868 struct Row4;
9869 struct Row5;
9870 struct Row6;
9871
9872 struct Row8;
9873 ˇstruct Row9;
9874 struct Row9.1;
9875 struct Row9.2;
9876 struct Row9.3;
9877 struct Row10;"#},
9878 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
9879 indoc! {r#"struct Row;
9880 struct Row1;
9881 struct Row1.1;
9882 struct Row1.2;
9883 struct Row2;ˇ
9884
9885 struct Row4;
9886 struct Row5;
9887 struct Row6;
9888
9889 struct Row8;
9890 ˇstruct Row9;
9891 struct Row9.1;
9892 struct Row9.2;
9893 struct Row9.3;
9894 struct Row10;"#},
9895 base_text,
9896 &mut cx,
9897 );
9898 // Same for selections
9899 assert_hunk_revert(
9900 indoc! {r#"struct Row;
9901 struct Row1;
9902 struct Row2;
9903 struct Row2.1;
9904 struct Row2.2;
9905 «ˇ
9906 struct Row4;
9907 struct» Row5;
9908 «struct Row6;
9909 ˇ»
9910 struct Row9.1;
9911 struct Row9.2;
9912 struct Row9.3;
9913 struct Row8;
9914 struct Row9;
9915 struct Row10;"#},
9916 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
9917 indoc! {r#"struct Row;
9918 struct Row1;
9919 struct Row2;
9920 struct Row2.1;
9921 struct Row2.2;
9922 «ˇ
9923 struct Row4;
9924 struct» Row5;
9925 «struct Row6;
9926 ˇ»
9927 struct Row9.1;
9928 struct Row9.2;
9929 struct Row9.3;
9930 struct Row8;
9931 struct Row9;
9932 struct Row10;"#},
9933 base_text,
9934 &mut cx,
9935 );
9936
9937 // When carets and selections intersect the addition hunks, those are reverted.
9938 // Adjacent carets got merged.
9939 assert_hunk_revert(
9940 indoc! {r#"struct Row;
9941 ˇ// something on the top
9942 struct Row1;
9943 struct Row2;
9944 struct Roˇw3.1;
9945 struct Row2.2;
9946 struct Row2.3;ˇ
9947
9948 struct Row4;
9949 struct ˇRow5.1;
9950 struct Row5.2;
9951 struct «Rowˇ»5.3;
9952 struct Row5;
9953 struct Row6;
9954 ˇ
9955 struct Row9.1;
9956 struct «Rowˇ»9.2;
9957 struct «ˇRow»9.3;
9958 struct Row8;
9959 struct Row9;
9960 «ˇ// something on bottom»
9961 struct Row10;"#},
9962 vec![
9963 DiffHunkStatus::Added,
9964 DiffHunkStatus::Added,
9965 DiffHunkStatus::Added,
9966 DiffHunkStatus::Added,
9967 DiffHunkStatus::Added,
9968 ],
9969 indoc! {r#"struct Row;
9970 ˇstruct Row1;
9971 struct Row2;
9972 ˇ
9973 struct Row4;
9974 ˇstruct Row5;
9975 struct Row6;
9976 ˇ
9977 ˇstruct Row8;
9978 struct Row9;
9979 ˇstruct Row10;"#},
9980 base_text,
9981 &mut cx,
9982 );
9983}
9984
9985#[gpui::test]
9986async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
9987 init_test(cx, |_| {});
9988 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9989 let base_text = indoc! {r#"struct Row;
9990struct Row1;
9991struct Row2;
9992
9993struct Row4;
9994struct Row5;
9995struct Row6;
9996
9997struct Row8;
9998struct Row9;
9999struct Row10;"#};
10000
10001 // Modification hunks behave the same as the addition ones.
10002 assert_hunk_revert(
10003 indoc! {r#"struct Row;
10004 struct Row1;
10005 struct Row33;
10006 ˇ
10007 struct Row4;
10008 struct Row5;
10009 struct Row6;
10010 ˇ
10011 struct Row99;
10012 struct Row9;
10013 struct Row10;"#},
10014 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10015 indoc! {r#"struct Row;
10016 struct Row1;
10017 struct Row33;
10018 ˇ
10019 struct Row4;
10020 struct Row5;
10021 struct Row6;
10022 ˇ
10023 struct Row99;
10024 struct Row9;
10025 struct Row10;"#},
10026 base_text,
10027 &mut cx,
10028 );
10029 assert_hunk_revert(
10030 indoc! {r#"struct Row;
10031 struct Row1;
10032 struct Row33;
10033 «ˇ
10034 struct Row4;
10035 struct» Row5;
10036 «struct Row6;
10037 ˇ»
10038 struct Row99;
10039 struct Row9;
10040 struct Row10;"#},
10041 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10042 indoc! {r#"struct Row;
10043 struct Row1;
10044 struct Row33;
10045 «ˇ
10046 struct Row4;
10047 struct» Row5;
10048 «struct Row6;
10049 ˇ»
10050 struct Row99;
10051 struct Row9;
10052 struct Row10;"#},
10053 base_text,
10054 &mut cx,
10055 );
10056
10057 assert_hunk_revert(
10058 indoc! {r#"ˇstruct Row1.1;
10059 struct Row1;
10060 «ˇstr»uct Row22;
10061
10062 struct ˇRow44;
10063 struct Row5;
10064 struct «Rˇ»ow66;ˇ
10065
10066 «struˇ»ct Row88;
10067 struct Row9;
10068 struct Row1011;ˇ"#},
10069 vec![
10070 DiffHunkStatus::Modified,
10071 DiffHunkStatus::Modified,
10072 DiffHunkStatus::Modified,
10073 DiffHunkStatus::Modified,
10074 DiffHunkStatus::Modified,
10075 DiffHunkStatus::Modified,
10076 ],
10077 indoc! {r#"struct Row;
10078 ˇstruct Row1;
10079 struct Row2;
10080 ˇ
10081 struct Row4;
10082 ˇstruct Row5;
10083 struct Row6;
10084 ˇ
10085 struct Row8;
10086 ˇstruct Row9;
10087 struct Row10;ˇ"#},
10088 base_text,
10089 &mut cx,
10090 );
10091}
10092
10093#[gpui::test]
10094async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
10095 init_test(cx, |_| {});
10096 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10097 let base_text = indoc! {r#"struct Row;
10098struct Row1;
10099struct Row2;
10100
10101struct Row4;
10102struct Row5;
10103struct Row6;
10104
10105struct Row8;
10106struct Row9;
10107struct Row10;"#};
10108
10109 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
10110 assert_hunk_revert(
10111 indoc! {r#"struct Row;
10112 struct Row2;
10113
10114 ˇstruct Row4;
10115 struct Row5;
10116 struct Row6;
10117 ˇ
10118 struct Row8;
10119 struct Row10;"#},
10120 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10121 indoc! {r#"struct Row;
10122 struct Row2;
10123
10124 ˇstruct Row4;
10125 struct Row5;
10126 struct Row6;
10127 ˇ
10128 struct Row8;
10129 struct Row10;"#},
10130 base_text,
10131 &mut cx,
10132 );
10133 assert_hunk_revert(
10134 indoc! {r#"struct Row;
10135 struct Row2;
10136
10137 «ˇstruct Row4;
10138 struct» Row5;
10139 «struct Row6;
10140 ˇ»
10141 struct Row8;
10142 struct Row10;"#},
10143 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10144 indoc! {r#"struct Row;
10145 struct Row2;
10146
10147 «ˇstruct Row4;
10148 struct» Row5;
10149 «struct Row6;
10150 ˇ»
10151 struct Row8;
10152 struct Row10;"#},
10153 base_text,
10154 &mut cx,
10155 );
10156
10157 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
10158 assert_hunk_revert(
10159 indoc! {r#"struct Row;
10160 ˇstruct Row2;
10161
10162 struct Row4;
10163 struct Row5;
10164 struct Row6;
10165
10166 struct Row8;ˇ
10167 struct Row10;"#},
10168 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10169 indoc! {r#"struct Row;
10170 struct Row1;
10171 ˇstruct Row2;
10172
10173 struct Row4;
10174 struct Row5;
10175 struct Row6;
10176
10177 struct Row8;ˇ
10178 struct Row9;
10179 struct Row10;"#},
10180 base_text,
10181 &mut cx,
10182 );
10183 assert_hunk_revert(
10184 indoc! {r#"struct Row;
10185 struct Row2«ˇ;
10186 struct Row4;
10187 struct» Row5;
10188 «struct Row6;
10189
10190 struct Row8;ˇ»
10191 struct Row10;"#},
10192 vec![
10193 DiffHunkStatus::Removed,
10194 DiffHunkStatus::Removed,
10195 DiffHunkStatus::Removed,
10196 ],
10197 indoc! {r#"struct Row;
10198 struct Row1;
10199 struct Row2«ˇ;
10200
10201 struct Row4;
10202 struct» Row5;
10203 «struct Row6;
10204
10205 struct Row8;ˇ»
10206 struct Row9;
10207 struct Row10;"#},
10208 base_text,
10209 &mut cx,
10210 );
10211}
10212
10213#[gpui::test]
10214async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
10215 init_test(cx, |_| {});
10216
10217 let cols = 4;
10218 let rows = 10;
10219 let sample_text_1 = sample_text(rows, cols, 'a');
10220 assert_eq!(
10221 sample_text_1,
10222 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10223 );
10224 let sample_text_2 = sample_text(rows, cols, 'l');
10225 assert_eq!(
10226 sample_text_2,
10227 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10228 );
10229 let sample_text_3 = sample_text(rows, cols, 'v');
10230 assert_eq!(
10231 sample_text_3,
10232 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10233 );
10234
10235 fn diff_every_buffer_row(
10236 buffer: &Model<Buffer>,
10237 sample_text: String,
10238 cols: usize,
10239 cx: &mut gpui::TestAppContext,
10240 ) {
10241 // revert first character in each row, creating one large diff hunk per buffer
10242 let is_first_char = |offset: usize| offset % cols == 0;
10243 buffer.update(cx, |buffer, cx| {
10244 buffer.set_text(
10245 sample_text
10246 .chars()
10247 .enumerate()
10248 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
10249 .collect::<String>(),
10250 cx,
10251 );
10252 buffer.set_diff_base(Some(sample_text), cx);
10253 });
10254 cx.executor().run_until_parked();
10255 }
10256
10257 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10258 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10259
10260 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10261 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10262
10263 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10264 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10265
10266 let multibuffer = cx.new_model(|cx| {
10267 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10268 multibuffer.push_excerpts(
10269 buffer_1.clone(),
10270 [
10271 ExcerptRange {
10272 context: Point::new(0, 0)..Point::new(3, 0),
10273 primary: None,
10274 },
10275 ExcerptRange {
10276 context: Point::new(5, 0)..Point::new(7, 0),
10277 primary: None,
10278 },
10279 ExcerptRange {
10280 context: Point::new(9, 0)..Point::new(10, 4),
10281 primary: None,
10282 },
10283 ],
10284 cx,
10285 );
10286 multibuffer.push_excerpts(
10287 buffer_2.clone(),
10288 [
10289 ExcerptRange {
10290 context: Point::new(0, 0)..Point::new(3, 0),
10291 primary: None,
10292 },
10293 ExcerptRange {
10294 context: Point::new(5, 0)..Point::new(7, 0),
10295 primary: None,
10296 },
10297 ExcerptRange {
10298 context: Point::new(9, 0)..Point::new(10, 4),
10299 primary: None,
10300 },
10301 ],
10302 cx,
10303 );
10304 multibuffer.push_excerpts(
10305 buffer_3.clone(),
10306 [
10307 ExcerptRange {
10308 context: Point::new(0, 0)..Point::new(3, 0),
10309 primary: None,
10310 },
10311 ExcerptRange {
10312 context: Point::new(5, 0)..Point::new(7, 0),
10313 primary: None,
10314 },
10315 ExcerptRange {
10316 context: Point::new(9, 0)..Point::new(10, 4),
10317 primary: None,
10318 },
10319 ],
10320 cx,
10321 );
10322 multibuffer
10323 });
10324
10325 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
10326 editor.update(cx, |editor, cx| {
10327 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");
10328 editor.select_all(&SelectAll, cx);
10329 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10330 });
10331 cx.executor().run_until_parked();
10332 // When all ranges are selected, all buffer hunks are reverted.
10333 editor.update(cx, |editor, cx| {
10334 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");
10335 });
10336 buffer_1.update(cx, |buffer, _| {
10337 assert_eq!(buffer.text(), sample_text_1);
10338 });
10339 buffer_2.update(cx, |buffer, _| {
10340 assert_eq!(buffer.text(), sample_text_2);
10341 });
10342 buffer_3.update(cx, |buffer, _| {
10343 assert_eq!(buffer.text(), sample_text_3);
10344 });
10345
10346 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
10347 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
10348 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
10349 editor.update(cx, |editor, cx| {
10350 editor.change_selections(None, cx, |s| {
10351 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
10352 });
10353 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
10354 });
10355 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
10356 // but not affect buffer_2 and its related excerpts.
10357 editor.update(cx, |editor, cx| {
10358 assert_eq!(
10359 editor.text(cx),
10360 "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"
10361 );
10362 });
10363 buffer_1.update(cx, |buffer, _| {
10364 assert_eq!(buffer.text(), sample_text_1);
10365 });
10366 buffer_2.update(cx, |buffer, _| {
10367 assert_eq!(
10368 buffer.text(),
10369 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
10370 );
10371 });
10372 buffer_3.update(cx, |buffer, _| {
10373 assert_eq!(
10374 buffer.text(),
10375 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
10376 );
10377 });
10378}
10379
10380#[gpui::test]
10381async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
10382 init_test(cx, |_| {});
10383
10384 let cols = 4;
10385 let rows = 10;
10386 let sample_text_1 = sample_text(rows, cols, 'a');
10387 assert_eq!(
10388 sample_text_1,
10389 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10390 );
10391 let sample_text_2 = sample_text(rows, cols, 'l');
10392 assert_eq!(
10393 sample_text_2,
10394 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10395 );
10396 let sample_text_3 = sample_text(rows, cols, 'v');
10397 assert_eq!(
10398 sample_text_3,
10399 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10400 );
10401
10402 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
10403 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
10404 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
10405
10406 let multi_buffer = cx.new_model(|cx| {
10407 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10408 multibuffer.push_excerpts(
10409 buffer_1.clone(),
10410 [
10411 ExcerptRange {
10412 context: Point::new(0, 0)..Point::new(3, 0),
10413 primary: None,
10414 },
10415 ExcerptRange {
10416 context: Point::new(5, 0)..Point::new(7, 0),
10417 primary: None,
10418 },
10419 ExcerptRange {
10420 context: Point::new(9, 0)..Point::new(10, 4),
10421 primary: None,
10422 },
10423 ],
10424 cx,
10425 );
10426 multibuffer.push_excerpts(
10427 buffer_2.clone(),
10428 [
10429 ExcerptRange {
10430 context: Point::new(0, 0)..Point::new(3, 0),
10431 primary: None,
10432 },
10433 ExcerptRange {
10434 context: Point::new(5, 0)..Point::new(7, 0),
10435 primary: None,
10436 },
10437 ExcerptRange {
10438 context: Point::new(9, 0)..Point::new(10, 4),
10439 primary: None,
10440 },
10441 ],
10442 cx,
10443 );
10444 multibuffer.push_excerpts(
10445 buffer_3.clone(),
10446 [
10447 ExcerptRange {
10448 context: Point::new(0, 0)..Point::new(3, 0),
10449 primary: None,
10450 },
10451 ExcerptRange {
10452 context: Point::new(5, 0)..Point::new(7, 0),
10453 primary: None,
10454 },
10455 ExcerptRange {
10456 context: Point::new(9, 0)..Point::new(10, 4),
10457 primary: None,
10458 },
10459 ],
10460 cx,
10461 );
10462 multibuffer
10463 });
10464
10465 let fs = FakeFs::new(cx.executor());
10466 fs.insert_tree(
10467 "/a",
10468 json!({
10469 "main.rs": sample_text_1,
10470 "other.rs": sample_text_2,
10471 "lib.rs": sample_text_3,
10472 }),
10473 )
10474 .await;
10475 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10476 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10477 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10478 let multi_buffer_editor = cx.new_view(|cx| {
10479 Editor::new(
10480 EditorMode::Full,
10481 multi_buffer,
10482 Some(project.clone()),
10483 true,
10484 cx,
10485 )
10486 });
10487 let multibuffer_item_id = workspace
10488 .update(cx, |workspace, cx| {
10489 assert!(
10490 workspace.active_item(cx).is_none(),
10491 "active item should be None before the first item is added"
10492 );
10493 workspace.add_item_to_active_pane(
10494 Box::new(multi_buffer_editor.clone()),
10495 None,
10496 true,
10497 cx,
10498 );
10499 let active_item = workspace
10500 .active_item(cx)
10501 .expect("should have an active item after adding the multi buffer");
10502 assert!(
10503 !active_item.is_singleton(cx),
10504 "A multi buffer was expected to active after adding"
10505 );
10506 active_item.item_id()
10507 })
10508 .unwrap();
10509 cx.executor().run_until_parked();
10510
10511 multi_buffer_editor.update(cx, |editor, cx| {
10512 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
10513 editor.open_excerpts(&OpenExcerpts, cx);
10514 });
10515 cx.executor().run_until_parked();
10516 let first_item_id = workspace
10517 .update(cx, |workspace, cx| {
10518 let active_item = workspace
10519 .active_item(cx)
10520 .expect("should have an active item after navigating into the 1st buffer");
10521 let first_item_id = active_item.item_id();
10522 assert_ne!(
10523 first_item_id, multibuffer_item_id,
10524 "Should navigate into the 1st buffer and activate it"
10525 );
10526 assert!(
10527 active_item.is_singleton(cx),
10528 "New active item should be a singleton buffer"
10529 );
10530 assert_eq!(
10531 active_item
10532 .act_as::<Editor>(cx)
10533 .expect("should have navigated into an editor for the 1st buffer")
10534 .read(cx)
10535 .text(cx),
10536 sample_text_1
10537 );
10538
10539 workspace
10540 .go_back(workspace.active_pane().downgrade(), cx)
10541 .detach_and_log_err(cx);
10542
10543 first_item_id
10544 })
10545 .unwrap();
10546 cx.executor().run_until_parked();
10547 workspace
10548 .update(cx, |workspace, cx| {
10549 let active_item = workspace
10550 .active_item(cx)
10551 .expect("should have an active item after navigating back");
10552 assert_eq!(
10553 active_item.item_id(),
10554 multibuffer_item_id,
10555 "Should navigate back to the multi buffer"
10556 );
10557 assert!(!active_item.is_singleton(cx));
10558 })
10559 .unwrap();
10560
10561 multi_buffer_editor.update(cx, |editor, cx| {
10562 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
10563 s.select_ranges(Some(39..40))
10564 });
10565 editor.open_excerpts(&OpenExcerpts, cx);
10566 });
10567 cx.executor().run_until_parked();
10568 let second_item_id = workspace
10569 .update(cx, |workspace, cx| {
10570 let active_item = workspace
10571 .active_item(cx)
10572 .expect("should have an active item after navigating into the 2nd buffer");
10573 let second_item_id = active_item.item_id();
10574 assert_ne!(
10575 second_item_id, multibuffer_item_id,
10576 "Should navigate away from the multibuffer"
10577 );
10578 assert_ne!(
10579 second_item_id, first_item_id,
10580 "Should navigate into the 2nd buffer and activate it"
10581 );
10582 assert!(
10583 active_item.is_singleton(cx),
10584 "New active item should be a singleton buffer"
10585 );
10586 assert_eq!(
10587 active_item
10588 .act_as::<Editor>(cx)
10589 .expect("should have navigated into an editor")
10590 .read(cx)
10591 .text(cx),
10592 sample_text_2
10593 );
10594
10595 workspace
10596 .go_back(workspace.active_pane().downgrade(), cx)
10597 .detach_and_log_err(cx);
10598
10599 second_item_id
10600 })
10601 .unwrap();
10602 cx.executor().run_until_parked();
10603 workspace
10604 .update(cx, |workspace, cx| {
10605 let active_item = workspace
10606 .active_item(cx)
10607 .expect("should have an active item after navigating back from the 2nd buffer");
10608 assert_eq!(
10609 active_item.item_id(),
10610 multibuffer_item_id,
10611 "Should navigate back from the 2nd buffer to the multi buffer"
10612 );
10613 assert!(!active_item.is_singleton(cx));
10614 })
10615 .unwrap();
10616
10617 multi_buffer_editor.update(cx, |editor, cx| {
10618 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
10619 s.select_ranges(Some(60..70))
10620 });
10621 editor.open_excerpts(&OpenExcerpts, cx);
10622 });
10623 cx.executor().run_until_parked();
10624 workspace
10625 .update(cx, |workspace, cx| {
10626 let active_item = workspace
10627 .active_item(cx)
10628 .expect("should have an active item after navigating into the 3rd buffer");
10629 let third_item_id = active_item.item_id();
10630 assert_ne!(
10631 third_item_id, multibuffer_item_id,
10632 "Should navigate into the 3rd buffer and activate it"
10633 );
10634 assert_ne!(third_item_id, first_item_id);
10635 assert_ne!(third_item_id, second_item_id);
10636 assert!(
10637 active_item.is_singleton(cx),
10638 "New active item should be a singleton buffer"
10639 );
10640 assert_eq!(
10641 active_item
10642 .act_as::<Editor>(cx)
10643 .expect("should have navigated into an editor")
10644 .read(cx)
10645 .text(cx),
10646 sample_text_3
10647 );
10648
10649 workspace
10650 .go_back(workspace.active_pane().downgrade(), cx)
10651 .detach_and_log_err(cx);
10652 })
10653 .unwrap();
10654 cx.executor().run_until_parked();
10655 workspace
10656 .update(cx, |workspace, cx| {
10657 let active_item = workspace
10658 .active_item(cx)
10659 .expect("should have an active item after navigating back from the 3rd buffer");
10660 assert_eq!(
10661 active_item.item_id(),
10662 multibuffer_item_id,
10663 "Should navigate back from the 3rd buffer to the multi buffer"
10664 );
10665 assert!(!active_item.is_singleton(cx));
10666 })
10667 .unwrap();
10668}
10669
10670#[gpui::test]
10671async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10672 init_test(cx, |_| {});
10673
10674 let mut cx = EditorTestContext::new(cx).await;
10675
10676 let diff_base = r#"
10677 use some::mod;
10678
10679 const A: u32 = 42;
10680
10681 fn main() {
10682 println!("hello");
10683
10684 println!("world");
10685 }
10686 "#
10687 .unindent();
10688
10689 cx.set_state(
10690 &r#"
10691 use some::modified;
10692
10693 ˇ
10694 fn main() {
10695 println!("hello there");
10696
10697 println!("around the");
10698 println!("world");
10699 }
10700 "#
10701 .unindent(),
10702 );
10703
10704 cx.set_diff_base(Some(&diff_base));
10705 executor.run_until_parked();
10706 let unexpanded_hunks = vec![
10707 (
10708 "use some::mod;\n".to_string(),
10709 DiffHunkStatus::Modified,
10710 DisplayRow(0)..DisplayRow(1),
10711 ),
10712 (
10713 "const A: u32 = 42;\n".to_string(),
10714 DiffHunkStatus::Removed,
10715 DisplayRow(2)..DisplayRow(2),
10716 ),
10717 (
10718 " println!(\"hello\");\n".to_string(),
10719 DiffHunkStatus::Modified,
10720 DisplayRow(4)..DisplayRow(5),
10721 ),
10722 (
10723 "".to_string(),
10724 DiffHunkStatus::Added,
10725 DisplayRow(6)..DisplayRow(7),
10726 ),
10727 ];
10728 cx.update_editor(|editor, cx| {
10729 let snapshot = editor.snapshot(cx);
10730 let all_hunks = editor_hunks(editor, &snapshot, cx);
10731 assert_eq!(all_hunks, unexpanded_hunks);
10732 });
10733
10734 cx.update_editor(|editor, cx| {
10735 for _ in 0..4 {
10736 editor.go_to_hunk(&GoToHunk, cx);
10737 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10738 }
10739 });
10740 executor.run_until_parked();
10741 cx.assert_editor_state(
10742 &r#"
10743 use some::modified;
10744
10745 ˇ
10746 fn main() {
10747 println!("hello there");
10748
10749 println!("around the");
10750 println!("world");
10751 }
10752 "#
10753 .unindent(),
10754 );
10755 cx.update_editor(|editor, cx| {
10756 let snapshot = editor.snapshot(cx);
10757 let all_hunks = editor_hunks(editor, &snapshot, cx);
10758 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10759 assert_eq!(
10760 expanded_hunks_background_highlights(editor, cx),
10761 vec![DisplayRow(1)..=DisplayRow(1), DisplayRow(7)..=DisplayRow(7), DisplayRow(9)..=DisplayRow(9)],
10762 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
10763 );
10764 assert_eq!(
10765 all_hunks,
10766 vec![
10767 ("use some::mod;\n".to_string(), DiffHunkStatus::Modified, DisplayRow(1)..DisplayRow(2)),
10768 ("const A: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(4)..DisplayRow(4)),
10769 (" println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(7)..DisplayRow(8)),
10770 ("".to_string(), DiffHunkStatus::Added, DisplayRow(9)..DisplayRow(10)),
10771 ],
10772 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
10773 (from modified and removed hunks)"
10774 );
10775 assert_eq!(
10776 all_hunks, all_expanded_hunks,
10777 "Editor hunks should not change and all be expanded"
10778 );
10779 });
10780
10781 cx.update_editor(|editor, cx| {
10782 editor.cancel(&Cancel, cx);
10783
10784 let snapshot = editor.snapshot(cx);
10785 let all_hunks = editor_hunks(editor, &snapshot, cx);
10786 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
10787 assert_eq!(
10788 expanded_hunks_background_highlights(editor, cx),
10789 Vec::new(),
10790 "After cancelling in editor, no git highlights should be left"
10791 );
10792 assert_eq!(
10793 all_expanded_hunks,
10794 Vec::new(),
10795 "After cancelling in editor, no hunks should be expanded"
10796 );
10797 assert_eq!(
10798 all_hunks, unexpanded_hunks,
10799 "After cancelling in editor, regular hunks' coordinates should get back to normal"
10800 );
10801 });
10802}
10803
10804#[gpui::test]
10805async fn test_toggled_diff_base_change(
10806 executor: BackgroundExecutor,
10807 cx: &mut gpui::TestAppContext,
10808) {
10809 init_test(cx, |_| {});
10810
10811 let mut cx = EditorTestContext::new(cx).await;
10812
10813 let diff_base = r#"
10814 use some::mod1;
10815 use some::mod2;
10816
10817 const A: u32 = 42;
10818 const B: u32 = 42;
10819 const C: u32 = 42;
10820
10821 fn main(ˇ) {
10822 println!("hello");
10823
10824 println!("world");
10825 }
10826 "#
10827 .unindent();
10828
10829 cx.set_state(
10830 &r#"
10831 use some::mod2;
10832
10833 const A: u32 = 42;
10834 const C: u32 = 42;
10835
10836 fn main(ˇ) {
10837 //println!("hello");
10838
10839 println!("world");
10840 //
10841 //
10842 }
10843 "#
10844 .unindent(),
10845 );
10846
10847 cx.set_diff_base(Some(&diff_base));
10848 executor.run_until_parked();
10849 cx.update_editor(|editor, cx| {
10850 let snapshot = editor.snapshot(cx);
10851 let all_hunks = editor_hunks(editor, &snapshot, cx);
10852 assert_eq!(
10853 all_hunks,
10854 vec![
10855 (
10856 "use some::mod1;\n".to_string(),
10857 DiffHunkStatus::Removed,
10858 DisplayRow(0)..DisplayRow(0)
10859 ),
10860 (
10861 "const B: u32 = 42;\n".to_string(),
10862 DiffHunkStatus::Removed,
10863 DisplayRow(3)..DisplayRow(3)
10864 ),
10865 (
10866 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
10867 DiffHunkStatus::Modified,
10868 DisplayRow(5)..DisplayRow(7)
10869 ),
10870 (
10871 "".to_string(),
10872 DiffHunkStatus::Added,
10873 DisplayRow(9)..DisplayRow(11)
10874 ),
10875 ]
10876 );
10877 });
10878
10879 cx.update_editor(|editor, cx| {
10880 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10881 });
10882 executor.run_until_parked();
10883 cx.assert_editor_state(
10884 &r#"
10885 use some::mod2;
10886
10887 const A: u32 = 42;
10888 const C: u32 = 42;
10889
10890 fn main(ˇ) {
10891 //println!("hello");
10892
10893 println!("world");
10894 //
10895 //
10896 }
10897 "#
10898 .unindent(),
10899 );
10900 cx.update_editor(|editor, cx| {
10901 let snapshot = editor.snapshot(cx);
10902 let all_hunks = editor_hunks(editor, &snapshot, cx);
10903 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10904 assert_eq!(
10905 expanded_hunks_background_highlights(editor, cx),
10906 vec![DisplayRow(9)..=DisplayRow(10), DisplayRow(13)..=DisplayRow(14)],
10907 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
10908 );
10909 assert_eq!(
10910 all_hunks,
10911 vec![
10912 ("use some::mod1;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(1)..DisplayRow(1)),
10913 ("const B: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(5)..DisplayRow(5)),
10914 ("fn main(ˇ) {\n println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(9)..DisplayRow(11)),
10915 ("".to_string(), DiffHunkStatus::Added, DisplayRow(13)..DisplayRow(15)),
10916 ],
10917 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
10918 (from modified and removed hunks)"
10919 );
10920 assert_eq!(
10921 all_hunks, all_expanded_hunks,
10922 "Editor hunks should not change and all be expanded"
10923 );
10924 });
10925
10926 cx.set_diff_base(Some("new diff base!"));
10927 executor.run_until_parked();
10928
10929 cx.update_editor(|editor, cx| {
10930 let snapshot = editor.snapshot(cx);
10931 let all_hunks = editor_hunks(editor, &snapshot, cx);
10932 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
10933 assert_eq!(
10934 expanded_hunks_background_highlights(editor, cx),
10935 Vec::new(),
10936 "After diff base is changed, old git highlights should be removed"
10937 );
10938 assert_eq!(
10939 all_expanded_hunks,
10940 Vec::new(),
10941 "After diff base is changed, old git hunk expansions should be removed"
10942 );
10943 assert_eq!(
10944 all_hunks,
10945 vec![(
10946 "new diff base!".to_string(),
10947 DiffHunkStatus::Modified,
10948 DisplayRow(0)..snapshot.display_snapshot.max_point().row()
10949 )],
10950 "After diff base is changed, hunks should update"
10951 );
10952 });
10953}
10954
10955#[gpui::test]
10956async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10957 init_test(cx, |_| {});
10958
10959 let mut cx = EditorTestContext::new(cx).await;
10960
10961 let diff_base = r#"
10962 use some::mod1;
10963 use some::mod2;
10964
10965 const A: u32 = 42;
10966 const B: u32 = 42;
10967 const C: u32 = 42;
10968
10969 fn main(ˇ) {
10970 println!("hello");
10971
10972 println!("world");
10973 }
10974
10975 fn another() {
10976 println!("another");
10977 }
10978
10979 fn another2() {
10980 println!("another2");
10981 }
10982 "#
10983 .unindent();
10984
10985 cx.set_state(
10986 &r#"
10987 «use some::mod2;
10988
10989 const A: u32 = 42;
10990 const C: u32 = 42;
10991
10992 fn main() {
10993 //println!("hello");
10994
10995 println!("world");
10996 //
10997 //ˇ»
10998 }
10999
11000 fn another() {
11001 println!("another");
11002 println!("another");
11003 }
11004
11005 println!("another2");
11006 }
11007 "#
11008 .unindent(),
11009 );
11010
11011 cx.set_diff_base(Some(&diff_base));
11012 executor.run_until_parked();
11013 cx.update_editor(|editor, cx| {
11014 let snapshot = editor.snapshot(cx);
11015 let all_hunks = editor_hunks(editor, &snapshot, cx);
11016 assert_eq!(
11017 all_hunks,
11018 vec![
11019 (
11020 "use some::mod1;\n".to_string(),
11021 DiffHunkStatus::Removed,
11022 DisplayRow(0)..DisplayRow(0)
11023 ),
11024 (
11025 "const B: u32 = 42;\n".to_string(),
11026 DiffHunkStatus::Removed,
11027 DisplayRow(3)..DisplayRow(3)
11028 ),
11029 (
11030 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11031 DiffHunkStatus::Modified,
11032 DisplayRow(5)..DisplayRow(7)
11033 ),
11034 (
11035 "".to_string(),
11036 DiffHunkStatus::Added,
11037 DisplayRow(9)..DisplayRow(11)
11038 ),
11039 (
11040 "".to_string(),
11041 DiffHunkStatus::Added,
11042 DisplayRow(15)..DisplayRow(16)
11043 ),
11044 (
11045 "fn another2() {\n".to_string(),
11046 DiffHunkStatus::Removed,
11047 DisplayRow(18)..DisplayRow(18)
11048 ),
11049 ]
11050 );
11051 });
11052
11053 cx.update_editor(|editor, cx| {
11054 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11055 });
11056 executor.run_until_parked();
11057 cx.assert_editor_state(
11058 &r#"
11059 «use some::mod2;
11060
11061 const A: u32 = 42;
11062 const C: u32 = 42;
11063
11064 fn main() {
11065 //println!("hello");
11066
11067 println!("world");
11068 //
11069 //ˇ»
11070 }
11071
11072 fn another() {
11073 println!("another");
11074 println!("another");
11075 }
11076
11077 println!("another2");
11078 }
11079 "#
11080 .unindent(),
11081 );
11082 cx.update_editor(|editor, cx| {
11083 let snapshot = editor.snapshot(cx);
11084 let all_hunks = editor_hunks(editor, &snapshot, cx);
11085 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11086 assert_eq!(
11087 expanded_hunks_background_highlights(editor, cx),
11088 vec![
11089 DisplayRow(9)..=DisplayRow(10),
11090 DisplayRow(13)..=DisplayRow(14),
11091 DisplayRow(19)..=DisplayRow(19)
11092 ]
11093 );
11094 assert_eq!(
11095 all_hunks,
11096 vec![
11097 (
11098 "use some::mod1;\n".to_string(),
11099 DiffHunkStatus::Removed,
11100 DisplayRow(1)..DisplayRow(1)
11101 ),
11102 (
11103 "const B: u32 = 42;\n".to_string(),
11104 DiffHunkStatus::Removed,
11105 DisplayRow(5)..DisplayRow(5)
11106 ),
11107 (
11108 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11109 DiffHunkStatus::Modified,
11110 DisplayRow(9)..DisplayRow(11)
11111 ),
11112 (
11113 "".to_string(),
11114 DiffHunkStatus::Added,
11115 DisplayRow(13)..DisplayRow(15)
11116 ),
11117 (
11118 "".to_string(),
11119 DiffHunkStatus::Added,
11120 DisplayRow(19)..DisplayRow(20)
11121 ),
11122 (
11123 "fn another2() {\n".to_string(),
11124 DiffHunkStatus::Removed,
11125 DisplayRow(23)..DisplayRow(23)
11126 ),
11127 ],
11128 );
11129 assert_eq!(all_hunks, all_expanded_hunks);
11130 });
11131
11132 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
11133 cx.executor().run_until_parked();
11134 cx.assert_editor_state(
11135 &r#"
11136 «use some::mod2;
11137
11138 const A: u32 = 42;
11139 const C: u32 = 42;
11140
11141 fn main() {
11142 //println!("hello");
11143
11144 println!("world");
11145 //
11146 //ˇ»
11147 }
11148
11149 fn another() {
11150 println!("another");
11151 println!("another");
11152 }
11153
11154 println!("another2");
11155 }
11156 "#
11157 .unindent(),
11158 );
11159 cx.update_editor(|editor, cx| {
11160 let snapshot = editor.snapshot(cx);
11161 let all_hunks = editor_hunks(editor, &snapshot, cx);
11162 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11163 assert_eq!(
11164 expanded_hunks_background_highlights(editor, cx),
11165 vec![DisplayRow(0)..=DisplayRow(0), DisplayRow(5)..=DisplayRow(5)],
11166 "Only one hunk is left not folded, its highlight should be visible"
11167 );
11168 assert_eq!(
11169 all_hunks,
11170 vec![
11171 (
11172 "use some::mod1;\n".to_string(),
11173 DiffHunkStatus::Removed,
11174 DisplayRow(0)..DisplayRow(0)
11175 ),
11176 (
11177 "const B: u32 = 42;\n".to_string(),
11178 DiffHunkStatus::Removed,
11179 DisplayRow(0)..DisplayRow(0)
11180 ),
11181 (
11182 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11183 DiffHunkStatus::Modified,
11184 DisplayRow(0)..DisplayRow(0)
11185 ),
11186 (
11187 "".to_string(),
11188 DiffHunkStatus::Added,
11189 DisplayRow(0)..DisplayRow(1)
11190 ),
11191 (
11192 "".to_string(),
11193 DiffHunkStatus::Added,
11194 DisplayRow(5)..DisplayRow(6)
11195 ),
11196 (
11197 "fn another2() {\n".to_string(),
11198 DiffHunkStatus::Removed,
11199 DisplayRow(9)..DisplayRow(9)
11200 ),
11201 ],
11202 "Hunk list should still return shifted folded hunks"
11203 );
11204 assert_eq!(
11205 all_expanded_hunks,
11206 vec![
11207 (
11208 "".to_string(),
11209 DiffHunkStatus::Added,
11210 DisplayRow(5)..DisplayRow(6)
11211 ),
11212 (
11213 "fn another2() {\n".to_string(),
11214 DiffHunkStatus::Removed,
11215 DisplayRow(9)..DisplayRow(9)
11216 ),
11217 ],
11218 "Only non-folded hunks should be left expanded"
11219 );
11220 });
11221
11222 cx.update_editor(|editor, cx| {
11223 editor.select_all(&SelectAll, cx);
11224 editor.unfold_lines(&UnfoldLines, cx);
11225 });
11226 cx.executor().run_until_parked();
11227 cx.assert_editor_state(
11228 &r#"
11229 «use some::mod2;
11230
11231 const A: u32 = 42;
11232 const C: u32 = 42;
11233
11234 fn main() {
11235 //println!("hello");
11236
11237 println!("world");
11238 //
11239 //
11240 }
11241
11242 fn another() {
11243 println!("another");
11244 println!("another");
11245 }
11246
11247 println!("another2");
11248 }
11249 ˇ»"#
11250 .unindent(),
11251 );
11252 cx.update_editor(|editor, cx| {
11253 let snapshot = editor.snapshot(cx);
11254 let all_hunks = editor_hunks(editor, &snapshot, cx);
11255 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11256 assert_eq!(
11257 expanded_hunks_background_highlights(editor, cx),
11258 vec![
11259 DisplayRow(9)..=DisplayRow(10),
11260 DisplayRow(13)..=DisplayRow(14),
11261 DisplayRow(19)..=DisplayRow(19)
11262 ],
11263 "After unfolding, all hunk diffs should be visible again"
11264 );
11265 assert_eq!(
11266 all_hunks,
11267 vec![
11268 (
11269 "use some::mod1;\n".to_string(),
11270 DiffHunkStatus::Removed,
11271 DisplayRow(1)..DisplayRow(1)
11272 ),
11273 (
11274 "const B: u32 = 42;\n".to_string(),
11275 DiffHunkStatus::Removed,
11276 DisplayRow(5)..DisplayRow(5)
11277 ),
11278 (
11279 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
11280 DiffHunkStatus::Modified,
11281 DisplayRow(9)..DisplayRow(11)
11282 ),
11283 (
11284 "".to_string(),
11285 DiffHunkStatus::Added,
11286 DisplayRow(13)..DisplayRow(15)
11287 ),
11288 (
11289 "".to_string(),
11290 DiffHunkStatus::Added,
11291 DisplayRow(19)..DisplayRow(20)
11292 ),
11293 (
11294 "fn another2() {\n".to_string(),
11295 DiffHunkStatus::Removed,
11296 DisplayRow(23)..DisplayRow(23)
11297 ),
11298 ],
11299 );
11300 assert_eq!(all_hunks, all_expanded_hunks);
11301 });
11302}
11303
11304#[gpui::test]
11305async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
11306 init_test(cx, |_| {});
11307
11308 let cols = 4;
11309 let rows = 10;
11310 let sample_text_1 = sample_text(rows, cols, 'a');
11311 assert_eq!(
11312 sample_text_1,
11313 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11314 );
11315 let modified_sample_text_1 = "aaaa\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
11316 let sample_text_2 = sample_text(rows, cols, 'l');
11317 assert_eq!(
11318 sample_text_2,
11319 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11320 );
11321 let modified_sample_text_2 = "llll\nmmmm\n1n1n1n1n1\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
11322 let sample_text_3 = sample_text(rows, cols, 'v');
11323 assert_eq!(
11324 sample_text_3,
11325 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11326 );
11327 let modified_sample_text_3 =
11328 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n@@@@\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
11329 let buffer_1 = cx.new_model(|cx| {
11330 let mut buffer = Buffer::local(modified_sample_text_1.to_string(), cx);
11331 buffer.set_diff_base(Some(sample_text_1.clone()), cx);
11332 buffer
11333 });
11334 let buffer_2 = cx.new_model(|cx| {
11335 let mut buffer = Buffer::local(modified_sample_text_2.to_string(), cx);
11336 buffer.set_diff_base(Some(sample_text_2.clone()), cx);
11337 buffer
11338 });
11339 let buffer_3 = cx.new_model(|cx| {
11340 let mut buffer = Buffer::local(modified_sample_text_3.to_string(), cx);
11341 buffer.set_diff_base(Some(sample_text_3.clone()), cx);
11342 buffer
11343 });
11344
11345 let multi_buffer = cx.new_model(|cx| {
11346 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
11347 multibuffer.push_excerpts(
11348 buffer_1.clone(),
11349 [
11350 ExcerptRange {
11351 context: Point::new(0, 0)..Point::new(3, 0),
11352 primary: None,
11353 },
11354 ExcerptRange {
11355 context: Point::new(5, 0)..Point::new(7, 0),
11356 primary: None,
11357 },
11358 ExcerptRange {
11359 context: Point::new(9, 0)..Point::new(10, 4),
11360 primary: None,
11361 },
11362 ],
11363 cx,
11364 );
11365 multibuffer.push_excerpts(
11366 buffer_2.clone(),
11367 [
11368 ExcerptRange {
11369 context: Point::new(0, 0)..Point::new(3, 0),
11370 primary: None,
11371 },
11372 ExcerptRange {
11373 context: Point::new(5, 0)..Point::new(7, 0),
11374 primary: None,
11375 },
11376 ExcerptRange {
11377 context: Point::new(9, 0)..Point::new(10, 4),
11378 primary: None,
11379 },
11380 ],
11381 cx,
11382 );
11383 multibuffer.push_excerpts(
11384 buffer_3.clone(),
11385 [
11386 ExcerptRange {
11387 context: Point::new(0, 0)..Point::new(3, 0),
11388 primary: None,
11389 },
11390 ExcerptRange {
11391 context: Point::new(5, 0)..Point::new(7, 0),
11392 primary: None,
11393 },
11394 ExcerptRange {
11395 context: Point::new(9, 0)..Point::new(10, 4),
11396 primary: None,
11397 },
11398 ],
11399 cx,
11400 );
11401 multibuffer
11402 });
11403
11404 let fs = FakeFs::new(cx.executor());
11405 fs.insert_tree(
11406 "/a",
11407 json!({
11408 "main.rs": modified_sample_text_1,
11409 "other.rs": modified_sample_text_2,
11410 "lib.rs": modified_sample_text_3,
11411 }),
11412 )
11413 .await;
11414
11415 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11416 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11417 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11418 let multi_buffer_editor = cx.new_view(|cx| {
11419 Editor::new(
11420 EditorMode::Full,
11421 multi_buffer,
11422 Some(project.clone()),
11423 true,
11424 cx,
11425 )
11426 });
11427 cx.executor().run_until_parked();
11428
11429 let expected_all_hunks = vec![
11430 (
11431 "bbbb\n".to_string(),
11432 DiffHunkStatus::Removed,
11433 DisplayRow(4)..DisplayRow(4),
11434 ),
11435 (
11436 "nnnn\n".to_string(),
11437 DiffHunkStatus::Modified,
11438 DisplayRow(21)..DisplayRow(22),
11439 ),
11440 (
11441 "".to_string(),
11442 DiffHunkStatus::Added,
11443 DisplayRow(41)..DisplayRow(42),
11444 ),
11445 ];
11446 let expected_all_hunks_shifted = vec![
11447 (
11448 "bbbb\n".to_string(),
11449 DiffHunkStatus::Removed,
11450 DisplayRow(5)..DisplayRow(5),
11451 ),
11452 (
11453 "nnnn\n".to_string(),
11454 DiffHunkStatus::Modified,
11455 DisplayRow(23)..DisplayRow(24),
11456 ),
11457 (
11458 "".to_string(),
11459 DiffHunkStatus::Added,
11460 DisplayRow(43)..DisplayRow(44),
11461 ),
11462 ];
11463
11464 multi_buffer_editor.update(cx, |editor, cx| {
11465 let snapshot = editor.snapshot(cx);
11466 let all_hunks = editor_hunks(editor, &snapshot, cx);
11467 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11468 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11469 assert_eq!(all_hunks, expected_all_hunks);
11470 assert_eq!(all_expanded_hunks, Vec::new());
11471 });
11472
11473 multi_buffer_editor.update(cx, |editor, cx| {
11474 editor.select_all(&SelectAll, cx);
11475 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11476 });
11477 cx.executor().run_until_parked();
11478 multi_buffer_editor.update(cx, |editor, cx| {
11479 let snapshot = editor.snapshot(cx);
11480 let all_hunks = editor_hunks(editor, &snapshot, cx);
11481 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11482 assert_eq!(
11483 expanded_hunks_background_highlights(editor, cx),
11484 vec![
11485 DisplayRow(23)..=DisplayRow(23),
11486 DisplayRow(43)..=DisplayRow(43)
11487 ],
11488 );
11489 assert_eq!(all_hunks, expected_all_hunks_shifted);
11490 assert_eq!(all_hunks, all_expanded_hunks);
11491 });
11492
11493 multi_buffer_editor.update(cx, |editor, cx| {
11494 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11495 });
11496 cx.executor().run_until_parked();
11497 multi_buffer_editor.update(cx, |editor, cx| {
11498 let snapshot = editor.snapshot(cx);
11499 let all_hunks = editor_hunks(editor, &snapshot, cx);
11500 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11501 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11502 assert_eq!(all_hunks, expected_all_hunks);
11503 assert_eq!(all_expanded_hunks, Vec::new());
11504 });
11505
11506 multi_buffer_editor.update(cx, |editor, cx| {
11507 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11508 });
11509 cx.executor().run_until_parked();
11510 multi_buffer_editor.update(cx, |editor, cx| {
11511 let snapshot = editor.snapshot(cx);
11512 let all_hunks = editor_hunks(editor, &snapshot, cx);
11513 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11514 assert_eq!(
11515 expanded_hunks_background_highlights(editor, cx),
11516 vec![
11517 DisplayRow(23)..=DisplayRow(23),
11518 DisplayRow(43)..=DisplayRow(43)
11519 ],
11520 );
11521 assert_eq!(all_hunks, expected_all_hunks_shifted);
11522 assert_eq!(all_hunks, all_expanded_hunks);
11523 });
11524
11525 multi_buffer_editor.update(cx, |editor, cx| {
11526 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11527 });
11528 cx.executor().run_until_parked();
11529 multi_buffer_editor.update(cx, |editor, cx| {
11530 let snapshot = editor.snapshot(cx);
11531 let all_hunks = editor_hunks(editor, &snapshot, cx);
11532 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11533 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11534 assert_eq!(all_hunks, expected_all_hunks);
11535 assert_eq!(all_expanded_hunks, Vec::new());
11536 });
11537}
11538
11539#[gpui::test]
11540async fn test_edits_around_toggled_additions(
11541 executor: BackgroundExecutor,
11542 cx: &mut gpui::TestAppContext,
11543) {
11544 init_test(cx, |_| {});
11545
11546 let mut cx = EditorTestContext::new(cx).await;
11547
11548 let diff_base = r#"
11549 use some::mod1;
11550 use some::mod2;
11551
11552 const A: u32 = 42;
11553
11554 fn main() {
11555 println!("hello");
11556
11557 println!("world");
11558 }
11559 "#
11560 .unindent();
11561 executor.run_until_parked();
11562 cx.set_state(
11563 &r#"
11564 use some::mod1;
11565 use some::mod2;
11566
11567 const A: u32 = 42;
11568 const B: u32 = 42;
11569 const C: u32 = 42;
11570 ˇ
11571
11572 fn main() {
11573 println!("hello");
11574
11575 println!("world");
11576 }
11577 "#
11578 .unindent(),
11579 );
11580
11581 cx.set_diff_base(Some(&diff_base));
11582 executor.run_until_parked();
11583 cx.update_editor(|editor, cx| {
11584 let snapshot = editor.snapshot(cx);
11585 let all_hunks = editor_hunks(editor, &snapshot, cx);
11586 assert_eq!(
11587 all_hunks,
11588 vec![(
11589 "".to_string(),
11590 DiffHunkStatus::Added,
11591 DisplayRow(4)..DisplayRow(7)
11592 )]
11593 );
11594 });
11595 cx.update_editor(|editor, cx| {
11596 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11597 });
11598 executor.run_until_parked();
11599 cx.assert_editor_state(
11600 &r#"
11601 use some::mod1;
11602 use some::mod2;
11603
11604 const A: u32 = 42;
11605 const B: u32 = 42;
11606 const C: u32 = 42;
11607 ˇ
11608
11609 fn main() {
11610 println!("hello");
11611
11612 println!("world");
11613 }
11614 "#
11615 .unindent(),
11616 );
11617 cx.update_editor(|editor, cx| {
11618 let snapshot = editor.snapshot(cx);
11619 let all_hunks = editor_hunks(editor, &snapshot, cx);
11620 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11621 assert_eq!(
11622 all_hunks,
11623 vec![(
11624 "".to_string(),
11625 DiffHunkStatus::Added,
11626 DisplayRow(4)..DisplayRow(7)
11627 )]
11628 );
11629 assert_eq!(
11630 expanded_hunks_background_highlights(editor, cx),
11631 vec![DisplayRow(4)..=DisplayRow(6)]
11632 );
11633 assert_eq!(all_hunks, all_expanded_hunks);
11634 });
11635
11636 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
11637 executor.run_until_parked();
11638 cx.assert_editor_state(
11639 &r#"
11640 use some::mod1;
11641 use some::mod2;
11642
11643 const A: u32 = 42;
11644 const B: u32 = 42;
11645 const C: u32 = 42;
11646 const D: u32 = 42;
11647 ˇ
11648
11649 fn main() {
11650 println!("hello");
11651
11652 println!("world");
11653 }
11654 "#
11655 .unindent(),
11656 );
11657 cx.update_editor(|editor, cx| {
11658 let snapshot = editor.snapshot(cx);
11659 let all_hunks = editor_hunks(editor, &snapshot, cx);
11660 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11661 assert_eq!(
11662 all_hunks,
11663 vec![(
11664 "".to_string(),
11665 DiffHunkStatus::Added,
11666 DisplayRow(4)..DisplayRow(8)
11667 )]
11668 );
11669 assert_eq!(
11670 expanded_hunks_background_highlights(editor, cx),
11671 vec![DisplayRow(4)..=DisplayRow(6)],
11672 "Edited hunk should have one more line added"
11673 );
11674 assert_eq!(
11675 all_hunks, all_expanded_hunks,
11676 "Expanded hunk should also grow with the addition"
11677 );
11678 });
11679
11680 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
11681 executor.run_until_parked();
11682 cx.assert_editor_state(
11683 &r#"
11684 use some::mod1;
11685 use some::mod2;
11686
11687 const A: u32 = 42;
11688 const B: u32 = 42;
11689 const C: u32 = 42;
11690 const D: u32 = 42;
11691 const E: u32 = 42;
11692 ˇ
11693
11694 fn main() {
11695 println!("hello");
11696
11697 println!("world");
11698 }
11699 "#
11700 .unindent(),
11701 );
11702 cx.update_editor(|editor, cx| {
11703 let snapshot = editor.snapshot(cx);
11704 let all_hunks = editor_hunks(editor, &snapshot, cx);
11705 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11706 assert_eq!(
11707 all_hunks,
11708 vec![(
11709 "".to_string(),
11710 DiffHunkStatus::Added,
11711 DisplayRow(4)..DisplayRow(9)
11712 )]
11713 );
11714 assert_eq!(
11715 expanded_hunks_background_highlights(editor, cx),
11716 vec![DisplayRow(4)..=DisplayRow(6)],
11717 "Edited hunk should have one more line added"
11718 );
11719 assert_eq!(all_hunks, all_expanded_hunks);
11720 });
11721
11722 cx.update_editor(|editor, cx| {
11723 editor.move_up(&MoveUp, cx);
11724 editor.delete_line(&DeleteLine, cx);
11725 });
11726 executor.run_until_parked();
11727 cx.assert_editor_state(
11728 &r#"
11729 use some::mod1;
11730 use some::mod2;
11731
11732 const A: u32 = 42;
11733 const B: u32 = 42;
11734 const C: u32 = 42;
11735 const D: u32 = 42;
11736 ˇ
11737
11738 fn main() {
11739 println!("hello");
11740
11741 println!("world");
11742 }
11743 "#
11744 .unindent(),
11745 );
11746 cx.update_editor(|editor, cx| {
11747 let snapshot = editor.snapshot(cx);
11748 let all_hunks = editor_hunks(editor, &snapshot, cx);
11749 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11750 assert_eq!(
11751 all_hunks,
11752 vec![(
11753 "".to_string(),
11754 DiffHunkStatus::Added,
11755 DisplayRow(4)..DisplayRow(8)
11756 )]
11757 );
11758 assert_eq!(
11759 expanded_hunks_background_highlights(editor, cx),
11760 vec![DisplayRow(4)..=DisplayRow(6)],
11761 "Deleting a line should shrint the hunk"
11762 );
11763 assert_eq!(
11764 all_hunks, all_expanded_hunks,
11765 "Expanded hunk should also shrink with the addition"
11766 );
11767 });
11768
11769 cx.update_editor(|editor, cx| {
11770 editor.move_up(&MoveUp, cx);
11771 editor.delete_line(&DeleteLine, cx);
11772 editor.move_up(&MoveUp, cx);
11773 editor.delete_line(&DeleteLine, cx);
11774 editor.move_up(&MoveUp, cx);
11775 editor.delete_line(&DeleteLine, cx);
11776 });
11777 executor.run_until_parked();
11778 cx.assert_editor_state(
11779 &r#"
11780 use some::mod1;
11781 use some::mod2;
11782
11783 const A: u32 = 42;
11784 ˇ
11785
11786 fn main() {
11787 println!("hello");
11788
11789 println!("world");
11790 }
11791 "#
11792 .unindent(),
11793 );
11794 cx.update_editor(|editor, cx| {
11795 let snapshot = editor.snapshot(cx);
11796 let all_hunks = editor_hunks(editor, &snapshot, cx);
11797 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11798 assert_eq!(
11799 all_hunks,
11800 vec![(
11801 "".to_string(),
11802 DiffHunkStatus::Added,
11803 DisplayRow(5)..DisplayRow(6)
11804 )]
11805 );
11806 assert_eq!(
11807 expanded_hunks_background_highlights(editor, cx),
11808 vec![DisplayRow(5)..=DisplayRow(5)]
11809 );
11810 assert_eq!(all_hunks, all_expanded_hunks);
11811 });
11812
11813 cx.update_editor(|editor, cx| {
11814 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
11815 editor.delete_line(&DeleteLine, cx);
11816 });
11817 executor.run_until_parked();
11818 cx.assert_editor_state(
11819 &r#"
11820 ˇ
11821
11822 fn main() {
11823 println!("hello");
11824
11825 println!("world");
11826 }
11827 "#
11828 .unindent(),
11829 );
11830 cx.update_editor(|editor, cx| {
11831 let snapshot = editor.snapshot(cx);
11832 let all_hunks = editor_hunks(editor, &snapshot, cx);
11833 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11834 assert_eq!(
11835 all_hunks,
11836 vec![
11837 (
11838 "use some::mod1;\nuse some::mod2;\n".to_string(),
11839 DiffHunkStatus::Removed,
11840 DisplayRow(0)..DisplayRow(0)
11841 ),
11842 (
11843 "const A: u32 = 42;\n".to_string(),
11844 DiffHunkStatus::Removed,
11845 DisplayRow(2)..DisplayRow(2)
11846 )
11847 ]
11848 );
11849 assert_eq!(
11850 expanded_hunks_background_highlights(editor, cx),
11851 Vec::new(),
11852 "Should close all stale expanded addition hunks"
11853 );
11854 assert_eq!(
11855 all_expanded_hunks,
11856 vec![(
11857 "const A: u32 = 42;\n".to_string(),
11858 DiffHunkStatus::Removed,
11859 DisplayRow(2)..DisplayRow(2)
11860 )],
11861 "Should open hunks that were adjacent to the stale addition one"
11862 );
11863 });
11864}
11865
11866#[gpui::test]
11867async fn test_edits_around_toggled_deletions(
11868 executor: BackgroundExecutor,
11869 cx: &mut gpui::TestAppContext,
11870) {
11871 init_test(cx, |_| {});
11872
11873 let mut cx = EditorTestContext::new(cx).await;
11874
11875 let diff_base = r#"
11876 use some::mod1;
11877 use some::mod2;
11878
11879 const A: u32 = 42;
11880 const B: u32 = 42;
11881 const C: u32 = 42;
11882
11883
11884 fn main() {
11885 println!("hello");
11886
11887 println!("world");
11888 }
11889 "#
11890 .unindent();
11891 executor.run_until_parked();
11892 cx.set_state(
11893 &r#"
11894 use some::mod1;
11895 use some::mod2;
11896
11897 ˇconst B: u32 = 42;
11898 const C: u32 = 42;
11899
11900
11901 fn main() {
11902 println!("hello");
11903
11904 println!("world");
11905 }
11906 "#
11907 .unindent(),
11908 );
11909
11910 cx.set_diff_base(Some(&diff_base));
11911 executor.run_until_parked();
11912 cx.update_editor(|editor, cx| {
11913 let snapshot = editor.snapshot(cx);
11914 let all_hunks = editor_hunks(editor, &snapshot, cx);
11915 assert_eq!(
11916 all_hunks,
11917 vec![(
11918 "const A: u32 = 42;\n".to_string(),
11919 DiffHunkStatus::Removed,
11920 DisplayRow(3)..DisplayRow(3)
11921 )]
11922 );
11923 });
11924 cx.update_editor(|editor, cx| {
11925 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11926 });
11927 executor.run_until_parked();
11928 cx.assert_editor_state(
11929 &r#"
11930 use some::mod1;
11931 use some::mod2;
11932
11933 ˇconst B: u32 = 42;
11934 const C: u32 = 42;
11935
11936
11937 fn main() {
11938 println!("hello");
11939
11940 println!("world");
11941 }
11942 "#
11943 .unindent(),
11944 );
11945 cx.update_editor(|editor, cx| {
11946 let snapshot = editor.snapshot(cx);
11947 let all_hunks = editor_hunks(editor, &snapshot, cx);
11948 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11949 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11950 assert_eq!(
11951 all_hunks,
11952 vec![(
11953 "const A: u32 = 42;\n".to_string(),
11954 DiffHunkStatus::Removed,
11955 DisplayRow(4)..DisplayRow(4)
11956 )]
11957 );
11958 assert_eq!(all_hunks, all_expanded_hunks);
11959 });
11960
11961 cx.update_editor(|editor, cx| {
11962 editor.delete_line(&DeleteLine, cx);
11963 });
11964 executor.run_until_parked();
11965 cx.assert_editor_state(
11966 &r#"
11967 use some::mod1;
11968 use some::mod2;
11969
11970 ˇconst C: u32 = 42;
11971
11972
11973 fn main() {
11974 println!("hello");
11975
11976 println!("world");
11977 }
11978 "#
11979 .unindent(),
11980 );
11981 cx.update_editor(|editor, cx| {
11982 let snapshot = editor.snapshot(cx);
11983 let all_hunks = editor_hunks(editor, &snapshot, cx);
11984 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11985 assert_eq!(
11986 expanded_hunks_background_highlights(editor, cx),
11987 Vec::new(),
11988 "Deleted hunks do not highlight current editor's background"
11989 );
11990 assert_eq!(
11991 all_hunks,
11992 vec![(
11993 "const A: u32 = 42;\nconst B: u32 = 42;\n".to_string(),
11994 DiffHunkStatus::Removed,
11995 DisplayRow(5)..DisplayRow(5)
11996 )]
11997 );
11998 assert_eq!(all_hunks, all_expanded_hunks);
11999 });
12000
12001 cx.update_editor(|editor, cx| {
12002 editor.delete_line(&DeleteLine, cx);
12003 });
12004 executor.run_until_parked();
12005 cx.assert_editor_state(
12006 &r#"
12007 use some::mod1;
12008 use some::mod2;
12009
12010 ˇ
12011
12012 fn main() {
12013 println!("hello");
12014
12015 println!("world");
12016 }
12017 "#
12018 .unindent(),
12019 );
12020 cx.update_editor(|editor, cx| {
12021 let snapshot = editor.snapshot(cx);
12022 let all_hunks = editor_hunks(editor, &snapshot, cx);
12023 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12024 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
12025 assert_eq!(
12026 all_hunks,
12027 vec![(
12028 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12029 DiffHunkStatus::Removed,
12030 DisplayRow(6)..DisplayRow(6)
12031 )]
12032 );
12033 assert_eq!(all_hunks, all_expanded_hunks);
12034 });
12035
12036 cx.update_editor(|editor, cx| {
12037 editor.handle_input("replacement", cx);
12038 });
12039 executor.run_until_parked();
12040 cx.assert_editor_state(
12041 &r#"
12042 use some::mod1;
12043 use some::mod2;
12044
12045 replacementˇ
12046
12047 fn main() {
12048 println!("hello");
12049
12050 println!("world");
12051 }
12052 "#
12053 .unindent(),
12054 );
12055 cx.update_editor(|editor, cx| {
12056 let snapshot = editor.snapshot(cx);
12057 let all_hunks = editor_hunks(editor, &snapshot, cx);
12058 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12059 assert_eq!(
12060 all_hunks,
12061 vec![(
12062 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n\n".to_string(),
12063 DiffHunkStatus::Modified,
12064 DisplayRow(7)..DisplayRow(8)
12065 )]
12066 );
12067 assert_eq!(
12068 expanded_hunks_background_highlights(editor, cx),
12069 vec![DisplayRow(7)..=DisplayRow(7)],
12070 "Modified expanded hunks should display additions and highlight their background"
12071 );
12072 assert_eq!(all_hunks, all_expanded_hunks);
12073 });
12074}
12075
12076#[gpui::test]
12077async fn test_edits_around_toggled_modifications(
12078 executor: BackgroundExecutor,
12079 cx: &mut gpui::TestAppContext,
12080) {
12081 init_test(cx, |_| {});
12082
12083 let mut cx = EditorTestContext::new(cx).await;
12084
12085 let diff_base = r#"
12086 use some::mod1;
12087 use some::mod2;
12088
12089 const A: u32 = 42;
12090 const B: u32 = 42;
12091 const C: u32 = 42;
12092 const D: u32 = 42;
12093
12094
12095 fn main() {
12096 println!("hello");
12097
12098 println!("world");
12099 }"#
12100 .unindent();
12101 executor.run_until_parked();
12102 cx.set_state(
12103 &r#"
12104 use some::mod1;
12105 use some::mod2;
12106
12107 const A: u32 = 42;
12108 const B: u32 = 42;
12109 const C: u32 = 43ˇ
12110 const D: u32 = 42;
12111
12112
12113 fn main() {
12114 println!("hello");
12115
12116 println!("world");
12117 }"#
12118 .unindent(),
12119 );
12120
12121 cx.set_diff_base(Some(&diff_base));
12122 executor.run_until_parked();
12123 cx.update_editor(|editor, cx| {
12124 let snapshot = editor.snapshot(cx);
12125 let all_hunks = editor_hunks(editor, &snapshot, cx);
12126 assert_eq!(
12127 all_hunks,
12128 vec![(
12129 "const C: u32 = 42;\n".to_string(),
12130 DiffHunkStatus::Modified,
12131 DisplayRow(5)..DisplayRow(6)
12132 )]
12133 );
12134 });
12135 cx.update_editor(|editor, cx| {
12136 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, 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 const D: u32 = 42;
12148
12149
12150 fn main() {
12151 println!("hello");
12152
12153 println!("world");
12154 }"#
12155 .unindent(),
12156 );
12157 cx.update_editor(|editor, cx| {
12158 let snapshot = editor.snapshot(cx);
12159 let all_hunks = editor_hunks(editor, &snapshot, cx);
12160 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12161 assert_eq!(
12162 expanded_hunks_background_highlights(editor, cx),
12163 vec![DisplayRow(6)..=DisplayRow(6)],
12164 );
12165 assert_eq!(
12166 all_hunks,
12167 vec![(
12168 "const C: u32 = 42;\n".to_string(),
12169 DiffHunkStatus::Modified,
12170 DisplayRow(6)..DisplayRow(7)
12171 )]
12172 );
12173 assert_eq!(all_hunks, all_expanded_hunks);
12174 });
12175
12176 cx.update_editor(|editor, cx| {
12177 editor.handle_input("\nnew_line\n", cx);
12178 });
12179 executor.run_until_parked();
12180 cx.assert_editor_state(
12181 &r#"
12182 use some::mod1;
12183 use some::mod2;
12184
12185 const A: u32 = 42;
12186 const B: u32 = 42;
12187 const C: u32 = 43
12188 new_line
12189 ˇ
12190 const D: u32 = 42;
12191
12192
12193 fn main() {
12194 println!("hello");
12195
12196 println!("world");
12197 }"#
12198 .unindent(),
12199 );
12200 cx.update_editor(|editor, cx| {
12201 let snapshot = editor.snapshot(cx);
12202 let all_hunks = editor_hunks(editor, &snapshot, cx);
12203 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12204 assert_eq!(
12205 expanded_hunks_background_highlights(editor, cx),
12206 vec![DisplayRow(6)..=DisplayRow(6)],
12207 "Modified hunk should grow highlighted lines on more text additions"
12208 );
12209 assert_eq!(
12210 all_hunks,
12211 vec![(
12212 "const C: u32 = 42;\n".to_string(),
12213 DiffHunkStatus::Modified,
12214 DisplayRow(6)..DisplayRow(9)
12215 )]
12216 );
12217 assert_eq!(all_hunks, all_expanded_hunks);
12218 });
12219
12220 cx.update_editor(|editor, cx| {
12221 editor.move_up(&MoveUp, cx);
12222 editor.move_up(&MoveUp, cx);
12223 editor.move_up(&MoveUp, cx);
12224 editor.delete_line(&DeleteLine, cx);
12225 });
12226 executor.run_until_parked();
12227 cx.assert_editor_state(
12228 &r#"
12229 use some::mod1;
12230 use some::mod2;
12231
12232 const A: u32 = 42;
12233 ˇconst C: u32 = 43
12234 new_line
12235
12236 const D: u32 = 42;
12237
12238
12239 fn main() {
12240 println!("hello");
12241
12242 println!("world");
12243 }"#
12244 .unindent(),
12245 );
12246 cx.update_editor(|editor, cx| {
12247 let snapshot = editor.snapshot(cx);
12248 let all_hunks = editor_hunks(editor, &snapshot, cx);
12249 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12250 assert_eq!(
12251 expanded_hunks_background_highlights(editor, cx),
12252 vec![DisplayRow(6)..=DisplayRow(8)],
12253 );
12254 assert_eq!(
12255 all_hunks,
12256 vec![(
12257 "const B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12258 DiffHunkStatus::Modified,
12259 DisplayRow(6)..DisplayRow(9)
12260 )],
12261 "Modified hunk should grow deleted lines on text deletions above"
12262 );
12263 assert_eq!(all_hunks, all_expanded_hunks);
12264 });
12265
12266 cx.update_editor(|editor, cx| {
12267 editor.move_up(&MoveUp, cx);
12268 editor.handle_input("v", cx);
12269 });
12270 executor.run_until_parked();
12271 cx.assert_editor_state(
12272 &r#"
12273 use some::mod1;
12274 use some::mod2;
12275
12276 vˇconst A: u32 = 42;
12277 const C: u32 = 43
12278 new_line
12279
12280 const D: u32 = 42;
12281
12282
12283 fn main() {
12284 println!("hello");
12285
12286 println!("world");
12287 }"#
12288 .unindent(),
12289 );
12290 cx.update_editor(|editor, cx| {
12291 let snapshot = editor.snapshot(cx);
12292 let all_hunks = editor_hunks(editor, &snapshot, cx);
12293 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12294 assert_eq!(
12295 expanded_hunks_background_highlights(editor, cx),
12296 vec![DisplayRow(6)..=DisplayRow(9)],
12297 "Modified hunk should grow deleted lines on text modifications above"
12298 );
12299 assert_eq!(
12300 all_hunks,
12301 vec![(
12302 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12303 DiffHunkStatus::Modified,
12304 DisplayRow(6)..DisplayRow(10)
12305 )]
12306 );
12307 assert_eq!(all_hunks, all_expanded_hunks);
12308 });
12309
12310 cx.update_editor(|editor, cx| {
12311 editor.move_down(&MoveDown, cx);
12312 editor.move_down(&MoveDown, cx);
12313 editor.delete_line(&DeleteLine, cx)
12314 });
12315 executor.run_until_parked();
12316 cx.assert_editor_state(
12317 &r#"
12318 use some::mod1;
12319 use some::mod2;
12320
12321 vconst A: u32 = 42;
12322 const C: u32 = 43
12323 ˇ
12324 const D: u32 = 42;
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![DisplayRow(6)..=DisplayRow(8)],
12341 "Modified hunk should grow shrink lines on modification lines removal"
12342 );
12343 assert_eq!(
12344 all_hunks,
12345 vec![(
12346 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
12347 DiffHunkStatus::Modified,
12348 DisplayRow(6)..DisplayRow(9)
12349 )]
12350 );
12351 assert_eq!(all_hunks, all_expanded_hunks);
12352 });
12353
12354 cx.update_editor(|editor, cx| {
12355 editor.move_up(&MoveUp, cx);
12356 editor.move_up(&MoveUp, cx);
12357 editor.select_down_by_lines(&SelectDownByLines { lines: 4 }, cx);
12358 editor.delete_line(&DeleteLine, cx)
12359 });
12360 executor.run_until_parked();
12361 cx.assert_editor_state(
12362 &r#"
12363 use some::mod1;
12364 use some::mod2;
12365
12366 ˇ
12367
12368 fn main() {
12369 println!("hello");
12370
12371 println!("world");
12372 }"#
12373 .unindent(),
12374 );
12375 cx.update_editor(|editor, cx| {
12376 let snapshot = editor.snapshot(cx);
12377 let all_hunks = editor_hunks(editor, &snapshot, cx);
12378 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12379 assert_eq!(
12380 expanded_hunks_background_highlights(editor, cx),
12381 Vec::new(),
12382 "Modified hunk should turn into a removed one on all modified lines removal"
12383 );
12384 assert_eq!(
12385 all_hunks,
12386 vec![(
12387 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\nconst D: u32 = 42;\n"
12388 .to_string(),
12389 DiffHunkStatus::Removed,
12390 DisplayRow(7)..DisplayRow(7)
12391 )]
12392 );
12393 assert_eq!(all_hunks, all_expanded_hunks);
12394 });
12395}
12396
12397#[gpui::test]
12398async fn test_multiple_expanded_hunks_merge(
12399 executor: BackgroundExecutor,
12400 cx: &mut gpui::TestAppContext,
12401) {
12402 init_test(cx, |_| {});
12403
12404 let mut cx = EditorTestContext::new(cx).await;
12405
12406 let diff_base = r#"
12407 use some::mod1;
12408 use some::mod2;
12409
12410 const A: u32 = 42;
12411 const B: u32 = 42;
12412 const C: u32 = 42;
12413 const D: u32 = 42;
12414
12415
12416 fn main() {
12417 println!("hello");
12418
12419 println!("world");
12420 }"#
12421 .unindent();
12422 executor.run_until_parked();
12423 cx.set_state(
12424 &r#"
12425 use some::mod1;
12426 use some::mod2;
12427
12428 const A: u32 = 42;
12429 const B: u32 = 42;
12430 const C: u32 = 43ˇ
12431 const D: u32 = 42;
12432
12433
12434 fn main() {
12435 println!("hello");
12436
12437 println!("world");
12438 }"#
12439 .unindent(),
12440 );
12441
12442 cx.set_diff_base(Some(&diff_base));
12443 executor.run_until_parked();
12444 cx.update_editor(|editor, cx| {
12445 let snapshot = editor.snapshot(cx);
12446 let all_hunks = editor_hunks(editor, &snapshot, cx);
12447 assert_eq!(
12448 all_hunks,
12449 vec![(
12450 "const C: u32 = 42;\n".to_string(),
12451 DiffHunkStatus::Modified,
12452 DisplayRow(5)..DisplayRow(6)
12453 )]
12454 );
12455 });
12456 cx.update_editor(|editor, cx| {
12457 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, 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 const D: u32 = 42;
12469
12470
12471 fn main() {
12472 println!("hello");
12473
12474 println!("world");
12475 }"#
12476 .unindent(),
12477 );
12478 cx.update_editor(|editor, cx| {
12479 let snapshot = editor.snapshot(cx);
12480 let all_hunks = editor_hunks(editor, &snapshot, cx);
12481 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
12482 assert_eq!(
12483 expanded_hunks_background_highlights(editor, cx),
12484 vec![DisplayRow(6)..=DisplayRow(6)],
12485 );
12486 assert_eq!(
12487 all_hunks,
12488 vec![(
12489 "const C: u32 = 42;\n".to_string(),
12490 DiffHunkStatus::Modified,
12491 DisplayRow(6)..DisplayRow(7)
12492 )]
12493 );
12494 assert_eq!(all_hunks, all_expanded_hunks);
12495 });
12496
12497 cx.update_editor(|editor, cx| {
12498 editor.handle_input("\nnew_line\n", cx);
12499 });
12500 executor.run_until_parked();
12501 cx.assert_editor_state(
12502 &r#"
12503 use some::mod1;
12504 use some::mod2;
12505
12506 const A: u32 = 42;
12507 const B: u32 = 42;
12508 const C: u32 = 43
12509 new_line
12510 ˇ
12511 const D: u32 = 42;
12512
12513
12514 fn main() {
12515 println!("hello");
12516
12517 println!("world");
12518 }"#
12519 .unindent(),
12520 );
12521}
12522
12523async fn setup_indent_guides_editor(
12524 text: &str,
12525 cx: &mut gpui::TestAppContext,
12526) -> (BufferId, EditorTestContext) {
12527 init_test(cx, |_| {});
12528
12529 let mut cx = EditorTestContext::new(cx).await;
12530
12531 let buffer_id = cx.update_editor(|editor, cx| {
12532 editor.set_text(text, cx);
12533 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12534 let buffer_id = buffer_ids[0];
12535 buffer_id
12536 });
12537
12538 (buffer_id, cx)
12539}
12540
12541fn assert_indent_guides(
12542 range: Range<u32>,
12543 expected: Vec<IndentGuide>,
12544 active_indices: Option<Vec<usize>>,
12545 cx: &mut EditorTestContext,
12546) {
12547 let indent_guides = cx.update_editor(|editor, cx| {
12548 let snapshot = editor.snapshot(cx).display_snapshot;
12549 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12550 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12551 true,
12552 &snapshot,
12553 cx,
12554 );
12555
12556 indent_guides.sort_by(|a, b| {
12557 a.depth.cmp(&b.depth).then(
12558 a.start_row
12559 .cmp(&b.start_row)
12560 .then(a.end_row.cmp(&b.end_row)),
12561 )
12562 });
12563 indent_guides
12564 });
12565
12566 if let Some(expected) = active_indices {
12567 let active_indices = cx.update_editor(|editor, cx| {
12568 let snapshot = editor.snapshot(cx).display_snapshot;
12569 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
12570 });
12571
12572 assert_eq!(
12573 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
12574 expected,
12575 "Active indent guide indices do not match"
12576 );
12577 }
12578
12579 let expected: Vec<_> = expected
12580 .into_iter()
12581 .map(|guide| MultiBufferIndentGuide {
12582 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
12583 buffer: guide,
12584 })
12585 .collect();
12586
12587 assert_eq!(indent_guides, expected, "Indent guides do not match");
12588}
12589
12590fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
12591 IndentGuide {
12592 buffer_id,
12593 start_row,
12594 end_row,
12595 depth,
12596 tab_size: 4,
12597 settings: IndentGuideSettings {
12598 enabled: true,
12599 line_width: 1,
12600 active_line_width: 1,
12601 ..Default::default()
12602 },
12603 }
12604}
12605
12606#[gpui::test]
12607async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12608 let (buffer_id, mut cx) = setup_indent_guides_editor(
12609 &"
12610 fn main() {
12611 let a = 1;
12612 }"
12613 .unindent(),
12614 cx,
12615 )
12616 .await;
12617
12618 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12619}
12620
12621#[gpui::test]
12622async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
12623 let (buffer_id, mut cx) = setup_indent_guides_editor(
12624 &"
12625 fn main() {
12626 let a = 1;
12627 let b = 2;
12628 }"
12629 .unindent(),
12630 cx,
12631 )
12632 .await;
12633
12634 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
12635}
12636
12637#[gpui::test]
12638async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
12639 let (buffer_id, mut cx) = setup_indent_guides_editor(
12640 &"
12641 fn main() {
12642 let a = 1;
12643 if a == 3 {
12644 let b = 2;
12645 } else {
12646 let c = 3;
12647 }
12648 }"
12649 .unindent(),
12650 cx,
12651 )
12652 .await;
12653
12654 assert_indent_guides(
12655 0..8,
12656 vec![
12657 indent_guide(buffer_id, 1, 6, 0),
12658 indent_guide(buffer_id, 3, 3, 1),
12659 indent_guide(buffer_id, 5, 5, 1),
12660 ],
12661 None,
12662 &mut cx,
12663 );
12664}
12665
12666#[gpui::test]
12667async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
12668 let (buffer_id, mut cx) = setup_indent_guides_editor(
12669 &"
12670 fn main() {
12671 let a = 1;
12672 let b = 2;
12673 let c = 3;
12674 }"
12675 .unindent(),
12676 cx,
12677 )
12678 .await;
12679
12680 assert_indent_guides(
12681 0..5,
12682 vec![
12683 indent_guide(buffer_id, 1, 3, 0),
12684 indent_guide(buffer_id, 2, 2, 1),
12685 ],
12686 None,
12687 &mut cx,
12688 );
12689}
12690
12691#[gpui::test]
12692async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
12693 let (buffer_id, mut cx) = setup_indent_guides_editor(
12694 &"
12695 fn main() {
12696 let a = 1;
12697
12698 let c = 3;
12699 }"
12700 .unindent(),
12701 cx,
12702 )
12703 .await;
12704
12705 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
12706}
12707
12708#[gpui::test]
12709async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
12710 let (buffer_id, mut cx) = setup_indent_guides_editor(
12711 &"
12712 fn main() {
12713 let a = 1;
12714
12715 let c = 3;
12716
12717 if a == 3 {
12718 let b = 2;
12719 } else {
12720 let c = 3;
12721 }
12722 }"
12723 .unindent(),
12724 cx,
12725 )
12726 .await;
12727
12728 assert_indent_guides(
12729 0..11,
12730 vec![
12731 indent_guide(buffer_id, 1, 9, 0),
12732 indent_guide(buffer_id, 6, 6, 1),
12733 indent_guide(buffer_id, 8, 8, 1),
12734 ],
12735 None,
12736 &mut cx,
12737 );
12738}
12739
12740#[gpui::test]
12741async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
12742 let (buffer_id, mut cx) = setup_indent_guides_editor(
12743 &"
12744 fn main() {
12745 let a = 1;
12746
12747 let c = 3;
12748
12749 if a == 3 {
12750 let b = 2;
12751 } else {
12752 let c = 3;
12753 }
12754 }"
12755 .unindent(),
12756 cx,
12757 )
12758 .await;
12759
12760 assert_indent_guides(
12761 1..11,
12762 vec![
12763 indent_guide(buffer_id, 1, 9, 0),
12764 indent_guide(buffer_id, 6, 6, 1),
12765 indent_guide(buffer_id, 8, 8, 1),
12766 ],
12767 None,
12768 &mut cx,
12769 );
12770}
12771
12772#[gpui::test]
12773async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
12774 let (buffer_id, mut cx) = setup_indent_guides_editor(
12775 &"
12776 fn main() {
12777 let a = 1;
12778
12779 let c = 3;
12780
12781 if a == 3 {
12782 let b = 2;
12783 } else {
12784 let c = 3;
12785 }
12786 }"
12787 .unindent(),
12788 cx,
12789 )
12790 .await;
12791
12792 assert_indent_guides(
12793 1..10,
12794 vec![
12795 indent_guide(buffer_id, 1, 9, 0),
12796 indent_guide(buffer_id, 6, 6, 1),
12797 indent_guide(buffer_id, 8, 8, 1),
12798 ],
12799 None,
12800 &mut cx,
12801 );
12802}
12803
12804#[gpui::test]
12805async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
12806 let (buffer_id, mut cx) = setup_indent_guides_editor(
12807 &"
12808 block1
12809 block2
12810 block3
12811 block4
12812 block2
12813 block1
12814 block1"
12815 .unindent(),
12816 cx,
12817 )
12818 .await;
12819
12820 assert_indent_guides(
12821 1..10,
12822 vec![
12823 indent_guide(buffer_id, 1, 4, 0),
12824 indent_guide(buffer_id, 2, 3, 1),
12825 indent_guide(buffer_id, 3, 3, 2),
12826 ],
12827 None,
12828 &mut cx,
12829 );
12830}
12831
12832#[gpui::test]
12833async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
12834 let (buffer_id, mut cx) = setup_indent_guides_editor(
12835 &"
12836 block1
12837 block2
12838 block3
12839
12840 block1
12841 block1"
12842 .unindent(),
12843 cx,
12844 )
12845 .await;
12846
12847 assert_indent_guides(
12848 0..6,
12849 vec![
12850 indent_guide(buffer_id, 1, 2, 0),
12851 indent_guide(buffer_id, 2, 2, 1),
12852 ],
12853 None,
12854 &mut cx,
12855 );
12856}
12857
12858#[gpui::test]
12859async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
12860 let (buffer_id, mut cx) = setup_indent_guides_editor(
12861 &"
12862 block1
12863
12864
12865
12866 block2
12867 "
12868 .unindent(),
12869 cx,
12870 )
12871 .await;
12872
12873 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12874}
12875
12876#[gpui::test]
12877async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
12878 let (buffer_id, mut cx) = setup_indent_guides_editor(
12879 &"
12880 def a:
12881 \tb = 3
12882 \tif True:
12883 \t\tc = 4
12884 \t\td = 5
12885 \tprint(b)
12886 "
12887 .unindent(),
12888 cx,
12889 )
12890 .await;
12891
12892 assert_indent_guides(
12893 0..6,
12894 vec![
12895 indent_guide(buffer_id, 1, 6, 0),
12896 indent_guide(buffer_id, 3, 4, 1),
12897 ],
12898 None,
12899 &mut cx,
12900 );
12901}
12902
12903#[gpui::test]
12904async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12905 let (buffer_id, mut cx) = setup_indent_guides_editor(
12906 &"
12907 fn main() {
12908 let a = 1;
12909 }"
12910 .unindent(),
12911 cx,
12912 )
12913 .await;
12914
12915 cx.update_editor(|editor, cx| {
12916 editor.change_selections(None, cx, |s| {
12917 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12918 });
12919 });
12920
12921 assert_indent_guides(
12922 0..3,
12923 vec![indent_guide(buffer_id, 1, 1, 0)],
12924 Some(vec![0]),
12925 &mut cx,
12926 );
12927}
12928
12929#[gpui::test]
12930async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
12931 let (buffer_id, mut cx) = setup_indent_guides_editor(
12932 &"
12933 fn main() {
12934 if 1 == 2 {
12935 let a = 1;
12936 }
12937 }"
12938 .unindent(),
12939 cx,
12940 )
12941 .await;
12942
12943 cx.update_editor(|editor, cx| {
12944 editor.change_selections(None, cx, |s| {
12945 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12946 });
12947 });
12948
12949 assert_indent_guides(
12950 0..4,
12951 vec![
12952 indent_guide(buffer_id, 1, 3, 0),
12953 indent_guide(buffer_id, 2, 2, 1),
12954 ],
12955 Some(vec![1]),
12956 &mut cx,
12957 );
12958
12959 cx.update_editor(|editor, cx| {
12960 editor.change_selections(None, cx, |s| {
12961 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12962 });
12963 });
12964
12965 assert_indent_guides(
12966 0..4,
12967 vec![
12968 indent_guide(buffer_id, 1, 3, 0),
12969 indent_guide(buffer_id, 2, 2, 1),
12970 ],
12971 Some(vec![1]),
12972 &mut cx,
12973 );
12974
12975 cx.update_editor(|editor, cx| {
12976 editor.change_selections(None, cx, |s| {
12977 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
12978 });
12979 });
12980
12981 assert_indent_guides(
12982 0..4,
12983 vec![
12984 indent_guide(buffer_id, 1, 3, 0),
12985 indent_guide(buffer_id, 2, 2, 1),
12986 ],
12987 Some(vec![0]),
12988 &mut cx,
12989 );
12990}
12991
12992#[gpui::test]
12993async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
12994 let (buffer_id, mut cx) = setup_indent_guides_editor(
12995 &"
12996 fn main() {
12997 let a = 1;
12998
12999 let b = 2;
13000 }"
13001 .unindent(),
13002 cx,
13003 )
13004 .await;
13005
13006 cx.update_editor(|editor, cx| {
13007 editor.change_selections(None, cx, |s| {
13008 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13009 });
13010 });
13011
13012 assert_indent_guides(
13013 0..5,
13014 vec![indent_guide(buffer_id, 1, 3, 0)],
13015 Some(vec![0]),
13016 &mut cx,
13017 );
13018}
13019
13020#[gpui::test]
13021async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13022 let (buffer_id, mut cx) = setup_indent_guides_editor(
13023 &"
13024 def m:
13025 a = 1
13026 pass"
13027 .unindent(),
13028 cx,
13029 )
13030 .await;
13031
13032 cx.update_editor(|editor, cx| {
13033 editor.change_selections(None, cx, |s| {
13034 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13035 });
13036 });
13037
13038 assert_indent_guides(
13039 0..3,
13040 vec![indent_guide(buffer_id, 1, 2, 0)],
13041 Some(vec![0]),
13042 &mut cx,
13043 );
13044}
13045
13046#[gpui::test]
13047fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13048 init_test(cx, |_| {});
13049
13050 let editor = cx.add_window(|cx| {
13051 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13052 build_editor(buffer, cx)
13053 });
13054
13055 let render_args = Arc::new(Mutex::new(None));
13056 let snapshot = editor
13057 .update(cx, |editor, cx| {
13058 let snapshot = editor.buffer().read(cx).snapshot(cx);
13059 let range =
13060 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13061
13062 struct RenderArgs {
13063 row: MultiBufferRow,
13064 folded: bool,
13065 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13066 }
13067
13068 let crease = Crease::new(
13069 range,
13070 FoldPlaceholder::test(),
13071 {
13072 let toggle_callback = render_args.clone();
13073 move |row, folded, callback, _cx| {
13074 *toggle_callback.lock() = Some(RenderArgs {
13075 row,
13076 folded,
13077 callback,
13078 });
13079 div()
13080 }
13081 },
13082 |_row, _folded, _cx| div(),
13083 );
13084
13085 editor.insert_creases(Some(crease), cx);
13086 let snapshot = editor.snapshot(cx);
13087 let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13088 snapshot
13089 })
13090 .unwrap();
13091
13092 let render_args = render_args.lock().take().unwrap();
13093 assert_eq!(render_args.row, MultiBufferRow(1));
13094 assert_eq!(render_args.folded, false);
13095 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13096
13097 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13098 .unwrap();
13099 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13100 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13101
13102 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13103 .unwrap();
13104 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13105 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13106}
13107
13108fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13109 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13110 point..point
13111}
13112
13113fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13114 let (text, ranges) = marked_text_ranges(marked_text, true);
13115 assert_eq!(view.text(cx), text);
13116 assert_eq!(
13117 view.selections.ranges(cx),
13118 ranges,
13119 "Assert selections are {}",
13120 marked_text
13121 );
13122}
13123
13124pub fn handle_signature_help_request(
13125 cx: &mut EditorLspTestContext,
13126 mocked_response: lsp::SignatureHelp,
13127) -> impl Future<Output = ()> {
13128 let mut request =
13129 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13130 let mocked_response = mocked_response.clone();
13131 async move { Ok(Some(mocked_response)) }
13132 });
13133
13134 async move {
13135 request.next().await;
13136 }
13137}
13138
13139/// Handle completion request passing a marked string specifying where the completion
13140/// should be triggered from using '|' character, what range should be replaced, and what completions
13141/// should be returned using '<' and '>' to delimit the range
13142pub fn handle_completion_request(
13143 cx: &mut EditorLspTestContext,
13144 marked_string: &str,
13145 completions: Vec<&'static str>,
13146 counter: Arc<AtomicUsize>,
13147) -> impl Future<Output = ()> {
13148 let complete_from_marker: TextRangeMarker = '|'.into();
13149 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13150 let (_, mut marked_ranges) = marked_text_ranges_by(
13151 marked_string,
13152 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13153 );
13154
13155 let complete_from_position =
13156 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13157 let replace_range =
13158 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13159
13160 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13161 let completions = completions.clone();
13162 counter.fetch_add(1, atomic::Ordering::Release);
13163 async move {
13164 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13165 assert_eq!(
13166 params.text_document_position.position,
13167 complete_from_position
13168 );
13169 Ok(Some(lsp::CompletionResponse::Array(
13170 completions
13171 .iter()
13172 .map(|completion_text| lsp::CompletionItem {
13173 label: completion_text.to_string(),
13174 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13175 range: replace_range,
13176 new_text: completion_text.to_string(),
13177 })),
13178 ..Default::default()
13179 })
13180 .collect(),
13181 )))
13182 }
13183 });
13184
13185 async move {
13186 request.next().await;
13187 }
13188}
13189
13190fn handle_resolve_completion_request(
13191 cx: &mut EditorLspTestContext,
13192 edits: Option<Vec<(&'static str, &'static str)>>,
13193) -> impl Future<Output = ()> {
13194 let edits = edits.map(|edits| {
13195 edits
13196 .iter()
13197 .map(|(marked_string, new_text)| {
13198 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13199 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13200 lsp::TextEdit::new(replace_range, new_text.to_string())
13201 })
13202 .collect::<Vec<_>>()
13203 });
13204
13205 let mut request =
13206 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13207 let edits = edits.clone();
13208 async move {
13209 Ok(lsp::CompletionItem {
13210 additional_text_edits: edits,
13211 ..Default::default()
13212 })
13213 }
13214 });
13215
13216 async move {
13217 request.next().await;
13218 }
13219}
13220
13221pub(crate) fn update_test_language_settings(
13222 cx: &mut TestAppContext,
13223 f: impl Fn(&mut AllLanguageSettingsContent),
13224) {
13225 _ = cx.update(|cx| {
13226 SettingsStore::update_global(cx, |store, cx| {
13227 store.update_user_settings::<AllLanguageSettings>(cx, f);
13228 });
13229 });
13230}
13231
13232pub(crate) fn update_test_project_settings(
13233 cx: &mut TestAppContext,
13234 f: impl Fn(&mut ProjectSettings),
13235) {
13236 _ = cx.update(|cx| {
13237 SettingsStore::update_global(cx, |store, cx| {
13238 store.update_user_settings::<ProjectSettings>(cx, f);
13239 });
13240 });
13241}
13242
13243pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
13244 _ = cx.update(|cx| {
13245 assets::Assets.load_test_fonts(cx);
13246 let store = SettingsStore::test(cx);
13247 cx.set_global(store);
13248 theme::init(theme::LoadThemes::JustBase, cx);
13249 release_channel::init(SemanticVersion::default(), cx);
13250 client::init_settings(cx);
13251 language::init(cx);
13252 Project::init_settings(cx);
13253 workspace::init_settings(cx);
13254 crate::init(cx);
13255 });
13256
13257 update_test_language_settings(cx, f);
13258}
13259
13260pub(crate) fn rust_lang() -> Arc<Language> {
13261 Arc::new(Language::new(
13262 LanguageConfig {
13263 name: "Rust".into(),
13264 matcher: LanguageMatcher {
13265 path_suffixes: vec!["rs".to_string()],
13266 ..Default::default()
13267 },
13268 ..Default::default()
13269 },
13270 Some(tree_sitter_rust::language()),
13271 ))
13272}
13273
13274#[track_caller]
13275fn assert_hunk_revert(
13276 not_reverted_text_with_selections: &str,
13277 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
13278 expected_reverted_text_with_selections: &str,
13279 base_text: &str,
13280 cx: &mut EditorLspTestContext,
13281) {
13282 cx.set_state(not_reverted_text_with_selections);
13283 cx.update_editor(|editor, cx| {
13284 editor
13285 .buffer()
13286 .read(cx)
13287 .as_singleton()
13288 .unwrap()
13289 .update(cx, |buffer, cx| {
13290 buffer.set_diff_base(Some(base_text.into()), cx);
13291 });
13292 });
13293 cx.executor().run_until_parked();
13294
13295 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
13296 let snapshot = editor.buffer().read(cx).snapshot(cx);
13297 let reverted_hunk_statuses = snapshot
13298 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
13299 .map(|hunk| hunk_status(&hunk))
13300 .collect::<Vec<_>>();
13301
13302 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
13303 reverted_hunk_statuses
13304 });
13305 cx.executor().run_until_parked();
13306 cx.assert_editor_state(expected_reverted_text_with_selections);
13307 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
13308}