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, AssetSource, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
14 WindowBounds, 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_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
7024 init_test(cx, |_| {});
7025
7026 let mut cx = EditorLspTestContext::new_rust(
7027 lsp::ServerCapabilities {
7028 completion_provider: Some(lsp::CompletionOptions {
7029 trigger_characters: Some(vec![".".to_string()]),
7030 resolve_provider: Some(true),
7031 ..Default::default()
7032 }),
7033 ..Default::default()
7034 },
7035 cx,
7036 )
7037 .await;
7038
7039 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
7040 cx.simulate_keystroke(".");
7041 let completion_item = lsp::CompletionItem {
7042 label: "Some".into(),
7043 kind: Some(lsp::CompletionItemKind::SNIPPET),
7044 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
7045 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
7046 kind: lsp::MarkupKind::Markdown,
7047 value: "```rust\nSome(2)\n```".to_string(),
7048 })),
7049 deprecated: Some(false),
7050 sort_text: Some("Some".to_string()),
7051 filter_text: Some("Some".to_string()),
7052 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
7053 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
7054 range: lsp::Range {
7055 start: lsp::Position {
7056 line: 0,
7057 character: 22,
7058 },
7059 end: lsp::Position {
7060 line: 0,
7061 character: 22,
7062 },
7063 },
7064 new_text: "Some(2)".to_string(),
7065 })),
7066 additional_text_edits: Some(vec![lsp::TextEdit {
7067 range: lsp::Range {
7068 start: lsp::Position {
7069 line: 0,
7070 character: 20,
7071 },
7072 end: lsp::Position {
7073 line: 0,
7074 character: 22,
7075 },
7076 },
7077 new_text: "".to_string(),
7078 }]),
7079 ..Default::default()
7080 };
7081
7082 let closure_completion_item = completion_item.clone();
7083 let counter = Arc::new(AtomicUsize::new(0));
7084 let counter_clone = counter.clone();
7085 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
7086 let task_completion_item = closure_completion_item.clone();
7087 counter_clone.fetch_add(1, atomic::Ordering::Release);
7088 async move {
7089 Ok(Some(lsp::CompletionResponse::Array(vec![
7090 task_completion_item,
7091 ])))
7092 }
7093 });
7094
7095 cx.condition(|editor, _| editor.context_menu_visible())
7096 .await;
7097 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
7098 assert!(request.next().await.is_some());
7099 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
7100
7101 cx.simulate_keystroke("S");
7102 cx.simulate_keystroke("o");
7103 cx.simulate_keystroke("m");
7104 cx.condition(|editor, _| editor.context_menu_visible())
7105 .await;
7106 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
7107 assert!(request.next().await.is_some());
7108 assert!(request.next().await.is_some());
7109 assert!(request.next().await.is_some());
7110 request.close();
7111 assert!(request.next().await.is_none());
7112 assert_eq!(
7113 counter.load(atomic::Ordering::Acquire),
7114 4,
7115 "With the completions menu open, only one LSP request should happen per input"
7116 );
7117}
7118
7119#[gpui::test]
7120async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
7121 init_test(cx, |_| {});
7122 let mut cx = EditorTestContext::new(cx).await;
7123 let language = Arc::new(Language::new(
7124 LanguageConfig {
7125 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
7126 ..Default::default()
7127 },
7128 Some(tree_sitter_rust::language()),
7129 ));
7130 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
7131
7132 // If multiple selections intersect a line, the line is only toggled once.
7133 cx.set_state(indoc! {"
7134 fn a() {
7135 «//b();
7136 ˇ»// «c();
7137 //ˇ» d();
7138 }
7139 "});
7140
7141 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7142
7143 cx.assert_editor_state(indoc! {"
7144 fn a() {
7145 «b();
7146 c();
7147 ˇ» d();
7148 }
7149 "});
7150
7151 // The comment prefix is inserted at the same column for every line in a
7152 // selection.
7153 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7154
7155 cx.assert_editor_state(indoc! {"
7156 fn a() {
7157 // «b();
7158 // c();
7159 ˇ»// d();
7160 }
7161 "});
7162
7163 // If a selection ends at the beginning of a line, that line is not toggled.
7164 cx.set_selections_state(indoc! {"
7165 fn a() {
7166 // b();
7167 «// c();
7168 ˇ» // d();
7169 }
7170 "});
7171
7172 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7173
7174 cx.assert_editor_state(indoc! {"
7175 fn a() {
7176 // b();
7177 «c();
7178 ˇ» // d();
7179 }
7180 "});
7181
7182 // If a selection span a single line and is empty, the line is toggled.
7183 cx.set_state(indoc! {"
7184 fn a() {
7185 a();
7186 b();
7187 ˇ
7188 }
7189 "});
7190
7191 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7192
7193 cx.assert_editor_state(indoc! {"
7194 fn a() {
7195 a();
7196 b();
7197 //•ˇ
7198 }
7199 "});
7200
7201 // If a selection span multiple lines, empty lines are not toggled.
7202 cx.set_state(indoc! {"
7203 fn a() {
7204 «a();
7205
7206 c();ˇ»
7207 }
7208 "});
7209
7210 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7211
7212 cx.assert_editor_state(indoc! {"
7213 fn a() {
7214 // «a();
7215
7216 // c();ˇ»
7217 }
7218 "});
7219
7220 // If a selection includes multiple comment prefixes, all lines are uncommented.
7221 cx.set_state(indoc! {"
7222 fn a() {
7223 «// a();
7224 /// b();
7225 //! c();ˇ»
7226 }
7227 "});
7228
7229 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
7230
7231 cx.assert_editor_state(indoc! {"
7232 fn a() {
7233 «a();
7234 b();
7235 c();ˇ»
7236 }
7237 "});
7238}
7239
7240#[gpui::test]
7241async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
7242 init_test(cx, |_| {});
7243
7244 let language = Arc::new(Language::new(
7245 LanguageConfig {
7246 line_comments: vec!["// ".into()],
7247 ..Default::default()
7248 },
7249 Some(tree_sitter_rust::language()),
7250 ));
7251
7252 let mut cx = EditorTestContext::new(cx).await;
7253
7254 cx.language_registry().add(language.clone());
7255 cx.update_buffer(|buffer, cx| {
7256 buffer.set_language(Some(language), cx);
7257 });
7258
7259 let toggle_comments = &ToggleComments {
7260 advance_downwards: true,
7261 };
7262
7263 // Single cursor on one line -> advance
7264 // Cursor moves horizontally 3 characters as well on non-blank line
7265 cx.set_state(indoc!(
7266 "fn a() {
7267 ˇdog();
7268 cat();
7269 }"
7270 ));
7271 cx.update_editor(|editor, cx| {
7272 editor.toggle_comments(toggle_comments, cx);
7273 });
7274 cx.assert_editor_state(indoc!(
7275 "fn a() {
7276 // dog();
7277 catˇ();
7278 }"
7279 ));
7280
7281 // Single selection on one line -> don't advance
7282 cx.set_state(indoc!(
7283 "fn a() {
7284 «dog()ˇ»;
7285 cat();
7286 }"
7287 ));
7288 cx.update_editor(|editor, cx| {
7289 editor.toggle_comments(toggle_comments, cx);
7290 });
7291 cx.assert_editor_state(indoc!(
7292 "fn a() {
7293 // «dog()ˇ»;
7294 cat();
7295 }"
7296 ));
7297
7298 // Multiple cursors on one line -> advance
7299 cx.set_state(indoc!(
7300 "fn a() {
7301 ˇdˇog();
7302 cat();
7303 }"
7304 ));
7305 cx.update_editor(|editor, cx| {
7306 editor.toggle_comments(toggle_comments, cx);
7307 });
7308 cx.assert_editor_state(indoc!(
7309 "fn a() {
7310 // dog();
7311 catˇ(ˇ);
7312 }"
7313 ));
7314
7315 // Multiple cursors on one line, with selection -> don't advance
7316 cx.set_state(indoc!(
7317 "fn a() {
7318 ˇdˇog«()ˇ»;
7319 cat();
7320 }"
7321 ));
7322 cx.update_editor(|editor, cx| {
7323 editor.toggle_comments(toggle_comments, cx);
7324 });
7325 cx.assert_editor_state(indoc!(
7326 "fn a() {
7327 // ˇdˇog«()ˇ»;
7328 cat();
7329 }"
7330 ));
7331
7332 // Single cursor on one line -> advance
7333 // Cursor moves to column 0 on blank line
7334 cx.set_state(indoc!(
7335 "fn a() {
7336 ˇdog();
7337
7338 cat();
7339 }"
7340 ));
7341 cx.update_editor(|editor, cx| {
7342 editor.toggle_comments(toggle_comments, cx);
7343 });
7344 cx.assert_editor_state(indoc!(
7345 "fn a() {
7346 // dog();
7347 ˇ
7348 cat();
7349 }"
7350 ));
7351
7352 // Single cursor on one line -> advance
7353 // Cursor starts and ends at column 0
7354 cx.set_state(indoc!(
7355 "fn a() {
7356 ˇ dog();
7357 cat();
7358 }"
7359 ));
7360 cx.update_editor(|editor, cx| {
7361 editor.toggle_comments(toggle_comments, cx);
7362 });
7363 cx.assert_editor_state(indoc!(
7364 "fn a() {
7365 // dog();
7366 ˇ cat();
7367 }"
7368 ));
7369}
7370
7371#[gpui::test]
7372async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
7373 init_test(cx, |_| {});
7374
7375 let mut cx = EditorTestContext::new(cx).await;
7376
7377 let html_language = Arc::new(
7378 Language::new(
7379 LanguageConfig {
7380 name: "HTML".into(),
7381 block_comment: Some(("<!-- ".into(), " -->".into())),
7382 ..Default::default()
7383 },
7384 Some(tree_sitter_html::language()),
7385 )
7386 .with_injection_query(
7387 r#"
7388 (script_element
7389 (raw_text) @content
7390 (#set! "language" "javascript"))
7391 "#,
7392 )
7393 .unwrap(),
7394 );
7395
7396 let javascript_language = Arc::new(Language::new(
7397 LanguageConfig {
7398 name: "JavaScript".into(),
7399 line_comments: vec!["// ".into()],
7400 ..Default::default()
7401 },
7402 Some(tree_sitter_typescript::language_tsx()),
7403 ));
7404
7405 cx.language_registry().add(html_language.clone());
7406 cx.language_registry().add(javascript_language.clone());
7407 cx.update_buffer(|buffer, cx| {
7408 buffer.set_language(Some(html_language), cx);
7409 });
7410
7411 // Toggle comments for empty selections
7412 cx.set_state(
7413 &r#"
7414 <p>A</p>ˇ
7415 <p>B</p>ˇ
7416 <p>C</p>ˇ
7417 "#
7418 .unindent(),
7419 );
7420 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
7421 cx.assert_editor_state(
7422 &r#"
7423 <!-- <p>A</p>ˇ -->
7424 <!-- <p>B</p>ˇ -->
7425 <!-- <p>C</p>ˇ -->
7426 "#
7427 .unindent(),
7428 );
7429 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
7430 cx.assert_editor_state(
7431 &r#"
7432 <p>A</p>ˇ
7433 <p>B</p>ˇ
7434 <p>C</p>ˇ
7435 "#
7436 .unindent(),
7437 );
7438
7439 // Toggle comments for mixture of empty and non-empty selections, where
7440 // multiple selections occupy a given line.
7441 cx.set_state(
7442 &r#"
7443 <p>A«</p>
7444 <p>ˇ»B</p>ˇ
7445 <p>C«</p>
7446 <p>ˇ»D</p>ˇ
7447 "#
7448 .unindent(),
7449 );
7450
7451 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
7452 cx.assert_editor_state(
7453 &r#"
7454 <!-- <p>A«</p>
7455 <p>ˇ»B</p>ˇ -->
7456 <!-- <p>C«</p>
7457 <p>ˇ»D</p>ˇ -->
7458 "#
7459 .unindent(),
7460 );
7461 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
7462 cx.assert_editor_state(
7463 &r#"
7464 <p>A«</p>
7465 <p>ˇ»B</p>ˇ
7466 <p>C«</p>
7467 <p>ˇ»D</p>ˇ
7468 "#
7469 .unindent(),
7470 );
7471
7472 // Toggle comments when different languages are active for different
7473 // selections.
7474 cx.set_state(
7475 &r#"
7476 ˇ<script>
7477 ˇvar x = new Y();
7478 ˇ</script>
7479 "#
7480 .unindent(),
7481 );
7482 cx.executor().run_until_parked();
7483 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
7484 cx.assert_editor_state(
7485 &r#"
7486 <!-- ˇ<script> -->
7487 // ˇvar x = new Y();
7488 <!-- ˇ</script> -->
7489 "#
7490 .unindent(),
7491 );
7492}
7493
7494#[gpui::test]
7495fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
7496 init_test(cx, |_| {});
7497
7498 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
7499 let multibuffer = cx.new_model(|cx| {
7500 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
7501 multibuffer.push_excerpts(
7502 buffer.clone(),
7503 [
7504 ExcerptRange {
7505 context: Point::new(0, 0)..Point::new(0, 4),
7506 primary: None,
7507 },
7508 ExcerptRange {
7509 context: Point::new(1, 0)..Point::new(1, 4),
7510 primary: None,
7511 },
7512 ],
7513 cx,
7514 );
7515 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
7516 multibuffer
7517 });
7518
7519 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
7520 _ = view.update(cx, |view, cx| {
7521 assert_eq!(view.text(cx), "aaaa\nbbbb");
7522 view.change_selections(None, cx, |s| {
7523 s.select_ranges([
7524 Point::new(0, 0)..Point::new(0, 0),
7525 Point::new(1, 0)..Point::new(1, 0),
7526 ])
7527 });
7528
7529 view.handle_input("X", cx);
7530 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
7531 assert_eq!(
7532 view.selections.ranges(cx),
7533 [
7534 Point::new(0, 1)..Point::new(0, 1),
7535 Point::new(1, 1)..Point::new(1, 1),
7536 ]
7537 );
7538
7539 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
7540 view.change_selections(None, cx, |s| {
7541 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
7542 });
7543 view.backspace(&Default::default(), cx);
7544 assert_eq!(view.text(cx), "Xa\nbbb");
7545 assert_eq!(
7546 view.selections.ranges(cx),
7547 [Point::new(1, 0)..Point::new(1, 0)]
7548 );
7549
7550 view.change_selections(None, cx, |s| {
7551 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
7552 });
7553 view.backspace(&Default::default(), cx);
7554 assert_eq!(view.text(cx), "X\nbb");
7555 assert_eq!(
7556 view.selections.ranges(cx),
7557 [Point::new(0, 1)..Point::new(0, 1)]
7558 );
7559 });
7560}
7561
7562#[gpui::test]
7563fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
7564 init_test(cx, |_| {});
7565
7566 let markers = vec![('[', ']').into(), ('(', ')').into()];
7567 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
7568 indoc! {"
7569 [aaaa
7570 (bbbb]
7571 cccc)",
7572 },
7573 markers.clone(),
7574 );
7575 let excerpt_ranges = markers.into_iter().map(|marker| {
7576 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
7577 ExcerptRange {
7578 context,
7579 primary: None,
7580 }
7581 });
7582 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
7583 let multibuffer = cx.new_model(|cx| {
7584 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
7585 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
7586 multibuffer
7587 });
7588
7589 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
7590 _ = view.update(cx, |view, cx| {
7591 let (expected_text, selection_ranges) = marked_text_ranges(
7592 indoc! {"
7593 aaaa
7594 bˇbbb
7595 bˇbbˇb
7596 cccc"
7597 },
7598 true,
7599 );
7600 assert_eq!(view.text(cx), expected_text);
7601 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
7602
7603 view.handle_input("X", cx);
7604
7605 let (expected_text, expected_selections) = marked_text_ranges(
7606 indoc! {"
7607 aaaa
7608 bXˇbbXb
7609 bXˇbbXˇb
7610 cccc"
7611 },
7612 false,
7613 );
7614 assert_eq!(view.text(cx), expected_text);
7615 assert_eq!(view.selections.ranges(cx), expected_selections);
7616
7617 view.newline(&Newline, cx);
7618 let (expected_text, expected_selections) = marked_text_ranges(
7619 indoc! {"
7620 aaaa
7621 bX
7622 ˇbbX
7623 b
7624 bX
7625 ˇbbX
7626 ˇb
7627 cccc"
7628 },
7629 false,
7630 );
7631 assert_eq!(view.text(cx), expected_text);
7632 assert_eq!(view.selections.ranges(cx), expected_selections);
7633 });
7634}
7635
7636#[gpui::test]
7637fn test_refresh_selections(cx: &mut TestAppContext) {
7638 init_test(cx, |_| {});
7639
7640 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
7641 let mut excerpt1_id = None;
7642 let multibuffer = cx.new_model(|cx| {
7643 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
7644 excerpt1_id = multibuffer
7645 .push_excerpts(
7646 buffer.clone(),
7647 [
7648 ExcerptRange {
7649 context: Point::new(0, 0)..Point::new(1, 4),
7650 primary: None,
7651 },
7652 ExcerptRange {
7653 context: Point::new(1, 0)..Point::new(2, 4),
7654 primary: None,
7655 },
7656 ],
7657 cx,
7658 )
7659 .into_iter()
7660 .next();
7661 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
7662 multibuffer
7663 });
7664
7665 let editor = cx.add_window(|cx| {
7666 let mut editor = build_editor(multibuffer.clone(), cx);
7667 let snapshot = editor.snapshot(cx);
7668 editor.change_selections(None, cx, |s| {
7669 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
7670 });
7671 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
7672 assert_eq!(
7673 editor.selections.ranges(cx),
7674 [
7675 Point::new(1, 3)..Point::new(1, 3),
7676 Point::new(2, 1)..Point::new(2, 1),
7677 ]
7678 );
7679 editor
7680 });
7681
7682 // Refreshing selections is a no-op when excerpts haven't changed.
7683 _ = editor.update(cx, |editor, cx| {
7684 editor.change_selections(None, cx, |s| s.refresh());
7685 assert_eq!(
7686 editor.selections.ranges(cx),
7687 [
7688 Point::new(1, 3)..Point::new(1, 3),
7689 Point::new(2, 1)..Point::new(2, 1),
7690 ]
7691 );
7692 });
7693
7694 _ = multibuffer.update(cx, |multibuffer, cx| {
7695 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
7696 });
7697 _ = editor.update(cx, |editor, cx| {
7698 // Removing an excerpt causes the first selection to become degenerate.
7699 assert_eq!(
7700 editor.selections.ranges(cx),
7701 [
7702 Point::new(0, 0)..Point::new(0, 0),
7703 Point::new(0, 1)..Point::new(0, 1)
7704 ]
7705 );
7706
7707 // Refreshing selections will relocate the first selection to the original buffer
7708 // location.
7709 editor.change_selections(None, cx, |s| s.refresh());
7710 assert_eq!(
7711 editor.selections.ranges(cx),
7712 [
7713 Point::new(0, 1)..Point::new(0, 1),
7714 Point::new(0, 3)..Point::new(0, 3)
7715 ]
7716 );
7717 assert!(editor.selections.pending_anchor().is_some());
7718 });
7719}
7720
7721#[gpui::test]
7722fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
7723 init_test(cx, |_| {});
7724
7725 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
7726 let mut excerpt1_id = None;
7727 let multibuffer = cx.new_model(|cx| {
7728 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
7729 excerpt1_id = multibuffer
7730 .push_excerpts(
7731 buffer.clone(),
7732 [
7733 ExcerptRange {
7734 context: Point::new(0, 0)..Point::new(1, 4),
7735 primary: None,
7736 },
7737 ExcerptRange {
7738 context: Point::new(1, 0)..Point::new(2, 4),
7739 primary: None,
7740 },
7741 ],
7742 cx,
7743 )
7744 .into_iter()
7745 .next();
7746 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
7747 multibuffer
7748 });
7749
7750 let editor = cx.add_window(|cx| {
7751 let mut editor = build_editor(multibuffer.clone(), cx);
7752 let snapshot = editor.snapshot(cx);
7753 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
7754 assert_eq!(
7755 editor.selections.ranges(cx),
7756 [Point::new(1, 3)..Point::new(1, 3)]
7757 );
7758 editor
7759 });
7760
7761 _ = multibuffer.update(cx, |multibuffer, cx| {
7762 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
7763 });
7764 _ = editor.update(cx, |editor, cx| {
7765 assert_eq!(
7766 editor.selections.ranges(cx),
7767 [Point::new(0, 0)..Point::new(0, 0)]
7768 );
7769
7770 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
7771 editor.change_selections(None, cx, |s| s.refresh());
7772 assert_eq!(
7773 editor.selections.ranges(cx),
7774 [Point::new(0, 3)..Point::new(0, 3)]
7775 );
7776 assert!(editor.selections.pending_anchor().is_some());
7777 });
7778}
7779
7780#[gpui::test]
7781async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
7782 init_test(cx, |_| {});
7783
7784 let language = Arc::new(
7785 Language::new(
7786 LanguageConfig {
7787 brackets: BracketPairConfig {
7788 pairs: vec![
7789 BracketPair {
7790 start: "{".to_string(),
7791 end: "}".to_string(),
7792 close: true,
7793 surround: true,
7794 newline: true,
7795 },
7796 BracketPair {
7797 start: "/* ".to_string(),
7798 end: " */".to_string(),
7799 close: true,
7800 surround: true,
7801 newline: true,
7802 },
7803 ],
7804 ..Default::default()
7805 },
7806 ..Default::default()
7807 },
7808 Some(tree_sitter_rust::language()),
7809 )
7810 .with_indents_query("")
7811 .unwrap(),
7812 );
7813
7814 let text = concat!(
7815 "{ }\n", //
7816 " x\n", //
7817 " /* */\n", //
7818 "x\n", //
7819 "{{} }\n", //
7820 );
7821
7822 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
7823 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7824 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7825 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
7826 .await;
7827
7828 _ = view.update(cx, |view, cx| {
7829 view.change_selections(None, cx, |s| {
7830 s.select_display_ranges([
7831 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
7832 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
7833 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
7834 ])
7835 });
7836 view.newline(&Newline, cx);
7837
7838 assert_eq!(
7839 view.buffer().read(cx).read(cx).text(),
7840 concat!(
7841 "{ \n", // Suppress rustfmt
7842 "\n", //
7843 "}\n", //
7844 " x\n", //
7845 " /* \n", //
7846 " \n", //
7847 " */\n", //
7848 "x\n", //
7849 "{{} \n", //
7850 "}\n", //
7851 )
7852 );
7853 });
7854}
7855
7856#[gpui::test]
7857fn test_highlighted_ranges(cx: &mut TestAppContext) {
7858 init_test(cx, |_| {});
7859
7860 let editor = cx.add_window(|cx| {
7861 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
7862 build_editor(buffer.clone(), cx)
7863 });
7864
7865 _ = editor.update(cx, |editor, cx| {
7866 struct Type1;
7867 struct Type2;
7868
7869 let buffer = editor.buffer.read(cx).snapshot(cx);
7870
7871 let anchor_range =
7872 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
7873
7874 editor.highlight_background::<Type1>(
7875 &[
7876 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
7877 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
7878 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
7879 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
7880 ],
7881 |_| Hsla::red(),
7882 cx,
7883 );
7884 editor.highlight_background::<Type2>(
7885 &[
7886 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
7887 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
7888 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
7889 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
7890 ],
7891 |_| Hsla::green(),
7892 cx,
7893 );
7894
7895 let snapshot = editor.snapshot(cx);
7896 let mut highlighted_ranges = editor.background_highlights_in_range(
7897 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
7898 &snapshot,
7899 cx.theme().colors(),
7900 );
7901 // Enforce a consistent ordering based on color without relying on the ordering of the
7902 // highlight's `TypeId` which is non-executor.
7903 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
7904 assert_eq!(
7905 highlighted_ranges,
7906 &[
7907 (
7908 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
7909 Hsla::red(),
7910 ),
7911 (
7912 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
7913 Hsla::red(),
7914 ),
7915 (
7916 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
7917 Hsla::green(),
7918 ),
7919 (
7920 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
7921 Hsla::green(),
7922 ),
7923 ]
7924 );
7925 assert_eq!(
7926 editor.background_highlights_in_range(
7927 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
7928 &snapshot,
7929 cx.theme().colors(),
7930 ),
7931 &[(
7932 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
7933 Hsla::red(),
7934 )]
7935 );
7936 });
7937}
7938
7939#[gpui::test]
7940async fn test_following(cx: &mut gpui::TestAppContext) {
7941 init_test(cx, |_| {});
7942
7943 let fs = FakeFs::new(cx.executor());
7944 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7945
7946 let buffer = project.update(cx, |project, cx| {
7947 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
7948 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
7949 });
7950 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
7951 let follower = cx.update(|cx| {
7952 cx.open_window(
7953 WindowOptions {
7954 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
7955 gpui::Point::new(px(0.), px(0.)),
7956 gpui::Point::new(px(10.), px(80.)),
7957 ))),
7958 ..Default::default()
7959 },
7960 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
7961 )
7962 .unwrap()
7963 });
7964
7965 let is_still_following = Rc::new(RefCell::new(true));
7966 let follower_edit_event_count = Rc::new(RefCell::new(0));
7967 let pending_update = Rc::new(RefCell::new(None));
7968 _ = follower.update(cx, {
7969 let update = pending_update.clone();
7970 let is_still_following = is_still_following.clone();
7971 let follower_edit_event_count = follower_edit_event_count.clone();
7972 |_, cx| {
7973 cx.subscribe(
7974 &leader.root_view(cx).unwrap(),
7975 move |_, leader, event, cx| {
7976 leader
7977 .read(cx)
7978 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
7979 },
7980 )
7981 .detach();
7982
7983 cx.subscribe(
7984 &follower.root_view(cx).unwrap(),
7985 move |_, _, event: &EditorEvent, _cx| {
7986 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
7987 *is_still_following.borrow_mut() = false;
7988 }
7989
7990 if let EditorEvent::BufferEdited = event {
7991 *follower_edit_event_count.borrow_mut() += 1;
7992 }
7993 },
7994 )
7995 .detach();
7996 }
7997 });
7998
7999 // Update the selections only
8000 _ = leader.update(cx, |leader, cx| {
8001 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
8002 });
8003 follower
8004 .update(cx, |follower, cx| {
8005 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8006 })
8007 .unwrap()
8008 .await
8009 .unwrap();
8010 _ = follower.update(cx, |follower, cx| {
8011 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
8012 });
8013 assert_eq!(*is_still_following.borrow(), true);
8014 assert_eq!(*follower_edit_event_count.borrow(), 0);
8015
8016 // Update the scroll position only
8017 _ = leader.update(cx, |leader, cx| {
8018 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
8019 });
8020 follower
8021 .update(cx, |follower, cx| {
8022 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8023 })
8024 .unwrap()
8025 .await
8026 .unwrap();
8027 assert_eq!(
8028 follower
8029 .update(cx, |follower, cx| follower.scroll_position(cx))
8030 .unwrap(),
8031 gpui::Point::new(1.5, 3.5)
8032 );
8033 assert_eq!(*is_still_following.borrow(), true);
8034 assert_eq!(*follower_edit_event_count.borrow(), 0);
8035
8036 // Update the selections and scroll position. The follower's scroll position is updated
8037 // via autoscroll, not via the leader's exact scroll position.
8038 _ = leader.update(cx, |leader, cx| {
8039 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
8040 leader.request_autoscroll(Autoscroll::newest(), cx);
8041 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
8042 });
8043 follower
8044 .update(cx, |follower, cx| {
8045 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8046 })
8047 .unwrap()
8048 .await
8049 .unwrap();
8050 _ = follower.update(cx, |follower, cx| {
8051 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
8052 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
8053 });
8054 assert_eq!(*is_still_following.borrow(), true);
8055
8056 // Creating a pending selection that precedes another selection
8057 _ = leader.update(cx, |leader, cx| {
8058 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
8059 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
8060 });
8061 follower
8062 .update(cx, |follower, cx| {
8063 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8064 })
8065 .unwrap()
8066 .await
8067 .unwrap();
8068 _ = follower.update(cx, |follower, cx| {
8069 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
8070 });
8071 assert_eq!(*is_still_following.borrow(), true);
8072
8073 // Extend the pending selection so that it surrounds another selection
8074 _ = leader.update(cx, |leader, cx| {
8075 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
8076 });
8077 follower
8078 .update(cx, |follower, cx| {
8079 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
8080 })
8081 .unwrap()
8082 .await
8083 .unwrap();
8084 _ = follower.update(cx, |follower, cx| {
8085 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
8086 });
8087
8088 // Scrolling locally breaks the follow
8089 _ = follower.update(cx, |follower, cx| {
8090 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
8091 follower.set_scroll_anchor(
8092 ScrollAnchor {
8093 anchor: top_anchor,
8094 offset: gpui::Point::new(0.0, 0.5),
8095 },
8096 cx,
8097 );
8098 });
8099 assert_eq!(*is_still_following.borrow(), false);
8100}
8101
8102#[gpui::test]
8103async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
8104 init_test(cx, |_| {});
8105
8106 let fs = FakeFs::new(cx.executor());
8107 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
8108 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8109 let pane = workspace
8110 .update(cx, |workspace, _| workspace.active_pane().clone())
8111 .unwrap();
8112
8113 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
8114
8115 let leader = pane.update(cx, |_, cx| {
8116 let multibuffer = cx.new_model(|_| MultiBuffer::new(0, ReadWrite));
8117 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
8118 });
8119
8120 // Start following the editor when it has no excerpts.
8121 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
8122 let follower_1 = cx
8123 .update_window(*workspace.deref(), |_, cx| {
8124 Editor::from_state_proto(
8125 pane.clone(),
8126 workspace.root_view(cx).unwrap(),
8127 ViewId {
8128 creator: Default::default(),
8129 id: 0,
8130 },
8131 &mut state_message,
8132 cx,
8133 )
8134 })
8135 .unwrap()
8136 .unwrap()
8137 .await
8138 .unwrap();
8139
8140 let update_message = Rc::new(RefCell::new(None));
8141 follower_1.update(cx, {
8142 let update = update_message.clone();
8143 |_, cx| {
8144 cx.subscribe(&leader, move |_, leader, event, cx| {
8145 leader
8146 .read(cx)
8147 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
8148 })
8149 .detach();
8150 }
8151 });
8152
8153 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
8154 (
8155 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
8156 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
8157 )
8158 });
8159
8160 // Insert some excerpts.
8161 _ = leader.update(cx, |leader, cx| {
8162 leader.buffer.update(cx, |multibuffer, cx| {
8163 let excerpt_ids = multibuffer.push_excerpts(
8164 buffer_1.clone(),
8165 [
8166 ExcerptRange {
8167 context: 1..6,
8168 primary: None,
8169 },
8170 ExcerptRange {
8171 context: 12..15,
8172 primary: None,
8173 },
8174 ExcerptRange {
8175 context: 0..3,
8176 primary: None,
8177 },
8178 ],
8179 cx,
8180 );
8181 multibuffer.insert_excerpts_after(
8182 excerpt_ids[0],
8183 buffer_2.clone(),
8184 [
8185 ExcerptRange {
8186 context: 8..12,
8187 primary: None,
8188 },
8189 ExcerptRange {
8190 context: 0..6,
8191 primary: None,
8192 },
8193 ],
8194 cx,
8195 );
8196 });
8197 });
8198
8199 // Apply the update of adding the excerpts.
8200 follower_1
8201 .update(cx, |follower, cx| {
8202 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
8203 })
8204 .await
8205 .unwrap();
8206 assert_eq!(
8207 follower_1.update(cx, |editor, cx| editor.text(cx)),
8208 leader.update(cx, |editor, cx| editor.text(cx))
8209 );
8210 update_message.borrow_mut().take();
8211
8212 // Start following separately after it already has excerpts.
8213 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
8214 let follower_2 = cx
8215 .update_window(*workspace.deref(), |_, cx| {
8216 Editor::from_state_proto(
8217 pane.clone(),
8218 workspace.root_view(cx).unwrap().clone(),
8219 ViewId {
8220 creator: Default::default(),
8221 id: 0,
8222 },
8223 &mut state_message,
8224 cx,
8225 )
8226 })
8227 .unwrap()
8228 .unwrap()
8229 .await
8230 .unwrap();
8231 assert_eq!(
8232 follower_2.update(cx, |editor, cx| editor.text(cx)),
8233 leader.update(cx, |editor, cx| editor.text(cx))
8234 );
8235
8236 // Remove some excerpts.
8237 _ = leader.update(cx, |leader, cx| {
8238 leader.buffer.update(cx, |multibuffer, cx| {
8239 let excerpt_ids = multibuffer.excerpt_ids();
8240 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
8241 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
8242 });
8243 });
8244
8245 // Apply the update of removing the excerpts.
8246 follower_1
8247 .update(cx, |follower, cx| {
8248 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
8249 })
8250 .await
8251 .unwrap();
8252 follower_2
8253 .update(cx, |follower, cx| {
8254 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
8255 })
8256 .await
8257 .unwrap();
8258 update_message.borrow_mut().take();
8259 assert_eq!(
8260 follower_1.update(cx, |editor, cx| editor.text(cx)),
8261 leader.update(cx, |editor, cx| editor.text(cx))
8262 );
8263}
8264
8265#[gpui::test]
8266async fn go_to_prev_overlapping_diagnostic(
8267 executor: BackgroundExecutor,
8268 cx: &mut gpui::TestAppContext,
8269) {
8270 init_test(cx, |_| {});
8271
8272 let mut cx = EditorTestContext::new(cx).await;
8273 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
8274
8275 cx.set_state(indoc! {"
8276 ˇfn func(abc def: i32) -> u32 {
8277 }
8278 "});
8279
8280 _ = cx.update(|cx| {
8281 _ = project.update(cx, |project, cx| {
8282 project
8283 .update_diagnostics(
8284 LanguageServerId(0),
8285 lsp::PublishDiagnosticsParams {
8286 uri: lsp::Url::from_file_path("/root/file").unwrap(),
8287 version: None,
8288 diagnostics: vec![
8289 lsp::Diagnostic {
8290 range: lsp::Range::new(
8291 lsp::Position::new(0, 11),
8292 lsp::Position::new(0, 12),
8293 ),
8294 severity: Some(lsp::DiagnosticSeverity::ERROR),
8295 ..Default::default()
8296 },
8297 lsp::Diagnostic {
8298 range: lsp::Range::new(
8299 lsp::Position::new(0, 12),
8300 lsp::Position::new(0, 15),
8301 ),
8302 severity: Some(lsp::DiagnosticSeverity::ERROR),
8303 ..Default::default()
8304 },
8305 lsp::Diagnostic {
8306 range: lsp::Range::new(
8307 lsp::Position::new(0, 25),
8308 lsp::Position::new(0, 28),
8309 ),
8310 severity: Some(lsp::DiagnosticSeverity::ERROR),
8311 ..Default::default()
8312 },
8313 ],
8314 },
8315 &[],
8316 cx,
8317 )
8318 .unwrap()
8319 });
8320 });
8321
8322 executor.run_until_parked();
8323
8324 cx.update_editor(|editor, cx| {
8325 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
8326 });
8327
8328 cx.assert_editor_state(indoc! {"
8329 fn func(abc def: i32) -> ˇu32 {
8330 }
8331 "});
8332
8333 cx.update_editor(|editor, cx| {
8334 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
8335 });
8336
8337 cx.assert_editor_state(indoc! {"
8338 fn func(abc ˇdef: i32) -> u32 {
8339 }
8340 "});
8341
8342 cx.update_editor(|editor, cx| {
8343 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
8344 });
8345
8346 cx.assert_editor_state(indoc! {"
8347 fn func(abcˇ def: i32) -> u32 {
8348 }
8349 "});
8350
8351 cx.update_editor(|editor, cx| {
8352 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
8353 });
8354
8355 cx.assert_editor_state(indoc! {"
8356 fn func(abc def: i32) -> ˇu32 {
8357 }
8358 "});
8359}
8360
8361#[gpui::test]
8362async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
8363 init_test(cx, |_| {});
8364
8365 let mut cx = EditorTestContext::new(cx).await;
8366
8367 let diff_base = r#"
8368 use some::mod;
8369
8370 const A: u32 = 42;
8371
8372 fn main() {
8373 println!("hello");
8374
8375 println!("world");
8376 }
8377 "#
8378 .unindent();
8379
8380 // Edits are modified, removed, modified, added
8381 cx.set_state(
8382 &r#"
8383 use some::modified;
8384
8385 ˇ
8386 fn main() {
8387 println!("hello there");
8388
8389 println!("around the");
8390 println!("world");
8391 }
8392 "#
8393 .unindent(),
8394 );
8395
8396 cx.set_diff_base(Some(&diff_base));
8397 executor.run_until_parked();
8398
8399 cx.update_editor(|editor, cx| {
8400 //Wrap around the bottom of the buffer
8401 for _ in 0..3 {
8402 editor.go_to_hunk(&GoToHunk, cx);
8403 }
8404 });
8405
8406 cx.assert_editor_state(
8407 &r#"
8408 ˇuse some::modified;
8409
8410
8411 fn main() {
8412 println!("hello there");
8413
8414 println!("around the");
8415 println!("world");
8416 }
8417 "#
8418 .unindent(),
8419 );
8420
8421 cx.update_editor(|editor, cx| {
8422 //Wrap around the top of the buffer
8423 for _ in 0..2 {
8424 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
8425 }
8426 });
8427
8428 cx.assert_editor_state(
8429 &r#"
8430 use some::modified;
8431
8432
8433 fn main() {
8434 ˇ println!("hello there");
8435
8436 println!("around the");
8437 println!("world");
8438 }
8439 "#
8440 .unindent(),
8441 );
8442
8443 cx.update_editor(|editor, cx| {
8444 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
8445 });
8446
8447 cx.assert_editor_state(
8448 &r#"
8449 use some::modified;
8450
8451 ˇ
8452 fn main() {
8453 println!("hello there");
8454
8455 println!("around the");
8456 println!("world");
8457 }
8458 "#
8459 .unindent(),
8460 );
8461
8462 cx.update_editor(|editor, cx| {
8463 for _ in 0..3 {
8464 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
8465 }
8466 });
8467
8468 cx.assert_editor_state(
8469 &r#"
8470 use some::modified;
8471
8472
8473 fn main() {
8474 ˇ println!("hello there");
8475
8476 println!("around the");
8477 println!("world");
8478 }
8479 "#
8480 .unindent(),
8481 );
8482
8483 cx.update_editor(|editor, cx| {
8484 editor.fold(&Fold, cx);
8485
8486 //Make sure that the fold only gets one hunk
8487 for _ in 0..4 {
8488 editor.go_to_hunk(&GoToHunk, cx);
8489 }
8490 });
8491
8492 cx.assert_editor_state(
8493 &r#"
8494 ˇuse some::modified;
8495
8496
8497 fn main() {
8498 println!("hello there");
8499
8500 println!("around the");
8501 println!("world");
8502 }
8503 "#
8504 .unindent(),
8505 );
8506}
8507
8508#[test]
8509fn test_split_words() {
8510 fn split(text: &str) -> Vec<&str> {
8511 split_words(text).collect()
8512 }
8513
8514 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
8515 assert_eq!(split("hello_world"), &["hello_", "world"]);
8516 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
8517 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
8518 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
8519 assert_eq!(split("helloworld"), &["helloworld"]);
8520
8521 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
8522}
8523
8524#[gpui::test]
8525async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
8526 init_test(cx, |_| {});
8527
8528 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
8529 let mut assert = |before, after| {
8530 let _state_context = cx.set_state(before);
8531 cx.update_editor(|editor, cx| {
8532 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
8533 });
8534 cx.assert_editor_state(after);
8535 };
8536
8537 // Outside bracket jumps to outside of matching bracket
8538 assert("console.logˇ(var);", "console.log(var)ˇ;");
8539 assert("console.log(var)ˇ;", "console.logˇ(var);");
8540
8541 // Inside bracket jumps to inside of matching bracket
8542 assert("console.log(ˇvar);", "console.log(varˇ);");
8543 assert("console.log(varˇ);", "console.log(ˇvar);");
8544
8545 // When outside a bracket and inside, favor jumping to the inside bracket
8546 assert(
8547 "console.log('foo', [1, 2, 3]ˇ);",
8548 "console.log(ˇ'foo', [1, 2, 3]);",
8549 );
8550 assert(
8551 "console.log(ˇ'foo', [1, 2, 3]);",
8552 "console.log('foo', [1, 2, 3]ˇ);",
8553 );
8554
8555 // Bias forward if two options are equally likely
8556 assert(
8557 "let result = curried_fun()ˇ();",
8558 "let result = curried_fun()()ˇ;",
8559 );
8560
8561 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
8562 assert(
8563 indoc! {"
8564 function test() {
8565 console.log('test')ˇ
8566 }"},
8567 indoc! {"
8568 function test() {
8569 console.logˇ('test')
8570 }"},
8571 );
8572}
8573
8574#[gpui::test]
8575async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
8576 init_test(cx, |_| {});
8577
8578 let fs = FakeFs::new(cx.executor());
8579 fs.insert_tree(
8580 "/a",
8581 json!({
8582 "main.rs": "fn main() { let a = 5; }",
8583 "other.rs": "// Test file",
8584 }),
8585 )
8586 .await;
8587 let project = Project::test(fs, ["/a".as_ref()], cx).await;
8588
8589 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8590 language_registry.add(Arc::new(Language::new(
8591 LanguageConfig {
8592 name: "Rust".into(),
8593 matcher: LanguageMatcher {
8594 path_suffixes: vec!["rs".to_string()],
8595 ..Default::default()
8596 },
8597 brackets: BracketPairConfig {
8598 pairs: vec![BracketPair {
8599 start: "{".to_string(),
8600 end: "}".to_string(),
8601 close: true,
8602 surround: true,
8603 newline: true,
8604 }],
8605 disabled_scopes_by_bracket_ix: Vec::new(),
8606 },
8607 ..Default::default()
8608 },
8609 Some(tree_sitter_rust::language()),
8610 )));
8611 let mut fake_servers = language_registry.register_fake_lsp_adapter(
8612 "Rust",
8613 FakeLspAdapter {
8614 capabilities: lsp::ServerCapabilities {
8615 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
8616 first_trigger_character: "{".to_string(),
8617 more_trigger_character: None,
8618 }),
8619 ..Default::default()
8620 },
8621 ..Default::default()
8622 },
8623 );
8624
8625 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8626
8627 let cx = &mut VisualTestContext::from_window(*workspace, cx);
8628
8629 let worktree_id = workspace
8630 .update(cx, |workspace, cx| {
8631 workspace.project().update(cx, |project, cx| {
8632 project.worktrees().next().unwrap().read(cx).id()
8633 })
8634 })
8635 .unwrap();
8636
8637 let buffer = project
8638 .update(cx, |project, cx| {
8639 project.open_local_buffer("/a/main.rs", cx)
8640 })
8641 .await
8642 .unwrap();
8643 cx.executor().run_until_parked();
8644 cx.executor().start_waiting();
8645 let fake_server = fake_servers.next().await.unwrap();
8646 let editor_handle = workspace
8647 .update(cx, |workspace, cx| {
8648 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
8649 })
8650 .unwrap()
8651 .await
8652 .unwrap()
8653 .downcast::<Editor>()
8654 .unwrap();
8655
8656 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
8657 assert_eq!(
8658 params.text_document_position.text_document.uri,
8659 lsp::Url::from_file_path("/a/main.rs").unwrap(),
8660 );
8661 assert_eq!(
8662 params.text_document_position.position,
8663 lsp::Position::new(0, 21),
8664 );
8665
8666 Ok(Some(vec![lsp::TextEdit {
8667 new_text: "]".to_string(),
8668 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
8669 }]))
8670 });
8671
8672 editor_handle.update(cx, |editor, cx| {
8673 editor.focus(cx);
8674 editor.change_selections(None, cx, |s| {
8675 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
8676 });
8677 editor.handle_input("{", cx);
8678 });
8679
8680 cx.executor().run_until_parked();
8681
8682 _ = buffer.update(cx, |buffer, _| {
8683 assert_eq!(
8684 buffer.text(),
8685 "fn main() { let a = {5}; }",
8686 "No extra braces from on type formatting should appear in the buffer"
8687 )
8688 });
8689}
8690
8691#[gpui::test]
8692async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
8693 init_test(cx, |_| {});
8694
8695 let fs = FakeFs::new(cx.executor());
8696 fs.insert_tree(
8697 "/a",
8698 json!({
8699 "main.rs": "fn main() { let a = 5; }",
8700 "other.rs": "// Test file",
8701 }),
8702 )
8703 .await;
8704
8705 let project = Project::test(fs, ["/a".as_ref()], cx).await;
8706
8707 let server_restarts = Arc::new(AtomicUsize::new(0));
8708 let closure_restarts = Arc::clone(&server_restarts);
8709 let language_server_name = "test language server";
8710 let language_name: Arc<str> = "Rust".into();
8711
8712 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8713 language_registry.add(Arc::new(Language::new(
8714 LanguageConfig {
8715 name: Arc::clone(&language_name),
8716 matcher: LanguageMatcher {
8717 path_suffixes: vec!["rs".to_string()],
8718 ..Default::default()
8719 },
8720 ..Default::default()
8721 },
8722 Some(tree_sitter_rust::language()),
8723 )));
8724 let mut fake_servers = language_registry.register_fake_lsp_adapter(
8725 "Rust",
8726 FakeLspAdapter {
8727 name: language_server_name,
8728 initialization_options: Some(json!({
8729 "testOptionValue": true
8730 })),
8731 initializer: Some(Box::new(move |fake_server| {
8732 let task_restarts = Arc::clone(&closure_restarts);
8733 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
8734 task_restarts.fetch_add(1, atomic::Ordering::Release);
8735 futures::future::ready(Ok(()))
8736 });
8737 })),
8738 ..Default::default()
8739 },
8740 );
8741
8742 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8743 let _buffer = project
8744 .update(cx, |project, cx| {
8745 project.open_local_buffer("/a/main.rs", cx)
8746 })
8747 .await
8748 .unwrap();
8749 let _fake_server = fake_servers.next().await.unwrap();
8750 update_test_language_settings(cx, |language_settings| {
8751 language_settings.languages.insert(
8752 Arc::clone(&language_name),
8753 LanguageSettingsContent {
8754 tab_size: NonZeroU32::new(8),
8755 ..Default::default()
8756 },
8757 );
8758 });
8759 cx.executor().run_until_parked();
8760 assert_eq!(
8761 server_restarts.load(atomic::Ordering::Acquire),
8762 0,
8763 "Should not restart LSP server on an unrelated change"
8764 );
8765
8766 update_test_project_settings(cx, |project_settings| {
8767 project_settings.lsp.insert(
8768 "Some other server name".into(),
8769 LspSettings {
8770 binary: None,
8771 settings: None,
8772 initialization_options: Some(json!({
8773 "some other init value": false
8774 })),
8775 },
8776 );
8777 });
8778 cx.executor().run_until_parked();
8779 assert_eq!(
8780 server_restarts.load(atomic::Ordering::Acquire),
8781 0,
8782 "Should not restart LSP server on an unrelated LSP settings change"
8783 );
8784
8785 update_test_project_settings(cx, |project_settings| {
8786 project_settings.lsp.insert(
8787 language_server_name.into(),
8788 LspSettings {
8789 binary: None,
8790 settings: None,
8791 initialization_options: Some(json!({
8792 "anotherInitValue": false
8793 })),
8794 },
8795 );
8796 });
8797 cx.executor().run_until_parked();
8798 assert_eq!(
8799 server_restarts.load(atomic::Ordering::Acquire),
8800 1,
8801 "Should restart LSP server on a related LSP settings change"
8802 );
8803
8804 update_test_project_settings(cx, |project_settings| {
8805 project_settings.lsp.insert(
8806 language_server_name.into(),
8807 LspSettings {
8808 binary: None,
8809 settings: None,
8810 initialization_options: Some(json!({
8811 "anotherInitValue": false
8812 })),
8813 },
8814 );
8815 });
8816 cx.executor().run_until_parked();
8817 assert_eq!(
8818 server_restarts.load(atomic::Ordering::Acquire),
8819 1,
8820 "Should not restart LSP server on a related LSP settings change that is the same"
8821 );
8822
8823 update_test_project_settings(cx, |project_settings| {
8824 project_settings.lsp.insert(
8825 language_server_name.into(),
8826 LspSettings {
8827 binary: None,
8828 settings: None,
8829 initialization_options: None,
8830 },
8831 );
8832 });
8833 cx.executor().run_until_parked();
8834 assert_eq!(
8835 server_restarts.load(atomic::Ordering::Acquire),
8836 2,
8837 "Should restart LSP server on another related LSP settings change"
8838 );
8839}
8840
8841#[gpui::test]
8842async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
8843 init_test(cx, |_| {});
8844
8845 let mut cx = EditorLspTestContext::new_rust(
8846 lsp::ServerCapabilities {
8847 completion_provider: Some(lsp::CompletionOptions {
8848 trigger_characters: Some(vec![".".to_string()]),
8849 resolve_provider: Some(true),
8850 ..Default::default()
8851 }),
8852 ..Default::default()
8853 },
8854 cx,
8855 )
8856 .await;
8857
8858 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8859 cx.simulate_keystroke(".");
8860 let completion_item = lsp::CompletionItem {
8861 label: "some".into(),
8862 kind: Some(lsp::CompletionItemKind::SNIPPET),
8863 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8864 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8865 kind: lsp::MarkupKind::Markdown,
8866 value: "```rust\nSome(2)\n```".to_string(),
8867 })),
8868 deprecated: Some(false),
8869 sort_text: Some("fffffff2".to_string()),
8870 filter_text: Some("some".to_string()),
8871 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8872 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8873 range: lsp::Range {
8874 start: lsp::Position {
8875 line: 0,
8876 character: 22,
8877 },
8878 end: lsp::Position {
8879 line: 0,
8880 character: 22,
8881 },
8882 },
8883 new_text: "Some(2)".to_string(),
8884 })),
8885 additional_text_edits: Some(vec![lsp::TextEdit {
8886 range: lsp::Range {
8887 start: lsp::Position {
8888 line: 0,
8889 character: 20,
8890 },
8891 end: lsp::Position {
8892 line: 0,
8893 character: 22,
8894 },
8895 },
8896 new_text: "".to_string(),
8897 }]),
8898 ..Default::default()
8899 };
8900
8901 let closure_completion_item = completion_item.clone();
8902 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8903 let task_completion_item = closure_completion_item.clone();
8904 async move {
8905 Ok(Some(lsp::CompletionResponse::Array(vec![
8906 task_completion_item,
8907 ])))
8908 }
8909 });
8910
8911 request.next().await;
8912
8913 cx.condition(|editor, _| editor.context_menu_visible())
8914 .await;
8915 let apply_additional_edits = cx.update_editor(|editor, cx| {
8916 editor
8917 .confirm_completion(&ConfirmCompletion::default(), cx)
8918 .unwrap()
8919 });
8920 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
8921
8922 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
8923 let task_completion_item = completion_item.clone();
8924 async move { Ok(task_completion_item) }
8925 })
8926 .next()
8927 .await
8928 .unwrap();
8929 apply_additional_edits.await.unwrap();
8930 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
8931}
8932
8933#[gpui::test]
8934async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
8935 init_test(cx, |_| {});
8936
8937 let mut cx = EditorLspTestContext::new(
8938 Language::new(
8939 LanguageConfig {
8940 matcher: LanguageMatcher {
8941 path_suffixes: vec!["jsx".into()],
8942 ..Default::default()
8943 },
8944 overrides: [(
8945 "element".into(),
8946 LanguageConfigOverride {
8947 word_characters: Override::Set(['-'].into_iter().collect()),
8948 ..Default::default()
8949 },
8950 )]
8951 .into_iter()
8952 .collect(),
8953 ..Default::default()
8954 },
8955 Some(tree_sitter_typescript::language_tsx()),
8956 )
8957 .with_override_query("(jsx_self_closing_element) @element")
8958 .unwrap(),
8959 lsp::ServerCapabilities {
8960 completion_provider: Some(lsp::CompletionOptions {
8961 trigger_characters: Some(vec![":".to_string()]),
8962 ..Default::default()
8963 }),
8964 ..Default::default()
8965 },
8966 cx,
8967 )
8968 .await;
8969
8970 cx.lsp
8971 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8972 Ok(Some(lsp::CompletionResponse::Array(vec![
8973 lsp::CompletionItem {
8974 label: "bg-blue".into(),
8975 ..Default::default()
8976 },
8977 lsp::CompletionItem {
8978 label: "bg-red".into(),
8979 ..Default::default()
8980 },
8981 lsp::CompletionItem {
8982 label: "bg-yellow".into(),
8983 ..Default::default()
8984 },
8985 ])))
8986 });
8987
8988 cx.set_state(r#"<p class="bgˇ" />"#);
8989
8990 // Trigger completion when typing a dash, because the dash is an extra
8991 // word character in the 'element' scope, which contains the cursor.
8992 cx.simulate_keystroke("-");
8993 cx.executor().run_until_parked();
8994 cx.update_editor(|editor, _| {
8995 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8996 assert_eq!(
8997 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8998 &["bg-red", "bg-blue", "bg-yellow"]
8999 );
9000 } else {
9001 panic!("expected completion menu to be open");
9002 }
9003 });
9004
9005 cx.simulate_keystroke("l");
9006 cx.executor().run_until_parked();
9007 cx.update_editor(|editor, _| {
9008 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9009 assert_eq!(
9010 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9011 &["bg-blue", "bg-yellow"]
9012 );
9013 } else {
9014 panic!("expected completion menu to be open");
9015 }
9016 });
9017
9018 // When filtering completions, consider the character after the '-' to
9019 // be the start of a subword.
9020 cx.set_state(r#"<p class="yelˇ" />"#);
9021 cx.simulate_keystroke("l");
9022 cx.executor().run_until_parked();
9023 cx.update_editor(|editor, _| {
9024 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
9025 assert_eq!(
9026 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
9027 &["bg-yellow"]
9028 );
9029 } else {
9030 panic!("expected completion menu to be open");
9031 }
9032 });
9033}
9034
9035#[gpui::test]
9036async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
9037 init_test(cx, |settings| {
9038 settings.defaults.formatter = Some(language_settings::Formatter::Prettier)
9039 });
9040
9041 let fs = FakeFs::new(cx.executor());
9042 fs.insert_file("/file.ts", Default::default()).await;
9043
9044 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
9045 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9046
9047 language_registry.add(Arc::new(Language::new(
9048 LanguageConfig {
9049 name: "TypeScript".into(),
9050 matcher: LanguageMatcher {
9051 path_suffixes: vec!["ts".to_string()],
9052 ..Default::default()
9053 },
9054 ..Default::default()
9055 },
9056 Some(tree_sitter_rust::language()),
9057 )));
9058 update_test_language_settings(cx, |settings| {
9059 settings.defaults.prettier = Some(PrettierSettings {
9060 allowed: true,
9061 ..PrettierSettings::default()
9062 });
9063 });
9064
9065 let test_plugin = "test_plugin";
9066 let _ = language_registry.register_fake_lsp_adapter(
9067 "TypeScript",
9068 FakeLspAdapter {
9069 prettier_plugins: vec![test_plugin],
9070 ..Default::default()
9071 },
9072 );
9073
9074 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
9075 let buffer = project
9076 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
9077 .await
9078 .unwrap();
9079
9080 let buffer_text = "one\ntwo\nthree\n";
9081 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9082 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9083 _ = editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
9084
9085 editor
9086 .update(cx, |editor, cx| {
9087 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
9088 })
9089 .unwrap()
9090 .await;
9091 assert_eq!(
9092 editor.update(cx, |editor, cx| editor.text(cx)),
9093 buffer_text.to_string() + prettier_format_suffix,
9094 "Test prettier formatting was not applied to the original buffer text",
9095 );
9096
9097 update_test_language_settings(cx, |settings| {
9098 settings.defaults.formatter = Some(language_settings::Formatter::Auto)
9099 });
9100 let format = editor.update(cx, |editor, cx| {
9101 editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
9102 });
9103 format.await.unwrap();
9104 assert_eq!(
9105 editor.update(cx, |editor, cx| editor.text(cx)),
9106 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
9107 "Autoformatting (via test prettier) was not applied to the original buffer text",
9108 );
9109}
9110
9111#[gpui::test]
9112async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
9113 init_test(cx, |_| {});
9114 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9115 let base_text = indoc! {r#"struct Row;
9116struct Row1;
9117struct Row2;
9118
9119struct Row4;
9120struct Row5;
9121struct Row6;
9122
9123struct Row8;
9124struct Row9;
9125struct Row10;"#};
9126
9127 // When addition hunks are not adjacent to carets, no hunk revert is performed
9128 assert_hunk_revert(
9129 indoc! {r#"struct Row;
9130 struct Row1;
9131 struct Row1.1;
9132 struct Row1.2;
9133 struct Row2;ˇ
9134
9135 struct Row4;
9136 struct Row5;
9137 struct Row6;
9138
9139 struct Row8;
9140 ˇstruct Row9;
9141 struct Row9.1;
9142 struct Row9.2;
9143 struct Row9.3;
9144 struct Row10;"#},
9145 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
9146 indoc! {r#"struct Row;
9147 struct Row1;
9148 struct Row1.1;
9149 struct Row1.2;
9150 struct Row2;ˇ
9151
9152 struct Row4;
9153 struct Row5;
9154 struct Row6;
9155
9156 struct Row8;
9157 ˇstruct Row9;
9158 struct Row9.1;
9159 struct Row9.2;
9160 struct Row9.3;
9161 struct Row10;"#},
9162 base_text,
9163 &mut cx,
9164 );
9165 // Same for selections
9166 assert_hunk_revert(
9167 indoc! {r#"struct Row;
9168 struct Row1;
9169 struct Row2;
9170 struct Row2.1;
9171 struct Row2.2;
9172 «ˇ
9173 struct Row4;
9174 struct» Row5;
9175 «struct Row6;
9176 ˇ»
9177 struct Row9.1;
9178 struct Row9.2;
9179 struct Row9.3;
9180 struct Row8;
9181 struct Row9;
9182 struct Row10;"#},
9183 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
9184 indoc! {r#"struct Row;
9185 struct Row1;
9186 struct Row2;
9187 struct Row2.1;
9188 struct Row2.2;
9189 «ˇ
9190 struct Row4;
9191 struct» Row5;
9192 «struct Row6;
9193 ˇ»
9194 struct Row9.1;
9195 struct Row9.2;
9196 struct Row9.3;
9197 struct Row8;
9198 struct Row9;
9199 struct Row10;"#},
9200 base_text,
9201 &mut cx,
9202 );
9203
9204 // When carets and selections intersect the addition hunks, those are reverted.
9205 // Adjacent carets got merged.
9206 assert_hunk_revert(
9207 indoc! {r#"struct Row;
9208 ˇ// something on the top
9209 struct Row1;
9210 struct Row2;
9211 struct Roˇw3.1;
9212 struct Row2.2;
9213 struct Row2.3;ˇ
9214
9215 struct Row4;
9216 struct ˇRow5.1;
9217 struct Row5.2;
9218 struct «Rowˇ»5.3;
9219 struct Row5;
9220 struct Row6;
9221 ˇ
9222 struct Row9.1;
9223 struct «Rowˇ»9.2;
9224 struct «ˇRow»9.3;
9225 struct Row8;
9226 struct Row9;
9227 «ˇ// something on bottom»
9228 struct Row10;"#},
9229 vec![
9230 DiffHunkStatus::Added,
9231 DiffHunkStatus::Added,
9232 DiffHunkStatus::Added,
9233 DiffHunkStatus::Added,
9234 DiffHunkStatus::Added,
9235 ],
9236 indoc! {r#"struct Row;
9237 ˇstruct Row1;
9238 struct Row2;
9239 ˇ
9240 struct Row4;
9241 ˇstruct Row5;
9242 struct Row6;
9243 ˇ
9244 ˇstruct Row8;
9245 struct Row9;
9246 ˇstruct Row10;"#},
9247 base_text,
9248 &mut cx,
9249 );
9250}
9251
9252#[gpui::test]
9253async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
9254 init_test(cx, |_| {});
9255 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9256 let base_text = indoc! {r#"struct Row;
9257struct Row1;
9258struct Row2;
9259
9260struct Row4;
9261struct Row5;
9262struct Row6;
9263
9264struct Row8;
9265struct Row9;
9266struct Row10;"#};
9267
9268 // Modification hunks behave the same as the addition ones.
9269 assert_hunk_revert(
9270 indoc! {r#"struct Row;
9271 struct Row1;
9272 struct Row33;
9273 ˇ
9274 struct Row4;
9275 struct Row5;
9276 struct Row6;
9277 ˇ
9278 struct Row99;
9279 struct Row9;
9280 struct Row10;"#},
9281 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
9282 indoc! {r#"struct Row;
9283 struct Row1;
9284 struct Row33;
9285 ˇ
9286 struct Row4;
9287 struct Row5;
9288 struct Row6;
9289 ˇ
9290 struct Row99;
9291 struct Row9;
9292 struct Row10;"#},
9293 base_text,
9294 &mut cx,
9295 );
9296 assert_hunk_revert(
9297 indoc! {r#"struct Row;
9298 struct Row1;
9299 struct Row33;
9300 «ˇ
9301 struct Row4;
9302 struct» Row5;
9303 «struct Row6;
9304 ˇ»
9305 struct Row99;
9306 struct Row9;
9307 struct Row10;"#},
9308 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
9309 indoc! {r#"struct Row;
9310 struct Row1;
9311 struct Row33;
9312 «ˇ
9313 struct Row4;
9314 struct» Row5;
9315 «struct Row6;
9316 ˇ»
9317 struct Row99;
9318 struct Row9;
9319 struct Row10;"#},
9320 base_text,
9321 &mut cx,
9322 );
9323
9324 assert_hunk_revert(
9325 indoc! {r#"ˇstruct Row1.1;
9326 struct Row1;
9327 «ˇstr»uct Row22;
9328
9329 struct ˇRow44;
9330 struct Row5;
9331 struct «Rˇ»ow66;ˇ
9332
9333 «struˇ»ct Row88;
9334 struct Row9;
9335 struct Row1011;ˇ"#},
9336 vec![
9337 DiffHunkStatus::Modified,
9338 DiffHunkStatus::Modified,
9339 DiffHunkStatus::Modified,
9340 DiffHunkStatus::Modified,
9341 DiffHunkStatus::Modified,
9342 DiffHunkStatus::Modified,
9343 ],
9344 indoc! {r#"struct Row;
9345 ˇstruct Row1;
9346 struct Row2;
9347 ˇ
9348 struct Row4;
9349 ˇstruct Row5;
9350 struct Row6;
9351 ˇ
9352 struct Row8;
9353 ˇstruct Row9;
9354 struct Row10;ˇ"#},
9355 base_text,
9356 &mut cx,
9357 );
9358}
9359
9360#[gpui::test]
9361async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
9362 init_test(cx, |_| {});
9363 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9364 let base_text = indoc! {r#"struct Row;
9365struct Row1;
9366struct Row2;
9367
9368struct Row4;
9369struct Row5;
9370struct Row6;
9371
9372struct Row8;
9373struct Row9;
9374struct Row10;"#};
9375
9376 // Deletion hunks trigger with carets on ajacent rows, so carets and selections have to stay farther to avoid the revert
9377 assert_hunk_revert(
9378 indoc! {r#"struct Row;
9379 struct Row2;
9380
9381 ˇstruct Row4;
9382 struct Row5;
9383 struct Row6;
9384 ˇ
9385 struct Row8;
9386 struct Row10;"#},
9387 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
9388 indoc! {r#"struct Row;
9389 struct Row2;
9390
9391 ˇstruct Row4;
9392 struct Row5;
9393 struct Row6;
9394 ˇ
9395 struct Row8;
9396 struct Row10;"#},
9397 base_text,
9398 &mut cx,
9399 );
9400 assert_hunk_revert(
9401 indoc! {r#"struct Row;
9402 struct Row2;
9403
9404 «ˇstruct Row4;
9405 struct» Row5;
9406 «struct Row6;
9407 ˇ»
9408 struct Row8;
9409 struct Row10;"#},
9410 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
9411 indoc! {r#"struct Row;
9412 struct Row2;
9413
9414 «ˇstruct Row4;
9415 struct» Row5;
9416 «struct Row6;
9417 ˇ»
9418 struct Row8;
9419 struct Row10;"#},
9420 base_text,
9421 &mut cx,
9422 );
9423
9424 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
9425 assert_hunk_revert(
9426 indoc! {r#"struct Row;
9427 ˇstruct Row2;
9428
9429 struct Row4;
9430 struct Row5;
9431 struct Row6;
9432
9433 struct Row8;ˇ
9434 struct Row10;"#},
9435 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
9436 indoc! {r#"struct Row;
9437 struct Row1;
9438 ˇstruct Row2;
9439
9440 struct Row4;
9441 struct Row5;
9442 struct Row6;
9443
9444 struct Row8;ˇ
9445 struct Row9;
9446 struct Row10;"#},
9447 base_text,
9448 &mut cx,
9449 );
9450 assert_hunk_revert(
9451 indoc! {r#"struct Row;
9452 struct Row2«ˇ;
9453 struct Row4;
9454 struct» Row5;
9455 «struct Row6;
9456
9457 struct Row8;ˇ»
9458 struct Row10;"#},
9459 vec![
9460 DiffHunkStatus::Removed,
9461 DiffHunkStatus::Removed,
9462 DiffHunkStatus::Removed,
9463 ],
9464 indoc! {r#"struct Row;
9465 struct Row1;
9466 struct Row2«ˇ;
9467
9468 struct Row4;
9469 struct» Row5;
9470 «struct Row6;
9471
9472 struct Row8;ˇ»
9473 struct Row9;
9474 struct Row10;"#},
9475 base_text,
9476 &mut cx,
9477 );
9478}
9479
9480#[gpui::test]
9481async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
9482 init_test(cx, |_| {});
9483
9484 let cols = 4;
9485 let rows = 10;
9486 let sample_text_1 = sample_text(rows, cols, 'a');
9487 assert_eq!(
9488 sample_text_1,
9489 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
9490 );
9491 let sample_text_2 = sample_text(rows, cols, 'l');
9492 assert_eq!(
9493 sample_text_2,
9494 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
9495 );
9496 let sample_text_3 = sample_text(rows, cols, 'v');
9497 assert_eq!(
9498 sample_text_3,
9499 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
9500 );
9501
9502 fn diff_every_buffer_row(
9503 buffer: &Model<Buffer>,
9504 sample_text: String,
9505 cols: usize,
9506 cx: &mut gpui::TestAppContext,
9507 ) {
9508 // revert first character in each row, creating one large diff hunk per buffer
9509 let is_first_char = |offset: usize| offset % cols == 0;
9510 buffer.update(cx, |buffer, cx| {
9511 buffer.set_text(
9512 sample_text
9513 .chars()
9514 .enumerate()
9515 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
9516 .collect::<String>(),
9517 cx,
9518 );
9519 buffer.set_diff_base(Some(sample_text), cx);
9520 });
9521 cx.executor().run_until_parked();
9522 }
9523
9524 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
9525 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
9526
9527 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
9528 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
9529
9530 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
9531 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
9532
9533 let multibuffer = cx.new_model(|cx| {
9534 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
9535 multibuffer.push_excerpts(
9536 buffer_1.clone(),
9537 [
9538 ExcerptRange {
9539 context: Point::new(0, 0)..Point::new(3, 0),
9540 primary: None,
9541 },
9542 ExcerptRange {
9543 context: Point::new(5, 0)..Point::new(7, 0),
9544 primary: None,
9545 },
9546 ExcerptRange {
9547 context: Point::new(9, 0)..Point::new(10, 4),
9548 primary: None,
9549 },
9550 ],
9551 cx,
9552 );
9553 multibuffer.push_excerpts(
9554 buffer_2.clone(),
9555 [
9556 ExcerptRange {
9557 context: Point::new(0, 0)..Point::new(3, 0),
9558 primary: None,
9559 },
9560 ExcerptRange {
9561 context: Point::new(5, 0)..Point::new(7, 0),
9562 primary: None,
9563 },
9564 ExcerptRange {
9565 context: Point::new(9, 0)..Point::new(10, 4),
9566 primary: None,
9567 },
9568 ],
9569 cx,
9570 );
9571 multibuffer.push_excerpts(
9572 buffer_3.clone(),
9573 [
9574 ExcerptRange {
9575 context: Point::new(0, 0)..Point::new(3, 0),
9576 primary: None,
9577 },
9578 ExcerptRange {
9579 context: Point::new(5, 0)..Point::new(7, 0),
9580 primary: None,
9581 },
9582 ExcerptRange {
9583 context: Point::new(9, 0)..Point::new(10, 4),
9584 primary: None,
9585 },
9586 ],
9587 cx,
9588 );
9589 multibuffer
9590 });
9591
9592 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9593 editor.update(cx, |editor, cx| {
9594 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");
9595 editor.select_all(&SelectAll, cx);
9596 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
9597 });
9598 cx.executor().run_until_parked();
9599 // When all ranges are selected, all buffer hunks are reverted.
9600 editor.update(cx, |editor, cx| {
9601 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");
9602 });
9603 buffer_1.update(cx, |buffer, _| {
9604 assert_eq!(buffer.text(), sample_text_1);
9605 });
9606 buffer_2.update(cx, |buffer, _| {
9607 assert_eq!(buffer.text(), sample_text_2);
9608 });
9609 buffer_3.update(cx, |buffer, _| {
9610 assert_eq!(buffer.text(), sample_text_3);
9611 });
9612
9613 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
9614 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
9615 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
9616 editor.update(cx, |editor, cx| {
9617 editor.change_selections(None, cx, |s| {
9618 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
9619 });
9620 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
9621 });
9622 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
9623 // but not affect buffer_2 and its related excerpts.
9624 editor.update(cx, |editor, cx| {
9625 assert_eq!(
9626 editor.text(cx),
9627 "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"
9628 );
9629 });
9630 buffer_1.update(cx, |buffer, _| {
9631 assert_eq!(buffer.text(), sample_text_1);
9632 });
9633 buffer_2.update(cx, |buffer, _| {
9634 assert_eq!(
9635 buffer.text(),
9636 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
9637 );
9638 });
9639 buffer_3.update(cx, |buffer, _| {
9640 assert_eq!(
9641 buffer.text(),
9642 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
9643 );
9644 });
9645}
9646
9647#[gpui::test]
9648async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
9649 init_test(cx, |_| {});
9650
9651 let cols = 4;
9652 let rows = 10;
9653 let sample_text_1 = sample_text(rows, cols, 'a');
9654 assert_eq!(
9655 sample_text_1,
9656 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
9657 );
9658 let sample_text_2 = sample_text(rows, cols, 'l');
9659 assert_eq!(
9660 sample_text_2,
9661 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
9662 );
9663 let sample_text_3 = sample_text(rows, cols, 'v');
9664 assert_eq!(
9665 sample_text_3,
9666 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
9667 );
9668
9669 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
9670 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
9671 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
9672
9673 let multi_buffer = cx.new_model(|cx| {
9674 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
9675 multibuffer.push_excerpts(
9676 buffer_1.clone(),
9677 [
9678 ExcerptRange {
9679 context: Point::new(0, 0)..Point::new(3, 0),
9680 primary: None,
9681 },
9682 ExcerptRange {
9683 context: Point::new(5, 0)..Point::new(7, 0),
9684 primary: None,
9685 },
9686 ExcerptRange {
9687 context: Point::new(9, 0)..Point::new(10, 4),
9688 primary: None,
9689 },
9690 ],
9691 cx,
9692 );
9693 multibuffer.push_excerpts(
9694 buffer_2.clone(),
9695 [
9696 ExcerptRange {
9697 context: Point::new(0, 0)..Point::new(3, 0),
9698 primary: None,
9699 },
9700 ExcerptRange {
9701 context: Point::new(5, 0)..Point::new(7, 0),
9702 primary: None,
9703 },
9704 ExcerptRange {
9705 context: Point::new(9, 0)..Point::new(10, 4),
9706 primary: None,
9707 },
9708 ],
9709 cx,
9710 );
9711 multibuffer.push_excerpts(
9712 buffer_3.clone(),
9713 [
9714 ExcerptRange {
9715 context: Point::new(0, 0)..Point::new(3, 0),
9716 primary: None,
9717 },
9718 ExcerptRange {
9719 context: Point::new(5, 0)..Point::new(7, 0),
9720 primary: None,
9721 },
9722 ExcerptRange {
9723 context: Point::new(9, 0)..Point::new(10, 4),
9724 primary: None,
9725 },
9726 ],
9727 cx,
9728 );
9729 multibuffer
9730 });
9731
9732 let fs = FakeFs::new(cx.executor());
9733 fs.insert_tree(
9734 "/a",
9735 json!({
9736 "main.rs": sample_text_1,
9737 "other.rs": sample_text_2,
9738 "lib.rs": sample_text_3,
9739 }),
9740 )
9741 .await;
9742 let project = Project::test(fs, ["/a".as_ref()], cx).await;
9743 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9744 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9745 let multi_buffer_editor = cx.new_view(|cx| {
9746 Editor::new(
9747 EditorMode::Full,
9748 multi_buffer,
9749 Some(project.clone()),
9750 true,
9751 cx,
9752 )
9753 });
9754 let multibuffer_item_id = workspace
9755 .update(cx, |workspace, cx| {
9756 assert!(
9757 workspace.active_item(cx).is_none(),
9758 "active item should be None before the first item is added"
9759 );
9760 workspace.add_item_to_active_pane(Box::new(multi_buffer_editor.clone()), None, cx);
9761 let active_item = workspace
9762 .active_item(cx)
9763 .expect("should have an active item after adding the multi buffer");
9764 assert!(
9765 !active_item.is_singleton(cx),
9766 "A multi buffer was expected to active after adding"
9767 );
9768 active_item.item_id()
9769 })
9770 .unwrap();
9771 cx.executor().run_until_parked();
9772
9773 multi_buffer_editor.update(cx, |editor, cx| {
9774 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
9775 editor.open_excerpts(&OpenExcerpts, cx);
9776 });
9777 cx.executor().run_until_parked();
9778 let first_item_id = workspace
9779 .update(cx, |workspace, cx| {
9780 let active_item = workspace
9781 .active_item(cx)
9782 .expect("should have an active item after navigating into the 1st buffer");
9783 let first_item_id = active_item.item_id();
9784 assert_ne!(
9785 first_item_id, multibuffer_item_id,
9786 "Should navigate into the 1st buffer and activate it"
9787 );
9788 assert!(
9789 active_item.is_singleton(cx),
9790 "New active item should be a singleton buffer"
9791 );
9792 assert_eq!(
9793 active_item
9794 .act_as::<Editor>(cx)
9795 .expect("should have navigated into an editor for the 1st buffer")
9796 .read(cx)
9797 .text(cx),
9798 sample_text_1
9799 );
9800
9801 workspace
9802 .go_back(workspace.active_pane().downgrade(), cx)
9803 .detach_and_log_err(cx);
9804
9805 first_item_id
9806 })
9807 .unwrap();
9808 cx.executor().run_until_parked();
9809 workspace
9810 .update(cx, |workspace, cx| {
9811 let active_item = workspace
9812 .active_item(cx)
9813 .expect("should have an active item after navigating back");
9814 assert_eq!(
9815 active_item.item_id(),
9816 multibuffer_item_id,
9817 "Should navigate back to the multi buffer"
9818 );
9819 assert!(!active_item.is_singleton(cx));
9820 })
9821 .unwrap();
9822
9823 multi_buffer_editor.update(cx, |editor, cx| {
9824 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
9825 s.select_ranges(Some(39..40))
9826 });
9827 editor.open_excerpts(&OpenExcerpts, cx);
9828 });
9829 cx.executor().run_until_parked();
9830 let second_item_id = workspace
9831 .update(cx, |workspace, cx| {
9832 let active_item = workspace
9833 .active_item(cx)
9834 .expect("should have an active item after navigating into the 2nd buffer");
9835 let second_item_id = active_item.item_id();
9836 assert_ne!(
9837 second_item_id, multibuffer_item_id,
9838 "Should navigate away from the multibuffer"
9839 );
9840 assert_ne!(
9841 second_item_id, first_item_id,
9842 "Should navigate into the 2nd buffer and activate it"
9843 );
9844 assert!(
9845 active_item.is_singleton(cx),
9846 "New active item should be a singleton buffer"
9847 );
9848 assert_eq!(
9849 active_item
9850 .act_as::<Editor>(cx)
9851 .expect("should have navigated into an editor")
9852 .read(cx)
9853 .text(cx),
9854 sample_text_2
9855 );
9856
9857 workspace
9858 .go_back(workspace.active_pane().downgrade(), cx)
9859 .detach_and_log_err(cx);
9860
9861 second_item_id
9862 })
9863 .unwrap();
9864 cx.executor().run_until_parked();
9865 workspace
9866 .update(cx, |workspace, cx| {
9867 let active_item = workspace
9868 .active_item(cx)
9869 .expect("should have an active item after navigating back from the 2nd buffer");
9870 assert_eq!(
9871 active_item.item_id(),
9872 multibuffer_item_id,
9873 "Should navigate back from the 2nd buffer to the multi buffer"
9874 );
9875 assert!(!active_item.is_singleton(cx));
9876 })
9877 .unwrap();
9878
9879 multi_buffer_editor.update(cx, |editor, cx| {
9880 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
9881 s.select_ranges(Some(60..70))
9882 });
9883 editor.open_excerpts(&OpenExcerpts, cx);
9884 });
9885 cx.executor().run_until_parked();
9886 workspace
9887 .update(cx, |workspace, cx| {
9888 let active_item = workspace
9889 .active_item(cx)
9890 .expect("should have an active item after navigating into the 3rd buffer");
9891 let third_item_id = active_item.item_id();
9892 assert_ne!(
9893 third_item_id, multibuffer_item_id,
9894 "Should navigate into the 3rd buffer and activate it"
9895 );
9896 assert_ne!(third_item_id, first_item_id);
9897 assert_ne!(third_item_id, second_item_id);
9898 assert!(
9899 active_item.is_singleton(cx),
9900 "New active item should be a singleton buffer"
9901 );
9902 assert_eq!(
9903 active_item
9904 .act_as::<Editor>(cx)
9905 .expect("should have navigated into an editor")
9906 .read(cx)
9907 .text(cx),
9908 sample_text_3
9909 );
9910
9911 workspace
9912 .go_back(workspace.active_pane().downgrade(), cx)
9913 .detach_and_log_err(cx);
9914 })
9915 .unwrap();
9916 cx.executor().run_until_parked();
9917 workspace
9918 .update(cx, |workspace, cx| {
9919 let active_item = workspace
9920 .active_item(cx)
9921 .expect("should have an active item after navigating back from the 3rd buffer");
9922 assert_eq!(
9923 active_item.item_id(),
9924 multibuffer_item_id,
9925 "Should navigate back from the 3rd buffer to the multi buffer"
9926 );
9927 assert!(!active_item.is_singleton(cx));
9928 })
9929 .unwrap();
9930}
9931
9932#[gpui::test]
9933async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9934 init_test(cx, |_| {});
9935
9936 let mut cx = EditorTestContext::new(cx).await;
9937
9938 let diff_base = r#"
9939 use some::mod;
9940
9941 const A: u32 = 42;
9942
9943 fn main() {
9944 println!("hello");
9945
9946 println!("world");
9947 }
9948 "#
9949 .unindent();
9950
9951 cx.set_state(
9952 &r#"
9953 use some::modified;
9954
9955 ˇ
9956 fn main() {
9957 println!("hello there");
9958
9959 println!("around the");
9960 println!("world");
9961 }
9962 "#
9963 .unindent(),
9964 );
9965
9966 cx.set_diff_base(Some(&diff_base));
9967 executor.run_until_parked();
9968 let unexpanded_hunks = vec![
9969 (
9970 "use some::mod;\n".to_string(),
9971 DiffHunkStatus::Modified,
9972 DisplayRow(0)..DisplayRow(1),
9973 ),
9974 (
9975 "const A: u32 = 42;\n".to_string(),
9976 DiffHunkStatus::Removed,
9977 DisplayRow(2)..DisplayRow(2),
9978 ),
9979 (
9980 " println!(\"hello\");\n".to_string(),
9981 DiffHunkStatus::Modified,
9982 DisplayRow(4)..DisplayRow(5),
9983 ),
9984 (
9985 "".to_string(),
9986 DiffHunkStatus::Added,
9987 DisplayRow(6)..DisplayRow(7),
9988 ),
9989 ];
9990 cx.update_editor(|editor, cx| {
9991 let snapshot = editor.snapshot(cx);
9992 let all_hunks = editor_hunks(editor, &snapshot, cx);
9993 assert_eq!(all_hunks, unexpanded_hunks);
9994 });
9995
9996 cx.update_editor(|editor, cx| {
9997 for _ in 0..4 {
9998 editor.go_to_hunk(&GoToHunk, cx);
9999 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10000 }
10001 });
10002 executor.run_until_parked();
10003 cx.assert_editor_state(
10004 &r#"
10005 use some::modified;
10006
10007 ˇ
10008 fn main() {
10009 println!("hello there");
10010
10011 println!("around the");
10012 println!("world");
10013 }
10014 "#
10015 .unindent(),
10016 );
10017 cx.update_editor(|editor, cx| {
10018 let snapshot = editor.snapshot(cx);
10019 let all_hunks = editor_hunks(editor, &snapshot, cx);
10020 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10021 assert_eq!(
10022 expanded_hunks_background_highlights(editor, cx),
10023 vec![DisplayRow(1)..=DisplayRow(1), DisplayRow(7)..=DisplayRow(7), DisplayRow(9)..=DisplayRow(9)],
10024 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
10025 );
10026 assert_eq!(
10027 all_hunks,
10028 vec![
10029 ("use some::mod;\n".to_string(), DiffHunkStatus::Modified, DisplayRow(1)..DisplayRow(2)),
10030 ("const A: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(4)..DisplayRow(4)),
10031 (" println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(7)..DisplayRow(8)),
10032 ("".to_string(), DiffHunkStatus::Added, DisplayRow(9)..DisplayRow(10)),
10033 ],
10034 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
10035 (from modified and removed hunks)"
10036 );
10037 assert_eq!(
10038 all_hunks, all_expanded_hunks,
10039 "Editor hunks should not change and all be expanded"
10040 );
10041 });
10042
10043 cx.update_editor(|editor, cx| {
10044 editor.cancel(&Cancel, cx);
10045
10046 let snapshot = editor.snapshot(cx);
10047 let all_hunks = editor_hunks(editor, &snapshot, cx);
10048 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
10049 assert_eq!(
10050 expanded_hunks_background_highlights(editor, cx),
10051 Vec::new(),
10052 "After cancelling in editor, no git highlights should be left"
10053 );
10054 assert_eq!(
10055 all_expanded_hunks,
10056 Vec::new(),
10057 "After cancelling in editor, no hunks should be expanded"
10058 );
10059 assert_eq!(
10060 all_hunks, unexpanded_hunks,
10061 "After cancelling in editor, regular hunks' coordinates should get back to normal"
10062 );
10063 });
10064}
10065
10066#[gpui::test]
10067async fn test_toggled_diff_base_change(
10068 executor: BackgroundExecutor,
10069 cx: &mut gpui::TestAppContext,
10070) {
10071 init_test(cx, |_| {});
10072
10073 let mut cx = EditorTestContext::new(cx).await;
10074
10075 let diff_base = r#"
10076 use some::mod1;
10077 use some::mod2;
10078
10079 const A: u32 = 42;
10080 const B: u32 = 42;
10081 const C: u32 = 42;
10082
10083 fn main(ˇ) {
10084 println!("hello");
10085
10086 println!("world");
10087 }
10088 "#
10089 .unindent();
10090
10091 cx.set_state(
10092 &r#"
10093 use some::mod2;
10094
10095 const A: u32 = 42;
10096 const C: u32 = 42;
10097
10098 fn main(ˇ) {
10099 //println!("hello");
10100
10101 println!("world");
10102 //
10103 //
10104 }
10105 "#
10106 .unindent(),
10107 );
10108
10109 cx.set_diff_base(Some(&diff_base));
10110 executor.run_until_parked();
10111 cx.update_editor(|editor, cx| {
10112 let snapshot = editor.snapshot(cx);
10113 let all_hunks = editor_hunks(editor, &snapshot, cx);
10114 assert_eq!(
10115 all_hunks,
10116 vec![
10117 (
10118 "use some::mod1;\n".to_string(),
10119 DiffHunkStatus::Removed,
10120 DisplayRow(0)..DisplayRow(0)
10121 ),
10122 (
10123 "const B: u32 = 42;\n".to_string(),
10124 DiffHunkStatus::Removed,
10125 DisplayRow(3)..DisplayRow(3)
10126 ),
10127 (
10128 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
10129 DiffHunkStatus::Modified,
10130 DisplayRow(5)..DisplayRow(7)
10131 ),
10132 (
10133 "".to_string(),
10134 DiffHunkStatus::Added,
10135 DisplayRow(9)..DisplayRow(11)
10136 ),
10137 ]
10138 );
10139 });
10140
10141 cx.update_editor(|editor, cx| {
10142 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10143 });
10144 executor.run_until_parked();
10145 cx.assert_editor_state(
10146 &r#"
10147 use some::mod2;
10148
10149 const A: u32 = 42;
10150 const C: u32 = 42;
10151
10152 fn main(ˇ) {
10153 //println!("hello");
10154
10155 println!("world");
10156 //
10157 //
10158 }
10159 "#
10160 .unindent(),
10161 );
10162 cx.update_editor(|editor, cx| {
10163 let snapshot = editor.snapshot(cx);
10164 let all_hunks = editor_hunks(editor, &snapshot, cx);
10165 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10166 assert_eq!(
10167 expanded_hunks_background_highlights(editor, cx),
10168 vec![DisplayRow(9)..=DisplayRow(10), DisplayRow(13)..=DisplayRow(14)],
10169 "After expanding, all git additions should be highlighted for Modified (split into added and removed) and Added hunks"
10170 );
10171 assert_eq!(
10172 all_hunks,
10173 vec![
10174 ("use some::mod1;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(1)..DisplayRow(1)),
10175 ("const B: u32 = 42;\n".to_string(), DiffHunkStatus::Removed, DisplayRow(5)..DisplayRow(5)),
10176 ("fn main(ˇ) {\n println!(\"hello\");\n".to_string(), DiffHunkStatus::Modified, DisplayRow(9)..DisplayRow(11)),
10177 ("".to_string(), DiffHunkStatus::Added, DisplayRow(13)..DisplayRow(15)),
10178 ],
10179 "After expanding, all hunks' display rows should have shifted by the amount of deleted lines added \
10180 (from modified and removed hunks)"
10181 );
10182 assert_eq!(
10183 all_hunks, all_expanded_hunks,
10184 "Editor hunks should not change and all be expanded"
10185 );
10186 });
10187
10188 cx.set_diff_base(Some("new diff base!"));
10189 executor.run_until_parked();
10190
10191 cx.update_editor(|editor, cx| {
10192 let snapshot = editor.snapshot(cx);
10193 let all_hunks = editor_hunks(editor, &snapshot, cx);
10194 let all_expanded_hunks = expanded_hunks(editor, &snapshot, cx);
10195 assert_eq!(
10196 expanded_hunks_background_highlights(editor, cx),
10197 Vec::new(),
10198 "After diff base is changed, old git highlights should be removed"
10199 );
10200 assert_eq!(
10201 all_expanded_hunks,
10202 Vec::new(),
10203 "After diff base is changed, old git hunk expansions should be removed"
10204 );
10205 assert_eq!(
10206 all_hunks,
10207 vec![(
10208 "new diff base!".to_string(),
10209 DiffHunkStatus::Modified,
10210 DisplayRow(0)..snapshot.display_snapshot.max_point().row()
10211 )],
10212 "After diff base is changed, hunks should update"
10213 );
10214 });
10215}
10216
10217#[gpui::test]
10218async fn test_fold_unfold_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10219 init_test(cx, |_| {});
10220
10221 let mut cx = EditorTestContext::new(cx).await;
10222
10223 let diff_base = r#"
10224 use some::mod1;
10225 use some::mod2;
10226
10227 const A: u32 = 42;
10228 const B: u32 = 42;
10229 const C: u32 = 42;
10230
10231 fn main(ˇ) {
10232 println!("hello");
10233
10234 println!("world");
10235 }
10236
10237 fn another() {
10238 println!("another");
10239 }
10240
10241 fn another2() {
10242 println!("another2");
10243 }
10244 "#
10245 .unindent();
10246
10247 cx.set_state(
10248 &r#"
10249 «use some::mod2;
10250
10251 const A: u32 = 42;
10252 const C: u32 = 42;
10253
10254 fn main() {
10255 //println!("hello");
10256
10257 println!("world");
10258 //
10259 //ˇ»
10260 }
10261
10262 fn another() {
10263 println!("another");
10264 println!("another");
10265 }
10266
10267 println!("another2");
10268 }
10269 "#
10270 .unindent(),
10271 );
10272
10273 cx.set_diff_base(Some(&diff_base));
10274 executor.run_until_parked();
10275 cx.update_editor(|editor, cx| {
10276 let snapshot = editor.snapshot(cx);
10277 let all_hunks = editor_hunks(editor, &snapshot, cx);
10278 assert_eq!(
10279 all_hunks,
10280 vec![
10281 (
10282 "use some::mod1;\n".to_string(),
10283 DiffHunkStatus::Removed,
10284 DisplayRow(0)..DisplayRow(0)
10285 ),
10286 (
10287 "const B: u32 = 42;\n".to_string(),
10288 DiffHunkStatus::Removed,
10289 DisplayRow(3)..DisplayRow(3)
10290 ),
10291 (
10292 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
10293 DiffHunkStatus::Modified,
10294 DisplayRow(5)..DisplayRow(7)
10295 ),
10296 (
10297 "".to_string(),
10298 DiffHunkStatus::Added,
10299 DisplayRow(9)..DisplayRow(11)
10300 ),
10301 (
10302 "".to_string(),
10303 DiffHunkStatus::Added,
10304 DisplayRow(15)..DisplayRow(16)
10305 ),
10306 (
10307 "fn another2() {\n".to_string(),
10308 DiffHunkStatus::Removed,
10309 DisplayRow(18)..DisplayRow(18)
10310 ),
10311 ]
10312 );
10313 });
10314
10315 cx.update_editor(|editor, cx| {
10316 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10317 });
10318 executor.run_until_parked();
10319 cx.assert_editor_state(
10320 &r#"
10321 «use some::mod2;
10322
10323 const A: u32 = 42;
10324 const C: u32 = 42;
10325
10326 fn main() {
10327 //println!("hello");
10328
10329 println!("world");
10330 //
10331 //ˇ»
10332 }
10333
10334 fn another() {
10335 println!("another");
10336 println!("another");
10337 }
10338
10339 println!("another2");
10340 }
10341 "#
10342 .unindent(),
10343 );
10344 cx.update_editor(|editor, cx| {
10345 let snapshot = editor.snapshot(cx);
10346 let all_hunks = editor_hunks(editor, &snapshot, cx);
10347 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10348 assert_eq!(
10349 expanded_hunks_background_highlights(editor, cx),
10350 vec![
10351 DisplayRow(9)..=DisplayRow(10),
10352 DisplayRow(13)..=DisplayRow(14),
10353 DisplayRow(19)..=DisplayRow(19)
10354 ]
10355 );
10356 assert_eq!(
10357 all_hunks,
10358 vec![
10359 (
10360 "use some::mod1;\n".to_string(),
10361 DiffHunkStatus::Removed,
10362 DisplayRow(1)..DisplayRow(1)
10363 ),
10364 (
10365 "const B: u32 = 42;\n".to_string(),
10366 DiffHunkStatus::Removed,
10367 DisplayRow(5)..DisplayRow(5)
10368 ),
10369 (
10370 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
10371 DiffHunkStatus::Modified,
10372 DisplayRow(9)..DisplayRow(11)
10373 ),
10374 (
10375 "".to_string(),
10376 DiffHunkStatus::Added,
10377 DisplayRow(13)..DisplayRow(15)
10378 ),
10379 (
10380 "".to_string(),
10381 DiffHunkStatus::Added,
10382 DisplayRow(19)..DisplayRow(20)
10383 ),
10384 (
10385 "fn another2() {\n".to_string(),
10386 DiffHunkStatus::Removed,
10387 DisplayRow(23)..DisplayRow(23)
10388 ),
10389 ],
10390 );
10391 assert_eq!(all_hunks, all_expanded_hunks);
10392 });
10393
10394 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
10395 cx.executor().run_until_parked();
10396 cx.assert_editor_state(
10397 &r#"
10398 «use some::mod2;
10399
10400 const A: u32 = 42;
10401 const C: u32 = 42;
10402
10403 fn main() {
10404 //println!("hello");
10405
10406 println!("world");
10407 //
10408 //ˇ»
10409 }
10410
10411 fn another() {
10412 println!("another");
10413 println!("another");
10414 }
10415
10416 println!("another2");
10417 }
10418 "#
10419 .unindent(),
10420 );
10421 cx.update_editor(|editor, cx| {
10422 let snapshot = editor.snapshot(cx);
10423 let all_hunks = editor_hunks(editor, &snapshot, cx);
10424 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10425 assert_eq!(
10426 expanded_hunks_background_highlights(editor, cx),
10427 vec![DisplayRow(0)..=DisplayRow(0), DisplayRow(5)..=DisplayRow(5)],
10428 "Only one hunk is left not folded, its highlight should be visible"
10429 );
10430 assert_eq!(
10431 all_hunks,
10432 vec![
10433 (
10434 "use some::mod1;\n".to_string(),
10435 DiffHunkStatus::Removed,
10436 DisplayRow(0)..DisplayRow(0)
10437 ),
10438 (
10439 "const B: u32 = 42;\n".to_string(),
10440 DiffHunkStatus::Removed,
10441 DisplayRow(0)..DisplayRow(0)
10442 ),
10443 (
10444 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
10445 DiffHunkStatus::Modified,
10446 DisplayRow(0)..DisplayRow(0)
10447 ),
10448 (
10449 "".to_string(),
10450 DiffHunkStatus::Added,
10451 DisplayRow(0)..DisplayRow(1)
10452 ),
10453 (
10454 "".to_string(),
10455 DiffHunkStatus::Added,
10456 DisplayRow(5)..DisplayRow(6)
10457 ),
10458 (
10459 "fn another2() {\n".to_string(),
10460 DiffHunkStatus::Removed,
10461 DisplayRow(9)..DisplayRow(9)
10462 ),
10463 ],
10464 "Hunk list should still return shifted folded hunks"
10465 );
10466 assert_eq!(
10467 all_expanded_hunks,
10468 vec![
10469 (
10470 "".to_string(),
10471 DiffHunkStatus::Added,
10472 DisplayRow(5)..DisplayRow(6)
10473 ),
10474 (
10475 "fn another2() {\n".to_string(),
10476 DiffHunkStatus::Removed,
10477 DisplayRow(9)..DisplayRow(9)
10478 ),
10479 ],
10480 "Only non-folded hunks should be left expanded"
10481 );
10482 });
10483
10484 cx.update_editor(|editor, cx| {
10485 editor.select_all(&SelectAll, cx);
10486 editor.unfold_lines(&UnfoldLines, cx);
10487 });
10488 cx.executor().run_until_parked();
10489 cx.assert_editor_state(
10490 &r#"
10491 «use some::mod2;
10492
10493 const A: u32 = 42;
10494 const C: u32 = 42;
10495
10496 fn main() {
10497 //println!("hello");
10498
10499 println!("world");
10500 //
10501 //
10502 }
10503
10504 fn another() {
10505 println!("another");
10506 println!("another");
10507 }
10508
10509 println!("another2");
10510 }
10511 ˇ»"#
10512 .unindent(),
10513 );
10514 cx.update_editor(|editor, cx| {
10515 let snapshot = editor.snapshot(cx);
10516 let all_hunks = editor_hunks(editor, &snapshot, cx);
10517 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10518 assert_eq!(
10519 expanded_hunks_background_highlights(editor, cx),
10520 vec![
10521 DisplayRow(9)..=DisplayRow(10),
10522 DisplayRow(13)..=DisplayRow(14),
10523 DisplayRow(19)..=DisplayRow(19)
10524 ],
10525 "After unfolding, all hunk diffs should be visible again"
10526 );
10527 assert_eq!(
10528 all_hunks,
10529 vec![
10530 (
10531 "use some::mod1;\n".to_string(),
10532 DiffHunkStatus::Removed,
10533 DisplayRow(1)..DisplayRow(1)
10534 ),
10535 (
10536 "const B: u32 = 42;\n".to_string(),
10537 DiffHunkStatus::Removed,
10538 DisplayRow(5)..DisplayRow(5)
10539 ),
10540 (
10541 "fn main(ˇ) {\n println!(\"hello\");\n".to_string(),
10542 DiffHunkStatus::Modified,
10543 DisplayRow(9)..DisplayRow(11)
10544 ),
10545 (
10546 "".to_string(),
10547 DiffHunkStatus::Added,
10548 DisplayRow(13)..DisplayRow(15)
10549 ),
10550 (
10551 "".to_string(),
10552 DiffHunkStatus::Added,
10553 DisplayRow(19)..DisplayRow(20)
10554 ),
10555 (
10556 "fn another2() {\n".to_string(),
10557 DiffHunkStatus::Removed,
10558 DisplayRow(23)..DisplayRow(23)
10559 ),
10560 ],
10561 );
10562 assert_eq!(all_hunks, all_expanded_hunks);
10563 });
10564}
10565
10566#[gpui::test]
10567async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
10568 init_test(cx, |_| {});
10569
10570 let cols = 4;
10571 let rows = 10;
10572 let sample_text_1 = sample_text(rows, cols, 'a');
10573 assert_eq!(
10574 sample_text_1,
10575 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10576 );
10577 let modified_sample_text_1 = "aaaa\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
10578 let sample_text_2 = sample_text(rows, cols, 'l');
10579 assert_eq!(
10580 sample_text_2,
10581 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10582 );
10583 let modified_sample_text_2 = "llll\nmmmm\n1n1n1n1n1\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
10584 let sample_text_3 = sample_text(rows, cols, 'v');
10585 assert_eq!(
10586 sample_text_3,
10587 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10588 );
10589 let modified_sample_text_3 =
10590 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n@@@@\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
10591 let buffer_1 = cx.new_model(|cx| {
10592 let mut buffer = Buffer::local(modified_sample_text_1.to_string(), cx);
10593 buffer.set_diff_base(Some(sample_text_1.clone()), cx);
10594 buffer
10595 });
10596 let buffer_2 = cx.new_model(|cx| {
10597 let mut buffer = Buffer::local(modified_sample_text_2.to_string(), cx);
10598 buffer.set_diff_base(Some(sample_text_2.clone()), cx);
10599 buffer
10600 });
10601 let buffer_3 = cx.new_model(|cx| {
10602 let mut buffer = Buffer::local(modified_sample_text_3.to_string(), cx);
10603 buffer.set_diff_base(Some(sample_text_3.clone()), cx);
10604 buffer
10605 });
10606
10607 let multi_buffer = cx.new_model(|cx| {
10608 let mut multibuffer = MultiBuffer::new(0, ReadWrite);
10609 multibuffer.push_excerpts(
10610 buffer_1.clone(),
10611 [
10612 ExcerptRange {
10613 context: Point::new(0, 0)..Point::new(3, 0),
10614 primary: None,
10615 },
10616 ExcerptRange {
10617 context: Point::new(5, 0)..Point::new(7, 0),
10618 primary: None,
10619 },
10620 ExcerptRange {
10621 context: Point::new(9, 0)..Point::new(10, 4),
10622 primary: None,
10623 },
10624 ],
10625 cx,
10626 );
10627 multibuffer.push_excerpts(
10628 buffer_2.clone(),
10629 [
10630 ExcerptRange {
10631 context: Point::new(0, 0)..Point::new(3, 0),
10632 primary: None,
10633 },
10634 ExcerptRange {
10635 context: Point::new(5, 0)..Point::new(7, 0),
10636 primary: None,
10637 },
10638 ExcerptRange {
10639 context: Point::new(9, 0)..Point::new(10, 4),
10640 primary: None,
10641 },
10642 ],
10643 cx,
10644 );
10645 multibuffer.push_excerpts(
10646 buffer_3.clone(),
10647 [
10648 ExcerptRange {
10649 context: Point::new(0, 0)..Point::new(3, 0),
10650 primary: None,
10651 },
10652 ExcerptRange {
10653 context: Point::new(5, 0)..Point::new(7, 0),
10654 primary: None,
10655 },
10656 ExcerptRange {
10657 context: Point::new(9, 0)..Point::new(10, 4),
10658 primary: None,
10659 },
10660 ],
10661 cx,
10662 );
10663 multibuffer
10664 });
10665
10666 let fs = FakeFs::new(cx.executor());
10667 fs.insert_tree(
10668 "/a",
10669 json!({
10670 "main.rs": modified_sample_text_1,
10671 "other.rs": modified_sample_text_2,
10672 "lib.rs": modified_sample_text_3,
10673 }),
10674 )
10675 .await;
10676
10677 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10678 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10679 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10680 let multi_buffer_editor = cx.new_view(|cx| {
10681 Editor::new(
10682 EditorMode::Full,
10683 multi_buffer,
10684 Some(project.clone()),
10685 true,
10686 cx,
10687 )
10688 });
10689 cx.executor().run_until_parked();
10690
10691 let expected_all_hunks = vec![
10692 (
10693 "bbbb\n".to_string(),
10694 DiffHunkStatus::Removed,
10695 DisplayRow(4)..DisplayRow(4),
10696 ),
10697 (
10698 "nnnn\n".to_string(),
10699 DiffHunkStatus::Modified,
10700 DisplayRow(21)..DisplayRow(22),
10701 ),
10702 (
10703 "".to_string(),
10704 DiffHunkStatus::Added,
10705 DisplayRow(41)..DisplayRow(42),
10706 ),
10707 ];
10708 let expected_all_hunks_shifted = vec![
10709 (
10710 "bbbb\n".to_string(),
10711 DiffHunkStatus::Removed,
10712 DisplayRow(5)..DisplayRow(5),
10713 ),
10714 (
10715 "nnnn\n".to_string(),
10716 DiffHunkStatus::Modified,
10717 DisplayRow(23)..DisplayRow(24),
10718 ),
10719 (
10720 "".to_string(),
10721 DiffHunkStatus::Added,
10722 DisplayRow(43)..DisplayRow(44),
10723 ),
10724 ];
10725
10726 multi_buffer_editor.update(cx, |editor, cx| {
10727 let snapshot = editor.snapshot(cx);
10728 let all_hunks = editor_hunks(editor, &snapshot, cx);
10729 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10730 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
10731 assert_eq!(all_hunks, expected_all_hunks);
10732 assert_eq!(all_expanded_hunks, Vec::new());
10733 });
10734
10735 multi_buffer_editor.update(cx, |editor, cx| {
10736 editor.select_all(&SelectAll, cx);
10737 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10738 });
10739 cx.executor().run_until_parked();
10740 multi_buffer_editor.update(cx, |editor, cx| {
10741 let snapshot = editor.snapshot(cx);
10742 let all_hunks = editor_hunks(editor, &snapshot, cx);
10743 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10744 assert_eq!(
10745 expanded_hunks_background_highlights(editor, cx),
10746 vec![
10747 DisplayRow(23)..=DisplayRow(23),
10748 DisplayRow(43)..=DisplayRow(43)
10749 ],
10750 );
10751 assert_eq!(all_hunks, expected_all_hunks_shifted);
10752 assert_eq!(all_hunks, all_expanded_hunks);
10753 });
10754
10755 multi_buffer_editor.update(cx, |editor, cx| {
10756 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10757 });
10758 cx.executor().run_until_parked();
10759 multi_buffer_editor.update(cx, |editor, cx| {
10760 let snapshot = editor.snapshot(cx);
10761 let all_hunks = editor_hunks(editor, &snapshot, cx);
10762 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10763 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
10764 assert_eq!(all_hunks, expected_all_hunks);
10765 assert_eq!(all_expanded_hunks, Vec::new());
10766 });
10767
10768 multi_buffer_editor.update(cx, |editor, cx| {
10769 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10770 });
10771 cx.executor().run_until_parked();
10772 multi_buffer_editor.update(cx, |editor, cx| {
10773 let snapshot = editor.snapshot(cx);
10774 let all_hunks = editor_hunks(editor, &snapshot, cx);
10775 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10776 assert_eq!(
10777 expanded_hunks_background_highlights(editor, cx),
10778 vec![
10779 DisplayRow(23)..=DisplayRow(23),
10780 DisplayRow(43)..=DisplayRow(43)
10781 ],
10782 );
10783 assert_eq!(all_hunks, expected_all_hunks_shifted);
10784 assert_eq!(all_hunks, all_expanded_hunks);
10785 });
10786
10787 multi_buffer_editor.update(cx, |editor, cx| {
10788 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
10789 });
10790 cx.executor().run_until_parked();
10791 multi_buffer_editor.update(cx, |editor, cx| {
10792 let snapshot = editor.snapshot(cx);
10793 let all_hunks = editor_hunks(editor, &snapshot, cx);
10794 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10795 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
10796 assert_eq!(all_hunks, expected_all_hunks);
10797 assert_eq!(all_expanded_hunks, Vec::new());
10798 });
10799}
10800
10801#[gpui::test]
10802async fn test_edits_around_toggled_additions(
10803 executor: BackgroundExecutor,
10804 cx: &mut gpui::TestAppContext,
10805) {
10806 init_test(cx, |_| {});
10807
10808 let mut cx = EditorTestContext::new(cx).await;
10809
10810 let diff_base = r#"
10811 use some::mod1;
10812 use some::mod2;
10813
10814 const A: u32 = 42;
10815
10816 fn main() {
10817 println!("hello");
10818
10819 println!("world");
10820 }
10821 "#
10822 .unindent();
10823 executor.run_until_parked();
10824 cx.set_state(
10825 &r#"
10826 use some::mod1;
10827 use some::mod2;
10828
10829 const A: u32 = 42;
10830 const B: u32 = 42;
10831 const C: u32 = 42;
10832 ˇ
10833
10834 fn main() {
10835 println!("hello");
10836
10837 println!("world");
10838 }
10839 "#
10840 .unindent(),
10841 );
10842
10843 cx.set_diff_base(Some(&diff_base));
10844 executor.run_until_parked();
10845 cx.update_editor(|editor, cx| {
10846 let snapshot = editor.snapshot(cx);
10847 let all_hunks = editor_hunks(editor, &snapshot, cx);
10848 assert_eq!(
10849 all_hunks,
10850 vec![(
10851 "".to_string(),
10852 DiffHunkStatus::Added,
10853 DisplayRow(4)..DisplayRow(7)
10854 )]
10855 );
10856 });
10857 cx.update_editor(|editor, cx| {
10858 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
10859 });
10860 executor.run_until_parked();
10861 cx.assert_editor_state(
10862 &r#"
10863 use some::mod1;
10864 use some::mod2;
10865
10866 const A: u32 = 42;
10867 const B: u32 = 42;
10868 const C: u32 = 42;
10869 ˇ
10870
10871 fn main() {
10872 println!("hello");
10873
10874 println!("world");
10875 }
10876 "#
10877 .unindent(),
10878 );
10879 cx.update_editor(|editor, cx| {
10880 let snapshot = editor.snapshot(cx);
10881 let all_hunks = editor_hunks(editor, &snapshot, cx);
10882 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10883 assert_eq!(
10884 all_hunks,
10885 vec![(
10886 "".to_string(),
10887 DiffHunkStatus::Added,
10888 DisplayRow(4)..DisplayRow(7)
10889 )]
10890 );
10891 assert_eq!(
10892 expanded_hunks_background_highlights(editor, cx),
10893 vec![DisplayRow(4)..=DisplayRow(6)]
10894 );
10895 assert_eq!(all_hunks, all_expanded_hunks);
10896 });
10897
10898 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
10899 executor.run_until_parked();
10900 cx.assert_editor_state(
10901 &r#"
10902 use some::mod1;
10903 use some::mod2;
10904
10905 const A: u32 = 42;
10906 const B: u32 = 42;
10907 const C: u32 = 42;
10908 const D: u32 = 42;
10909 ˇ
10910
10911 fn main() {
10912 println!("hello");
10913
10914 println!("world");
10915 }
10916 "#
10917 .unindent(),
10918 );
10919 cx.update_editor(|editor, cx| {
10920 let snapshot = editor.snapshot(cx);
10921 let all_hunks = editor_hunks(editor, &snapshot, cx);
10922 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10923 assert_eq!(
10924 all_hunks,
10925 vec![(
10926 "".to_string(),
10927 DiffHunkStatus::Added,
10928 DisplayRow(4)..DisplayRow(8)
10929 )]
10930 );
10931 assert_eq!(
10932 expanded_hunks_background_highlights(editor, cx),
10933 vec![DisplayRow(4)..=DisplayRow(6)],
10934 "Edited hunk should have one more line added"
10935 );
10936 assert_eq!(
10937 all_hunks, all_expanded_hunks,
10938 "Expanded hunk should also grow with the addition"
10939 );
10940 });
10941
10942 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
10943 executor.run_until_parked();
10944 cx.assert_editor_state(
10945 &r#"
10946 use some::mod1;
10947 use some::mod2;
10948
10949 const A: u32 = 42;
10950 const B: u32 = 42;
10951 const C: u32 = 42;
10952 const D: u32 = 42;
10953 const E: u32 = 42;
10954 ˇ
10955
10956 fn main() {
10957 println!("hello");
10958
10959 println!("world");
10960 }
10961 "#
10962 .unindent(),
10963 );
10964 cx.update_editor(|editor, cx| {
10965 let snapshot = editor.snapshot(cx);
10966 let all_hunks = editor_hunks(editor, &snapshot, cx);
10967 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
10968 assert_eq!(
10969 all_hunks,
10970 vec![(
10971 "".to_string(),
10972 DiffHunkStatus::Added,
10973 DisplayRow(4)..DisplayRow(9)
10974 )]
10975 );
10976 assert_eq!(
10977 expanded_hunks_background_highlights(editor, cx),
10978 vec![DisplayRow(4)..=DisplayRow(6)],
10979 "Edited hunk should have one more line added"
10980 );
10981 assert_eq!(all_hunks, all_expanded_hunks);
10982 });
10983
10984 cx.update_editor(|editor, cx| {
10985 editor.move_up(&MoveUp, cx);
10986 editor.delete_line(&DeleteLine, cx);
10987 });
10988 executor.run_until_parked();
10989 cx.assert_editor_state(
10990 &r#"
10991 use some::mod1;
10992 use some::mod2;
10993
10994 const A: u32 = 42;
10995 const B: u32 = 42;
10996 const C: u32 = 42;
10997 const D: u32 = 42;
10998 ˇ
10999
11000 fn main() {
11001 println!("hello");
11002
11003 println!("world");
11004 }
11005 "#
11006 .unindent(),
11007 );
11008 cx.update_editor(|editor, cx| {
11009 let snapshot = editor.snapshot(cx);
11010 let all_hunks = editor_hunks(editor, &snapshot, cx);
11011 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11012 assert_eq!(
11013 all_hunks,
11014 vec![(
11015 "".to_string(),
11016 DiffHunkStatus::Added,
11017 DisplayRow(4)..DisplayRow(8)
11018 )]
11019 );
11020 assert_eq!(
11021 expanded_hunks_background_highlights(editor, cx),
11022 vec![DisplayRow(4)..=DisplayRow(6)],
11023 "Deleting a line should shrint the hunk"
11024 );
11025 assert_eq!(
11026 all_hunks, all_expanded_hunks,
11027 "Expanded hunk should also shrink with the addition"
11028 );
11029 });
11030
11031 cx.update_editor(|editor, cx| {
11032 editor.move_up(&MoveUp, cx);
11033 editor.delete_line(&DeleteLine, cx);
11034 editor.move_up(&MoveUp, cx);
11035 editor.delete_line(&DeleteLine, cx);
11036 editor.move_up(&MoveUp, cx);
11037 editor.delete_line(&DeleteLine, cx);
11038 });
11039 executor.run_until_parked();
11040 cx.assert_editor_state(
11041 &r#"
11042 use some::mod1;
11043 use some::mod2;
11044
11045 const A: u32 = 42;
11046 ˇ
11047
11048 fn main() {
11049 println!("hello");
11050
11051 println!("world");
11052 }
11053 "#
11054 .unindent(),
11055 );
11056 cx.update_editor(|editor, cx| {
11057 let snapshot = editor.snapshot(cx);
11058 let all_hunks = editor_hunks(editor, &snapshot, cx);
11059 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11060 assert_eq!(
11061 all_hunks,
11062 vec![(
11063 "".to_string(),
11064 DiffHunkStatus::Added,
11065 DisplayRow(5)..DisplayRow(6)
11066 )]
11067 );
11068 assert_eq!(
11069 expanded_hunks_background_highlights(editor, cx),
11070 vec![DisplayRow(5)..=DisplayRow(5)]
11071 );
11072 assert_eq!(all_hunks, all_expanded_hunks);
11073 });
11074
11075 cx.update_editor(|editor, cx| {
11076 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
11077 editor.delete_line(&DeleteLine, cx);
11078 });
11079 executor.run_until_parked();
11080 cx.assert_editor_state(
11081 &r#"
11082 ˇ
11083
11084 fn main() {
11085 println!("hello");
11086
11087 println!("world");
11088 }
11089 "#
11090 .unindent(),
11091 );
11092 cx.update_editor(|editor, cx| {
11093 let snapshot = editor.snapshot(cx);
11094 let all_hunks = editor_hunks(editor, &snapshot, cx);
11095 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11096 assert_eq!(
11097 all_hunks,
11098 vec![
11099 (
11100 "use some::mod1;\nuse some::mod2;\n".to_string(),
11101 DiffHunkStatus::Removed,
11102 DisplayRow(0)..DisplayRow(0)
11103 ),
11104 (
11105 "const A: u32 = 42;\n".to_string(),
11106 DiffHunkStatus::Removed,
11107 DisplayRow(2)..DisplayRow(2)
11108 )
11109 ]
11110 );
11111 assert_eq!(
11112 expanded_hunks_background_highlights(editor, cx),
11113 Vec::new(),
11114 "Should close all stale expanded addition hunks"
11115 );
11116 assert_eq!(
11117 all_expanded_hunks,
11118 vec![(
11119 "const A: u32 = 42;\n".to_string(),
11120 DiffHunkStatus::Removed,
11121 DisplayRow(2)..DisplayRow(2)
11122 )],
11123 "Should open hunks that were adjacent to the stale addition one"
11124 );
11125 });
11126}
11127
11128#[gpui::test]
11129async fn test_edits_around_toggled_deletions(
11130 executor: BackgroundExecutor,
11131 cx: &mut gpui::TestAppContext,
11132) {
11133 init_test(cx, |_| {});
11134
11135 let mut cx = EditorTestContext::new(cx).await;
11136
11137 let diff_base = r#"
11138 use some::mod1;
11139 use some::mod2;
11140
11141 const A: u32 = 42;
11142 const B: u32 = 42;
11143 const C: u32 = 42;
11144
11145
11146 fn main() {
11147 println!("hello");
11148
11149 println!("world");
11150 }
11151 "#
11152 .unindent();
11153 executor.run_until_parked();
11154 cx.set_state(
11155 &r#"
11156 use some::mod1;
11157 use some::mod2;
11158
11159 ˇconst B: u32 = 42;
11160 const C: u32 = 42;
11161
11162
11163 fn main() {
11164 println!("hello");
11165
11166 println!("world");
11167 }
11168 "#
11169 .unindent(),
11170 );
11171
11172 cx.set_diff_base(Some(&diff_base));
11173 executor.run_until_parked();
11174 cx.update_editor(|editor, cx| {
11175 let snapshot = editor.snapshot(cx);
11176 let all_hunks = editor_hunks(editor, &snapshot, cx);
11177 assert_eq!(
11178 all_hunks,
11179 vec![(
11180 "const A: u32 = 42;\n".to_string(),
11181 DiffHunkStatus::Removed,
11182 DisplayRow(3)..DisplayRow(3)
11183 )]
11184 );
11185 });
11186 cx.update_editor(|editor, cx| {
11187 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11188 });
11189 executor.run_until_parked();
11190 cx.assert_editor_state(
11191 &r#"
11192 use some::mod1;
11193 use some::mod2;
11194
11195 ˇconst B: u32 = 42;
11196 const C: u32 = 42;
11197
11198
11199 fn main() {
11200 println!("hello");
11201
11202 println!("world");
11203 }
11204 "#
11205 .unindent(),
11206 );
11207 cx.update_editor(|editor, cx| {
11208 let snapshot = editor.snapshot(cx);
11209 let all_hunks = editor_hunks(editor, &snapshot, cx);
11210 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11211 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11212 assert_eq!(
11213 all_hunks,
11214 vec![(
11215 "const A: u32 = 42;\n".to_string(),
11216 DiffHunkStatus::Removed,
11217 DisplayRow(4)..DisplayRow(4)
11218 )]
11219 );
11220 assert_eq!(all_hunks, all_expanded_hunks);
11221 });
11222
11223 cx.update_editor(|editor, cx| {
11224 editor.delete_line(&DeleteLine, cx);
11225 });
11226 executor.run_until_parked();
11227 cx.assert_editor_state(
11228 &r#"
11229 use some::mod1;
11230 use some::mod2;
11231
11232 ˇconst C: u32 = 42;
11233
11234
11235 fn main() {
11236 println!("hello");
11237
11238 println!("world");
11239 }
11240 "#
11241 .unindent(),
11242 );
11243 cx.update_editor(|editor, cx| {
11244 let snapshot = editor.snapshot(cx);
11245 let all_hunks = editor_hunks(editor, &snapshot, cx);
11246 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11247 assert_eq!(
11248 expanded_hunks_background_highlights(editor, cx),
11249 Vec::new(),
11250 "Deleted hunks do not highlight current editor's background"
11251 );
11252 assert_eq!(
11253 all_hunks,
11254 vec![(
11255 "const A: u32 = 42;\nconst B: u32 = 42;\n".to_string(),
11256 DiffHunkStatus::Removed,
11257 DisplayRow(5)..DisplayRow(5)
11258 )]
11259 );
11260 assert_eq!(all_hunks, all_expanded_hunks);
11261 });
11262
11263 cx.update_editor(|editor, cx| {
11264 editor.delete_line(&DeleteLine, cx);
11265 });
11266 executor.run_until_parked();
11267 cx.assert_editor_state(
11268 &r#"
11269 use some::mod1;
11270 use some::mod2;
11271
11272 ˇ
11273
11274 fn main() {
11275 println!("hello");
11276
11277 println!("world");
11278 }
11279 "#
11280 .unindent(),
11281 );
11282 cx.update_editor(|editor, cx| {
11283 let snapshot = editor.snapshot(cx);
11284 let all_hunks = editor_hunks(editor, &snapshot, cx);
11285 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11286 assert_eq!(expanded_hunks_background_highlights(editor, cx), Vec::new());
11287 assert_eq!(
11288 all_hunks,
11289 vec![(
11290 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
11291 DiffHunkStatus::Removed,
11292 DisplayRow(6)..DisplayRow(6)
11293 )]
11294 );
11295 assert_eq!(all_hunks, all_expanded_hunks);
11296 });
11297
11298 cx.update_editor(|editor, cx| {
11299 editor.handle_input("replacement", cx);
11300 });
11301 executor.run_until_parked();
11302 cx.assert_editor_state(
11303 &r#"
11304 use some::mod1;
11305 use some::mod2;
11306
11307 replacementˇ
11308
11309 fn main() {
11310 println!("hello");
11311
11312 println!("world");
11313 }
11314 "#
11315 .unindent(),
11316 );
11317 cx.update_editor(|editor, cx| {
11318 let snapshot = editor.snapshot(cx);
11319 let all_hunks = editor_hunks(editor, &snapshot, cx);
11320 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11321 assert_eq!(
11322 all_hunks,
11323 vec![(
11324 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n\n".to_string(),
11325 DiffHunkStatus::Modified,
11326 DisplayRow(7)..DisplayRow(8)
11327 )]
11328 );
11329 assert_eq!(
11330 expanded_hunks_background_highlights(editor, cx),
11331 vec![DisplayRow(7)..=DisplayRow(7)],
11332 "Modified expanded hunks should display additions and highlight their background"
11333 );
11334 assert_eq!(all_hunks, all_expanded_hunks);
11335 });
11336}
11337
11338#[gpui::test]
11339async fn test_edits_around_toggled_modifications(
11340 executor: BackgroundExecutor,
11341 cx: &mut gpui::TestAppContext,
11342) {
11343 init_test(cx, |_| {});
11344
11345 let mut cx = EditorTestContext::new(cx).await;
11346
11347 let diff_base = r#"
11348 use some::mod1;
11349 use some::mod2;
11350
11351 const A: u32 = 42;
11352 const B: u32 = 42;
11353 const C: u32 = 42;
11354 const D: u32 = 42;
11355
11356
11357 fn main() {
11358 println!("hello");
11359
11360 println!("world");
11361 }"#
11362 .unindent();
11363 executor.run_until_parked();
11364 cx.set_state(
11365 &r#"
11366 use some::mod1;
11367 use some::mod2;
11368
11369 const A: u32 = 42;
11370 const B: u32 = 42;
11371 const C: u32 = 43ˇ
11372 const D: u32 = 42;
11373
11374
11375 fn main() {
11376 println!("hello");
11377
11378 println!("world");
11379 }"#
11380 .unindent(),
11381 );
11382
11383 cx.set_diff_base(Some(&diff_base));
11384 executor.run_until_parked();
11385 cx.update_editor(|editor, cx| {
11386 let snapshot = editor.snapshot(cx);
11387 let all_hunks = editor_hunks(editor, &snapshot, cx);
11388 assert_eq!(
11389 all_hunks,
11390 vec![(
11391 "const C: u32 = 42;\n".to_string(),
11392 DiffHunkStatus::Modified,
11393 DisplayRow(5)..DisplayRow(6)
11394 )]
11395 );
11396 });
11397 cx.update_editor(|editor, cx| {
11398 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11399 });
11400 executor.run_until_parked();
11401 cx.assert_editor_state(
11402 &r#"
11403 use some::mod1;
11404 use some::mod2;
11405
11406 const A: u32 = 42;
11407 const B: u32 = 42;
11408 const C: u32 = 43ˇ
11409 const D: u32 = 42;
11410
11411
11412 fn main() {
11413 println!("hello");
11414
11415 println!("world");
11416 }"#
11417 .unindent(),
11418 );
11419 cx.update_editor(|editor, cx| {
11420 let snapshot = editor.snapshot(cx);
11421 let all_hunks = editor_hunks(editor, &snapshot, cx);
11422 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11423 assert_eq!(
11424 expanded_hunks_background_highlights(editor, cx),
11425 vec![DisplayRow(6)..=DisplayRow(6)],
11426 );
11427 assert_eq!(
11428 all_hunks,
11429 vec![(
11430 "const C: u32 = 42;\n".to_string(),
11431 DiffHunkStatus::Modified,
11432 DisplayRow(6)..DisplayRow(7)
11433 )]
11434 );
11435 assert_eq!(all_hunks, all_expanded_hunks);
11436 });
11437
11438 cx.update_editor(|editor, cx| {
11439 editor.handle_input("\nnew_line\n", cx);
11440 });
11441 executor.run_until_parked();
11442 cx.assert_editor_state(
11443 &r#"
11444 use some::mod1;
11445 use some::mod2;
11446
11447 const A: u32 = 42;
11448 const B: u32 = 42;
11449 const C: u32 = 43
11450 new_line
11451 ˇ
11452 const D: u32 = 42;
11453
11454
11455 fn main() {
11456 println!("hello");
11457
11458 println!("world");
11459 }"#
11460 .unindent(),
11461 );
11462 cx.update_editor(|editor, cx| {
11463 let snapshot = editor.snapshot(cx);
11464 let all_hunks = editor_hunks(editor, &snapshot, cx);
11465 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11466 assert_eq!(
11467 expanded_hunks_background_highlights(editor, cx),
11468 vec![DisplayRow(6)..=DisplayRow(6)],
11469 "Modified hunk should grow highlighted lines on more text additions"
11470 );
11471 assert_eq!(
11472 all_hunks,
11473 vec![(
11474 "const C: u32 = 42;\n".to_string(),
11475 DiffHunkStatus::Modified,
11476 DisplayRow(6)..DisplayRow(9)
11477 )]
11478 );
11479 assert_eq!(all_hunks, all_expanded_hunks);
11480 });
11481
11482 cx.update_editor(|editor, cx| {
11483 editor.move_up(&MoveUp, cx);
11484 editor.move_up(&MoveUp, cx);
11485 editor.move_up(&MoveUp, cx);
11486 editor.delete_line(&DeleteLine, cx);
11487 });
11488 executor.run_until_parked();
11489 cx.assert_editor_state(
11490 &r#"
11491 use some::mod1;
11492 use some::mod2;
11493
11494 const A: u32 = 42;
11495 ˇconst C: u32 = 43
11496 new_line
11497
11498 const D: u32 = 42;
11499
11500
11501 fn main() {
11502 println!("hello");
11503
11504 println!("world");
11505 }"#
11506 .unindent(),
11507 );
11508 cx.update_editor(|editor, cx| {
11509 let snapshot = editor.snapshot(cx);
11510 let all_hunks = editor_hunks(editor, &snapshot, cx);
11511 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11512 assert_eq!(
11513 expanded_hunks_background_highlights(editor, cx),
11514 vec![DisplayRow(6)..=DisplayRow(8)],
11515 );
11516 assert_eq!(
11517 all_hunks,
11518 vec![(
11519 "const B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
11520 DiffHunkStatus::Modified,
11521 DisplayRow(6)..DisplayRow(9)
11522 )],
11523 "Modified hunk should grow deleted lines on text deletions above"
11524 );
11525 assert_eq!(all_hunks, all_expanded_hunks);
11526 });
11527
11528 cx.update_editor(|editor, cx| {
11529 editor.move_up(&MoveUp, cx);
11530 editor.handle_input("v", cx);
11531 });
11532 executor.run_until_parked();
11533 cx.assert_editor_state(
11534 &r#"
11535 use some::mod1;
11536 use some::mod2;
11537
11538 vˇconst A: u32 = 42;
11539 const C: u32 = 43
11540 new_line
11541
11542 const D: u32 = 42;
11543
11544
11545 fn main() {
11546 println!("hello");
11547
11548 println!("world");
11549 }"#
11550 .unindent(),
11551 );
11552 cx.update_editor(|editor, cx| {
11553 let snapshot = editor.snapshot(cx);
11554 let all_hunks = editor_hunks(editor, &snapshot, cx);
11555 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11556 assert_eq!(
11557 expanded_hunks_background_highlights(editor, cx),
11558 vec![DisplayRow(6)..=DisplayRow(9)],
11559 "Modified hunk should grow deleted lines on text modifications above"
11560 );
11561 assert_eq!(
11562 all_hunks,
11563 vec![(
11564 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
11565 DiffHunkStatus::Modified,
11566 DisplayRow(6)..DisplayRow(10)
11567 )]
11568 );
11569 assert_eq!(all_hunks, all_expanded_hunks);
11570 });
11571
11572 cx.update_editor(|editor, cx| {
11573 editor.move_down(&MoveDown, cx);
11574 editor.move_down(&MoveDown, cx);
11575 editor.delete_line(&DeleteLine, cx)
11576 });
11577 executor.run_until_parked();
11578 cx.assert_editor_state(
11579 &r#"
11580 use some::mod1;
11581 use some::mod2;
11582
11583 vconst A: u32 = 42;
11584 const C: u32 = 43
11585 ˇ
11586 const D: u32 = 42;
11587
11588
11589 fn main() {
11590 println!("hello");
11591
11592 println!("world");
11593 }"#
11594 .unindent(),
11595 );
11596 cx.update_editor(|editor, cx| {
11597 let snapshot = editor.snapshot(cx);
11598 let all_hunks = editor_hunks(editor, &snapshot, cx);
11599 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11600 assert_eq!(
11601 expanded_hunks_background_highlights(editor, cx),
11602 vec![DisplayRow(6)..=DisplayRow(8)],
11603 "Modified hunk should grow shrink lines on modification lines removal"
11604 );
11605 assert_eq!(
11606 all_hunks,
11607 vec![(
11608 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\n".to_string(),
11609 DiffHunkStatus::Modified,
11610 DisplayRow(6)..DisplayRow(9)
11611 )]
11612 );
11613 assert_eq!(all_hunks, all_expanded_hunks);
11614 });
11615
11616 cx.update_editor(|editor, cx| {
11617 editor.move_up(&MoveUp, cx);
11618 editor.move_up(&MoveUp, cx);
11619 editor.select_down_by_lines(&SelectDownByLines { lines: 4 }, cx);
11620 editor.delete_line(&DeleteLine, cx)
11621 });
11622 executor.run_until_parked();
11623 cx.assert_editor_state(
11624 &r#"
11625 use some::mod1;
11626 use some::mod2;
11627
11628 ˇ
11629
11630 fn main() {
11631 println!("hello");
11632
11633 println!("world");
11634 }"#
11635 .unindent(),
11636 );
11637 cx.update_editor(|editor, cx| {
11638 let snapshot = editor.snapshot(cx);
11639 let all_hunks = editor_hunks(editor, &snapshot, cx);
11640 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11641 assert_eq!(
11642 expanded_hunks_background_highlights(editor, cx),
11643 Vec::new(),
11644 "Modified hunk should turn into a removed one on all modified lines removal"
11645 );
11646 assert_eq!(
11647 all_hunks,
11648 vec![(
11649 "const A: u32 = 42;\nconst B: u32 = 42;\nconst C: u32 = 42;\nconst D: u32 = 42;\n"
11650 .to_string(),
11651 DiffHunkStatus::Removed,
11652 DisplayRow(7)..DisplayRow(7)
11653 )]
11654 );
11655 assert_eq!(all_hunks, all_expanded_hunks);
11656 });
11657}
11658
11659#[gpui::test]
11660async fn test_multiple_expanded_hunks_merge(
11661 executor: BackgroundExecutor,
11662 cx: &mut gpui::TestAppContext,
11663) {
11664 init_test(cx, |_| {});
11665
11666 let mut cx = EditorTestContext::new(cx).await;
11667
11668 let diff_base = r#"
11669 use some::mod1;
11670 use some::mod2;
11671
11672 const A: u32 = 42;
11673 const B: u32 = 42;
11674 const C: u32 = 42;
11675 const D: u32 = 42;
11676
11677
11678 fn main() {
11679 println!("hello");
11680
11681 println!("world");
11682 }"#
11683 .unindent();
11684 executor.run_until_parked();
11685 cx.set_state(
11686 &r#"
11687 use some::mod1;
11688 use some::mod2;
11689
11690 const A: u32 = 42;
11691 const B: u32 = 42;
11692 const C: u32 = 43ˇ
11693 const D: u32 = 42;
11694
11695
11696 fn main() {
11697 println!("hello");
11698
11699 println!("world");
11700 }"#
11701 .unindent(),
11702 );
11703
11704 cx.set_diff_base(Some(&diff_base));
11705 executor.run_until_parked();
11706 cx.update_editor(|editor, cx| {
11707 let snapshot = editor.snapshot(cx);
11708 let all_hunks = editor_hunks(editor, &snapshot, cx);
11709 assert_eq!(
11710 all_hunks,
11711 vec![(
11712 "const C: u32 = 42;\n".to_string(),
11713 DiffHunkStatus::Modified,
11714 DisplayRow(5)..DisplayRow(6)
11715 )]
11716 );
11717 });
11718 cx.update_editor(|editor, cx| {
11719 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11720 });
11721 executor.run_until_parked();
11722 cx.assert_editor_state(
11723 &r#"
11724 use some::mod1;
11725 use some::mod2;
11726
11727 const A: u32 = 42;
11728 const B: u32 = 42;
11729 const C: u32 = 43ˇ
11730 const D: u32 = 42;
11731
11732
11733 fn main() {
11734 println!("hello");
11735
11736 println!("world");
11737 }"#
11738 .unindent(),
11739 );
11740 cx.update_editor(|editor, cx| {
11741 let snapshot = editor.snapshot(cx);
11742 let all_hunks = editor_hunks(editor, &snapshot, cx);
11743 let all_expanded_hunks = expanded_hunks(&editor, &snapshot, cx);
11744 assert_eq!(
11745 expanded_hunks_background_highlights(editor, cx),
11746 vec![DisplayRow(6)..=DisplayRow(6)],
11747 );
11748 assert_eq!(
11749 all_hunks,
11750 vec![(
11751 "const C: u32 = 42;\n".to_string(),
11752 DiffHunkStatus::Modified,
11753 DisplayRow(6)..DisplayRow(7)
11754 )]
11755 );
11756 assert_eq!(all_hunks, all_expanded_hunks);
11757 });
11758
11759 cx.update_editor(|editor, cx| {
11760 editor.handle_input("\nnew_line\n", cx);
11761 });
11762 executor.run_until_parked();
11763 cx.assert_editor_state(
11764 &r#"
11765 use some::mod1;
11766 use some::mod2;
11767
11768 const A: u32 = 42;
11769 const B: u32 = 42;
11770 const C: u32 = 43
11771 new_line
11772 ˇ
11773 const D: u32 = 42;
11774
11775
11776 fn main() {
11777 println!("hello");
11778
11779 println!("world");
11780 }"#
11781 .unindent(),
11782 );
11783}
11784
11785async fn setup_indent_guides_editor(
11786 text: &str,
11787 cx: &mut gpui::TestAppContext,
11788) -> (BufferId, EditorTestContext) {
11789 init_test(cx, |_| {});
11790
11791 let mut cx = EditorTestContext::new(cx).await;
11792
11793 let buffer_id = cx.update_editor(|editor, cx| {
11794 editor.set_text(text, cx);
11795 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
11796 let buffer_id = buffer_ids[0];
11797 buffer_id
11798 });
11799
11800 (buffer_id, cx)
11801}
11802
11803fn assert_indent_guides(
11804 range: Range<u32>,
11805 expected: Vec<IndentGuide>,
11806 active_indices: Option<Vec<usize>>,
11807 cx: &mut EditorTestContext,
11808) {
11809 let indent_guides = cx.update_editor(|editor, cx| {
11810 let snapshot = editor.snapshot(cx).display_snapshot;
11811 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
11812 MultiBufferRow(range.start)..MultiBufferRow(range.end),
11813 true,
11814 &snapshot,
11815 cx,
11816 );
11817
11818 indent_guides.sort_by(|a, b| {
11819 a.depth.cmp(&b.depth).then(
11820 a.start_row
11821 .cmp(&b.start_row)
11822 .then(a.end_row.cmp(&b.end_row)),
11823 )
11824 });
11825 indent_guides
11826 });
11827
11828 if let Some(expected) = active_indices {
11829 let active_indices = cx.update_editor(|editor, cx| {
11830 let snapshot = editor.snapshot(cx).display_snapshot;
11831 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
11832 });
11833
11834 assert_eq!(
11835 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
11836 expected,
11837 "Active indent guide indices do not match"
11838 );
11839 }
11840
11841 let expected: Vec<_> = expected
11842 .into_iter()
11843 .map(|guide| MultiBufferIndentGuide {
11844 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
11845 buffer: guide,
11846 })
11847 .collect();
11848
11849 assert_eq!(indent_guides, expected, "Indent guides do not match");
11850}
11851
11852fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
11853 IndentGuide {
11854 buffer_id,
11855 start_row,
11856 end_row,
11857 depth,
11858 tab_size: 4,
11859 settings: IndentGuideSettings {
11860 enabled: true,
11861 line_width: 1,
11862 active_line_width: 1,
11863 ..Default::default()
11864 },
11865 }
11866}
11867
11868#[gpui::test]
11869async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
11870 let (buffer_id, mut cx) = setup_indent_guides_editor(
11871 &"
11872 fn main() {
11873 let a = 1;
11874 }"
11875 .unindent(),
11876 cx,
11877 )
11878 .await;
11879
11880 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
11881}
11882
11883#[gpui::test]
11884async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
11885 let (buffer_id, mut cx) = setup_indent_guides_editor(
11886 &"
11887 fn main() {
11888 let a = 1;
11889 let b = 2;
11890 }"
11891 .unindent(),
11892 cx,
11893 )
11894 .await;
11895
11896 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
11897}
11898
11899#[gpui::test]
11900async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
11901 let (buffer_id, mut cx) = setup_indent_guides_editor(
11902 &"
11903 fn main() {
11904 let a = 1;
11905 if a == 3 {
11906 let b = 2;
11907 } else {
11908 let c = 3;
11909 }
11910 }"
11911 .unindent(),
11912 cx,
11913 )
11914 .await;
11915
11916 assert_indent_guides(
11917 0..8,
11918 vec![
11919 indent_guide(buffer_id, 1, 6, 0),
11920 indent_guide(buffer_id, 3, 3, 1),
11921 indent_guide(buffer_id, 5, 5, 1),
11922 ],
11923 None,
11924 &mut cx,
11925 );
11926}
11927
11928#[gpui::test]
11929async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
11930 let (buffer_id, mut cx) = setup_indent_guides_editor(
11931 &"
11932 fn main() {
11933 let a = 1;
11934 let b = 2;
11935 let c = 3;
11936 }"
11937 .unindent(),
11938 cx,
11939 )
11940 .await;
11941
11942 assert_indent_guides(
11943 0..5,
11944 vec![
11945 indent_guide(buffer_id, 1, 3, 0),
11946 indent_guide(buffer_id, 2, 2, 1),
11947 ],
11948 None,
11949 &mut cx,
11950 );
11951}
11952
11953#[gpui::test]
11954async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
11955 let (buffer_id, mut cx) = setup_indent_guides_editor(
11956 &"
11957 fn main() {
11958 let a = 1;
11959
11960 let c = 3;
11961 }"
11962 .unindent(),
11963 cx,
11964 )
11965 .await;
11966
11967 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
11968}
11969
11970#[gpui::test]
11971async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
11972 let (buffer_id, mut cx) = setup_indent_guides_editor(
11973 &"
11974 fn main() {
11975 let a = 1;
11976
11977 let c = 3;
11978
11979 if a == 3 {
11980 let b = 2;
11981 } else {
11982 let c = 3;
11983 }
11984 }"
11985 .unindent(),
11986 cx,
11987 )
11988 .await;
11989
11990 assert_indent_guides(
11991 0..11,
11992 vec![
11993 indent_guide(buffer_id, 1, 9, 0),
11994 indent_guide(buffer_id, 6, 6, 1),
11995 indent_guide(buffer_id, 8, 8, 1),
11996 ],
11997 None,
11998 &mut cx,
11999 );
12000}
12001
12002#[gpui::test]
12003async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
12004 let (buffer_id, mut cx) = setup_indent_guides_editor(
12005 &"
12006 fn main() {
12007 let a = 1;
12008
12009 let c = 3;
12010
12011 if a == 3 {
12012 let b = 2;
12013 } else {
12014 let c = 3;
12015 }
12016 }"
12017 .unindent(),
12018 cx,
12019 )
12020 .await;
12021
12022 assert_indent_guides(
12023 1..11,
12024 vec![
12025 indent_guide(buffer_id, 1, 9, 0),
12026 indent_guide(buffer_id, 6, 6, 1),
12027 indent_guide(buffer_id, 8, 8, 1),
12028 ],
12029 None,
12030 &mut cx,
12031 );
12032}
12033
12034#[gpui::test]
12035async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
12036 let (buffer_id, mut cx) = setup_indent_guides_editor(
12037 &"
12038 fn main() {
12039 let a = 1;
12040
12041 let c = 3;
12042
12043 if a == 3 {
12044 let b = 2;
12045 } else {
12046 let c = 3;
12047 }
12048 }"
12049 .unindent(),
12050 cx,
12051 )
12052 .await;
12053
12054 assert_indent_guides(
12055 1..10,
12056 vec![
12057 indent_guide(buffer_id, 1, 9, 0),
12058 indent_guide(buffer_id, 6, 6, 1),
12059 indent_guide(buffer_id, 8, 8, 1),
12060 ],
12061 None,
12062 &mut cx,
12063 );
12064}
12065
12066#[gpui::test]
12067async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
12068 let (buffer_id, mut cx) = setup_indent_guides_editor(
12069 &"
12070 block1
12071 block2
12072 block3
12073 block4
12074 block2
12075 block1
12076 block1"
12077 .unindent(),
12078 cx,
12079 )
12080 .await;
12081
12082 assert_indent_guides(
12083 1..10,
12084 vec![
12085 indent_guide(buffer_id, 1, 4, 0),
12086 indent_guide(buffer_id, 2, 3, 1),
12087 indent_guide(buffer_id, 3, 3, 2),
12088 ],
12089 None,
12090 &mut cx,
12091 );
12092}
12093
12094#[gpui::test]
12095async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
12096 let (buffer_id, mut cx) = setup_indent_guides_editor(
12097 &"
12098 block1
12099 block2
12100 block3
12101
12102 block1
12103 block1"
12104 .unindent(),
12105 cx,
12106 )
12107 .await;
12108
12109 assert_indent_guides(
12110 0..6,
12111 vec![
12112 indent_guide(buffer_id, 1, 2, 0),
12113 indent_guide(buffer_id, 2, 2, 1),
12114 ],
12115 None,
12116 &mut cx,
12117 );
12118}
12119
12120#[gpui::test]
12121async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
12122 let (buffer_id, mut cx) = setup_indent_guides_editor(
12123 &"
12124 block1
12125
12126
12127
12128 block2
12129 "
12130 .unindent(),
12131 cx,
12132 )
12133 .await;
12134
12135 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12136}
12137
12138#[gpui::test]
12139async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
12140 let (buffer_id, mut cx) = setup_indent_guides_editor(
12141 &"
12142 def a:
12143 \tb = 3
12144 \tif True:
12145 \t\tc = 4
12146 \t\td = 5
12147 \tprint(b)
12148 "
12149 .unindent(),
12150 cx,
12151 )
12152 .await;
12153
12154 assert_indent_guides(
12155 0..6,
12156 vec![
12157 indent_guide(buffer_id, 1, 6, 0),
12158 indent_guide(buffer_id, 3, 4, 1),
12159 ],
12160 None,
12161 &mut cx,
12162 );
12163}
12164
12165#[gpui::test]
12166async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12167 let (buffer_id, mut cx) = setup_indent_guides_editor(
12168 &"
12169 fn main() {
12170 let a = 1;
12171 }"
12172 .unindent(),
12173 cx,
12174 )
12175 .await;
12176
12177 cx.update_editor(|editor, cx| {
12178 editor.change_selections(None, cx, |s| {
12179 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12180 });
12181 });
12182
12183 assert_indent_guides(
12184 0..3,
12185 vec![indent_guide(buffer_id, 1, 1, 0)],
12186 Some(vec![0]),
12187 &mut cx,
12188 );
12189}
12190
12191#[gpui::test]
12192async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
12193 let (buffer_id, mut cx) = setup_indent_guides_editor(
12194 &"
12195 fn main() {
12196 if 1 == 2 {
12197 let a = 1;
12198 }
12199 }"
12200 .unindent(),
12201 cx,
12202 )
12203 .await;
12204
12205 cx.update_editor(|editor, cx| {
12206 editor.change_selections(None, cx, |s| {
12207 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12208 });
12209 });
12210
12211 assert_indent_guides(
12212 0..4,
12213 vec![
12214 indent_guide(buffer_id, 1, 3, 0),
12215 indent_guide(buffer_id, 2, 2, 1),
12216 ],
12217 Some(vec![1]),
12218 &mut cx,
12219 );
12220
12221 cx.update_editor(|editor, cx| {
12222 editor.change_selections(None, cx, |s| {
12223 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12224 });
12225 });
12226
12227 assert_indent_guides(
12228 0..4,
12229 vec![
12230 indent_guide(buffer_id, 1, 3, 0),
12231 indent_guide(buffer_id, 2, 2, 1),
12232 ],
12233 Some(vec![1]),
12234 &mut cx,
12235 );
12236
12237 cx.update_editor(|editor, cx| {
12238 editor.change_selections(None, cx, |s| {
12239 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
12240 });
12241 });
12242
12243 assert_indent_guides(
12244 0..4,
12245 vec![
12246 indent_guide(buffer_id, 1, 3, 0),
12247 indent_guide(buffer_id, 2, 2, 1),
12248 ],
12249 Some(vec![0]),
12250 &mut cx,
12251 );
12252}
12253
12254#[gpui::test]
12255async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
12256 let (buffer_id, mut cx) = setup_indent_guides_editor(
12257 &"
12258 fn main() {
12259 let a = 1;
12260
12261 let b = 2;
12262 }"
12263 .unindent(),
12264 cx,
12265 )
12266 .await;
12267
12268 cx.update_editor(|editor, cx| {
12269 editor.change_selections(None, cx, |s| {
12270 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
12271 });
12272 });
12273
12274 assert_indent_guides(
12275 0..5,
12276 vec![indent_guide(buffer_id, 1, 3, 0)],
12277 Some(vec![0]),
12278 &mut cx,
12279 );
12280}
12281
12282#[gpui::test]
12283async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
12284 let (buffer_id, mut cx) = setup_indent_guides_editor(
12285 &"
12286 def m:
12287 a = 1
12288 pass"
12289 .unindent(),
12290 cx,
12291 )
12292 .await;
12293
12294 cx.update_editor(|editor, cx| {
12295 editor.change_selections(None, cx, |s| {
12296 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
12297 });
12298 });
12299
12300 assert_indent_guides(
12301 0..3,
12302 vec![indent_guide(buffer_id, 1, 2, 0)],
12303 Some(vec![0]),
12304 &mut cx,
12305 );
12306}
12307
12308#[gpui::test]
12309fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
12310 init_test(cx, |_| {});
12311
12312 let editor = cx.add_window(|cx| {
12313 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
12314 build_editor(buffer, cx)
12315 });
12316
12317 let render_args = Arc::new(Mutex::new(None));
12318 let snapshot = editor
12319 .update(cx, |editor, cx| {
12320 let snapshot = editor.buffer().read(cx).snapshot(cx);
12321 let range =
12322 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
12323
12324 struct RenderArgs {
12325 row: MultiBufferRow,
12326 folded: bool,
12327 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
12328 }
12329
12330 let crease = Crease::new(
12331 range,
12332 FoldPlaceholder::test(),
12333 {
12334 let toggle_callback = render_args.clone();
12335 move |row, folded, callback, _cx| {
12336 *toggle_callback.lock() = Some(RenderArgs {
12337 row,
12338 folded,
12339 callback,
12340 });
12341 div()
12342 }
12343 },
12344 |_row, _folded, _cx| div(),
12345 );
12346
12347 editor.insert_creases(Some(crease), cx);
12348 let snapshot = editor.snapshot(cx);
12349 let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
12350 snapshot
12351 })
12352 .unwrap();
12353
12354 let render_args = render_args.lock().take().unwrap();
12355 assert_eq!(render_args.row, MultiBufferRow(1));
12356 assert_eq!(render_args.folded, false);
12357 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
12358
12359 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
12360 .unwrap();
12361 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
12362 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
12363
12364 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
12365 .unwrap();
12366 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
12367 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
12368}
12369
12370fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
12371 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
12372 point..point
12373}
12374
12375fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
12376 let (text, ranges) = marked_text_ranges(marked_text, true);
12377 assert_eq!(view.text(cx), text);
12378 assert_eq!(
12379 view.selections.ranges(cx),
12380 ranges,
12381 "Assert selections are {}",
12382 marked_text
12383 );
12384}
12385
12386/// Handle completion request passing a marked string specifying where the completion
12387/// should be triggered from using '|' character, what range should be replaced, and what completions
12388/// should be returned using '<' and '>' to delimit the range
12389pub fn handle_completion_request(
12390 cx: &mut EditorLspTestContext,
12391 marked_string: &str,
12392 completions: Vec<&'static str>,
12393 counter: Arc<AtomicUsize>,
12394) -> impl Future<Output = ()> {
12395 let complete_from_marker: TextRangeMarker = '|'.into();
12396 let replace_range_marker: TextRangeMarker = ('<', '>').into();
12397 let (_, mut marked_ranges) = marked_text_ranges_by(
12398 marked_string,
12399 vec![complete_from_marker.clone(), replace_range_marker.clone()],
12400 );
12401
12402 let complete_from_position =
12403 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
12404 let replace_range =
12405 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
12406
12407 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
12408 let completions = completions.clone();
12409 counter.fetch_add(1, atomic::Ordering::Release);
12410 async move {
12411 assert_eq!(params.text_document_position.text_document.uri, url.clone());
12412 assert_eq!(
12413 params.text_document_position.position,
12414 complete_from_position
12415 );
12416 Ok(Some(lsp::CompletionResponse::Array(
12417 completions
12418 .iter()
12419 .map(|completion_text| lsp::CompletionItem {
12420 label: completion_text.to_string(),
12421 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12422 range: replace_range,
12423 new_text: completion_text.to_string(),
12424 })),
12425 ..Default::default()
12426 })
12427 .collect(),
12428 )))
12429 }
12430 });
12431
12432 async move {
12433 request.next().await;
12434 }
12435}
12436
12437fn handle_resolve_completion_request(
12438 cx: &mut EditorLspTestContext,
12439 edits: Option<Vec<(&'static str, &'static str)>>,
12440) -> impl Future<Output = ()> {
12441 let edits = edits.map(|edits| {
12442 edits
12443 .iter()
12444 .map(|(marked_string, new_text)| {
12445 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
12446 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
12447 lsp::TextEdit::new(replace_range, new_text.to_string())
12448 })
12449 .collect::<Vec<_>>()
12450 });
12451
12452 let mut request =
12453 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
12454 let edits = edits.clone();
12455 async move {
12456 Ok(lsp::CompletionItem {
12457 additional_text_edits: edits,
12458 ..Default::default()
12459 })
12460 }
12461 });
12462
12463 async move {
12464 request.next().await;
12465 }
12466}
12467
12468pub(crate) fn update_test_language_settings(
12469 cx: &mut TestAppContext,
12470 f: impl Fn(&mut AllLanguageSettingsContent),
12471) {
12472 _ = cx.update(|cx| {
12473 SettingsStore::update_global(cx, |store, cx| {
12474 store.update_user_settings::<AllLanguageSettings>(cx, f);
12475 });
12476 });
12477}
12478
12479pub(crate) fn update_test_project_settings(
12480 cx: &mut TestAppContext,
12481 f: impl Fn(&mut ProjectSettings),
12482) {
12483 _ = cx.update(|cx| {
12484 SettingsStore::update_global(cx, |store, cx| {
12485 store.update_user_settings::<ProjectSettings>(cx, f);
12486 });
12487 });
12488}
12489
12490pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
12491 _ = cx.update(|cx| {
12492 cx.text_system()
12493 .add_fonts(vec![assets::Assets
12494 .load("fonts/zed-mono/zed-mono-extended.ttf")
12495 .unwrap()
12496 .unwrap()])
12497 .unwrap();
12498 let store = SettingsStore::test(cx);
12499 cx.set_global(store);
12500 theme::init(theme::LoadThemes::JustBase, cx);
12501 release_channel::init(SemanticVersion::default(), cx);
12502 client::init_settings(cx);
12503 language::init(cx);
12504 Project::init_settings(cx);
12505 workspace::init_settings(cx);
12506 crate::init(cx);
12507 });
12508
12509 update_test_language_settings(cx, f);
12510}
12511
12512pub(crate) fn rust_lang() -> Arc<Language> {
12513 Arc::new(Language::new(
12514 LanguageConfig {
12515 name: "Rust".into(),
12516 matcher: LanguageMatcher {
12517 path_suffixes: vec!["rs".to_string()],
12518 ..Default::default()
12519 },
12520 ..Default::default()
12521 },
12522 Some(tree_sitter_rust::language()),
12523 ))
12524}
12525
12526#[track_caller]
12527fn assert_hunk_revert(
12528 not_reverted_text_with_selections: &str,
12529 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
12530 expected_reverted_text_with_selections: &str,
12531 base_text: &str,
12532 cx: &mut EditorLspTestContext,
12533) {
12534 cx.set_state(not_reverted_text_with_selections);
12535 cx.update_editor(|editor, cx| {
12536 editor
12537 .buffer()
12538 .read(cx)
12539 .as_singleton()
12540 .unwrap()
12541 .update(cx, |buffer, cx| {
12542 buffer.set_diff_base(Some(base_text.into()), cx);
12543 });
12544 });
12545 cx.executor().run_until_parked();
12546
12547 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
12548 let snapshot = editor.buffer().read(cx).snapshot(cx);
12549 let reverted_hunk_statuses = snapshot
12550 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
12551 .map(|hunk| hunk_status(&hunk))
12552 .collect::<Vec<_>>();
12553
12554 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
12555 reverted_hunk_statuses
12556 });
12557 cx.executor().run_until_parked();
12558 cx.assert_editor_state(expected_reverted_text_with_selections);
12559 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
12560}