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 Point,
25};
26use language_settings::IndentGuideSettings;
27use multi_buffer::MultiBufferIndentGuide;
28use parking_lot::Mutex;
29use project::project_settings::{LspSettings, ProjectSettings};
30use project::FakeFs;
31use serde_json::{self, json};
32use std::sync::atomic;
33use std::sync::atomic::AtomicUsize;
34use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
35use unindent::Unindent;
36use util::{
37 assert_set_eq,
38 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
39};
40use workspace::{
41 item::{FollowEvent, FollowableItem, Item, ItemHandle},
42 NavigationEntry, ViewId,
43};
44
45#[gpui::test]
46fn test_edit_events(cx: &mut TestAppContext) {
47 init_test(cx, |_| {});
48
49 let buffer = cx.new_model(|cx| {
50 let mut buffer = language::Buffer::local("123456", cx);
51 buffer.set_group_interval(Duration::from_secs(1));
52 buffer
53 });
54
55 let events = Rc::new(RefCell::new(Vec::new()));
56 let editor1 = cx.add_window({
57 let events = events.clone();
58 |cx| {
59 let view = cx.view().clone();
60 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
61 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
62 EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
63 _ => {}
64 })
65 .detach();
66 Editor::for_buffer(buffer.clone(), None, cx)
67 }
68 });
69
70 let editor2 = cx.add_window({
71 let events = events.clone();
72 |cx| {
73 cx.subscribe(
74 &cx.view().clone(),
75 move |_, _, event: &EditorEvent, _| match event {
76 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
77 EditorEvent::BufferEdited => {
78 events.borrow_mut().push(("editor2", "buffer edited"))
79 }
80 _ => {}
81 },
82 )
83 .detach();
84 Editor::for_buffer(buffer.clone(), None, cx)
85 }
86 });
87
88 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
89
90 // Mutating editor 1 will emit an `Edited` event only for that editor.
91 _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
92 assert_eq!(
93 mem::take(&mut *events.borrow_mut()),
94 [
95 ("editor1", "edited"),
96 ("editor1", "buffer edited"),
97 ("editor2", "buffer edited"),
98 ]
99 );
100
101 // Mutating editor 2 will emit an `Edited` event only for that editor.
102 _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
103 assert_eq!(
104 mem::take(&mut *events.borrow_mut()),
105 [
106 ("editor2", "edited"),
107 ("editor1", "buffer edited"),
108 ("editor2", "buffer edited"),
109 ]
110 );
111
112 // Undoing on editor 1 will emit an `Edited` event only for that editor.
113 _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
114 assert_eq!(
115 mem::take(&mut *events.borrow_mut()),
116 [
117 ("editor1", "edited"),
118 ("editor1", "buffer edited"),
119 ("editor2", "buffer edited"),
120 ]
121 );
122
123 // Redoing on editor 1 will emit an `Edited` event only for that editor.
124 _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
125 assert_eq!(
126 mem::take(&mut *events.borrow_mut()),
127 [
128 ("editor1", "edited"),
129 ("editor1", "buffer edited"),
130 ("editor2", "buffer edited"),
131 ]
132 );
133
134 // Undoing on editor 2 will emit an `Edited` event only for that editor.
135 _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
136 assert_eq!(
137 mem::take(&mut *events.borrow_mut()),
138 [
139 ("editor2", "edited"),
140 ("editor1", "buffer edited"),
141 ("editor2", "buffer edited"),
142 ]
143 );
144
145 // Redoing on editor 2 will emit an `Edited` event only for that editor.
146 _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
147 assert_eq!(
148 mem::take(&mut *events.borrow_mut()),
149 [
150 ("editor2", "edited"),
151 ("editor1", "buffer edited"),
152 ("editor2", "buffer edited"),
153 ]
154 );
155
156 // No event is emitted when the mutation is a no-op.
157 _ = editor2.update(cx, |editor, cx| {
158 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
159
160 editor.backspace(&Backspace, cx);
161 });
162 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
163}
164
165#[gpui::test]
166fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
167 init_test(cx, |_| {});
168
169 let mut now = Instant::now();
170 let buffer = cx.new_model(|cx| language::Buffer::local("123456", cx));
171 let group_interval = buffer.update(cx, |buffer, _| buffer.transaction_group_interval());
172 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
173 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
174
175 _ = editor.update(cx, |editor, cx| {
176 editor.start_transaction_at(now, cx);
177 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
178
179 editor.insert("cd", cx);
180 editor.end_transaction_at(now, cx);
181 assert_eq!(editor.text(cx), "12cd56");
182 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
183
184 editor.start_transaction_at(now, cx);
185 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
186 editor.insert("e", cx);
187 editor.end_transaction_at(now, cx);
188 assert_eq!(editor.text(cx), "12cde6");
189 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
190
191 now += group_interval + Duration::from_millis(1);
192 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
193
194 // Simulate an edit in another editor
195 _ = buffer.update(cx, |buffer, cx| {
196 buffer.start_transaction_at(now, cx);
197 buffer.edit([(0..1, "a")], None, cx);
198 buffer.edit([(1..1, "b")], None, cx);
199 buffer.end_transaction_at(now, cx);
200 });
201
202 assert_eq!(editor.text(cx), "ab2cde6");
203 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
204
205 // Last transaction happened past the group interval in a different editor.
206 // Undo it individually and don't restore selections.
207 editor.undo(&Undo, cx);
208 assert_eq!(editor.text(cx), "12cde6");
209 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
210
211 // First two transactions happened within the group interval in this editor.
212 // Undo them together and restore selections.
213 editor.undo(&Undo, cx);
214 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
215 assert_eq!(editor.text(cx), "123456");
216 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
217
218 // Redo the first two transactions together.
219 editor.redo(&Redo, cx);
220 assert_eq!(editor.text(cx), "12cde6");
221 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
222
223 // Redo the last transaction on its own.
224 editor.redo(&Redo, cx);
225 assert_eq!(editor.text(cx), "ab2cde6");
226 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
227
228 // Test empty transactions.
229 editor.start_transaction_at(now, cx);
230 editor.end_transaction_at(now, cx);
231 editor.undo(&Undo, cx);
232 assert_eq!(editor.text(cx), "12cde6");
233 });
234}
235
236#[gpui::test]
237fn test_ime_composition(cx: &mut TestAppContext) {
238 init_test(cx, |_| {});
239
240 let buffer = cx.new_model(|cx| {
241 let mut buffer = language::Buffer::local("abcde", cx);
242 // Ensure automatic grouping doesn't occur.
243 buffer.set_group_interval(Duration::ZERO);
244 buffer
245 });
246
247 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
248 cx.add_window(|cx| {
249 let mut editor = build_editor(buffer.clone(), cx);
250
251 // Start a new IME composition.
252 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
253 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
254 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
255 assert_eq!(editor.text(cx), "äbcde");
256 assert_eq!(
257 editor.marked_text_ranges(cx),
258 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
259 );
260
261 // Finalize IME composition.
262 editor.replace_text_in_range(None, "ā", cx);
263 assert_eq!(editor.text(cx), "ābcde");
264 assert_eq!(editor.marked_text_ranges(cx), None);
265
266 // IME composition edits are grouped and are undone/redone at once.
267 editor.undo(&Default::default(), cx);
268 assert_eq!(editor.text(cx), "abcde");
269 assert_eq!(editor.marked_text_ranges(cx), None);
270 editor.redo(&Default::default(), cx);
271 assert_eq!(editor.text(cx), "ābcde");
272 assert_eq!(editor.marked_text_ranges(cx), None);
273
274 // Start a new IME composition.
275 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
276 assert_eq!(
277 editor.marked_text_ranges(cx),
278 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
279 );
280
281 // Undoing during an IME composition cancels it.
282 editor.undo(&Default::default(), cx);
283 assert_eq!(editor.text(cx), "ābcde");
284 assert_eq!(editor.marked_text_ranges(cx), None);
285
286 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
287 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
288 assert_eq!(editor.text(cx), "ābcdè");
289 assert_eq!(
290 editor.marked_text_ranges(cx),
291 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
292 );
293
294 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
295 editor.replace_text_in_range(Some(4..999), "ę", cx);
296 assert_eq!(editor.text(cx), "ābcdę");
297 assert_eq!(editor.marked_text_ranges(cx), None);
298
299 // Start a new IME composition with multiple cursors.
300 editor.change_selections(None, cx, |s| {
301 s.select_ranges([
302 OffsetUtf16(1)..OffsetUtf16(1),
303 OffsetUtf16(3)..OffsetUtf16(3),
304 OffsetUtf16(5)..OffsetUtf16(5),
305 ])
306 });
307 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
308 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
309 assert_eq!(
310 editor.marked_text_ranges(cx),
311 Some(vec![
312 OffsetUtf16(0)..OffsetUtf16(3),
313 OffsetUtf16(4)..OffsetUtf16(7),
314 OffsetUtf16(8)..OffsetUtf16(11)
315 ])
316 );
317
318 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
319 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
320 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
321 assert_eq!(
322 editor.marked_text_ranges(cx),
323 Some(vec![
324 OffsetUtf16(1)..OffsetUtf16(2),
325 OffsetUtf16(5)..OffsetUtf16(6),
326 OffsetUtf16(9)..OffsetUtf16(10)
327 ])
328 );
329
330 // Finalize IME composition with multiple cursors.
331 editor.replace_text_in_range(Some(9..10), "2", cx);
332 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
333 assert_eq!(editor.marked_text_ranges(cx), None);
334
335 editor
336 });
337}
338
339#[gpui::test]
340fn test_selection_with_mouse(cx: &mut TestAppContext) {
341 init_test(cx, |_| {});
342
343 let editor = cx.add_window(|cx| {
344 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
345 build_editor(buffer, cx)
346 });
347
348 _ = editor.update(cx, |view, cx| {
349 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
350 });
351 assert_eq!(
352 editor
353 .update(cx, |view, cx| view.selections.display_ranges(cx))
354 .unwrap(),
355 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
356 );
357
358 _ = editor.update(cx, |view, cx| {
359 view.update_selection(
360 DisplayPoint::new(DisplayRow(3), 3),
361 0,
362 gpui::Point::<f32>::default(),
363 cx,
364 );
365 });
366
367 assert_eq!(
368 editor
369 .update(cx, |view, cx| view.selections.display_ranges(cx))
370 .unwrap(),
371 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
372 );
373
374 _ = editor.update(cx, |view, cx| {
375 view.update_selection(
376 DisplayPoint::new(DisplayRow(1), 1),
377 0,
378 gpui::Point::<f32>::default(),
379 cx,
380 );
381 });
382
383 assert_eq!(
384 editor
385 .update(cx, |view, cx| view.selections.display_ranges(cx))
386 .unwrap(),
387 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
388 );
389
390 _ = editor.update(cx, |view, cx| {
391 view.end_selection(cx);
392 view.update_selection(
393 DisplayPoint::new(DisplayRow(3), 3),
394 0,
395 gpui::Point::<f32>::default(),
396 cx,
397 );
398 });
399
400 assert_eq!(
401 editor
402 .update(cx, |view, cx| view.selections.display_ranges(cx))
403 .unwrap(),
404 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
405 );
406
407 _ = editor.update(cx, |view, cx| {
408 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
409 view.update_selection(
410 DisplayPoint::new(DisplayRow(0), 0),
411 0,
412 gpui::Point::<f32>::default(),
413 cx,
414 );
415 });
416
417 assert_eq!(
418 editor
419 .update(cx, |view, cx| view.selections.display_ranges(cx))
420 .unwrap(),
421 [
422 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
423 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
424 ]
425 );
426
427 _ = editor.update(cx, |view, cx| {
428 view.end_selection(cx);
429 });
430
431 assert_eq!(
432 editor
433 .update(cx, |view, cx| view.selections.display_ranges(cx))
434 .unwrap(),
435 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
436 );
437}
438
439#[gpui::test]
440fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
441 init_test(cx, |_| {});
442
443 let editor = cx.add_window(|cx| {
444 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
445 build_editor(buffer, cx)
446 });
447
448 _ = editor.update(cx, |view, cx| {
449 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
450 });
451
452 _ = editor.update(cx, |view, cx| {
453 view.end_selection(cx);
454 });
455
456 _ = editor.update(cx, |view, cx| {
457 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
458 });
459
460 _ = editor.update(cx, |view, cx| {
461 view.end_selection(cx);
462 });
463
464 assert_eq!(
465 editor
466 .update(cx, |view, cx| view.selections.display_ranges(cx))
467 .unwrap(),
468 [
469 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
470 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
471 ]
472 );
473
474 _ = editor.update(cx, |view, cx| {
475 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
476 });
477
478 _ = editor.update(cx, |view, cx| {
479 view.end_selection(cx);
480 });
481
482 assert_eq!(
483 editor
484 .update(cx, |view, cx| view.selections.display_ranges(cx))
485 .unwrap(),
486 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
487 );
488}
489
490#[gpui::test]
491fn test_canceling_pending_selection(cx: &mut TestAppContext) {
492 init_test(cx, |_| {});
493
494 let view = cx.add_window(|cx| {
495 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
496 build_editor(buffer, cx)
497 });
498
499 _ = view.update(cx, |view, cx| {
500 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
501 assert_eq!(
502 view.selections.display_ranges(cx),
503 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
504 );
505 });
506
507 _ = view.update(cx, |view, cx| {
508 view.update_selection(
509 DisplayPoint::new(DisplayRow(3), 3),
510 0,
511 gpui::Point::<f32>::default(),
512 cx,
513 );
514 assert_eq!(
515 view.selections.display_ranges(cx),
516 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
517 );
518 });
519
520 _ = view.update(cx, |view, cx| {
521 view.cancel(&Cancel, cx);
522 view.update_selection(
523 DisplayPoint::new(DisplayRow(1), 1),
524 0,
525 gpui::Point::<f32>::default(),
526 cx,
527 );
528 assert_eq!(
529 view.selections.display_ranges(cx),
530 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
531 );
532 });
533}
534
535#[gpui::test]
536fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
537 init_test(cx, |_| {});
538
539 let view = cx.add_window(|cx| {
540 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
541 build_editor(buffer, cx)
542 });
543
544 _ = view.update(cx, |view, cx| {
545 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
546 assert_eq!(
547 view.selections.display_ranges(cx),
548 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
549 );
550
551 view.move_down(&Default::default(), cx);
552 assert_eq!(
553 view.selections.display_ranges(cx),
554 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
555 );
556
557 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
558 assert_eq!(
559 view.selections.display_ranges(cx),
560 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
561 );
562
563 view.move_up(&Default::default(), cx);
564 assert_eq!(
565 view.selections.display_ranges(cx),
566 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
567 );
568 });
569}
570
571#[gpui::test]
572fn test_clone(cx: &mut TestAppContext) {
573 init_test(cx, |_| {});
574
575 let (text, selection_ranges) = marked_text_ranges(
576 indoc! {"
577 one
578 two
579 threeˇ
580 four
581 fiveˇ
582 "},
583 true,
584 );
585
586 let editor = cx.add_window(|cx| {
587 let buffer = MultiBuffer::build_simple(&text, cx);
588 build_editor(buffer, cx)
589 });
590
591 _ = editor.update(cx, |editor, cx| {
592 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
593 editor.fold_ranges(
594 [
595 (Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
596 (Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
597 ],
598 true,
599 cx,
600 );
601 });
602
603 let cloned_editor = editor
604 .update(cx, |editor, cx| {
605 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
606 })
607 .unwrap()
608 .unwrap();
609
610 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
611 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
612
613 assert_eq!(
614 cloned_editor
615 .update(cx, |e, cx| e.display_text(cx))
616 .unwrap(),
617 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
618 );
619 assert_eq!(
620 cloned_snapshot
621 .folds_in_range(0..text.len())
622 .collect::<Vec<_>>(),
623 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
624 );
625 assert_set_eq!(
626 cloned_editor
627 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
628 .unwrap(),
629 editor
630 .update(cx, |editor, cx| editor.selections.ranges(cx))
631 .unwrap()
632 );
633 assert_set_eq!(
634 cloned_editor
635 .update(cx, |e, cx| e.selections.display_ranges(cx))
636 .unwrap(),
637 editor
638 .update(cx, |e, cx| e.selections.display_ranges(cx))
639 .unwrap()
640 );
641}
642
643#[gpui::test]
644async fn test_navigation_history(cx: &mut TestAppContext) {
645 init_test(cx, |_| {});
646
647 use workspace::item::Item;
648
649 let fs = FakeFs::new(cx.executor());
650 let project = Project::test(fs, [], cx).await;
651 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
652 let pane = workspace
653 .update(cx, |workspace, _| workspace.active_pane().clone())
654 .unwrap();
655
656 _ = workspace.update(cx, |_v, cx| {
657 cx.new_view(|cx| {
658 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
659 let mut editor = build_editor(buffer.clone(), cx);
660 let handle = cx.view();
661 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
662
663 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
664 editor.nav_history.as_mut().unwrap().pop_backward(cx)
665 }
666
667 // Move the cursor a small distance.
668 // Nothing is added to the navigation history.
669 editor.change_selections(None, cx, |s| {
670 s.select_display_ranges([
671 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
672 ])
673 });
674 editor.change_selections(None, cx, |s| {
675 s.select_display_ranges([
676 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
677 ])
678 });
679 assert!(pop_history(&mut editor, cx).is_none());
680
681 // Move the cursor a large distance.
682 // The history can jump back to the previous position.
683 editor.change_selections(None, cx, |s| {
684 s.select_display_ranges([
685 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
686 ])
687 });
688 let nav_entry = pop_history(&mut editor, cx).unwrap();
689 editor.navigate(nav_entry.data.unwrap(), cx);
690 assert_eq!(nav_entry.item.id(), cx.entity_id());
691 assert_eq!(
692 editor.selections.display_ranges(cx),
693 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
694 );
695 assert!(pop_history(&mut editor, cx).is_none());
696
697 // Move the cursor a small distance via the mouse.
698 // Nothing is added to the navigation history.
699 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
700 editor.end_selection(cx);
701 assert_eq!(
702 editor.selections.display_ranges(cx),
703 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
704 );
705 assert!(pop_history(&mut editor, cx).is_none());
706
707 // Move the cursor a large distance via the mouse.
708 // The history can jump back to the previous position.
709 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
710 editor.end_selection(cx);
711 assert_eq!(
712 editor.selections.display_ranges(cx),
713 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
714 );
715 let nav_entry = pop_history(&mut editor, cx).unwrap();
716 editor.navigate(nav_entry.data.unwrap(), cx);
717 assert_eq!(nav_entry.item.id(), cx.entity_id());
718 assert_eq!(
719 editor.selections.display_ranges(cx),
720 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
721 );
722 assert!(pop_history(&mut editor, cx).is_none());
723
724 // Set scroll position to check later
725 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
726 let original_scroll_position = editor.scroll_manager.anchor();
727
728 // Jump to the end of the document and adjust scroll
729 editor.move_to_end(&MoveToEnd, cx);
730 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
731 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
732
733 let nav_entry = pop_history(&mut editor, cx).unwrap();
734 editor.navigate(nav_entry.data.unwrap(), cx);
735 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
736
737 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
738 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
739 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
740 let invalid_point = Point::new(9999, 0);
741 editor.navigate(
742 Box::new(NavigationData {
743 cursor_anchor: invalid_anchor,
744 cursor_position: invalid_point,
745 scroll_anchor: ScrollAnchor {
746 anchor: invalid_anchor,
747 offset: Default::default(),
748 },
749 scroll_top_row: invalid_point.row,
750 }),
751 cx,
752 );
753 assert_eq!(
754 editor.selections.display_ranges(cx),
755 &[editor.max_point(cx)..editor.max_point(cx)]
756 );
757 assert_eq!(
758 editor.scroll_position(cx),
759 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
760 );
761
762 editor
763 })
764 });
765}
766
767#[gpui::test]
768fn test_cancel(cx: &mut TestAppContext) {
769 init_test(cx, |_| {});
770
771 let view = cx.add_window(|cx| {
772 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
773 build_editor(buffer, cx)
774 });
775
776 _ = view.update(cx, |view, cx| {
777 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
778 view.update_selection(
779 DisplayPoint::new(DisplayRow(1), 1),
780 0,
781 gpui::Point::<f32>::default(),
782 cx,
783 );
784 view.end_selection(cx);
785
786 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
787 view.update_selection(
788 DisplayPoint::new(DisplayRow(0), 3),
789 0,
790 gpui::Point::<f32>::default(),
791 cx,
792 );
793 view.end_selection(cx);
794 assert_eq!(
795 view.selections.display_ranges(cx),
796 [
797 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
798 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
799 ]
800 );
801 });
802
803 _ = view.update(cx, |view, cx| {
804 view.cancel(&Cancel, cx);
805 assert_eq!(
806 view.selections.display_ranges(cx),
807 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
808 );
809 });
810
811 _ = view.update(cx, |view, cx| {
812 view.cancel(&Cancel, cx);
813 assert_eq!(
814 view.selections.display_ranges(cx),
815 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
816 );
817 });
818}
819
820#[gpui::test]
821fn test_fold_action(cx: &mut TestAppContext) {
822 init_test(cx, |_| {});
823
824 let view = cx.add_window(|cx| {
825 let buffer = MultiBuffer::build_simple(
826 &"
827 impl Foo {
828 // Hello!
829
830 fn a() {
831 1
832 }
833
834 fn b() {
835 2
836 }
837
838 fn c() {
839 3
840 }
841 }
842 "
843 .unindent(),
844 cx,
845 );
846 build_editor(buffer.clone(), cx)
847 });
848
849 _ = view.update(cx, |view, cx| {
850 view.change_selections(None, cx, |s| {
851 s.select_display_ranges([
852 DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(12), 0)
853 ]);
854 });
855 view.fold(&Fold, cx);
856 assert_eq!(
857 view.display_text(cx),
858 "
859 impl Foo {
860 // Hello!
861
862 fn a() {
863 1
864 }
865
866 fn b() {⋯
867 }
868
869 fn c() {⋯
870 }
871 }
872 "
873 .unindent(),
874 );
875
876 view.fold(&Fold, cx);
877 assert_eq!(
878 view.display_text(cx),
879 "
880 impl Foo {⋯
881 }
882 "
883 .unindent(),
884 );
885
886 view.unfold_lines(&UnfoldLines, cx);
887 assert_eq!(
888 view.display_text(cx),
889 "
890 impl Foo {
891 // Hello!
892
893 fn a() {
894 1
895 }
896
897 fn b() {⋯
898 }
899
900 fn c() {⋯
901 }
902 }
903 "
904 .unindent(),
905 );
906
907 view.unfold_lines(&UnfoldLines, cx);
908 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
909 });
910}
911
912#[gpui::test]
913fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
914 init_test(cx, |_| {});
915
916 let view = cx.add_window(|cx| {
917 let buffer = MultiBuffer::build_simple(
918 &"
919 class Foo:
920 # Hello!
921
922 def a():
923 print(1)
924
925 def b():
926 print(2)
927
928 def c():
929 print(3)
930 "
931 .unindent(),
932 cx,
933 );
934 build_editor(buffer.clone(), cx)
935 });
936
937 _ = view.update(cx, |view, cx| {
938 view.change_selections(None, cx, |s| {
939 s.select_display_ranges([
940 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(10), 0)
941 ]);
942 });
943 view.fold(&Fold, cx);
944 assert_eq!(
945 view.display_text(cx),
946 "
947 class Foo:
948 # Hello!
949
950 def a():
951 print(1)
952
953 def b():⋯
954
955 def c():⋯
956 "
957 .unindent(),
958 );
959
960 view.fold(&Fold, cx);
961 assert_eq!(
962 view.display_text(cx),
963 "
964 class Foo:⋯
965 "
966 .unindent(),
967 );
968
969 view.unfold_lines(&UnfoldLines, cx);
970 assert_eq!(
971 view.display_text(cx),
972 "
973 class Foo:
974 # Hello!
975
976 def a():
977 print(1)
978
979 def b():⋯
980
981 def c():⋯
982 "
983 .unindent(),
984 );
985
986 view.unfold_lines(&UnfoldLines, cx);
987 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
988 });
989}
990
991#[gpui::test]
992fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
993 init_test(cx, |_| {});
994
995 let view = cx.add_window(|cx| {
996 let buffer = MultiBuffer::build_simple(
997 &"
998 class Foo:
999 # Hello!
1000
1001 def a():
1002 print(1)
1003
1004 def b():
1005 print(2)
1006
1007
1008 def c():
1009 print(3)
1010
1011
1012 "
1013 .unindent(),
1014 cx,
1015 );
1016 build_editor(buffer.clone(), cx)
1017 });
1018
1019 _ = view.update(cx, |view, cx| {
1020 view.change_selections(None, cx, |s| {
1021 s.select_display_ranges([
1022 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(11), 0)
1023 ]);
1024 });
1025 view.fold(&Fold, cx);
1026 assert_eq!(
1027 view.display_text(cx),
1028 "
1029 class Foo:
1030 # Hello!
1031
1032 def a():
1033 print(1)
1034
1035 def b():⋯
1036
1037
1038 def c():⋯
1039
1040
1041 "
1042 .unindent(),
1043 );
1044
1045 view.fold(&Fold, cx);
1046 assert_eq!(
1047 view.display_text(cx),
1048 "
1049 class Foo:⋯
1050
1051
1052 "
1053 .unindent(),
1054 );
1055
1056 view.unfold_lines(&UnfoldLines, cx);
1057 assert_eq!(
1058 view.display_text(cx),
1059 "
1060 class Foo:
1061 # Hello!
1062
1063 def a():
1064 print(1)
1065
1066 def b():⋯
1067
1068
1069 def c():⋯
1070
1071
1072 "
1073 .unindent(),
1074 );
1075
1076 view.unfold_lines(&UnfoldLines, cx);
1077 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1078 });
1079}
1080
1081#[gpui::test]
1082fn test_move_cursor(cx: &mut TestAppContext) {
1083 init_test(cx, |_| {});
1084
1085 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1086 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1087
1088 _ = buffer.update(cx, |buffer, cx| {
1089 buffer.edit(
1090 vec![
1091 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1092 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1093 ],
1094 None,
1095 cx,
1096 );
1097 });
1098 _ = view.update(cx, |view, cx| {
1099 assert_eq!(
1100 view.selections.display_ranges(cx),
1101 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1102 );
1103
1104 view.move_down(&MoveDown, cx);
1105 assert_eq!(
1106 view.selections.display_ranges(cx),
1107 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1108 );
1109
1110 view.move_right(&MoveRight, cx);
1111 assert_eq!(
1112 view.selections.display_ranges(cx),
1113 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1114 );
1115
1116 view.move_left(&MoveLeft, cx);
1117 assert_eq!(
1118 view.selections.display_ranges(cx),
1119 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1120 );
1121
1122 view.move_up(&MoveUp, cx);
1123 assert_eq!(
1124 view.selections.display_ranges(cx),
1125 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1126 );
1127
1128 view.move_to_end(&MoveToEnd, cx);
1129 assert_eq!(
1130 view.selections.display_ranges(cx),
1131 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1132 );
1133
1134 view.move_to_beginning(&MoveToBeginning, cx);
1135 assert_eq!(
1136 view.selections.display_ranges(cx),
1137 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1138 );
1139
1140 view.change_selections(None, cx, |s| {
1141 s.select_display_ranges([
1142 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1143 ]);
1144 });
1145 view.select_to_beginning(&SelectToBeginning, cx);
1146 assert_eq!(
1147 view.selections.display_ranges(cx),
1148 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1149 );
1150
1151 view.select_to_end(&SelectToEnd, cx);
1152 assert_eq!(
1153 view.selections.display_ranges(cx),
1154 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1155 );
1156 });
1157}
1158
1159// TODO: Re-enable this test
1160#[cfg(target_os = "macos")]
1161#[gpui::test]
1162fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1163 init_test(cx, |_| {});
1164
1165 let view = cx.add_window(|cx| {
1166 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1167 build_editor(buffer.clone(), cx)
1168 });
1169
1170 assert_eq!('ⓐ'.len_utf8(), 3);
1171 assert_eq!('α'.len_utf8(), 2);
1172
1173 _ = view.update(cx, |view, cx| {
1174 view.fold_ranges(
1175 vec![
1176 (Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1177 (Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1178 (Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1179 ],
1180 true,
1181 cx,
1182 );
1183 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1184
1185 view.move_right(&MoveRight, cx);
1186 assert_eq!(
1187 view.selections.display_ranges(cx),
1188 &[empty_range(0, "ⓐ".len())]
1189 );
1190 view.move_right(&MoveRight, cx);
1191 assert_eq!(
1192 view.selections.display_ranges(cx),
1193 &[empty_range(0, "ⓐⓑ".len())]
1194 );
1195 view.move_right(&MoveRight, cx);
1196 assert_eq!(
1197 view.selections.display_ranges(cx),
1198 &[empty_range(0, "ⓐⓑ⋯".len())]
1199 );
1200
1201 view.move_down(&MoveDown, cx);
1202 assert_eq!(
1203 view.selections.display_ranges(cx),
1204 &[empty_range(1, "ab⋯e".len())]
1205 );
1206 view.move_left(&MoveLeft, cx);
1207 assert_eq!(
1208 view.selections.display_ranges(cx),
1209 &[empty_range(1, "ab⋯".len())]
1210 );
1211 view.move_left(&MoveLeft, cx);
1212 assert_eq!(
1213 view.selections.display_ranges(cx),
1214 &[empty_range(1, "ab".len())]
1215 );
1216 view.move_left(&MoveLeft, cx);
1217 assert_eq!(
1218 view.selections.display_ranges(cx),
1219 &[empty_range(1, "a".len())]
1220 );
1221
1222 view.move_down(&MoveDown, cx);
1223 assert_eq!(
1224 view.selections.display_ranges(cx),
1225 &[empty_range(2, "α".len())]
1226 );
1227 view.move_right(&MoveRight, cx);
1228 assert_eq!(
1229 view.selections.display_ranges(cx),
1230 &[empty_range(2, "αβ".len())]
1231 );
1232 view.move_right(&MoveRight, cx);
1233 assert_eq!(
1234 view.selections.display_ranges(cx),
1235 &[empty_range(2, "αβ⋯".len())]
1236 );
1237 view.move_right(&MoveRight, cx);
1238 assert_eq!(
1239 view.selections.display_ranges(cx),
1240 &[empty_range(2, "αβ⋯ε".len())]
1241 );
1242
1243 view.move_up(&MoveUp, cx);
1244 assert_eq!(
1245 view.selections.display_ranges(cx),
1246 &[empty_range(1, "ab⋯e".len())]
1247 );
1248 view.move_down(&MoveDown, cx);
1249 assert_eq!(
1250 view.selections.display_ranges(cx),
1251 &[empty_range(2, "αβ⋯ε".len())]
1252 );
1253 view.move_up(&MoveUp, cx);
1254 assert_eq!(
1255 view.selections.display_ranges(cx),
1256 &[empty_range(1, "ab⋯e".len())]
1257 );
1258
1259 view.move_up(&MoveUp, cx);
1260 assert_eq!(
1261 view.selections.display_ranges(cx),
1262 &[empty_range(0, "ⓐⓑ".len())]
1263 );
1264 view.move_left(&MoveLeft, cx);
1265 assert_eq!(
1266 view.selections.display_ranges(cx),
1267 &[empty_range(0, "ⓐ".len())]
1268 );
1269 view.move_left(&MoveLeft, cx);
1270 assert_eq!(
1271 view.selections.display_ranges(cx),
1272 &[empty_range(0, "".len())]
1273 );
1274 });
1275}
1276
1277#[gpui::test]
1278fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1279 init_test(cx, |_| {});
1280
1281 let view = cx.add_window(|cx| {
1282 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1283 build_editor(buffer.clone(), cx)
1284 });
1285 _ = view.update(cx, |view, cx| {
1286 view.change_selections(None, cx, |s| {
1287 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1288 });
1289 view.move_down(&MoveDown, cx);
1290 assert_eq!(
1291 view.selections.display_ranges(cx),
1292 &[empty_range(1, "abcd".len())]
1293 );
1294
1295 view.move_down(&MoveDown, cx);
1296 assert_eq!(
1297 view.selections.display_ranges(cx),
1298 &[empty_range(2, "αβγ".len())]
1299 );
1300
1301 view.move_down(&MoveDown, cx);
1302 assert_eq!(
1303 view.selections.display_ranges(cx),
1304 &[empty_range(3, "abcd".len())]
1305 );
1306
1307 view.move_down(&MoveDown, cx);
1308 assert_eq!(
1309 view.selections.display_ranges(cx),
1310 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1311 );
1312
1313 view.move_up(&MoveUp, cx);
1314 assert_eq!(
1315 view.selections.display_ranges(cx),
1316 &[empty_range(3, "abcd".len())]
1317 );
1318
1319 view.move_up(&MoveUp, cx);
1320 assert_eq!(
1321 view.selections.display_ranges(cx),
1322 &[empty_range(2, "αβγ".len())]
1323 );
1324 });
1325}
1326
1327#[gpui::test]
1328fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1329 init_test(cx, |_| {});
1330 let move_to_beg = MoveToBeginningOfLine {
1331 stop_at_soft_wraps: true,
1332 };
1333
1334 let move_to_end = MoveToEndOfLine {
1335 stop_at_soft_wraps: true,
1336 };
1337
1338 let view = cx.add_window(|cx| {
1339 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1340 build_editor(buffer, cx)
1341 });
1342 _ = view.update(cx, |view, cx| {
1343 view.change_selections(None, cx, |s| {
1344 s.select_display_ranges([
1345 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1346 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1347 ]);
1348 });
1349 });
1350
1351 _ = view.update(cx, |view, cx| {
1352 view.move_to_beginning_of_line(&move_to_beg, cx);
1353 assert_eq!(
1354 view.selections.display_ranges(cx),
1355 &[
1356 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1357 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1358 ]
1359 );
1360 });
1361
1362 _ = view.update(cx, |view, cx| {
1363 view.move_to_beginning_of_line(&move_to_beg, cx);
1364 assert_eq!(
1365 view.selections.display_ranges(cx),
1366 &[
1367 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1368 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1369 ]
1370 );
1371 });
1372
1373 _ = view.update(cx, |view, cx| {
1374 view.move_to_beginning_of_line(&move_to_beg, cx);
1375 assert_eq!(
1376 view.selections.display_ranges(cx),
1377 &[
1378 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1379 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1380 ]
1381 );
1382 });
1383
1384 _ = view.update(cx, |view, cx| {
1385 view.move_to_end_of_line(&move_to_end, cx);
1386 assert_eq!(
1387 view.selections.display_ranges(cx),
1388 &[
1389 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1390 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1391 ]
1392 );
1393 });
1394
1395 // Moving to the end of line again is a no-op.
1396 _ = view.update(cx, |view, cx| {
1397 view.move_to_end_of_line(&move_to_end, cx);
1398 assert_eq!(
1399 view.selections.display_ranges(cx),
1400 &[
1401 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1402 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1403 ]
1404 );
1405 });
1406
1407 _ = view.update(cx, |view, cx| {
1408 view.move_left(&MoveLeft, cx);
1409 view.select_to_beginning_of_line(
1410 &SelectToBeginningOfLine {
1411 stop_at_soft_wraps: true,
1412 },
1413 cx,
1414 );
1415 assert_eq!(
1416 view.selections.display_ranges(cx),
1417 &[
1418 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1419 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1420 ]
1421 );
1422 });
1423
1424 _ = view.update(cx, |view, cx| {
1425 view.select_to_beginning_of_line(
1426 &SelectToBeginningOfLine {
1427 stop_at_soft_wraps: true,
1428 },
1429 cx,
1430 );
1431 assert_eq!(
1432 view.selections.display_ranges(cx),
1433 &[
1434 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1435 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1436 ]
1437 );
1438 });
1439
1440 _ = view.update(cx, |view, cx| {
1441 view.select_to_beginning_of_line(
1442 &SelectToBeginningOfLine {
1443 stop_at_soft_wraps: true,
1444 },
1445 cx,
1446 );
1447 assert_eq!(
1448 view.selections.display_ranges(cx),
1449 &[
1450 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1451 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1452 ]
1453 );
1454 });
1455
1456 _ = view.update(cx, |view, cx| {
1457 view.select_to_end_of_line(
1458 &SelectToEndOfLine {
1459 stop_at_soft_wraps: true,
1460 },
1461 cx,
1462 );
1463 assert_eq!(
1464 view.selections.display_ranges(cx),
1465 &[
1466 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1467 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1468 ]
1469 );
1470 });
1471
1472 _ = view.update(cx, |view, cx| {
1473 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1474 assert_eq!(view.display_text(cx), "ab\n de");
1475 assert_eq!(
1476 view.selections.display_ranges(cx),
1477 &[
1478 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1479 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1480 ]
1481 );
1482 });
1483
1484 _ = view.update(cx, |view, cx| {
1485 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1486 assert_eq!(view.display_text(cx), "\n");
1487 assert_eq!(
1488 view.selections.display_ranges(cx),
1489 &[
1490 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1491 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1492 ]
1493 );
1494 });
1495}
1496
1497#[gpui::test]
1498fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1499 init_test(cx, |_| {});
1500 let move_to_beg = MoveToBeginningOfLine {
1501 stop_at_soft_wraps: false,
1502 };
1503
1504 let move_to_end = MoveToEndOfLine {
1505 stop_at_soft_wraps: false,
1506 };
1507
1508 let view = cx.add_window(|cx| {
1509 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1510 build_editor(buffer, cx)
1511 });
1512
1513 _ = view.update(cx, |view, cx| {
1514 view.set_wrap_width(Some(140.0.into()), cx);
1515
1516 // We expect the following lines after wrapping
1517 // ```
1518 // thequickbrownfox
1519 // jumpedoverthelazydo
1520 // gs
1521 // ```
1522 // The final `gs` was soft-wrapped onto a new line.
1523 assert_eq!(
1524 "thequickbrownfox\njumpedoverthelaz\nydogs",
1525 view.display_text(cx),
1526 );
1527
1528 // First, let's assert behavior on the first line, that was not soft-wrapped.
1529 // Start the cursor at the `k` on the first line
1530 view.change_selections(None, cx, |s| {
1531 s.select_display_ranges([
1532 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1533 ]);
1534 });
1535
1536 // Moving to the beginning of the line should put us at the beginning of the line.
1537 view.move_to_beginning_of_line(&move_to_beg, cx);
1538 assert_eq!(
1539 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1540 view.selections.display_ranges(cx)
1541 );
1542
1543 // Moving to the end of the line should put us at the end of the line.
1544 view.move_to_end_of_line(&move_to_end, cx);
1545 assert_eq!(
1546 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1547 view.selections.display_ranges(cx)
1548 );
1549
1550 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1551 // Start the cursor at the last line (`y` that was wrapped to a new line)
1552 view.change_selections(None, cx, |s| {
1553 s.select_display_ranges([
1554 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1555 ]);
1556 });
1557
1558 // Moving to the beginning of the line should put us at the start of the second line of
1559 // display text, i.e., the `j`.
1560 view.move_to_beginning_of_line(&move_to_beg, cx);
1561 assert_eq!(
1562 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1563 view.selections.display_ranges(cx)
1564 );
1565
1566 // Moving to the beginning of the line again should be a no-op.
1567 view.move_to_beginning_of_line(&move_to_beg, cx);
1568 assert_eq!(
1569 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1570 view.selections.display_ranges(cx)
1571 );
1572
1573 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1574 // next display line.
1575 view.move_to_end_of_line(&move_to_end, cx);
1576 assert_eq!(
1577 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1578 view.selections.display_ranges(cx)
1579 );
1580
1581 // Moving to the end of the line again should be a no-op.
1582 view.move_to_end_of_line(&move_to_end, cx);
1583 assert_eq!(
1584 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1585 view.selections.display_ranges(cx)
1586 );
1587 });
1588}
1589
1590#[gpui::test]
1591fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1592 init_test(cx, |_| {});
1593
1594 let view = cx.add_window(|cx| {
1595 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1596 build_editor(buffer, cx)
1597 });
1598 _ = view.update(cx, |view, cx| {
1599 view.change_selections(None, cx, |s| {
1600 s.select_display_ranges([
1601 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1602 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1603 ])
1604 });
1605
1606 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1607 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
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_next_word_end(&MoveToNextWordEnd, 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_right(&MoveRight, cx);
1631 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1632 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1633
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_next_word_end(&SelectToNextWordEnd, cx);
1638 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1639 });
1640}
1641
1642#[gpui::test]
1643fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1644 init_test(cx, |_| {});
1645
1646 let view = cx.add_window(|cx| {
1647 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1648 build_editor(buffer, cx)
1649 });
1650
1651 _ = view.update(cx, |view, cx| {
1652 view.set_wrap_width(Some(140.0.into()), cx);
1653 assert_eq!(
1654 view.display_text(cx),
1655 "use one::{\n two::three::\n four::five\n};"
1656 );
1657
1658 view.change_selections(None, cx, |s| {
1659 s.select_display_ranges([
1660 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1661 ]);
1662 });
1663
1664 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1665 assert_eq!(
1666 view.selections.display_ranges(cx),
1667 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1668 );
1669
1670 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1671 assert_eq!(
1672 view.selections.display_ranges(cx),
1673 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1674 );
1675
1676 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1677 assert_eq!(
1678 view.selections.display_ranges(cx),
1679 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1680 );
1681
1682 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1683 assert_eq!(
1684 view.selections.display_ranges(cx),
1685 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1686 );
1687
1688 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1689 assert_eq!(
1690 view.selections.display_ranges(cx),
1691 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1692 );
1693
1694 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1695 assert_eq!(
1696 view.selections.display_ranges(cx),
1697 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1698 );
1699 });
1700}
1701
1702#[gpui::test]
1703async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1704 init_test(cx, |_| {});
1705 let mut cx = EditorTestContext::new(cx).await;
1706
1707 let line_height = cx.editor(|editor, cx| {
1708 editor
1709 .style()
1710 .unwrap()
1711 .text
1712 .line_height_in_pixels(cx.rem_size())
1713 });
1714 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1715
1716 cx.set_state(
1717 &r#"ˇone
1718 two
1719
1720 three
1721 fourˇ
1722 five
1723
1724 six"#
1725 .unindent(),
1726 );
1727
1728 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1729 cx.assert_editor_state(
1730 &r#"one
1731 two
1732 ˇ
1733 three
1734 four
1735 five
1736 ˇ
1737 six"#
1738 .unindent(),
1739 );
1740
1741 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1742 cx.assert_editor_state(
1743 &r#"one
1744 two
1745
1746 three
1747 four
1748 five
1749 ˇ
1750 sixˇ"#
1751 .unindent(),
1752 );
1753
1754 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1755 cx.assert_editor_state(
1756 &r#"one
1757 two
1758
1759 three
1760 four
1761 five
1762
1763 sixˇ"#
1764 .unindent(),
1765 );
1766
1767 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1768 cx.assert_editor_state(
1769 &r#"one
1770 two
1771
1772 three
1773 four
1774 five
1775 ˇ
1776 six"#
1777 .unindent(),
1778 );
1779
1780 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1781 cx.assert_editor_state(
1782 &r#"one
1783 two
1784 ˇ
1785 three
1786 four
1787 five
1788
1789 six"#
1790 .unindent(),
1791 );
1792
1793 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1794 cx.assert_editor_state(
1795 &r#"ˇone
1796 two
1797
1798 three
1799 four
1800 five
1801
1802 six"#
1803 .unindent(),
1804 );
1805}
1806
1807#[gpui::test]
1808async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1809 init_test(cx, |_| {});
1810 let mut cx = EditorTestContext::new(cx).await;
1811 let line_height = cx.editor(|editor, cx| {
1812 editor
1813 .style()
1814 .unwrap()
1815 .text
1816 .line_height_in_pixels(cx.rem_size())
1817 });
1818 let window = cx.window;
1819 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1820
1821 cx.set_state(
1822 &r#"ˇone
1823 two
1824 three
1825 four
1826 five
1827 six
1828 seven
1829 eight
1830 nine
1831 ten
1832 "#,
1833 );
1834
1835 cx.update_editor(|editor, cx| {
1836 assert_eq!(
1837 editor.snapshot(cx).scroll_position(),
1838 gpui::Point::new(0., 0.)
1839 );
1840 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1841 assert_eq!(
1842 editor.snapshot(cx).scroll_position(),
1843 gpui::Point::new(0., 3.)
1844 );
1845 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1846 assert_eq!(
1847 editor.snapshot(cx).scroll_position(),
1848 gpui::Point::new(0., 6.)
1849 );
1850 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1851 assert_eq!(
1852 editor.snapshot(cx).scroll_position(),
1853 gpui::Point::new(0., 3.)
1854 );
1855
1856 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1857 assert_eq!(
1858 editor.snapshot(cx).scroll_position(),
1859 gpui::Point::new(0., 1.)
1860 );
1861 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
1862 assert_eq!(
1863 editor.snapshot(cx).scroll_position(),
1864 gpui::Point::new(0., 3.)
1865 );
1866 });
1867}
1868
1869#[gpui::test]
1870async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
1871 init_test(cx, |_| {});
1872 let mut cx = EditorTestContext::new(cx).await;
1873
1874 let line_height = cx.update_editor(|editor, cx| {
1875 editor.set_vertical_scroll_margin(2, cx);
1876 editor
1877 .style()
1878 .unwrap()
1879 .text
1880 .line_height_in_pixels(cx.rem_size())
1881 });
1882 let window = cx.window;
1883 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
1884
1885 cx.set_state(
1886 &r#"ˇone
1887 two
1888 three
1889 four
1890 five
1891 six
1892 seven
1893 eight
1894 nine
1895 ten
1896 "#,
1897 );
1898 cx.update_editor(|editor, cx| {
1899 assert_eq!(
1900 editor.snapshot(cx).scroll_position(),
1901 gpui::Point::new(0., 0.0)
1902 );
1903 });
1904
1905 // Add a cursor below the visible area. Since both cursors cannot fit
1906 // on screen, the editor autoscrolls to reveal the newest cursor, and
1907 // allows the vertical scroll margin below that cursor.
1908 cx.update_editor(|editor, cx| {
1909 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1910 selections.select_ranges([
1911 Point::new(0, 0)..Point::new(0, 0),
1912 Point::new(6, 0)..Point::new(6, 0),
1913 ]);
1914 })
1915 });
1916 cx.update_editor(|editor, cx| {
1917 assert_eq!(
1918 editor.snapshot(cx).scroll_position(),
1919 gpui::Point::new(0., 3.0)
1920 );
1921 });
1922
1923 // Move down. The editor cursor scrolls down to track the newest cursor.
1924 cx.update_editor(|editor, cx| {
1925 editor.move_down(&Default::default(), cx);
1926 });
1927 cx.update_editor(|editor, cx| {
1928 assert_eq!(
1929 editor.snapshot(cx).scroll_position(),
1930 gpui::Point::new(0., 4.0)
1931 );
1932 });
1933
1934 // Add a cursor above the visible area. Since both cursors fit on screen,
1935 // the editor scrolls to show both.
1936 cx.update_editor(|editor, cx| {
1937 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
1938 selections.select_ranges([
1939 Point::new(1, 0)..Point::new(1, 0),
1940 Point::new(6, 0)..Point::new(6, 0),
1941 ]);
1942 })
1943 });
1944 cx.update_editor(|editor, cx| {
1945 assert_eq!(
1946 editor.snapshot(cx).scroll_position(),
1947 gpui::Point::new(0., 1.0)
1948 );
1949 });
1950}
1951
1952#[gpui::test]
1953async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
1954 init_test(cx, |_| {});
1955 let mut cx = EditorTestContext::new(cx).await;
1956
1957 let line_height = cx.editor(|editor, cx| {
1958 editor
1959 .style()
1960 .unwrap()
1961 .text
1962 .line_height_in_pixels(cx.rem_size())
1963 });
1964 let window = cx.window;
1965 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
1966 cx.set_state(
1967 &r#"
1968 ˇone
1969 two
1970 threeˇ
1971 four
1972 five
1973 six
1974 seven
1975 eight
1976 nine
1977 ten
1978 "#
1979 .unindent(),
1980 );
1981
1982 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
1983 cx.assert_editor_state(
1984 &r#"
1985 one
1986 two
1987 three
1988 ˇfour
1989 five
1990 sixˇ
1991 seven
1992 eight
1993 nine
1994 ten
1995 "#
1996 .unindent(),
1997 );
1998
1999 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2000 cx.assert_editor_state(
2001 &r#"
2002 one
2003 two
2004 three
2005 four
2006 five
2007 six
2008 ˇseven
2009 eight
2010 nineˇ
2011 ten
2012 "#
2013 .unindent(),
2014 );
2015
2016 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2017 cx.assert_editor_state(
2018 &r#"
2019 one
2020 two
2021 three
2022 ˇfour
2023 five
2024 sixˇ
2025 seven
2026 eight
2027 nine
2028 ten
2029 "#
2030 .unindent(),
2031 );
2032
2033 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2034 cx.assert_editor_state(
2035 &r#"
2036 ˇone
2037 two
2038 threeˇ
2039 four
2040 five
2041 six
2042 seven
2043 eight
2044 nine
2045 ten
2046 "#
2047 .unindent(),
2048 );
2049
2050 // Test select collapsing
2051 cx.update_editor(|editor, cx| {
2052 editor.move_page_down(&MovePageDown::default(), cx);
2053 editor.move_page_down(&MovePageDown::default(), cx);
2054 editor.move_page_down(&MovePageDown::default(), cx);
2055 });
2056 cx.assert_editor_state(
2057 &r#"
2058 one
2059 two
2060 three
2061 four
2062 five
2063 six
2064 seven
2065 eight
2066 nine
2067 ˇten
2068 ˇ"#
2069 .unindent(),
2070 );
2071}
2072
2073#[gpui::test]
2074async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2075 init_test(cx, |_| {});
2076 let mut cx = EditorTestContext::new(cx).await;
2077 cx.set_state("one «two threeˇ» four");
2078 cx.update_editor(|editor, cx| {
2079 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2080 assert_eq!(editor.text(cx), " four");
2081 });
2082}
2083
2084#[gpui::test]
2085fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2086 init_test(cx, |_| {});
2087
2088 let view = cx.add_window(|cx| {
2089 let buffer = MultiBuffer::build_simple("one two three four", cx);
2090 build_editor(buffer.clone(), cx)
2091 });
2092
2093 _ = view.update(cx, |view, cx| {
2094 view.change_selections(None, cx, |s| {
2095 s.select_display_ranges([
2096 // an empty selection - the preceding word fragment is deleted
2097 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2098 // characters selected - they are deleted
2099 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2100 ])
2101 });
2102 view.delete_to_previous_word_start(&DeleteToPreviousWordStart, cx);
2103 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2104 });
2105
2106 _ = view.update(cx, |view, cx| {
2107 view.change_selections(None, cx, |s| {
2108 s.select_display_ranges([
2109 // an empty selection - the following word fragment is deleted
2110 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2111 // characters selected - they are deleted
2112 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2113 ])
2114 });
2115 view.delete_to_next_word_end(&DeleteToNextWordEnd, cx);
2116 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2117 });
2118}
2119
2120#[gpui::test]
2121fn test_newline(cx: &mut TestAppContext) {
2122 init_test(cx, |_| {});
2123
2124 let view = cx.add_window(|cx| {
2125 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2126 build_editor(buffer.clone(), cx)
2127 });
2128
2129 _ = view.update(cx, |view, cx| {
2130 view.change_selections(None, cx, |s| {
2131 s.select_display_ranges([
2132 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2133 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2134 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2135 ])
2136 });
2137
2138 view.newline(&Newline, cx);
2139 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2140 });
2141}
2142
2143#[gpui::test]
2144fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2145 init_test(cx, |_| {});
2146
2147 let editor = cx.add_window(|cx| {
2148 let buffer = MultiBuffer::build_simple(
2149 "
2150 a
2151 b(
2152 X
2153 )
2154 c(
2155 X
2156 )
2157 "
2158 .unindent()
2159 .as_str(),
2160 cx,
2161 );
2162 let mut editor = build_editor(buffer.clone(), cx);
2163 editor.change_selections(None, cx, |s| {
2164 s.select_ranges([
2165 Point::new(2, 4)..Point::new(2, 5),
2166 Point::new(5, 4)..Point::new(5, 5),
2167 ])
2168 });
2169 editor
2170 });
2171
2172 _ = editor.update(cx, |editor, cx| {
2173 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2174 editor.buffer.update(cx, |buffer, cx| {
2175 buffer.edit(
2176 [
2177 (Point::new(1, 2)..Point::new(3, 0), ""),
2178 (Point::new(4, 2)..Point::new(6, 0), ""),
2179 ],
2180 None,
2181 cx,
2182 );
2183 assert_eq!(
2184 buffer.read(cx).text(),
2185 "
2186 a
2187 b()
2188 c()
2189 "
2190 .unindent()
2191 );
2192 });
2193 assert_eq!(
2194 editor.selections.ranges(cx),
2195 &[
2196 Point::new(1, 2)..Point::new(1, 2),
2197 Point::new(2, 2)..Point::new(2, 2),
2198 ],
2199 );
2200
2201 editor.newline(&Newline, cx);
2202 assert_eq!(
2203 editor.text(cx),
2204 "
2205 a
2206 b(
2207 )
2208 c(
2209 )
2210 "
2211 .unindent()
2212 );
2213
2214 // The selections are moved after the inserted newlines
2215 assert_eq!(
2216 editor.selections.ranges(cx),
2217 &[
2218 Point::new(2, 0)..Point::new(2, 0),
2219 Point::new(4, 0)..Point::new(4, 0),
2220 ],
2221 );
2222 });
2223}
2224
2225#[gpui::test]
2226async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2227 init_test(cx, |settings| {
2228 settings.defaults.tab_size = NonZeroU32::new(4)
2229 });
2230
2231 let language = Arc::new(
2232 Language::new(
2233 LanguageConfig::default(),
2234 Some(tree_sitter_rust::language()),
2235 )
2236 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2237 .unwrap(),
2238 );
2239
2240 let mut cx = EditorTestContext::new(cx).await;
2241 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2242 cx.set_state(indoc! {"
2243 const a: ˇA = (
2244 (ˇ
2245 «const_functionˇ»(ˇ),
2246 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2247 )ˇ
2248 ˇ);ˇ
2249 "});
2250
2251 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2252 cx.assert_editor_state(indoc! {"
2253 ˇ
2254 const a: A = (
2255 ˇ
2256 (
2257 ˇ
2258 ˇ
2259 const_function(),
2260 ˇ
2261 ˇ
2262 ˇ
2263 ˇ
2264 something_else,
2265 ˇ
2266 )
2267 ˇ
2268 ˇ
2269 );
2270 "});
2271}
2272
2273#[gpui::test]
2274async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2275 init_test(cx, |settings| {
2276 settings.defaults.tab_size = NonZeroU32::new(4)
2277 });
2278
2279 let language = Arc::new(
2280 Language::new(
2281 LanguageConfig::default(),
2282 Some(tree_sitter_rust::language()),
2283 )
2284 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2285 .unwrap(),
2286 );
2287
2288 let mut cx = EditorTestContext::new(cx).await;
2289 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2290 cx.set_state(indoc! {"
2291 const a: ˇA = (
2292 (ˇ
2293 «const_functionˇ»(ˇ),
2294 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2295 )ˇ
2296 ˇ);ˇ
2297 "});
2298
2299 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2300 cx.assert_editor_state(indoc! {"
2301 const a: A = (
2302 ˇ
2303 (
2304 ˇ
2305 const_function(),
2306 ˇ
2307 ˇ
2308 something_else,
2309 ˇ
2310 ˇ
2311 ˇ
2312 ˇ
2313 )
2314 ˇ
2315 );
2316 ˇ
2317 ˇ
2318 "});
2319}
2320
2321#[gpui::test]
2322async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2323 init_test(cx, |settings| {
2324 settings.defaults.tab_size = NonZeroU32::new(4)
2325 });
2326
2327 let language = Arc::new(Language::new(
2328 LanguageConfig {
2329 line_comments: vec!["//".into()],
2330 ..LanguageConfig::default()
2331 },
2332 None,
2333 ));
2334 {
2335 let mut cx = EditorTestContext::new(cx).await;
2336 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2337 cx.set_state(indoc! {"
2338 // Fooˇ
2339 "});
2340
2341 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2342 cx.assert_editor_state(indoc! {"
2343 // Foo
2344 //ˇ
2345 "});
2346 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2347 cx.set_state(indoc! {"
2348 ˇ// Foo
2349 "});
2350 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2351 cx.assert_editor_state(indoc! {"
2352
2353 ˇ// Foo
2354 "});
2355 }
2356 // Ensure that comment continuations can be disabled.
2357 update_test_language_settings(cx, |settings| {
2358 settings.defaults.extend_comment_on_newline = Some(false);
2359 });
2360 let mut cx = EditorTestContext::new(cx).await;
2361 cx.set_state(indoc! {"
2362 // Fooˇ
2363 "});
2364 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2365 cx.assert_editor_state(indoc! {"
2366 // Foo
2367 ˇ
2368 "});
2369}
2370
2371#[gpui::test]
2372fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2373 init_test(cx, |_| {});
2374
2375 let editor = cx.add_window(|cx| {
2376 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2377 let mut editor = build_editor(buffer.clone(), cx);
2378 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2379 editor
2380 });
2381
2382 _ = editor.update(cx, |editor, cx| {
2383 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2384 editor.buffer.update(cx, |buffer, cx| {
2385 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2386 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2387 });
2388 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2389
2390 editor.insert("Z", cx);
2391 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2392
2393 // The selections are moved after the inserted characters
2394 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2395 });
2396}
2397
2398#[gpui::test]
2399async fn test_tab(cx: &mut gpui::TestAppContext) {
2400 init_test(cx, |settings| {
2401 settings.defaults.tab_size = NonZeroU32::new(3)
2402 });
2403
2404 let mut cx = EditorTestContext::new(cx).await;
2405 cx.set_state(indoc! {"
2406 ˇabˇc
2407 ˇ🏀ˇ🏀ˇefg
2408 dˇ
2409 "});
2410 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2411 cx.assert_editor_state(indoc! {"
2412 ˇab ˇc
2413 ˇ🏀 ˇ🏀 ˇefg
2414 d ˇ
2415 "});
2416
2417 cx.set_state(indoc! {"
2418 a
2419 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2420 "});
2421 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2422 cx.assert_editor_state(indoc! {"
2423 a
2424 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2425 "});
2426}
2427
2428#[gpui::test]
2429async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2430 init_test(cx, |_| {});
2431
2432 let mut cx = EditorTestContext::new(cx).await;
2433 let language = Arc::new(
2434 Language::new(
2435 LanguageConfig::default(),
2436 Some(tree_sitter_rust::language()),
2437 )
2438 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2439 .unwrap(),
2440 );
2441 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2442
2443 // cursors that are already at the suggested indent level insert
2444 // a soft tab. cursors that are to the left of the suggested indent
2445 // auto-indent their line.
2446 cx.set_state(indoc! {"
2447 ˇ
2448 const a: B = (
2449 c(
2450 d(
2451 ˇ
2452 )
2453 ˇ
2454 ˇ )
2455 );
2456 "});
2457 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2458 cx.assert_editor_state(indoc! {"
2459 ˇ
2460 const a: B = (
2461 c(
2462 d(
2463 ˇ
2464 )
2465 ˇ
2466 ˇ)
2467 );
2468 "});
2469
2470 // handle auto-indent when there are multiple cursors on the same line
2471 cx.set_state(indoc! {"
2472 const a: B = (
2473 c(
2474 ˇ ˇ
2475 ˇ )
2476 );
2477 "});
2478 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2479 cx.assert_editor_state(indoc! {"
2480 const a: B = (
2481 c(
2482 ˇ
2483 ˇ)
2484 );
2485 "});
2486}
2487
2488#[gpui::test]
2489async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2490 init_test(cx, |settings| {
2491 settings.defaults.tab_size = NonZeroU32::new(4)
2492 });
2493
2494 let language = Arc::new(
2495 Language::new(
2496 LanguageConfig::default(),
2497 Some(tree_sitter_rust::language()),
2498 )
2499 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2500 .unwrap(),
2501 );
2502
2503 let mut cx = EditorTestContext::new(cx).await;
2504 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2505 cx.set_state(indoc! {"
2506 fn a() {
2507 if b {
2508 \t ˇc
2509 }
2510 }
2511 "});
2512
2513 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2514 cx.assert_editor_state(indoc! {"
2515 fn a() {
2516 if b {
2517 ˇc
2518 }
2519 }
2520 "});
2521}
2522
2523#[gpui::test]
2524async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2525 init_test(cx, |settings| {
2526 settings.defaults.tab_size = NonZeroU32::new(4);
2527 });
2528
2529 let mut cx = EditorTestContext::new(cx).await;
2530
2531 cx.set_state(indoc! {"
2532 «oneˇ» «twoˇ»
2533 three
2534 four
2535 "});
2536 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2537 cx.assert_editor_state(indoc! {"
2538 «oneˇ» «twoˇ»
2539 three
2540 four
2541 "});
2542
2543 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2544 cx.assert_editor_state(indoc! {"
2545 «oneˇ» «twoˇ»
2546 three
2547 four
2548 "});
2549
2550 // select across line ending
2551 cx.set_state(indoc! {"
2552 one two
2553 t«hree
2554 ˇ» four
2555 "});
2556 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2557 cx.assert_editor_state(indoc! {"
2558 one two
2559 t«hree
2560 ˇ» four
2561 "});
2562
2563 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2564 cx.assert_editor_state(indoc! {"
2565 one two
2566 t«hree
2567 ˇ» four
2568 "});
2569
2570 // Ensure that indenting/outdenting works when the cursor is at column 0.
2571 cx.set_state(indoc! {"
2572 one two
2573 ˇthree
2574 four
2575 "});
2576 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2577 cx.assert_editor_state(indoc! {"
2578 one two
2579 ˇthree
2580 four
2581 "});
2582
2583 cx.set_state(indoc! {"
2584 one two
2585 ˇ three
2586 four
2587 "});
2588 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2589 cx.assert_editor_state(indoc! {"
2590 one two
2591 ˇthree
2592 four
2593 "});
2594}
2595
2596#[gpui::test]
2597async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2598 init_test(cx, |settings| {
2599 settings.defaults.hard_tabs = Some(true);
2600 });
2601
2602 let mut cx = EditorTestContext::new(cx).await;
2603
2604 // select two ranges on one line
2605 cx.set_state(indoc! {"
2606 «oneˇ» «twoˇ»
2607 three
2608 four
2609 "});
2610 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2611 cx.assert_editor_state(indoc! {"
2612 \t«oneˇ» «twoˇ»
2613 three
2614 four
2615 "});
2616 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2617 cx.assert_editor_state(indoc! {"
2618 \t\t«oneˇ» «twoˇ»
2619 three
2620 four
2621 "});
2622 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2623 cx.assert_editor_state(indoc! {"
2624 \t«oneˇ» «twoˇ»
2625 three
2626 four
2627 "});
2628 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2629 cx.assert_editor_state(indoc! {"
2630 «oneˇ» «twoˇ»
2631 three
2632 four
2633 "});
2634
2635 // select across a line ending
2636 cx.set_state(indoc! {"
2637 one two
2638 t«hree
2639 ˇ»four
2640 "});
2641 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2642 cx.assert_editor_state(indoc! {"
2643 one two
2644 \tt«hree
2645 ˇ»four
2646 "});
2647 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2648 cx.assert_editor_state(indoc! {"
2649 one two
2650 \t\tt«hree
2651 ˇ»four
2652 "});
2653 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2654 cx.assert_editor_state(indoc! {"
2655 one two
2656 \tt«hree
2657 ˇ»four
2658 "});
2659 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2660 cx.assert_editor_state(indoc! {"
2661 one two
2662 t«hree
2663 ˇ»four
2664 "});
2665
2666 // Ensure that indenting/outdenting works when the cursor is at column 0.
2667 cx.set_state(indoc! {"
2668 one two
2669 ˇthree
2670 four
2671 "});
2672 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2673 cx.assert_editor_state(indoc! {"
2674 one two
2675 ˇthree
2676 four
2677 "});
2678 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2679 cx.assert_editor_state(indoc! {"
2680 one two
2681 \tˇthree
2682 four
2683 "});
2684 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2685 cx.assert_editor_state(indoc! {"
2686 one two
2687 ˇthree
2688 four
2689 "});
2690}
2691
2692#[gpui::test]
2693fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2694 init_test(cx, |settings| {
2695 settings.languages.extend([
2696 (
2697 "TOML".into(),
2698 LanguageSettingsContent {
2699 tab_size: NonZeroU32::new(2),
2700 ..Default::default()
2701 },
2702 ),
2703 (
2704 "Rust".into(),
2705 LanguageSettingsContent {
2706 tab_size: NonZeroU32::new(4),
2707 ..Default::default()
2708 },
2709 ),
2710 ]);
2711 });
2712
2713 let toml_language = Arc::new(Language::new(
2714 LanguageConfig {
2715 name: "TOML".into(),
2716 ..Default::default()
2717 },
2718 None,
2719 ));
2720 let rust_language = Arc::new(Language::new(
2721 LanguageConfig {
2722 name: "Rust".into(),
2723 ..Default::default()
2724 },
2725 None,
2726 ));
2727
2728 let toml_buffer =
2729 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2730 let rust_buffer = cx.new_model(|cx| {
2731 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2732 });
2733 let multibuffer = cx.new_model(|cx| {
2734 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
2735 multibuffer.push_excerpts(
2736 toml_buffer.clone(),
2737 [ExcerptRange {
2738 context: Point::new(0, 0)..Point::new(2, 0),
2739 primary: None,
2740 }],
2741 cx,
2742 );
2743 multibuffer.push_excerpts(
2744 rust_buffer.clone(),
2745 [ExcerptRange {
2746 context: Point::new(0, 0)..Point::new(1, 0),
2747 primary: None,
2748 }],
2749 cx,
2750 );
2751 multibuffer
2752 });
2753
2754 cx.add_window(|cx| {
2755 let mut editor = build_editor(multibuffer, cx);
2756
2757 assert_eq!(
2758 editor.text(cx),
2759 indoc! {"
2760 a = 1
2761 b = 2
2762
2763 const c: usize = 3;
2764 "}
2765 );
2766
2767 select_ranges(
2768 &mut editor,
2769 indoc! {"
2770 «aˇ» = 1
2771 b = 2
2772
2773 «const c:ˇ» usize = 3;
2774 "},
2775 cx,
2776 );
2777
2778 editor.tab(&Tab, cx);
2779 assert_text_with_selections(
2780 &mut editor,
2781 indoc! {"
2782 «aˇ» = 1
2783 b = 2
2784
2785 «const c:ˇ» usize = 3;
2786 "},
2787 cx,
2788 );
2789 editor.tab_prev(&TabPrev, cx);
2790 assert_text_with_selections(
2791 &mut editor,
2792 indoc! {"
2793 «aˇ» = 1
2794 b = 2
2795
2796 «const c:ˇ» usize = 3;
2797 "},
2798 cx,
2799 );
2800
2801 editor
2802 });
2803}
2804
2805#[gpui::test]
2806async fn test_backspace(cx: &mut gpui::TestAppContext) {
2807 init_test(cx, |_| {});
2808
2809 let mut cx = EditorTestContext::new(cx).await;
2810
2811 // Basic backspace
2812 cx.set_state(indoc! {"
2813 onˇe two three
2814 fou«rˇ» five six
2815 seven «ˇeight nine
2816 »ten
2817 "});
2818 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2819 cx.assert_editor_state(indoc! {"
2820 oˇe two three
2821 fouˇ five six
2822 seven ˇten
2823 "});
2824
2825 // Test backspace inside and around indents
2826 cx.set_state(indoc! {"
2827 zero
2828 ˇone
2829 ˇtwo
2830 ˇ ˇ ˇ three
2831 ˇ ˇ four
2832 "});
2833 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2834 cx.assert_editor_state(indoc! {"
2835 zero
2836 ˇone
2837 ˇtwo
2838 ˇ threeˇ four
2839 "});
2840
2841 // Test backspace with line_mode set to true
2842 cx.update_editor(|e, _| e.selections.line_mode = true);
2843 cx.set_state(indoc! {"
2844 The ˇquick ˇbrown
2845 fox jumps over
2846 the lazy dog
2847 ˇThe qu«ick bˇ»rown"});
2848 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2849 cx.assert_editor_state(indoc! {"
2850 ˇfox jumps over
2851 the lazy dogˇ"});
2852}
2853
2854#[gpui::test]
2855async fn test_delete(cx: &mut gpui::TestAppContext) {
2856 init_test(cx, |_| {});
2857
2858 let mut cx = EditorTestContext::new(cx).await;
2859 cx.set_state(indoc! {"
2860 onˇe two three
2861 fou«rˇ» five six
2862 seven «ˇeight nine
2863 »ten
2864 "});
2865 cx.update_editor(|e, cx| e.delete(&Delete, cx));
2866 cx.assert_editor_state(indoc! {"
2867 onˇ two three
2868 fouˇ five six
2869 seven ˇten
2870 "});
2871
2872 // Test backspace with line_mode set to true
2873 cx.update_editor(|e, _| e.selections.line_mode = true);
2874 cx.set_state(indoc! {"
2875 The ˇquick ˇbrown
2876 fox «ˇjum»ps over
2877 the lazy dog
2878 ˇThe qu«ick bˇ»rown"});
2879 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
2880 cx.assert_editor_state("ˇthe lazy dogˇ");
2881}
2882
2883#[gpui::test]
2884fn test_delete_line(cx: &mut TestAppContext) {
2885 init_test(cx, |_| {});
2886
2887 let view = cx.add_window(|cx| {
2888 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2889 build_editor(buffer, cx)
2890 });
2891 _ = view.update(cx, |view, cx| {
2892 view.change_selections(None, cx, |s| {
2893 s.select_display_ranges([
2894 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
2895 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
2896 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
2897 ])
2898 });
2899 view.delete_line(&DeleteLine, cx);
2900 assert_eq!(view.display_text(cx), "ghi");
2901 assert_eq!(
2902 view.selections.display_ranges(cx),
2903 vec![
2904 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
2905 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
2906 ]
2907 );
2908 });
2909
2910 let view = cx.add_window(|cx| {
2911 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
2912 build_editor(buffer, cx)
2913 });
2914 _ = view.update(cx, |view, cx| {
2915 view.change_selections(None, cx, |s| {
2916 s.select_display_ranges([
2917 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
2918 ])
2919 });
2920 view.delete_line(&DeleteLine, cx);
2921 assert_eq!(view.display_text(cx), "ghi\n");
2922 assert_eq!(
2923 view.selections.display_ranges(cx),
2924 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
2925 );
2926 });
2927}
2928
2929#[gpui::test]
2930fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
2931 init_test(cx, |_| {});
2932
2933 cx.add_window(|cx| {
2934 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
2935 let mut editor = build_editor(buffer.clone(), cx);
2936 let buffer = buffer.read(cx).as_singleton().unwrap();
2937
2938 assert_eq!(
2939 editor.selections.ranges::<Point>(cx),
2940 &[Point::new(0, 0)..Point::new(0, 0)]
2941 );
2942
2943 // When on single line, replace newline at end by space
2944 editor.join_lines(&JoinLines, cx);
2945 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
2946 assert_eq!(
2947 editor.selections.ranges::<Point>(cx),
2948 &[Point::new(0, 3)..Point::new(0, 3)]
2949 );
2950
2951 // When multiple lines are selected, remove newlines that are spanned by the selection
2952 editor.change_selections(None, cx, |s| {
2953 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
2954 });
2955 editor.join_lines(&JoinLines, cx);
2956 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
2957 assert_eq!(
2958 editor.selections.ranges::<Point>(cx),
2959 &[Point::new(0, 11)..Point::new(0, 11)]
2960 );
2961
2962 // Undo should be transactional
2963 editor.undo(&Undo, cx);
2964 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
2965 assert_eq!(
2966 editor.selections.ranges::<Point>(cx),
2967 &[Point::new(0, 5)..Point::new(2, 2)]
2968 );
2969
2970 // When joining an empty line don't insert a space
2971 editor.change_selections(None, cx, |s| {
2972 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
2973 });
2974 editor.join_lines(&JoinLines, cx);
2975 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
2976 assert_eq!(
2977 editor.selections.ranges::<Point>(cx),
2978 [Point::new(2, 3)..Point::new(2, 3)]
2979 );
2980
2981 // We can remove trailing newlines
2982 editor.join_lines(&JoinLines, cx);
2983 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
2984 assert_eq!(
2985 editor.selections.ranges::<Point>(cx),
2986 [Point::new(2, 3)..Point::new(2, 3)]
2987 );
2988
2989 // We don't blow up on the last line
2990 editor.join_lines(&JoinLines, cx);
2991 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
2992 assert_eq!(
2993 editor.selections.ranges::<Point>(cx),
2994 [Point::new(2, 3)..Point::new(2, 3)]
2995 );
2996
2997 // reset to test indentation
2998 editor.buffer.update(cx, |buffer, cx| {
2999 buffer.edit(
3000 [
3001 (Point::new(1, 0)..Point::new(1, 2), " "),
3002 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3003 ],
3004 None,
3005 cx,
3006 )
3007 });
3008
3009 // We remove any leading spaces
3010 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3011 editor.change_selections(None, cx, |s| {
3012 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3013 });
3014 editor.join_lines(&JoinLines, cx);
3015 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3016
3017 // We don't insert a space for a line containing only spaces
3018 editor.join_lines(&JoinLines, cx);
3019 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3020
3021 // We ignore any leading tabs
3022 editor.join_lines(&JoinLines, cx);
3023 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3024
3025 editor
3026 });
3027}
3028
3029#[gpui::test]
3030fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3031 init_test(cx, |_| {});
3032
3033 cx.add_window(|cx| {
3034 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3035 let mut editor = build_editor(buffer.clone(), cx);
3036 let buffer = buffer.read(cx).as_singleton().unwrap();
3037
3038 editor.change_selections(None, cx, |s| {
3039 s.select_ranges([
3040 Point::new(0, 2)..Point::new(1, 1),
3041 Point::new(1, 2)..Point::new(1, 2),
3042 Point::new(3, 1)..Point::new(3, 2),
3043 ])
3044 });
3045
3046 editor.join_lines(&JoinLines, cx);
3047 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3048
3049 assert_eq!(
3050 editor.selections.ranges::<Point>(cx),
3051 [
3052 Point::new(0, 7)..Point::new(0, 7),
3053 Point::new(1, 3)..Point::new(1, 3)
3054 ]
3055 );
3056 editor
3057 });
3058}
3059
3060#[gpui::test]
3061async fn test_join_lines_with_git_diff_base(
3062 executor: BackgroundExecutor,
3063 cx: &mut gpui::TestAppContext,
3064) {
3065 init_test(cx, |_| {});
3066
3067 let mut cx = EditorTestContext::new(cx).await;
3068
3069 let diff_base = r#"
3070 Line 0
3071 Line 1
3072 Line 2
3073 Line 3
3074 "#
3075 .unindent();
3076
3077 cx.set_state(
3078 &r#"
3079 ˇLine 0
3080 Line 1
3081 Line 2
3082 Line 3
3083 "#
3084 .unindent(),
3085 );
3086
3087 cx.set_diff_base(Some(&diff_base));
3088 executor.run_until_parked();
3089
3090 // Join lines
3091 cx.update_editor(|editor, cx| {
3092 editor.join_lines(&JoinLines, cx);
3093 });
3094 executor.run_until_parked();
3095
3096 cx.assert_editor_state(
3097 &r#"
3098 Line 0ˇ Line 1
3099 Line 2
3100 Line 3
3101 "#
3102 .unindent(),
3103 );
3104 // Join again
3105 cx.update_editor(|editor, cx| {
3106 editor.join_lines(&JoinLines, cx);
3107 });
3108 executor.run_until_parked();
3109
3110 cx.assert_editor_state(
3111 &r#"
3112 Line 0 Line 1ˇ Line 2
3113 Line 3
3114 "#
3115 .unindent(),
3116 );
3117}
3118
3119#[gpui::test]
3120async fn test_custom_newlines_cause_no_false_positive_diffs(
3121 executor: BackgroundExecutor,
3122 cx: &mut gpui::TestAppContext,
3123) {
3124 init_test(cx, |_| {});
3125 let mut cx = EditorTestContext::new(cx).await;
3126 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3127 cx.set_diff_base(Some("Line 0\r\nLine 1\r\nLine 2\r\nLine 3"));
3128 executor.run_until_parked();
3129
3130 cx.update_editor(|editor, cx| {
3131 assert_eq!(
3132 editor
3133 .buffer()
3134 .read(cx)
3135 .snapshot(cx)
3136 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
3137 .collect::<Vec<_>>(),
3138 Vec::new(),
3139 "Should not have any diffs for files with custom newlines"
3140 );
3141 });
3142}
3143
3144#[gpui::test]
3145async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3146 init_test(cx, |_| {});
3147
3148 let mut cx = EditorTestContext::new(cx).await;
3149
3150 // Test sort_lines_case_insensitive()
3151 cx.set_state(indoc! {"
3152 «z
3153 y
3154 x
3155 Z
3156 Y
3157 Xˇ»
3158 "});
3159 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3160 cx.assert_editor_state(indoc! {"
3161 «x
3162 X
3163 y
3164 Y
3165 z
3166 Zˇ»
3167 "});
3168
3169 // Test reverse_lines()
3170 cx.set_state(indoc! {"
3171 «5
3172 4
3173 3
3174 2
3175 1ˇ»
3176 "});
3177 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3178 cx.assert_editor_state(indoc! {"
3179 «1
3180 2
3181 3
3182 4
3183 5ˇ»
3184 "});
3185
3186 // Skip testing shuffle_line()
3187
3188 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3189 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3190
3191 // Don't manipulate when cursor is on single line, but expand the selection
3192 cx.set_state(indoc! {"
3193 ddˇdd
3194 ccc
3195 bb
3196 a
3197 "});
3198 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3199 cx.assert_editor_state(indoc! {"
3200 «ddddˇ»
3201 ccc
3202 bb
3203 a
3204 "});
3205
3206 // Basic manipulate case
3207 // Start selection moves to column 0
3208 // End of selection shrinks to fit shorter line
3209 cx.set_state(indoc! {"
3210 dd«d
3211 ccc
3212 bb
3213 aaaaaˇ»
3214 "});
3215 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3216 cx.assert_editor_state(indoc! {"
3217 «aaaaa
3218 bb
3219 ccc
3220 dddˇ»
3221 "});
3222
3223 // Manipulate case with newlines
3224 cx.set_state(indoc! {"
3225 dd«d
3226 ccc
3227
3228 bb
3229 aaaaa
3230
3231 ˇ»
3232 "});
3233 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3234 cx.assert_editor_state(indoc! {"
3235 «
3236
3237 aaaaa
3238 bb
3239 ccc
3240 dddˇ»
3241
3242 "});
3243
3244 // Adding new line
3245 cx.set_state(indoc! {"
3246 aa«a
3247 bbˇ»b
3248 "});
3249 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3250 cx.assert_editor_state(indoc! {"
3251 «aaa
3252 bbb
3253 added_lineˇ»
3254 "});
3255
3256 // Removing line
3257 cx.set_state(indoc! {"
3258 aa«a
3259 bbbˇ»
3260 "});
3261 cx.update_editor(|e, cx| {
3262 e.manipulate_lines(cx, |lines| {
3263 lines.pop();
3264 })
3265 });
3266 cx.assert_editor_state(indoc! {"
3267 «aaaˇ»
3268 "});
3269
3270 // Removing all lines
3271 cx.set_state(indoc! {"
3272 aa«a
3273 bbbˇ»
3274 "});
3275 cx.update_editor(|e, cx| {
3276 e.manipulate_lines(cx, |lines| {
3277 lines.drain(..);
3278 })
3279 });
3280 cx.assert_editor_state(indoc! {"
3281 ˇ
3282 "});
3283}
3284
3285#[gpui::test]
3286async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3287 init_test(cx, |_| {});
3288
3289 let mut cx = EditorTestContext::new(cx).await;
3290
3291 // Consider continuous selection as single selection
3292 cx.set_state(indoc! {"
3293 Aaa«aa
3294 cˇ»c«c
3295 bb
3296 aaaˇ»aa
3297 "});
3298 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3299 cx.assert_editor_state(indoc! {"
3300 «Aaaaa
3301 ccc
3302 bb
3303 aaaaaˇ»
3304 "});
3305
3306 cx.set_state(indoc! {"
3307 Aaa«aa
3308 cˇ»c«c
3309 bb
3310 aaaˇ»aa
3311 "});
3312 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3313 cx.assert_editor_state(indoc! {"
3314 «Aaaaa
3315 ccc
3316 bbˇ»
3317 "});
3318
3319 // Consider non continuous selection as distinct dedup operations
3320 cx.set_state(indoc! {"
3321 «aaaaa
3322 bb
3323 aaaaa
3324 aaaaaˇ»
3325
3326 aaa«aaˇ»
3327 "});
3328 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3329 cx.assert_editor_state(indoc! {"
3330 «aaaaa
3331 bbˇ»
3332
3333 «aaaaaˇ»
3334 "});
3335}
3336
3337#[gpui::test]
3338async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3339 init_test(cx, |_| {});
3340
3341 let mut cx = EditorTestContext::new(cx).await;
3342
3343 cx.set_state(indoc! {"
3344 «Aaa
3345 aAa
3346 Aaaˇ»
3347 "});
3348 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3349 cx.assert_editor_state(indoc! {"
3350 «Aaa
3351 aAaˇ»
3352 "});
3353
3354 cx.set_state(indoc! {"
3355 «Aaa
3356 aAa
3357 aaAˇ»
3358 "});
3359 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3360 cx.assert_editor_state(indoc! {"
3361 «Aaaˇ»
3362 "});
3363}
3364
3365#[gpui::test]
3366async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3367 init_test(cx, |_| {});
3368
3369 let mut cx = EditorTestContext::new(cx).await;
3370
3371 // Manipulate with multiple selections on a single line
3372 cx.set_state(indoc! {"
3373 dd«dd
3374 cˇ»c«c
3375 bb
3376 aaaˇ»aa
3377 "});
3378 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3379 cx.assert_editor_state(indoc! {"
3380 «aaaaa
3381 bb
3382 ccc
3383 ddddˇ»
3384 "});
3385
3386 // Manipulate with multiple disjoin selections
3387 cx.set_state(indoc! {"
3388 5«
3389 4
3390 3
3391 2
3392 1ˇ»
3393
3394 dd«dd
3395 ccc
3396 bb
3397 aaaˇ»aa
3398 "});
3399 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3400 cx.assert_editor_state(indoc! {"
3401 «1
3402 2
3403 3
3404 4
3405 5ˇ»
3406
3407 «aaaaa
3408 bb
3409 ccc
3410 ddddˇ»
3411 "});
3412
3413 // Adding lines on each selection
3414 cx.set_state(indoc! {"
3415 2«
3416 1ˇ»
3417
3418 bb«bb
3419 aaaˇ»aa
3420 "});
3421 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3422 cx.assert_editor_state(indoc! {"
3423 «2
3424 1
3425 added lineˇ»
3426
3427 «bbbb
3428 aaaaa
3429 added lineˇ»
3430 "});
3431
3432 // Removing lines on each selection
3433 cx.set_state(indoc! {"
3434 2«
3435 1ˇ»
3436
3437 bb«bb
3438 aaaˇ»aa
3439 "});
3440 cx.update_editor(|e, cx| {
3441 e.manipulate_lines(cx, |lines| {
3442 lines.pop();
3443 })
3444 });
3445 cx.assert_editor_state(indoc! {"
3446 «2ˇ»
3447
3448 «bbbbˇ»
3449 "});
3450}
3451
3452#[gpui::test]
3453async fn test_manipulate_text(cx: &mut TestAppContext) {
3454 init_test(cx, |_| {});
3455
3456 let mut cx = EditorTestContext::new(cx).await;
3457
3458 // Test convert_to_upper_case()
3459 cx.set_state(indoc! {"
3460 «hello worldˇ»
3461 "});
3462 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3463 cx.assert_editor_state(indoc! {"
3464 «HELLO WORLDˇ»
3465 "});
3466
3467 // Test convert_to_lower_case()
3468 cx.set_state(indoc! {"
3469 «HELLO WORLDˇ»
3470 "});
3471 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3472 cx.assert_editor_state(indoc! {"
3473 «hello worldˇ»
3474 "});
3475
3476 // Test multiple line, single selection case
3477 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3478 cx.set_state(indoc! {"
3479 «The quick brown
3480 fox jumps over
3481 the lazy dogˇ»
3482 "});
3483 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3484 cx.assert_editor_state(indoc! {"
3485 «The Quick Brown
3486 Fox Jumps Over
3487 The Lazy Dogˇ»
3488 "});
3489
3490 // Test multiple line, single selection case
3491 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3492 cx.set_state(indoc! {"
3493 «The quick brown
3494 fox jumps over
3495 the lazy dogˇ»
3496 "});
3497 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3498 cx.assert_editor_state(indoc! {"
3499 «TheQuickBrown
3500 FoxJumpsOver
3501 TheLazyDogˇ»
3502 "});
3503
3504 // From here on out, test more complex cases of manipulate_text()
3505
3506 // Test no selection case - should affect words cursors are in
3507 // Cursor at beginning, middle, and end of word
3508 cx.set_state(indoc! {"
3509 ˇhello big beauˇtiful worldˇ
3510 "});
3511 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3512 cx.assert_editor_state(indoc! {"
3513 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3514 "});
3515
3516 // Test multiple selections on a single line and across multiple lines
3517 cx.set_state(indoc! {"
3518 «Theˇ» quick «brown
3519 foxˇ» jumps «overˇ»
3520 the «lazyˇ» dog
3521 "});
3522 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3523 cx.assert_editor_state(indoc! {"
3524 «THEˇ» quick «BROWN
3525 FOXˇ» jumps «OVERˇ»
3526 the «LAZYˇ» dog
3527 "});
3528
3529 // Test case where text length grows
3530 cx.set_state(indoc! {"
3531 «tschüߡ»
3532 "});
3533 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3534 cx.assert_editor_state(indoc! {"
3535 «TSCHÜSSˇ»
3536 "});
3537
3538 // Test to make sure we don't crash when text shrinks
3539 cx.set_state(indoc! {"
3540 aaa_bbbˇ
3541 "});
3542 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3543 cx.assert_editor_state(indoc! {"
3544 «aaaBbbˇ»
3545 "});
3546
3547 // Test to make sure we all aware of the fact that each word can grow and shrink
3548 // Final selections should be aware of this fact
3549 cx.set_state(indoc! {"
3550 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3551 "});
3552 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3553 cx.assert_editor_state(indoc! {"
3554 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3555 "});
3556
3557 cx.set_state(indoc! {"
3558 «hElLo, WoRld!ˇ»
3559 "});
3560 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3561 cx.assert_editor_state(indoc! {"
3562 «HeLlO, wOrLD!ˇ»
3563 "});
3564}
3565
3566#[gpui::test]
3567fn test_duplicate_line(cx: &mut TestAppContext) {
3568 init_test(cx, |_| {});
3569
3570 let view = cx.add_window(|cx| {
3571 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3572 build_editor(buffer, cx)
3573 });
3574 _ = view.update(cx, |view, cx| {
3575 view.change_selections(None, cx, |s| {
3576 s.select_display_ranges([
3577 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3578 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3579 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3580 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3581 ])
3582 });
3583 view.duplicate_line_down(&DuplicateLineDown, cx);
3584 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3585 assert_eq!(
3586 view.selections.display_ranges(cx),
3587 vec![
3588 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3589 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3590 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3591 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3592 ]
3593 );
3594 });
3595
3596 let view = cx.add_window(|cx| {
3597 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3598 build_editor(buffer, cx)
3599 });
3600 _ = view.update(cx, |view, cx| {
3601 view.change_selections(None, cx, |s| {
3602 s.select_display_ranges([
3603 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3604 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3605 ])
3606 });
3607 view.duplicate_line_down(&DuplicateLineDown, cx);
3608 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3609 assert_eq!(
3610 view.selections.display_ranges(cx),
3611 vec![
3612 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3613 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3614 ]
3615 );
3616 });
3617
3618 // With `move_upwards` the selections stay in place, except for
3619 // the lines inserted above them
3620 let view = cx.add_window(|cx| {
3621 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3622 build_editor(buffer, cx)
3623 });
3624 _ = view.update(cx, |view, cx| {
3625 view.change_selections(None, cx, |s| {
3626 s.select_display_ranges([
3627 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3628 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3629 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3630 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3631 ])
3632 });
3633 view.duplicate_line_up(&DuplicateLineUp, cx);
3634 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3635 assert_eq!(
3636 view.selections.display_ranges(cx),
3637 vec![
3638 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3639 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3640 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3641 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3642 ]
3643 );
3644 });
3645
3646 let view = cx.add_window(|cx| {
3647 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3648 build_editor(buffer, cx)
3649 });
3650 _ = view.update(cx, |view, cx| {
3651 view.change_selections(None, cx, |s| {
3652 s.select_display_ranges([
3653 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3654 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3655 ])
3656 });
3657 view.duplicate_line_up(&DuplicateLineUp, cx);
3658 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3659 assert_eq!(
3660 view.selections.display_ranges(cx),
3661 vec![
3662 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3663 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3664 ]
3665 );
3666 });
3667}
3668
3669#[gpui::test]
3670fn test_move_line_up_down(cx: &mut TestAppContext) {
3671 init_test(cx, |_| {});
3672
3673 let view = cx.add_window(|cx| {
3674 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3675 build_editor(buffer, cx)
3676 });
3677 _ = view.update(cx, |view, cx| {
3678 view.fold_ranges(
3679 vec![
3680 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3681 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3682 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3683 ],
3684 true,
3685 cx,
3686 );
3687 view.change_selections(None, cx, |s| {
3688 s.select_display_ranges([
3689 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3690 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3691 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3692 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3693 ])
3694 });
3695 assert_eq!(
3696 view.display_text(cx),
3697 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3698 );
3699
3700 view.move_line_up(&MoveLineUp, cx);
3701 assert_eq!(
3702 view.display_text(cx),
3703 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3704 );
3705 assert_eq!(
3706 view.selections.display_ranges(cx),
3707 vec![
3708 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3709 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3710 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3711 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3712 ]
3713 );
3714 });
3715
3716 _ = view.update(cx, |view, cx| {
3717 view.move_line_down(&MoveLineDown, cx);
3718 assert_eq!(
3719 view.display_text(cx),
3720 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3721 );
3722 assert_eq!(
3723 view.selections.display_ranges(cx),
3724 vec![
3725 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3726 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3727 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3728 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3729 ]
3730 );
3731 });
3732
3733 _ = view.update(cx, |view, cx| {
3734 view.move_line_down(&MoveLineDown, cx);
3735 assert_eq!(
3736 view.display_text(cx),
3737 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3738 );
3739 assert_eq!(
3740 view.selections.display_ranges(cx),
3741 vec![
3742 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3743 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3744 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3745 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3746 ]
3747 );
3748 });
3749
3750 _ = view.update(cx, |view, cx| {
3751 view.move_line_up(&MoveLineUp, cx);
3752 assert_eq!(
3753 view.display_text(cx),
3754 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3755 );
3756 assert_eq!(
3757 view.selections.display_ranges(cx),
3758 vec![
3759 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3760 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3761 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3762 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3763 ]
3764 );
3765 });
3766}
3767
3768#[gpui::test]
3769fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3770 init_test(cx, |_| {});
3771
3772 let editor = cx.add_window(|cx| {
3773 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3774 build_editor(buffer, cx)
3775 });
3776 _ = editor.update(cx, |editor, cx| {
3777 let snapshot = editor.buffer.read(cx).snapshot(cx);
3778 editor.insert_blocks(
3779 [BlockProperties {
3780 style: BlockStyle::Fixed,
3781 position: snapshot.anchor_after(Point::new(2, 0)),
3782 disposition: BlockDisposition::Below,
3783 height: 1,
3784 render: Box::new(|_| div().into_any()),
3785 }],
3786 Some(Autoscroll::fit()),
3787 cx,
3788 );
3789 editor.change_selections(None, cx, |s| {
3790 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
3791 });
3792 editor.move_line_down(&MoveLineDown, cx);
3793 });
3794}
3795
3796#[gpui::test]
3797fn test_transpose(cx: &mut TestAppContext) {
3798 init_test(cx, |_| {});
3799
3800 _ = cx.add_window(|cx| {
3801 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
3802 editor.set_style(EditorStyle::default(), cx);
3803 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
3804 editor.transpose(&Default::default(), cx);
3805 assert_eq!(editor.text(cx), "bac");
3806 assert_eq!(editor.selections.ranges(cx), [2..2]);
3807
3808 editor.transpose(&Default::default(), cx);
3809 assert_eq!(editor.text(cx), "bca");
3810 assert_eq!(editor.selections.ranges(cx), [3..3]);
3811
3812 editor.transpose(&Default::default(), cx);
3813 assert_eq!(editor.text(cx), "bac");
3814 assert_eq!(editor.selections.ranges(cx), [3..3]);
3815
3816 editor
3817 });
3818
3819 _ = cx.add_window(|cx| {
3820 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3821 editor.set_style(EditorStyle::default(), cx);
3822 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
3823 editor.transpose(&Default::default(), cx);
3824 assert_eq!(editor.text(cx), "acb\nde");
3825 assert_eq!(editor.selections.ranges(cx), [3..3]);
3826
3827 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3828 editor.transpose(&Default::default(), cx);
3829 assert_eq!(editor.text(cx), "acbd\ne");
3830 assert_eq!(editor.selections.ranges(cx), [5..5]);
3831
3832 editor.transpose(&Default::default(), cx);
3833 assert_eq!(editor.text(cx), "acbde\n");
3834 assert_eq!(editor.selections.ranges(cx), [6..6]);
3835
3836 editor.transpose(&Default::default(), cx);
3837 assert_eq!(editor.text(cx), "acbd\ne");
3838 assert_eq!(editor.selections.ranges(cx), [6..6]);
3839
3840 editor
3841 });
3842
3843 _ = cx.add_window(|cx| {
3844 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
3845 editor.set_style(EditorStyle::default(), cx);
3846 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
3847 editor.transpose(&Default::default(), cx);
3848 assert_eq!(editor.text(cx), "bacd\ne");
3849 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
3850
3851 editor.transpose(&Default::default(), cx);
3852 assert_eq!(editor.text(cx), "bcade\n");
3853 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
3854
3855 editor.transpose(&Default::default(), cx);
3856 assert_eq!(editor.text(cx), "bcda\ne");
3857 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3858
3859 editor.transpose(&Default::default(), cx);
3860 assert_eq!(editor.text(cx), "bcade\n");
3861 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
3862
3863 editor.transpose(&Default::default(), cx);
3864 assert_eq!(editor.text(cx), "bcaed\n");
3865 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
3866
3867 editor
3868 });
3869
3870 _ = cx.add_window(|cx| {
3871 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
3872 editor.set_style(EditorStyle::default(), cx);
3873 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
3874 editor.transpose(&Default::default(), cx);
3875 assert_eq!(editor.text(cx), "🏀🍐✋");
3876 assert_eq!(editor.selections.ranges(cx), [8..8]);
3877
3878 editor.transpose(&Default::default(), cx);
3879 assert_eq!(editor.text(cx), "🏀✋🍐");
3880 assert_eq!(editor.selections.ranges(cx), [11..11]);
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
3887 });
3888}
3889
3890#[gpui::test]
3891async fn test_clipboard(cx: &mut gpui::TestAppContext) {
3892 init_test(cx, |_| {});
3893
3894 let mut cx = EditorTestContext::new(cx).await;
3895
3896 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
3897 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3898 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
3899
3900 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
3901 cx.set_state("two ˇfour ˇsix ˇ");
3902 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3903 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
3904
3905 // Paste again but with only two cursors. Since the number of cursors doesn't
3906 // match the number of slices in the clipboard, the entire clipboard text
3907 // is pasted at each cursor.
3908 cx.set_state("ˇtwo one✅ four three six five ˇ");
3909 cx.update_editor(|e, cx| {
3910 e.handle_input("( ", cx);
3911 e.paste(&Paste, cx);
3912 e.handle_input(") ", cx);
3913 });
3914 cx.assert_editor_state(
3915 &([
3916 "( one✅ ",
3917 "three ",
3918 "five ) ˇtwo one✅ four three six five ( one✅ ",
3919 "three ",
3920 "five ) ˇ",
3921 ]
3922 .join("\n")),
3923 );
3924
3925 // Cut with three selections, one of which is full-line.
3926 cx.set_state(indoc! {"
3927 1«2ˇ»3
3928 4ˇ567
3929 «8ˇ»9"});
3930 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3931 cx.assert_editor_state(indoc! {"
3932 1ˇ3
3933 ˇ9"});
3934
3935 // Paste with three selections, noticing how the copied selection that was full-line
3936 // gets inserted before the second cursor.
3937 cx.set_state(indoc! {"
3938 1ˇ3
3939 9ˇ
3940 «oˇ»ne"});
3941 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3942 cx.assert_editor_state(indoc! {"
3943 12ˇ3
3944 4567
3945 9ˇ
3946 8ˇne"});
3947
3948 // Copy with a single cursor only, which writes the whole line into the clipboard.
3949 cx.set_state(indoc! {"
3950 The quick brown
3951 fox juˇmps over
3952 the lazy dog"});
3953 cx.update_editor(|e, cx| e.copy(&Copy, cx));
3954 assert_eq!(
3955 cx.read_from_clipboard().map(|item| item.text().to_owned()),
3956 Some("fox jumps over\n".to_owned())
3957 );
3958
3959 // Paste with three selections, noticing how the copied full-line selection is inserted
3960 // before the empty selections but replaces the selection that is non-empty.
3961 cx.set_state(indoc! {"
3962 Tˇhe quick brown
3963 «foˇ»x jumps over
3964 tˇhe lazy dog"});
3965 cx.update_editor(|e, cx| e.paste(&Paste, cx));
3966 cx.assert_editor_state(indoc! {"
3967 fox jumps over
3968 Tˇhe quick brown
3969 fox jumps over
3970 ˇx jumps over
3971 fox jumps over
3972 tˇhe lazy dog"});
3973}
3974
3975#[gpui::test]
3976async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
3977 init_test(cx, |_| {});
3978
3979 let mut cx = EditorTestContext::new(cx).await;
3980 let language = Arc::new(Language::new(
3981 LanguageConfig::default(),
3982 Some(tree_sitter_rust::language()),
3983 ));
3984 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
3985
3986 // Cut an indented block, without the leading whitespace.
3987 cx.set_state(indoc! {"
3988 const a: B = (
3989 c(),
3990 «d(
3991 e,
3992 f
3993 )ˇ»
3994 );
3995 "});
3996 cx.update_editor(|e, cx| e.cut(&Cut, cx));
3997 cx.assert_editor_state(indoc! {"
3998 const a: B = (
3999 c(),
4000 ˇ
4001 );
4002 "});
4003
4004 // Paste it at the same position.
4005 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4006 cx.assert_editor_state(indoc! {"
4007 const a: B = (
4008 c(),
4009 d(
4010 e,
4011 f
4012 )ˇ
4013 );
4014 "});
4015
4016 // Paste it at a line with a lower indent level.
4017 cx.set_state(indoc! {"
4018 ˇ
4019 const a: B = (
4020 c(),
4021 );
4022 "});
4023 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4024 cx.assert_editor_state(indoc! {"
4025 d(
4026 e,
4027 f
4028 )ˇ
4029 const a: B = (
4030 c(),
4031 );
4032 "});
4033
4034 // Cut an indented block, with the leading whitespace.
4035 cx.set_state(indoc! {"
4036 const a: B = (
4037 c(),
4038 « d(
4039 e,
4040 f
4041 )
4042 ˇ»);
4043 "});
4044 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4045 cx.assert_editor_state(indoc! {"
4046 const a: B = (
4047 c(),
4048 ˇ);
4049 "});
4050
4051 // Paste it at the same position.
4052 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4053 cx.assert_editor_state(indoc! {"
4054 const a: B = (
4055 c(),
4056 d(
4057 e,
4058 f
4059 )
4060 ˇ);
4061 "});
4062
4063 // Paste it at a line with a higher indent level.
4064 cx.set_state(indoc! {"
4065 const a: B = (
4066 c(),
4067 d(
4068 e,
4069 fˇ
4070 )
4071 );
4072 "});
4073 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4074 cx.assert_editor_state(indoc! {"
4075 const a: B = (
4076 c(),
4077 d(
4078 e,
4079 f d(
4080 e,
4081 f
4082 )
4083 ˇ
4084 )
4085 );
4086 "});
4087}
4088
4089#[gpui::test]
4090fn test_select_all(cx: &mut TestAppContext) {
4091 init_test(cx, |_| {});
4092
4093 let view = cx.add_window(|cx| {
4094 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4095 build_editor(buffer, cx)
4096 });
4097 _ = view.update(cx, |view, cx| {
4098 view.select_all(&SelectAll, cx);
4099 assert_eq!(
4100 view.selections.display_ranges(cx),
4101 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4102 );
4103 });
4104}
4105
4106#[gpui::test]
4107fn test_select_line(cx: &mut TestAppContext) {
4108 init_test(cx, |_| {});
4109
4110 let view = cx.add_window(|cx| {
4111 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4112 build_editor(buffer, cx)
4113 });
4114 _ = view.update(cx, |view, cx| {
4115 view.change_selections(None, cx, |s| {
4116 s.select_display_ranges([
4117 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4118 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4119 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4120 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4121 ])
4122 });
4123 view.select_line(&SelectLine, cx);
4124 assert_eq!(
4125 view.selections.display_ranges(cx),
4126 vec![
4127 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4128 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4129 ]
4130 );
4131 });
4132
4133 _ = view.update(cx, |view, cx| {
4134 view.select_line(&SelectLine, cx);
4135 assert_eq!(
4136 view.selections.display_ranges(cx),
4137 vec![
4138 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4139 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4140 ]
4141 );
4142 });
4143
4144 _ = view.update(cx, |view, cx| {
4145 view.select_line(&SelectLine, cx);
4146 assert_eq!(
4147 view.selections.display_ranges(cx),
4148 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4149 );
4150 });
4151}
4152
4153#[gpui::test]
4154fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4155 init_test(cx, |_| {});
4156
4157 let view = cx.add_window(|cx| {
4158 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4159 build_editor(buffer, cx)
4160 });
4161 _ = view.update(cx, |view, cx| {
4162 view.fold_ranges(
4163 vec![
4164 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4165 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4166 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4167 ],
4168 true,
4169 cx,
4170 );
4171 view.change_selections(None, cx, |s| {
4172 s.select_display_ranges([
4173 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4174 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4175 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4176 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4177 ])
4178 });
4179 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4180 });
4181
4182 _ = view.update(cx, |view, cx| {
4183 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4184 assert_eq!(
4185 view.display_text(cx),
4186 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4187 );
4188 assert_eq!(
4189 view.selections.display_ranges(cx),
4190 [
4191 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4192 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4193 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4194 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4195 ]
4196 );
4197 });
4198
4199 _ = view.update(cx, |view, cx| {
4200 view.change_selections(None, cx, |s| {
4201 s.select_display_ranges([
4202 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4203 ])
4204 });
4205 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4206 assert_eq!(
4207 view.display_text(cx),
4208 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4209 );
4210 assert_eq!(
4211 view.selections.display_ranges(cx),
4212 [
4213 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4214 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4215 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4216 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4217 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4218 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4219 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4220 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4221 ]
4222 );
4223 });
4224}
4225
4226#[gpui::test]
4227async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4228 init_test(cx, |_| {});
4229
4230 let mut cx = EditorTestContext::new(cx).await;
4231
4232 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4233 cx.set_state(indoc!(
4234 r#"abc
4235 defˇghi
4236
4237 jk
4238 nlmo
4239 "#
4240 ));
4241
4242 cx.update_editor(|editor, cx| {
4243 editor.add_selection_above(&Default::default(), cx);
4244 });
4245
4246 cx.assert_editor_state(indoc!(
4247 r#"abcˇ
4248 defˇghi
4249
4250 jk
4251 nlmo
4252 "#
4253 ));
4254
4255 cx.update_editor(|editor, cx| {
4256 editor.add_selection_above(&Default::default(), cx);
4257 });
4258
4259 cx.assert_editor_state(indoc!(
4260 r#"abcˇ
4261 defˇghi
4262
4263 jk
4264 nlmo
4265 "#
4266 ));
4267
4268 cx.update_editor(|view, cx| {
4269 view.add_selection_below(&Default::default(), cx);
4270 });
4271
4272 cx.assert_editor_state(indoc!(
4273 r#"abc
4274 defˇghi
4275
4276 jk
4277 nlmo
4278 "#
4279 ));
4280
4281 cx.update_editor(|view, cx| {
4282 view.undo_selection(&Default::default(), cx);
4283 });
4284
4285 cx.assert_editor_state(indoc!(
4286 r#"abcˇ
4287 defˇghi
4288
4289 jk
4290 nlmo
4291 "#
4292 ));
4293
4294 cx.update_editor(|view, cx| {
4295 view.redo_selection(&Default::default(), cx);
4296 });
4297
4298 cx.assert_editor_state(indoc!(
4299 r#"abc
4300 defˇghi
4301
4302 jk
4303 nlmo
4304 "#
4305 ));
4306
4307 cx.update_editor(|view, cx| {
4308 view.add_selection_below(&Default::default(), cx);
4309 });
4310
4311 cx.assert_editor_state(indoc!(
4312 r#"abc
4313 defˇghi
4314
4315 jk
4316 nlmˇo
4317 "#
4318 ));
4319
4320 cx.update_editor(|view, cx| {
4321 view.add_selection_below(&Default::default(), cx);
4322 });
4323
4324 cx.assert_editor_state(indoc!(
4325 r#"abc
4326 defˇghi
4327
4328 jk
4329 nlmˇo
4330 "#
4331 ));
4332
4333 // change selections
4334 cx.set_state(indoc!(
4335 r#"abc
4336 def«ˇg»hi
4337
4338 jk
4339 nlmo
4340 "#
4341 ));
4342
4343 cx.update_editor(|view, cx| {
4344 view.add_selection_below(&Default::default(), cx);
4345 });
4346
4347 cx.assert_editor_state(indoc!(
4348 r#"abc
4349 def«ˇg»hi
4350
4351 jk
4352 nlm«ˇo»
4353 "#
4354 ));
4355
4356 cx.update_editor(|view, cx| {
4357 view.add_selection_below(&Default::default(), cx);
4358 });
4359
4360 cx.assert_editor_state(indoc!(
4361 r#"abc
4362 def«ˇg»hi
4363
4364 jk
4365 nlm«ˇo»
4366 "#
4367 ));
4368
4369 cx.update_editor(|view, cx| {
4370 view.add_selection_above(&Default::default(), cx);
4371 });
4372
4373 cx.assert_editor_state(indoc!(
4374 r#"abc
4375 def«ˇg»hi
4376
4377 jk
4378 nlmo
4379 "#
4380 ));
4381
4382 cx.update_editor(|view, cx| {
4383 view.add_selection_above(&Default::default(), cx);
4384 });
4385
4386 cx.assert_editor_state(indoc!(
4387 r#"abc
4388 def«ˇg»hi
4389
4390 jk
4391 nlmo
4392 "#
4393 ));
4394
4395 // Change selections again
4396 cx.set_state(indoc!(
4397 r#"a«bc
4398 defgˇ»hi
4399
4400 jk
4401 nlmo
4402 "#
4403 ));
4404
4405 cx.update_editor(|view, cx| {
4406 view.add_selection_below(&Default::default(), cx);
4407 });
4408
4409 cx.assert_editor_state(indoc!(
4410 r#"a«bcˇ»
4411 d«efgˇ»hi
4412
4413 j«kˇ»
4414 nlmo
4415 "#
4416 ));
4417
4418 cx.update_editor(|view, cx| {
4419 view.add_selection_below(&Default::default(), cx);
4420 });
4421 cx.assert_editor_state(indoc!(
4422 r#"a«bcˇ»
4423 d«efgˇ»hi
4424
4425 j«kˇ»
4426 n«lmoˇ»
4427 "#
4428 ));
4429 cx.update_editor(|view, cx| {
4430 view.add_selection_above(&Default::default(), cx);
4431 });
4432
4433 cx.assert_editor_state(indoc!(
4434 r#"a«bcˇ»
4435 d«efgˇ»hi
4436
4437 j«kˇ»
4438 nlmo
4439 "#
4440 ));
4441
4442 // Change selections again
4443 cx.set_state(indoc!(
4444 r#"abc
4445 d«ˇefghi
4446
4447 jk
4448 nlm»o
4449 "#
4450 ));
4451
4452 cx.update_editor(|view, cx| {
4453 view.add_selection_above(&Default::default(), cx);
4454 });
4455
4456 cx.assert_editor_state(indoc!(
4457 r#"a«ˇbc»
4458 d«ˇef»ghi
4459
4460 j«ˇk»
4461 n«ˇlm»o
4462 "#
4463 ));
4464
4465 cx.update_editor(|view, cx| {
4466 view.add_selection_below(&Default::default(), cx);
4467 });
4468
4469 cx.assert_editor_state(indoc!(
4470 r#"abc
4471 d«ˇef»ghi
4472
4473 j«ˇk»
4474 n«ˇlm»o
4475 "#
4476 ));
4477}
4478
4479#[gpui::test]
4480async fn test_select_next(cx: &mut gpui::TestAppContext) {
4481 init_test(cx, |_| {});
4482
4483 let mut cx = EditorTestContext::new(cx).await;
4484 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4485
4486 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4487 .unwrap();
4488 cx.assert_editor_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(|view, cx| view.undo_selection(&UndoSelection, cx));
4495 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4496
4497 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4498 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
4499
4500 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4501 .unwrap();
4502 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
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
4509#[gpui::test]
4510async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
4511 init_test(cx, |_| {});
4512
4513 let mut cx = EditorTestContext::new(cx).await;
4514 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4515
4516 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
4517 .unwrap();
4518 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
4519}
4520
4521#[gpui::test]
4522async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4523 init_test(cx, |_| {});
4524
4525 let mut cx = EditorTestContext::new(cx).await;
4526 cx.set_state(
4527 r#"let foo = 2;
4528lˇet foo = 2;
4529let fooˇ = 2;
4530let foo = 2;
4531let foo = ˇ2;"#,
4532 );
4533
4534 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4535 .unwrap();
4536 cx.assert_editor_state(
4537 r#"let foo = 2;
4538«letˇ» foo = 2;
4539let «fooˇ» = 2;
4540let foo = 2;
4541let foo = «2ˇ»;"#,
4542 );
4543
4544 // noop for multiple selections with different contents
4545 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
4546 .unwrap();
4547 cx.assert_editor_state(
4548 r#"let foo = 2;
4549«letˇ» foo = 2;
4550let «fooˇ» = 2;
4551let foo = 2;
4552let foo = «2ˇ»;"#,
4553 );
4554}
4555
4556#[gpui::test]
4557async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
4558 init_test(cx, |_| {});
4559
4560 let mut cx = EditorTestContext::new_multibuffer(
4561 cx,
4562 [
4563 &indoc! {
4564 "aaa\n«bbb\nccc\n»ddd"
4565 },
4566 &indoc! {
4567 "aaa\n«bbb\nccc\n»ddd"
4568 },
4569 ],
4570 );
4571
4572 cx.assert_editor_state(indoc! {"
4573 ˇbbb
4574 ccc
4575
4576 bbb
4577 ccc
4578 "});
4579 cx.dispatch_action(SelectPrevious::default());
4580 cx.assert_editor_state(indoc! {"
4581 «bbbˇ»
4582 ccc
4583
4584 bbb
4585 ccc
4586 "});
4587 cx.dispatch_action(SelectPrevious::default());
4588 cx.assert_editor_state(indoc! {"
4589 «bbbˇ»
4590 ccc
4591
4592 «bbbˇ»
4593 ccc
4594 "});
4595}
4596
4597#[gpui::test]
4598async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
4599 init_test(cx, |_| {});
4600
4601 let mut cx = EditorTestContext::new(cx).await;
4602 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
4603
4604 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4605 .unwrap();
4606 cx.assert_editor_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(|view, cx| view.undo_selection(&UndoSelection, cx));
4613 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
4614
4615 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4616 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
4617
4618 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4619 .unwrap();
4620 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
4621
4622 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4623 .unwrap();
4624 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\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
4631#[gpui::test]
4632async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
4633 init_test(cx, |_| {});
4634
4635 let mut cx = EditorTestContext::new(cx).await;
4636 cx.set_state(
4637 r#"let foo = 2;
4638lˇet foo = 2;
4639let fooˇ = 2;
4640let foo = 2;
4641let foo = ˇ2;"#,
4642 );
4643
4644 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4645 .unwrap();
4646 cx.assert_editor_state(
4647 r#"let foo = 2;
4648«letˇ» foo = 2;
4649let «fooˇ» = 2;
4650let foo = 2;
4651let foo = «2ˇ»;"#,
4652 );
4653
4654 // noop for multiple selections with different contents
4655 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4656 .unwrap();
4657 cx.assert_editor_state(
4658 r#"let foo = 2;
4659«letˇ» foo = 2;
4660let «fooˇ» = 2;
4661let foo = 2;
4662let foo = «2ˇ»;"#,
4663 );
4664}
4665
4666#[gpui::test]
4667async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
4668 init_test(cx, |_| {});
4669
4670 let mut cx = EditorTestContext::new(cx).await;
4671 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
4672
4673 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4674 .unwrap();
4675 cx.assert_editor_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\n«abcˇ»");
4680
4681 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
4682 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
4683
4684 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
4685 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
4686
4687 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
4688 .unwrap();
4689 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\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
4696#[gpui::test]
4697async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
4698 init_test(cx, |_| {});
4699
4700 let language = Arc::new(Language::new(
4701 LanguageConfig::default(),
4702 Some(tree_sitter_rust::language()),
4703 ));
4704
4705 let text = r#"
4706 use mod1::mod2::{mod3, mod4};
4707
4708 fn fn_1(param1: bool, param2: &str) {
4709 let var1 = "text";
4710 }
4711 "#
4712 .unindent();
4713
4714 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
4715 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4716 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4717
4718 view.condition::<crate::EditorEvent>(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
4719 .await;
4720
4721 _ = view.update(cx, |view, cx| {
4722 view.change_selections(None, cx, |s| {
4723 s.select_display_ranges([
4724 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
4725 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
4726 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
4727 ]);
4728 });
4729 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4730 });
4731 assert_eq!(
4732 view.update(cx, |view, cx| { view.selections.display_ranges(cx) }),
4733 &[
4734 DisplayPoint::new(DisplayRow(0), 23)..DisplayPoint::new(DisplayRow(0), 27),
4735 DisplayPoint::new(DisplayRow(2), 35)..DisplayPoint::new(DisplayRow(2), 7),
4736 DisplayPoint::new(DisplayRow(3), 15)..DisplayPoint::new(DisplayRow(3), 21),
4737 ]
4738 );
4739
4740 _ = view.update(cx, |view, cx| {
4741 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4742 });
4743 assert_eq!(
4744 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4745 &[
4746 DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 28),
4747 DisplayPoint::new(DisplayRow(4), 1)..DisplayPoint::new(DisplayRow(2), 0),
4748 ]
4749 );
4750
4751 _ = view.update(cx, |view, cx| {
4752 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4753 });
4754 assert_eq!(
4755 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4756 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
4757 );
4758
4759 // Trying to expand the selected syntax node one more time has no effect.
4760 _ = view.update(cx, |view, cx| {
4761 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4762 });
4763 assert_eq!(
4764 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4765 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
4766 );
4767
4768 _ = view.update(cx, |view, cx| {
4769 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4770 });
4771 assert_eq!(
4772 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4773 &[
4774 DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 28),
4775 DisplayPoint::new(DisplayRow(4), 1)..DisplayPoint::new(DisplayRow(2), 0),
4776 ]
4777 );
4778
4779 _ = view.update(cx, |view, cx| {
4780 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4781 });
4782 assert_eq!(
4783 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4784 &[
4785 DisplayPoint::new(DisplayRow(0), 23)..DisplayPoint::new(DisplayRow(0), 27),
4786 DisplayPoint::new(DisplayRow(2), 35)..DisplayPoint::new(DisplayRow(2), 7),
4787 DisplayPoint::new(DisplayRow(3), 15)..DisplayPoint::new(DisplayRow(3), 21),
4788 ]
4789 );
4790
4791 _ = view.update(cx, |view, cx| {
4792 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4793 });
4794 assert_eq!(
4795 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4796 &[
4797 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
4798 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
4799 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
4800 ]
4801 );
4802
4803 // Trying to shrink the selected syntax node one more time has no effect.
4804 _ = view.update(cx, |view, cx| {
4805 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
4806 });
4807 assert_eq!(
4808 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4809 &[
4810 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
4811 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
4812 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
4813 ]
4814 );
4815
4816 // Ensure that we keep expanding the selection if the larger selection starts or ends within
4817 // a fold.
4818 _ = view.update(cx, |view, cx| {
4819 view.fold_ranges(
4820 vec![
4821 (
4822 Point::new(0, 21)..Point::new(0, 24),
4823 FoldPlaceholder::test(),
4824 ),
4825 (
4826 Point::new(3, 20)..Point::new(3, 22),
4827 FoldPlaceholder::test(),
4828 ),
4829 ],
4830 true,
4831 cx,
4832 );
4833 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
4834 });
4835 assert_eq!(
4836 view.update(cx, |view, cx| view.selections.display_ranges(cx)),
4837 &[
4838 DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 28),
4839 DisplayPoint::new(DisplayRow(2), 35)..DisplayPoint::new(DisplayRow(2), 7),
4840 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(3), 23),
4841 ]
4842 );
4843}
4844
4845#[gpui::test]
4846async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
4847 init_test(cx, |_| {});
4848
4849 let language = Arc::new(
4850 Language::new(
4851 LanguageConfig {
4852 brackets: BracketPairConfig {
4853 pairs: vec![
4854 BracketPair {
4855 start: "{".to_string(),
4856 end: "}".to_string(),
4857 close: false,
4858 surround: false,
4859 newline: true,
4860 },
4861 BracketPair {
4862 start: "(".to_string(),
4863 end: ")".to_string(),
4864 close: false,
4865 surround: false,
4866 newline: true,
4867 },
4868 ],
4869 ..Default::default()
4870 },
4871 ..Default::default()
4872 },
4873 Some(tree_sitter_rust::language()),
4874 )
4875 .with_indents_query(
4876 r#"
4877 (_ "(" ")" @end) @indent
4878 (_ "{" "}" @end) @indent
4879 "#,
4880 )
4881 .unwrap(),
4882 );
4883
4884 let text = "fn a() {}";
4885
4886 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
4887 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
4888 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
4889 editor
4890 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
4891 .await;
4892
4893 _ = editor.update(cx, |editor, cx| {
4894 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
4895 editor.newline(&Newline, cx);
4896 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
4897 assert_eq!(
4898 editor.selections.ranges(cx),
4899 &[
4900 Point::new(1, 4)..Point::new(1, 4),
4901 Point::new(3, 4)..Point::new(3, 4),
4902 Point::new(5, 0)..Point::new(5, 0)
4903 ]
4904 );
4905 });
4906}
4907
4908#[gpui::test]
4909async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
4910 init_test(cx, |_| {});
4911
4912 let mut cx = EditorTestContext::new(cx).await;
4913
4914 let language = Arc::new(Language::new(
4915 LanguageConfig {
4916 brackets: BracketPairConfig {
4917 pairs: vec![
4918 BracketPair {
4919 start: "{".to_string(),
4920 end: "}".to_string(),
4921 close: true,
4922 surround: true,
4923 newline: true,
4924 },
4925 BracketPair {
4926 start: "(".to_string(),
4927 end: ")".to_string(),
4928 close: true,
4929 surround: true,
4930 newline: true,
4931 },
4932 BracketPair {
4933 start: "/*".to_string(),
4934 end: " */".to_string(),
4935 close: true,
4936 surround: true,
4937 newline: true,
4938 },
4939 BracketPair {
4940 start: "[".to_string(),
4941 end: "]".to_string(),
4942 close: false,
4943 surround: false,
4944 newline: true,
4945 },
4946 BracketPair {
4947 start: "\"".to_string(),
4948 end: "\"".to_string(),
4949 close: true,
4950 surround: true,
4951 newline: false,
4952 },
4953 BracketPair {
4954 start: "<".to_string(),
4955 end: ">".to_string(),
4956 close: false,
4957 surround: true,
4958 newline: true,
4959 },
4960 ],
4961 ..Default::default()
4962 },
4963 autoclose_before: "})]".to_string(),
4964 ..Default::default()
4965 },
4966 Some(tree_sitter_rust::language()),
4967 ));
4968
4969 cx.language_registry().add(language.clone());
4970 cx.update_buffer(|buffer, cx| {
4971 buffer.set_language(Some(language), cx);
4972 });
4973
4974 cx.set_state(
4975 &r#"
4976 🏀ˇ
4977 εˇ
4978 ❤️ˇ
4979 "#
4980 .unindent(),
4981 );
4982
4983 // autoclose multiple nested brackets at multiple cursors
4984 cx.update_editor(|view, cx| {
4985 view.handle_input("{", cx);
4986 view.handle_input("{", cx);
4987 view.handle_input("{", cx);
4988 });
4989 cx.assert_editor_state(
4990 &"
4991 🏀{{{ˇ}}}
4992 ε{{{ˇ}}}
4993 ❤️{{{ˇ}}}
4994 "
4995 .unindent(),
4996 );
4997
4998 // insert a different closing bracket
4999 cx.update_editor(|view, cx| {
5000 view.handle_input(")", cx);
5001 });
5002 cx.assert_editor_state(
5003 &"
5004 🏀{{{)ˇ}}}
5005 ε{{{)ˇ}}}
5006 ❤️{{{)ˇ}}}
5007 "
5008 .unindent(),
5009 );
5010
5011 // skip over the auto-closed brackets when typing a closing bracket
5012 cx.update_editor(|view, cx| {
5013 view.move_right(&MoveRight, cx);
5014 view.handle_input("}", cx);
5015 view.handle_input("}", cx);
5016 view.handle_input("}", cx);
5017 });
5018 cx.assert_editor_state(
5019 &"
5020 🏀{{{)}}}}ˇ
5021 ε{{{)}}}}ˇ
5022 ❤️{{{)}}}}ˇ
5023 "
5024 .unindent(),
5025 );
5026
5027 // autoclose multi-character pairs
5028 cx.set_state(
5029 &"
5030 ˇ
5031 ˇ
5032 "
5033 .unindent(),
5034 );
5035 cx.update_editor(|view, cx| {
5036 view.handle_input("/", cx);
5037 view.handle_input("*", cx);
5038 });
5039 cx.assert_editor_state(
5040 &"
5041 /*ˇ */
5042 /*ˇ */
5043 "
5044 .unindent(),
5045 );
5046
5047 // one cursor autocloses a multi-character pair, one cursor
5048 // does not autoclose.
5049 cx.set_state(
5050 &"
5051 /ˇ
5052 ˇ
5053 "
5054 .unindent(),
5055 );
5056 cx.update_editor(|view, cx| view.handle_input("*", cx));
5057 cx.assert_editor_state(
5058 &"
5059 /*ˇ */
5060 *ˇ
5061 "
5062 .unindent(),
5063 );
5064
5065 // Don't autoclose if the next character isn't whitespace and isn't
5066 // listed in the language's "autoclose_before" section.
5067 cx.set_state("ˇa b");
5068 cx.update_editor(|view, cx| view.handle_input("{", cx));
5069 cx.assert_editor_state("{ˇa b");
5070
5071 // Don't autoclose if `close` is false for the bracket pair
5072 cx.set_state("ˇ");
5073 cx.update_editor(|view, cx| view.handle_input("[", cx));
5074 cx.assert_editor_state("[ˇ");
5075
5076 // Surround with brackets if text is selected
5077 cx.set_state("«aˇ» b");
5078 cx.update_editor(|view, cx| view.handle_input("{", cx));
5079 cx.assert_editor_state("{«aˇ»} b");
5080
5081 // Autclose pair where the start and end characters are the same
5082 cx.set_state("aˇ");
5083 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5084 cx.assert_editor_state("a\"ˇ\"");
5085 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5086 cx.assert_editor_state("a\"\"ˇ");
5087
5088 // Don't autoclose pair if autoclose is disabled
5089 cx.set_state("ˇ");
5090 cx.update_editor(|view, cx| view.handle_input("<", cx));
5091 cx.assert_editor_state("<ˇ");
5092
5093 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5094 cx.set_state("«aˇ» b");
5095 cx.update_editor(|view, cx| view.handle_input("<", cx));
5096 cx.assert_editor_state("<«aˇ»> b");
5097}
5098
5099#[gpui::test]
5100async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5101 init_test(cx, |settings| {
5102 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5103 });
5104
5105 let mut cx = EditorTestContext::new(cx).await;
5106
5107 let language = Arc::new(Language::new(
5108 LanguageConfig {
5109 brackets: BracketPairConfig {
5110 pairs: vec![
5111 BracketPair {
5112 start: "{".to_string(),
5113 end: "}".to_string(),
5114 close: true,
5115 surround: true,
5116 newline: true,
5117 },
5118 BracketPair {
5119 start: "(".to_string(),
5120 end: ")".to_string(),
5121 close: true,
5122 surround: true,
5123 newline: true,
5124 },
5125 BracketPair {
5126 start: "[".to_string(),
5127 end: "]".to_string(),
5128 close: false,
5129 surround: false,
5130 newline: true,
5131 },
5132 ],
5133 ..Default::default()
5134 },
5135 autoclose_before: "})]".to_string(),
5136 ..Default::default()
5137 },
5138 Some(tree_sitter_rust::language()),
5139 ));
5140
5141 cx.language_registry().add(language.clone());
5142 cx.update_buffer(|buffer, cx| {
5143 buffer.set_language(Some(language), cx);
5144 });
5145
5146 cx.set_state(
5147 &"
5148 ˇ
5149 ˇ
5150 ˇ
5151 "
5152 .unindent(),
5153 );
5154
5155 // ensure only matching closing brackets are skipped over
5156 cx.update_editor(|view, cx| {
5157 view.handle_input("}", cx);
5158 view.move_left(&MoveLeft, cx);
5159 view.handle_input(")", cx);
5160 view.move_left(&MoveLeft, cx);
5161 });
5162 cx.assert_editor_state(
5163 &"
5164 ˇ)}
5165 ˇ)}
5166 ˇ)}
5167 "
5168 .unindent(),
5169 );
5170
5171 // skip-over closing brackets at multiple cursors
5172 cx.update_editor(|view, cx| {
5173 view.handle_input(")", cx);
5174 view.handle_input("}", cx);
5175 });
5176 cx.assert_editor_state(
5177 &"
5178 )}ˇ
5179 )}ˇ
5180 )}ˇ
5181 "
5182 .unindent(),
5183 );
5184
5185 // ignore non-close brackets
5186 cx.update_editor(|view, cx| {
5187 view.handle_input("]", cx);
5188 view.move_left(&MoveLeft, cx);
5189 view.handle_input("]", cx);
5190 });
5191 cx.assert_editor_state(
5192 &"
5193 )}]ˇ]
5194 )}]ˇ]
5195 )}]ˇ]
5196 "
5197 .unindent(),
5198 );
5199}
5200
5201#[gpui::test]
5202async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5203 init_test(cx, |_| {});
5204
5205 let mut cx = EditorTestContext::new(cx).await;
5206
5207 let html_language = Arc::new(
5208 Language::new(
5209 LanguageConfig {
5210 name: "HTML".into(),
5211 brackets: BracketPairConfig {
5212 pairs: vec![
5213 BracketPair {
5214 start: "<".into(),
5215 end: ">".into(),
5216 close: true,
5217 ..Default::default()
5218 },
5219 BracketPair {
5220 start: "{".into(),
5221 end: "}".into(),
5222 close: true,
5223 ..Default::default()
5224 },
5225 BracketPair {
5226 start: "(".into(),
5227 end: ")".into(),
5228 close: true,
5229 ..Default::default()
5230 },
5231 ],
5232 ..Default::default()
5233 },
5234 autoclose_before: "})]>".into(),
5235 ..Default::default()
5236 },
5237 Some(tree_sitter_html::language()),
5238 )
5239 .with_injection_query(
5240 r#"
5241 (script_element
5242 (raw_text) @content
5243 (#set! "language" "javascript"))
5244 "#,
5245 )
5246 .unwrap(),
5247 );
5248
5249 let javascript_language = Arc::new(Language::new(
5250 LanguageConfig {
5251 name: "JavaScript".into(),
5252 brackets: BracketPairConfig {
5253 pairs: vec![
5254 BracketPair {
5255 start: "/*".into(),
5256 end: " */".into(),
5257 close: true,
5258 ..Default::default()
5259 },
5260 BracketPair {
5261 start: "{".into(),
5262 end: "}".into(),
5263 close: true,
5264 ..Default::default()
5265 },
5266 BracketPair {
5267 start: "(".into(),
5268 end: ")".into(),
5269 close: true,
5270 ..Default::default()
5271 },
5272 ],
5273 ..Default::default()
5274 },
5275 autoclose_before: "})]>".into(),
5276 ..Default::default()
5277 },
5278 Some(tree_sitter_typescript::language_tsx()),
5279 ));
5280
5281 cx.language_registry().add(html_language.clone());
5282 cx.language_registry().add(javascript_language.clone());
5283
5284 cx.update_buffer(|buffer, cx| {
5285 buffer.set_language(Some(html_language), cx);
5286 });
5287
5288 cx.set_state(
5289 &r#"
5290 <body>ˇ
5291 <script>
5292 var x = 1;ˇ
5293 </script>
5294 </body>ˇ
5295 "#
5296 .unindent(),
5297 );
5298
5299 // Precondition: different languages are active at different locations.
5300 cx.update_editor(|editor, cx| {
5301 let snapshot = editor.snapshot(cx);
5302 let cursors = editor.selections.ranges::<usize>(cx);
5303 let languages = cursors
5304 .iter()
5305 .map(|c| snapshot.language_at(c.start).unwrap().name())
5306 .collect::<Vec<_>>();
5307 assert_eq!(
5308 languages,
5309 &["HTML".into(), "JavaScript".into(), "HTML".into()]
5310 );
5311 });
5312
5313 // Angle brackets autoclose in HTML, but not JavaScript.
5314 cx.update_editor(|editor, cx| {
5315 editor.handle_input("<", cx);
5316 editor.handle_input("a", cx);
5317 });
5318 cx.assert_editor_state(
5319 &r#"
5320 <body><aˇ>
5321 <script>
5322 var x = 1;<aˇ
5323 </script>
5324 </body><aˇ>
5325 "#
5326 .unindent(),
5327 );
5328
5329 // Curly braces and parens autoclose in both HTML and JavaScript.
5330 cx.update_editor(|editor, cx| {
5331 editor.handle_input(" b=", cx);
5332 editor.handle_input("{", cx);
5333 editor.handle_input("c", cx);
5334 editor.handle_input("(", cx);
5335 });
5336 cx.assert_editor_state(
5337 &r#"
5338 <body><a b={c(ˇ)}>
5339 <script>
5340 var x = 1;<a b={c(ˇ)}
5341 </script>
5342 </body><a b={c(ˇ)}>
5343 "#
5344 .unindent(),
5345 );
5346
5347 // Brackets that were already autoclosed are skipped.
5348 cx.update_editor(|editor, cx| {
5349 editor.handle_input(")", cx);
5350 editor.handle_input("d", cx);
5351 editor.handle_input("}", cx);
5352 });
5353 cx.assert_editor_state(
5354 &r#"
5355 <body><a b={c()d}ˇ>
5356 <script>
5357 var x = 1;<a b={c()d}ˇ
5358 </script>
5359 </body><a b={c()d}ˇ>
5360 "#
5361 .unindent(),
5362 );
5363 cx.update_editor(|editor, cx| {
5364 editor.handle_input(">", cx);
5365 });
5366 cx.assert_editor_state(
5367 &r#"
5368 <body><a b={c()d}>ˇ
5369 <script>
5370 var x = 1;<a b={c()d}>ˇ
5371 </script>
5372 </body><a b={c()d}>ˇ
5373 "#
5374 .unindent(),
5375 );
5376
5377 // Reset
5378 cx.set_state(
5379 &r#"
5380 <body>ˇ
5381 <script>
5382 var x = 1;ˇ
5383 </script>
5384 </body>ˇ
5385 "#
5386 .unindent(),
5387 );
5388
5389 cx.update_editor(|editor, cx| {
5390 editor.handle_input("<", cx);
5391 });
5392 cx.assert_editor_state(
5393 &r#"
5394 <body><ˇ>
5395 <script>
5396 var x = 1;<ˇ
5397 </script>
5398 </body><ˇ>
5399 "#
5400 .unindent(),
5401 );
5402
5403 // When backspacing, the closing angle brackets are removed.
5404 cx.update_editor(|editor, cx| {
5405 editor.backspace(&Backspace, cx);
5406 });
5407 cx.assert_editor_state(
5408 &r#"
5409 <body>ˇ
5410 <script>
5411 var x = 1;ˇ
5412 </script>
5413 </body>ˇ
5414 "#
5415 .unindent(),
5416 );
5417
5418 // Block comments autoclose in JavaScript, but not HTML.
5419 cx.update_editor(|editor, cx| {
5420 editor.handle_input("/", cx);
5421 editor.handle_input("*", cx);
5422 });
5423 cx.assert_editor_state(
5424 &r#"
5425 <body>/*ˇ
5426 <script>
5427 var x = 1;/*ˇ */
5428 </script>
5429 </body>/*ˇ
5430 "#
5431 .unindent(),
5432 );
5433}
5434
5435#[gpui::test]
5436async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
5437 init_test(cx, |_| {});
5438
5439 let mut cx = EditorTestContext::new(cx).await;
5440
5441 let rust_language = Arc::new(
5442 Language::new(
5443 LanguageConfig {
5444 name: "Rust".into(),
5445 brackets: serde_json::from_value(json!([
5446 { "start": "{", "end": "}", "close": true, "newline": true },
5447 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
5448 ]))
5449 .unwrap(),
5450 autoclose_before: "})]>".into(),
5451 ..Default::default()
5452 },
5453 Some(tree_sitter_rust::language()),
5454 )
5455 .with_override_query("(string_literal) @string")
5456 .unwrap(),
5457 );
5458
5459 cx.language_registry().add(rust_language.clone());
5460 cx.update_buffer(|buffer, cx| {
5461 buffer.set_language(Some(rust_language), cx);
5462 });
5463
5464 cx.set_state(
5465 &r#"
5466 let x = ˇ
5467 "#
5468 .unindent(),
5469 );
5470
5471 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
5472 cx.update_editor(|editor, cx| {
5473 editor.handle_input("\"", cx);
5474 });
5475 cx.assert_editor_state(
5476 &r#"
5477 let x = "ˇ"
5478 "#
5479 .unindent(),
5480 );
5481
5482 // Inserting another quotation mark. The cursor moves across the existing
5483 // automatically-inserted quotation mark.
5484 cx.update_editor(|editor, cx| {
5485 editor.handle_input("\"", cx);
5486 });
5487 cx.assert_editor_state(
5488 &r#"
5489 let x = ""ˇ
5490 "#
5491 .unindent(),
5492 );
5493
5494 // Reset
5495 cx.set_state(
5496 &r#"
5497 let x = ˇ
5498 "#
5499 .unindent(),
5500 );
5501
5502 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
5503 cx.update_editor(|editor, cx| {
5504 editor.handle_input("\"", cx);
5505 editor.handle_input(" ", cx);
5506 editor.move_left(&Default::default(), cx);
5507 editor.handle_input("\\", cx);
5508 editor.handle_input("\"", cx);
5509 });
5510 cx.assert_editor_state(
5511 &r#"
5512 let x = "\"ˇ "
5513 "#
5514 .unindent(),
5515 );
5516
5517 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
5518 // mark. Nothing is inserted.
5519 cx.update_editor(|editor, cx| {
5520 editor.move_right(&Default::default(), cx);
5521 editor.handle_input("\"", cx);
5522 });
5523 cx.assert_editor_state(
5524 &r#"
5525 let x = "\" "ˇ
5526 "#
5527 .unindent(),
5528 );
5529}
5530
5531#[gpui::test]
5532async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
5533 init_test(cx, |_| {});
5534
5535 let language = Arc::new(Language::new(
5536 LanguageConfig {
5537 brackets: BracketPairConfig {
5538 pairs: vec![
5539 BracketPair {
5540 start: "{".to_string(),
5541 end: "}".to_string(),
5542 close: true,
5543 surround: true,
5544 newline: true,
5545 },
5546 BracketPair {
5547 start: "/* ".to_string(),
5548 end: "*/".to_string(),
5549 close: true,
5550 surround: true,
5551 ..Default::default()
5552 },
5553 ],
5554 ..Default::default()
5555 },
5556 ..Default::default()
5557 },
5558 Some(tree_sitter_rust::language()),
5559 ));
5560
5561 let text = r#"
5562 a
5563 b
5564 c
5565 "#
5566 .unindent();
5567
5568 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5569 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5570 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5571 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5572 .await;
5573
5574 _ = view.update(cx, |view, cx| {
5575 view.change_selections(None, cx, |s| {
5576 s.select_display_ranges([
5577 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5578 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5579 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
5580 ])
5581 });
5582
5583 view.handle_input("{", cx);
5584 view.handle_input("{", cx);
5585 view.handle_input("{", cx);
5586 assert_eq!(
5587 view.text(cx),
5588 "
5589 {{{a}}}
5590 {{{b}}}
5591 {{{c}}}
5592 "
5593 .unindent()
5594 );
5595 assert_eq!(
5596 view.selections.display_ranges(cx),
5597 [
5598 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
5599 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
5600 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
5601 ]
5602 );
5603
5604 view.undo(&Undo, cx);
5605 view.undo(&Undo, cx);
5606 view.undo(&Undo, cx);
5607 assert_eq!(
5608 view.text(cx),
5609 "
5610 a
5611 b
5612 c
5613 "
5614 .unindent()
5615 );
5616 assert_eq!(
5617 view.selections.display_ranges(cx),
5618 [
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 // Ensure inserting the first character of a multi-byte bracket pair
5626 // doesn't surround the selections with the bracket.
5627 view.handle_input("/", cx);
5628 assert_eq!(
5629 view.text(cx),
5630 "
5631 /
5632 /
5633 /
5634 "
5635 .unindent()
5636 );
5637 assert_eq!(
5638 view.selections.display_ranges(cx),
5639 [
5640 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
5641 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
5642 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
5643 ]
5644 );
5645
5646 view.undo(&Undo, cx);
5647 assert_eq!(
5648 view.text(cx),
5649 "
5650 a
5651 b
5652 c
5653 "
5654 .unindent()
5655 );
5656 assert_eq!(
5657 view.selections.display_ranges(cx),
5658 [
5659 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5660 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
5661 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
5662 ]
5663 );
5664
5665 // Ensure inserting the last character of a multi-byte bracket pair
5666 // doesn't surround the selections with the bracket.
5667 view.handle_input("*", cx);
5668 assert_eq!(
5669 view.text(cx),
5670 "
5671 *
5672 *
5673 *
5674 "
5675 .unindent()
5676 );
5677 assert_eq!(
5678 view.selections.display_ranges(cx),
5679 [
5680 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
5681 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
5682 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
5683 ]
5684 );
5685 });
5686}
5687
5688#[gpui::test]
5689async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
5690 init_test(cx, |_| {});
5691
5692 let language = Arc::new(Language::new(
5693 LanguageConfig {
5694 brackets: BracketPairConfig {
5695 pairs: vec![BracketPair {
5696 start: "{".to_string(),
5697 end: "}".to_string(),
5698 close: true,
5699 surround: true,
5700 newline: true,
5701 }],
5702 ..Default::default()
5703 },
5704 autoclose_before: "}".to_string(),
5705 ..Default::default()
5706 },
5707 Some(tree_sitter_rust::language()),
5708 ));
5709
5710 let text = r#"
5711 a
5712 b
5713 c
5714 "#
5715 .unindent();
5716
5717 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5718 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5719 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5720 editor
5721 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5722 .await;
5723
5724 _ = editor.update(cx, |editor, cx| {
5725 editor.change_selections(None, cx, |s| {
5726 s.select_ranges([
5727 Point::new(0, 1)..Point::new(0, 1),
5728 Point::new(1, 1)..Point::new(1, 1),
5729 Point::new(2, 1)..Point::new(2, 1),
5730 ])
5731 });
5732
5733 editor.handle_input("{", cx);
5734 editor.handle_input("{", cx);
5735 editor.handle_input("_", cx);
5736 assert_eq!(
5737 editor.text(cx),
5738 "
5739 a{{_}}
5740 b{{_}}
5741 c{{_}}
5742 "
5743 .unindent()
5744 );
5745 assert_eq!(
5746 editor.selections.ranges::<Point>(cx),
5747 [
5748 Point::new(0, 4)..Point::new(0, 4),
5749 Point::new(1, 4)..Point::new(1, 4),
5750 Point::new(2, 4)..Point::new(2, 4)
5751 ]
5752 );
5753
5754 editor.backspace(&Default::default(), cx);
5755 editor.backspace(&Default::default(), cx);
5756 assert_eq!(
5757 editor.text(cx),
5758 "
5759 a{}
5760 b{}
5761 c{}
5762 "
5763 .unindent()
5764 );
5765 assert_eq!(
5766 editor.selections.ranges::<Point>(cx),
5767 [
5768 Point::new(0, 2)..Point::new(0, 2),
5769 Point::new(1, 2)..Point::new(1, 2),
5770 Point::new(2, 2)..Point::new(2, 2)
5771 ]
5772 );
5773
5774 editor.delete_to_previous_word_start(&Default::default(), cx);
5775 assert_eq!(
5776 editor.text(cx),
5777 "
5778 a
5779 b
5780 c
5781 "
5782 .unindent()
5783 );
5784 assert_eq!(
5785 editor.selections.ranges::<Point>(cx),
5786 [
5787 Point::new(0, 1)..Point::new(0, 1),
5788 Point::new(1, 1)..Point::new(1, 1),
5789 Point::new(2, 1)..Point::new(2, 1)
5790 ]
5791 );
5792 });
5793}
5794
5795#[gpui::test]
5796async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
5797 init_test(cx, |settings| {
5798 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5799 });
5800
5801 let mut cx = EditorTestContext::new(cx).await;
5802
5803 let language = Arc::new(Language::new(
5804 LanguageConfig {
5805 brackets: BracketPairConfig {
5806 pairs: vec![
5807 BracketPair {
5808 start: "{".to_string(),
5809 end: "}".to_string(),
5810 close: true,
5811 surround: true,
5812 newline: true,
5813 },
5814 BracketPair {
5815 start: "(".to_string(),
5816 end: ")".to_string(),
5817 close: true,
5818 surround: true,
5819 newline: true,
5820 },
5821 BracketPair {
5822 start: "[".to_string(),
5823 end: "]".to_string(),
5824 close: false,
5825 surround: true,
5826 newline: true,
5827 },
5828 ],
5829 ..Default::default()
5830 },
5831 autoclose_before: "})]".to_string(),
5832 ..Default::default()
5833 },
5834 Some(tree_sitter_rust::language()),
5835 ));
5836
5837 cx.language_registry().add(language.clone());
5838 cx.update_buffer(|buffer, cx| {
5839 buffer.set_language(Some(language), cx);
5840 });
5841
5842 cx.set_state(
5843 &"
5844 {(ˇ)}
5845 [[ˇ]]
5846 {(ˇ)}
5847 "
5848 .unindent(),
5849 );
5850
5851 cx.update_editor(|view, cx| {
5852 view.backspace(&Default::default(), cx);
5853 view.backspace(&Default::default(), cx);
5854 });
5855
5856 cx.assert_editor_state(
5857 &"
5858 ˇ
5859 ˇ]]
5860 ˇ
5861 "
5862 .unindent(),
5863 );
5864
5865 cx.update_editor(|view, cx| {
5866 view.handle_input("{", cx);
5867 view.handle_input("{", cx);
5868 view.move_right(&MoveRight, cx);
5869 view.move_right(&MoveRight, cx);
5870 view.move_left(&MoveLeft, cx);
5871 view.move_left(&MoveLeft, cx);
5872 view.backspace(&Default::default(), cx);
5873 });
5874
5875 cx.assert_editor_state(
5876 &"
5877 {ˇ}
5878 {ˇ}]]
5879 {ˇ}
5880 "
5881 .unindent(),
5882 );
5883
5884 cx.update_editor(|view, cx| {
5885 view.backspace(&Default::default(), cx);
5886 });
5887
5888 cx.assert_editor_state(
5889 &"
5890 ˇ
5891 ˇ]]
5892 ˇ
5893 "
5894 .unindent(),
5895 );
5896}
5897
5898#[gpui::test]
5899async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
5900 init_test(cx, |_| {});
5901
5902 let language = Arc::new(Language::new(
5903 LanguageConfig::default(),
5904 Some(tree_sitter_rust::language()),
5905 ));
5906
5907 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
5908 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5909 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5910 editor
5911 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5912 .await;
5913
5914 _ = editor.update(cx, |editor, cx| {
5915 editor.set_auto_replace_emoji_shortcode(true);
5916
5917 editor.handle_input("Hello ", cx);
5918 editor.handle_input(":wave", cx);
5919 assert_eq!(editor.text(cx), "Hello :wave".unindent());
5920
5921 editor.handle_input(":", cx);
5922 assert_eq!(editor.text(cx), "Hello 👋".unindent());
5923
5924 editor.handle_input(" :smile", cx);
5925 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
5926
5927 editor.handle_input(":", cx);
5928 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
5929
5930 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
5931 editor.handle_input(":wave", cx);
5932 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
5933
5934 editor.handle_input(":", cx);
5935 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
5936
5937 editor.handle_input(":1", cx);
5938 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
5939
5940 editor.handle_input(":", cx);
5941 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
5942
5943 // Ensure shortcode does not get replaced when it is part of a word
5944 editor.handle_input(" Test:wave", cx);
5945 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
5946
5947 editor.handle_input(":", cx);
5948 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
5949
5950 editor.set_auto_replace_emoji_shortcode(false);
5951
5952 // Ensure shortcode does not get replaced when auto replace is off
5953 editor.handle_input(" :wave", cx);
5954 assert_eq!(
5955 editor.text(cx),
5956 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
5957 );
5958
5959 editor.handle_input(":", cx);
5960 assert_eq!(
5961 editor.text(cx),
5962 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
5963 );
5964 });
5965}
5966
5967#[gpui::test]
5968async fn test_snippets(cx: &mut gpui::TestAppContext) {
5969 init_test(cx, |_| {});
5970
5971 let (text, insertion_ranges) = marked_text_ranges(
5972 indoc! {"
5973 a.ˇ b
5974 a.ˇ b
5975 a.ˇ b
5976 "},
5977 false,
5978 );
5979
5980 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
5981 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5982
5983 _ = editor.update(cx, |editor, cx| {
5984 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
5985
5986 editor
5987 .insert_snippet(&insertion_ranges, snippet, cx)
5988 .unwrap();
5989
5990 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
5991 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
5992 assert_eq!(editor.text(cx), expected_text);
5993 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
5994 }
5995
5996 assert(
5997 editor,
5998 cx,
5999 indoc! {"
6000 a.f(«one», two, «three») b
6001 a.f(«one», two, «three») b
6002 a.f(«one», two, «three») b
6003 "},
6004 );
6005
6006 // Can't move earlier than the first tab stop
6007 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6008 assert(
6009 editor,
6010 cx,
6011 indoc! {"
6012 a.f(«one», two, «three») b
6013 a.f(«one», two, «three») b
6014 a.f(«one», two, «three») b
6015 "},
6016 );
6017
6018 assert!(editor.move_to_next_snippet_tabstop(cx));
6019 assert(
6020 editor,
6021 cx,
6022 indoc! {"
6023 a.f(one, «two», three) b
6024 a.f(one, «two», three) b
6025 a.f(one, «two», three) b
6026 "},
6027 );
6028
6029 editor.move_to_prev_snippet_tabstop(cx);
6030 assert(
6031 editor,
6032 cx,
6033 indoc! {"
6034 a.f(«one», two, «three») b
6035 a.f(«one», two, «three») b
6036 a.f(«one», two, «three») b
6037 "},
6038 );
6039
6040 assert!(editor.move_to_next_snippet_tabstop(cx));
6041 assert(
6042 editor,
6043 cx,
6044 indoc! {"
6045 a.f(one, «two», three) b
6046 a.f(one, «two», three) b
6047 a.f(one, «two», three) b
6048 "},
6049 );
6050 assert!(editor.move_to_next_snippet_tabstop(cx));
6051 assert(
6052 editor,
6053 cx,
6054 indoc! {"
6055 a.f(one, two, three)ˇ b
6056 a.f(one, two, three)ˇ b
6057 a.f(one, two, three)ˇ b
6058 "},
6059 );
6060
6061 // As soon as the last tab stop is reached, snippet state is gone
6062 editor.move_to_prev_snippet_tabstop(cx);
6063 assert(
6064 editor,
6065 cx,
6066 indoc! {"
6067 a.f(one, two, three)ˇ b
6068 a.f(one, two, three)ˇ b
6069 a.f(one, two, three)ˇ b
6070 "},
6071 );
6072 });
6073}
6074
6075#[gpui::test]
6076async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6077 init_test(cx, |_| {});
6078
6079 let fs = FakeFs::new(cx.executor());
6080 fs.insert_file("/file.rs", Default::default()).await;
6081
6082 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6083
6084 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6085 language_registry.add(rust_lang());
6086 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6087 "Rust",
6088 FakeLspAdapter {
6089 capabilities: lsp::ServerCapabilities {
6090 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6091 ..Default::default()
6092 },
6093 ..Default::default()
6094 },
6095 );
6096
6097 let buffer = project
6098 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6099 .await
6100 .unwrap();
6101
6102 cx.executor().start_waiting();
6103 let fake_server = fake_servers.next().await.unwrap();
6104
6105 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6106 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6107 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6108 assert!(cx.read(|cx| editor.is_dirty(cx)));
6109
6110 let save = editor
6111 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6112 .unwrap();
6113 fake_server
6114 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6115 assert_eq!(
6116 params.text_document.uri,
6117 lsp::Url::from_file_path("/file.rs").unwrap()
6118 );
6119 assert_eq!(params.options.tab_size, 4);
6120 Ok(Some(vec![lsp::TextEdit::new(
6121 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6122 ", ".to_string(),
6123 )]))
6124 })
6125 .next()
6126 .await;
6127 cx.executor().start_waiting();
6128 save.await;
6129
6130 assert_eq!(
6131 editor.update(cx, |editor, cx| editor.text(cx)),
6132 "one, two\nthree\n"
6133 );
6134 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6135
6136 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6137 assert!(cx.read(|cx| editor.is_dirty(cx)));
6138
6139 // Ensure we can still save even if formatting hangs.
6140 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6141 assert_eq!(
6142 params.text_document.uri,
6143 lsp::Url::from_file_path("/file.rs").unwrap()
6144 );
6145 futures::future::pending::<()>().await;
6146 unreachable!()
6147 });
6148 let save = editor
6149 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6150 .unwrap();
6151 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6152 cx.executor().start_waiting();
6153 save.await;
6154 assert_eq!(
6155 editor.update(cx, |editor, cx| editor.text(cx)),
6156 "one\ntwo\nthree\n"
6157 );
6158 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6159
6160 // For non-dirty buffer, no formatting request should be sent
6161 let save = editor
6162 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6163 .unwrap();
6164 let _pending_format_request = fake_server
6165 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6166 panic!("Should not be invoked on non-dirty buffer");
6167 })
6168 .next();
6169 cx.executor().start_waiting();
6170 save.await;
6171
6172 // Set rust language override and assert overridden tabsize is sent to language server
6173 update_test_language_settings(cx, |settings| {
6174 settings.languages.insert(
6175 "Rust".into(),
6176 LanguageSettingsContent {
6177 tab_size: NonZeroU32::new(8),
6178 ..Default::default()
6179 },
6180 );
6181 });
6182
6183 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6184 assert!(cx.read(|cx| editor.is_dirty(cx)));
6185 let save = editor
6186 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6187 .unwrap();
6188 fake_server
6189 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6190 assert_eq!(
6191 params.text_document.uri,
6192 lsp::Url::from_file_path("/file.rs").unwrap()
6193 );
6194 assert_eq!(params.options.tab_size, 8);
6195 Ok(Some(vec![]))
6196 })
6197 .next()
6198 .await;
6199 cx.executor().start_waiting();
6200 save.await;
6201}
6202
6203#[gpui::test]
6204async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6205 init_test(cx, |_| {});
6206
6207 let cols = 4;
6208 let rows = 10;
6209 let sample_text_1 = sample_text(rows, cols, 'a');
6210 assert_eq!(
6211 sample_text_1,
6212 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6213 );
6214 let sample_text_2 = sample_text(rows, cols, 'l');
6215 assert_eq!(
6216 sample_text_2,
6217 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6218 );
6219 let sample_text_3 = sample_text(rows, cols, 'v');
6220 assert_eq!(
6221 sample_text_3,
6222 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6223 );
6224
6225 let fs = FakeFs::new(cx.executor());
6226 fs.insert_tree(
6227 "/a",
6228 json!({
6229 "main.rs": sample_text_1,
6230 "other.rs": sample_text_2,
6231 "lib.rs": sample_text_3,
6232 }),
6233 )
6234 .await;
6235
6236 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6237 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6238 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6239
6240 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6241 language_registry.add(rust_lang());
6242 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6243 "Rust",
6244 FakeLspAdapter {
6245 capabilities: lsp::ServerCapabilities {
6246 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6247 ..Default::default()
6248 },
6249 ..Default::default()
6250 },
6251 );
6252
6253 let worktree = project.update(cx, |project, _| {
6254 let mut worktrees = project.worktrees().collect::<Vec<_>>();
6255 assert_eq!(worktrees.len(), 1);
6256 worktrees.pop().unwrap()
6257 });
6258 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6259
6260 let buffer_1 = project
6261 .update(cx, |project, cx| {
6262 project.open_buffer((worktree_id, "main.rs"), cx)
6263 })
6264 .await
6265 .unwrap();
6266 let buffer_2 = project
6267 .update(cx, |project, cx| {
6268 project.open_buffer((worktree_id, "other.rs"), cx)
6269 })
6270 .await
6271 .unwrap();
6272 let buffer_3 = project
6273 .update(cx, |project, cx| {
6274 project.open_buffer((worktree_id, "lib.rs"), cx)
6275 })
6276 .await
6277 .unwrap();
6278
6279 let multi_buffer = cx.new_model(|cx| {
6280 let mut multi_buffer = MultiBuffer::new(0, ReadWrite);
6281 multi_buffer.push_excerpts(
6282 buffer_1.clone(),
6283 [
6284 ExcerptRange {
6285 context: Point::new(0, 0)..Point::new(3, 0),
6286 primary: None,
6287 },
6288 ExcerptRange {
6289 context: Point::new(5, 0)..Point::new(7, 0),
6290 primary: None,
6291 },
6292 ExcerptRange {
6293 context: Point::new(9, 0)..Point::new(10, 4),
6294 primary: None,
6295 },
6296 ],
6297 cx,
6298 );
6299 multi_buffer.push_excerpts(
6300 buffer_2.clone(),
6301 [
6302 ExcerptRange {
6303 context: Point::new(0, 0)..Point::new(3, 0),
6304 primary: None,
6305 },
6306 ExcerptRange {
6307 context: Point::new(5, 0)..Point::new(7, 0),
6308 primary: None,
6309 },
6310 ExcerptRange {
6311 context: Point::new(9, 0)..Point::new(10, 4),
6312 primary: None,
6313 },
6314 ],
6315 cx,
6316 );
6317 multi_buffer.push_excerpts(
6318 buffer_3.clone(),
6319 [
6320 ExcerptRange {
6321 context: Point::new(0, 0)..Point::new(3, 0),
6322 primary: None,
6323 },
6324 ExcerptRange {
6325 context: Point::new(5, 0)..Point::new(7, 0),
6326 primary: None,
6327 },
6328 ExcerptRange {
6329 context: Point::new(9, 0)..Point::new(10, 4),
6330 primary: None,
6331 },
6332 ],
6333 cx,
6334 );
6335 multi_buffer
6336 });
6337 let multi_buffer_editor = cx.new_view(|cx| {
6338 Editor::new(
6339 EditorMode::Full,
6340 multi_buffer,
6341 Some(project.clone()),
6342 true,
6343 cx,
6344 )
6345 });
6346
6347 multi_buffer_editor.update(cx, |editor, cx| {
6348 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
6349 editor.insert("|one|two|three|", cx);
6350 });
6351 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6352 multi_buffer_editor.update(cx, |editor, cx| {
6353 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
6354 s.select_ranges(Some(60..70))
6355 });
6356 editor.insert("|four|five|six|", cx);
6357 });
6358 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6359
6360 // First two buffers should be edited, but not the third one.
6361 assert_eq!(
6362 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6363 "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}",
6364 );
6365 buffer_1.update(cx, |buffer, _| {
6366 assert!(buffer.is_dirty());
6367 assert_eq!(
6368 buffer.text(),
6369 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
6370 )
6371 });
6372 buffer_2.update(cx, |buffer, _| {
6373 assert!(buffer.is_dirty());
6374 assert_eq!(
6375 buffer.text(),
6376 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
6377 )
6378 });
6379 buffer_3.update(cx, |buffer, _| {
6380 assert!(!buffer.is_dirty());
6381 assert_eq!(buffer.text(), sample_text_3,)
6382 });
6383
6384 cx.executor().start_waiting();
6385 let save = multi_buffer_editor
6386 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6387 .unwrap();
6388
6389 let fake_server = fake_servers.next().await.unwrap();
6390 fake_server
6391 .server
6392 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6393 Ok(Some(vec![lsp::TextEdit::new(
6394 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6395 format!("[{} formatted]", params.text_document.uri),
6396 )]))
6397 })
6398 .detach();
6399 save.await;
6400
6401 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
6402 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
6403 assert_eq!(
6404 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
6405 "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}",
6406 );
6407 buffer_1.update(cx, |buffer, _| {
6408 assert!(!buffer.is_dirty());
6409 assert_eq!(
6410 buffer.text(),
6411 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
6412 )
6413 });
6414 buffer_2.update(cx, |buffer, _| {
6415 assert!(!buffer.is_dirty());
6416 assert_eq!(
6417 buffer.text(),
6418 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
6419 )
6420 });
6421 buffer_3.update(cx, |buffer, _| {
6422 assert!(!buffer.is_dirty());
6423 assert_eq!(buffer.text(), sample_text_3,)
6424 });
6425}
6426
6427#[gpui::test]
6428async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
6429 init_test(cx, |_| {});
6430
6431 let fs = FakeFs::new(cx.executor());
6432 fs.insert_file("/file.rs", Default::default()).await;
6433
6434 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6435
6436 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6437 language_registry.add(rust_lang());
6438 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6439 "Rust",
6440 FakeLspAdapter {
6441 capabilities: lsp::ServerCapabilities {
6442 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
6443 ..Default::default()
6444 },
6445 ..Default::default()
6446 },
6447 );
6448
6449 let buffer = project
6450 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6451 .await
6452 .unwrap();
6453
6454 cx.executor().start_waiting();
6455 let fake_server = fake_servers.next().await.unwrap();
6456
6457 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6458 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6459 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6460 assert!(cx.read(|cx| editor.is_dirty(cx)));
6461
6462 let save = editor
6463 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6464 .unwrap();
6465 fake_server
6466 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6467 assert_eq!(
6468 params.text_document.uri,
6469 lsp::Url::from_file_path("/file.rs").unwrap()
6470 );
6471 assert_eq!(params.options.tab_size, 4);
6472 Ok(Some(vec![lsp::TextEdit::new(
6473 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6474 ", ".to_string(),
6475 )]))
6476 })
6477 .next()
6478 .await;
6479 cx.executor().start_waiting();
6480 save.await;
6481 assert_eq!(
6482 editor.update(cx, |editor, cx| editor.text(cx)),
6483 "one, two\nthree\n"
6484 );
6485 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6486
6487 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6488 assert!(cx.read(|cx| editor.is_dirty(cx)));
6489
6490 // Ensure we can still save even if formatting hangs.
6491 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
6492 move |params, _| async move {
6493 assert_eq!(
6494 params.text_document.uri,
6495 lsp::Url::from_file_path("/file.rs").unwrap()
6496 );
6497 futures::future::pending::<()>().await;
6498 unreachable!()
6499 },
6500 );
6501 let save = editor
6502 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6503 .unwrap();
6504 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6505 cx.executor().start_waiting();
6506 save.await;
6507 assert_eq!(
6508 editor.update(cx, |editor, cx| editor.text(cx)),
6509 "one\ntwo\nthree\n"
6510 );
6511 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6512
6513 // For non-dirty buffer, no formatting request should be sent
6514 let save = editor
6515 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6516 .unwrap();
6517 let _pending_format_request = fake_server
6518 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6519 panic!("Should not be invoked on non-dirty buffer");
6520 })
6521 .next();
6522 cx.executor().start_waiting();
6523 save.await;
6524
6525 // Set Rust language override and assert overridden tabsize is sent to language server
6526 update_test_language_settings(cx, |settings| {
6527 settings.languages.insert(
6528 "Rust".into(),
6529 LanguageSettingsContent {
6530 tab_size: NonZeroU32::new(8),
6531 ..Default::default()
6532 },
6533 );
6534 });
6535
6536 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6537 assert!(cx.read(|cx| editor.is_dirty(cx)));
6538 let save = editor
6539 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6540 .unwrap();
6541 fake_server
6542 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
6543 assert_eq!(
6544 params.text_document.uri,
6545 lsp::Url::from_file_path("/file.rs").unwrap()
6546 );
6547 assert_eq!(params.options.tab_size, 8);
6548 Ok(Some(vec![]))
6549 })
6550 .next()
6551 .await;
6552 cx.executor().start_waiting();
6553 save.await;
6554}
6555
6556#[gpui::test]
6557async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
6558 init_test(cx, |settings| {
6559 settings.defaults.formatter = Some(language_settings::Formatter::LanguageServer)
6560 });
6561
6562 let fs = FakeFs::new(cx.executor());
6563 fs.insert_file("/file.rs", Default::default()).await;
6564
6565 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6566
6567 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6568 language_registry.add(Arc::new(Language::new(
6569 LanguageConfig {
6570 name: "Rust".into(),
6571 matcher: LanguageMatcher {
6572 path_suffixes: vec!["rs".to_string()],
6573 ..Default::default()
6574 },
6575 ..LanguageConfig::default()
6576 },
6577 Some(tree_sitter_rust::language()),
6578 )));
6579 update_test_language_settings(cx, |settings| {
6580 // Enable Prettier formatting for the same buffer, and ensure
6581 // LSP is called instead of Prettier.
6582 settings.defaults.prettier = Some(PrettierSettings {
6583 allowed: true,
6584 ..PrettierSettings::default()
6585 });
6586 });
6587 let mut fake_servers = language_registry.register_fake_lsp_adapter(
6588 "Rust",
6589 FakeLspAdapter {
6590 capabilities: lsp::ServerCapabilities {
6591 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6592 ..Default::default()
6593 },
6594 ..Default::default()
6595 },
6596 );
6597
6598 let buffer = project
6599 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6600 .await
6601 .unwrap();
6602
6603 cx.executor().start_waiting();
6604 let fake_server = fake_servers.next().await.unwrap();
6605
6606 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6607 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6608 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6609
6610 let format = editor
6611 .update(cx, |editor, cx| {
6612 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
6613 })
6614 .unwrap();
6615 fake_server
6616 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6617 assert_eq!(
6618 params.text_document.uri,
6619 lsp::Url::from_file_path("/file.rs").unwrap()
6620 );
6621 assert_eq!(params.options.tab_size, 4);
6622 Ok(Some(vec![lsp::TextEdit::new(
6623 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6624 ", ".to_string(),
6625 )]))
6626 })
6627 .next()
6628 .await;
6629 cx.executor().start_waiting();
6630 format.await;
6631 assert_eq!(
6632 editor.update(cx, |editor, cx| editor.text(cx)),
6633 "one, two\nthree\n"
6634 );
6635
6636 _ = editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6637 // Ensure we don't lock if formatting hangs.
6638 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6639 assert_eq!(
6640 params.text_document.uri,
6641 lsp::Url::from_file_path("/file.rs").unwrap()
6642 );
6643 futures::future::pending::<()>().await;
6644 unreachable!()
6645 });
6646 let format = editor
6647 .update(cx, |editor, cx| {
6648 editor.perform_format(project, FormatTrigger::Manual, cx)
6649 })
6650 .unwrap();
6651 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6652 cx.executor().start_waiting();
6653 format.await;
6654 assert_eq!(
6655 editor.update(cx, |editor, cx| editor.text(cx)),
6656 "one\ntwo\nthree\n"
6657 );
6658}
6659
6660#[gpui::test]
6661async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
6662 init_test(cx, |_| {});
6663
6664 let mut cx = EditorLspTestContext::new_rust(
6665 lsp::ServerCapabilities {
6666 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6667 ..Default::default()
6668 },
6669 cx,
6670 )
6671 .await;
6672
6673 cx.set_state(indoc! {"
6674 one.twoˇ
6675 "});
6676
6677 // The format request takes a long time. When it completes, it inserts
6678 // a newline and an indent before the `.`
6679 cx.lsp
6680 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
6681 let executor = cx.background_executor().clone();
6682 async move {
6683 executor.timer(Duration::from_millis(100)).await;
6684 Ok(Some(vec![lsp::TextEdit {
6685 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
6686 new_text: "\n ".into(),
6687 }]))
6688 }
6689 });
6690
6691 // Submit a format request.
6692 let format_1 = cx
6693 .update_editor(|editor, cx| editor.format(&Format, cx))
6694 .unwrap();
6695 cx.executor().run_until_parked();
6696
6697 // Submit a second format request.
6698 let format_2 = cx
6699 .update_editor(|editor, cx| editor.format(&Format, cx))
6700 .unwrap();
6701 cx.executor().run_until_parked();
6702
6703 // Wait for both format requests to complete
6704 cx.executor().advance_clock(Duration::from_millis(200));
6705 cx.executor().start_waiting();
6706 format_1.await.unwrap();
6707 cx.executor().start_waiting();
6708 format_2.await.unwrap();
6709
6710 // The formatting edits only happens once.
6711 cx.assert_editor_state(indoc! {"
6712 one
6713 .twoˇ
6714 "});
6715}
6716
6717#[gpui::test]
6718async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
6719 init_test(cx, |settings| {
6720 settings.defaults.formatter = Some(language_settings::Formatter::Auto)
6721 });
6722
6723 let mut cx = EditorLspTestContext::new_rust(
6724 lsp::ServerCapabilities {
6725 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6726 ..Default::default()
6727 },
6728 cx,
6729 )
6730 .await;
6731
6732 // Set up a buffer white some trailing whitespace and no trailing newline.
6733 cx.set_state(
6734 &[
6735 "one ", //
6736 "twoˇ", //
6737 "three ", //
6738 "four", //
6739 ]
6740 .join("\n"),
6741 );
6742
6743 // Submit a format request.
6744 let format = cx
6745 .update_editor(|editor, cx| editor.format(&Format, cx))
6746 .unwrap();
6747
6748 // Record which buffer changes have been sent to the language server
6749 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
6750 cx.lsp
6751 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
6752 let buffer_changes = buffer_changes.clone();
6753 move |params, _| {
6754 buffer_changes.lock().extend(
6755 params
6756 .content_changes
6757 .into_iter()
6758 .map(|e| (e.range.unwrap(), e.text)),
6759 );
6760 }
6761 });
6762
6763 // Handle formatting requests to the language server.
6764 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
6765 let buffer_changes = buffer_changes.clone();
6766 move |_, _| {
6767 // When formatting is requested, trailing whitespace has already been stripped,
6768 // and the trailing newline has already been added.
6769 assert_eq!(
6770 &buffer_changes.lock()[1..],
6771 &[
6772 (
6773 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
6774 "".into()
6775 ),
6776 (
6777 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
6778 "".into()
6779 ),
6780 (
6781 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
6782 "\n".into()
6783 ),
6784 ]
6785 );
6786
6787 // Insert blank lines between each line of the buffer.
6788 async move {
6789 Ok(Some(vec![
6790 lsp::TextEdit {
6791 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
6792 new_text: "\n".into(),
6793 },
6794 lsp::TextEdit {
6795 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
6796 new_text: "\n".into(),
6797 },
6798 ]))
6799 }
6800 }
6801 });
6802
6803 // After formatting the buffer, the trailing whitespace is stripped,
6804 // a newline is appended, and the edits provided by the language server
6805 // have been applied.
6806 format.await.unwrap();
6807 cx.assert_editor_state(
6808 &[
6809 "one", //
6810 "", //
6811 "twoˇ", //
6812 "", //
6813 "three", //
6814 "four", //
6815 "", //
6816 ]
6817 .join("\n"),
6818 );
6819
6820 // Undoing the formatting undoes the trailing whitespace removal, the
6821 // trailing newline, and the LSP edits.
6822 cx.update_buffer(|buffer, cx| buffer.undo(cx));
6823 cx.assert_editor_state(
6824 &[
6825 "one ", //
6826 "twoˇ", //
6827 "three ", //
6828 "four", //
6829 ]
6830 .join("\n"),
6831 );
6832}
6833
6834#[gpui::test]
6835async fn test_completion(cx: &mut gpui::TestAppContext) {
6836 init_test(cx, |_| {});
6837
6838 let mut cx = EditorLspTestContext::new_rust(
6839 lsp::ServerCapabilities {
6840 completion_provider: Some(lsp::CompletionOptions {
6841 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
6842 resolve_provider: Some(true),
6843 ..Default::default()
6844 }),
6845 ..Default::default()
6846 },
6847 cx,
6848 )
6849 .await;
6850 let counter = Arc::new(AtomicUsize::new(0));
6851
6852 cx.set_state(indoc! {"
6853 oneˇ
6854 two
6855 three
6856 "});
6857 cx.simulate_keystroke(".");
6858 handle_completion_request(
6859 &mut cx,
6860 indoc! {"
6861 one.|<>
6862 two
6863 three
6864 "},
6865 vec!["first_completion", "second_completion"],
6866 counter.clone(),
6867 )
6868 .await;
6869 cx.condition(|editor, _| editor.context_menu_visible())
6870 .await;
6871 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
6872
6873 let apply_additional_edits = cx.update_editor(|editor, cx| {
6874 editor.context_menu_next(&Default::default(), cx);
6875 editor
6876 .confirm_completion(&ConfirmCompletion::default(), cx)
6877 .unwrap()
6878 });
6879 cx.assert_editor_state(indoc! {"
6880 one.second_completionˇ
6881 two
6882 three
6883 "});
6884
6885 handle_resolve_completion_request(
6886 &mut cx,
6887 Some(vec![
6888 (
6889 //This overlaps with the primary completion edit which is
6890 //misbehavior from the LSP spec, test that we filter it out
6891 indoc! {"
6892 one.second_ˇcompletion
6893 two
6894 threeˇ
6895 "},
6896 "overlapping additional edit",
6897 ),
6898 (
6899 indoc! {"
6900 one.second_completion
6901 two
6902 threeˇ
6903 "},
6904 "\nadditional edit",
6905 ),
6906 ]),
6907 )
6908 .await;
6909 apply_additional_edits.await.unwrap();
6910 cx.assert_editor_state(indoc! {"
6911 one.second_completionˇ
6912 two
6913 three
6914 additional edit
6915 "});
6916
6917 cx.set_state(indoc! {"
6918 one.second_completion
6919 twoˇ
6920 threeˇ
6921 additional edit
6922 "});
6923 cx.simulate_keystroke(" ");
6924 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
6925 cx.simulate_keystroke("s");
6926 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
6927
6928 cx.assert_editor_state(indoc! {"
6929 one.second_completion
6930 two sˇ
6931 three sˇ
6932 additional edit
6933 "});
6934 handle_completion_request(
6935 &mut cx,
6936 indoc! {"
6937 one.second_completion
6938 two s
6939 three <s|>
6940 additional edit
6941 "},
6942 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
6943 counter.clone(),
6944 )
6945 .await;
6946 cx.condition(|editor, _| editor.context_menu_visible())
6947 .await;
6948 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
6949
6950 cx.simulate_keystroke("i");
6951
6952 handle_completion_request(
6953 &mut cx,
6954 indoc! {"
6955 one.second_completion
6956 two si
6957 three <si|>
6958 additional edit
6959 "},
6960 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
6961 counter.clone(),
6962 )
6963 .await;
6964 cx.condition(|editor, _| editor.context_menu_visible())
6965 .await;
6966 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
6967
6968 let apply_additional_edits = cx.update_editor(|editor, cx| {
6969 editor
6970 .confirm_completion(&ConfirmCompletion::default(), cx)
6971 .unwrap()
6972 });
6973 cx.assert_editor_state(indoc! {"
6974 one.second_completion
6975 two sixth_completionˇ
6976 three sixth_completionˇ
6977 additional edit
6978 "});
6979
6980 handle_resolve_completion_request(&mut cx, None).await;
6981 apply_additional_edits.await.unwrap();
6982
6983 _ = cx.update(|cx| {
6984 cx.update_global::<SettingsStore, _>(|settings, cx| {
6985 settings.update_user_settings::<EditorSettings>(cx, |settings| {
6986 settings.show_completions_on_input = Some(false);
6987 });
6988 })
6989 });
6990 cx.set_state("editorˇ");
6991 cx.simulate_keystroke(".");
6992 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
6993 cx.simulate_keystroke("c");
6994 cx.simulate_keystroke("l");
6995 cx.simulate_keystroke("o");
6996 cx.assert_editor_state("editor.cloˇ");
6997 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
6998 cx.update_editor(|editor, cx| {
6999 editor.show_completions(&ShowCompletions { trigger: None }, cx);
7000 });
7001 handle_completion_request(
7002 &mut cx,
7003 "editor.<clo|>",
7004 vec!["close", "clobber"],
7005 counter.clone(),
7006 )
7007 .await;
7008 cx.condition(|editor, _| editor.context_menu_visible())
7009 .await;
7010 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
7011
7012 let apply_additional_edits = cx.update_editor(|editor, cx| {
7013 editor
7014 .confirm_completion(&ConfirmCompletion::default(), cx)
7015 .unwrap()
7016 });
7017 cx.assert_editor_state("editor.closeˇ");
7018 handle_resolve_completion_request(&mut cx, None).await;
7019 apply_additional_edits.await.unwrap();
7020}
7021
7022#[gpui::test]
7023async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
7024 init_test(cx, |_| {});
7025 let mut cx = EditorLspTestContext::new_rust(
7026 lsp::ServerCapabilities {
7027 completion_provider: Some(lsp::CompletionOptions {
7028 trigger_characters: Some(vec![".".to_string()]),
7029 ..Default::default()
7030 }),
7031 ..Default::default()
7032 },
7033 cx,
7034 )
7035 .await;
7036 cx.lsp
7037 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
7038 Ok(Some(lsp::CompletionResponse::Array(vec![
7039 lsp::CompletionItem {
7040 label: "first".into(),
7041 ..Default::default()
7042 },
7043 lsp::CompletionItem {
7044 label: "last".into(),
7045 ..Default::default()
7046 },
7047 ])))
7048 });
7049 cx.set_state("variableˇ");
7050 cx.simulate_keystroke(".");
7051 cx.executor().run_until_parked();
7052
7053 cx.update_editor(|editor, _| {
7054 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7055 assert_eq!(
7056 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
7057 &["first", "last"]
7058 );
7059 } else {
7060 panic!("expected completion menu to be open");
7061 }
7062 });
7063
7064 cx.update_editor(|editor, cx| {
7065 editor.move_page_down(&MovePageDown::default(), cx);
7066 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7067 assert!(
7068 menu.selected_item == 1,
7069 "expected PageDown to select the last item from the context menu"
7070 );
7071 } else {
7072 panic!("expected completion menu to stay open after PageDown");
7073 }
7074 });
7075
7076 cx.update_editor(|editor, cx| {
7077 editor.move_page_up(&MovePageUp::default(), cx);
7078 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
7079 assert!(
7080 menu.selected_item == 0,
7081 "expected PageUp to select the first item from the context menu"
7082 );
7083 } else {
7084 panic!("expected completion menu to stay open after PageUp");
7085 }
7086 });
7087}
7088
7089#[gpui::test]
7090async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
7091 init_test(cx, |_| {});
7092
7093 let mut cx = EditorLspTestContext::new_rust(
7094 lsp::ServerCapabilities {
7095 completion_provider: Some(lsp::CompletionOptions {
7096 trigger_characters: Some(vec![".".to_string()]),
7097 resolve_provider: Some(true),
7098 ..Default::default()
7099 }),
7100 ..Default::default()
7101 },
7102 cx,
7103 )
7104 .await;
7105
7106 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
7107 cx.simulate_keystroke(".");
7108 let completion_item = lsp::CompletionItem {
7109 label: "Some".into(),
7110 kind: Some(lsp::CompletionItemKind::SNIPPET),
7111 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
7112 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
7113 kind: lsp::MarkupKind::Markdown,
7114 value: "```rust\nSome(2)\n```".to_string(),
7115 })),
7116 deprecated: Some(false),
7117 sort_text: Some("Some".to_string()),
7118 filter_text: Some("Some".to_string()),
7119 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
7120 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7121 range: lsp::Range {
7122 start: lsp::Position {
7123 line: 0,
7124 character: 22,
7125 },
7126 end: lsp::Position {
7127 line: 0,
7128 character: 22,
7129 },
7130 },
7131 new_text: "Some(2)".to_string(),
7132 })),
7133 additional_text_edits: Some(vec![lsp::TextEdit {
7134 range: lsp::Range {
7135 start: lsp::Position {
7136 line: 0,
7137 character: 20,
7138 },
7139 end: lsp::Position {
7140 line: 0,
7141 character: 22,
7142 },
7143 },
7144 new_text: "".to_string(),
7145 }]),
7146 ..Default::default()
7147 };
7148
7149 let closure_completion_item = completion_item.clone();
7150 let counter = Arc::new(AtomicUsize::new(0));
7151 let counter_clone = counter.clone();
7152 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
7153 let task_completion_item = closure_completion_item.clone();
7154 counter_clone.fetch_add(1, atomic::Ordering::Release);
7155 async move {
7156 Ok(Some(lsp::CompletionResponse::Array(vec![
7157 task_completion_item,
7158 ])))
7159 }
7160 });
7161
7162 cx.condition(|editor, _| editor.context_menu_visible())
7163 .await;
7164 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
7165 assert!(request.next().await.is_some());
7166 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
7167
7168 cx.simulate_keystroke("S");
7169 cx.simulate_keystroke("o");
7170 cx.simulate_keystroke("m");
7171 cx.condition(|editor, _| editor.context_menu_visible())
7172 .await;
7173 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
7174 assert!(request.next().await.is_some());
7175 assert!(request.next().await.is_some());
7176 assert!(request.next().await.is_some());
7177 request.close();
7178 assert!(request.next().await.is_none());
7179 assert_eq!(
7180 counter.load(atomic::Ordering::Acquire),
7181 4,
7182 "With the completions menu open, only one LSP request should happen per input"
7183 );
7184}
7185
7186#[gpui::test]
7187async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
7188 init_test(cx, |_| {});
7189 let mut cx = EditorTestContext::new(cx).await;
7190 let language = Arc::new(Language::new(
7191 LanguageConfig {
7192 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
7193 ..Default::default()
7194 },
7195 Some(tree_sitter_rust::language()),
7196 ));
7197 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
7198
7199 // If multiple selections intersect a line, the line is only toggled once.
7200 cx.set_state(indoc! {"
7201 fn a() {
7202 «//b();
7203 ˇ»// «c();
7204 //ˇ» d();
7205 }
7206 "});
7207
7208 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7209
7210 cx.assert_editor_state(indoc! {"
7211 fn a() {
7212 «b();
7213 c();
7214 ˇ» d();
7215 }
7216 "});
7217
7218 // The comment prefix is inserted at the same column for every line in a
7219 // selection.
7220 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7221
7222 cx.assert_editor_state(indoc! {"
7223 fn a() {
7224 // «b();
7225 // c();
7226 ˇ»// d();
7227 }
7228 "});
7229
7230 // If a selection ends at the beginning of a line, that line is not toggled.
7231 cx.set_selections_state(indoc! {"
7232 fn a() {
7233 // b();
7234 «// c();
7235 ˇ» // d();
7236 }
7237 "});
7238
7239 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7240
7241 cx.assert_editor_state(indoc! {"
7242 fn a() {
7243 // b();
7244 «c();
7245 ˇ» // d();
7246 }
7247 "});
7248
7249 // If a selection span a single line and is empty, the line is toggled.
7250 cx.set_state(indoc! {"
7251 fn a() {
7252 a();
7253 b();
7254 ˇ
7255 }
7256 "});
7257
7258 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7259
7260 cx.assert_editor_state(indoc! {"
7261 fn a() {
7262 a();
7263 b();
7264 //•ˇ
7265 }
7266 "});
7267
7268 // If a selection span multiple lines, empty lines are not toggled.
7269 cx.set_state(indoc! {"
7270 fn a() {
7271 «a();
7272
7273 c();ˇ»
7274 }
7275 "});
7276
7277 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7278
7279 cx.assert_editor_state(indoc! {"
7280 fn a() {
7281 // «a();
7282
7283 // c();ˇ»
7284 }
7285 "});
7286
7287 // If a selection includes multiple comment prefixes, all lines are uncommented.
7288 cx.set_state(indoc! {"
7289 fn a() {
7290 «// a();
7291 /// b();
7292 //! c();ˇ»
7293 }
7294 "});
7295
7296 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7297
7298 cx.assert_editor_state(indoc! {"
7299 fn a() {
7300 «a();
7301 b();
7302 c();ˇ»
7303 }
7304 "});
7305}
7306
7307#[gpui::test]
7308async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
7309 init_test(cx, |_| {});
7310
7311 let language = Arc::new(Language::new(
7312 LanguageConfig {
7313 line_comments: vec!["// ".into()],
7314 ..Default::default()
7315 },
7316 Some(tree_sitter_rust::language()),
7317 ));
7318
7319 let mut cx = EditorTestContext::new(cx).await;
7320
7321 cx.language_registry().add(language.clone());
7322 cx.update_buffer(|buffer, cx| {
7323 buffer.set_language(Some(language), cx);
7324 });
7325
7326 let toggle_comments = &ToggleComments {
7327 advance_downwards: true,
7328 };
7329
7330 // Single cursor on one line -> advance
7331 // Cursor moves horizontally 3 characters as well on non-blank line
7332 cx.set_state(indoc!(
7333 "fn a() {
7334 ˇdog();
7335 cat();
7336 }"
7337 ));
7338 cx.update_editor(|editor, cx| {
7339 editor.toggle_comments(toggle_comments, cx);
7340 });
7341 cx.assert_editor_state(indoc!(
7342 "fn a() {
7343 // dog();
7344 catˇ();
7345 }"
7346 ));
7347
7348 // Single selection on one line -> don't advance
7349 cx.set_state(indoc!(
7350 "fn a() {
7351 «dog()ˇ»;
7352 cat();
7353 }"
7354 ));
7355 cx.update_editor(|editor, cx| {
7356 editor.toggle_comments(toggle_comments, cx);
7357 });
7358 cx.assert_editor_state(indoc!(
7359 "fn a() {
7360 // «dog()ˇ»;
7361 cat();
7362 }"
7363 ));
7364
7365 // Multiple cursors on one line -> advance
7366 cx.set_state(indoc!(
7367 "fn a() {
7368 ˇdˇog();
7369 cat();
7370 }"
7371 ));
7372 cx.update_editor(|editor, cx| {
7373 editor.toggle_comments(toggle_comments, cx);
7374 });
7375 cx.assert_editor_state(indoc!(
7376 "fn a() {
7377 // dog();
7378 catˇ(ˇ);
7379 }"
7380 ));
7381
7382 // Multiple cursors on one line, with selection -> don't advance
7383 cx.set_state(indoc!(
7384 "fn a() {
7385 ˇdˇog«()ˇ»;
7386 cat();
7387 }"
7388 ));
7389 cx.update_editor(|editor, cx| {
7390 editor.toggle_comments(toggle_comments, cx);
7391 });
7392 cx.assert_editor_state(indoc!(
7393 "fn a() {
7394 // ˇdˇog«()ˇ»;
7395 cat();
7396 }"
7397 ));
7398
7399 // Single cursor on one line -> advance
7400 // Cursor moves to column 0 on blank line
7401 cx.set_state(indoc!(
7402 "fn a() {
7403 ˇdog();
7404
7405 cat();
7406 }"
7407 ));
7408 cx.update_editor(|editor, cx| {
7409 editor.toggle_comments(toggle_comments, cx);
7410 });
7411 cx.assert_editor_state(indoc!(
7412 "fn a() {
7413 // dog();
7414 ˇ
7415 cat();
7416 }"
7417 ));
7418
7419 // Single cursor on one line -> advance
7420 // Cursor starts and ends at column 0
7421 cx.set_state(indoc!(
7422 "fn a() {
7423 ˇ dog();
7424 cat();
7425 }"
7426 ));
7427 cx.update_editor(|editor, cx| {
7428 editor.toggle_comments(toggle_comments, cx);
7429 });
7430 cx.assert_editor_state(indoc!(
7431 "fn a() {
7432 // dog();
7433 ˇ cat();
7434 }"
7435 ));
7436}
7437
7438#[gpui::test]
7439async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
7440 init_test(cx, |_| {});
7441
7442 let mut cx = EditorTestContext::new(cx).await;
7443
7444 let html_language = Arc::new(
7445 Language::new(
7446 LanguageConfig {
7447 name: "HTML".into(),
7448 block_comment: Some(("<!-- ".into(), " -->".into())),
7449 ..Default::default()
7450 },
7451 Some(tree_sitter_html::language()),
7452 )
7453 .with_injection_query(
7454 r#"
7455 (script_element
7456 (raw_text) @content
7457 (#set! "language" "javascript"))
7458 "#,
7459 )
7460 .unwrap(),
7461 );
7462
7463 let javascript_language = Arc::new(Language::new(
7464 LanguageConfig {
7465 name: "JavaScript".into(),
7466 line_comments: vec!["// ".into()],
7467 ..Default::default()
7468 },
7469 Some(tree_sitter_typescript::language_tsx()),
7470 ));
7471
7472 cx.language_registry().add(html_language.clone());
7473 cx.language_registry().add(javascript_language.clone());
7474 cx.update_buffer(|buffer, cx| {
7475 buffer.set_language(Some(html_language), cx);
7476 });
7477
7478 // Toggle comments for empty selections
7479 cx.set_state(
7480 &r#"
7481 <p>A</p>ˇ
7482 <p>B</p>ˇ
7483 <p>C</p>ˇ
7484 "#
7485 .unindent(),
7486 );
7487 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
7488 cx.assert_editor_state(
7489 &r#"
7490 <!-- <p>A</p>ˇ -->
7491 <!-- <p>B</p>ˇ -->
7492 <!-- <p>C</p>ˇ -->
7493 "#
7494 .unindent(),
7495 );
7496 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
7497 cx.assert_editor_state(
7498 &r#"
7499 <p>A</p>ˇ
7500 <p>B</p>ˇ
7501 <p>C</p>ˇ
7502 "#
7503 .unindent(),
7504 );
7505
7506 // Toggle comments for mixture of empty and non-empty selections, where
7507 // multiple selections occupy a given line.
7508 cx.set_state(
7509 &r#"
7510 <p>A«</p>
7511 <p>ˇ»B</p>ˇ
7512 <p>C«</p>
7513 <p>ˇ»D</p>ˇ
7514 "#
7515 .unindent(),
7516 );
7517
7518 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
7519 cx.assert_editor_state(
7520 &r#"
7521 <!-- <p>A«</p>
7522 <p>ˇ»B</p>ˇ -->
7523 <!-- <p>C«</p>
7524 <p>ˇ»D</p>ˇ -->
7525 "#
7526 .unindent(),
7527 );
7528 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
7529 cx.assert_editor_state(
7530 &r#"
7531 <p>A«</p>
7532 <p>ˇ»B</p>ˇ
7533 <p>C«</p>
7534 <p>ˇ»D</p>ˇ
7535 "#
7536 .unindent(),
7537 );
7538
7539 // Toggle comments when different languages are active for different
7540 // selections.
7541 cx.set_state(
7542 &r#"
7543 ˇ<script>
7544 ˇvar x = new Y();
7545 ˇ</script>
7546 "#
7547 .unindent(),
7548 );
7549 cx.executor().run_until_parked();
7550 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
7551 cx.assert_editor_state(
7552 &r#"
7553 <!-- ˇ<script> -->
7554 // ˇvar x = new Y();
7555 <!-- ˇ</script> -->
7556 "#
7557 .unindent(),
7558 );
7559}
7560
7561#[gpui::test]
7562fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
7563 init_test(cx, |_| {});
7564
7565 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
7566 let multibuffer = cx.new_model(|cx| {
7567 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
7568 multibuffer.push_excerpts(
7569 buffer.clone(),
7570 [
7571 ExcerptRange {
7572 context: Point::new(0, 0)..Point::new(0, 4),
7573 primary: None,
7574 },
7575 ExcerptRange {
7576 context: Point::new(1, 0)..Point::new(1, 4),
7577 primary: None,
7578 },
7579 ],
7580 cx,
7581 );
7582 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
7583 multibuffer
7584 });
7585
7586 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
7587 _ = view.update(cx, |view, cx| {
7588 assert_eq!(view.text(cx), "aaaa\nbbbb");
7589 view.change_selections(None, cx, |s| {
7590 s.select_ranges([
7591 Point::new(0, 0)..Point::new(0, 0),
7592 Point::new(1, 0)..Point::new(1, 0),
7593 ])
7594 });
7595
7596 view.handle_input("X", cx);
7597 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
7598 assert_eq!(
7599 view.selections.ranges(cx),
7600 [
7601 Point::new(0, 1)..Point::new(0, 1),
7602 Point::new(1, 1)..Point::new(1, 1),
7603 ]
7604 );
7605
7606 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
7607 view.change_selections(None, cx, |s| {
7608 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
7609 });
7610 view.backspace(&Default::default(), cx);
7611 assert_eq!(view.text(cx), "Xa\nbbb");
7612 assert_eq!(
7613 view.selections.ranges(cx),
7614 [Point::new(1, 0)..Point::new(1, 0)]
7615 );
7616
7617 view.change_selections(None, cx, |s| {
7618 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
7619 });
7620 view.backspace(&Default::default(), cx);
7621 assert_eq!(view.text(cx), "X\nbb");
7622 assert_eq!(
7623 view.selections.ranges(cx),
7624 [Point::new(0, 1)..Point::new(0, 1)]
7625 );
7626 });
7627}
7628
7629#[gpui::test]
7630fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
7631 init_test(cx, |_| {});
7632
7633 let markers = vec![('[', ']').into(), ('(', ')').into()];
7634 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
7635 indoc! {"
7636 [aaaa
7637 (bbbb]
7638 cccc)",
7639 },
7640 markers.clone(),
7641 );
7642 let excerpt_ranges = markers.into_iter().map(|marker| {
7643 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
7644 ExcerptRange {
7645 context,
7646 primary: None,
7647 }
7648 });
7649 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
7650 let multibuffer = cx.new_model(|cx| {
7651 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
7652 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
7653 multibuffer
7654 });
7655
7656 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
7657 _ = view.update(cx, |view, cx| {
7658 let (expected_text, selection_ranges) = marked_text_ranges(
7659 indoc! {"
7660 aaaa
7661 bˇbbb
7662 bˇbbˇb
7663 cccc"
7664 },
7665 true,
7666 );
7667 assert_eq!(view.text(cx), expected_text);
7668 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
7669
7670 view.handle_input("X", cx);
7671
7672 let (expected_text, expected_selections) = marked_text_ranges(
7673 indoc! {"
7674 aaaa
7675 bXˇbbXb
7676 bXˇbbXˇb
7677 cccc"
7678 },
7679 false,
7680 );
7681 assert_eq!(view.text(cx), expected_text);
7682 assert_eq!(view.selections.ranges(cx), expected_selections);
7683
7684 view.newline(&Newline, cx);
7685 let (expected_text, expected_selections) = marked_text_ranges(
7686 indoc! {"
7687 aaaa
7688 bX
7689 ˇbbX
7690 b
7691 bX
7692 ˇbbX
7693 ˇb
7694 cccc"
7695 },
7696 false,
7697 );
7698 assert_eq!(view.text(cx), expected_text);
7699 assert_eq!(view.selections.ranges(cx), expected_selections);
7700 });
7701}
7702
7703#[gpui::test]
7704fn test_refresh_selections(cx: &mut TestAppContext) {
7705 init_test(cx, |_| {});
7706
7707 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
7708 let mut excerpt1_id = None;
7709 let multibuffer = cx.new_model(|cx| {
7710 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
7711 excerpt1_id = multibuffer
7712 .push_excerpts(
7713 buffer.clone(),
7714 [
7715 ExcerptRange {
7716 context: Point::new(0, 0)..Point::new(1, 4),
7717 primary: None,
7718 },
7719 ExcerptRange {
7720 context: Point::new(1, 0)..Point::new(2, 4),
7721 primary: None,
7722 },
7723 ],
7724 cx,
7725 )
7726 .into_iter()
7727 .next();
7728 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
7729 multibuffer
7730 });
7731
7732 let editor = cx.add_window(|cx| {
7733 let mut editor = build_editor(multibuffer.clone(), cx);
7734 let snapshot = editor.snapshot(cx);
7735 editor.change_selections(None, cx, |s| {
7736 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
7737 });
7738 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
7739 assert_eq!(
7740 editor.selections.ranges(cx),
7741 [
7742 Point::new(1, 3)..Point::new(1, 3),
7743 Point::new(2, 1)..Point::new(2, 1),
7744 ]
7745 );
7746 editor
7747 });
7748
7749 // Refreshing selections is a no-op when excerpts haven't changed.
7750 _ = editor.update(cx, |editor, cx| {
7751 editor.change_selections(None, cx, |s| s.refresh());
7752 assert_eq!(
7753 editor.selections.ranges(cx),
7754 [
7755 Point::new(1, 3)..Point::new(1, 3),
7756 Point::new(2, 1)..Point::new(2, 1),
7757 ]
7758 );
7759 });
7760
7761 _ = multibuffer.update(cx, |multibuffer, cx| {
7762 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
7763 });
7764 _ = editor.update(cx, |editor, cx| {
7765 // Removing an excerpt causes the first selection to become degenerate.
7766 assert_eq!(
7767 editor.selections.ranges(cx),
7768 [
7769 Point::new(0, 0)..Point::new(0, 0),
7770 Point::new(0, 1)..Point::new(0, 1)
7771 ]
7772 );
7773
7774 // Refreshing selections will relocate the first selection to the original buffer
7775 // location.
7776 editor.change_selections(None, cx, |s| s.refresh());
7777 assert_eq!(
7778 editor.selections.ranges(cx),
7779 [
7780 Point::new(0, 1)..Point::new(0, 1),
7781 Point::new(0, 3)..Point::new(0, 3)
7782 ]
7783 );
7784 assert!(editor.selections.pending_anchor().is_some());
7785 });
7786}
7787
7788#[gpui::test]
7789fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
7790 init_test(cx, |_| {});
7791
7792 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
7793 let mut excerpt1_id = None;
7794 let multibuffer = cx.new_model(|cx| {
7795 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
7796 excerpt1_id = multibuffer
7797 .push_excerpts(
7798 buffer.clone(),
7799 [
7800 ExcerptRange {
7801 context: Point::new(0, 0)..Point::new(1, 4),
7802 primary: None,
7803 },
7804 ExcerptRange {
7805 context: Point::new(1, 0)..Point::new(2, 4),
7806 primary: None,
7807 },
7808 ],
7809 cx,
7810 )
7811 .into_iter()
7812 .next();
7813 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
7814 multibuffer
7815 });
7816
7817 let editor = cx.add_window(|cx| {
7818 let mut editor = build_editor(multibuffer.clone(), cx);
7819 let snapshot = editor.snapshot(cx);
7820 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
7821 assert_eq!(
7822 editor.selections.ranges(cx),
7823 [Point::new(1, 3)..Point::new(1, 3)]
7824 );
7825 editor
7826 });
7827
7828 _ = multibuffer.update(cx, |multibuffer, cx| {
7829 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
7830 });
7831 _ = editor.update(cx, |editor, cx| {
7832 assert_eq!(
7833 editor.selections.ranges(cx),
7834 [Point::new(0, 0)..Point::new(0, 0)]
7835 );
7836
7837 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
7838 editor.change_selections(None, cx, |s| s.refresh());
7839 assert_eq!(
7840 editor.selections.ranges(cx),
7841 [Point::new(0, 3)..Point::new(0, 3)]
7842 );
7843 assert!(editor.selections.pending_anchor().is_some());
7844 });
7845}
7846
7847#[gpui::test]
7848async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
7849 init_test(cx, |_| {});
7850
7851 let language = Arc::new(
7852 Language::new(
7853 LanguageConfig {
7854 brackets: BracketPairConfig {
7855 pairs: vec![
7856 BracketPair {
7857 start: "{".to_string(),
7858 end: "}".to_string(),
7859 close: true,
7860 surround: true,
7861 newline: true,
7862 },
7863 BracketPair {
7864 start: "/* ".to_string(),
7865 end: " */".to_string(),
7866 close: true,
7867 surround: true,
7868 newline: true,
7869 },
7870 ],
7871 ..Default::default()
7872 },
7873 ..Default::default()
7874 },
7875 Some(tree_sitter_rust::language()),
7876 )
7877 .with_indents_query("")
7878 .unwrap(),
7879 );
7880
7881 let text = concat!(
7882 "{ }\n", //
7883 " x\n", //
7884 " /* */\n", //
7885 "x\n", //
7886 "{{} }\n", //
7887 );
7888
7889 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
7890 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7891 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7892 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
7893 .await;
7894
7895 _ = view.update(cx, |view, cx| {
7896 view.change_selections(None, cx, |s| {
7897 s.select_display_ranges([
7898 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
7899 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
7900 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
7901 ])
7902 });
7903 view.newline(&Newline, cx);
7904
7905 assert_eq!(
7906 view.buffer().read(cx).read(cx).text(),
7907 concat!(
7908 "{ \n", // Suppress rustfmt
7909 "\n", //
7910 "}\n", //
7911 " x\n", //
7912 " /* \n", //
7913 " \n", //
7914 " */\n", //
7915 "x\n", //
7916 "{{} \n", //
7917 "}\n", //
7918 )
7919 );
7920 });
7921}
7922
7923#[gpui::test]
7924fn test_highlighted_ranges(cx: &mut TestAppContext) {
7925 init_test(cx, |_| {});
7926
7927 let editor = cx.add_window(|cx| {
7928 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
7929 build_editor(buffer.clone(), cx)
7930 });
7931
7932 _ = editor.update(cx, |editor, cx| {
7933 struct Type1;
7934 struct Type2;
7935
7936 let buffer = editor.buffer.read(cx).snapshot(cx);
7937
7938 let anchor_range =
7939 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
7940
7941 editor.highlight_background::<Type1>(
7942 &[
7943 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
7944 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
7945 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
7946 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
7947 ],
7948 |_| Hsla::red(),
7949 cx,
7950 );
7951 editor.highlight_background::<Type2>(
7952 &[
7953 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
7954 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
7955 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
7956 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
7957 ],
7958 |_| Hsla::green(),
7959 cx,
7960 );
7961
7962 let snapshot = editor.snapshot(cx);
7963 let mut highlighted_ranges = editor.background_highlights_in_range(
7964 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
7965 &snapshot,
7966 cx.theme().colors(),
7967 );
7968 // Enforce a consistent ordering based on color without relying on the ordering of the
7969 // highlight's `TypeId` which is non-executor.
7970 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
7971 assert_eq!(
7972 highlighted_ranges,
7973 &[
7974 (
7975 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
7976 Hsla::red(),
7977 ),
7978 (
7979 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
7980 Hsla::red(),
7981 ),
7982 (
7983 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
7984 Hsla::green(),
7985 ),
7986 (
7987 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
7988 Hsla::green(),
7989 ),
7990 ]
7991 );
7992 assert_eq!(
7993 editor.background_highlights_in_range(
7994 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
7995 &snapshot,
7996 cx.theme().colors(),
7997 ),
7998 &[(
7999 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
8000 Hsla::red(),
8001 )]
8002 );
8003 });
8004}
8005
8006#[gpui::test]
8007async fn test_following(cx: &mut gpui::TestAppContext) {
8008 init_test(cx, |_| {});
8009
8010 let fs = FakeFs::new(cx.executor());
8011 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8012
8013 let buffer = project.update(cx, |project, cx| {
8014 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
8015 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
8016 });
8017 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
8018 let follower = cx.update(|cx| {
8019 cx.open_window(
8020 WindowOptions {
8021 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
8022 gpui::Point::new(px(0.), px(0.)),
8023 gpui::Point::new(px(10.), px(80.)),
8024 ))),
8025 ..Default::default()
8026 },
8027 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
8028 )
8029 .unwrap()
8030 });
8031
8032 let is_still_following = Rc::new(RefCell::new(true));
8033 let follower_edit_event_count = Rc::new(RefCell::new(0));
8034 let pending_update = Rc::new(RefCell::new(None));
8035 _ = follower.update(cx, {
8036 let update = pending_update.clone();
8037 let is_still_following = is_still_following.clone();
8038 let follower_edit_event_count = follower_edit_event_count.clone();
8039 |_, cx| {
8040 cx.subscribe(
8041 &leader.root_view(cx).unwrap(),
8042 move |_, leader, event, cx| {
8043 leader
8044 .read(cx)
8045 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
8046 },
8047 )
8048 .detach();
8049
8050 cx.subscribe(
8051 &follower.root_view(cx).unwrap(),
8052 move |_, _, event: &EditorEvent, _cx| {
8053 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
8054 *is_still_following.borrow_mut() = false;
8055 }
8056
8057 if let EditorEvent::BufferEdited = event {
8058 *follower_edit_event_count.borrow_mut() += 1;
8059 }
8060 },
8061 )
8062 .detach();
8063 }
8064 });
8065
8066 // Update the selections only
8067 _ = leader.update(cx, |leader, cx| {
8068 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
8069 });
8070 follower
8071 .update(cx, |follower, cx| {
8072 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8073 })
8074 .unwrap()
8075 .await
8076 .unwrap();
8077 _ = follower.update(cx, |follower, cx| {
8078 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
8079 });
8080 assert_eq!(*is_still_following.borrow(), true);
8081 assert_eq!(*follower_edit_event_count.borrow(), 0);
8082
8083 // Update the scroll position only
8084 _ = leader.update(cx, |leader, cx| {
8085 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
8086 });
8087 follower
8088 .update(cx, |follower, cx| {
8089 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8090 })
8091 .unwrap()
8092 .await
8093 .unwrap();
8094 assert_eq!(
8095 follower
8096 .update(cx, |follower, cx| follower.scroll_position(cx))
8097 .unwrap(),
8098 gpui::Point::new(1.5, 3.5)
8099 );
8100 assert_eq!(*is_still_following.borrow(), true);
8101 assert_eq!(*follower_edit_event_count.borrow(), 0);
8102
8103 // Update the selections and scroll position. The follower's scroll position is updated
8104 // via autoscroll, not via the leader's exact scroll position.
8105 _ = leader.update(cx, |leader, cx| {
8106 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
8107 leader.request_autoscroll(Autoscroll::newest(), cx);
8108 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
8109 });
8110 follower
8111 .update(cx, |follower, cx| {
8112 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8113 })
8114 .unwrap()
8115 .await
8116 .unwrap();
8117 _ = follower.update(cx, |follower, cx| {
8118 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
8119 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
8120 });
8121 assert_eq!(*is_still_following.borrow(), true);
8122
8123 // Creating a pending selection that precedes another selection
8124 _ = leader.update(cx, |leader, cx| {
8125 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
8126 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
8127 });
8128 follower
8129 .update(cx, |follower, cx| {
8130 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8131 })
8132 .unwrap()
8133 .await
8134 .unwrap();
8135 _ = follower.update(cx, |follower, cx| {
8136 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
8137 });
8138 assert_eq!(*is_still_following.borrow(), true);
8139
8140 // Extend the pending selection so that it surrounds another selection
8141 _ = leader.update(cx, |leader, cx| {
8142 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
8143 });
8144 follower
8145 .update(cx, |follower, cx| {
8146 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8147 })
8148 .unwrap()
8149 .await
8150 .unwrap();
8151 _ = follower.update(cx, |follower, cx| {
8152 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
8153 });
8154
8155 // Scrolling locally breaks the follow
8156 _ = follower.update(cx, |follower, cx| {
8157 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
8158 follower.set_scroll_anchor(
8159 ScrollAnchor {
8160 anchor: top_anchor,
8161 offset: gpui::Point::new(0.0, 0.5),
8162 },
8163 cx,
8164 );
8165 });
8166 assert_eq!(*is_still_following.borrow(), false);
8167}
8168
8169#[gpui::test]
8170async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
8171 init_test(cx, |_| {});
8172
8173 let fs = FakeFs::new(cx.executor());
8174 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8175 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8176 let pane = workspace
8177 .update(cx, |workspace, _| workspace.active_pane().clone())
8178 .unwrap();
8179
8180 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
8181
8182 let leader = pane.update(cx, |_, cx| {
8183 let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
8184 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
8185 });
8186
8187 // Start following the editor when it has no excerpts.
8188 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
8189 let follower_1 = cx
8190 .update_window(*workspace.deref(), |_, cx| {
8191 Editor::from_state_proto(
8192 pane.clone(),
8193 workspace.root_view(cx).unwrap(),
8194 ViewId {
8195 creator: Default::default(),
8196 id: 0,
8197 },
8198 &mut state_message,
8199 cx,
8200 )
8201 })
8202 .unwrap()
8203 .unwrap()
8204 .await
8205 .unwrap();
8206
8207 let update_message = Rc::new(RefCell::new(None));
8208 follower_1.update(cx, {
8209 let update = update_message.clone();
8210 |_, cx| {
8211 cx.subscribe(&leader, move |_, leader, event, cx| {
8212 leader
8213 .read(cx)
8214 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
8215 })
8216 .detach();
8217 }
8218 });
8219
8220 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
8221 (
8222 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
8223 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
8224 )
8225 });
8226
8227 // Insert some excerpts.
8228 _ = leader.update(cx, |leader, cx| {
8229 leader.buffer.update(cx, |multibuffer, cx| {
8230 let excerpt_ids = multibuffer.push_excerpts(
8231 buffer_1.clone(),
8232 [
8233 ExcerptRange {
8234 context: 1..6,
8235 primary: None,
8236 },
8237 ExcerptRange {
8238 context: 12..15,
8239 primary: None,
8240 },
8241 ExcerptRange {
8242 context: 0..3,
8243 primary: None,
8244 },
8245 ],
8246 cx,
8247 );
8248 multibuffer.insert_excerpts_after(
8249 excerpt_ids[0],
8250 buffer_2.clone(),
8251 [
8252 ExcerptRange {
8253 context: 8..12,
8254 primary: None,
8255 },
8256 ExcerptRange {
8257 context: 0..6,
8258 primary: None,
8259 },
8260 ],
8261 cx,
8262 );
8263 });
8264 });
8265
8266 // Apply the update of adding the excerpts.
8267 follower_1
8268 .update(cx, |follower, cx| {
8269 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
8270 })
8271 .await
8272 .unwrap();
8273 assert_eq!(
8274 follower_1.update(cx, |editor, cx| editor.text(cx)),
8275 leader.update(cx, |editor, cx| editor.text(cx))
8276 );
8277 update_message.borrow_mut().take();
8278
8279 // Start following separately after it already has excerpts.
8280 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
8281 let follower_2 = cx
8282 .update_window(*workspace.deref(), |_, cx| {
8283 Editor::from_state_proto(
8284 pane.clone(),
8285 workspace.root_view(cx).unwrap().clone(),
8286 ViewId {
8287 creator: Default::default(),
8288 id: 0,
8289 },
8290 &mut state_message,
8291 cx,
8292 )
8293 })
8294 .unwrap()
8295 .unwrap()
8296 .await
8297 .unwrap();
8298 assert_eq!(
8299 follower_2.update(cx, |editor, cx| editor.text(cx)),
8300 leader.update(cx, |editor, cx| editor.text(cx))
8301 );
8302
8303 // Remove some excerpts.
8304 _ = leader.update(cx, |leader, cx| {
8305 leader.buffer.update(cx, |multibuffer, cx| {
8306 let excerpt_ids = multibuffer.excerpt_ids();
8307 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
8308 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
8309 });
8310 });
8311
8312 // Apply the update of removing the excerpts.
8313 follower_1
8314 .update(cx, |follower, cx| {
8315 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
8316 })
8317 .await
8318 .unwrap();
8319 follower_2
8320 .update(cx, |follower, cx| {
8321 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
8322 })
8323 .await
8324 .unwrap();
8325 update_message.borrow_mut().take();
8326 assert_eq!(
8327 follower_1.update(cx, |editor, cx| editor.text(cx)),
8328 leader.update(cx, |editor, cx| editor.text(cx))
8329 );
8330}
8331
8332#[gpui::test]
8333async fn go_to_prev_overlapping_diagnostic(
8334 executor: BackgroundExecutor,
8335 cx: &mut gpui::TestAppContext,
8336) {
8337 init_test(cx, |_| {});
8338
8339 let mut cx = EditorTestContext::new(cx).await;
8340 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
8341
8342 cx.set_state(indoc! {"
8343 ˇfn func(abc def: i32) -> u32 {
8344 }
8345 "});
8346
8347 _ = cx.update(|cx| {
8348 _ = project.update(cx, |project, cx| {
8349 project
8350 .update_diagnostics(
8351 LanguageServerId(0),
8352 lsp::PublishDiagnosticsParams {
8353 uri: lsp::Url::from_file_path("/root/file").unwrap(),
8354 version: None,
8355 diagnostics: vec![
8356 lsp::Diagnostic {
8357 range: lsp::Range::new(
8358 lsp::Position::new(0, 11),
8359 lsp::Position::new(0, 12),
8360 ),
8361 severity: Some(lsp::DiagnosticSeverity::ERROR),
8362 ..Default::default()
8363 },
8364 lsp::Diagnostic {
8365 range: lsp::Range::new(
8366 lsp::Position::new(0, 12),
8367 lsp::Position::new(0, 15),
8368 ),
8369 severity: Some(lsp::DiagnosticSeverity::ERROR),
8370 ..Default::default()
8371 },
8372 lsp::Diagnostic {
8373 range: lsp::Range::new(
8374 lsp::Position::new(0, 25),
8375 lsp::Position::new(0, 28),
8376 ),
8377 severity: Some(lsp::DiagnosticSeverity::ERROR),
8378 ..Default::default()
8379 },
8380 ],
8381 },
8382 &[],
8383 cx,
8384 )
8385 .unwrap()
8386 });
8387 });
8388
8389 executor.run_until_parked();
8390
8391 cx.update_editor(|editor, cx| {
8392 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
8393 });
8394
8395 cx.assert_editor_state(indoc! {"
8396 fn func(abc def: i32) -> ˇu32 {
8397 }
8398 "});
8399
8400 cx.update_editor(|editor, cx| {
8401 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
8402 });
8403
8404 cx.assert_editor_state(indoc! {"
8405 fn func(abc ˇdef: i32) -> u32 {
8406 }
8407 "});
8408
8409 cx.update_editor(|editor, cx| {
8410 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
8411 });
8412
8413 cx.assert_editor_state(indoc! {"
8414 fn func(abcˇ def: i32) -> u32 {
8415 }
8416 "});
8417
8418 cx.update_editor(|editor, cx| {
8419 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
8420 });
8421
8422 cx.assert_editor_state(indoc! {"
8423 fn func(abc def: i32) -> ˇu32 {
8424 }
8425 "});
8426}
8427
8428#[gpui::test]
8429async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
8430 init_test(cx, |_| {});
8431
8432 let mut cx = EditorTestContext::new(cx).await;
8433
8434 let diff_base = r#"
8435 use some::mod;
8436
8437 const A: u32 = 42;
8438
8439 fn main() {
8440 println!("hello");
8441
8442 println!("world");
8443 }
8444 "#
8445 .unindent();
8446
8447 // Edits are modified, removed, modified, added
8448 cx.set_state(
8449 &r#"
8450 use some::modified;
8451
8452 ˇ
8453 fn main() {
8454 println!("hello there");
8455
8456 println!("around the");
8457 println!("world");
8458 }
8459 "#
8460 .unindent(),
8461 );
8462
8463 cx.set_diff_base(Some(&diff_base));
8464 executor.run_until_parked();
8465
8466 cx.update_editor(|editor, cx| {
8467 //Wrap around the bottom of the buffer
8468 for _ in 0..3 {
8469 editor.go_to_hunk(&GoToHunk, cx);
8470 }
8471 });
8472
8473 cx.assert_editor_state(
8474 &r#"
8475 ˇuse some::modified;
8476
8477
8478 fn main() {
8479 println!("hello there");
8480
8481 println!("around the");
8482 println!("world");
8483 }
8484 "#
8485 .unindent(),
8486 );
8487
8488 cx.update_editor(|editor, cx| {
8489 //Wrap around the top of the buffer
8490 for _ in 0..2 {
8491 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
8492 }
8493 });
8494
8495 cx.assert_editor_state(
8496 &r#"
8497 use some::modified;
8498
8499
8500 fn main() {
8501 ˇ println!("hello there");
8502
8503 println!("around the");
8504 println!("world");
8505 }
8506 "#
8507 .unindent(),
8508 );
8509
8510 cx.update_editor(|editor, cx| {
8511 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
8512 });
8513
8514 cx.assert_editor_state(
8515 &r#"
8516 use some::modified;
8517
8518 ˇ
8519 fn main() {
8520 println!("hello there");
8521
8522 println!("around the");
8523 println!("world");
8524 }
8525 "#
8526 .unindent(),
8527 );
8528
8529 cx.update_editor(|editor, cx| {
8530 for _ in 0..3 {
8531 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
8532 }
8533 });
8534
8535 cx.assert_editor_state(
8536 &r#"
8537 use some::modified;
8538
8539
8540 fn main() {
8541 ˇ println!("hello there");
8542
8543 println!("around the");
8544 println!("world");
8545 }
8546 "#
8547 .unindent(),
8548 );
8549
8550 cx.update_editor(|editor, cx| {
8551 editor.fold(&Fold, cx);
8552
8553 //Make sure that the fold only gets one hunk
8554 for _ in 0..4 {
8555 editor.go_to_hunk(&GoToHunk, cx);
8556 }
8557 });
8558
8559 cx.assert_editor_state(
8560 &r#"
8561 ˇuse some::modified;
8562
8563
8564 fn main() {
8565 println!("hello there");
8566
8567 println!("around the");
8568 println!("world");
8569 }
8570 "#
8571 .unindent(),
8572 );
8573}
8574
8575#[test]
8576fn test_split_words() {
8577 fn split(text: &str) -> Vec<&str> {
8578 split_words(text).collect()
8579 }
8580
8581 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
8582 assert_eq!(split("hello_world"), &["hello_", "world"]);
8583 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
8584 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
8585 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
8586 assert_eq!(split("helloworld"), &["helloworld"]);
8587
8588 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
8589}
8590
8591#[gpui::test]
8592async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
8593 init_test(cx, |_| {});
8594
8595 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
8596 let mut assert = |before, after| {
8597 let _state_context = cx.set_state(before);
8598 cx.update_editor(|editor, cx| {
8599 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
8600 });
8601 cx.assert_editor_state(after);
8602 };
8603
8604 // Outside bracket jumps to outside of matching bracket
8605 assert("console.logˇ(var);", "console.log(var)ˇ;");
8606 assert("console.log(var)ˇ;", "console.logˇ(var);");
8607
8608 // Inside bracket jumps to inside of matching bracket
8609 assert("console.log(ˇvar);", "console.log(varˇ);");
8610 assert("console.log(varˇ);", "console.log(ˇvar);");
8611
8612 // When outside a bracket and inside, favor jumping to the inside bracket
8613 assert(
8614 "console.log('foo', [1, 2, 3]ˇ);",
8615 "console.log(ˇ'foo', [1, 2, 3]);",
8616 );
8617 assert(
8618 "console.log(ˇ'foo', [1, 2, 3]);",
8619 "console.log('foo', [1, 2, 3]ˇ);",
8620 );
8621
8622 // Bias forward if two options are equally likely
8623 assert(
8624 "let result = curried_fun()ˇ();",
8625 "let result = curried_fun()()ˇ;",
8626 );
8627
8628 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
8629 assert(
8630 indoc! {"
8631 function test() {
8632 console.log('test')ˇ
8633 }"},
8634 indoc! {"
8635 function test() {
8636 console.logˇ('test')
8637 }"},
8638 );
8639}
8640
8641#[gpui::test]
8642async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
8643 init_test(cx, |_| {});
8644
8645 let fs = FakeFs::new(cx.executor());
8646 fs.insert_tree(
8647 "/a",
8648 json!({
8649 "main.rs": "fn main() { let a = 5; }",
8650 "other.rs": "// Test file",
8651 }),
8652 )
8653 .await;
8654 let project = Project::test(fs, ["/a".as_ref()], cx).await;
8655
8656 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8657 language_registry.add(Arc::new(Language::new(
8658 LanguageConfig {
8659 name: "Rust".into(),
8660 matcher: LanguageMatcher {
8661 path_suffixes: vec!["rs".to_string()],
8662 ..Default::default()
8663 },
8664 brackets: BracketPairConfig {
8665 pairs: vec![BracketPair {
8666 start: "{".to_string(),
8667 end: "}".to_string(),
8668 close: true,
8669 surround: true,
8670 newline: true,
8671 }],
8672 disabled_scopes_by_bracket_ix: Vec::new(),
8673 },
8674 ..Default::default()
8675 },
8676 Some(tree_sitter_rust::language()),
8677 )));
8678 let mut fake_servers = language_registry.register_fake_lsp_adapter(
8679 "Rust",
8680 FakeLspAdapter {
8681 capabilities: lsp::ServerCapabilities {
8682 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
8683 first_trigger_character: "{".to_string(),
8684 more_trigger_character: None,
8685 }),
8686 ..Default::default()
8687 },
8688 ..Default::default()
8689 },
8690 );
8691
8692 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8693
8694 let cx = &mut VisualTestContext::from_window(*workspace, cx);
8695
8696 let worktree_id = workspace
8697 .update(cx, |workspace, cx| {
8698 workspace.project().update(cx, |project, cx| {
8699 project.worktrees().next().unwrap().read(cx).id()
8700 })
8701 })
8702 .unwrap();
8703
8704 let buffer = project
8705 .update(cx, |project, cx| {
8706 project.open_local_buffer("/a/main.rs", cx)
8707 })
8708 .await
8709 .unwrap();
8710 cx.executor().run_until_parked();
8711 cx.executor().start_waiting();
8712 let fake_server = fake_servers.next().await.unwrap();
8713 let editor_handle = workspace
8714 .update(cx, |workspace, cx| {
8715 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
8716 })
8717 .unwrap()
8718 .await
8719 .unwrap()
8720 .downcast::<Editor>()
8721 .unwrap();
8722
8723 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
8724 assert_eq!(
8725 params.text_document_position.text_document.uri,
8726 lsp::Url::from_file_path("/a/main.rs").unwrap(),
8727 );
8728 assert_eq!(
8729 params.text_document_position.position,
8730 lsp::Position::new(0, 21),
8731 );
8732
8733 Ok(Some(vec![lsp::TextEdit {
8734 new_text: "]".to_string(),
8735 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
8736 }]))
8737 });
8738
8739 editor_handle.update(cx, |editor, cx| {
8740 editor.focus(cx);
8741 editor.change_selections(None, cx, |s| {
8742 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
8743 });
8744 editor.handle_input("{", cx);
8745 });
8746
8747 cx.executor().run_until_parked();
8748
8749 _ = buffer.update(cx, |buffer, _| {
8750 assert_eq!(
8751 buffer.text(),
8752 "fn main() { let a = {5}; }",
8753 "No extra braces from on type formatting should appear in the buffer"
8754 )
8755 });
8756}
8757
8758#[gpui::test]
8759async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
8760 init_test(cx, |_| {});
8761
8762 let fs = FakeFs::new(cx.executor());
8763 fs.insert_tree(
8764 "/a",
8765 json!({
8766 "main.rs": "fn main() { let a = 5; }",
8767 "other.rs": "// Test file",
8768 }),
8769 )
8770 .await;
8771
8772 let project = Project::test(fs, ["/a".as_ref()], cx).await;
8773
8774 let server_restarts = Arc::new(AtomicUsize::new(0));
8775 let closure_restarts = Arc::clone(&server_restarts);
8776 let language_server_name = "test language server";
8777 let language_name: Arc<str> = "Rust".into();
8778
8779 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8780 language_registry.add(Arc::new(Language::new(
8781 LanguageConfig {
8782 name: Arc::clone(&language_name),
8783 matcher: LanguageMatcher {
8784 path_suffixes: vec!["rs".to_string()],
8785 ..Default::default()
8786 },
8787 ..Default::default()
8788 },
8789 Some(tree_sitter_rust::language()),
8790 )));
8791 let mut fake_servers = language_registry.register_fake_lsp_adapter(
8792 "Rust",
8793 FakeLspAdapter {
8794 name: language_server_name,
8795 initialization_options: Some(json!({
8796 "testOptionValue": true
8797 })),
8798 initializer: Some(Box::new(move |fake_server| {
8799 let task_restarts = Arc::clone(&closure_restarts);
8800 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
8801 task_restarts.fetch_add(1, atomic::Ordering::Release);
8802 futures::future::ready(Ok(()))
8803 });
8804 })),
8805 ..Default::default()
8806 },
8807 );
8808
8809 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8810 let _buffer = project
8811 .update(cx, |project, cx| {
8812 project.open_local_buffer("/a/main.rs", cx)
8813 })
8814 .await
8815 .unwrap();
8816 let _fake_server = fake_servers.next().await.unwrap();
8817 update_test_language_settings(cx, |language_settings| {
8818 language_settings.languages.insert(
8819 Arc::clone(&language_name),
8820 LanguageSettingsContent {
8821 tab_size: NonZeroU32::new(8),
8822 ..Default::default()
8823 },
8824 );
8825 });
8826 cx.executor().run_until_parked();
8827 assert_eq!(
8828 server_restarts.load(atomic::Ordering::Acquire),
8829 0,
8830 "Should not restart LSP server on an unrelated change"
8831 );
8832
8833 update_test_project_settings(cx, |project_settings| {
8834 project_settings.lsp.insert(
8835 "Some other server name".into(),
8836 LspSettings {
8837 binary: None,
8838 settings: None,
8839 initialization_options: Some(json!({
8840 "some other init value": false
8841 })),
8842 },
8843 );
8844 });
8845 cx.executor().run_until_parked();
8846 assert_eq!(
8847 server_restarts.load(atomic::Ordering::Acquire),
8848 0,
8849 "Should not restart LSP server on an unrelated LSP settings change"
8850 );
8851
8852 update_test_project_settings(cx, |project_settings| {
8853 project_settings.lsp.insert(
8854 language_server_name.into(),
8855 LspSettings {
8856 binary: None,
8857 settings: None,
8858 initialization_options: Some(json!({
8859 "anotherInitValue": false
8860 })),
8861 },
8862 );
8863 });
8864 cx.executor().run_until_parked();
8865 assert_eq!(
8866 server_restarts.load(atomic::Ordering::Acquire),
8867 1,
8868 "Should restart LSP server on a related LSP settings change"
8869 );
8870
8871 update_test_project_settings(cx, |project_settings| {
8872 project_settings.lsp.insert(
8873 language_server_name.into(),
8874 LspSettings {
8875 binary: None,
8876 settings: None,
8877 initialization_options: Some(json!({
8878 "anotherInitValue": false
8879 })),
8880 },
8881 );
8882 });
8883 cx.executor().run_until_parked();
8884 assert_eq!(
8885 server_restarts.load(atomic::Ordering::Acquire),
8886 1,
8887 "Should not restart LSP server on a related LSP settings change that is the same"
8888 );
8889
8890 update_test_project_settings(cx, |project_settings| {
8891 project_settings.lsp.insert(
8892 language_server_name.into(),
8893 LspSettings {
8894 binary: None,
8895 settings: None,
8896 initialization_options: None,
8897 },
8898 );
8899 });
8900 cx.executor().run_until_parked();
8901 assert_eq!(
8902 server_restarts.load(atomic::Ordering::Acquire),
8903 2,
8904 "Should restart LSP server on another related LSP settings change"
8905 );
8906}
8907
8908#[gpui::test]
8909async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
8910 init_test(cx, |_| {});
8911
8912 let mut cx = EditorLspTestContext::new_rust(
8913 lsp::ServerCapabilities {
8914 completion_provider: Some(lsp::CompletionOptions {
8915 trigger_characters: Some(vec![".".to_string()]),
8916 resolve_provider: Some(true),
8917 ..Default::default()
8918 }),
8919 ..Default::default()
8920 },
8921 cx,
8922 )
8923 .await;
8924
8925 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8926 cx.simulate_keystroke(".");
8927 let completion_item = lsp::CompletionItem {
8928 label: "some".into(),
8929 kind: Some(lsp::CompletionItemKind::SNIPPET),
8930 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8931 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8932 kind: lsp::MarkupKind::Markdown,
8933 value: "```rust\nSome(2)\n```".to_string(),
8934 })),
8935 deprecated: Some(false),
8936 sort_text: Some("fffffff2".to_string()),
8937 filter_text: Some("some".to_string()),
8938 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8939 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8940 range: lsp::Range {
8941 start: lsp::Position {
8942 line: 0,
8943 character: 22,
8944 },
8945 end: lsp::Position {
8946 line: 0,
8947 character: 22,
8948 },
8949 },
8950 new_text: "Some(2)".to_string(),
8951 })),
8952 additional_text_edits: Some(vec![lsp::TextEdit {
8953 range: lsp::Range {
8954 start: lsp::Position {
8955 line: 0,
8956 character: 20,
8957 },
8958 end: lsp::Position {
8959 line: 0,
8960 character: 22,
8961 },
8962 },
8963 new_text: "".to_string(),
8964 }]),
8965 ..Default::default()
8966 };
8967
8968 let closure_completion_item = completion_item.clone();
8969 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8970 let task_completion_item = closure_completion_item.clone();
8971 async move {
8972 Ok(Some(lsp::CompletionResponse::Array(vec![
8973 task_completion_item,
8974 ])))
8975 }
8976 });
8977
8978 request.next().await;
8979
8980 cx.condition(|editor, _| editor.context_menu_visible())
8981 .await;
8982 let apply_additional_edits = cx.update_editor(|editor, cx| {
8983 editor
8984 .confirm_completion(&ConfirmCompletion::default(), cx)
8985 .unwrap()
8986 });
8987 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
8988
8989 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
8990 let task_completion_item = completion_item.clone();
8991 async move { Ok(task_completion_item) }
8992 })
8993 .next()
8994 .await
8995 .unwrap();
8996 apply_additional_edits.await.unwrap();
8997 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
8998}
8999
9000#[gpui::test]
9001async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
9002 init_test(cx, |_| {});
9003
9004 let mut cx = EditorLspTestContext::new(
9005 Language::new(
9006 LanguageConfig {
9007 matcher: LanguageMatcher {
9008 path_suffixes: vec!["jsx".into()],
9009 ..Default::default()
9010 },
9011 overrides: [(
9012 "element".into(),
9013 LanguageConfigOverride {
9014 word_characters: Override::Set(['-'].into_iter().collect()),
9015 ..Default::default()
9016 },
9017 )]
9018 .into_iter()
9019 .collect(),
9020 ..Default::default()
9021 },
9022 Some(tree_sitter_typescript::language_tsx()),
9023 )
9024 .with_override_query("(jsx_self_closing_element) @element")
9025 .unwrap(),
9026 lsp::ServerCapabilities {
9027 completion_provider: Some(lsp::CompletionOptions {
9028 trigger_characters: Some(vec![":".to_string()]),
9029 ..Default::default()
9030 }),
9031 ..Default::default()
9032 },
9033 cx,
9034 )
9035 .await;
9036
9037 cx.lsp
9038 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9039 Ok(Some(lsp::CompletionResponse::Array(vec![
9040 lsp::CompletionItem {
9041 label: "bg-blue".into(),
9042 ..Default::default()
9043 },
9044 lsp::CompletionItem {
9045 label: "bg-red".into(),
9046 ..Default::default()
9047 },
9048 lsp::CompletionItem {
9049 label: "bg-yellow".into(),
9050 ..Default::default()
9051 },
9052 ])))
9053 });
9054
9055 cx.set_state(r#"<p class="bgˇ" />"#);
9056
9057 // Trigger completion when typing a dash, because the dash is an extra
9058 // word character in the 'element' scope, which contains the cursor.
9059 cx.simulate_keystroke("-");
9060 cx.executor().run_until_parked();
9061 cx.update_editor(|editor, _| {
9062 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9063 assert_eq!(
9064 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9065 &["bg-red", "bg-blue", "bg-yellow"]
9066 );
9067 } else {
9068 panic!("expected completion menu to be open");
9069 }
9070 });
9071
9072 cx.simulate_keystroke("l");
9073 cx.executor().run_until_parked();
9074 cx.update_editor(|editor, _| {
9075 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9076 assert_eq!(
9077 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9078 &["bg-blue", "bg-yellow"]
9079 );
9080 } else {
9081 panic!("expected completion menu to be open");
9082 }
9083 });
9084
9085 // When filtering completions, consider the character after the '-' to
9086 // be the start of a subword.
9087 cx.set_state(r#"<p class="yelˇ" />"#);
9088 cx.simulate_keystroke("l");
9089 cx.executor().run_until_parked();
9090 cx.update_editor(|editor, _| {
9091 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9092 assert_eq!(
9093 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9094 &["bg-yellow"]
9095 );
9096 } else {
9097 panic!("expected completion menu to be open");
9098 }
9099 });
9100}
9101
9102#[gpui::test]
9103async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
9104 init_test(cx, |settings| {
9105 settings.defaults.formatter = Some(language_settings::Formatter::Prettier)
9106 });
9107
9108 let fs = FakeFs::new(cx.executor());
9109 fs.insert_file("/file.ts", Default::default()).await;
9110
9111 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
9112 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9113
9114 language_registry.add(Arc::new(Language::new(
9115 LanguageConfig {
9116 name: "TypeScript".into(),
9117 matcher: LanguageMatcher {
9118 path_suffixes: vec!["ts".to_string()],
9119 ..Default::default()
9120 },
9121 ..Default::default()
9122 },
9123 Some(tree_sitter_rust::language()),
9124 )));
9125 update_test_language_settings(cx, |settings| {
9126 settings.defaults.prettier = Some(PrettierSettings {
9127 allowed: true,
9128 ..PrettierSettings::default()
9129 });
9130 });
9131
9132 let test_plugin = "test_plugin";
9133 let _ = language_registry.register_fake_lsp_adapter(
9134 "TypeScript",
9135 FakeLspAdapter {
9136 prettier_plugins: vec![test_plugin],
9137 ..Default::default()
9138 },
9139 );
9140
9141 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
9142 let buffer = project
9143 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
9144 .await
9145 .unwrap();
9146
9147 let buffer_text = "one\ntwo\nthree\n";
9148 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9149 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9150 _ = editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
9151
9152 editor
9153 .update(cx, |editor, cx| {
9154 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
9155 })
9156 .unwrap()
9157 .await;
9158 assert_eq!(
9159 editor.update(cx, |editor, cx| editor.text(cx)),
9160 buffer_text.to_string() + prettier_format_suffix,
9161 "Test prettier formatting was not applied to the original buffer text",
9162 );
9163
9164 update_test_language_settings(cx, |settings| {
9165 settings.defaults.formatter = Some(language_settings::Formatter::Auto)
9166 });
9167 let format = editor.update(cx, |editor, cx| {
9168 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
9169 });
9170 format.await.unwrap();
9171 assert_eq!(
9172 editor.update(cx, |editor, cx| editor.text(cx)),
9173 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
9174 "Autoformatting (via test prettier) was not applied to the original buffer text",
9175 );
9176}
9177
9178#[gpui::test]
9179async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
9180 init_test(cx, |_| {});
9181 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9182 let base_text = indoc! {r#"struct Row;
9183struct Row1;
9184struct Row2;
9185
9186struct Row4;
9187struct Row5;
9188struct Row6;
9189
9190struct Row8;
9191struct Row9;
9192struct Row10;"#};
9193
9194 // When addition hunks are not adjacent to carets, no hunk revert is performed
9195 assert_hunk_revert(
9196 indoc! {r#"struct Row;
9197 struct Row1;
9198 struct Row1.1;
9199 struct Row1.2;
9200 struct Row2;ˇ
9201
9202 struct Row4;
9203 struct Row5;
9204 struct Row6;
9205
9206 struct Row8;
9207 ˇstruct Row9;
9208 struct Row9.1;
9209 struct Row9.2;
9210 struct Row9.3;
9211 struct Row10;"#},
9212 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
9213 indoc! {r#"struct Row;
9214 struct Row1;
9215 struct Row1.1;
9216 struct Row1.2;
9217 struct Row2;ˇ
9218
9219 struct Row4;
9220 struct Row5;
9221 struct Row6;
9222
9223 struct Row8;
9224 ˇstruct Row9;
9225 struct Row9.1;
9226 struct Row9.2;
9227 struct Row9.3;
9228 struct Row10;"#},
9229 base_text,
9230 &mut cx,
9231 );
9232 // Same for selections
9233 assert_hunk_revert(
9234 indoc! {r#"struct Row;
9235 struct Row1;
9236 struct Row2;
9237 struct Row2.1;
9238 struct Row2.2;
9239 «ˇ
9240 struct Row4;
9241 struct» Row5;
9242 «struct Row6;
9243 ˇ»
9244 struct Row9.1;
9245 struct Row9.2;
9246 struct Row9.3;
9247 struct Row8;
9248 struct Row9;
9249 struct Row10;"#},
9250 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
9251 indoc! {r#"struct Row;
9252 struct Row1;
9253 struct Row2;
9254 struct Row2.1;
9255 struct Row2.2;
9256 «ˇ
9257 struct Row4;
9258 struct» Row5;
9259 «struct Row6;
9260 ˇ»
9261 struct Row9.1;
9262 struct Row9.2;
9263 struct Row9.3;
9264 struct Row8;
9265 struct Row9;
9266 struct Row10;"#},
9267 base_text,
9268 &mut cx,
9269 );
9270
9271 // When carets and selections intersect the addition hunks, those are reverted.
9272 // Adjacent carets got merged.
9273 assert_hunk_revert(
9274 indoc! {r#"struct Row;
9275 ˇ// something on the top
9276 struct Row1;
9277 struct Row2;
9278 struct Roˇw3.1;
9279 struct Row2.2;
9280 struct Row2.3;ˇ
9281
9282 struct Row4;
9283 struct ˇRow5.1;
9284 struct Row5.2;
9285 struct «Rowˇ»5.3;
9286 struct Row5;
9287 struct Row6;
9288 ˇ
9289 struct Row9.1;
9290 struct «Rowˇ»9.2;
9291 struct «ˇRow»9.3;
9292 struct Row8;
9293 struct Row9;
9294 «ˇ// something on bottom»
9295 struct Row10;"#},
9296 vec![
9297 DiffHunkStatus::Added,
9298 DiffHunkStatus::Added,
9299 DiffHunkStatus::Added,
9300 DiffHunkStatus::Added,
9301 DiffHunkStatus::Added,
9302 ],
9303 indoc! {r#"struct Row;
9304 ˇstruct Row1;
9305 struct Row2;
9306 ˇ
9307 struct Row4;
9308 ˇstruct Row5;
9309 struct Row6;
9310 ˇ
9311 ˇstruct Row8;
9312 struct Row9;
9313 ˇstruct Row10;"#},
9314 base_text,
9315 &mut cx,
9316 );
9317}
9318
9319#[gpui::test]
9320async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
9321 init_test(cx, |_| {});
9322 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9323 let base_text = indoc! {r#"struct Row;
9324struct Row1;
9325struct Row2;
9326
9327struct Row4;
9328struct Row5;
9329struct Row6;
9330
9331struct Row8;
9332struct Row9;
9333struct Row10;"#};
9334
9335 // Modification hunks behave the same as the addition ones.
9336 assert_hunk_revert(
9337 indoc! {r#"struct Row;
9338 struct Row1;
9339 struct Row33;
9340 ˇ
9341 struct Row4;
9342 struct Row5;
9343 struct Row6;
9344 ˇ
9345 struct Row99;
9346 struct Row9;
9347 struct Row10;"#},
9348 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
9349 indoc! {r#"struct Row;
9350 struct Row1;
9351 struct Row33;
9352 ˇ
9353 struct Row4;
9354 struct Row5;
9355 struct Row6;
9356 ˇ
9357 struct Row99;
9358 struct Row9;
9359 struct Row10;"#},
9360 base_text,
9361 &mut cx,
9362 );
9363 assert_hunk_revert(
9364 indoc! {r#"struct Row;
9365 struct Row1;
9366 struct Row33;
9367 «ˇ
9368 struct Row4;
9369 struct» Row5;
9370 «struct Row6;
9371 ˇ»
9372 struct Row99;
9373 struct Row9;
9374 struct Row10;"#},
9375 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
9376 indoc! {r#"struct Row;
9377 struct Row1;
9378 struct Row33;
9379 «ˇ
9380 struct Row4;
9381 struct» Row5;
9382 «struct Row6;
9383 ˇ»
9384 struct Row99;
9385 struct Row9;
9386 struct Row10;"#},
9387 base_text,
9388 &mut cx,
9389 );
9390
9391 assert_hunk_revert(
9392 indoc! {r#"ˇstruct Row1.1;
9393 struct Row1;
9394 «ˇstr»uct Row22;
9395
9396 struct ˇRow44;
9397 struct Row5;
9398 struct «Rˇ»ow66;ˇ
9399
9400 «struˇ»ct Row88;
9401 struct Row9;
9402 struct Row1011;ˇ"#},
9403 vec![
9404 DiffHunkStatus::Modified,
9405 DiffHunkStatus::Modified,
9406 DiffHunkStatus::Modified,
9407 DiffHunkStatus::Modified,
9408 DiffHunkStatus::Modified,
9409 DiffHunkStatus::Modified,
9410 ],
9411 indoc! {r#"struct Row;
9412 ˇstruct Row1;
9413 struct Row2;
9414 ˇ
9415 struct Row4;
9416 ˇstruct Row5;
9417 struct Row6;
9418 ˇ
9419 struct Row8;
9420 ˇstruct Row9;
9421 struct Row10;ˇ"#},
9422 base_text,
9423 &mut cx,
9424 );
9425}
9426
9427#[gpui::test]
9428async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
9429 init_test(cx, |_| {});
9430 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9431 let base_text = indoc! {r#"struct Row;
9432struct Row1;
9433struct Row2;
9434
9435struct Row4;
9436struct Row5;
9437struct Row6;
9438
9439struct Row8;
9440struct Row9;
9441struct Row10;"#};
9442
9443 // Deletion hunks trigger with carets on ajacent rows, so carets and selections have to stay farther to avoid the revert
9444 assert_hunk_revert(
9445 indoc! {r#"struct Row;
9446 struct Row2;
9447
9448 ˇstruct Row4;
9449 struct Row5;
9450 struct Row6;
9451 ˇ
9452 struct Row8;
9453 struct Row10;"#},
9454 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
9455 indoc! {r#"struct Row;
9456 struct Row2;
9457
9458 ˇstruct Row4;
9459 struct Row5;
9460 struct Row6;
9461 ˇ
9462 struct Row8;
9463 struct Row10;"#},
9464 base_text,
9465 &mut cx,
9466 );
9467 assert_hunk_revert(
9468 indoc! {r#"struct Row;
9469 struct Row2;
9470
9471 «ˇstruct Row4;
9472 struct» Row5;
9473 «struct Row6;
9474 ˇ»
9475 struct Row8;
9476 struct Row10;"#},
9477 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
9478 indoc! {r#"struct Row;
9479 struct Row2;
9480
9481 «ˇstruct Row4;
9482 struct» Row5;
9483 «struct Row6;
9484 ˇ»
9485 struct Row8;
9486 struct Row10;"#},
9487 base_text,
9488 &mut cx,
9489 );
9490
9491 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
9492 assert_hunk_revert(
9493 indoc! {r#"struct Row;
9494 ˇstruct Row2;
9495
9496 struct Row4;
9497 struct Row5;
9498 struct Row6;
9499
9500 struct Row8;ˇ
9501 struct Row10;"#},
9502 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
9503 indoc! {r#"struct Row;
9504 struct Row1;
9505 ˇstruct Row2;
9506
9507 struct Row4;
9508 struct Row5;
9509 struct Row6;
9510
9511 struct Row8;ˇ
9512 struct Row9;
9513 struct Row10;"#},
9514 base_text,
9515 &mut cx,
9516 );
9517 assert_hunk_revert(
9518 indoc! {r#"struct Row;
9519 struct Row2«ˇ;
9520 struct Row4;
9521 struct» Row5;
9522 «struct Row6;
9523
9524 struct Row8;ˇ»
9525 struct Row10;"#},
9526 vec![
9527 DiffHunkStatus::Removed,
9528 DiffHunkStatus::Removed,
9529 DiffHunkStatus::Removed,
9530 ],
9531 indoc! {r#"struct Row;
9532 struct Row1;
9533 struct Row2«ˇ;
9534
9535 struct Row4;
9536 struct» Row5;
9537 «struct Row6;
9538
9539 struct Row8;ˇ»
9540 struct Row9;
9541 struct Row10;"#},
9542 base_text,
9543 &mut cx,
9544 );
9545}
9546
9547#[gpui::test]
9548async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
9549 init_test(cx, |_| {});
9550
9551 let cols = 4;
9552 let rows = 10;
9553 let sample_text_1 = sample_text(rows, cols, 'a');
9554 assert_eq!(
9555 sample_text_1,
9556 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
9557 );
9558 let sample_text_2 = sample_text(rows, cols, 'l');
9559 assert_eq!(
9560 sample_text_2,
9561 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
9562 );
9563 let sample_text_3 = sample_text(rows, cols, 'v');
9564 assert_eq!(
9565 sample_text_3,
9566 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
9567 );
9568
9569 fn diff_every_buffer_row(
9570 buffer: &Model<Buffer>,
9571 sample_text: String,
9572 cols: usize,
9573 cx: &mut gpui::TestAppContext,
9574 ) {
9575 // revert first character in each row, creating one large diff hunk per buffer
9576 let is_first_char = |offset: usize| offset % cols == 0;
9577 buffer.update(cx, |buffer, cx| {
9578 buffer.set_text(
9579 sample_text
9580 .chars()
9581 .enumerate()
9582 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
9583 .collect::<String>(),
9584 cx,
9585 );
9586 buffer.set_diff_base(Some(sample_text), cx);
9587 });
9588 cx.executor().run_until_parked();
9589 }
9590
9591 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
9592 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
9593
9594 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
9595 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
9596
9597 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
9598 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
9599
9600 let multibuffer = cx.new_model(|cx| {
9601 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
9602 multibuffer.push_excerpts(
9603 buffer_1.clone(),
9604 [
9605 ExcerptRange {
9606 context: Point::new(0, 0)..Point::new(3, 0),
9607 primary: None,
9608 },
9609 ExcerptRange {
9610 context: Point::new(5, 0)..Point::new(7, 0),
9611 primary: None,
9612 },
9613 ExcerptRange {
9614 context: Point::new(9, 0)..Point::new(10, 4),
9615 primary: None,
9616 },
9617 ],
9618 cx,
9619 );
9620 multibuffer.push_excerpts(
9621 buffer_2.clone(),
9622 [
9623 ExcerptRange {
9624 context: Point::new(0, 0)..Point::new(3, 0),
9625 primary: None,
9626 },
9627 ExcerptRange {
9628 context: Point::new(5, 0)..Point::new(7, 0),
9629 primary: None,
9630 },
9631 ExcerptRange {
9632 context: Point::new(9, 0)..Point::new(10, 4),
9633 primary: None,
9634 },
9635 ],
9636 cx,
9637 );
9638 multibuffer.push_excerpts(
9639 buffer_3.clone(),
9640 [
9641 ExcerptRange {
9642 context: Point::new(0, 0)..Point::new(3, 0),
9643 primary: None,
9644 },
9645 ExcerptRange {
9646 context: Point::new(5, 0)..Point::new(7, 0),
9647 primary: None,
9648 },
9649 ExcerptRange {
9650 context: Point::new(9, 0)..Point::new(10, 4),
9651 primary: None,
9652 },
9653 ],
9654 cx,
9655 );
9656 multibuffer
9657 });
9658
9659 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9660 editor.update(cx, |editor, cx| {
9661 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");
9662 editor.select_all(&SelectAll, cx);
9663 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
9664 });
9665 cx.executor().run_until_parked();
9666 // When all ranges are selected, all buffer hunks are reverted.
9667 editor.update(cx, |editor, cx| {
9668 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");
9669 });
9670 buffer_1.update(cx, |buffer, _| {
9671 assert_eq!(buffer.text(), sample_text_1);
9672 });
9673 buffer_2.update(cx, |buffer, _| {
9674 assert_eq!(buffer.text(), sample_text_2);
9675 });
9676 buffer_3.update(cx, |buffer, _| {
9677 assert_eq!(buffer.text(), sample_text_3);
9678 });
9679
9680 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
9681 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
9682 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
9683 editor.update(cx, |editor, cx| {
9684 editor.change_selections(None, cx, |s| {
9685 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
9686 });
9687 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
9688 });
9689 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
9690 // but not affect buffer_2 and its related excerpts.
9691 editor.update(cx, |editor, cx| {
9692 assert_eq!(
9693 editor.text(cx),
9694 "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"
9695 );
9696 });
9697 buffer_1.update(cx, |buffer, _| {
9698 assert_eq!(buffer.text(), sample_text_1);
9699 });
9700 buffer_2.update(cx, |buffer, _| {
9701 assert_eq!(
9702 buffer.text(),
9703 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
9704 );
9705 });
9706 buffer_3.update(cx, |buffer, _| {
9707 assert_eq!(
9708 buffer.text(),
9709 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
9710 );
9711 });
9712}
9713
9714#[gpui::test]
9715async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
9716 init_test(cx, |_| {});
9717
9718 let cols = 4;
9719 let rows = 10;
9720 let sample_text_1 = sample_text(rows, cols, 'a');
9721 assert_eq!(
9722 sample_text_1,
9723 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
9724 );
9725 let sample_text_2 = sample_text(rows, cols, 'l');
9726 assert_eq!(
9727 sample_text_2,
9728 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
9729 );
9730 let sample_text_3 = sample_text(rows, cols, 'v');
9731 assert_eq!(
9732 sample_text_3,
9733 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
9734 );
9735
9736 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
9737 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
9738 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
9739
9740 let multi_buffer = cx.new_model(|cx| {
9741 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
9742 multibuffer.push_excerpts(
9743 buffer_1.clone(),
9744 [
9745 ExcerptRange {
9746 context: Point::new(0, 0)..Point::new(3, 0),
9747 primary: None,
9748 },
9749 ExcerptRange {
9750 context: Point::new(5, 0)..Point::new(7, 0),
9751 primary: None,
9752 },
9753 ExcerptRange {
9754 context: Point::new(9, 0)..Point::new(10, 4),
9755 primary: None,
9756 },
9757 ],
9758 cx,
9759 );
9760 multibuffer.push_excerpts(
9761 buffer_2.clone(),
9762 [
9763 ExcerptRange {
9764 context: Point::new(0, 0)..Point::new(3, 0),
9765 primary: None,
9766 },
9767 ExcerptRange {
9768 context: Point::new(5, 0)..Point::new(7, 0),
9769 primary: None,
9770 },
9771 ExcerptRange {
9772 context: Point::new(9, 0)..Point::new(10, 4),
9773 primary: None,
9774 },
9775 ],
9776 cx,
9777 );
9778 multibuffer.push_excerpts(
9779 buffer_3.clone(),
9780 [
9781 ExcerptRange {
9782 context: Point::new(0, 0)..Point::new(3, 0),
9783 primary: None,
9784 },
9785 ExcerptRange {
9786 context: Point::new(5, 0)..Point::new(7, 0),
9787 primary: None,
9788 },
9789 ExcerptRange {
9790 context: Point::new(9, 0)..Point::new(10, 4),
9791 primary: None,
9792 },
9793 ],
9794 cx,
9795 );
9796 multibuffer
9797 });
9798
9799 let fs = FakeFs::new(cx.executor());
9800 fs.insert_tree(
9801 "/a",
9802 json!({
9803 "main.rs": sample_text_1,
9804 "other.rs": sample_text_2,
9805 "lib.rs": sample_text_3,
9806 }),
9807 )
9808 .await;
9809 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9810 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9811 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9812 let multi_buffer_editor = cx.new_view(|cx| {
9813 Editor::new(
9814 EditorMode::Full,
9815 multi_buffer,
9816 Some(project.clone()),
9817 true,
9818 cx,
9819 )
9820 });
9821 let multibuffer_item_id = workspace
9822 .update(cx, |workspace, cx| {
9823 assert!(
9824 workspace.active_item(cx).is_none(),
9825 "active item should be None before the first item is added"
9826 );
9827 workspace.add_item_to_active_pane(Box::new(multi_buffer_editor.clone()), None, cx);
9828 let active_item = workspace
9829 .active_item(cx)
9830 .expect("should have an active item after adding the multi buffer");
9831 assert!(
9832 !active_item.is_singleton(cx),
9833 "A multi buffer was expected to active after adding"
9834 );
9835 active_item.item_id()
9836 })
9837 .unwrap();
9838 cx.executor().run_until_parked();
9839
9840 multi_buffer_editor.update(cx, |editor, cx| {
9841 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
9842 editor.open_excerpts(&OpenExcerpts, cx);
9843 });
9844 cx.executor().run_until_parked();
9845 let first_item_id = workspace
9846 .update(cx, |workspace, cx| {
9847 let active_item = workspace
9848 .active_item(cx)
9849 .expect("should have an active item after navigating into the 1st buffer");
9850 let first_item_id = active_item.item_id();
9851 assert_ne!(
9852 first_item_id, multibuffer_item_id,
9853 "Should navigate into the 1st buffer and activate it"
9854 );
9855 assert!(
9856 active_item.is_singleton(cx),
9857 "New active item should be a singleton buffer"
9858 );
9859 assert_eq!(
9860 active_item
9861 .act_as::<Editor>(cx)
9862 .expect("should have navigated into an editor for the 1st buffer")
9863 .read(cx)
9864 .text(cx),
9865 sample_text_1
9866 );
9867
9868 workspace
9869 .go_back(workspace.active_pane().downgrade(), cx)
9870 .detach_and_log_err(cx);
9871
9872 first_item_id
9873 })
9874 .unwrap();
9875 cx.executor().run_until_parked();
9876 workspace
9877 .update(cx, |workspace, cx| {
9878 let active_item = workspace
9879 .active_item(cx)
9880 .expect("should have an active item after navigating back");
9881 assert_eq!(
9882 active_item.item_id(),
9883 multibuffer_item_id,
9884 "Should navigate back to the multi buffer"
9885 );
9886 assert!(!active_item.is_singleton(cx));
9887 })
9888 .unwrap();
9889
9890 multi_buffer_editor.update(cx, |editor, cx| {
9891 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
9892 s.select_ranges(Some(39..40))
9893 });
9894 editor.open_excerpts(&OpenExcerpts, cx);
9895 });
9896 cx.executor().run_until_parked();
9897 let second_item_id = workspace
9898 .update(cx, |workspace, cx| {
9899 let active_item = workspace
9900 .active_item(cx)
9901 .expect("should have an active item after navigating into the 2nd buffer");
9902 let second_item_id = active_item.item_id();
9903 assert_ne!(
9904 second_item_id, multibuffer_item_id,
9905 "Should navigate away from the multibuffer"
9906 );
9907 assert_ne!(
9908 second_item_id, first_item_id,
9909 "Should navigate into the 2nd buffer and activate it"
9910 );
9911 assert!(
9912 active_item.is_singleton(cx),
9913 "New active item should be a singleton buffer"
9914 );
9915 assert_eq!(
9916 active_item
9917 .act_as::<Editor>(cx)
9918 .expect("should have navigated into an editor")
9919 .read(cx)
9920 .text(cx),
9921 sample_text_2
9922 );
9923
9924 workspace
9925 .go_back(workspace.active_pane().downgrade(), cx)
9926 .detach_and_log_err(cx);
9927
9928 second_item_id
9929 })
9930 .unwrap();
9931 cx.executor().run_until_parked();
9932 workspace
9933 .update(cx, |workspace, cx| {
9934 let active_item = workspace
9935 .active_item(cx)
9936 .expect("should have an active item after navigating back from the 2nd buffer");
9937 assert_eq!(
9938 active_item.item_id(),
9939 multibuffer_item_id,
9940 "Should navigate back from the 2nd buffer to the multi buffer"
9941 );
9942 assert!(!active_item.is_singleton(cx));
9943 })
9944 .unwrap();
9945
9946 multi_buffer_editor.update(cx, |editor, cx| {
9947 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
9948 s.select_ranges(Some(60..70))
9949 });
9950 editor.open_excerpts(&OpenExcerpts, cx);
9951 });
9952 cx.executor().run_until_parked();
9953 workspace
9954 .update(cx, |workspace, cx| {
9955 let active_item = workspace
9956 .active_item(cx)
9957 .expect("should have an active item after navigating into the 3rd buffer");
9958 let third_item_id = active_item.item_id();
9959 assert_ne!(
9960 third_item_id, multibuffer_item_id,
9961 "Should navigate into the 3rd buffer and activate it"
9962 );
9963 assert_ne!(third_item_id, first_item_id);
9964 assert_ne!(third_item_id, second_item_id);
9965 assert!(
9966 active_item.is_singleton(cx),
9967 "New active item should be a singleton buffer"
9968 );
9969 assert_eq!(
9970 active_item
9971 .act_as::<Editor>(cx)
9972 .expect("should have navigated into an editor")
9973 .read(cx)
9974 .text(cx),
9975 sample_text_3
9976 );
9977
9978 workspace
9979 .go_back(workspace.active_pane().downgrade(), cx)
9980 .detach_and_log_err(cx);
9981 })
9982 .unwrap();
9983 cx.executor().run_until_parked();
9984 workspace
9985 .update(cx, |workspace, cx| {
9986 let active_item = workspace
9987 .active_item(cx)
9988 .expect("should have an active item after navigating back from the 3rd buffer");
9989 assert_eq!(
9990 active_item.item_id(),
9991 multibuffer_item_id,
9992 "Should navigate back from the 3rd buffer to the multi buffer"
9993 );
9994 assert!(!active_item.is_singleton(cx));
9995 })
9996 .unwrap();
9997}
9998
9999#[gpui::test]
10000async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10001 init_test(cx, |_| {});
10002
10003 let mut cx = EditorTestContext::new(cx).await;
10004
10005 let diff_base = r#"
10006 use some::mod;
10007
10008 const A: u32 = 42;
10009
10010 fn main() {
10011 println!("hello");
10012
10013 println!("world");
10014 }
10015 "#
10016 .unindent();
10017
10018 cx.set_state(
10019 &r#"
10020 use some::modified;
10021
10022 ˇ
10023 fn main() {
10024 println!("hello there");
10025
10026 println!("around the");
10027 println!("world");
10028 }
10029 "#
10030 .unindent(),
10031 );
10032
10033 cx.set_diff_base(Some(&diff_base));
10034 executor.run_until_parked();
10035 let unexpanded_hunks = vec![
10036 (
10037 "use some::mod;\n".to_string(),
10038 DiffHunkStatus::Modified,
10039 DisplayRow(0)..DisplayRow(1),
10040 ),
10041 (
10042 "const A: u32 = 42;\n".to_string(),
10043 DiffHunkStatus::Removed,
10044 DisplayRow(2)..DisplayRow(2),
10045 ),
10046 (
10047 " println!(\"hello\");\n".to_string(),
10048 DiffHunkStatus::Modified,
10049 DisplayRow(4)..DisplayRow(5),
10050 ),
10051 (
10052 "".to_string(),
10053 DiffHunkStatus::Added,
10054 DisplayRow(6)..DisplayRow(7),
10055 ),
10056 ];
10057 cx.update_editor(|editor, cx| {
10058 let snapshot = editor.snapshot(cx);
10059 let all_hunks = editor_hunks(editor, &snapshot, cx);
10060 assert_eq!(all_hunks, unexpanded_hunks);
10061 });
10062
10063 cx.update_editor(|editor, cx| {
10064 for _ in 0..4 {
10065 editor.go_to_hunk(&GoToHunk, cx);
10066 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10067 }
10068 });
10069 executor.run_until_parked();
10070 cx.assert_editor_state(
10071 &r#"
10072 use some::modified;
10073
10074 ˇ
10075 fn main() {
10076 println!("hello there");
10077
10078 println!("around the");
10079 println!("world");
10080 }
10081 "#
10082 .unindent(),
10083 );
10084 cx.update_editor(|editor, cx| {
10085 let snapshot = editor.snapshot(cx);
10086 let all_hunks = editor_hunks(editor, &snapshot, cx);
10087 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10088 assert_eq!(
10089 expanded_hunks_background_highlights(editor, cx),
10090 vec![DisplayRow(1)..=DisplayRow(1), DisplayRow(7)..=DisplayRow(7), DisplayRow(9)..=DisplayRow(9)],
10091 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
10092 );
10093 assert_eq!(
10094 all_hunks,
10095 vec![
10096 ("use some::mod;\n".to_string(), DiffHunkStatus::Modified, DisplayRow(1)..DisplayRow(2)),
10097 ("const A: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(4)..DisplayRow(4)),
10098 (" println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(7)..DisplayRow(8)),
10099 ("".to_string(), DiffHunkStatus::Added, DisplayRow(9)..DisplayRow(10)),
10100 ],
10101 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
10102 (from modified and removed hunks)"
10103 );
10104 assert_eq!(
10105 all_hunks, all_expanded_hunks,
10106 "Editor hunks should not change and all be expanded"
10107 );
10108 });
10109
10110 cx.update_editor(|editor, cx| {
10111 editor.cancel(&Cancel, cx);
10112
10113 let snapshot = editor.snapshot(cx);
10114 let all_hunks = editor_hunks(editor, &snapshot, cx);
10115 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
10116 assert_eq!(
10117 expanded_hunks_background_highlights(editor, cx),
10118 Vec::new(),
10119 "After cancelling in editor, no git highlights should be left"
10120 );
10121 assert_eq!(
10122 all_expanded_hunks,
10123 Vec::new(),
10124 "After cancelling in editor, no hunks should be expanded"
10125 );
10126 assert_eq!(
10127 all_hunks, unexpanded_hunks,
10128 "After cancelling in editor, regular hunks' coordinates should get back to normal"
10129 );
10130 });
10131}
10132
10133#[gpui::test]
10134async fn test_toggled_diff_base_change(
10135 executor: BackgroundExecutor,
10136 cx: &mut gpui::TestAppContext,
10137) {
10138 init_test(cx, |_| {});
10139
10140 let mut cx = EditorTestContext::new(cx).await;
10141
10142 let diff_base = r#"
10143 use some::mod1;
10144 use some::mod2;
10145
10146 const A: u32 = 42;
10147 const B: u32 = 42;
10148 const C: u32 = 42;
10149
10150 fn main(ˇ) {
10151 println!("hello");
10152
10153 println!("world");
10154 }
10155 "#
10156 .unindent();
10157
10158 cx.set_state(
10159 &r#"
10160 use some::mod2;
10161
10162 const A: u32 = 42;
10163 const C: u32 = 42;
10164
10165 fn main(ˇ) {
10166 //println!("hello");
10167
10168 println!("world");
10169 //
10170 //
10171 }
10172 "#
10173 .unindent(),
10174 );
10175
10176 cx.set_diff_base(Some(&diff_base));
10177 executor.run_until_parked();
10178 cx.update_editor(|editor, cx| {
10179 let snapshot = editor.snapshot(cx);
10180 let all_hunks = editor_hunks(editor, &snapshot, cx);
10181 assert_eq!(
10182 all_hunks,
10183 vec![
10184 (
10185 "use some::mod1;\n".to_string(),
10186 DiffHunkStatus::Removed,
10187 DisplayRow(0)..DisplayRow(0)
10188 ),
10189 (
10190 "const B: u32 = 42;\n".to_string(),
10191 DiffHunkStatus::Removed,
10192 DisplayRow(3)..DisplayRow(3)
10193 ),
10194 (
10195 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
10196 DiffHunkStatus::Modified,
10197 DisplayRow(5)..DisplayRow(7)
10198 ),
10199 (
10200 "".to_string(),
10201 DiffHunkStatus::Added,
10202 DisplayRow(9)..DisplayRow(11)
10203 ),
10204 ]
10205 );
10206 });
10207
10208 cx.update_editor(|editor, cx| {
10209 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10210 });
10211 executor.run_until_parked();
10212 cx.assert_editor_state(
10213 &r#"
10214 use some::mod2;
10215
10216 const A: u32 = 42;
10217 const C: u32 = 42;
10218
10219 fn main(ˇ) {
10220 //println!("hello");
10221
10222 println!("world");
10223 //
10224 //
10225 }
10226 "#
10227 .unindent(),
10228 );
10229 cx.update_editor(|editor, cx| {
10230 let snapshot = editor.snapshot(cx);
10231 let all_hunks = editor_hunks(editor, &snapshot, cx);
10232 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10233 assert_eq!(
10234 expanded_hunks_background_highlights(editor, cx),
10235 vec![DisplayRow(9)..=DisplayRow(10), DisplayRow(13)..=DisplayRow(14)],
10236 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
10237 );
10238 assert_eq!(
10239 all_hunks,
10240 vec![
10241 ("use some::mod1;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(1)..DisplayRow(1)),
10242 ("const B: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(5)..DisplayRow(5)),
10243 ("fn main(ˇ) {\n println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(9)..DisplayRow(11)),
10244 ("".to_string(), DiffHunkStatus::Added, DisplayRow(13)..DisplayRow(15)),
10245 ],
10246 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
10247 (from modified and removed hunks)"
10248 );
10249 assert_eq!(
10250 all_hunks, all_expanded_hunks,
10251 "Editor hunks should not change and all be expanded"
10252 );
10253 });
10254
10255 cx.set_diff_base(Some("new diff base!"));
10256 executor.run_until_parked();
10257
10258 cx.update_editor(|editor, cx| {
10259 let snapshot = editor.snapshot(cx);
10260 let all_hunks = editor_hunks(editor, &snapshot, cx);
10261 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
10262 assert_eq!(
10263 expanded_hunks_background_highlights(editor, cx),
10264 Vec::new(),
10265 "After diff base is changed, old git highlights should be removed"
10266 );
10267 assert_eq!(
10268 all_expanded_hunks,
10269 Vec::new(),
10270 "After diff base is changed, old git hunk expansions should be removed"
10271 );
10272 assert_eq!(
10273 all_hunks,
10274 vec![(
10275 "new diff base!".to_string(),
10276 DiffHunkStatus::Modified,
10277 DisplayRow(0)..snapshot.display_snapshot.max_point().row()
10278 )],
10279 "After diff base is changed, hunks should update"
10280 );
10281 });
10282}
10283
10284#[gpui::test]
10285async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10286 init_test(cx, |_| {});
10287
10288 let mut cx = EditorTestContext::new(cx).await;
10289
10290 let diff_base = r#"
10291 use some::mod1;
10292 use some::mod2;
10293
10294 const A: u32 = 42;
10295 const B: u32 = 42;
10296 const C: u32 = 42;
10297
10298 fn main(ˇ) {
10299 println!("hello");
10300
10301 println!("world");
10302 }
10303
10304 fn another() {
10305 println!("another");
10306 }
10307
10308 fn another2() {
10309 println!("another2");
10310 }
10311 "#
10312 .unindent();
10313
10314 cx.set_state(
10315 &r#"
10316 «use some::mod2;
10317
10318 const A: u32 = 42;
10319 const C: u32 = 42;
10320
10321 fn main() {
10322 //println!("hello");
10323
10324 println!("world");
10325 //
10326 //ˇ»
10327 }
10328
10329 fn another() {
10330 println!("another");
10331 println!("another");
10332 }
10333
10334 println!("another2");
10335 }
10336 "#
10337 .unindent(),
10338 );
10339
10340 cx.set_diff_base(Some(&diff_base));
10341 executor.run_until_parked();
10342 cx.update_editor(|editor, cx| {
10343 let snapshot = editor.snapshot(cx);
10344 let all_hunks = editor_hunks(editor, &snapshot, cx);
10345 assert_eq!(
10346 all_hunks,
10347 vec![
10348 (
10349 "use some::mod1;\n".to_string(),
10350 DiffHunkStatus::Removed,
10351 DisplayRow(0)..DisplayRow(0)
10352 ),
10353 (
10354 "const B: u32 = 42;\n".to_string(),
10355 DiffHunkStatus::Removed,
10356 DisplayRow(3)..DisplayRow(3)
10357 ),
10358 (
10359 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
10360 DiffHunkStatus::Modified,
10361 DisplayRow(5)..DisplayRow(7)
10362 ),
10363 (
10364 "".to_string(),
10365 DiffHunkStatus::Added,
10366 DisplayRow(9)..DisplayRow(11)
10367 ),
10368 (
10369 "".to_string(),
10370 DiffHunkStatus::Added,
10371 DisplayRow(15)..DisplayRow(16)
10372 ),
10373 (
10374 "fn another2() {\n".to_string(),
10375 DiffHunkStatus::Removed,
10376 DisplayRow(18)..DisplayRow(18)
10377 ),
10378 ]
10379 );
10380 });
10381
10382 cx.update_editor(|editor, cx| {
10383 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10384 });
10385 executor.run_until_parked();
10386 cx.assert_editor_state(
10387 &r#"
10388 «use some::mod2;
10389
10390 const A: u32 = 42;
10391 const C: u32 = 42;
10392
10393 fn main() {
10394 //println!("hello");
10395
10396 println!("world");
10397 //
10398 //ˇ»
10399 }
10400
10401 fn another() {
10402 println!("another");
10403 println!("another");
10404 }
10405
10406 println!("another2");
10407 }
10408 "#
10409 .unindent(),
10410 );
10411 cx.update_editor(|editor, cx| {
10412 let snapshot = editor.snapshot(cx);
10413 let all_hunks = editor_hunks(editor, &snapshot, cx);
10414 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10415 assert_eq!(
10416 expanded_hunks_background_highlights(editor, cx),
10417 vec![
10418 DisplayRow(9)..=DisplayRow(10),
10419 DisplayRow(13)..=DisplayRow(14),
10420 DisplayRow(19)..=DisplayRow(19)
10421 ]
10422 );
10423 assert_eq!(
10424 all_hunks,
10425 vec![
10426 (
10427 "use some::mod1;\n".to_string(),
10428 DiffHunkStatus::Removed,
10429 DisplayRow(1)..DisplayRow(1)
10430 ),
10431 (
10432 "const B: u32 = 42;\n".to_string(),
10433 DiffHunkStatus::Removed,
10434 DisplayRow(5)..DisplayRow(5)
10435 ),
10436 (
10437 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
10438 DiffHunkStatus::Modified,
10439 DisplayRow(9)..DisplayRow(11)
10440 ),
10441 (
10442 "".to_string(),
10443 DiffHunkStatus::Added,
10444 DisplayRow(13)..DisplayRow(15)
10445 ),
10446 (
10447 "".to_string(),
10448 DiffHunkStatus::Added,
10449 DisplayRow(19)..DisplayRow(20)
10450 ),
10451 (
10452 "fn another2() {\n".to_string(),
10453 DiffHunkStatus::Removed,
10454 DisplayRow(23)..DisplayRow(23)
10455 ),
10456 ],
10457 );
10458 assert_eq!(all_hunks, all_expanded_hunks);
10459 });
10460
10461 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
10462 cx.executor().run_until_parked();
10463 cx.assert_editor_state(
10464 &r#"
10465 «use some::mod2;
10466
10467 const A: u32 = 42;
10468 const C: u32 = 42;
10469
10470 fn main() {
10471 //println!("hello");
10472
10473 println!("world");
10474 //
10475 //ˇ»
10476 }
10477
10478 fn another() {
10479 println!("another");
10480 println!("another");
10481 }
10482
10483 println!("another2");
10484 }
10485 "#
10486 .unindent(),
10487 );
10488 cx.update_editor(|editor, cx| {
10489 let snapshot = editor.snapshot(cx);
10490 let all_hunks = editor_hunks(editor, &snapshot, cx);
10491 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10492 assert_eq!(
10493 expanded_hunks_background_highlights(editor, cx),
10494 vec![DisplayRow(0)..=DisplayRow(0), DisplayRow(5)..=DisplayRow(5)],
10495 "Only one hunk is left not folded, its highlight should be visible"
10496 );
10497 assert_eq!(
10498 all_hunks,
10499 vec![
10500 (
10501 "use some::mod1;\n".to_string(),
10502 DiffHunkStatus::Removed,
10503 DisplayRow(0)..DisplayRow(0)
10504 ),
10505 (
10506 "const B: u32 = 42;\n".to_string(),
10507 DiffHunkStatus::Removed,
10508 DisplayRow(0)..DisplayRow(0)
10509 ),
10510 (
10511 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
10512 DiffHunkStatus::Modified,
10513 DisplayRow(0)..DisplayRow(0)
10514 ),
10515 (
10516 "".to_string(),
10517 DiffHunkStatus::Added,
10518 DisplayRow(0)..DisplayRow(1)
10519 ),
10520 (
10521 "".to_string(),
10522 DiffHunkStatus::Added,
10523 DisplayRow(5)..DisplayRow(6)
10524 ),
10525 (
10526 "fn another2() {\n".to_string(),
10527 DiffHunkStatus::Removed,
10528 DisplayRow(9)..DisplayRow(9)
10529 ),
10530 ],
10531 "Hunk list should still return shifted folded hunks"
10532 );
10533 assert_eq!(
10534 all_expanded_hunks,
10535 vec![
10536 (
10537 "".to_string(),
10538 DiffHunkStatus::Added,
10539 DisplayRow(5)..DisplayRow(6)
10540 ),
10541 (
10542 "fn another2() {\n".to_string(),
10543 DiffHunkStatus::Removed,
10544 DisplayRow(9)..DisplayRow(9)
10545 ),
10546 ],
10547 "Only non-folded hunks should be left expanded"
10548 );
10549 });
10550
10551 cx.update_editor(|editor, cx| {
10552 editor.select_all(&SelectAll, cx);
10553 editor.unfold_lines(&UnfoldLines, cx);
10554 });
10555 cx.executor().run_until_parked();
10556 cx.assert_editor_state(
10557 &r#"
10558 «use some::mod2;
10559
10560 const A: u32 = 42;
10561 const C: u32 = 42;
10562
10563 fn main() {
10564 //println!("hello");
10565
10566 println!("world");
10567 //
10568 //
10569 }
10570
10571 fn another() {
10572 println!("another");
10573 println!("another");
10574 }
10575
10576 println!("another2");
10577 }
10578 ˇ»"#
10579 .unindent(),
10580 );
10581 cx.update_editor(|editor, cx| {
10582 let snapshot = editor.snapshot(cx);
10583 let all_hunks = editor_hunks(editor, &snapshot, cx);
10584 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10585 assert_eq!(
10586 expanded_hunks_background_highlights(editor, cx),
10587 vec![
10588 DisplayRow(9)..=DisplayRow(10),
10589 DisplayRow(13)..=DisplayRow(14),
10590 DisplayRow(19)..=DisplayRow(19)
10591 ],
10592 "After unfolding, all hunk diffs should be visible again"
10593 );
10594 assert_eq!(
10595 all_hunks,
10596 vec![
10597 (
10598 "use some::mod1;\n".to_string(),
10599 DiffHunkStatus::Removed,
10600 DisplayRow(1)..DisplayRow(1)
10601 ),
10602 (
10603 "const B: u32 = 42;\n".to_string(),
10604 DiffHunkStatus::Removed,
10605 DisplayRow(5)..DisplayRow(5)
10606 ),
10607 (
10608 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
10609 DiffHunkStatus::Modified,
10610 DisplayRow(9)..DisplayRow(11)
10611 ),
10612 (
10613 "".to_string(),
10614 DiffHunkStatus::Added,
10615 DisplayRow(13)..DisplayRow(15)
10616 ),
10617 (
10618 "".to_string(),
10619 DiffHunkStatus::Added,
10620 DisplayRow(19)..DisplayRow(20)
10621 ),
10622 (
10623 "fn another2() {\n".to_string(),
10624 DiffHunkStatus::Removed,
10625 DisplayRow(23)..DisplayRow(23)
10626 ),
10627 ],
10628 );
10629 assert_eq!(all_hunks, all_expanded_hunks);
10630 });
10631}
10632
10633#[gpui::test]
10634async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
10635 init_test(cx, |_| {});
10636
10637 let cols = 4;
10638 let rows = 10;
10639 let sample_text_1 = sample_text(rows, cols, 'a');
10640 assert_eq!(
10641 sample_text_1,
10642 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10643 );
10644 let modified_sample_text_1 = "aaaa\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
10645 let sample_text_2 = sample_text(rows, cols, 'l');
10646 assert_eq!(
10647 sample_text_2,
10648 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10649 );
10650 let modified_sample_text_2 = "llll\nmmmm\n1n1n1n1n1\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
10651 let sample_text_3 = sample_text(rows, cols, 'v');
10652 assert_eq!(
10653 sample_text_3,
10654 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10655 );
10656 let modified_sample_text_3 =
10657 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n@@@@\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
10658 let buffer_1 = cx.new_model(|cx| {
10659 let mut buffer = Buffer::local(modified_sample_text_1.to_string(), cx);
10660 buffer.set_diff_base(Some(sample_text_1.clone()), cx);
10661 buffer
10662 });
10663 let buffer_2 = cx.new_model(|cx| {
10664 let mut buffer = Buffer::local(modified_sample_text_2.to_string(), cx);
10665 buffer.set_diff_base(Some(sample_text_2.clone()), cx);
10666 buffer
10667 });
10668 let buffer_3 = cx.new_model(|cx| {
10669 let mut buffer = Buffer::local(modified_sample_text_3.to_string(), cx);
10670 buffer.set_diff_base(Some(sample_text_3.clone()), cx);
10671 buffer
10672 });
10673
10674 let multi_buffer = cx.new_model(|cx| {
10675 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10676 multibuffer.push_excerpts(
10677 buffer_1.clone(),
10678 [
10679 ExcerptRange {
10680 context: Point::new(0, 0)..Point::new(3, 0),
10681 primary: None,
10682 },
10683 ExcerptRange {
10684 context: Point::new(5, 0)..Point::new(7, 0),
10685 primary: None,
10686 },
10687 ExcerptRange {
10688 context: Point::new(9, 0)..Point::new(10, 4),
10689 primary: None,
10690 },
10691 ],
10692 cx,
10693 );
10694 multibuffer.push_excerpts(
10695 buffer_2.clone(),
10696 [
10697 ExcerptRange {
10698 context: Point::new(0, 0)..Point::new(3, 0),
10699 primary: None,
10700 },
10701 ExcerptRange {
10702 context: Point::new(5, 0)..Point::new(7, 0),
10703 primary: None,
10704 },
10705 ExcerptRange {
10706 context: Point::new(9, 0)..Point::new(10, 4),
10707 primary: None,
10708 },
10709 ],
10710 cx,
10711 );
10712 multibuffer.push_excerpts(
10713 buffer_3.clone(),
10714 [
10715 ExcerptRange {
10716 context: Point::new(0, 0)..Point::new(3, 0),
10717 primary: None,
10718 },
10719 ExcerptRange {
10720 context: Point::new(5, 0)..Point::new(7, 0),
10721 primary: None,
10722 },
10723 ExcerptRange {
10724 context: Point::new(9, 0)..Point::new(10, 4),
10725 primary: None,
10726 },
10727 ],
10728 cx,
10729 );
10730 multibuffer
10731 });
10732
10733 let fs = FakeFs::new(cx.executor());
10734 fs.insert_tree(
10735 "/a",
10736 json!({
10737 "main.rs": modified_sample_text_1,
10738 "other.rs": modified_sample_text_2,
10739 "lib.rs": modified_sample_text_3,
10740 }),
10741 )
10742 .await;
10743
10744 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10745 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10746 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10747 let multi_buffer_editor = cx.new_view(|cx| {
10748 Editor::new(
10749 EditorMode::Full,
10750 multi_buffer,
10751 Some(project.clone()),
10752 true,
10753 cx,
10754 )
10755 });
10756 cx.executor().run_until_parked();
10757
10758 let expected_all_hunks = vec![
10759 (
10760 "bbbb\n".to_string(),
10761 DiffHunkStatus::Removed,
10762 DisplayRow(4)..DisplayRow(4),
10763 ),
10764 (
10765 "nnnn\n".to_string(),
10766 DiffHunkStatus::Modified,
10767 DisplayRow(21)..DisplayRow(22),
10768 ),
10769 (
10770 "".to_string(),
10771 DiffHunkStatus::Added,
10772 DisplayRow(41)..DisplayRow(42),
10773 ),
10774 ];
10775 let expected_all_hunks_shifted = vec![
10776 (
10777 "bbbb\n".to_string(),
10778 DiffHunkStatus::Removed,
10779 DisplayRow(5)..DisplayRow(5),
10780 ),
10781 (
10782 "nnnn\n".to_string(),
10783 DiffHunkStatus::Modified,
10784 DisplayRow(23)..DisplayRow(24),
10785 ),
10786 (
10787 "".to_string(),
10788 DiffHunkStatus::Added,
10789 DisplayRow(43)..DisplayRow(44),
10790 ),
10791 ];
10792
10793 multi_buffer_editor.update(cx, |editor, cx| {
10794 let snapshot = editor.snapshot(cx);
10795 let all_hunks = editor_hunks(editor, &snapshot, cx);
10796 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10797 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
10798 assert_eq!(all_hunks, expected_all_hunks);
10799 assert_eq!(all_expanded_hunks, Vec::new());
10800 });
10801
10802 multi_buffer_editor.update(cx, |editor, cx| {
10803 editor.select_all(&SelectAll, cx);
10804 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10805 });
10806 cx.executor().run_until_parked();
10807 multi_buffer_editor.update(cx, |editor, cx| {
10808 let snapshot = editor.snapshot(cx);
10809 let all_hunks = editor_hunks(editor, &snapshot, cx);
10810 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10811 assert_eq!(
10812 expanded_hunks_background_highlights(editor, cx),
10813 vec![
10814 DisplayRow(23)..=DisplayRow(23),
10815 DisplayRow(43)..=DisplayRow(43)
10816 ],
10817 );
10818 assert_eq!(all_hunks, expected_all_hunks_shifted);
10819 assert_eq!(all_hunks, all_expanded_hunks);
10820 });
10821
10822 multi_buffer_editor.update(cx, |editor, cx| {
10823 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10824 });
10825 cx.executor().run_until_parked();
10826 multi_buffer_editor.update(cx, |editor, cx| {
10827 let snapshot = editor.snapshot(cx);
10828 let all_hunks = editor_hunks(editor, &snapshot, cx);
10829 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10830 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
10831 assert_eq!(all_hunks, expected_all_hunks);
10832 assert_eq!(all_expanded_hunks, Vec::new());
10833 });
10834
10835 multi_buffer_editor.update(cx, |editor, cx| {
10836 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10837 });
10838 cx.executor().run_until_parked();
10839 multi_buffer_editor.update(cx, |editor, cx| {
10840 let snapshot = editor.snapshot(cx);
10841 let all_hunks = editor_hunks(editor, &snapshot, cx);
10842 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10843 assert_eq!(
10844 expanded_hunks_background_highlights(editor, cx),
10845 vec![
10846 DisplayRow(23)..=DisplayRow(23),
10847 DisplayRow(43)..=DisplayRow(43)
10848 ],
10849 );
10850 assert_eq!(all_hunks, expected_all_hunks_shifted);
10851 assert_eq!(all_hunks, all_expanded_hunks);
10852 });
10853
10854 multi_buffer_editor.update(cx, |editor, cx| {
10855 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10856 });
10857 cx.executor().run_until_parked();
10858 multi_buffer_editor.update(cx, |editor, cx| {
10859 let snapshot = editor.snapshot(cx);
10860 let all_hunks = editor_hunks(editor, &snapshot, cx);
10861 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10862 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
10863 assert_eq!(all_hunks, expected_all_hunks);
10864 assert_eq!(all_expanded_hunks, Vec::new());
10865 });
10866}
10867
10868#[gpui::test]
10869async fn test_edits_around_toggled_additions(
10870 executor: BackgroundExecutor,
10871 cx: &mut gpui::TestAppContext,
10872) {
10873 init_test(cx, |_| {});
10874
10875 let mut cx = EditorTestContext::new(cx).await;
10876
10877 let diff_base = r#"
10878 use some::mod1;
10879 use some::mod2;
10880
10881 const A: u32 = 42;
10882
10883 fn main() {
10884 println!("hello");
10885
10886 println!("world");
10887 }
10888 "#
10889 .unindent();
10890 executor.run_until_parked();
10891 cx.set_state(
10892 &r#"
10893 use some::mod1;
10894 use some::mod2;
10895
10896 const A: u32 = 42;
10897 const B: u32 = 42;
10898 const C: u32 = 42;
10899 ˇ
10900
10901 fn main() {
10902 println!("hello");
10903
10904 println!("world");
10905 }
10906 "#
10907 .unindent(),
10908 );
10909
10910 cx.set_diff_base(Some(&diff_base));
10911 executor.run_until_parked();
10912 cx.update_editor(|editor, cx| {
10913 let snapshot = editor.snapshot(cx);
10914 let all_hunks = editor_hunks(editor, &snapshot, cx);
10915 assert_eq!(
10916 all_hunks,
10917 vec![(
10918 "".to_string(),
10919 DiffHunkStatus::Added,
10920 DisplayRow(4)..DisplayRow(7)
10921 )]
10922 );
10923 });
10924 cx.update_editor(|editor, cx| {
10925 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10926 });
10927 executor.run_until_parked();
10928 cx.assert_editor_state(
10929 &r#"
10930 use some::mod1;
10931 use some::mod2;
10932
10933 const A: u32 = 42;
10934 const B: u32 = 42;
10935 const C: u32 = 42;
10936 ˇ
10937
10938 fn main() {
10939 println!("hello");
10940
10941 println!("world");
10942 }
10943 "#
10944 .unindent(),
10945 );
10946 cx.update_editor(|editor, cx| {
10947 let snapshot = editor.snapshot(cx);
10948 let all_hunks = editor_hunks(editor, &snapshot, cx);
10949 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10950 assert_eq!(
10951 all_hunks,
10952 vec![(
10953 "".to_string(),
10954 DiffHunkStatus::Added,
10955 DisplayRow(4)..DisplayRow(7)
10956 )]
10957 );
10958 assert_eq!(
10959 expanded_hunks_background_highlights(editor, cx),
10960 vec![DisplayRow(4)..=DisplayRow(6)]
10961 );
10962 assert_eq!(all_hunks, all_expanded_hunks);
10963 });
10964
10965 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
10966 executor.run_until_parked();
10967 cx.assert_editor_state(
10968 &r#"
10969 use some::mod1;
10970 use some::mod2;
10971
10972 const A: u32 = 42;
10973 const B: u32 = 42;
10974 const C: u32 = 42;
10975 const D: u32 = 42;
10976 ˇ
10977
10978 fn main() {
10979 println!("hello");
10980
10981 println!("world");
10982 }
10983 "#
10984 .unindent(),
10985 );
10986 cx.update_editor(|editor, cx| {
10987 let snapshot = editor.snapshot(cx);
10988 let all_hunks = editor_hunks(editor, &snapshot, cx);
10989 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10990 assert_eq!(
10991 all_hunks,
10992 vec![(
10993 "".to_string(),
10994 DiffHunkStatus::Added,
10995 DisplayRow(4)..DisplayRow(8)
10996 )]
10997 );
10998 assert_eq!(
10999 expanded_hunks_background_highlights(editor, cx),
11000 vec![DisplayRow(4)..=DisplayRow(6)],
11001 "Edited hunk should have one more line added"
11002 );
11003 assert_eq!(
11004 all_hunks, all_expanded_hunks,
11005 "Expanded hunk should also grow with the addition"
11006 );
11007 });
11008
11009 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
11010 executor.run_until_parked();
11011 cx.assert_editor_state(
11012 &r#"
11013 use some::mod1;
11014 use some::mod2;
11015
11016 const A: u32 = 42;
11017 const B: u32 = 42;
11018 const C: u32 = 42;
11019 const D: u32 = 42;
11020 const E: u32 = 42;
11021 ˇ
11022
11023 fn main() {
11024 println!("hello");
11025
11026 println!("world");
11027 }
11028 "#
11029 .unindent(),
11030 );
11031 cx.update_editor(|editor, cx| {
11032 let snapshot = editor.snapshot(cx);
11033 let all_hunks = editor_hunks(editor, &snapshot, cx);
11034 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11035 assert_eq!(
11036 all_hunks,
11037 vec![(
11038 "".to_string(),
11039 DiffHunkStatus::Added,
11040 DisplayRow(4)..DisplayRow(9)
11041 )]
11042 );
11043 assert_eq!(
11044 expanded_hunks_background_highlights(editor, cx),
11045 vec![DisplayRow(4)..=DisplayRow(6)],
11046 "Edited hunk should have one more line added"
11047 );
11048 assert_eq!(all_hunks, all_expanded_hunks);
11049 });
11050
11051 cx.update_editor(|editor, cx| {
11052 editor.move_up(&MoveUp, cx);
11053 editor.delete_line(&DeleteLine, cx);
11054 });
11055 executor.run_until_parked();
11056 cx.assert_editor_state(
11057 &r#"
11058 use some::mod1;
11059 use some::mod2;
11060
11061 const A: u32 = 42;
11062 const B: u32 = 42;
11063 const C: u32 = 42;
11064 const D: u32 = 42;
11065 ˇ
11066
11067 fn main() {
11068 println!("hello");
11069
11070 println!("world");
11071 }
11072 "#
11073 .unindent(),
11074 );
11075 cx.update_editor(|editor, cx| {
11076 let snapshot = editor.snapshot(cx);
11077 let all_hunks = editor_hunks(editor, &snapshot, cx);
11078 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11079 assert_eq!(
11080 all_hunks,
11081 vec![(
11082 "".to_string(),
11083 DiffHunkStatus::Added,
11084 DisplayRow(4)..DisplayRow(8)
11085 )]
11086 );
11087 assert_eq!(
11088 expanded_hunks_background_highlights(editor, cx),
11089 vec![DisplayRow(4)..=DisplayRow(6)],
11090 "Deleting a line should shrint the hunk"
11091 );
11092 assert_eq!(
11093 all_hunks, all_expanded_hunks,
11094 "Expanded hunk should also shrink with the addition"
11095 );
11096 });
11097
11098 cx.update_editor(|editor, cx| {
11099 editor.move_up(&MoveUp, cx);
11100 editor.delete_line(&DeleteLine, cx);
11101 editor.move_up(&MoveUp, cx);
11102 editor.delete_line(&DeleteLine, cx);
11103 editor.move_up(&MoveUp, cx);
11104 editor.delete_line(&DeleteLine, cx);
11105 });
11106 executor.run_until_parked();
11107 cx.assert_editor_state(
11108 &r#"
11109 use some::mod1;
11110 use some::mod2;
11111
11112 const A: u32 = 42;
11113 ˇ
11114
11115 fn main() {
11116 println!("hello");
11117
11118 println!("world");
11119 }
11120 "#
11121 .unindent(),
11122 );
11123 cx.update_editor(|editor, cx| {
11124 let snapshot = editor.snapshot(cx);
11125 let all_hunks = editor_hunks(editor, &snapshot, cx);
11126 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11127 assert_eq!(
11128 all_hunks,
11129 vec![(
11130 "".to_string(),
11131 DiffHunkStatus::Added,
11132 DisplayRow(5)..DisplayRow(6)
11133 )]
11134 );
11135 assert_eq!(
11136 expanded_hunks_background_highlights(editor, cx),
11137 vec![DisplayRow(5)..=DisplayRow(5)]
11138 );
11139 assert_eq!(all_hunks, all_expanded_hunks);
11140 });
11141
11142 cx.update_editor(|editor, cx| {
11143 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
11144 editor.delete_line(&DeleteLine, cx);
11145 });
11146 executor.run_until_parked();
11147 cx.assert_editor_state(
11148 &r#"
11149 ˇ
11150
11151 fn main() {
11152 println!("hello");
11153
11154 println!("world");
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 all_hunks,
11165 vec![
11166 (
11167 "use some::mod1;\nuse some::mod2;\n".to_string(),
11168 DiffHunkStatus::Removed,
11169 DisplayRow(0)..DisplayRow(0)
11170 ),
11171 (
11172 "const A: u32 = 42;\n".to_string(),
11173 DiffHunkStatus::Removed,
11174 DisplayRow(2)..DisplayRow(2)
11175 )
11176 ]
11177 );
11178 assert_eq!(
11179 expanded_hunks_background_highlights(editor, cx),
11180 Vec::new(),
11181 "Should close all stale expanded addition hunks"
11182 );
11183 assert_eq!(
11184 all_expanded_hunks,
11185 vec![(
11186 "const A: u32 = 42;\n".to_string(),
11187 DiffHunkStatus::Removed,
11188 DisplayRow(2)..DisplayRow(2)
11189 )],
11190 "Should open hunks that were adjacent to the stale addition one"
11191 );
11192 });
11193}
11194
11195#[gpui::test]
11196async fn test_edits_around_toggled_deletions(
11197 executor: BackgroundExecutor,
11198 cx: &mut gpui::TestAppContext,
11199) {
11200 init_test(cx, |_| {});
11201
11202 let mut cx = EditorTestContext::new(cx).await;
11203
11204 let diff_base = r#"
11205 use some::mod1;
11206 use some::mod2;
11207
11208 const A: u32 = 42;
11209 const B: u32 = 42;
11210 const C: u32 = 42;
11211
11212
11213 fn main() {
11214 println!("hello");
11215
11216 println!("world");
11217 }
11218 "#
11219 .unindent();
11220 executor.run_until_parked();
11221 cx.set_state(
11222 &r#"
11223 use some::mod1;
11224 use some::mod2;
11225
11226 ˇconst B: u32 = 42;
11227 const C: u32 = 42;
11228
11229
11230 fn main() {
11231 println!("hello");
11232
11233 println!("world");
11234 }
11235 "#
11236 .unindent(),
11237 );
11238
11239 cx.set_diff_base(Some(&diff_base));
11240 executor.run_until_parked();
11241 cx.update_editor(|editor, cx| {
11242 let snapshot = editor.snapshot(cx);
11243 let all_hunks = editor_hunks(editor, &snapshot, cx);
11244 assert_eq!(
11245 all_hunks,
11246 vec![(
11247 "const A: u32 = 42;\n".to_string(),
11248 DiffHunkStatus::Removed,
11249 DisplayRow(3)..DisplayRow(3)
11250 )]
11251 );
11252 });
11253 cx.update_editor(|editor, cx| {
11254 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11255 });
11256 executor.run_until_parked();
11257 cx.assert_editor_state(
11258 &r#"
11259 use some::mod1;
11260 use some::mod2;
11261
11262 ˇconst B: u32 = 42;
11263 const C: u32 = 42;
11264
11265
11266 fn main() {
11267 println!("hello");
11268
11269 println!("world");
11270 }
11271 "#
11272 .unindent(),
11273 );
11274 cx.update_editor(|editor, cx| {
11275 let snapshot = editor.snapshot(cx);
11276 let all_hunks = editor_hunks(editor, &snapshot, cx);
11277 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11278 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11279 assert_eq!(
11280 all_hunks,
11281 vec![(
11282 "const A: u32 = 42;\n".to_string(),
11283 DiffHunkStatus::Removed,
11284 DisplayRow(4)..DisplayRow(4)
11285 )]
11286 );
11287 assert_eq!(all_hunks, all_expanded_hunks);
11288 });
11289
11290 cx.update_editor(|editor, cx| {
11291 editor.delete_line(&DeleteLine, cx);
11292 });
11293 executor.run_until_parked();
11294 cx.assert_editor_state(
11295 &r#"
11296 use some::mod1;
11297 use some::mod2;
11298
11299 ˇconst C: u32 = 42;
11300
11301
11302 fn main() {
11303 println!("hello");
11304
11305 println!("world");
11306 }
11307 "#
11308 .unindent(),
11309 );
11310 cx.update_editor(|editor, cx| {
11311 let snapshot = editor.snapshot(cx);
11312 let all_hunks = editor_hunks(editor, &snapshot, cx);
11313 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11314 assert_eq!(
11315 expanded_hunks_background_highlights(editor, cx),
11316 Vec::new(),
11317 "Deleted hunks do not highlight current editor's background"
11318 );
11319 assert_eq!(
11320 all_hunks,
11321 vec![(
11322 "const A: u32 = 42;\nconst B: u32 = 42;\n".to_string(),
11323 DiffHunkStatus::Removed,
11324 DisplayRow(5)..DisplayRow(5)
11325 )]
11326 );
11327 assert_eq!(all_hunks, all_expanded_hunks);
11328 });
11329
11330 cx.update_editor(|editor, cx| {
11331 editor.delete_line(&DeleteLine, cx);
11332 });
11333 executor.run_until_parked();
11334 cx.assert_editor_state(
11335 &r#"
11336 use some::mod1;
11337 use some::mod2;
11338
11339 ˇ
11340
11341 fn main() {
11342 println!("hello");
11343
11344 println!("world");
11345 }
11346 "#
11347 .unindent(),
11348 );
11349 cx.update_editor(|editor, cx| {
11350 let snapshot = editor.snapshot(cx);
11351 let all_hunks = editor_hunks(editor, &snapshot, cx);
11352 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11353 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11354 assert_eq!(
11355 all_hunks,
11356 vec![(
11357 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
11358 DiffHunkStatus::Removed,
11359 DisplayRow(6)..DisplayRow(6)
11360 )]
11361 );
11362 assert_eq!(all_hunks, all_expanded_hunks);
11363 });
11364
11365 cx.update_editor(|editor, cx| {
11366 editor.handle_input("replacement", cx);
11367 });
11368 executor.run_until_parked();
11369 cx.assert_editor_state(
11370 &r#"
11371 use some::mod1;
11372 use some::mod2;
11373
11374 replacementˇ
11375
11376 fn main() {
11377 println!("hello");
11378
11379 println!("world");
11380 }
11381 "#
11382 .unindent(),
11383 );
11384 cx.update_editor(|editor, cx| {
11385 let snapshot = editor.snapshot(cx);
11386 let all_hunks = editor_hunks(editor, &snapshot, cx);
11387 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11388 assert_eq!(
11389 all_hunks,
11390 vec![(
11391 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n\n".to_string(),
11392 DiffHunkStatus::Modified,
11393 DisplayRow(7)..DisplayRow(8)
11394 )]
11395 );
11396 assert_eq!(
11397 expanded_hunks_background_highlights(editor, cx),
11398 vec![DisplayRow(7)..=DisplayRow(7)],
11399 "Modified expanded hunks should display additions and highlight their background"
11400 );
11401 assert_eq!(all_hunks, all_expanded_hunks);
11402 });
11403}
11404
11405#[gpui::test]
11406async fn test_edits_around_toggled_modifications(
11407 executor: BackgroundExecutor,
11408 cx: &mut gpui::TestAppContext,
11409) {
11410 init_test(cx, |_| {});
11411
11412 let mut cx = EditorTestContext::new(cx).await;
11413
11414 let diff_base = r#"
11415 use some::mod1;
11416 use some::mod2;
11417
11418 const A: u32 = 42;
11419 const B: u32 = 42;
11420 const C: u32 = 42;
11421 const D: u32 = 42;
11422
11423
11424 fn main() {
11425 println!("hello");
11426
11427 println!("world");
11428 }"#
11429 .unindent();
11430 executor.run_until_parked();
11431 cx.set_state(
11432 &r#"
11433 use some::mod1;
11434 use some::mod2;
11435
11436 const A: u32 = 42;
11437 const B: u32 = 42;
11438 const C: u32 = 43ˇ
11439 const D: u32 = 42;
11440
11441
11442 fn main() {
11443 println!("hello");
11444
11445 println!("world");
11446 }"#
11447 .unindent(),
11448 );
11449
11450 cx.set_diff_base(Some(&diff_base));
11451 executor.run_until_parked();
11452 cx.update_editor(|editor, cx| {
11453 let snapshot = editor.snapshot(cx);
11454 let all_hunks = editor_hunks(editor, &snapshot, cx);
11455 assert_eq!(
11456 all_hunks,
11457 vec![(
11458 "const C: u32 = 42;\n".to_string(),
11459 DiffHunkStatus::Modified,
11460 DisplayRow(5)..DisplayRow(6)
11461 )]
11462 );
11463 });
11464 cx.update_editor(|editor, cx| {
11465 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11466 });
11467 executor.run_until_parked();
11468 cx.assert_editor_state(
11469 &r#"
11470 use some::mod1;
11471 use some::mod2;
11472
11473 const A: u32 = 42;
11474 const B: u32 = 42;
11475 const C: u32 = 43ˇ
11476 const D: u32 = 42;
11477
11478
11479 fn main() {
11480 println!("hello");
11481
11482 println!("world");
11483 }"#
11484 .unindent(),
11485 );
11486 cx.update_editor(|editor, cx| {
11487 let snapshot = editor.snapshot(cx);
11488 let all_hunks = editor_hunks(editor, &snapshot, cx);
11489 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11490 assert_eq!(
11491 expanded_hunks_background_highlights(editor, cx),
11492 vec![DisplayRow(6)..=DisplayRow(6)],
11493 );
11494 assert_eq!(
11495 all_hunks,
11496 vec![(
11497 "const C: u32 = 42;\n".to_string(),
11498 DiffHunkStatus::Modified,
11499 DisplayRow(6)..DisplayRow(7)
11500 )]
11501 );
11502 assert_eq!(all_hunks, all_expanded_hunks);
11503 });
11504
11505 cx.update_editor(|editor, cx| {
11506 editor.handle_input("\nnew_line\n", cx);
11507 });
11508 executor.run_until_parked();
11509 cx.assert_editor_state(
11510 &r#"
11511 use some::mod1;
11512 use some::mod2;
11513
11514 const A: u32 = 42;
11515 const B: u32 = 42;
11516 const C: u32 = 43
11517 new_line
11518 ˇ
11519 const D: u32 = 42;
11520
11521
11522 fn main() {
11523 println!("hello");
11524
11525 println!("world");
11526 }"#
11527 .unindent(),
11528 );
11529 cx.update_editor(|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!(
11534 expanded_hunks_background_highlights(editor, cx),
11535 vec![DisplayRow(6)..=DisplayRow(6)],
11536 "Modified hunk should grow highlighted lines on more text additions"
11537 );
11538 assert_eq!(
11539 all_hunks,
11540 vec![(
11541 "const C: u32 = 42;\n".to_string(),
11542 DiffHunkStatus::Modified,
11543 DisplayRow(6)..DisplayRow(9)
11544 )]
11545 );
11546 assert_eq!(all_hunks, all_expanded_hunks);
11547 });
11548
11549 cx.update_editor(|editor, cx| {
11550 editor.move_up(&MoveUp, cx);
11551 editor.move_up(&MoveUp, cx);
11552 editor.move_up(&MoveUp, cx);
11553 editor.delete_line(&DeleteLine, cx);
11554 });
11555 executor.run_until_parked();
11556 cx.assert_editor_state(
11557 &r#"
11558 use some::mod1;
11559 use some::mod2;
11560
11561 const A: u32 = 42;
11562 ˇconst C: u32 = 43
11563 new_line
11564
11565 const D: u32 = 42;
11566
11567
11568 fn main() {
11569 println!("hello");
11570
11571 println!("world");
11572 }"#
11573 .unindent(),
11574 );
11575 cx.update_editor(|editor, cx| {
11576 let snapshot = editor.snapshot(cx);
11577 let all_hunks = editor_hunks(editor, &snapshot, cx);
11578 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11579 assert_eq!(
11580 expanded_hunks_background_highlights(editor, cx),
11581 vec![DisplayRow(6)..=DisplayRow(8)],
11582 );
11583 assert_eq!(
11584 all_hunks,
11585 vec![(
11586 "const B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
11587 DiffHunkStatus::Modified,
11588 DisplayRow(6)..DisplayRow(9)
11589 )],
11590 "Modified hunk should grow deleted lines on text deletions above"
11591 );
11592 assert_eq!(all_hunks, all_expanded_hunks);
11593 });
11594
11595 cx.update_editor(|editor, cx| {
11596 editor.move_up(&MoveUp, cx);
11597 editor.handle_input("v", cx);
11598 });
11599 executor.run_until_parked();
11600 cx.assert_editor_state(
11601 &r#"
11602 use some::mod1;
11603 use some::mod2;
11604
11605 vˇconst A: u32 = 42;
11606 const C: u32 = 43
11607 new_line
11608
11609 const D: u32 = 42;
11610
11611
11612 fn main() {
11613 println!("hello");
11614
11615 println!("world");
11616 }"#
11617 .unindent(),
11618 );
11619 cx.update_editor(|editor, cx| {
11620 let snapshot = editor.snapshot(cx);
11621 let all_hunks = editor_hunks(editor, &snapshot, cx);
11622 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11623 assert_eq!(
11624 expanded_hunks_background_highlights(editor, cx),
11625 vec![DisplayRow(6)..=DisplayRow(9)],
11626 "Modified hunk should grow deleted lines on text modifications above"
11627 );
11628 assert_eq!(
11629 all_hunks,
11630 vec![(
11631 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
11632 DiffHunkStatus::Modified,
11633 DisplayRow(6)..DisplayRow(10)
11634 )]
11635 );
11636 assert_eq!(all_hunks, all_expanded_hunks);
11637 });
11638
11639 cx.update_editor(|editor, cx| {
11640 editor.move_down(&MoveDown, cx);
11641 editor.move_down(&MoveDown, cx);
11642 editor.delete_line(&DeleteLine, cx)
11643 });
11644 executor.run_until_parked();
11645 cx.assert_editor_state(
11646 &r#"
11647 use some::mod1;
11648 use some::mod2;
11649
11650 vconst A: u32 = 42;
11651 const C: u32 = 43
11652 ˇ
11653 const D: u32 = 42;
11654
11655
11656 fn main() {
11657 println!("hello");
11658
11659 println!("world");
11660 }"#
11661 .unindent(),
11662 );
11663 cx.update_editor(|editor, cx| {
11664 let snapshot = editor.snapshot(cx);
11665 let all_hunks = editor_hunks(editor, &snapshot, cx);
11666 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11667 assert_eq!(
11668 expanded_hunks_background_highlights(editor, cx),
11669 vec![DisplayRow(6)..=DisplayRow(8)],
11670 "Modified hunk should grow shrink lines on modification lines removal"
11671 );
11672 assert_eq!(
11673 all_hunks,
11674 vec![(
11675 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
11676 DiffHunkStatus::Modified,
11677 DisplayRow(6)..DisplayRow(9)
11678 )]
11679 );
11680 assert_eq!(all_hunks, all_expanded_hunks);
11681 });
11682
11683 cx.update_editor(|editor, cx| {
11684 editor.move_up(&MoveUp, cx);
11685 editor.move_up(&MoveUp, cx);
11686 editor.select_down_by_lines(&SelectDownByLines { lines: 4 }, cx);
11687 editor.delete_line(&DeleteLine, cx)
11688 });
11689 executor.run_until_parked();
11690 cx.assert_editor_state(
11691 &r#"
11692 use some::mod1;
11693 use some::mod2;
11694
11695 ˇ
11696
11697 fn main() {
11698 println!("hello");
11699
11700 println!("world");
11701 }"#
11702 .unindent(),
11703 );
11704 cx.update_editor(|editor, cx| {
11705 let snapshot = editor.snapshot(cx);
11706 let all_hunks = editor_hunks(editor, &snapshot, cx);
11707 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11708 assert_eq!(
11709 expanded_hunks_background_highlights(editor, cx),
11710 Vec::new(),
11711 "Modified hunk should turn into a removed one on all modified lines removal"
11712 );
11713 assert_eq!(
11714 all_hunks,
11715 vec![(
11716 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\nconst D: u32 = 42;\n"
11717 .to_string(),
11718 DiffHunkStatus::Removed,
11719 DisplayRow(7)..DisplayRow(7)
11720 )]
11721 );
11722 assert_eq!(all_hunks, all_expanded_hunks);
11723 });
11724}
11725
11726#[gpui::test]
11727async fn test_multiple_expanded_hunks_merge(
11728 executor: BackgroundExecutor,
11729 cx: &mut gpui::TestAppContext,
11730) {
11731 init_test(cx, |_| {});
11732
11733 let mut cx = EditorTestContext::new(cx).await;
11734
11735 let diff_base = r#"
11736 use some::mod1;
11737 use some::mod2;
11738
11739 const A: u32 = 42;
11740 const B: u32 = 42;
11741 const C: u32 = 42;
11742 const D: u32 = 42;
11743
11744
11745 fn main() {
11746 println!("hello");
11747
11748 println!("world");
11749 }"#
11750 .unindent();
11751 executor.run_until_parked();
11752 cx.set_state(
11753 &r#"
11754 use some::mod1;
11755 use some::mod2;
11756
11757 const A: u32 = 42;
11758 const B: u32 = 42;
11759 const C: u32 = 43ˇ
11760 const D: u32 = 42;
11761
11762
11763 fn main() {
11764 println!("hello");
11765
11766 println!("world");
11767 }"#
11768 .unindent(),
11769 );
11770
11771 cx.set_diff_base(Some(&diff_base));
11772 executor.run_until_parked();
11773 cx.update_editor(|editor, cx| {
11774 let snapshot = editor.snapshot(cx);
11775 let all_hunks = editor_hunks(editor, &snapshot, cx);
11776 assert_eq!(
11777 all_hunks,
11778 vec![(
11779 "const C: u32 = 42;\n".to_string(),
11780 DiffHunkStatus::Modified,
11781 DisplayRow(5)..DisplayRow(6)
11782 )]
11783 );
11784 });
11785 cx.update_editor(|editor, cx| {
11786 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11787 });
11788 executor.run_until_parked();
11789 cx.assert_editor_state(
11790 &r#"
11791 use some::mod1;
11792 use some::mod2;
11793
11794 const A: u32 = 42;
11795 const B: u32 = 42;
11796 const C: u32 = 43ˇ
11797 const D: u32 = 42;
11798
11799
11800 fn main() {
11801 println!("hello");
11802
11803 println!("world");
11804 }"#
11805 .unindent(),
11806 );
11807 cx.update_editor(|editor, cx| {
11808 let snapshot = editor.snapshot(cx);
11809 let all_hunks = editor_hunks(editor, &snapshot, cx);
11810 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11811 assert_eq!(
11812 expanded_hunks_background_highlights(editor, cx),
11813 vec![DisplayRow(6)..=DisplayRow(6)],
11814 );
11815 assert_eq!(
11816 all_hunks,
11817 vec![(
11818 "const C: u32 = 42;\n".to_string(),
11819 DiffHunkStatus::Modified,
11820 DisplayRow(6)..DisplayRow(7)
11821 )]
11822 );
11823 assert_eq!(all_hunks, all_expanded_hunks);
11824 });
11825
11826 cx.update_editor(|editor, cx| {
11827 editor.handle_input("\nnew_line\n", cx);
11828 });
11829 executor.run_until_parked();
11830 cx.assert_editor_state(
11831 &r#"
11832 use some::mod1;
11833 use some::mod2;
11834
11835 const A: u32 = 42;
11836 const B: u32 = 42;
11837 const C: u32 = 43
11838 new_line
11839 ˇ
11840 const D: u32 = 42;
11841
11842
11843 fn main() {
11844 println!("hello");
11845
11846 println!("world");
11847 }"#
11848 .unindent(),
11849 );
11850}
11851
11852async fn setup_indent_guides_editor(
11853 text: &str,
11854 cx: &mut gpui::TestAppContext,
11855) -> (BufferId, EditorTestContext) {
11856 init_test(cx, |_| {});
11857
11858 let mut cx = EditorTestContext::new(cx).await;
11859
11860 let buffer_id = cx.update_editor(|editor, cx| {
11861 editor.set_text(text, cx);
11862 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
11863 let buffer_id = buffer_ids[0];
11864 buffer_id
11865 });
11866
11867 (buffer_id, cx)
11868}
11869
11870fn assert_indent_guides(
11871 range: Range<u32>,
11872 expected: Vec<IndentGuide>,
11873 active_indices: Option<Vec<usize>>,
11874 cx: &mut EditorTestContext,
11875) {
11876 let indent_guides = cx.update_editor(|editor, cx| {
11877 let snapshot = editor.snapshot(cx).display_snapshot;
11878 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
11879 MultiBufferRow(range.start)..MultiBufferRow(range.end),
11880 true,
11881 &snapshot,
11882 cx,
11883 );
11884
11885 indent_guides.sort_by(|a, b| {
11886 a.depth.cmp(&b.depth).then(
11887 a.start_row
11888 .cmp(&b.start_row)
11889 .then(a.end_row.cmp(&b.end_row)),
11890 )
11891 });
11892 indent_guides
11893 });
11894
11895 if let Some(expected) = active_indices {
11896 let active_indices = cx.update_editor(|editor, cx| {
11897 let snapshot = editor.snapshot(cx).display_snapshot;
11898 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
11899 });
11900
11901 assert_eq!(
11902 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
11903 expected,
11904 "Active indent guide indices do not match"
11905 );
11906 }
11907
11908 let expected: Vec<_> = expected
11909 .into_iter()
11910 .map(|guide| MultiBufferIndentGuide {
11911 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
11912 buffer: guide,
11913 })
11914 .collect();
11915
11916 assert_eq!(indent_guides, expected, "Indent guides do not match");
11917}
11918
11919fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
11920 IndentGuide {
11921 buffer_id,
11922 start_row,
11923 end_row,
11924 depth,
11925 tab_size: 4,
11926 settings: IndentGuideSettings {
11927 enabled: true,
11928 line_width: 1,
11929 active_line_width: 1,
11930 ..Default::default()
11931 },
11932 }
11933}
11934
11935#[gpui::test]
11936async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
11937 let (buffer_id, mut cx) = setup_indent_guides_editor(
11938 &"
11939 fn main() {
11940 let a = 1;
11941 }"
11942 .unindent(),
11943 cx,
11944 )
11945 .await;
11946
11947 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
11948}
11949
11950#[gpui::test]
11951async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
11952 let (buffer_id, mut cx) = setup_indent_guides_editor(
11953 &"
11954 fn main() {
11955 let a = 1;
11956 let b = 2;
11957 }"
11958 .unindent(),
11959 cx,
11960 )
11961 .await;
11962
11963 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
11964}
11965
11966#[gpui::test]
11967async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
11968 let (buffer_id, mut cx) = setup_indent_guides_editor(
11969 &"
11970 fn main() {
11971 let a = 1;
11972 if a == 3 {
11973 let b = 2;
11974 } else {
11975 let c = 3;
11976 }
11977 }"
11978 .unindent(),
11979 cx,
11980 )
11981 .await;
11982
11983 assert_indent_guides(
11984 0..8,
11985 vec![
11986 indent_guide(buffer_id, 1, 6, 0),
11987 indent_guide(buffer_id, 3, 3, 1),
11988 indent_guide(buffer_id, 5, 5, 1),
11989 ],
11990 None,
11991 &mut cx,
11992 );
11993}
11994
11995#[gpui::test]
11996async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
11997 let (buffer_id, mut cx) = setup_indent_guides_editor(
11998 &"
11999 fn main() {
12000 let a = 1;
12001 let b = 2;
12002 let c = 3;
12003 }"
12004 .unindent(),
12005 cx,
12006 )
12007 .await;
12008
12009 assert_indent_guides(
12010 0..5,
12011 vec![
12012 indent_guide(buffer_id, 1, 3, 0),
12013 indent_guide(buffer_id, 2, 2, 1),
12014 ],
12015 None,
12016 &mut cx,
12017 );
12018}
12019
12020#[gpui::test]
12021async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
12022 let (buffer_id, mut cx) = setup_indent_guides_editor(
12023 &"
12024 fn main() {
12025 let a = 1;
12026
12027 let c = 3;
12028 }"
12029 .unindent(),
12030 cx,
12031 )
12032 .await;
12033
12034 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
12035}
12036
12037#[gpui::test]
12038async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
12039 let (buffer_id, mut cx) = setup_indent_guides_editor(
12040 &"
12041 fn main() {
12042 let a = 1;
12043
12044 let c = 3;
12045
12046 if a == 3 {
12047 let b = 2;
12048 } else {
12049 let c = 3;
12050 }
12051 }"
12052 .unindent(),
12053 cx,
12054 )
12055 .await;
12056
12057 assert_indent_guides(
12058 0..11,
12059 vec![
12060 indent_guide(buffer_id, 1, 9, 0),
12061 indent_guide(buffer_id, 6, 6, 1),
12062 indent_guide(buffer_id, 8, 8, 1),
12063 ],
12064 None,
12065 &mut cx,
12066 );
12067}
12068
12069#[gpui::test]
12070async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
12071 let (buffer_id, mut cx) = setup_indent_guides_editor(
12072 &"
12073 fn main() {
12074 let a = 1;
12075
12076 let c = 3;
12077
12078 if a == 3 {
12079 let b = 2;
12080 } else {
12081 let c = 3;
12082 }
12083 }"
12084 .unindent(),
12085 cx,
12086 )
12087 .await;
12088
12089 assert_indent_guides(
12090 1..11,
12091 vec![
12092 indent_guide(buffer_id, 1, 9, 0),
12093 indent_guide(buffer_id, 6, 6, 1),
12094 indent_guide(buffer_id, 8, 8, 1),
12095 ],
12096 None,
12097 &mut cx,
12098 );
12099}
12100
12101#[gpui::test]
12102async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
12103 let (buffer_id, mut cx) = setup_indent_guides_editor(
12104 &"
12105 fn main() {
12106 let a = 1;
12107
12108 let c = 3;
12109
12110 if a == 3 {
12111 let b = 2;
12112 } else {
12113 let c = 3;
12114 }
12115 }"
12116 .unindent(),
12117 cx,
12118 )
12119 .await;
12120
12121 assert_indent_guides(
12122 1..10,
12123 vec![
12124 indent_guide(buffer_id, 1, 9, 0),
12125 indent_guide(buffer_id, 6, 6, 1),
12126 indent_guide(buffer_id, 8, 8, 1),
12127 ],
12128 None,
12129 &mut cx,
12130 );
12131}
12132
12133#[gpui::test]
12134async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
12135 let (buffer_id, mut cx) = setup_indent_guides_editor(
12136 &"
12137 block1
12138 block2
12139 block3
12140 block4
12141 block2
12142 block1
12143 block1"
12144 .unindent(),
12145 cx,
12146 )
12147 .await;
12148
12149 assert_indent_guides(
12150 1..10,
12151 vec![
12152 indent_guide(buffer_id, 1, 4, 0),
12153 indent_guide(buffer_id, 2, 3, 1),
12154 indent_guide(buffer_id, 3, 3, 2),
12155 ],
12156 None,
12157 &mut cx,
12158 );
12159}
12160
12161#[gpui::test]
12162async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
12163 let (buffer_id, mut cx) = setup_indent_guides_editor(
12164 &"
12165 block1
12166 block2
12167 block3
12168
12169 block1
12170 block1"
12171 .unindent(),
12172 cx,
12173 )
12174 .await;
12175
12176 assert_indent_guides(
12177 0..6,
12178 vec![
12179 indent_guide(buffer_id, 1, 2, 0),
12180 indent_guide(buffer_id, 2, 2, 1),
12181 ],
12182 None,
12183 &mut cx,
12184 );
12185}
12186
12187#[gpui::test]
12188async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
12189 let (buffer_id, mut cx) = setup_indent_guides_editor(
12190 &"
12191 block1
12192
12193
12194
12195 block2
12196 "
12197 .unindent(),
12198 cx,
12199 )
12200 .await;
12201
12202 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12203}
12204
12205#[gpui::test]
12206async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
12207 let (buffer_id, mut cx) = setup_indent_guides_editor(
12208 &"
12209 def a:
12210 \tb = 3
12211 \tif True:
12212 \t\tc = 4
12213 \t\td = 5
12214 \tprint(b)
12215 "
12216 .unindent(),
12217 cx,
12218 )
12219 .await;
12220
12221 assert_indent_guides(
12222 0..6,
12223 vec![
12224 indent_guide(buffer_id, 1, 6, 0),
12225 indent_guide(buffer_id, 3, 4, 1),
12226 ],
12227 None,
12228 &mut cx,
12229 );
12230}
12231
12232#[gpui::test]
12233async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12234 let (buffer_id, mut cx) = setup_indent_guides_editor(
12235 &"
12236 fn main() {
12237 let a = 1;
12238 }"
12239 .unindent(),
12240 cx,
12241 )
12242 .await;
12243
12244 cx.update_editor(|editor, cx| {
12245 editor.change_selections(None, cx, |s| {
12246 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12247 });
12248 });
12249
12250 assert_indent_guides(
12251 0..3,
12252 vec![indent_guide(buffer_id, 1, 1, 0)],
12253 Some(vec![0]),
12254 &mut cx,
12255 );
12256}
12257
12258#[gpui::test]
12259async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
12260 let (buffer_id, mut cx) = setup_indent_guides_editor(
12261 &"
12262 fn main() {
12263 if 1 == 2 {
12264 let a = 1;
12265 }
12266 }"
12267 .unindent(),
12268 cx,
12269 )
12270 .await;
12271
12272 cx.update_editor(|editor, cx| {
12273 editor.change_selections(None, cx, |s| {
12274 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12275 });
12276 });
12277
12278 assert_indent_guides(
12279 0..4,
12280 vec![
12281 indent_guide(buffer_id, 1, 3, 0),
12282 indent_guide(buffer_id, 2, 2, 1),
12283 ],
12284 Some(vec![1]),
12285 &mut cx,
12286 );
12287
12288 cx.update_editor(|editor, cx| {
12289 editor.change_selections(None, cx, |s| {
12290 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12291 });
12292 });
12293
12294 assert_indent_guides(
12295 0..4,
12296 vec![
12297 indent_guide(buffer_id, 1, 3, 0),
12298 indent_guide(buffer_id, 2, 2, 1),
12299 ],
12300 Some(vec![1]),
12301 &mut cx,
12302 );
12303
12304 cx.update_editor(|editor, cx| {
12305 editor.change_selections(None, cx, |s| {
12306 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
12307 });
12308 });
12309
12310 assert_indent_guides(
12311 0..4,
12312 vec![
12313 indent_guide(buffer_id, 1, 3, 0),
12314 indent_guide(buffer_id, 2, 2, 1),
12315 ],
12316 Some(vec![0]),
12317 &mut cx,
12318 );
12319}
12320
12321#[gpui::test]
12322async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
12323 let (buffer_id, mut cx) = setup_indent_guides_editor(
12324 &"
12325 fn main() {
12326 let a = 1;
12327
12328 let b = 2;
12329 }"
12330 .unindent(),
12331 cx,
12332 )
12333 .await;
12334
12335 cx.update_editor(|editor, cx| {
12336 editor.change_selections(None, cx, |s| {
12337 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12338 });
12339 });
12340
12341 assert_indent_guides(
12342 0..5,
12343 vec![indent_guide(buffer_id, 1, 3, 0)],
12344 Some(vec![0]),
12345 &mut cx,
12346 );
12347}
12348
12349#[gpui::test]
12350async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
12351 let (buffer_id, mut cx) = setup_indent_guides_editor(
12352 &"
12353 def m:
12354 a = 1
12355 pass"
12356 .unindent(),
12357 cx,
12358 )
12359 .await;
12360
12361 cx.update_editor(|editor, cx| {
12362 editor.change_selections(None, cx, |s| {
12363 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12364 });
12365 });
12366
12367 assert_indent_guides(
12368 0..3,
12369 vec![indent_guide(buffer_id, 1, 2, 0)],
12370 Some(vec![0]),
12371 &mut cx,
12372 );
12373}
12374
12375#[gpui::test]
12376fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
12377 init_test(cx, |_| {});
12378
12379 let editor = cx.add_window(|cx| {
12380 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
12381 build_editor(buffer, cx)
12382 });
12383
12384 let render_args = Arc::new(Mutex::new(None));
12385 let snapshot = editor
12386 .update(cx, |editor, cx| {
12387 let snapshot = editor.buffer().read(cx).snapshot(cx);
12388 let range =
12389 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
12390
12391 struct RenderArgs {
12392 row: MultiBufferRow,
12393 folded: bool,
12394 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
12395 }
12396
12397 let crease = Crease::new(
12398 range,
12399 FoldPlaceholder::test(),
12400 {
12401 let toggle_callback = render_args.clone();
12402 move |row, folded, callback, _cx| {
12403 *toggle_callback.lock() = Some(RenderArgs {
12404 row,
12405 folded,
12406 callback,
12407 });
12408 div()
12409 }
12410 },
12411 |_row, _folded, _cx| div(),
12412 );
12413
12414 editor.insert_creases(Some(crease), cx);
12415 let snapshot = editor.snapshot(cx);
12416 let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
12417 snapshot
12418 })
12419 .unwrap();
12420
12421 let render_args = render_args.lock().take().unwrap();
12422 assert_eq!(render_args.row, MultiBufferRow(1));
12423 assert_eq!(render_args.folded, false);
12424 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
12425
12426 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
12427 .unwrap();
12428 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
12429 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
12430
12431 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
12432 .unwrap();
12433 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
12434 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
12435}
12436
12437fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
12438 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
12439 point..point
12440}
12441
12442fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
12443 let (text, ranges) = marked_text_ranges(marked_text, true);
12444 assert_eq!(view.text(cx), text);
12445 assert_eq!(
12446 view.selections.ranges(cx),
12447 ranges,
12448 "Assert selections are {}",
12449 marked_text
12450 );
12451}
12452
12453/// Handle completion request passing a marked string specifying where the completion
12454/// should be triggered from using '|' character, what range should be replaced, and what completions
12455/// should be returned using '<' and '>' to delimit the range
12456pub fn handle_completion_request(
12457 cx: &mut EditorLspTestContext,
12458 marked_string: &str,
12459 completions: Vec<&'static str>,
12460 counter: Arc<AtomicUsize>,
12461) -> impl Future<Output = ()> {
12462 let complete_from_marker: TextRangeMarker = '|'.into();
12463 let replace_range_marker: TextRangeMarker = ('<', '>').into();
12464 let (_, mut marked_ranges) = marked_text_ranges_by(
12465 marked_string,
12466 vec![complete_from_marker.clone(), replace_range_marker.clone()],
12467 );
12468
12469 let complete_from_position =
12470 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
12471 let replace_range =
12472 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
12473
12474 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
12475 let completions = completions.clone();
12476 counter.fetch_add(1, atomic::Ordering::Release);
12477 async move {
12478 assert_eq!(params.text_document_position.text_document.uri, url.clone());
12479 assert_eq!(
12480 params.text_document_position.position,
12481 complete_from_position
12482 );
12483 Ok(Some(lsp::CompletionResponse::Array(
12484 completions
12485 .iter()
12486 .map(|completion_text| lsp::CompletionItem {
12487 label: completion_text.to_string(),
12488 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12489 range: replace_range,
12490 new_text: completion_text.to_string(),
12491 })),
12492 ..Default::default()
12493 })
12494 .collect(),
12495 )))
12496 }
12497 });
12498
12499 async move {
12500 request.next().await;
12501 }
12502}
12503
12504fn handle_resolve_completion_request(
12505 cx: &mut EditorLspTestContext,
12506 edits: Option<Vec<(&'static str, &'static str)>>,
12507) -> impl Future<Output = ()> {
12508 let edits = edits.map(|edits| {
12509 edits
12510 .iter()
12511 .map(|(marked_string, new_text)| {
12512 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
12513 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
12514 lsp::TextEdit::new(replace_range, new_text.to_string())
12515 })
12516 .collect::<Vec<_>>()
12517 });
12518
12519 let mut request =
12520 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
12521 let edits = edits.clone();
12522 async move {
12523 Ok(lsp::CompletionItem {
12524 additional_text_edits: edits,
12525 ..Default::default()
12526 })
12527 }
12528 });
12529
12530 async move {
12531 request.next().await;
12532 }
12533}
12534
12535pub(crate) fn update_test_language_settings(
12536 cx: &mut TestAppContext,
12537 f: impl Fn(&mut AllLanguageSettingsContent),
12538) {
12539 _ = cx.update(|cx| {
12540 SettingsStore::update_global(cx, |store, cx| {
12541 store.update_user_settings::<AllLanguageSettings>(cx, f);
12542 });
12543 });
12544}
12545
12546pub(crate) fn update_test_project_settings(
12547 cx: &mut TestAppContext,
12548 f: impl Fn(&mut ProjectSettings),
12549) {
12550 _ = cx.update(|cx| {
12551 SettingsStore::update_global(cx, |store, cx| {
12552 store.update_user_settings::<ProjectSettings>(cx, f);
12553 });
12554 });
12555}
12556
12557pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
12558 _ = cx.update(|cx| {
12559 assets::Assets.load_test_fonts(cx);
12560 let store = SettingsStore::test(cx);
12561 cx.set_global(store);
12562 theme::init(theme::LoadThemes::JustBase, cx);
12563 release_channel::init(SemanticVersion::default(), cx);
12564 client::init_settings(cx);
12565 language::init(cx);
12566 Project::init_settings(cx);
12567 workspace::init_settings(cx);
12568 crate::init(cx);
12569 });
12570
12571 update_test_language_settings(cx, f);
12572}
12573
12574pub(crate) fn rust_lang() -> Arc<Language> {
12575 Arc::new(Language::new(
12576 LanguageConfig {
12577 name: "Rust".into(),
12578 matcher: LanguageMatcher {
12579 path_suffixes: vec!["rs".to_string()],
12580 ..Default::default()
12581 },
12582 ..Default::default()
12583 },
12584 Some(tree_sitter_rust::language()),
12585 ))
12586}
12587
12588#[track_caller]
12589fn assert_hunk_revert(
12590 not_reverted_text_with_selections: &str,
12591 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
12592 expected_reverted_text_with_selections: &str,
12593 base_text: &str,
12594 cx: &mut EditorLspTestContext,
12595) {
12596 cx.set_state(not_reverted_text_with_selections);
12597 cx.update_editor(|editor, cx| {
12598 editor
12599 .buffer()
12600 .read(cx)
12601 .as_singleton()
12602 .unwrap()
12603 .update(cx, |buffer, cx| {
12604 buffer.set_diff_base(Some(base_text.into()), cx);
12605 });
12606 });
12607 cx.executor().run_until_parked();
12608
12609 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
12610 let snapshot = editor.buffer().read(cx).snapshot(cx);
12611 let reverted_hunk_statuses = snapshot
12612 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
12613 .map(|hunk| hunk_status(&hunk))
12614 .collect::<Vec<_>>();
12615
12616 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
12617 reverted_hunk_statuses
12618 });
12619 cx.executor().run_until_parked();
12620 cx.assert_editor_state(expected_reverted_text_with_selections);
12621 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
12622}