1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
6 editor_test_context::EditorTestContext, select_ranges,
7 },
8 JoinLines,
9};
10use futures::StreamExt;
11use gpui::{
12 div, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds,
13 WindowOptions,
14};
15use indoc::indoc;
16use language::{
17 language_settings::{
18 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
19 },
20 BracketPairConfig,
21 Capability::ReadWrite,
22 FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
23 LanguageName, Override, ParsedMarkdown, Point,
24};
25use language_settings::{Formatter, FormatterList, IndentGuideSettings};
26use multi_buffer::MultiBufferIndentGuide;
27use parking_lot::Mutex;
28use project::FakeFs;
29use project::{
30 lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
31 project_settings::{LspSettings, ProjectSettings},
32};
33use serde_json::{self, json};
34use std::sync::atomic;
35use std::sync::atomic::AtomicUsize;
36use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
37use unindent::Unindent;
38use util::{
39 assert_set_eq,
40 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
41};
42use workspace::{
43 item::{FollowEvent, FollowableItem, Item, ItemHandle},
44 NavigationEntry, ViewId,
45};
46
47#[gpui::test]
48fn test_edit_events(cx: &mut TestAppContext) {
49 init_test(cx, |_| {});
50
51 let buffer = cx.new_model(|cx| {
52 let mut buffer = language::Buffer::local("123456", cx);
53 buffer.set_group_interval(Duration::from_secs(1));
54 buffer
55 });
56
57 let events = Rc::new(RefCell::new(Vec::new()));
58 let editor1 = cx.add_window({
59 let events = events.clone();
60 |cx| {
61 let view = cx.view().clone();
62 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
63 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
64 EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
65 _ => {}
66 })
67 .detach();
68 Editor::for_buffer(buffer.clone(), None, cx)
69 }
70 });
71
72 let editor2 = cx.add_window({
73 let events = events.clone();
74 |cx| {
75 cx.subscribe(
76 &cx.view().clone(),
77 move |_, _, event: &EditorEvent, _| match event {
78 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
79 EditorEvent::BufferEdited => {
80 events.borrow_mut().push(("editor2", "buffer edited"))
81 }
82 _ => {}
83 },
84 )
85 .detach();
86 Editor::for_buffer(buffer.clone(), None, cx)
87 }
88 });
89
90 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
91
92 // Mutating editor 1 will emit an `Edited` event only for that editor.
93 _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
94 assert_eq!(
95 mem::take(&mut *events.borrow_mut()),
96 [
97 ("editor1", "edited"),
98 ("editor1", "buffer edited"),
99 ("editor2", "buffer edited"),
100 ]
101 );
102
103 // Mutating editor 2 will emit an `Edited` event only for that editor.
104 _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
105 assert_eq!(
106 mem::take(&mut *events.borrow_mut()),
107 [
108 ("editor2", "edited"),
109 ("editor1", "buffer edited"),
110 ("editor2", "buffer edited"),
111 ]
112 );
113
114 // Undoing on editor 1 will emit an `Edited` event only for that editor.
115 _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
116 assert_eq!(
117 mem::take(&mut *events.borrow_mut()),
118 [
119 ("editor1", "edited"),
120 ("editor1", "buffer edited"),
121 ("editor2", "buffer edited"),
122 ]
123 );
124
125 // Redoing on editor 1 will emit an `Edited` event only for that editor.
126 _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
127 assert_eq!(
128 mem::take(&mut *events.borrow_mut()),
129 [
130 ("editor1", "edited"),
131 ("editor1", "buffer edited"),
132 ("editor2", "buffer edited"),
133 ]
134 );
135
136 // Undoing on editor 2 will emit an `Edited` event only for that editor.
137 _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
138 assert_eq!(
139 mem::take(&mut *events.borrow_mut()),
140 [
141 ("editor2", "edited"),
142 ("editor1", "buffer edited"),
143 ("editor2", "buffer edited"),
144 ]
145 );
146
147 // Redoing on editor 2 will emit an `Edited` event only for that editor.
148 _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
149 assert_eq!(
150 mem::take(&mut *events.borrow_mut()),
151 [
152 ("editor2", "edited"),
153 ("editor1", "buffer edited"),
154 ("editor2", "buffer edited"),
155 ]
156 );
157
158 // No event is emitted when the mutation is a no-op.
159 _ = editor2.update(cx, |editor, cx| {
160 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
161
162 editor.backspace(&Backspace, cx);
163 });
164 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
165}
166
167#[gpui::test]
168fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
169 init_test(cx, |_| {});
170
171 let mut now = Instant::now();
172 let group_interval = Duration::from_millis(1);
173 let buffer = cx.new_model(|cx| {
174 let mut buf = language::Buffer::local("123456", cx);
175 buf.set_group_interval(group_interval);
176 buf
177 });
178 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
179 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
180
181 _ = editor.update(cx, |editor, cx| {
182 editor.start_transaction_at(now, cx);
183 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
184
185 editor.insert("cd", cx);
186 editor.end_transaction_at(now, cx);
187 assert_eq!(editor.text(cx), "12cd56");
188 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
189
190 editor.start_transaction_at(now, cx);
191 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
192 editor.insert("e", cx);
193 editor.end_transaction_at(now, cx);
194 assert_eq!(editor.text(cx), "12cde6");
195 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
196
197 now += group_interval + Duration::from_millis(1);
198 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
199
200 // Simulate an edit in another editor
201 buffer.update(cx, |buffer, cx| {
202 buffer.start_transaction_at(now, cx);
203 buffer.edit([(0..1, "a")], None, cx);
204 buffer.edit([(1..1, "b")], None, cx);
205 buffer.end_transaction_at(now, cx);
206 });
207
208 assert_eq!(editor.text(cx), "ab2cde6");
209 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
210
211 // Last transaction happened past the group interval in a different editor.
212 // Undo it individually and don't restore selections.
213 editor.undo(&Undo, cx);
214 assert_eq!(editor.text(cx), "12cde6");
215 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
216
217 // First two transactions happened within the group interval in this editor.
218 // Undo them together and restore selections.
219 editor.undo(&Undo, cx);
220 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
221 assert_eq!(editor.text(cx), "123456");
222 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
223
224 // Redo the first two transactions together.
225 editor.redo(&Redo, cx);
226 assert_eq!(editor.text(cx), "12cde6");
227 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
228
229 // Redo the last transaction on its own.
230 editor.redo(&Redo, cx);
231 assert_eq!(editor.text(cx), "ab2cde6");
232 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
233
234 // Test empty transactions.
235 editor.start_transaction_at(now, cx);
236 editor.end_transaction_at(now, cx);
237 editor.undo(&Undo, cx);
238 assert_eq!(editor.text(cx), "12cde6");
239 });
240}
241
242#[gpui::test]
243fn test_ime_composition(cx: &mut TestAppContext) {
244 init_test(cx, |_| {});
245
246 let buffer = cx.new_model(|cx| {
247 let mut buffer = language::Buffer::local("abcde", cx);
248 // Ensure automatic grouping doesn't occur.
249 buffer.set_group_interval(Duration::ZERO);
250 buffer
251 });
252
253 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
254 cx.add_window(|cx| {
255 let mut editor = build_editor(buffer.clone(), cx);
256
257 // Start a new IME composition.
258 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
259 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
260 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
261 assert_eq!(editor.text(cx), "äbcde");
262 assert_eq!(
263 editor.marked_text_ranges(cx),
264 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
265 );
266
267 // Finalize IME composition.
268 editor.replace_text_in_range(None, "ā", cx);
269 assert_eq!(editor.text(cx), "ābcde");
270 assert_eq!(editor.marked_text_ranges(cx), None);
271
272 // IME composition edits are grouped and are undone/redone at once.
273 editor.undo(&Default::default(), cx);
274 assert_eq!(editor.text(cx), "abcde");
275 assert_eq!(editor.marked_text_ranges(cx), None);
276 editor.redo(&Default::default(), cx);
277 assert_eq!(editor.text(cx), "ābcde");
278 assert_eq!(editor.marked_text_ranges(cx), None);
279
280 // Start a new IME composition.
281 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
282 assert_eq!(
283 editor.marked_text_ranges(cx),
284 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
285 );
286
287 // Undoing during an IME composition cancels it.
288 editor.undo(&Default::default(), cx);
289 assert_eq!(editor.text(cx), "ābcde");
290 assert_eq!(editor.marked_text_ranges(cx), None);
291
292 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
293 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
294 assert_eq!(editor.text(cx), "ābcdè");
295 assert_eq!(
296 editor.marked_text_ranges(cx),
297 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
298 );
299
300 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
301 editor.replace_text_in_range(Some(4..999), "ę", cx);
302 assert_eq!(editor.text(cx), "ābcdę");
303 assert_eq!(editor.marked_text_ranges(cx), None);
304
305 // Start a new IME composition with multiple cursors.
306 editor.change_selections(None, cx, |s| {
307 s.select_ranges([
308 OffsetUtf16(1)..OffsetUtf16(1),
309 OffsetUtf16(3)..OffsetUtf16(3),
310 OffsetUtf16(5)..OffsetUtf16(5),
311 ])
312 });
313 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
314 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
315 assert_eq!(
316 editor.marked_text_ranges(cx),
317 Some(vec![
318 OffsetUtf16(0)..OffsetUtf16(3),
319 OffsetUtf16(4)..OffsetUtf16(7),
320 OffsetUtf16(8)..OffsetUtf16(11)
321 ])
322 );
323
324 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
325 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
326 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
327 assert_eq!(
328 editor.marked_text_ranges(cx),
329 Some(vec![
330 OffsetUtf16(1)..OffsetUtf16(2),
331 OffsetUtf16(5)..OffsetUtf16(6),
332 OffsetUtf16(9)..OffsetUtf16(10)
333 ])
334 );
335
336 // Finalize IME composition with multiple cursors.
337 editor.replace_text_in_range(Some(9..10), "2", cx);
338 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
339 assert_eq!(editor.marked_text_ranges(cx), None);
340
341 editor
342 });
343}
344
345#[gpui::test]
346fn test_selection_with_mouse(cx: &mut TestAppContext) {
347 init_test(cx, |_| {});
348
349 let editor = cx.add_window(|cx| {
350 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
351 build_editor(buffer, cx)
352 });
353
354 _ = editor.update(cx, |view, cx| {
355 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
356 });
357 assert_eq!(
358 editor
359 .update(cx, |view, cx| view.selections.display_ranges(cx))
360 .unwrap(),
361 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
362 );
363
364 _ = editor.update(cx, |view, cx| {
365 view.update_selection(
366 DisplayPoint::new(DisplayRow(3), 3),
367 0,
368 gpui::Point::<f32>::default(),
369 cx,
370 );
371 });
372
373 assert_eq!(
374 editor
375 .update(cx, |view, cx| view.selections.display_ranges(cx))
376 .unwrap(),
377 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
378 );
379
380 _ = editor.update(cx, |view, cx| {
381 view.update_selection(
382 DisplayPoint::new(DisplayRow(1), 1),
383 0,
384 gpui::Point::<f32>::default(),
385 cx,
386 );
387 });
388
389 assert_eq!(
390 editor
391 .update(cx, |view, cx| view.selections.display_ranges(cx))
392 .unwrap(),
393 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
394 );
395
396 _ = editor.update(cx, |view, cx| {
397 view.end_selection(cx);
398 view.update_selection(
399 DisplayPoint::new(DisplayRow(3), 3),
400 0,
401 gpui::Point::<f32>::default(),
402 cx,
403 );
404 });
405
406 assert_eq!(
407 editor
408 .update(cx, |view, cx| view.selections.display_ranges(cx))
409 .unwrap(),
410 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
411 );
412
413 _ = editor.update(cx, |view, cx| {
414 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
415 view.update_selection(
416 DisplayPoint::new(DisplayRow(0), 0),
417 0,
418 gpui::Point::<f32>::default(),
419 cx,
420 );
421 });
422
423 assert_eq!(
424 editor
425 .update(cx, |view, cx| view.selections.display_ranges(cx))
426 .unwrap(),
427 [
428 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
429 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
430 ]
431 );
432
433 _ = editor.update(cx, |view, cx| {
434 view.end_selection(cx);
435 });
436
437 assert_eq!(
438 editor
439 .update(cx, |view, cx| view.selections.display_ranges(cx))
440 .unwrap(),
441 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
442 );
443}
444
445#[gpui::test]
446fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
447 init_test(cx, |_| {});
448
449 let editor = cx.add_window(|cx| {
450 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
451 build_editor(buffer, cx)
452 });
453
454 _ = editor.update(cx, |view, cx| {
455 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
456 });
457
458 _ = editor.update(cx, |view, cx| {
459 view.end_selection(cx);
460 });
461
462 _ = editor.update(cx, |view, cx| {
463 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
464 });
465
466 _ = editor.update(cx, |view, cx| {
467 view.end_selection(cx);
468 });
469
470 assert_eq!(
471 editor
472 .update(cx, |view, cx| view.selections.display_ranges(cx))
473 .unwrap(),
474 [
475 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
476 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
477 ]
478 );
479
480 _ = editor.update(cx, |view, cx| {
481 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
482 });
483
484 _ = editor.update(cx, |view, cx| {
485 view.end_selection(cx);
486 });
487
488 assert_eq!(
489 editor
490 .update(cx, |view, cx| view.selections.display_ranges(cx))
491 .unwrap(),
492 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
493 );
494}
495
496#[gpui::test]
497fn test_canceling_pending_selection(cx: &mut TestAppContext) {
498 init_test(cx, |_| {});
499
500 let view = cx.add_window(|cx| {
501 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
502 build_editor(buffer, cx)
503 });
504
505 _ = view.update(cx, |view, cx| {
506 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
507 assert_eq!(
508 view.selections.display_ranges(cx),
509 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
510 );
511 });
512
513 _ = view.update(cx, |view, cx| {
514 view.update_selection(
515 DisplayPoint::new(DisplayRow(3), 3),
516 0,
517 gpui::Point::<f32>::default(),
518 cx,
519 );
520 assert_eq!(
521 view.selections.display_ranges(cx),
522 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
523 );
524 });
525
526 _ = view.update(cx, |view, cx| {
527 view.cancel(&Cancel, cx);
528 view.update_selection(
529 DisplayPoint::new(DisplayRow(1), 1),
530 0,
531 gpui::Point::<f32>::default(),
532 cx,
533 );
534 assert_eq!(
535 view.selections.display_ranges(cx),
536 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
537 );
538 });
539}
540
541#[gpui::test]
542fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
543 init_test(cx, |_| {});
544
545 let view = cx.add_window(|cx| {
546 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
547 build_editor(buffer, cx)
548 });
549
550 _ = view.update(cx, |view, cx| {
551 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
552 assert_eq!(
553 view.selections.display_ranges(cx),
554 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
555 );
556
557 view.move_down(&Default::default(), cx);
558 assert_eq!(
559 view.selections.display_ranges(cx),
560 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
561 );
562
563 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
564 assert_eq!(
565 view.selections.display_ranges(cx),
566 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
567 );
568
569 view.move_up(&Default::default(), cx);
570 assert_eq!(
571 view.selections.display_ranges(cx),
572 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
573 );
574 });
575}
576
577#[gpui::test]
578fn test_clone(cx: &mut TestAppContext) {
579 init_test(cx, |_| {});
580
581 let (text, selection_ranges) = marked_text_ranges(
582 indoc! {"
583 one
584 two
585 threeˇ
586 four
587 fiveˇ
588 "},
589 true,
590 );
591
592 let editor = cx.add_window(|cx| {
593 let buffer = MultiBuffer::build_simple(&text, cx);
594 build_editor(buffer, cx)
595 });
596
597 _ = editor.update(cx, |editor, cx| {
598 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
599 editor.fold_ranges(
600 [
601 (Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
602 (Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
603 ],
604 true,
605 cx,
606 );
607 });
608
609 let cloned_editor = editor
610 .update(cx, |editor, cx| {
611 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
612 })
613 .unwrap()
614 .unwrap();
615
616 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
617 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
618
619 assert_eq!(
620 cloned_editor
621 .update(cx, |e, cx| e.display_text(cx))
622 .unwrap(),
623 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
624 );
625 assert_eq!(
626 cloned_snapshot
627 .folds_in_range(0..text.len())
628 .collect::<Vec<_>>(),
629 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
630 );
631 assert_set_eq!(
632 cloned_editor
633 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
634 .unwrap(),
635 editor
636 .update(cx, |editor, cx| editor.selections.ranges(cx))
637 .unwrap()
638 );
639 assert_set_eq!(
640 cloned_editor
641 .update(cx, |e, cx| e.selections.display_ranges(cx))
642 .unwrap(),
643 editor
644 .update(cx, |e, cx| e.selections.display_ranges(cx))
645 .unwrap()
646 );
647}
648
649#[gpui::test]
650async fn test_navigation_history(cx: &mut TestAppContext) {
651 init_test(cx, |_| {});
652
653 use workspace::item::Item;
654
655 let fs = FakeFs::new(cx.executor());
656 let project = Project::test(fs, [], cx).await;
657 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
658 let pane = workspace
659 .update(cx, |workspace, _| workspace.active_pane().clone())
660 .unwrap();
661
662 _ = workspace.update(cx, |_v, cx| {
663 cx.new_view(|cx| {
664 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
665 let mut editor = build_editor(buffer.clone(), cx);
666 let handle = cx.view();
667 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(handle)));
668
669 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
670 editor.nav_history.as_mut().unwrap().pop_backward(cx)
671 }
672
673 // Move the cursor a small distance.
674 // Nothing is added to the navigation history.
675 editor.change_selections(None, cx, |s| {
676 s.select_display_ranges([
677 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
678 ])
679 });
680 editor.change_selections(None, cx, |s| {
681 s.select_display_ranges([
682 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
683 ])
684 });
685 assert!(pop_history(&mut editor, cx).is_none());
686
687 // Move the cursor a large distance.
688 // The history can jump back to the previous position.
689 editor.change_selections(None, cx, |s| {
690 s.select_display_ranges([
691 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
692 ])
693 });
694 let nav_entry = pop_history(&mut editor, cx).unwrap();
695 editor.navigate(nav_entry.data.unwrap(), cx);
696 assert_eq!(nav_entry.item.id(), cx.entity_id());
697 assert_eq!(
698 editor.selections.display_ranges(cx),
699 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
700 );
701 assert!(pop_history(&mut editor, cx).is_none());
702
703 // Move the cursor a small distance via the mouse.
704 // Nothing is added to the navigation history.
705 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
706 editor.end_selection(cx);
707 assert_eq!(
708 editor.selections.display_ranges(cx),
709 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
710 );
711 assert!(pop_history(&mut editor, cx).is_none());
712
713 // Move the cursor a large distance via the mouse.
714 // The history can jump back to the previous position.
715 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
716 editor.end_selection(cx);
717 assert_eq!(
718 editor.selections.display_ranges(cx),
719 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
720 );
721 let nav_entry = pop_history(&mut editor, cx).unwrap();
722 editor.navigate(nav_entry.data.unwrap(), cx);
723 assert_eq!(nav_entry.item.id(), cx.entity_id());
724 assert_eq!(
725 editor.selections.display_ranges(cx),
726 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
727 );
728 assert!(pop_history(&mut editor, cx).is_none());
729
730 // Set scroll position to check later
731 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
732 let original_scroll_position = editor.scroll_manager.anchor();
733
734 // Jump to the end of the document and adjust scroll
735 editor.move_to_end(&MoveToEnd, cx);
736 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
737 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
738
739 let nav_entry = pop_history(&mut editor, cx).unwrap();
740 editor.navigate(nav_entry.data.unwrap(), cx);
741 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
742
743 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
744 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
745 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
746 let invalid_point = Point::new(9999, 0);
747 editor.navigate(
748 Box::new(NavigationData {
749 cursor_anchor: invalid_anchor,
750 cursor_position: invalid_point,
751 scroll_anchor: ScrollAnchor {
752 anchor: invalid_anchor,
753 offset: Default::default(),
754 },
755 scroll_top_row: invalid_point.row,
756 }),
757 cx,
758 );
759 assert_eq!(
760 editor.selections.display_ranges(cx),
761 &[editor.max_point(cx)..editor.max_point(cx)]
762 );
763 assert_eq!(
764 editor.scroll_position(cx),
765 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
766 );
767
768 editor
769 })
770 });
771}
772
773#[gpui::test]
774fn test_cancel(cx: &mut TestAppContext) {
775 init_test(cx, |_| {});
776
777 let view = cx.add_window(|cx| {
778 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
779 build_editor(buffer, cx)
780 });
781
782 _ = view.update(cx, |view, cx| {
783 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
784 view.update_selection(
785 DisplayPoint::new(DisplayRow(1), 1),
786 0,
787 gpui::Point::<f32>::default(),
788 cx,
789 );
790 view.end_selection(cx);
791
792 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
793 view.update_selection(
794 DisplayPoint::new(DisplayRow(0), 3),
795 0,
796 gpui::Point::<f32>::default(),
797 cx,
798 );
799 view.end_selection(cx);
800 assert_eq!(
801 view.selections.display_ranges(cx),
802 [
803 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
804 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
805 ]
806 );
807 });
808
809 _ = view.update(cx, |view, cx| {
810 view.cancel(&Cancel, cx);
811 assert_eq!(
812 view.selections.display_ranges(cx),
813 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
814 );
815 });
816
817 _ = view.update(cx, |view, cx| {
818 view.cancel(&Cancel, cx);
819 assert_eq!(
820 view.selections.display_ranges(cx),
821 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
822 );
823 });
824}
825
826#[gpui::test]
827fn test_fold_action(cx: &mut TestAppContext) {
828 init_test(cx, |_| {});
829
830 let view = cx.add_window(|cx| {
831 let buffer = MultiBuffer::build_simple(
832 &"
833 impl Foo {
834 // Hello!
835
836 fn a() {
837 1
838 }
839
840 fn b() {
841 2
842 }
843
844 fn c() {
845 3
846 }
847 }
848 "
849 .unindent(),
850 cx,
851 );
852 build_editor(buffer.clone(), cx)
853 });
854
855 _ = view.update(cx, |view, cx| {
856 view.change_selections(None, cx, |s| {
857 s.select_display_ranges([
858 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
859 ]);
860 });
861 view.fold(&Fold, cx);
862 assert_eq!(
863 view.display_text(cx),
864 "
865 impl Foo {
866 // Hello!
867
868 fn a() {
869 1
870 }
871
872 fn b() {⋯
873 }
874
875 fn c() {⋯
876 }
877 }
878 "
879 .unindent(),
880 );
881
882 view.fold(&Fold, cx);
883 assert_eq!(
884 view.display_text(cx),
885 "
886 impl Foo {⋯
887 }
888 "
889 .unindent(),
890 );
891
892 view.unfold_lines(&UnfoldLines, cx);
893 assert_eq!(
894 view.display_text(cx),
895 "
896 impl Foo {
897 // Hello!
898
899 fn a() {
900 1
901 }
902
903 fn b() {⋯
904 }
905
906 fn c() {⋯
907 }
908 }
909 "
910 .unindent(),
911 );
912
913 view.unfold_lines(&UnfoldLines, cx);
914 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
915 });
916}
917
918#[gpui::test]
919fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
920 init_test(cx, |_| {});
921
922 let view = cx.add_window(|cx| {
923 let buffer = MultiBuffer::build_simple(
924 &"
925 class Foo:
926 # Hello!
927
928 def a():
929 print(1)
930
931 def b():
932 print(2)
933
934 def c():
935 print(3)
936 "
937 .unindent(),
938 cx,
939 );
940 build_editor(buffer.clone(), cx)
941 });
942
943 _ = view.update(cx, |view, cx| {
944 view.change_selections(None, cx, |s| {
945 s.select_display_ranges([
946 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
947 ]);
948 });
949 view.fold(&Fold, cx);
950 assert_eq!(
951 view.display_text(cx),
952 "
953 class Foo:
954 # Hello!
955
956 def a():
957 print(1)
958
959 def b():⋯
960
961 def c():⋯
962 "
963 .unindent(),
964 );
965
966 view.fold(&Fold, cx);
967 assert_eq!(
968 view.display_text(cx),
969 "
970 class Foo:⋯
971 "
972 .unindent(),
973 );
974
975 view.unfold_lines(&UnfoldLines, cx);
976 assert_eq!(
977 view.display_text(cx),
978 "
979 class Foo:
980 # Hello!
981
982 def a():
983 print(1)
984
985 def b():⋯
986
987 def c():⋯
988 "
989 .unindent(),
990 );
991
992 view.unfold_lines(&UnfoldLines, cx);
993 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
994 });
995}
996
997#[gpui::test]
998fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
999 init_test(cx, |_| {});
1000
1001 let view = cx.add_window(|cx| {
1002 let buffer = MultiBuffer::build_simple(
1003 &"
1004 class Foo:
1005 # Hello!
1006
1007 def a():
1008 print(1)
1009
1010 def b():
1011 print(2)
1012
1013
1014 def c():
1015 print(3)
1016
1017
1018 "
1019 .unindent(),
1020 cx,
1021 );
1022 build_editor(buffer.clone(), cx)
1023 });
1024
1025 _ = view.update(cx, |view, cx| {
1026 view.change_selections(None, cx, |s| {
1027 s.select_display_ranges([
1028 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1029 ]);
1030 });
1031 view.fold(&Fold, cx);
1032 assert_eq!(
1033 view.display_text(cx),
1034 "
1035 class Foo:
1036 # Hello!
1037
1038 def a():
1039 print(1)
1040
1041 def b():⋯
1042
1043
1044 def c():⋯
1045
1046
1047 "
1048 .unindent(),
1049 );
1050
1051 view.fold(&Fold, cx);
1052 assert_eq!(
1053 view.display_text(cx),
1054 "
1055 class Foo:⋯
1056
1057
1058 "
1059 .unindent(),
1060 );
1061
1062 view.unfold_lines(&UnfoldLines, cx);
1063 assert_eq!(
1064 view.display_text(cx),
1065 "
1066 class Foo:
1067 # Hello!
1068
1069 def a():
1070 print(1)
1071
1072 def b():⋯
1073
1074
1075 def c():⋯
1076
1077
1078 "
1079 .unindent(),
1080 );
1081
1082 view.unfold_lines(&UnfoldLines, cx);
1083 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1084 });
1085}
1086
1087#[gpui::test]
1088fn test_fold_at_level(cx: &mut TestAppContext) {
1089 init_test(cx, |_| {});
1090
1091 let view = cx.add_window(|cx| {
1092 let buffer = MultiBuffer::build_simple(
1093 &"
1094 class Foo:
1095 # Hello!
1096
1097 def a():
1098 print(1)
1099
1100 def b():
1101 print(2)
1102
1103
1104 class Bar:
1105 # World!
1106
1107 def a():
1108 print(1)
1109
1110 def b():
1111 print(2)
1112
1113
1114 "
1115 .unindent(),
1116 cx,
1117 );
1118 build_editor(buffer.clone(), cx)
1119 });
1120
1121 _ = view.update(cx, |view, cx| {
1122 view.fold_at_level(&FoldAtLevel { level: 2 }, cx);
1123 assert_eq!(
1124 view.display_text(cx),
1125 "
1126 class Foo:
1127 # Hello!
1128
1129 def a():⋯
1130
1131 def b():⋯
1132
1133
1134 class Bar:
1135 # World!
1136
1137 def a():⋯
1138
1139 def b():⋯
1140
1141
1142 "
1143 .unindent(),
1144 );
1145
1146 view.fold_at_level(&FoldAtLevel { level: 1 }, cx);
1147 assert_eq!(
1148 view.display_text(cx),
1149 "
1150 class Foo:⋯
1151
1152
1153 class Bar:⋯
1154
1155
1156 "
1157 .unindent(),
1158 );
1159
1160 view.unfold_all(&UnfoldAll, cx);
1161 view.fold_at_level(&FoldAtLevel { level: 0 }, cx);
1162 assert_eq!(
1163 view.display_text(cx),
1164 "
1165 class Foo:
1166 # Hello!
1167
1168 def a():
1169 print(1)
1170
1171 def b():
1172 print(2)
1173
1174
1175 class Bar:
1176 # World!
1177
1178 def a():
1179 print(1)
1180
1181 def b():
1182 print(2)
1183
1184
1185 "
1186 .unindent(),
1187 );
1188
1189 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1190 });
1191}
1192
1193#[gpui::test]
1194fn test_move_cursor(cx: &mut TestAppContext) {
1195 init_test(cx, |_| {});
1196
1197 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1198 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1199
1200 buffer.update(cx, |buffer, cx| {
1201 buffer.edit(
1202 vec![
1203 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1204 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1205 ],
1206 None,
1207 cx,
1208 );
1209 });
1210 _ = view.update(cx, |view, cx| {
1211 assert_eq!(
1212 view.selections.display_ranges(cx),
1213 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1214 );
1215
1216 view.move_down(&MoveDown, cx);
1217 assert_eq!(
1218 view.selections.display_ranges(cx),
1219 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1220 );
1221
1222 view.move_right(&MoveRight, cx);
1223 assert_eq!(
1224 view.selections.display_ranges(cx),
1225 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1226 );
1227
1228 view.move_left(&MoveLeft, cx);
1229 assert_eq!(
1230 view.selections.display_ranges(cx),
1231 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1232 );
1233
1234 view.move_up(&MoveUp, cx);
1235 assert_eq!(
1236 view.selections.display_ranges(cx),
1237 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1238 );
1239
1240 view.move_to_end(&MoveToEnd, cx);
1241 assert_eq!(
1242 view.selections.display_ranges(cx),
1243 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1244 );
1245
1246 view.move_to_beginning(&MoveToBeginning, cx);
1247 assert_eq!(
1248 view.selections.display_ranges(cx),
1249 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1250 );
1251
1252 view.change_selections(None, cx, |s| {
1253 s.select_display_ranges([
1254 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1255 ]);
1256 });
1257 view.select_to_beginning(&SelectToBeginning, cx);
1258 assert_eq!(
1259 view.selections.display_ranges(cx),
1260 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1261 );
1262
1263 view.select_to_end(&SelectToEnd, cx);
1264 assert_eq!(
1265 view.selections.display_ranges(cx),
1266 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1267 );
1268 });
1269}
1270
1271// TODO: Re-enable this test
1272#[cfg(target_os = "macos")]
1273#[gpui::test]
1274fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1275 init_test(cx, |_| {});
1276
1277 let view = cx.add_window(|cx| {
1278 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1279 build_editor(buffer.clone(), cx)
1280 });
1281
1282 assert_eq!('ⓐ'.len_utf8(), 3);
1283 assert_eq!('α'.len_utf8(), 2);
1284
1285 _ = view.update(cx, |view, cx| {
1286 view.fold_ranges(
1287 vec![
1288 (Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1289 (Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1290 (Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1291 ],
1292 true,
1293 cx,
1294 );
1295 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1296
1297 view.move_right(&MoveRight, cx);
1298 assert_eq!(
1299 view.selections.display_ranges(cx),
1300 &[empty_range(0, "ⓐ".len())]
1301 );
1302 view.move_right(&MoveRight, cx);
1303 assert_eq!(
1304 view.selections.display_ranges(cx),
1305 &[empty_range(0, "ⓐⓑ".len())]
1306 );
1307 view.move_right(&MoveRight, cx);
1308 assert_eq!(
1309 view.selections.display_ranges(cx),
1310 &[empty_range(0, "ⓐⓑ⋯".len())]
1311 );
1312
1313 view.move_down(&MoveDown, cx);
1314 assert_eq!(
1315 view.selections.display_ranges(cx),
1316 &[empty_range(1, "ab⋯e".len())]
1317 );
1318 view.move_left(&MoveLeft, cx);
1319 assert_eq!(
1320 view.selections.display_ranges(cx),
1321 &[empty_range(1, "ab⋯".len())]
1322 );
1323 view.move_left(&MoveLeft, cx);
1324 assert_eq!(
1325 view.selections.display_ranges(cx),
1326 &[empty_range(1, "ab".len())]
1327 );
1328 view.move_left(&MoveLeft, cx);
1329 assert_eq!(
1330 view.selections.display_ranges(cx),
1331 &[empty_range(1, "a".len())]
1332 );
1333
1334 view.move_down(&MoveDown, cx);
1335 assert_eq!(
1336 view.selections.display_ranges(cx),
1337 &[empty_range(2, "α".len())]
1338 );
1339 view.move_right(&MoveRight, cx);
1340 assert_eq!(
1341 view.selections.display_ranges(cx),
1342 &[empty_range(2, "αβ".len())]
1343 );
1344 view.move_right(&MoveRight, cx);
1345 assert_eq!(
1346 view.selections.display_ranges(cx),
1347 &[empty_range(2, "αβ⋯".len())]
1348 );
1349 view.move_right(&MoveRight, cx);
1350 assert_eq!(
1351 view.selections.display_ranges(cx),
1352 &[empty_range(2, "αβ⋯ε".len())]
1353 );
1354
1355 view.move_up(&MoveUp, cx);
1356 assert_eq!(
1357 view.selections.display_ranges(cx),
1358 &[empty_range(1, "ab⋯e".len())]
1359 );
1360 view.move_down(&MoveDown, cx);
1361 assert_eq!(
1362 view.selections.display_ranges(cx),
1363 &[empty_range(2, "αβ⋯ε".len())]
1364 );
1365 view.move_up(&MoveUp, cx);
1366 assert_eq!(
1367 view.selections.display_ranges(cx),
1368 &[empty_range(1, "ab⋯e".len())]
1369 );
1370
1371 view.move_up(&MoveUp, cx);
1372 assert_eq!(
1373 view.selections.display_ranges(cx),
1374 &[empty_range(0, "ⓐⓑ".len())]
1375 );
1376 view.move_left(&MoveLeft, cx);
1377 assert_eq!(
1378 view.selections.display_ranges(cx),
1379 &[empty_range(0, "ⓐ".len())]
1380 );
1381 view.move_left(&MoveLeft, cx);
1382 assert_eq!(
1383 view.selections.display_ranges(cx),
1384 &[empty_range(0, "".len())]
1385 );
1386 });
1387}
1388
1389#[gpui::test]
1390fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1391 init_test(cx, |_| {});
1392
1393 let view = cx.add_window(|cx| {
1394 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1395 build_editor(buffer.clone(), cx)
1396 });
1397 _ = view.update(cx, |view, cx| {
1398 view.change_selections(None, cx, |s| {
1399 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1400 });
1401 view.move_down(&MoveDown, cx);
1402 assert_eq!(
1403 view.selections.display_ranges(cx),
1404 &[empty_range(1, "abcd".len())]
1405 );
1406
1407 view.move_down(&MoveDown, cx);
1408 assert_eq!(
1409 view.selections.display_ranges(cx),
1410 &[empty_range(2, "αβγ".len())]
1411 );
1412
1413 view.move_down(&MoveDown, cx);
1414 assert_eq!(
1415 view.selections.display_ranges(cx),
1416 &[empty_range(3, "abcd".len())]
1417 );
1418
1419 view.move_down(&MoveDown, cx);
1420 assert_eq!(
1421 view.selections.display_ranges(cx),
1422 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1423 );
1424
1425 view.move_up(&MoveUp, cx);
1426 assert_eq!(
1427 view.selections.display_ranges(cx),
1428 &[empty_range(3, "abcd".len())]
1429 );
1430
1431 view.move_up(&MoveUp, cx);
1432 assert_eq!(
1433 view.selections.display_ranges(cx),
1434 &[empty_range(2, "αβγ".len())]
1435 );
1436 });
1437}
1438
1439#[gpui::test]
1440fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1441 init_test(cx, |_| {});
1442 let move_to_beg = MoveToBeginningOfLine {
1443 stop_at_soft_wraps: true,
1444 };
1445
1446 let move_to_end = MoveToEndOfLine {
1447 stop_at_soft_wraps: true,
1448 };
1449
1450 let view = cx.add_window(|cx| {
1451 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1452 build_editor(buffer, cx)
1453 });
1454 _ = view.update(cx, |view, cx| {
1455 view.change_selections(None, cx, |s| {
1456 s.select_display_ranges([
1457 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1458 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1459 ]);
1460 });
1461 });
1462
1463 _ = view.update(cx, |view, cx| {
1464 view.move_to_beginning_of_line(&move_to_beg, cx);
1465 assert_eq!(
1466 view.selections.display_ranges(cx),
1467 &[
1468 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1469 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1470 ]
1471 );
1472 });
1473
1474 _ = view.update(cx, |view, cx| {
1475 view.move_to_beginning_of_line(&move_to_beg, cx);
1476 assert_eq!(
1477 view.selections.display_ranges(cx),
1478 &[
1479 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1480 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1481 ]
1482 );
1483 });
1484
1485 _ = view.update(cx, |view, cx| {
1486 view.move_to_beginning_of_line(&move_to_beg, cx);
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), 2)..DisplayPoint::new(DisplayRow(1), 2),
1492 ]
1493 );
1494 });
1495
1496 _ = view.update(cx, |view, cx| {
1497 view.move_to_end_of_line(&move_to_end, cx);
1498 assert_eq!(
1499 view.selections.display_ranges(cx),
1500 &[
1501 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1502 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1503 ]
1504 );
1505 });
1506
1507 // Moving to the end of line again is a no-op.
1508 _ = view.update(cx, |view, cx| {
1509 view.move_to_end_of_line(&move_to_end, cx);
1510 assert_eq!(
1511 view.selections.display_ranges(cx),
1512 &[
1513 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1514 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1515 ]
1516 );
1517 });
1518
1519 _ = view.update(cx, |view, cx| {
1520 view.move_left(&MoveLeft, cx);
1521 view.select_to_beginning_of_line(
1522 &SelectToBeginningOfLine {
1523 stop_at_soft_wraps: true,
1524 },
1525 cx,
1526 );
1527 assert_eq!(
1528 view.selections.display_ranges(cx),
1529 &[
1530 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1531 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1532 ]
1533 );
1534 });
1535
1536 _ = view.update(cx, |view, cx| {
1537 view.select_to_beginning_of_line(
1538 &SelectToBeginningOfLine {
1539 stop_at_soft_wraps: true,
1540 },
1541 cx,
1542 );
1543 assert_eq!(
1544 view.selections.display_ranges(cx),
1545 &[
1546 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1547 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1548 ]
1549 );
1550 });
1551
1552 _ = view.update(cx, |view, cx| {
1553 view.select_to_beginning_of_line(
1554 &SelectToBeginningOfLine {
1555 stop_at_soft_wraps: true,
1556 },
1557 cx,
1558 );
1559 assert_eq!(
1560 view.selections.display_ranges(cx),
1561 &[
1562 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1563 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1564 ]
1565 );
1566 });
1567
1568 _ = view.update(cx, |view, cx| {
1569 view.select_to_end_of_line(
1570 &SelectToEndOfLine {
1571 stop_at_soft_wraps: true,
1572 },
1573 cx,
1574 );
1575 assert_eq!(
1576 view.selections.display_ranges(cx),
1577 &[
1578 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1579 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1580 ]
1581 );
1582 });
1583
1584 _ = view.update(cx, |view, cx| {
1585 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1586 assert_eq!(view.display_text(cx), "ab\n de");
1587 assert_eq!(
1588 view.selections.display_ranges(cx),
1589 &[
1590 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1591 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1592 ]
1593 );
1594 });
1595
1596 _ = view.update(cx, |view, cx| {
1597 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1598 assert_eq!(view.display_text(cx), "\n");
1599 assert_eq!(
1600 view.selections.display_ranges(cx),
1601 &[
1602 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1603 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1604 ]
1605 );
1606 });
1607}
1608
1609#[gpui::test]
1610fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1611 init_test(cx, |_| {});
1612 let move_to_beg = MoveToBeginningOfLine {
1613 stop_at_soft_wraps: false,
1614 };
1615
1616 let move_to_end = MoveToEndOfLine {
1617 stop_at_soft_wraps: false,
1618 };
1619
1620 let view = cx.add_window(|cx| {
1621 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1622 build_editor(buffer, cx)
1623 });
1624
1625 _ = view.update(cx, |view, cx| {
1626 view.set_wrap_width(Some(140.0.into()), cx);
1627
1628 // We expect the following lines after wrapping
1629 // ```
1630 // thequickbrownfox
1631 // jumpedoverthelazydo
1632 // gs
1633 // ```
1634 // The final `gs` was soft-wrapped onto a new line.
1635 assert_eq!(
1636 "thequickbrownfox\njumpedoverthelaz\nydogs",
1637 view.display_text(cx),
1638 );
1639
1640 // First, let's assert behavior on the first line, that was not soft-wrapped.
1641 // Start the cursor at the `k` on the first line
1642 view.change_selections(None, cx, |s| {
1643 s.select_display_ranges([
1644 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1645 ]);
1646 });
1647
1648 // Moving to the beginning of the line should put us at the beginning of the line.
1649 view.move_to_beginning_of_line(&move_to_beg, cx);
1650 assert_eq!(
1651 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1652 view.selections.display_ranges(cx)
1653 );
1654
1655 // Moving to the end of the line should put us at the end of the line.
1656 view.move_to_end_of_line(&move_to_end, cx);
1657 assert_eq!(
1658 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1659 view.selections.display_ranges(cx)
1660 );
1661
1662 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1663 // Start the cursor at the last line (`y` that was wrapped to a new line)
1664 view.change_selections(None, cx, |s| {
1665 s.select_display_ranges([
1666 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1667 ]);
1668 });
1669
1670 // Moving to the beginning of the line should put us at the start of the second line of
1671 // display text, i.e., the `j`.
1672 view.move_to_beginning_of_line(&move_to_beg, cx);
1673 assert_eq!(
1674 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1675 view.selections.display_ranges(cx)
1676 );
1677
1678 // Moving to the beginning of the line again should be a no-op.
1679 view.move_to_beginning_of_line(&move_to_beg, cx);
1680 assert_eq!(
1681 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1682 view.selections.display_ranges(cx)
1683 );
1684
1685 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1686 // next display line.
1687 view.move_to_end_of_line(&move_to_end, cx);
1688 assert_eq!(
1689 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1690 view.selections.display_ranges(cx)
1691 );
1692
1693 // Moving to the end of the line again should be a no-op.
1694 view.move_to_end_of_line(&move_to_end, cx);
1695 assert_eq!(
1696 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1697 view.selections.display_ranges(cx)
1698 );
1699 });
1700}
1701
1702#[gpui::test]
1703fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1704 init_test(cx, |_| {});
1705
1706 let view = cx.add_window(|cx| {
1707 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1708 build_editor(buffer, cx)
1709 });
1710 _ = view.update(cx, |view, cx| {
1711 view.change_selections(None, cx, |s| {
1712 s.select_display_ranges([
1713 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1714 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1715 ])
1716 });
1717
1718 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1719 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1720
1721 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1722 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1723
1724 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1725 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1726
1727 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1728 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1729
1730 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1731 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1732
1733 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1734 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1735
1736 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1737 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1738
1739 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1740 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1741
1742 view.move_right(&MoveRight, cx);
1743 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1744 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1745
1746 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1747 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1748
1749 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1750 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1751 });
1752}
1753
1754#[gpui::test]
1755fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1756 init_test(cx, |_| {});
1757
1758 let view = cx.add_window(|cx| {
1759 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1760 build_editor(buffer, cx)
1761 });
1762
1763 _ = view.update(cx, |view, cx| {
1764 view.set_wrap_width(Some(140.0.into()), cx);
1765 assert_eq!(
1766 view.display_text(cx),
1767 "use one::{\n two::three::\n four::five\n};"
1768 );
1769
1770 view.change_selections(None, cx, |s| {
1771 s.select_display_ranges([
1772 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1773 ]);
1774 });
1775
1776 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1777 assert_eq!(
1778 view.selections.display_ranges(cx),
1779 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1780 );
1781
1782 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1783 assert_eq!(
1784 view.selections.display_ranges(cx),
1785 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1786 );
1787
1788 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1789 assert_eq!(
1790 view.selections.display_ranges(cx),
1791 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1792 );
1793
1794 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1795 assert_eq!(
1796 view.selections.display_ranges(cx),
1797 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1798 );
1799
1800 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1801 assert_eq!(
1802 view.selections.display_ranges(cx),
1803 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1804 );
1805
1806 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1807 assert_eq!(
1808 view.selections.display_ranges(cx),
1809 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1810 );
1811 });
1812}
1813
1814#[gpui::test]
1815async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1816 init_test(cx, |_| {});
1817 let mut cx = EditorTestContext::new(cx).await;
1818
1819 let line_height = cx.editor(|editor, cx| {
1820 editor
1821 .style()
1822 .unwrap()
1823 .text
1824 .line_height_in_pixels(cx.rem_size())
1825 });
1826 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1827
1828 cx.set_state(
1829 &r#"ˇone
1830 two
1831
1832 three
1833 fourˇ
1834 five
1835
1836 six"#
1837 .unindent(),
1838 );
1839
1840 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1841 cx.assert_editor_state(
1842 &r#"one
1843 two
1844 ˇ
1845 three
1846 four
1847 five
1848 ˇ
1849 six"#
1850 .unindent(),
1851 );
1852
1853 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1854 cx.assert_editor_state(
1855 &r#"one
1856 two
1857
1858 three
1859 four
1860 five
1861 ˇ
1862 sixˇ"#
1863 .unindent(),
1864 );
1865
1866 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1867 cx.assert_editor_state(
1868 &r#"one
1869 two
1870
1871 three
1872 four
1873 five
1874
1875 sixˇ"#
1876 .unindent(),
1877 );
1878
1879 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1880 cx.assert_editor_state(
1881 &r#"one
1882 two
1883
1884 three
1885 four
1886 five
1887 ˇ
1888 six"#
1889 .unindent(),
1890 );
1891
1892 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1893 cx.assert_editor_state(
1894 &r#"one
1895 two
1896 ˇ
1897 three
1898 four
1899 five
1900
1901 six"#
1902 .unindent(),
1903 );
1904
1905 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1906 cx.assert_editor_state(
1907 &r#"ˇone
1908 two
1909
1910 three
1911 four
1912 five
1913
1914 six"#
1915 .unindent(),
1916 );
1917}
1918
1919#[gpui::test]
1920async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1921 init_test(cx, |_| {});
1922 let mut cx = EditorTestContext::new(cx).await;
1923 let line_height = cx.editor(|editor, cx| {
1924 editor
1925 .style()
1926 .unwrap()
1927 .text
1928 .line_height_in_pixels(cx.rem_size())
1929 });
1930 let window = cx.window;
1931 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1932
1933 cx.set_state(
1934 r#"ˇone
1935 two
1936 three
1937 four
1938 five
1939 six
1940 seven
1941 eight
1942 nine
1943 ten
1944 "#,
1945 );
1946
1947 cx.update_editor(|editor, cx| {
1948 assert_eq!(
1949 editor.snapshot(cx).scroll_position(),
1950 gpui::Point::new(0., 0.)
1951 );
1952 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1953 assert_eq!(
1954 editor.snapshot(cx).scroll_position(),
1955 gpui::Point::new(0., 3.)
1956 );
1957 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1958 assert_eq!(
1959 editor.snapshot(cx).scroll_position(),
1960 gpui::Point::new(0., 6.)
1961 );
1962 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1963 assert_eq!(
1964 editor.snapshot(cx).scroll_position(),
1965 gpui::Point::new(0., 3.)
1966 );
1967
1968 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1969 assert_eq!(
1970 editor.snapshot(cx).scroll_position(),
1971 gpui::Point::new(0., 1.)
1972 );
1973 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
1974 assert_eq!(
1975 editor.snapshot(cx).scroll_position(),
1976 gpui::Point::new(0., 3.)
1977 );
1978 });
1979}
1980
1981#[gpui::test]
1982async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
1983 init_test(cx, |_| {});
1984 let mut cx = EditorTestContext::new(cx).await;
1985
1986 let line_height = cx.update_editor(|editor, cx| {
1987 editor.set_vertical_scroll_margin(2, cx);
1988 editor
1989 .style()
1990 .unwrap()
1991 .text
1992 .line_height_in_pixels(cx.rem_size())
1993 });
1994 let window = cx.window;
1995 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
1996
1997 cx.set_state(
1998 r#"ˇone
1999 two
2000 three
2001 four
2002 five
2003 six
2004 seven
2005 eight
2006 nine
2007 ten
2008 "#,
2009 );
2010 cx.update_editor(|editor, cx| {
2011 assert_eq!(
2012 editor.snapshot(cx).scroll_position(),
2013 gpui::Point::new(0., 0.0)
2014 );
2015 });
2016
2017 // Add a cursor below the visible area. Since both cursors cannot fit
2018 // on screen, the editor autoscrolls to reveal the newest cursor, and
2019 // allows the vertical scroll margin below that cursor.
2020 cx.update_editor(|editor, cx| {
2021 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2022 selections.select_ranges([
2023 Point::new(0, 0)..Point::new(0, 0),
2024 Point::new(6, 0)..Point::new(6, 0),
2025 ]);
2026 })
2027 });
2028 cx.update_editor(|editor, cx| {
2029 assert_eq!(
2030 editor.snapshot(cx).scroll_position(),
2031 gpui::Point::new(0., 3.0)
2032 );
2033 });
2034
2035 // Move down. The editor cursor scrolls down to track the newest cursor.
2036 cx.update_editor(|editor, cx| {
2037 editor.move_down(&Default::default(), cx);
2038 });
2039 cx.update_editor(|editor, cx| {
2040 assert_eq!(
2041 editor.snapshot(cx).scroll_position(),
2042 gpui::Point::new(0., 4.0)
2043 );
2044 });
2045
2046 // Add a cursor above the visible area. Since both cursors fit on screen,
2047 // the editor scrolls to show both.
2048 cx.update_editor(|editor, cx| {
2049 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2050 selections.select_ranges([
2051 Point::new(1, 0)..Point::new(1, 0),
2052 Point::new(6, 0)..Point::new(6, 0),
2053 ]);
2054 })
2055 });
2056 cx.update_editor(|editor, cx| {
2057 assert_eq!(
2058 editor.snapshot(cx).scroll_position(),
2059 gpui::Point::new(0., 1.0)
2060 );
2061 });
2062}
2063
2064#[gpui::test]
2065async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
2066 init_test(cx, |_| {});
2067 let mut cx = EditorTestContext::new(cx).await;
2068
2069 let line_height = cx.editor(|editor, cx| {
2070 editor
2071 .style()
2072 .unwrap()
2073 .text
2074 .line_height_in_pixels(cx.rem_size())
2075 });
2076 let window = cx.window;
2077 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2078 cx.set_state(
2079 &r#"
2080 ˇone
2081 two
2082 threeˇ
2083 four
2084 five
2085 six
2086 seven
2087 eight
2088 nine
2089 ten
2090 "#
2091 .unindent(),
2092 );
2093
2094 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2095 cx.assert_editor_state(
2096 &r#"
2097 one
2098 two
2099 three
2100 ˇfour
2101 five
2102 sixˇ
2103 seven
2104 eight
2105 nine
2106 ten
2107 "#
2108 .unindent(),
2109 );
2110
2111 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2112 cx.assert_editor_state(
2113 &r#"
2114 one
2115 two
2116 three
2117 four
2118 five
2119 six
2120 ˇseven
2121 eight
2122 nineˇ
2123 ten
2124 "#
2125 .unindent(),
2126 );
2127
2128 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2129 cx.assert_editor_state(
2130 &r#"
2131 one
2132 two
2133 three
2134 ˇfour
2135 five
2136 sixˇ
2137 seven
2138 eight
2139 nine
2140 ten
2141 "#
2142 .unindent(),
2143 );
2144
2145 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2146 cx.assert_editor_state(
2147 &r#"
2148 ˇone
2149 two
2150 threeˇ
2151 four
2152 five
2153 six
2154 seven
2155 eight
2156 nine
2157 ten
2158 "#
2159 .unindent(),
2160 );
2161
2162 // Test select collapsing
2163 cx.update_editor(|editor, cx| {
2164 editor.move_page_down(&MovePageDown::default(), cx);
2165 editor.move_page_down(&MovePageDown::default(), cx);
2166 editor.move_page_down(&MovePageDown::default(), cx);
2167 });
2168 cx.assert_editor_state(
2169 &r#"
2170 one
2171 two
2172 three
2173 four
2174 five
2175 six
2176 seven
2177 eight
2178 nine
2179 ˇten
2180 ˇ"#
2181 .unindent(),
2182 );
2183}
2184
2185#[gpui::test]
2186async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2187 init_test(cx, |_| {});
2188 let mut cx = EditorTestContext::new(cx).await;
2189 cx.set_state("one «two threeˇ» four");
2190 cx.update_editor(|editor, cx| {
2191 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2192 assert_eq!(editor.text(cx), " four");
2193 });
2194}
2195
2196#[gpui::test]
2197fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2198 init_test(cx, |_| {});
2199
2200 let view = cx.add_window(|cx| {
2201 let buffer = MultiBuffer::build_simple("one two three four", cx);
2202 build_editor(buffer.clone(), cx)
2203 });
2204
2205 _ = view.update(cx, |view, cx| {
2206 view.change_selections(None, cx, |s| {
2207 s.select_display_ranges([
2208 // an empty selection - the preceding word fragment is deleted
2209 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2210 // characters selected - they are deleted
2211 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2212 ])
2213 });
2214 view.delete_to_previous_word_start(
2215 &DeleteToPreviousWordStart {
2216 ignore_newlines: false,
2217 },
2218 cx,
2219 );
2220 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2221 });
2222
2223 _ = view.update(cx, |view, cx| {
2224 view.change_selections(None, cx, |s| {
2225 s.select_display_ranges([
2226 // an empty selection - the following word fragment is deleted
2227 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2228 // characters selected - they are deleted
2229 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2230 ])
2231 });
2232 view.delete_to_next_word_end(
2233 &DeleteToNextWordEnd {
2234 ignore_newlines: false,
2235 },
2236 cx,
2237 );
2238 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2239 });
2240}
2241
2242#[gpui::test]
2243fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2244 init_test(cx, |_| {});
2245
2246 let view = cx.add_window(|cx| {
2247 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2248 build_editor(buffer.clone(), cx)
2249 });
2250 let del_to_prev_word_start = DeleteToPreviousWordStart {
2251 ignore_newlines: false,
2252 };
2253 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2254 ignore_newlines: true,
2255 };
2256
2257 _ = view.update(cx, |view, cx| {
2258 view.change_selections(None, cx, |s| {
2259 s.select_display_ranges([
2260 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2261 ])
2262 });
2263 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2264 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2265 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2266 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2267 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2268 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\n");
2269 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2270 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2");
2271 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2272 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n");
2273 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2274 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2275 });
2276}
2277
2278#[gpui::test]
2279fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2280 init_test(cx, |_| {});
2281
2282 let view = cx.add_window(|cx| {
2283 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2284 build_editor(buffer.clone(), cx)
2285 });
2286 let del_to_next_word_end = DeleteToNextWordEnd {
2287 ignore_newlines: false,
2288 };
2289 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2290 ignore_newlines: true,
2291 };
2292
2293 _ = view.update(cx, |view, cx| {
2294 view.change_selections(None, cx, |s| {
2295 s.select_display_ranges([
2296 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2297 ])
2298 });
2299 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2300 assert_eq!(
2301 view.buffer.read(cx).read(cx).text(),
2302 "one\n two\nthree\n four"
2303 );
2304 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2305 assert_eq!(
2306 view.buffer.read(cx).read(cx).text(),
2307 "\n two\nthree\n four"
2308 );
2309 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2310 assert_eq!(view.buffer.read(cx).read(cx).text(), "two\nthree\n four");
2311 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2312 assert_eq!(view.buffer.read(cx).read(cx).text(), "\nthree\n four");
2313 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2314 assert_eq!(view.buffer.read(cx).read(cx).text(), "\n four");
2315 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2316 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2317 });
2318}
2319
2320#[gpui::test]
2321fn test_newline(cx: &mut TestAppContext) {
2322 init_test(cx, |_| {});
2323
2324 let view = cx.add_window(|cx| {
2325 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2326 build_editor(buffer.clone(), cx)
2327 });
2328
2329 _ = view.update(cx, |view, cx| {
2330 view.change_selections(None, cx, |s| {
2331 s.select_display_ranges([
2332 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2333 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2334 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2335 ])
2336 });
2337
2338 view.newline(&Newline, cx);
2339 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2340 });
2341}
2342
2343#[gpui::test]
2344fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2345 init_test(cx, |_| {});
2346
2347 let editor = cx.add_window(|cx| {
2348 let buffer = MultiBuffer::build_simple(
2349 "
2350 a
2351 b(
2352 X
2353 )
2354 c(
2355 X
2356 )
2357 "
2358 .unindent()
2359 .as_str(),
2360 cx,
2361 );
2362 let mut editor = build_editor(buffer.clone(), cx);
2363 editor.change_selections(None, cx, |s| {
2364 s.select_ranges([
2365 Point::new(2, 4)..Point::new(2, 5),
2366 Point::new(5, 4)..Point::new(5, 5),
2367 ])
2368 });
2369 editor
2370 });
2371
2372 _ = editor.update(cx, |editor, cx| {
2373 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2374 editor.buffer.update(cx, |buffer, cx| {
2375 buffer.edit(
2376 [
2377 (Point::new(1, 2)..Point::new(3, 0), ""),
2378 (Point::new(4, 2)..Point::new(6, 0), ""),
2379 ],
2380 None,
2381 cx,
2382 );
2383 assert_eq!(
2384 buffer.read(cx).text(),
2385 "
2386 a
2387 b()
2388 c()
2389 "
2390 .unindent()
2391 );
2392 });
2393 assert_eq!(
2394 editor.selections.ranges(cx),
2395 &[
2396 Point::new(1, 2)..Point::new(1, 2),
2397 Point::new(2, 2)..Point::new(2, 2),
2398 ],
2399 );
2400
2401 editor.newline(&Newline, cx);
2402 assert_eq!(
2403 editor.text(cx),
2404 "
2405 a
2406 b(
2407 )
2408 c(
2409 )
2410 "
2411 .unindent()
2412 );
2413
2414 // The selections are moved after the inserted newlines
2415 assert_eq!(
2416 editor.selections.ranges(cx),
2417 &[
2418 Point::new(2, 0)..Point::new(2, 0),
2419 Point::new(4, 0)..Point::new(4, 0),
2420 ],
2421 );
2422 });
2423}
2424
2425#[gpui::test]
2426async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2427 init_test(cx, |settings| {
2428 settings.defaults.tab_size = NonZeroU32::new(4)
2429 });
2430
2431 let language = Arc::new(
2432 Language::new(
2433 LanguageConfig::default(),
2434 Some(tree_sitter_rust::LANGUAGE.into()),
2435 )
2436 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2437 .unwrap(),
2438 );
2439
2440 let mut cx = EditorTestContext::new(cx).await;
2441 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2442 cx.set_state(indoc! {"
2443 const a: ˇA = (
2444 (ˇ
2445 «const_functionˇ»(ˇ),
2446 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2447 )ˇ
2448 ˇ);ˇ
2449 "});
2450
2451 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2452 cx.assert_editor_state(indoc! {"
2453 ˇ
2454 const a: A = (
2455 ˇ
2456 (
2457 ˇ
2458 ˇ
2459 const_function(),
2460 ˇ
2461 ˇ
2462 ˇ
2463 ˇ
2464 something_else,
2465 ˇ
2466 )
2467 ˇ
2468 ˇ
2469 );
2470 "});
2471}
2472
2473#[gpui::test]
2474async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2475 init_test(cx, |settings| {
2476 settings.defaults.tab_size = NonZeroU32::new(4)
2477 });
2478
2479 let language = Arc::new(
2480 Language::new(
2481 LanguageConfig::default(),
2482 Some(tree_sitter_rust::LANGUAGE.into()),
2483 )
2484 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2485 .unwrap(),
2486 );
2487
2488 let mut cx = EditorTestContext::new(cx).await;
2489 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2490 cx.set_state(indoc! {"
2491 const a: ˇA = (
2492 (ˇ
2493 «const_functionˇ»(ˇ),
2494 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2495 )ˇ
2496 ˇ);ˇ
2497 "});
2498
2499 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2500 cx.assert_editor_state(indoc! {"
2501 const a: A = (
2502 ˇ
2503 (
2504 ˇ
2505 const_function(),
2506 ˇ
2507 ˇ
2508 something_else,
2509 ˇ
2510 ˇ
2511 ˇ
2512 ˇ
2513 )
2514 ˇ
2515 );
2516 ˇ
2517 ˇ
2518 "});
2519}
2520
2521#[gpui::test]
2522async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2523 init_test(cx, |settings| {
2524 settings.defaults.tab_size = NonZeroU32::new(4)
2525 });
2526
2527 let language = Arc::new(Language::new(
2528 LanguageConfig {
2529 line_comments: vec!["//".into()],
2530 ..LanguageConfig::default()
2531 },
2532 None,
2533 ));
2534 {
2535 let mut cx = EditorTestContext::new(cx).await;
2536 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2537 cx.set_state(indoc! {"
2538 // Fooˇ
2539 "});
2540
2541 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2542 cx.assert_editor_state(indoc! {"
2543 // Foo
2544 //ˇ
2545 "});
2546 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2547 cx.set_state(indoc! {"
2548 ˇ// Foo
2549 "});
2550 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2551 cx.assert_editor_state(indoc! {"
2552
2553 ˇ// Foo
2554 "});
2555 }
2556 // Ensure that comment continuations can be disabled.
2557 update_test_language_settings(cx, |settings| {
2558 settings.defaults.extend_comment_on_newline = Some(false);
2559 });
2560 let mut cx = EditorTestContext::new(cx).await;
2561 cx.set_state(indoc! {"
2562 // Fooˇ
2563 "});
2564 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2565 cx.assert_editor_state(indoc! {"
2566 // Foo
2567 ˇ
2568 "});
2569}
2570
2571#[gpui::test]
2572fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2573 init_test(cx, |_| {});
2574
2575 let editor = cx.add_window(|cx| {
2576 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2577 let mut editor = build_editor(buffer.clone(), cx);
2578 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2579 editor
2580 });
2581
2582 _ = editor.update(cx, |editor, cx| {
2583 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2584 editor.buffer.update(cx, |buffer, cx| {
2585 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2586 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2587 });
2588 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2589
2590 editor.insert("Z", cx);
2591 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2592
2593 // The selections are moved after the inserted characters
2594 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2595 });
2596}
2597
2598#[gpui::test]
2599async fn test_tab(cx: &mut gpui::TestAppContext) {
2600 init_test(cx, |settings| {
2601 settings.defaults.tab_size = NonZeroU32::new(3)
2602 });
2603
2604 let mut cx = EditorTestContext::new(cx).await;
2605 cx.set_state(indoc! {"
2606 ˇabˇc
2607 ˇ🏀ˇ🏀ˇefg
2608 dˇ
2609 "});
2610 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2611 cx.assert_editor_state(indoc! {"
2612 ˇab ˇc
2613 ˇ🏀 ˇ🏀 ˇefg
2614 d ˇ
2615 "});
2616
2617 cx.set_state(indoc! {"
2618 a
2619 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2620 "});
2621 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2622 cx.assert_editor_state(indoc! {"
2623 a
2624 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2625 "});
2626}
2627
2628#[gpui::test]
2629async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2630 init_test(cx, |_| {});
2631
2632 let mut cx = EditorTestContext::new(cx).await;
2633 let language = Arc::new(
2634 Language::new(
2635 LanguageConfig::default(),
2636 Some(tree_sitter_rust::LANGUAGE.into()),
2637 )
2638 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2639 .unwrap(),
2640 );
2641 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2642
2643 // cursors that are already at the suggested indent level insert
2644 // a soft tab. cursors that are to the left of the suggested indent
2645 // auto-indent their line.
2646 cx.set_state(indoc! {"
2647 ˇ
2648 const a: B = (
2649 c(
2650 d(
2651 ˇ
2652 )
2653 ˇ
2654 ˇ )
2655 );
2656 "});
2657 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2658 cx.assert_editor_state(indoc! {"
2659 ˇ
2660 const a: B = (
2661 c(
2662 d(
2663 ˇ
2664 )
2665 ˇ
2666 ˇ)
2667 );
2668 "});
2669
2670 // handle auto-indent when there are multiple cursors on the same line
2671 cx.set_state(indoc! {"
2672 const a: B = (
2673 c(
2674 ˇ ˇ
2675 ˇ )
2676 );
2677 "});
2678 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2679 cx.assert_editor_state(indoc! {"
2680 const a: B = (
2681 c(
2682 ˇ
2683 ˇ)
2684 );
2685 "});
2686}
2687
2688#[gpui::test]
2689async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2690 init_test(cx, |settings| {
2691 settings.defaults.tab_size = NonZeroU32::new(4)
2692 });
2693
2694 let language = Arc::new(
2695 Language::new(
2696 LanguageConfig::default(),
2697 Some(tree_sitter_rust::LANGUAGE.into()),
2698 )
2699 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2700 .unwrap(),
2701 );
2702
2703 let mut cx = EditorTestContext::new(cx).await;
2704 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2705 cx.set_state(indoc! {"
2706 fn a() {
2707 if b {
2708 \t ˇc
2709 }
2710 }
2711 "});
2712
2713 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2714 cx.assert_editor_state(indoc! {"
2715 fn a() {
2716 if b {
2717 ˇc
2718 }
2719 }
2720 "});
2721}
2722
2723#[gpui::test]
2724async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2725 init_test(cx, |settings| {
2726 settings.defaults.tab_size = NonZeroU32::new(4);
2727 });
2728
2729 let mut cx = EditorTestContext::new(cx).await;
2730
2731 cx.set_state(indoc! {"
2732 «oneˇ» «twoˇ»
2733 three
2734 four
2735 "});
2736 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2737 cx.assert_editor_state(indoc! {"
2738 «oneˇ» «twoˇ»
2739 three
2740 four
2741 "});
2742
2743 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2744 cx.assert_editor_state(indoc! {"
2745 «oneˇ» «twoˇ»
2746 three
2747 four
2748 "});
2749
2750 // select across line ending
2751 cx.set_state(indoc! {"
2752 one two
2753 t«hree
2754 ˇ» four
2755 "});
2756 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2757 cx.assert_editor_state(indoc! {"
2758 one two
2759 t«hree
2760 ˇ» four
2761 "});
2762
2763 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2764 cx.assert_editor_state(indoc! {"
2765 one two
2766 t«hree
2767 ˇ» four
2768 "});
2769
2770 // Ensure that indenting/outdenting works when the cursor is at column 0.
2771 cx.set_state(indoc! {"
2772 one two
2773 ˇthree
2774 four
2775 "});
2776 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2777 cx.assert_editor_state(indoc! {"
2778 one two
2779 ˇthree
2780 four
2781 "});
2782
2783 cx.set_state(indoc! {"
2784 one two
2785 ˇ three
2786 four
2787 "});
2788 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2789 cx.assert_editor_state(indoc! {"
2790 one two
2791 ˇthree
2792 four
2793 "});
2794}
2795
2796#[gpui::test]
2797async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2798 init_test(cx, |settings| {
2799 settings.defaults.hard_tabs = Some(true);
2800 });
2801
2802 let mut cx = EditorTestContext::new(cx).await;
2803
2804 // select two ranges on one line
2805 cx.set_state(indoc! {"
2806 «oneˇ» «twoˇ»
2807 three
2808 four
2809 "});
2810 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2811 cx.assert_editor_state(indoc! {"
2812 \t«oneˇ» «twoˇ»
2813 three
2814 four
2815 "});
2816 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2817 cx.assert_editor_state(indoc! {"
2818 \t\t«oneˇ» «twoˇ»
2819 three
2820 four
2821 "});
2822 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2823 cx.assert_editor_state(indoc! {"
2824 \t«oneˇ» «twoˇ»
2825 three
2826 four
2827 "});
2828 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2829 cx.assert_editor_state(indoc! {"
2830 «oneˇ» «twoˇ»
2831 three
2832 four
2833 "});
2834
2835 // select across a line ending
2836 cx.set_state(indoc! {"
2837 one two
2838 t«hree
2839 ˇ»four
2840 "});
2841 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2842 cx.assert_editor_state(indoc! {"
2843 one two
2844 \tt«hree
2845 ˇ»four
2846 "});
2847 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2848 cx.assert_editor_state(indoc! {"
2849 one two
2850 \t\tt«hree
2851 ˇ»four
2852 "});
2853 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2854 cx.assert_editor_state(indoc! {"
2855 one two
2856 \tt«hree
2857 ˇ»four
2858 "});
2859 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2860 cx.assert_editor_state(indoc! {"
2861 one two
2862 t«hree
2863 ˇ»four
2864 "});
2865
2866 // Ensure that indenting/outdenting works when the cursor is at column 0.
2867 cx.set_state(indoc! {"
2868 one two
2869 ˇthree
2870 four
2871 "});
2872 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2873 cx.assert_editor_state(indoc! {"
2874 one two
2875 ˇthree
2876 four
2877 "});
2878 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2879 cx.assert_editor_state(indoc! {"
2880 one two
2881 \tˇthree
2882 four
2883 "});
2884 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2885 cx.assert_editor_state(indoc! {"
2886 one two
2887 ˇthree
2888 four
2889 "});
2890}
2891
2892#[gpui::test]
2893fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2894 init_test(cx, |settings| {
2895 settings.languages.extend([
2896 (
2897 "TOML".into(),
2898 LanguageSettingsContent {
2899 tab_size: NonZeroU32::new(2),
2900 ..Default::default()
2901 },
2902 ),
2903 (
2904 "Rust".into(),
2905 LanguageSettingsContent {
2906 tab_size: NonZeroU32::new(4),
2907 ..Default::default()
2908 },
2909 ),
2910 ]);
2911 });
2912
2913 let toml_language = Arc::new(Language::new(
2914 LanguageConfig {
2915 name: "TOML".into(),
2916 ..Default::default()
2917 },
2918 None,
2919 ));
2920 let rust_language = Arc::new(Language::new(
2921 LanguageConfig {
2922 name: "Rust".into(),
2923 ..Default::default()
2924 },
2925 None,
2926 ));
2927
2928 let toml_buffer =
2929 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2930 let rust_buffer = cx.new_model(|cx| {
2931 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2932 });
2933 let multibuffer = cx.new_model(|cx| {
2934 let mut multibuffer = MultiBuffer::new(ReadWrite);
2935 multibuffer.push_excerpts(
2936 toml_buffer.clone(),
2937 [ExcerptRange {
2938 context: Point::new(0, 0)..Point::new(2, 0),
2939 primary: None,
2940 }],
2941 cx,
2942 );
2943 multibuffer.push_excerpts(
2944 rust_buffer.clone(),
2945 [ExcerptRange {
2946 context: Point::new(0, 0)..Point::new(1, 0),
2947 primary: None,
2948 }],
2949 cx,
2950 );
2951 multibuffer
2952 });
2953
2954 cx.add_window(|cx| {
2955 let mut editor = build_editor(multibuffer, cx);
2956
2957 assert_eq!(
2958 editor.text(cx),
2959 indoc! {"
2960 a = 1
2961 b = 2
2962
2963 const c: usize = 3;
2964 "}
2965 );
2966
2967 select_ranges(
2968 &mut editor,
2969 indoc! {"
2970 «aˇ» = 1
2971 b = 2
2972
2973 «const c:ˇ» usize = 3;
2974 "},
2975 cx,
2976 );
2977
2978 editor.tab(&Tab, cx);
2979 assert_text_with_selections(
2980 &mut editor,
2981 indoc! {"
2982 «aˇ» = 1
2983 b = 2
2984
2985 «const c:ˇ» usize = 3;
2986 "},
2987 cx,
2988 );
2989 editor.tab_prev(&TabPrev, cx);
2990 assert_text_with_selections(
2991 &mut editor,
2992 indoc! {"
2993 «aˇ» = 1
2994 b = 2
2995
2996 «const c:ˇ» usize = 3;
2997 "},
2998 cx,
2999 );
3000
3001 editor
3002 });
3003}
3004
3005#[gpui::test]
3006async fn test_backspace(cx: &mut gpui::TestAppContext) {
3007 init_test(cx, |_| {});
3008
3009 let mut cx = EditorTestContext::new(cx).await;
3010
3011 // Basic backspace
3012 cx.set_state(indoc! {"
3013 onˇe two three
3014 fou«rˇ» five six
3015 seven «ˇeight nine
3016 »ten
3017 "});
3018 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3019 cx.assert_editor_state(indoc! {"
3020 oˇe two three
3021 fouˇ five six
3022 seven ˇten
3023 "});
3024
3025 // Test backspace inside and around indents
3026 cx.set_state(indoc! {"
3027 zero
3028 ˇone
3029 ˇtwo
3030 ˇ ˇ ˇ three
3031 ˇ ˇ four
3032 "});
3033 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3034 cx.assert_editor_state(indoc! {"
3035 zero
3036 ˇone
3037 ˇtwo
3038 ˇ threeˇ four
3039 "});
3040
3041 // Test backspace with line_mode set to true
3042 cx.update_editor(|e, _| e.selections.line_mode = true);
3043 cx.set_state(indoc! {"
3044 The ˇquick ˇbrown
3045 fox jumps over
3046 the lazy dog
3047 ˇThe qu«ick bˇ»rown"});
3048 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3049 cx.assert_editor_state(indoc! {"
3050 ˇfox jumps over
3051 the lazy dogˇ"});
3052}
3053
3054#[gpui::test]
3055async fn test_delete(cx: &mut gpui::TestAppContext) {
3056 init_test(cx, |_| {});
3057
3058 let mut cx = EditorTestContext::new(cx).await;
3059 cx.set_state(indoc! {"
3060 onˇe two three
3061 fou«rˇ» five six
3062 seven «ˇeight nine
3063 »ten
3064 "});
3065 cx.update_editor(|e, cx| e.delete(&Delete, cx));
3066 cx.assert_editor_state(indoc! {"
3067 onˇ two three
3068 fouˇ five six
3069 seven ˇten
3070 "});
3071
3072 // Test backspace with line_mode set to true
3073 cx.update_editor(|e, _| e.selections.line_mode = true);
3074 cx.set_state(indoc! {"
3075 The ˇquick ˇbrown
3076 fox «ˇjum»ps over
3077 the lazy dog
3078 ˇThe qu«ick bˇ»rown"});
3079 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3080 cx.assert_editor_state("ˇthe lazy dogˇ");
3081}
3082
3083#[gpui::test]
3084fn test_delete_line(cx: &mut TestAppContext) {
3085 init_test(cx, |_| {});
3086
3087 let view = cx.add_window(|cx| {
3088 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3089 build_editor(buffer, cx)
3090 });
3091 _ = view.update(cx, |view, cx| {
3092 view.change_selections(None, cx, |s| {
3093 s.select_display_ranges([
3094 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3095 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3096 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3097 ])
3098 });
3099 view.delete_line(&DeleteLine, cx);
3100 assert_eq!(view.display_text(cx), "ghi");
3101 assert_eq!(
3102 view.selections.display_ranges(cx),
3103 vec![
3104 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3105 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3106 ]
3107 );
3108 });
3109
3110 let view = cx.add_window(|cx| {
3111 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3112 build_editor(buffer, cx)
3113 });
3114 _ = view.update(cx, |view, cx| {
3115 view.change_selections(None, cx, |s| {
3116 s.select_display_ranges([
3117 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3118 ])
3119 });
3120 view.delete_line(&DeleteLine, cx);
3121 assert_eq!(view.display_text(cx), "ghi\n");
3122 assert_eq!(
3123 view.selections.display_ranges(cx),
3124 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3125 );
3126 });
3127}
3128
3129#[gpui::test]
3130fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3131 init_test(cx, |_| {});
3132
3133 cx.add_window(|cx| {
3134 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3135 let mut editor = build_editor(buffer.clone(), cx);
3136 let buffer = buffer.read(cx).as_singleton().unwrap();
3137
3138 assert_eq!(
3139 editor.selections.ranges::<Point>(cx),
3140 &[Point::new(0, 0)..Point::new(0, 0)]
3141 );
3142
3143 // When on single line, replace newline at end by space
3144 editor.join_lines(&JoinLines, cx);
3145 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3146 assert_eq!(
3147 editor.selections.ranges::<Point>(cx),
3148 &[Point::new(0, 3)..Point::new(0, 3)]
3149 );
3150
3151 // When multiple lines are selected, remove newlines that are spanned by the selection
3152 editor.change_selections(None, cx, |s| {
3153 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3154 });
3155 editor.join_lines(&JoinLines, cx);
3156 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3157 assert_eq!(
3158 editor.selections.ranges::<Point>(cx),
3159 &[Point::new(0, 11)..Point::new(0, 11)]
3160 );
3161
3162 // Undo should be transactional
3163 editor.undo(&Undo, cx);
3164 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3165 assert_eq!(
3166 editor.selections.ranges::<Point>(cx),
3167 &[Point::new(0, 5)..Point::new(2, 2)]
3168 );
3169
3170 // When joining an empty line don't insert a space
3171 editor.change_selections(None, cx, |s| {
3172 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3173 });
3174 editor.join_lines(&JoinLines, cx);
3175 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3176 assert_eq!(
3177 editor.selections.ranges::<Point>(cx),
3178 [Point::new(2, 3)..Point::new(2, 3)]
3179 );
3180
3181 // We can remove trailing newlines
3182 editor.join_lines(&JoinLines, cx);
3183 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3184 assert_eq!(
3185 editor.selections.ranges::<Point>(cx),
3186 [Point::new(2, 3)..Point::new(2, 3)]
3187 );
3188
3189 // We don't blow up on the last line
3190 editor.join_lines(&JoinLines, cx);
3191 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3192 assert_eq!(
3193 editor.selections.ranges::<Point>(cx),
3194 [Point::new(2, 3)..Point::new(2, 3)]
3195 );
3196
3197 // reset to test indentation
3198 editor.buffer.update(cx, |buffer, cx| {
3199 buffer.edit(
3200 [
3201 (Point::new(1, 0)..Point::new(1, 2), " "),
3202 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3203 ],
3204 None,
3205 cx,
3206 )
3207 });
3208
3209 // We remove any leading spaces
3210 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3211 editor.change_selections(None, cx, |s| {
3212 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3213 });
3214 editor.join_lines(&JoinLines, cx);
3215 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3216
3217 // We don't insert a space for a line containing only spaces
3218 editor.join_lines(&JoinLines, cx);
3219 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3220
3221 // We ignore any leading tabs
3222 editor.join_lines(&JoinLines, cx);
3223 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3224
3225 editor
3226 });
3227}
3228
3229#[gpui::test]
3230fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3231 init_test(cx, |_| {});
3232
3233 cx.add_window(|cx| {
3234 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3235 let mut editor = build_editor(buffer.clone(), cx);
3236 let buffer = buffer.read(cx).as_singleton().unwrap();
3237
3238 editor.change_selections(None, cx, |s| {
3239 s.select_ranges([
3240 Point::new(0, 2)..Point::new(1, 1),
3241 Point::new(1, 2)..Point::new(1, 2),
3242 Point::new(3, 1)..Point::new(3, 2),
3243 ])
3244 });
3245
3246 editor.join_lines(&JoinLines, cx);
3247 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3248
3249 assert_eq!(
3250 editor.selections.ranges::<Point>(cx),
3251 [
3252 Point::new(0, 7)..Point::new(0, 7),
3253 Point::new(1, 3)..Point::new(1, 3)
3254 ]
3255 );
3256 editor
3257 });
3258}
3259
3260#[gpui::test]
3261async fn test_join_lines_with_git_diff_base(
3262 executor: BackgroundExecutor,
3263 cx: &mut gpui::TestAppContext,
3264) {
3265 init_test(cx, |_| {});
3266
3267 let mut cx = EditorTestContext::new(cx).await;
3268
3269 let diff_base = r#"
3270 Line 0
3271 Line 1
3272 Line 2
3273 Line 3
3274 "#
3275 .unindent();
3276
3277 cx.set_state(
3278 &r#"
3279 ˇLine 0
3280 Line 1
3281 Line 2
3282 Line 3
3283 "#
3284 .unindent(),
3285 );
3286
3287 cx.set_diff_base(Some(&diff_base));
3288 executor.run_until_parked();
3289
3290 // Join lines
3291 cx.update_editor(|editor, cx| {
3292 editor.join_lines(&JoinLines, cx);
3293 });
3294 executor.run_until_parked();
3295
3296 cx.assert_editor_state(
3297 &r#"
3298 Line 0ˇ Line 1
3299 Line 2
3300 Line 3
3301 "#
3302 .unindent(),
3303 );
3304 // Join again
3305 cx.update_editor(|editor, cx| {
3306 editor.join_lines(&JoinLines, cx);
3307 });
3308 executor.run_until_parked();
3309
3310 cx.assert_editor_state(
3311 &r#"
3312 Line 0 Line 1ˇ Line 2
3313 Line 3
3314 "#
3315 .unindent(),
3316 );
3317}
3318
3319#[gpui::test]
3320async fn test_custom_newlines_cause_no_false_positive_diffs(
3321 executor: BackgroundExecutor,
3322 cx: &mut gpui::TestAppContext,
3323) {
3324 init_test(cx, |_| {});
3325 let mut cx = EditorTestContext::new(cx).await;
3326 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3327 cx.set_diff_base(Some("Line 0\r\nLine 1\r\nLine 2\r\nLine 3"));
3328 executor.run_until_parked();
3329
3330 cx.update_editor(|editor, cx| {
3331 assert_eq!(
3332 editor
3333 .buffer()
3334 .read(cx)
3335 .snapshot(cx)
3336 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
3337 .collect::<Vec<_>>(),
3338 Vec::new(),
3339 "Should not have any diffs for files with custom newlines"
3340 );
3341 });
3342}
3343
3344#[gpui::test]
3345async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3346 init_test(cx, |_| {});
3347
3348 let mut cx = EditorTestContext::new(cx).await;
3349
3350 // Test sort_lines_case_insensitive()
3351 cx.set_state(indoc! {"
3352 «z
3353 y
3354 x
3355 Z
3356 Y
3357 Xˇ»
3358 "});
3359 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3360 cx.assert_editor_state(indoc! {"
3361 «x
3362 X
3363 y
3364 Y
3365 z
3366 Zˇ»
3367 "});
3368
3369 // Test reverse_lines()
3370 cx.set_state(indoc! {"
3371 «5
3372 4
3373 3
3374 2
3375 1ˇ»
3376 "});
3377 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3378 cx.assert_editor_state(indoc! {"
3379 «1
3380 2
3381 3
3382 4
3383 5ˇ»
3384 "});
3385
3386 // Skip testing shuffle_line()
3387
3388 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3389 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3390
3391 // Don't manipulate when cursor is on single line, but expand the selection
3392 cx.set_state(indoc! {"
3393 ddˇdd
3394 ccc
3395 bb
3396 a
3397 "});
3398 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3399 cx.assert_editor_state(indoc! {"
3400 «ddddˇ»
3401 ccc
3402 bb
3403 a
3404 "});
3405
3406 // Basic manipulate case
3407 // Start selection moves to column 0
3408 // End of selection shrinks to fit shorter line
3409 cx.set_state(indoc! {"
3410 dd«d
3411 ccc
3412 bb
3413 aaaaaˇ»
3414 "});
3415 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3416 cx.assert_editor_state(indoc! {"
3417 «aaaaa
3418 bb
3419 ccc
3420 dddˇ»
3421 "});
3422
3423 // Manipulate case with newlines
3424 cx.set_state(indoc! {"
3425 dd«d
3426 ccc
3427
3428 bb
3429 aaaaa
3430
3431 ˇ»
3432 "});
3433 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3434 cx.assert_editor_state(indoc! {"
3435 «
3436
3437 aaaaa
3438 bb
3439 ccc
3440 dddˇ»
3441
3442 "});
3443
3444 // Adding new line
3445 cx.set_state(indoc! {"
3446 aa«a
3447 bbˇ»b
3448 "});
3449 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3450 cx.assert_editor_state(indoc! {"
3451 «aaa
3452 bbb
3453 added_lineˇ»
3454 "});
3455
3456 // Removing line
3457 cx.set_state(indoc! {"
3458 aa«a
3459 bbbˇ»
3460 "});
3461 cx.update_editor(|e, cx| {
3462 e.manipulate_lines(cx, |lines| {
3463 lines.pop();
3464 })
3465 });
3466 cx.assert_editor_state(indoc! {"
3467 «aaaˇ»
3468 "});
3469
3470 // Removing all lines
3471 cx.set_state(indoc! {"
3472 aa«a
3473 bbbˇ»
3474 "});
3475 cx.update_editor(|e, cx| {
3476 e.manipulate_lines(cx, |lines| {
3477 lines.drain(..);
3478 })
3479 });
3480 cx.assert_editor_state(indoc! {"
3481 ˇ
3482 "});
3483}
3484
3485#[gpui::test]
3486async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3487 init_test(cx, |_| {});
3488
3489 let mut cx = EditorTestContext::new(cx).await;
3490
3491 // Consider continuous selection as single selection
3492 cx.set_state(indoc! {"
3493 Aaa«aa
3494 cˇ»c«c
3495 bb
3496 aaaˇ»aa
3497 "});
3498 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3499 cx.assert_editor_state(indoc! {"
3500 «Aaaaa
3501 ccc
3502 bb
3503 aaaaaˇ»
3504 "});
3505
3506 cx.set_state(indoc! {"
3507 Aaa«aa
3508 cˇ»c«c
3509 bb
3510 aaaˇ»aa
3511 "});
3512 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3513 cx.assert_editor_state(indoc! {"
3514 «Aaaaa
3515 ccc
3516 bbˇ»
3517 "});
3518
3519 // Consider non continuous selection as distinct dedup operations
3520 cx.set_state(indoc! {"
3521 «aaaaa
3522 bb
3523 aaaaa
3524 aaaaaˇ»
3525
3526 aaa«aaˇ»
3527 "});
3528 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3529 cx.assert_editor_state(indoc! {"
3530 «aaaaa
3531 bbˇ»
3532
3533 «aaaaaˇ»
3534 "});
3535}
3536
3537#[gpui::test]
3538async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3539 init_test(cx, |_| {});
3540
3541 let mut cx = EditorTestContext::new(cx).await;
3542
3543 cx.set_state(indoc! {"
3544 «Aaa
3545 aAa
3546 Aaaˇ»
3547 "});
3548 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3549 cx.assert_editor_state(indoc! {"
3550 «Aaa
3551 aAaˇ»
3552 "});
3553
3554 cx.set_state(indoc! {"
3555 «Aaa
3556 aAa
3557 aaAˇ»
3558 "});
3559 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3560 cx.assert_editor_state(indoc! {"
3561 «Aaaˇ»
3562 "});
3563}
3564
3565#[gpui::test]
3566async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3567 init_test(cx, |_| {});
3568
3569 let mut cx = EditorTestContext::new(cx).await;
3570
3571 // Manipulate with multiple selections on a single line
3572 cx.set_state(indoc! {"
3573 dd«dd
3574 cˇ»c«c
3575 bb
3576 aaaˇ»aa
3577 "});
3578 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3579 cx.assert_editor_state(indoc! {"
3580 «aaaaa
3581 bb
3582 ccc
3583 ddddˇ»
3584 "});
3585
3586 // Manipulate with multiple disjoin selections
3587 cx.set_state(indoc! {"
3588 5«
3589 4
3590 3
3591 2
3592 1ˇ»
3593
3594 dd«dd
3595 ccc
3596 bb
3597 aaaˇ»aa
3598 "});
3599 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3600 cx.assert_editor_state(indoc! {"
3601 «1
3602 2
3603 3
3604 4
3605 5ˇ»
3606
3607 «aaaaa
3608 bb
3609 ccc
3610 ddddˇ»
3611 "});
3612
3613 // Adding lines on each selection
3614 cx.set_state(indoc! {"
3615 2«
3616 1ˇ»
3617
3618 bb«bb
3619 aaaˇ»aa
3620 "});
3621 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3622 cx.assert_editor_state(indoc! {"
3623 «2
3624 1
3625 added lineˇ»
3626
3627 «bbbb
3628 aaaaa
3629 added lineˇ»
3630 "});
3631
3632 // Removing lines on each selection
3633 cx.set_state(indoc! {"
3634 2«
3635 1ˇ»
3636
3637 bb«bb
3638 aaaˇ»aa
3639 "});
3640 cx.update_editor(|e, cx| {
3641 e.manipulate_lines(cx, |lines| {
3642 lines.pop();
3643 })
3644 });
3645 cx.assert_editor_state(indoc! {"
3646 «2ˇ»
3647
3648 «bbbbˇ»
3649 "});
3650}
3651
3652#[gpui::test]
3653async fn test_manipulate_text(cx: &mut TestAppContext) {
3654 init_test(cx, |_| {});
3655
3656 let mut cx = EditorTestContext::new(cx).await;
3657
3658 // Test convert_to_upper_case()
3659 cx.set_state(indoc! {"
3660 «hello worldˇ»
3661 "});
3662 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3663 cx.assert_editor_state(indoc! {"
3664 «HELLO WORLDˇ»
3665 "});
3666
3667 // Test convert_to_lower_case()
3668 cx.set_state(indoc! {"
3669 «HELLO WORLDˇ»
3670 "});
3671 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3672 cx.assert_editor_state(indoc! {"
3673 «hello worldˇ»
3674 "});
3675
3676 // Test multiple line, single selection case
3677 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3678 cx.set_state(indoc! {"
3679 «The quick brown
3680 fox jumps over
3681 the lazy dogˇ»
3682 "});
3683 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3684 cx.assert_editor_state(indoc! {"
3685 «The Quick Brown
3686 Fox Jumps Over
3687 The Lazy Dogˇ»
3688 "});
3689
3690 // Test multiple line, single selection case
3691 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3692 cx.set_state(indoc! {"
3693 «The quick brown
3694 fox jumps over
3695 the lazy dogˇ»
3696 "});
3697 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3698 cx.assert_editor_state(indoc! {"
3699 «TheQuickBrown
3700 FoxJumpsOver
3701 TheLazyDogˇ»
3702 "});
3703
3704 // From here on out, test more complex cases of manipulate_text()
3705
3706 // Test no selection case - should affect words cursors are in
3707 // Cursor at beginning, middle, and end of word
3708 cx.set_state(indoc! {"
3709 ˇhello big beauˇtiful worldˇ
3710 "});
3711 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3712 cx.assert_editor_state(indoc! {"
3713 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3714 "});
3715
3716 // Test multiple selections on a single line and across multiple lines
3717 cx.set_state(indoc! {"
3718 «Theˇ» quick «brown
3719 foxˇ» jumps «overˇ»
3720 the «lazyˇ» dog
3721 "});
3722 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3723 cx.assert_editor_state(indoc! {"
3724 «THEˇ» quick «BROWN
3725 FOXˇ» jumps «OVERˇ»
3726 the «LAZYˇ» dog
3727 "});
3728
3729 // Test case where text length grows
3730 cx.set_state(indoc! {"
3731 «tschüߡ»
3732 "});
3733 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3734 cx.assert_editor_state(indoc! {"
3735 «TSCHÜSSˇ»
3736 "});
3737
3738 // Test to make sure we don't crash when text shrinks
3739 cx.set_state(indoc! {"
3740 aaa_bbbˇ
3741 "});
3742 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3743 cx.assert_editor_state(indoc! {"
3744 «aaaBbbˇ»
3745 "});
3746
3747 // Test to make sure we all aware of the fact that each word can grow and shrink
3748 // Final selections should be aware of this fact
3749 cx.set_state(indoc! {"
3750 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3751 "});
3752 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3753 cx.assert_editor_state(indoc! {"
3754 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3755 "});
3756
3757 cx.set_state(indoc! {"
3758 «hElLo, WoRld!ˇ»
3759 "});
3760 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3761 cx.assert_editor_state(indoc! {"
3762 «HeLlO, wOrLD!ˇ»
3763 "});
3764}
3765
3766#[gpui::test]
3767fn test_duplicate_line(cx: &mut TestAppContext) {
3768 init_test(cx, |_| {});
3769
3770 let view = cx.add_window(|cx| {
3771 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3772 build_editor(buffer, cx)
3773 });
3774 _ = view.update(cx, |view, cx| {
3775 view.change_selections(None, cx, |s| {
3776 s.select_display_ranges([
3777 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3778 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3779 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3780 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3781 ])
3782 });
3783 view.duplicate_line_down(&DuplicateLineDown, cx);
3784 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3785 assert_eq!(
3786 view.selections.display_ranges(cx),
3787 vec![
3788 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3789 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3790 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3791 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3792 ]
3793 );
3794 });
3795
3796 let view = cx.add_window(|cx| {
3797 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3798 build_editor(buffer, cx)
3799 });
3800 _ = view.update(cx, |view, cx| {
3801 view.change_selections(None, cx, |s| {
3802 s.select_display_ranges([
3803 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3804 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3805 ])
3806 });
3807 view.duplicate_line_down(&DuplicateLineDown, cx);
3808 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3809 assert_eq!(
3810 view.selections.display_ranges(cx),
3811 vec![
3812 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3813 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3814 ]
3815 );
3816 });
3817
3818 // With `move_upwards` the selections stay in place, except for
3819 // the lines inserted above them
3820 let view = cx.add_window(|cx| {
3821 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3822 build_editor(buffer, cx)
3823 });
3824 _ = view.update(cx, |view, cx| {
3825 view.change_selections(None, cx, |s| {
3826 s.select_display_ranges([
3827 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3828 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3829 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3830 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3831 ])
3832 });
3833 view.duplicate_line_up(&DuplicateLineUp, cx);
3834 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3835 assert_eq!(
3836 view.selections.display_ranges(cx),
3837 vec![
3838 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3839 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3840 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3841 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3842 ]
3843 );
3844 });
3845
3846 let view = cx.add_window(|cx| {
3847 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3848 build_editor(buffer, cx)
3849 });
3850 _ = view.update(cx, |view, cx| {
3851 view.change_selections(None, cx, |s| {
3852 s.select_display_ranges([
3853 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3854 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3855 ])
3856 });
3857 view.duplicate_line_up(&DuplicateLineUp, cx);
3858 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3859 assert_eq!(
3860 view.selections.display_ranges(cx),
3861 vec![
3862 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3863 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3864 ]
3865 );
3866 });
3867}
3868
3869#[gpui::test]
3870fn test_move_line_up_down(cx: &mut TestAppContext) {
3871 init_test(cx, |_| {});
3872
3873 let view = cx.add_window(|cx| {
3874 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3875 build_editor(buffer, cx)
3876 });
3877 _ = view.update(cx, |view, cx| {
3878 view.fold_ranges(
3879 vec![
3880 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3881 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3882 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3883 ],
3884 true,
3885 cx,
3886 );
3887 view.change_selections(None, cx, |s| {
3888 s.select_display_ranges([
3889 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3890 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3891 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3892 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3893 ])
3894 });
3895 assert_eq!(
3896 view.display_text(cx),
3897 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3898 );
3899
3900 view.move_line_up(&MoveLineUp, cx);
3901 assert_eq!(
3902 view.display_text(cx),
3903 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3904 );
3905 assert_eq!(
3906 view.selections.display_ranges(cx),
3907 vec![
3908 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3909 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3910 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3911 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3912 ]
3913 );
3914 });
3915
3916 _ = view.update(cx, |view, cx| {
3917 view.move_line_down(&MoveLineDown, cx);
3918 assert_eq!(
3919 view.display_text(cx),
3920 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3921 );
3922 assert_eq!(
3923 view.selections.display_ranges(cx),
3924 vec![
3925 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3926 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3927 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3928 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3929 ]
3930 );
3931 });
3932
3933 _ = view.update(cx, |view, cx| {
3934 view.move_line_down(&MoveLineDown, cx);
3935 assert_eq!(
3936 view.display_text(cx),
3937 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3938 );
3939 assert_eq!(
3940 view.selections.display_ranges(cx),
3941 vec![
3942 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3943 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3944 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3945 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3946 ]
3947 );
3948 });
3949
3950 _ = view.update(cx, |view, cx| {
3951 view.move_line_up(&MoveLineUp, cx);
3952 assert_eq!(
3953 view.display_text(cx),
3954 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3955 );
3956 assert_eq!(
3957 view.selections.display_ranges(cx),
3958 vec![
3959 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3960 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3961 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3962 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3963 ]
3964 );
3965 });
3966}
3967
3968#[gpui::test]
3969fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3970 init_test(cx, |_| {});
3971
3972 let editor = cx.add_window(|cx| {
3973 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3974 build_editor(buffer, cx)
3975 });
3976 _ = editor.update(cx, |editor, cx| {
3977 let snapshot = editor.buffer.read(cx).snapshot(cx);
3978 editor.insert_blocks(
3979 [BlockProperties {
3980 style: BlockStyle::Fixed,
3981 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
3982 height: 1,
3983 render: Box::new(|_| div().into_any()),
3984 priority: 0,
3985 }],
3986 Some(Autoscroll::fit()),
3987 cx,
3988 );
3989 editor.change_selections(None, cx, |s| {
3990 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
3991 });
3992 editor.move_line_down(&MoveLineDown, cx);
3993 });
3994}
3995
3996#[gpui::test]
3997async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
3998 init_test(cx, |_| {});
3999
4000 let mut cx = EditorTestContext::new(cx).await;
4001 cx.set_state(
4002 &"
4003 ˇzero
4004 one
4005 two
4006 three
4007 four
4008 five
4009 "
4010 .unindent(),
4011 );
4012
4013 // Create a four-line block that replaces three lines of text.
4014 cx.update_editor(|editor, cx| {
4015 let snapshot = editor.snapshot(cx);
4016 let snapshot = &snapshot.buffer_snapshot;
4017 let placement = BlockPlacement::Replace(
4018 snapshot.anchor_after(Point::new(1, 0))..snapshot.anchor_after(Point::new(3, 0)),
4019 );
4020 editor.insert_blocks(
4021 [BlockProperties {
4022 placement,
4023 height: 4,
4024 style: BlockStyle::Sticky,
4025 render: Box::new(|_| gpui::div().into_any_element()),
4026 priority: 0,
4027 }],
4028 None,
4029 cx,
4030 );
4031 });
4032
4033 // Move down so that the cursor touches the block.
4034 cx.update_editor(|editor, cx| {
4035 editor.move_down(&Default::default(), cx);
4036 });
4037 cx.assert_editor_state(
4038 &"
4039 zero
4040 «one
4041 two
4042 threeˇ»
4043 four
4044 five
4045 "
4046 .unindent(),
4047 );
4048
4049 // Move down past the block.
4050 cx.update_editor(|editor, cx| {
4051 editor.move_down(&Default::default(), cx);
4052 });
4053 cx.assert_editor_state(
4054 &"
4055 zero
4056 one
4057 two
4058 three
4059 ˇfour
4060 five
4061 "
4062 .unindent(),
4063 );
4064}
4065
4066#[gpui::test]
4067fn test_transpose(cx: &mut TestAppContext) {
4068 init_test(cx, |_| {});
4069
4070 _ = cx.add_window(|cx| {
4071 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
4072 editor.set_style(EditorStyle::default(), cx);
4073 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
4074 editor.transpose(&Default::default(), cx);
4075 assert_eq!(editor.text(cx), "bac");
4076 assert_eq!(editor.selections.ranges(cx), [2..2]);
4077
4078 editor.transpose(&Default::default(), cx);
4079 assert_eq!(editor.text(cx), "bca");
4080 assert_eq!(editor.selections.ranges(cx), [3..3]);
4081
4082 editor.transpose(&Default::default(), cx);
4083 assert_eq!(editor.text(cx), "bac");
4084 assert_eq!(editor.selections.ranges(cx), [3..3]);
4085
4086 editor
4087 });
4088
4089 _ = cx.add_window(|cx| {
4090 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4091 editor.set_style(EditorStyle::default(), cx);
4092 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
4093 editor.transpose(&Default::default(), cx);
4094 assert_eq!(editor.text(cx), "acb\nde");
4095 assert_eq!(editor.selections.ranges(cx), [3..3]);
4096
4097 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4098 editor.transpose(&Default::default(), cx);
4099 assert_eq!(editor.text(cx), "acbd\ne");
4100 assert_eq!(editor.selections.ranges(cx), [5..5]);
4101
4102 editor.transpose(&Default::default(), cx);
4103 assert_eq!(editor.text(cx), "acbde\n");
4104 assert_eq!(editor.selections.ranges(cx), [6..6]);
4105
4106 editor.transpose(&Default::default(), cx);
4107 assert_eq!(editor.text(cx), "acbd\ne");
4108 assert_eq!(editor.selections.ranges(cx), [6..6]);
4109
4110 editor
4111 });
4112
4113 _ = cx.add_window(|cx| {
4114 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4115 editor.set_style(EditorStyle::default(), cx);
4116 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4117 editor.transpose(&Default::default(), cx);
4118 assert_eq!(editor.text(cx), "bacd\ne");
4119 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4120
4121 editor.transpose(&Default::default(), cx);
4122 assert_eq!(editor.text(cx), "bcade\n");
4123 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4124
4125 editor.transpose(&Default::default(), cx);
4126 assert_eq!(editor.text(cx), "bcda\ne");
4127 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4128
4129 editor.transpose(&Default::default(), cx);
4130 assert_eq!(editor.text(cx), "bcade\n");
4131 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4132
4133 editor.transpose(&Default::default(), cx);
4134 assert_eq!(editor.text(cx), "bcaed\n");
4135 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4136
4137 editor
4138 });
4139
4140 _ = cx.add_window(|cx| {
4141 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
4142 editor.set_style(EditorStyle::default(), cx);
4143 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4144 editor.transpose(&Default::default(), cx);
4145 assert_eq!(editor.text(cx), "🏀🍐✋");
4146 assert_eq!(editor.selections.ranges(cx), [8..8]);
4147
4148 editor.transpose(&Default::default(), cx);
4149 assert_eq!(editor.text(cx), "🏀✋🍐");
4150 assert_eq!(editor.selections.ranges(cx), [11..11]);
4151
4152 editor.transpose(&Default::default(), cx);
4153 assert_eq!(editor.text(cx), "🏀🍐✋");
4154 assert_eq!(editor.selections.ranges(cx), [11..11]);
4155
4156 editor
4157 });
4158}
4159
4160#[gpui::test]
4161async fn test_rewrap(cx: &mut TestAppContext) {
4162 init_test(cx, |_| {});
4163
4164 let mut cx = EditorTestContext::new(cx).await;
4165
4166 {
4167 let language = Arc::new(Language::new(
4168 LanguageConfig {
4169 line_comments: vec!["// ".into()],
4170 ..LanguageConfig::default()
4171 },
4172 None,
4173 ));
4174 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4175
4176 let unwrapped_text = indoc! {"
4177 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4178 "};
4179
4180 let wrapped_text = indoc! {"
4181 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4182 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4183 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4184 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4185 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4186 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4187 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4188 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4189 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4190 // porttitor id. Aliquam id accumsan eros.ˇ
4191 "};
4192
4193 cx.set_state(unwrapped_text);
4194 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4195 cx.assert_editor_state(wrapped_text);
4196 }
4197
4198 // Test that rewrapping works inside of a selection
4199 {
4200 let language = Arc::new(Language::new(
4201 LanguageConfig {
4202 line_comments: vec!["// ".into()],
4203 ..LanguageConfig::default()
4204 },
4205 None,
4206 ));
4207 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4208
4209 let unwrapped_text = indoc! {"
4210 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ»
4211 "};
4212
4213 let wrapped_text = indoc! {"
4214 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4215 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4216 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4217 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4218 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4219 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4220 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4221 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4222 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4223 // porttitor id. Aliquam id accumsan eros.ˇ
4224 "};
4225
4226 cx.set_state(unwrapped_text);
4227 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4228 cx.assert_editor_state(wrapped_text);
4229 }
4230
4231 // Test that cursors that expand to the same region are collapsed.
4232 {
4233 let language = Arc::new(Language::new(
4234 LanguageConfig {
4235 line_comments: vec!["// ".into()],
4236 ..LanguageConfig::default()
4237 },
4238 None,
4239 ));
4240 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4241
4242 let unwrapped_text = indoc! {"
4243 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4244 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4245 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4246 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4247 "};
4248
4249 let wrapped_text = indoc! {"
4250 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4251 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4252 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4253 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4254 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4255 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4256 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4257 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4258 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4259 // porttitor id. Aliquam id accumsan eros.ˇ
4260 "};
4261
4262 cx.set_state(unwrapped_text);
4263 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4264 cx.assert_editor_state(wrapped_text);
4265 }
4266
4267 // Test that non-contiguous selections are treated separately.
4268 {
4269 let language = Arc::new(Language::new(
4270 LanguageConfig {
4271 line_comments: vec!["// ".into()],
4272 ..LanguageConfig::default()
4273 },
4274 None,
4275 ));
4276 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4277
4278 let unwrapped_text = indoc! {"
4279 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4280 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4281 //
4282 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4283 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4284 "};
4285
4286 let wrapped_text = indoc! {"
4287 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4288 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4289 // auctor, eu lacinia sapien scelerisque.ˇ
4290 //
4291 // Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4292 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4293 // blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4294 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4295 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4296 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4297 // vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ
4298 "};
4299
4300 cx.set_state(unwrapped_text);
4301 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4302 cx.assert_editor_state(wrapped_text);
4303 }
4304
4305 // Test that different comment prefixes are supported.
4306 {
4307 let language = Arc::new(Language::new(
4308 LanguageConfig {
4309 line_comments: vec!["# ".into()],
4310 ..LanguageConfig::default()
4311 },
4312 None,
4313 ));
4314 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4315
4316 let unwrapped_text = indoc! {"
4317 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4318 "};
4319
4320 let wrapped_text = indoc! {"
4321 # Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4322 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4323 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4324 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4325 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4326 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4327 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4328 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4329 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4330 # accumsan eros.ˇ
4331 "};
4332
4333 cx.set_state(unwrapped_text);
4334 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4335 cx.assert_editor_state(wrapped_text);
4336 }
4337
4338 // Test that rewrapping is ignored outside of comments in most languages.
4339 {
4340 let language = Arc::new(Language::new(
4341 LanguageConfig {
4342 line_comments: vec!["// ".into(), "/// ".into()],
4343 ..LanguageConfig::default()
4344 },
4345 Some(tree_sitter_rust::LANGUAGE.into()),
4346 ));
4347 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4348
4349 let unwrapped_text = indoc! {"
4350 /// Adds two numbers.
4351 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4352 fn add(a: u32, b: u32) -> u32 {
4353 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4354 }
4355 "};
4356
4357 let wrapped_text = indoc! {"
4358 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4359 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4360 fn add(a: u32, b: u32) -> u32 {
4361 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4362 }
4363 "};
4364
4365 cx.set_state(unwrapped_text);
4366 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4367 cx.assert_editor_state(wrapped_text);
4368 }
4369
4370 // Test that rewrapping works in Markdown and Plain Text languages.
4371 {
4372 let markdown_language = Arc::new(Language::new(
4373 LanguageConfig {
4374 name: "Markdown".into(),
4375 ..LanguageConfig::default()
4376 },
4377 None,
4378 ));
4379 cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
4380
4381 let unwrapped_text = indoc! {"
4382 # Hello
4383
4384 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4385 "};
4386
4387 let wrapped_text = indoc! {"
4388 # Hello
4389
4390 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4391 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4392 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4393 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4394 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4395 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4396 Integer sit amet scelerisque nisi.ˇ
4397 "};
4398
4399 cx.set_state(unwrapped_text);
4400 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4401 cx.assert_editor_state(wrapped_text);
4402
4403 let plaintext_language = Arc::new(Language::new(
4404 LanguageConfig {
4405 name: "Plain Text".into(),
4406 ..LanguageConfig::default()
4407 },
4408 None,
4409 ));
4410 cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
4411
4412 let unwrapped_text = indoc! {"
4413 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4414 "};
4415
4416 let wrapped_text = indoc! {"
4417 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4418 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4419 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4420 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4421 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4422 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4423 Integer sit amet scelerisque nisi.ˇ
4424 "};
4425
4426 cx.set_state(unwrapped_text);
4427 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4428 cx.assert_editor_state(wrapped_text);
4429 }
4430
4431 // Test rewrapping unaligned comments in a selection.
4432 {
4433 let language = Arc::new(Language::new(
4434 LanguageConfig {
4435 line_comments: vec!["// ".into(), "/// ".into()],
4436 ..LanguageConfig::default()
4437 },
4438 Some(tree_sitter_rust::LANGUAGE.into()),
4439 ));
4440 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4441
4442 let unwrapped_text = indoc! {"
4443 fn foo() {
4444 if true {
4445 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4446 // Praesent semper egestas tellus id dignissim.ˇ»
4447 do_something();
4448 } else {
4449 //
4450 }
4451 }
4452 "};
4453
4454 let wrapped_text = indoc! {"
4455 fn foo() {
4456 if true {
4457 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4458 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4459 // egestas tellus id dignissim.ˇ
4460 do_something();
4461 } else {
4462 //
4463 }
4464 }
4465 "};
4466
4467 cx.set_state(unwrapped_text);
4468 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4469 cx.assert_editor_state(wrapped_text);
4470
4471 let unwrapped_text = indoc! {"
4472 fn foo() {
4473 if true {
4474 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4475 // Praesent semper egestas tellus id dignissim.»
4476 do_something();
4477 } else {
4478 //
4479 }
4480
4481 }
4482 "};
4483
4484 let wrapped_text = indoc! {"
4485 fn foo() {
4486 if true {
4487 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4488 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4489 // egestas tellus id dignissim.ˇ
4490 do_something();
4491 } else {
4492 //
4493 }
4494
4495 }
4496 "};
4497
4498 cx.set_state(unwrapped_text);
4499 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4500 cx.assert_editor_state(wrapped_text);
4501 }
4502}
4503
4504#[gpui::test]
4505async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4506 init_test(cx, |_| {});
4507
4508 let mut cx = EditorTestContext::new(cx).await;
4509
4510 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4511 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4512 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4513
4514 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4515 cx.set_state("two ˇfour ˇsix ˇ");
4516 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4517 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4518
4519 // Paste again but with only two cursors. Since the number of cursors doesn't
4520 // match the number of slices in the clipboard, the entire clipboard text
4521 // is pasted at each cursor.
4522 cx.set_state("ˇtwo one✅ four three six five ˇ");
4523 cx.update_editor(|e, cx| {
4524 e.handle_input("( ", cx);
4525 e.paste(&Paste, cx);
4526 e.handle_input(") ", cx);
4527 });
4528 cx.assert_editor_state(
4529 &([
4530 "( one✅ ",
4531 "three ",
4532 "five ) ˇtwo one✅ four three six five ( one✅ ",
4533 "three ",
4534 "five ) ˇ",
4535 ]
4536 .join("\n")),
4537 );
4538
4539 // Cut with three selections, one of which is full-line.
4540 cx.set_state(indoc! {"
4541 1«2ˇ»3
4542 4ˇ567
4543 «8ˇ»9"});
4544 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4545 cx.assert_editor_state(indoc! {"
4546 1ˇ3
4547 ˇ9"});
4548
4549 // Paste with three selections, noticing how the copied selection that was full-line
4550 // gets inserted before the second cursor.
4551 cx.set_state(indoc! {"
4552 1ˇ3
4553 9ˇ
4554 «oˇ»ne"});
4555 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4556 cx.assert_editor_state(indoc! {"
4557 12ˇ3
4558 4567
4559 9ˇ
4560 8ˇne"});
4561
4562 // Copy with a single cursor only, which writes the whole line into the clipboard.
4563 cx.set_state(indoc! {"
4564 The quick brown
4565 fox juˇmps over
4566 the lazy dog"});
4567 cx.update_editor(|e, cx| e.copy(&Copy, cx));
4568 assert_eq!(
4569 cx.read_from_clipboard()
4570 .and_then(|item| item.text().as_deref().map(str::to_string)),
4571 Some("fox jumps over\n".to_string())
4572 );
4573
4574 // Paste with three selections, noticing how the copied full-line selection is inserted
4575 // before the empty selections but replaces the selection that is non-empty.
4576 cx.set_state(indoc! {"
4577 Tˇhe quick brown
4578 «foˇ»x jumps over
4579 tˇhe lazy dog"});
4580 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4581 cx.assert_editor_state(indoc! {"
4582 fox jumps over
4583 Tˇhe quick brown
4584 fox jumps over
4585 ˇx jumps over
4586 fox jumps over
4587 tˇhe lazy dog"});
4588}
4589
4590#[gpui::test]
4591async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4592 init_test(cx, |_| {});
4593
4594 let mut cx = EditorTestContext::new(cx).await;
4595 let language = Arc::new(Language::new(
4596 LanguageConfig::default(),
4597 Some(tree_sitter_rust::LANGUAGE.into()),
4598 ));
4599 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4600
4601 // Cut an indented block, without the leading whitespace.
4602 cx.set_state(indoc! {"
4603 const a: B = (
4604 c(),
4605 «d(
4606 e,
4607 f
4608 )ˇ»
4609 );
4610 "});
4611 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4612 cx.assert_editor_state(indoc! {"
4613 const a: B = (
4614 c(),
4615 ˇ
4616 );
4617 "});
4618
4619 // Paste it at the same position.
4620 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4621 cx.assert_editor_state(indoc! {"
4622 const a: B = (
4623 c(),
4624 d(
4625 e,
4626 f
4627 )ˇ
4628 );
4629 "});
4630
4631 // Paste it at a line with a lower indent level.
4632 cx.set_state(indoc! {"
4633 ˇ
4634 const a: B = (
4635 c(),
4636 );
4637 "});
4638 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4639 cx.assert_editor_state(indoc! {"
4640 d(
4641 e,
4642 f
4643 )ˇ
4644 const a: B = (
4645 c(),
4646 );
4647 "});
4648
4649 // Cut an indented block, with the leading whitespace.
4650 cx.set_state(indoc! {"
4651 const a: B = (
4652 c(),
4653 « d(
4654 e,
4655 f
4656 )
4657 ˇ»);
4658 "});
4659 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4660 cx.assert_editor_state(indoc! {"
4661 const a: B = (
4662 c(),
4663 ˇ);
4664 "});
4665
4666 // Paste it at the same position.
4667 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4668 cx.assert_editor_state(indoc! {"
4669 const a: B = (
4670 c(),
4671 d(
4672 e,
4673 f
4674 )
4675 ˇ);
4676 "});
4677
4678 // Paste it at a line with a higher indent level.
4679 cx.set_state(indoc! {"
4680 const a: B = (
4681 c(),
4682 d(
4683 e,
4684 fˇ
4685 )
4686 );
4687 "});
4688 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4689 cx.assert_editor_state(indoc! {"
4690 const a: B = (
4691 c(),
4692 d(
4693 e,
4694 f d(
4695 e,
4696 f
4697 )
4698 ˇ
4699 )
4700 );
4701 "});
4702}
4703
4704#[gpui::test]
4705fn test_select_all(cx: &mut TestAppContext) {
4706 init_test(cx, |_| {});
4707
4708 let view = cx.add_window(|cx| {
4709 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4710 build_editor(buffer, cx)
4711 });
4712 _ = view.update(cx, |view, cx| {
4713 view.select_all(&SelectAll, cx);
4714 assert_eq!(
4715 view.selections.display_ranges(cx),
4716 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4717 );
4718 });
4719}
4720
4721#[gpui::test]
4722fn test_select_line(cx: &mut TestAppContext) {
4723 init_test(cx, |_| {});
4724
4725 let view = cx.add_window(|cx| {
4726 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4727 build_editor(buffer, cx)
4728 });
4729 _ = view.update(cx, |view, cx| {
4730 view.change_selections(None, cx, |s| {
4731 s.select_display_ranges([
4732 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4733 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4734 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4735 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4736 ])
4737 });
4738 view.select_line(&SelectLine, cx);
4739 assert_eq!(
4740 view.selections.display_ranges(cx),
4741 vec![
4742 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4743 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4744 ]
4745 );
4746 });
4747
4748 _ = view.update(cx, |view, cx| {
4749 view.select_line(&SelectLine, cx);
4750 assert_eq!(
4751 view.selections.display_ranges(cx),
4752 vec![
4753 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4754 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4755 ]
4756 );
4757 });
4758
4759 _ = view.update(cx, |view, cx| {
4760 view.select_line(&SelectLine, cx);
4761 assert_eq!(
4762 view.selections.display_ranges(cx),
4763 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4764 );
4765 });
4766}
4767
4768#[gpui::test]
4769fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4770 init_test(cx, |_| {});
4771
4772 let view = cx.add_window(|cx| {
4773 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4774 build_editor(buffer, cx)
4775 });
4776 _ = view.update(cx, |view, cx| {
4777 view.fold_ranges(
4778 vec![
4779 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4780 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4781 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4782 ],
4783 true,
4784 cx,
4785 );
4786 view.change_selections(None, cx, |s| {
4787 s.select_display_ranges([
4788 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4789 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4790 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4791 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4792 ])
4793 });
4794 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4795 });
4796
4797 _ = view.update(cx, |view, cx| {
4798 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4799 assert_eq!(
4800 view.display_text(cx),
4801 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4802 );
4803 assert_eq!(
4804 view.selections.display_ranges(cx),
4805 [
4806 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4807 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4808 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4809 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4810 ]
4811 );
4812 });
4813
4814 _ = view.update(cx, |view, cx| {
4815 view.change_selections(None, cx, |s| {
4816 s.select_display_ranges([
4817 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4818 ])
4819 });
4820 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4821 assert_eq!(
4822 view.display_text(cx),
4823 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4824 );
4825 assert_eq!(
4826 view.selections.display_ranges(cx),
4827 [
4828 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4829 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4830 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4831 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4832 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4833 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4834 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4835 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4836 ]
4837 );
4838 });
4839}
4840
4841#[gpui::test]
4842async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4843 init_test(cx, |_| {});
4844
4845 let mut cx = EditorTestContext::new(cx).await;
4846
4847 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4848 cx.set_state(indoc!(
4849 r#"abc
4850 defˇghi
4851
4852 jk
4853 nlmo
4854 "#
4855 ));
4856
4857 cx.update_editor(|editor, cx| {
4858 editor.add_selection_above(&Default::default(), cx);
4859 });
4860
4861 cx.assert_editor_state(indoc!(
4862 r#"abcˇ
4863 defˇghi
4864
4865 jk
4866 nlmo
4867 "#
4868 ));
4869
4870 cx.update_editor(|editor, cx| {
4871 editor.add_selection_above(&Default::default(), cx);
4872 });
4873
4874 cx.assert_editor_state(indoc!(
4875 r#"abcˇ
4876 defˇghi
4877
4878 jk
4879 nlmo
4880 "#
4881 ));
4882
4883 cx.update_editor(|view, cx| {
4884 view.add_selection_below(&Default::default(), cx);
4885 });
4886
4887 cx.assert_editor_state(indoc!(
4888 r#"abc
4889 defˇghi
4890
4891 jk
4892 nlmo
4893 "#
4894 ));
4895
4896 cx.update_editor(|view, cx| {
4897 view.undo_selection(&Default::default(), cx);
4898 });
4899
4900 cx.assert_editor_state(indoc!(
4901 r#"abcˇ
4902 defˇghi
4903
4904 jk
4905 nlmo
4906 "#
4907 ));
4908
4909 cx.update_editor(|view, cx| {
4910 view.redo_selection(&Default::default(), cx);
4911 });
4912
4913 cx.assert_editor_state(indoc!(
4914 r#"abc
4915 defˇghi
4916
4917 jk
4918 nlmo
4919 "#
4920 ));
4921
4922 cx.update_editor(|view, cx| {
4923 view.add_selection_below(&Default::default(), cx);
4924 });
4925
4926 cx.assert_editor_state(indoc!(
4927 r#"abc
4928 defˇghi
4929
4930 jk
4931 nlmˇo
4932 "#
4933 ));
4934
4935 cx.update_editor(|view, cx| {
4936 view.add_selection_below(&Default::default(), cx);
4937 });
4938
4939 cx.assert_editor_state(indoc!(
4940 r#"abc
4941 defˇghi
4942
4943 jk
4944 nlmˇo
4945 "#
4946 ));
4947
4948 // change selections
4949 cx.set_state(indoc!(
4950 r#"abc
4951 def«ˇg»hi
4952
4953 jk
4954 nlmo
4955 "#
4956 ));
4957
4958 cx.update_editor(|view, cx| {
4959 view.add_selection_below(&Default::default(), cx);
4960 });
4961
4962 cx.assert_editor_state(indoc!(
4963 r#"abc
4964 def«ˇg»hi
4965
4966 jk
4967 nlm«ˇo»
4968 "#
4969 ));
4970
4971 cx.update_editor(|view, cx| {
4972 view.add_selection_below(&Default::default(), cx);
4973 });
4974
4975 cx.assert_editor_state(indoc!(
4976 r#"abc
4977 def«ˇg»hi
4978
4979 jk
4980 nlm«ˇo»
4981 "#
4982 ));
4983
4984 cx.update_editor(|view, cx| {
4985 view.add_selection_above(&Default::default(), cx);
4986 });
4987
4988 cx.assert_editor_state(indoc!(
4989 r#"abc
4990 def«ˇg»hi
4991
4992 jk
4993 nlmo
4994 "#
4995 ));
4996
4997 cx.update_editor(|view, cx| {
4998 view.add_selection_above(&Default::default(), cx);
4999 });
5000
5001 cx.assert_editor_state(indoc!(
5002 r#"abc
5003 def«ˇg»hi
5004
5005 jk
5006 nlmo
5007 "#
5008 ));
5009
5010 // Change selections again
5011 cx.set_state(indoc!(
5012 r#"a«bc
5013 defgˇ»hi
5014
5015 jk
5016 nlmo
5017 "#
5018 ));
5019
5020 cx.update_editor(|view, cx| {
5021 view.add_selection_below(&Default::default(), cx);
5022 });
5023
5024 cx.assert_editor_state(indoc!(
5025 r#"a«bcˇ»
5026 d«efgˇ»hi
5027
5028 j«kˇ»
5029 nlmo
5030 "#
5031 ));
5032
5033 cx.update_editor(|view, cx| {
5034 view.add_selection_below(&Default::default(), cx);
5035 });
5036 cx.assert_editor_state(indoc!(
5037 r#"a«bcˇ»
5038 d«efgˇ»hi
5039
5040 j«kˇ»
5041 n«lmoˇ»
5042 "#
5043 ));
5044 cx.update_editor(|view, cx| {
5045 view.add_selection_above(&Default::default(), cx);
5046 });
5047
5048 cx.assert_editor_state(indoc!(
5049 r#"a«bcˇ»
5050 d«efgˇ»hi
5051
5052 j«kˇ»
5053 nlmo
5054 "#
5055 ));
5056
5057 // Change selections again
5058 cx.set_state(indoc!(
5059 r#"abc
5060 d«ˇefghi
5061
5062 jk
5063 nlm»o
5064 "#
5065 ));
5066
5067 cx.update_editor(|view, cx| {
5068 view.add_selection_above(&Default::default(), cx);
5069 });
5070
5071 cx.assert_editor_state(indoc!(
5072 r#"a«ˇbc»
5073 d«ˇef»ghi
5074
5075 j«ˇk»
5076 n«ˇlm»o
5077 "#
5078 ));
5079
5080 cx.update_editor(|view, cx| {
5081 view.add_selection_below(&Default::default(), cx);
5082 });
5083
5084 cx.assert_editor_state(indoc!(
5085 r#"abc
5086 d«ˇef»ghi
5087
5088 j«ˇk»
5089 n«ˇlm»o
5090 "#
5091 ));
5092}
5093
5094#[gpui::test]
5095async fn test_select_next(cx: &mut gpui::TestAppContext) {
5096 init_test(cx, |_| {});
5097
5098 let mut cx = EditorTestContext::new(cx).await;
5099 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5100
5101 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5102 .unwrap();
5103 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5104
5105 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5106 .unwrap();
5107 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5108
5109 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5110 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5111
5112 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5113 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5114
5115 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5116 .unwrap();
5117 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5118
5119 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5120 .unwrap();
5121 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5122}
5123
5124#[gpui::test]
5125async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
5126 init_test(cx, |_| {});
5127
5128 let mut cx = EditorTestContext::new(cx).await;
5129 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5130
5131 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
5132 .unwrap();
5133 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5134}
5135
5136#[gpui::test]
5137async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5138 init_test(cx, |_| {});
5139
5140 let mut cx = EditorTestContext::new(cx).await;
5141 cx.set_state(
5142 r#"let foo = 2;
5143lˇet foo = 2;
5144let fooˇ = 2;
5145let foo = 2;
5146let foo = ˇ2;"#,
5147 );
5148
5149 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5150 .unwrap();
5151 cx.assert_editor_state(
5152 r#"let foo = 2;
5153«letˇ» foo = 2;
5154let «fooˇ» = 2;
5155let foo = 2;
5156let foo = «2ˇ»;"#,
5157 );
5158
5159 // noop for multiple selections with different contents
5160 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5161 .unwrap();
5162 cx.assert_editor_state(
5163 r#"let foo = 2;
5164«letˇ» foo = 2;
5165let «fooˇ» = 2;
5166let foo = 2;
5167let foo = «2ˇ»;"#,
5168 );
5169}
5170
5171#[gpui::test]
5172async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
5173 init_test(cx, |_| {});
5174
5175 let mut cx =
5176 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5177
5178 cx.assert_editor_state(indoc! {"
5179 ˇbbb
5180 ccc
5181
5182 bbb
5183 ccc
5184 "});
5185 cx.dispatch_action(SelectPrevious::default());
5186 cx.assert_editor_state(indoc! {"
5187 «bbbˇ»
5188 ccc
5189
5190 bbb
5191 ccc
5192 "});
5193 cx.dispatch_action(SelectPrevious::default());
5194 cx.assert_editor_state(indoc! {"
5195 «bbbˇ»
5196 ccc
5197
5198 «bbbˇ»
5199 ccc
5200 "});
5201}
5202
5203#[gpui::test]
5204async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
5205 init_test(cx, |_| {});
5206
5207 let mut cx = EditorTestContext::new(cx).await;
5208 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5209
5210 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5211 .unwrap();
5212 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5213
5214 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5215 .unwrap();
5216 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5217
5218 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5219 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5220
5221 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5222 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5223
5224 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5225 .unwrap();
5226 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5227
5228 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5229 .unwrap();
5230 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5231
5232 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5233 .unwrap();
5234 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5235}
5236
5237#[gpui::test]
5238async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5239 init_test(cx, |_| {});
5240
5241 let mut cx = EditorTestContext::new(cx).await;
5242 cx.set_state(
5243 r#"let foo = 2;
5244lˇet foo = 2;
5245let fooˇ = 2;
5246let foo = 2;
5247let foo = ˇ2;"#,
5248 );
5249
5250 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5251 .unwrap();
5252 cx.assert_editor_state(
5253 r#"let foo = 2;
5254«letˇ» foo = 2;
5255let «fooˇ» = 2;
5256let foo = 2;
5257let foo = «2ˇ»;"#,
5258 );
5259
5260 // noop for multiple selections with different contents
5261 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5262 .unwrap();
5263 cx.assert_editor_state(
5264 r#"let foo = 2;
5265«letˇ» foo = 2;
5266let «fooˇ» = 2;
5267let foo = 2;
5268let foo = «2ˇ»;"#,
5269 );
5270}
5271
5272#[gpui::test]
5273async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
5274 init_test(cx, |_| {});
5275
5276 let mut cx = EditorTestContext::new(cx).await;
5277 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5278
5279 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5280 .unwrap();
5281 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5282
5283 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5284 .unwrap();
5285 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5286
5287 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5288 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5289
5290 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5291 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5292
5293 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5294 .unwrap();
5295 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5296
5297 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5298 .unwrap();
5299 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5300}
5301
5302#[gpui::test]
5303async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5304 init_test(cx, |_| {});
5305
5306 let language = Arc::new(Language::new(
5307 LanguageConfig::default(),
5308 Some(tree_sitter_rust::LANGUAGE.into()),
5309 ));
5310
5311 let text = r#"
5312 use mod1::mod2::{mod3, mod4};
5313
5314 fn fn_1(param1: bool, param2: &str) {
5315 let var1 = "text";
5316 }
5317 "#
5318 .unindent();
5319
5320 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5321 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5322 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5323
5324 editor
5325 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5326 .await;
5327
5328 editor.update(cx, |view, cx| {
5329 view.change_selections(None, cx, |s| {
5330 s.select_display_ranges([
5331 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5332 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5333 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5334 ]);
5335 });
5336 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5337 });
5338 editor.update(cx, |editor, cx| {
5339 assert_text_with_selections(
5340 editor,
5341 indoc! {r#"
5342 use mod1::mod2::{mod3, «mod4ˇ»};
5343
5344 fn fn_1«ˇ(param1: bool, param2: &str)» {
5345 let var1 = "«textˇ»";
5346 }
5347 "#},
5348 cx,
5349 );
5350 });
5351
5352 editor.update(cx, |view, cx| {
5353 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5354 });
5355 editor.update(cx, |editor, cx| {
5356 assert_text_with_selections(
5357 editor,
5358 indoc! {r#"
5359 use mod1::mod2::«{mod3, mod4}ˇ»;
5360
5361 «ˇfn fn_1(param1: bool, param2: &str) {
5362 let var1 = "text";
5363 }»
5364 "#},
5365 cx,
5366 );
5367 });
5368
5369 editor.update(cx, |view, cx| {
5370 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5371 });
5372 assert_eq!(
5373 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5374 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5375 );
5376
5377 // Trying to expand the selected syntax node one more time has no effect.
5378 editor.update(cx, |view, cx| {
5379 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5380 });
5381 assert_eq!(
5382 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5383 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5384 );
5385
5386 editor.update(cx, |view, cx| {
5387 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5388 });
5389 editor.update(cx, |editor, cx| {
5390 assert_text_with_selections(
5391 editor,
5392 indoc! {r#"
5393 use mod1::mod2::«{mod3, mod4}ˇ»;
5394
5395 «ˇfn fn_1(param1: bool, param2: &str) {
5396 let var1 = "text";
5397 }»
5398 "#},
5399 cx,
5400 );
5401 });
5402
5403 editor.update(cx, |view, cx| {
5404 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5405 });
5406 editor.update(cx, |editor, cx| {
5407 assert_text_with_selections(
5408 editor,
5409 indoc! {r#"
5410 use mod1::mod2::{mod3, «mod4ˇ»};
5411
5412 fn fn_1«ˇ(param1: bool, param2: &str)» {
5413 let var1 = "«textˇ»";
5414 }
5415 "#},
5416 cx,
5417 );
5418 });
5419
5420 editor.update(cx, |view, cx| {
5421 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5422 });
5423 editor.update(cx, |editor, cx| {
5424 assert_text_with_selections(
5425 editor,
5426 indoc! {r#"
5427 use mod1::mod2::{mod3, mo«ˇ»d4};
5428
5429 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5430 let var1 = "te«ˇ»xt";
5431 }
5432 "#},
5433 cx,
5434 );
5435 });
5436
5437 // Trying to shrink the selected syntax node one more time has no effect.
5438 editor.update(cx, |view, cx| {
5439 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5440 });
5441 editor.update(cx, |editor, cx| {
5442 assert_text_with_selections(
5443 editor,
5444 indoc! {r#"
5445 use mod1::mod2::{mod3, mo«ˇ»d4};
5446
5447 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5448 let var1 = "te«ˇ»xt";
5449 }
5450 "#},
5451 cx,
5452 );
5453 });
5454
5455 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5456 // a fold.
5457 editor.update(cx, |view, cx| {
5458 view.fold_ranges(
5459 vec![
5460 (
5461 Point::new(0, 21)..Point::new(0, 24),
5462 FoldPlaceholder::test(),
5463 ),
5464 (
5465 Point::new(3, 20)..Point::new(3, 22),
5466 FoldPlaceholder::test(),
5467 ),
5468 ],
5469 true,
5470 cx,
5471 );
5472 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5473 });
5474 editor.update(cx, |editor, cx| {
5475 assert_text_with_selections(
5476 editor,
5477 indoc! {r#"
5478 use mod1::mod2::«{mod3, mod4}ˇ»;
5479
5480 fn fn_1«ˇ(param1: bool, param2: &str)» {
5481 «let var1 = "text";ˇ»
5482 }
5483 "#},
5484 cx,
5485 );
5486 });
5487}
5488
5489#[gpui::test]
5490async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5491 init_test(cx, |_| {});
5492
5493 let language = Arc::new(
5494 Language::new(
5495 LanguageConfig {
5496 brackets: BracketPairConfig {
5497 pairs: vec![
5498 BracketPair {
5499 start: "{".to_string(),
5500 end: "}".to_string(),
5501 close: false,
5502 surround: false,
5503 newline: true,
5504 },
5505 BracketPair {
5506 start: "(".to_string(),
5507 end: ")".to_string(),
5508 close: false,
5509 surround: false,
5510 newline: true,
5511 },
5512 ],
5513 ..Default::default()
5514 },
5515 ..Default::default()
5516 },
5517 Some(tree_sitter_rust::LANGUAGE.into()),
5518 )
5519 .with_indents_query(
5520 r#"
5521 (_ "(" ")" @end) @indent
5522 (_ "{" "}" @end) @indent
5523 "#,
5524 )
5525 .unwrap(),
5526 );
5527
5528 let text = "fn a() {}";
5529
5530 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5531 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5532 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5533 editor
5534 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5535 .await;
5536
5537 editor.update(cx, |editor, cx| {
5538 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5539 editor.newline(&Newline, cx);
5540 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5541 assert_eq!(
5542 editor.selections.ranges(cx),
5543 &[
5544 Point::new(1, 4)..Point::new(1, 4),
5545 Point::new(3, 4)..Point::new(3, 4),
5546 Point::new(5, 0)..Point::new(5, 0)
5547 ]
5548 );
5549 });
5550}
5551
5552#[gpui::test]
5553async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5554 init_test(cx, |_| {});
5555
5556 let mut cx = EditorTestContext::new(cx).await;
5557
5558 let language = Arc::new(Language::new(
5559 LanguageConfig {
5560 brackets: BracketPairConfig {
5561 pairs: vec![
5562 BracketPair {
5563 start: "{".to_string(),
5564 end: "}".to_string(),
5565 close: true,
5566 surround: true,
5567 newline: true,
5568 },
5569 BracketPair {
5570 start: "(".to_string(),
5571 end: ")".to_string(),
5572 close: true,
5573 surround: true,
5574 newline: true,
5575 },
5576 BracketPair {
5577 start: "/*".to_string(),
5578 end: " */".to_string(),
5579 close: true,
5580 surround: true,
5581 newline: true,
5582 },
5583 BracketPair {
5584 start: "[".to_string(),
5585 end: "]".to_string(),
5586 close: false,
5587 surround: false,
5588 newline: true,
5589 },
5590 BracketPair {
5591 start: "\"".to_string(),
5592 end: "\"".to_string(),
5593 close: true,
5594 surround: true,
5595 newline: false,
5596 },
5597 BracketPair {
5598 start: "<".to_string(),
5599 end: ">".to_string(),
5600 close: false,
5601 surround: true,
5602 newline: true,
5603 },
5604 ],
5605 ..Default::default()
5606 },
5607 autoclose_before: "})]".to_string(),
5608 ..Default::default()
5609 },
5610 Some(tree_sitter_rust::LANGUAGE.into()),
5611 ));
5612
5613 cx.language_registry().add(language.clone());
5614 cx.update_buffer(|buffer, cx| {
5615 buffer.set_language(Some(language), cx);
5616 });
5617
5618 cx.set_state(
5619 &r#"
5620 🏀ˇ
5621 εˇ
5622 ❤️ˇ
5623 "#
5624 .unindent(),
5625 );
5626
5627 // autoclose multiple nested brackets at multiple cursors
5628 cx.update_editor(|view, cx| {
5629 view.handle_input("{", cx);
5630 view.handle_input("{", cx);
5631 view.handle_input("{", cx);
5632 });
5633 cx.assert_editor_state(
5634 &"
5635 🏀{{{ˇ}}}
5636 ε{{{ˇ}}}
5637 ❤️{{{ˇ}}}
5638 "
5639 .unindent(),
5640 );
5641
5642 // insert a different closing bracket
5643 cx.update_editor(|view, cx| {
5644 view.handle_input(")", cx);
5645 });
5646 cx.assert_editor_state(
5647 &"
5648 🏀{{{)ˇ}}}
5649 ε{{{)ˇ}}}
5650 ❤️{{{)ˇ}}}
5651 "
5652 .unindent(),
5653 );
5654
5655 // skip over the auto-closed brackets when typing a closing bracket
5656 cx.update_editor(|view, cx| {
5657 view.move_right(&MoveRight, cx);
5658 view.handle_input("}", cx);
5659 view.handle_input("}", cx);
5660 view.handle_input("}", cx);
5661 });
5662 cx.assert_editor_state(
5663 &"
5664 🏀{{{)}}}}ˇ
5665 ε{{{)}}}}ˇ
5666 ❤️{{{)}}}}ˇ
5667 "
5668 .unindent(),
5669 );
5670
5671 // autoclose multi-character pairs
5672 cx.set_state(
5673 &"
5674 ˇ
5675 ˇ
5676 "
5677 .unindent(),
5678 );
5679 cx.update_editor(|view, cx| {
5680 view.handle_input("/", cx);
5681 view.handle_input("*", cx);
5682 });
5683 cx.assert_editor_state(
5684 &"
5685 /*ˇ */
5686 /*ˇ */
5687 "
5688 .unindent(),
5689 );
5690
5691 // one cursor autocloses a multi-character pair, one cursor
5692 // does not autoclose.
5693 cx.set_state(
5694 &"
5695 /ˇ
5696 ˇ
5697 "
5698 .unindent(),
5699 );
5700 cx.update_editor(|view, cx| view.handle_input("*", cx));
5701 cx.assert_editor_state(
5702 &"
5703 /*ˇ */
5704 *ˇ
5705 "
5706 .unindent(),
5707 );
5708
5709 // Don't autoclose if the next character isn't whitespace and isn't
5710 // listed in the language's "autoclose_before" section.
5711 cx.set_state("ˇa b");
5712 cx.update_editor(|view, cx| view.handle_input("{", cx));
5713 cx.assert_editor_state("{ˇa b");
5714
5715 // Don't autoclose if `close` is false for the bracket pair
5716 cx.set_state("ˇ");
5717 cx.update_editor(|view, cx| view.handle_input("[", cx));
5718 cx.assert_editor_state("[ˇ");
5719
5720 // Surround with brackets if text is selected
5721 cx.set_state("«aˇ» b");
5722 cx.update_editor(|view, cx| view.handle_input("{", cx));
5723 cx.assert_editor_state("{«aˇ»} b");
5724
5725 // Autclose pair where the start and end characters are the same
5726 cx.set_state("aˇ");
5727 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5728 cx.assert_editor_state("a\"ˇ\"");
5729 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5730 cx.assert_editor_state("a\"\"ˇ");
5731
5732 // Don't autoclose pair if autoclose is disabled
5733 cx.set_state("ˇ");
5734 cx.update_editor(|view, cx| view.handle_input("<", cx));
5735 cx.assert_editor_state("<ˇ");
5736
5737 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5738 cx.set_state("«aˇ» b");
5739 cx.update_editor(|view, cx| view.handle_input("<", cx));
5740 cx.assert_editor_state("<«aˇ»> b");
5741}
5742
5743#[gpui::test]
5744async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5745 init_test(cx, |settings| {
5746 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5747 });
5748
5749 let mut cx = EditorTestContext::new(cx).await;
5750
5751 let language = Arc::new(Language::new(
5752 LanguageConfig {
5753 brackets: BracketPairConfig {
5754 pairs: vec![
5755 BracketPair {
5756 start: "{".to_string(),
5757 end: "}".to_string(),
5758 close: true,
5759 surround: true,
5760 newline: true,
5761 },
5762 BracketPair {
5763 start: "(".to_string(),
5764 end: ")".to_string(),
5765 close: true,
5766 surround: true,
5767 newline: true,
5768 },
5769 BracketPair {
5770 start: "[".to_string(),
5771 end: "]".to_string(),
5772 close: false,
5773 surround: false,
5774 newline: true,
5775 },
5776 ],
5777 ..Default::default()
5778 },
5779 autoclose_before: "})]".to_string(),
5780 ..Default::default()
5781 },
5782 Some(tree_sitter_rust::LANGUAGE.into()),
5783 ));
5784
5785 cx.language_registry().add(language.clone());
5786 cx.update_buffer(|buffer, cx| {
5787 buffer.set_language(Some(language), cx);
5788 });
5789
5790 cx.set_state(
5791 &"
5792 ˇ
5793 ˇ
5794 ˇ
5795 "
5796 .unindent(),
5797 );
5798
5799 // ensure only matching closing brackets are skipped over
5800 cx.update_editor(|view, cx| {
5801 view.handle_input("}", cx);
5802 view.move_left(&MoveLeft, cx);
5803 view.handle_input(")", cx);
5804 view.move_left(&MoveLeft, cx);
5805 });
5806 cx.assert_editor_state(
5807 &"
5808 ˇ)}
5809 ˇ)}
5810 ˇ)}
5811 "
5812 .unindent(),
5813 );
5814
5815 // skip-over closing brackets at multiple cursors
5816 cx.update_editor(|view, cx| {
5817 view.handle_input(")", cx);
5818 view.handle_input("}", cx);
5819 });
5820 cx.assert_editor_state(
5821 &"
5822 )}ˇ
5823 )}ˇ
5824 )}ˇ
5825 "
5826 .unindent(),
5827 );
5828
5829 // ignore non-close brackets
5830 cx.update_editor(|view, cx| {
5831 view.handle_input("]", cx);
5832 view.move_left(&MoveLeft, cx);
5833 view.handle_input("]", cx);
5834 });
5835 cx.assert_editor_state(
5836 &"
5837 )}]ˇ]
5838 )}]ˇ]
5839 )}]ˇ]
5840 "
5841 .unindent(),
5842 );
5843}
5844
5845#[gpui::test]
5846async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5847 init_test(cx, |_| {});
5848
5849 let mut cx = EditorTestContext::new(cx).await;
5850
5851 let html_language = Arc::new(
5852 Language::new(
5853 LanguageConfig {
5854 name: "HTML".into(),
5855 brackets: BracketPairConfig {
5856 pairs: vec![
5857 BracketPair {
5858 start: "<".into(),
5859 end: ">".into(),
5860 close: true,
5861 ..Default::default()
5862 },
5863 BracketPair {
5864 start: "{".into(),
5865 end: "}".into(),
5866 close: true,
5867 ..Default::default()
5868 },
5869 BracketPair {
5870 start: "(".into(),
5871 end: ")".into(),
5872 close: true,
5873 ..Default::default()
5874 },
5875 ],
5876 ..Default::default()
5877 },
5878 autoclose_before: "})]>".into(),
5879 ..Default::default()
5880 },
5881 Some(tree_sitter_html::language()),
5882 )
5883 .with_injection_query(
5884 r#"
5885 (script_element
5886 (raw_text) @content
5887 (#set! "language" "javascript"))
5888 "#,
5889 )
5890 .unwrap(),
5891 );
5892
5893 let javascript_language = Arc::new(Language::new(
5894 LanguageConfig {
5895 name: "JavaScript".into(),
5896 brackets: BracketPairConfig {
5897 pairs: vec![
5898 BracketPair {
5899 start: "/*".into(),
5900 end: " */".into(),
5901 close: true,
5902 ..Default::default()
5903 },
5904 BracketPair {
5905 start: "{".into(),
5906 end: "}".into(),
5907 close: true,
5908 ..Default::default()
5909 },
5910 BracketPair {
5911 start: "(".into(),
5912 end: ")".into(),
5913 close: true,
5914 ..Default::default()
5915 },
5916 ],
5917 ..Default::default()
5918 },
5919 autoclose_before: "})]>".into(),
5920 ..Default::default()
5921 },
5922 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
5923 ));
5924
5925 cx.language_registry().add(html_language.clone());
5926 cx.language_registry().add(javascript_language.clone());
5927
5928 cx.update_buffer(|buffer, cx| {
5929 buffer.set_language(Some(html_language), cx);
5930 });
5931
5932 cx.set_state(
5933 &r#"
5934 <body>ˇ
5935 <script>
5936 var x = 1;ˇ
5937 </script>
5938 </body>ˇ
5939 "#
5940 .unindent(),
5941 );
5942
5943 // Precondition: different languages are active at different locations.
5944 cx.update_editor(|editor, cx| {
5945 let snapshot = editor.snapshot(cx);
5946 let cursors = editor.selections.ranges::<usize>(cx);
5947 let languages = cursors
5948 .iter()
5949 .map(|c| snapshot.language_at(c.start).unwrap().name())
5950 .collect::<Vec<_>>();
5951 assert_eq!(
5952 languages,
5953 &["HTML".into(), "JavaScript".into(), "HTML".into()]
5954 );
5955 });
5956
5957 // Angle brackets autoclose in HTML, but not JavaScript.
5958 cx.update_editor(|editor, cx| {
5959 editor.handle_input("<", cx);
5960 editor.handle_input("a", cx);
5961 });
5962 cx.assert_editor_state(
5963 &r#"
5964 <body><aˇ>
5965 <script>
5966 var x = 1;<aˇ
5967 </script>
5968 </body><aˇ>
5969 "#
5970 .unindent(),
5971 );
5972
5973 // Curly braces and parens autoclose in both HTML and JavaScript.
5974 cx.update_editor(|editor, cx| {
5975 editor.handle_input(" b=", cx);
5976 editor.handle_input("{", cx);
5977 editor.handle_input("c", cx);
5978 editor.handle_input("(", cx);
5979 });
5980 cx.assert_editor_state(
5981 &r#"
5982 <body><a b={c(ˇ)}>
5983 <script>
5984 var x = 1;<a b={c(ˇ)}
5985 </script>
5986 </body><a b={c(ˇ)}>
5987 "#
5988 .unindent(),
5989 );
5990
5991 // Brackets that were already autoclosed are skipped.
5992 cx.update_editor(|editor, cx| {
5993 editor.handle_input(")", cx);
5994 editor.handle_input("d", cx);
5995 editor.handle_input("}", cx);
5996 });
5997 cx.assert_editor_state(
5998 &r#"
5999 <body><a b={c()d}ˇ>
6000 <script>
6001 var x = 1;<a b={c()d}ˇ
6002 </script>
6003 </body><a b={c()d}ˇ>
6004 "#
6005 .unindent(),
6006 );
6007 cx.update_editor(|editor, cx| {
6008 editor.handle_input(">", cx);
6009 });
6010 cx.assert_editor_state(
6011 &r#"
6012 <body><a b={c()d}>ˇ
6013 <script>
6014 var x = 1;<a b={c()d}>ˇ
6015 </script>
6016 </body><a b={c()d}>ˇ
6017 "#
6018 .unindent(),
6019 );
6020
6021 // Reset
6022 cx.set_state(
6023 &r#"
6024 <body>ˇ
6025 <script>
6026 var x = 1;ˇ
6027 </script>
6028 </body>ˇ
6029 "#
6030 .unindent(),
6031 );
6032
6033 cx.update_editor(|editor, cx| {
6034 editor.handle_input("<", cx);
6035 });
6036 cx.assert_editor_state(
6037 &r#"
6038 <body><ˇ>
6039 <script>
6040 var x = 1;<ˇ
6041 </script>
6042 </body><ˇ>
6043 "#
6044 .unindent(),
6045 );
6046
6047 // When backspacing, the closing angle brackets are removed.
6048 cx.update_editor(|editor, cx| {
6049 editor.backspace(&Backspace, cx);
6050 });
6051 cx.assert_editor_state(
6052 &r#"
6053 <body>ˇ
6054 <script>
6055 var x = 1;ˇ
6056 </script>
6057 </body>ˇ
6058 "#
6059 .unindent(),
6060 );
6061
6062 // Block comments autoclose in JavaScript, but not HTML.
6063 cx.update_editor(|editor, cx| {
6064 editor.handle_input("/", cx);
6065 editor.handle_input("*", cx);
6066 });
6067 cx.assert_editor_state(
6068 &r#"
6069 <body>/*ˇ
6070 <script>
6071 var x = 1;/*ˇ */
6072 </script>
6073 </body>/*ˇ
6074 "#
6075 .unindent(),
6076 );
6077}
6078
6079#[gpui::test]
6080async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
6081 init_test(cx, |_| {});
6082
6083 let mut cx = EditorTestContext::new(cx).await;
6084
6085 let rust_language = Arc::new(
6086 Language::new(
6087 LanguageConfig {
6088 name: "Rust".into(),
6089 brackets: serde_json::from_value(json!([
6090 { "start": "{", "end": "}", "close": true, "newline": true },
6091 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6092 ]))
6093 .unwrap(),
6094 autoclose_before: "})]>".into(),
6095 ..Default::default()
6096 },
6097 Some(tree_sitter_rust::LANGUAGE.into()),
6098 )
6099 .with_override_query("(string_literal) @string")
6100 .unwrap(),
6101 );
6102
6103 cx.language_registry().add(rust_language.clone());
6104 cx.update_buffer(|buffer, cx| {
6105 buffer.set_language(Some(rust_language), cx);
6106 });
6107
6108 cx.set_state(
6109 &r#"
6110 let x = ˇ
6111 "#
6112 .unindent(),
6113 );
6114
6115 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6116 cx.update_editor(|editor, cx| {
6117 editor.handle_input("\"", cx);
6118 });
6119 cx.assert_editor_state(
6120 &r#"
6121 let x = "ˇ"
6122 "#
6123 .unindent(),
6124 );
6125
6126 // Inserting another quotation mark. The cursor moves across the existing
6127 // automatically-inserted quotation mark.
6128 cx.update_editor(|editor, cx| {
6129 editor.handle_input("\"", cx);
6130 });
6131 cx.assert_editor_state(
6132 &r#"
6133 let x = ""ˇ
6134 "#
6135 .unindent(),
6136 );
6137
6138 // Reset
6139 cx.set_state(
6140 &r#"
6141 let x = ˇ
6142 "#
6143 .unindent(),
6144 );
6145
6146 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6147 cx.update_editor(|editor, cx| {
6148 editor.handle_input("\"", cx);
6149 editor.handle_input(" ", cx);
6150 editor.move_left(&Default::default(), cx);
6151 editor.handle_input("\\", cx);
6152 editor.handle_input("\"", cx);
6153 });
6154 cx.assert_editor_state(
6155 &r#"
6156 let x = "\"ˇ "
6157 "#
6158 .unindent(),
6159 );
6160
6161 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6162 // mark. Nothing is inserted.
6163 cx.update_editor(|editor, cx| {
6164 editor.move_right(&Default::default(), cx);
6165 editor.handle_input("\"", cx);
6166 });
6167 cx.assert_editor_state(
6168 &r#"
6169 let x = "\" "ˇ
6170 "#
6171 .unindent(),
6172 );
6173}
6174
6175#[gpui::test]
6176async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
6177 init_test(cx, |_| {});
6178
6179 let language = Arc::new(Language::new(
6180 LanguageConfig {
6181 brackets: BracketPairConfig {
6182 pairs: vec![
6183 BracketPair {
6184 start: "{".to_string(),
6185 end: "}".to_string(),
6186 close: true,
6187 surround: true,
6188 newline: true,
6189 },
6190 BracketPair {
6191 start: "/* ".to_string(),
6192 end: "*/".to_string(),
6193 close: true,
6194 surround: true,
6195 ..Default::default()
6196 },
6197 ],
6198 ..Default::default()
6199 },
6200 ..Default::default()
6201 },
6202 Some(tree_sitter_rust::LANGUAGE.into()),
6203 ));
6204
6205 let text = r#"
6206 a
6207 b
6208 c
6209 "#
6210 .unindent();
6211
6212 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6213 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6214 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6215 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6216 .await;
6217
6218 view.update(cx, |view, cx| {
6219 view.change_selections(None, cx, |s| {
6220 s.select_display_ranges([
6221 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6222 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6223 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6224 ])
6225 });
6226
6227 view.handle_input("{", cx);
6228 view.handle_input("{", cx);
6229 view.handle_input("{", cx);
6230 assert_eq!(
6231 view.text(cx),
6232 "
6233 {{{a}}}
6234 {{{b}}}
6235 {{{c}}}
6236 "
6237 .unindent()
6238 );
6239 assert_eq!(
6240 view.selections.display_ranges(cx),
6241 [
6242 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6243 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6244 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6245 ]
6246 );
6247
6248 view.undo(&Undo, cx);
6249 view.undo(&Undo, cx);
6250 view.undo(&Undo, cx);
6251 assert_eq!(
6252 view.text(cx),
6253 "
6254 a
6255 b
6256 c
6257 "
6258 .unindent()
6259 );
6260 assert_eq!(
6261 view.selections.display_ranges(cx),
6262 [
6263 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6264 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6265 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6266 ]
6267 );
6268
6269 // Ensure inserting the first character of a multi-byte bracket pair
6270 // doesn't surround the selections with the bracket.
6271 view.handle_input("/", cx);
6272 assert_eq!(
6273 view.text(cx),
6274 "
6275 /
6276 /
6277 /
6278 "
6279 .unindent()
6280 );
6281 assert_eq!(
6282 view.selections.display_ranges(cx),
6283 [
6284 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6285 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6286 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6287 ]
6288 );
6289
6290 view.undo(&Undo, cx);
6291 assert_eq!(
6292 view.text(cx),
6293 "
6294 a
6295 b
6296 c
6297 "
6298 .unindent()
6299 );
6300 assert_eq!(
6301 view.selections.display_ranges(cx),
6302 [
6303 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6304 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6305 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6306 ]
6307 );
6308
6309 // Ensure inserting the last character of a multi-byte bracket pair
6310 // doesn't surround the selections with the bracket.
6311 view.handle_input("*", cx);
6312 assert_eq!(
6313 view.text(cx),
6314 "
6315 *
6316 *
6317 *
6318 "
6319 .unindent()
6320 );
6321 assert_eq!(
6322 view.selections.display_ranges(cx),
6323 [
6324 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6325 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6326 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6327 ]
6328 );
6329 });
6330}
6331
6332#[gpui::test]
6333async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6334 init_test(cx, |_| {});
6335
6336 let language = Arc::new(Language::new(
6337 LanguageConfig {
6338 brackets: BracketPairConfig {
6339 pairs: vec![BracketPair {
6340 start: "{".to_string(),
6341 end: "}".to_string(),
6342 close: true,
6343 surround: true,
6344 newline: true,
6345 }],
6346 ..Default::default()
6347 },
6348 autoclose_before: "}".to_string(),
6349 ..Default::default()
6350 },
6351 Some(tree_sitter_rust::LANGUAGE.into()),
6352 ));
6353
6354 let text = r#"
6355 a
6356 b
6357 c
6358 "#
6359 .unindent();
6360
6361 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6362 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6363 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6364 editor
6365 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6366 .await;
6367
6368 editor.update(cx, |editor, cx| {
6369 editor.change_selections(None, cx, |s| {
6370 s.select_ranges([
6371 Point::new(0, 1)..Point::new(0, 1),
6372 Point::new(1, 1)..Point::new(1, 1),
6373 Point::new(2, 1)..Point::new(2, 1),
6374 ])
6375 });
6376
6377 editor.handle_input("{", cx);
6378 editor.handle_input("{", cx);
6379 editor.handle_input("_", cx);
6380 assert_eq!(
6381 editor.text(cx),
6382 "
6383 a{{_}}
6384 b{{_}}
6385 c{{_}}
6386 "
6387 .unindent()
6388 );
6389 assert_eq!(
6390 editor.selections.ranges::<Point>(cx),
6391 [
6392 Point::new(0, 4)..Point::new(0, 4),
6393 Point::new(1, 4)..Point::new(1, 4),
6394 Point::new(2, 4)..Point::new(2, 4)
6395 ]
6396 );
6397
6398 editor.backspace(&Default::default(), cx);
6399 editor.backspace(&Default::default(), cx);
6400 assert_eq!(
6401 editor.text(cx),
6402 "
6403 a{}
6404 b{}
6405 c{}
6406 "
6407 .unindent()
6408 );
6409 assert_eq!(
6410 editor.selections.ranges::<Point>(cx),
6411 [
6412 Point::new(0, 2)..Point::new(0, 2),
6413 Point::new(1, 2)..Point::new(1, 2),
6414 Point::new(2, 2)..Point::new(2, 2)
6415 ]
6416 );
6417
6418 editor.delete_to_previous_word_start(&Default::default(), cx);
6419 assert_eq!(
6420 editor.text(cx),
6421 "
6422 a
6423 b
6424 c
6425 "
6426 .unindent()
6427 );
6428 assert_eq!(
6429 editor.selections.ranges::<Point>(cx),
6430 [
6431 Point::new(0, 1)..Point::new(0, 1),
6432 Point::new(1, 1)..Point::new(1, 1),
6433 Point::new(2, 1)..Point::new(2, 1)
6434 ]
6435 );
6436 });
6437}
6438
6439#[gpui::test]
6440async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6441 init_test(cx, |settings| {
6442 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6443 });
6444
6445 let mut cx = EditorTestContext::new(cx).await;
6446
6447 let language = Arc::new(Language::new(
6448 LanguageConfig {
6449 brackets: BracketPairConfig {
6450 pairs: vec![
6451 BracketPair {
6452 start: "{".to_string(),
6453 end: "}".to_string(),
6454 close: true,
6455 surround: true,
6456 newline: true,
6457 },
6458 BracketPair {
6459 start: "(".to_string(),
6460 end: ")".to_string(),
6461 close: true,
6462 surround: true,
6463 newline: true,
6464 },
6465 BracketPair {
6466 start: "[".to_string(),
6467 end: "]".to_string(),
6468 close: false,
6469 surround: true,
6470 newline: true,
6471 },
6472 ],
6473 ..Default::default()
6474 },
6475 autoclose_before: "})]".to_string(),
6476 ..Default::default()
6477 },
6478 Some(tree_sitter_rust::LANGUAGE.into()),
6479 ));
6480
6481 cx.language_registry().add(language.clone());
6482 cx.update_buffer(|buffer, cx| {
6483 buffer.set_language(Some(language), cx);
6484 });
6485
6486 cx.set_state(
6487 &"
6488 {(ˇ)}
6489 [[ˇ]]
6490 {(ˇ)}
6491 "
6492 .unindent(),
6493 );
6494
6495 cx.update_editor(|view, cx| {
6496 view.backspace(&Default::default(), cx);
6497 view.backspace(&Default::default(), cx);
6498 });
6499
6500 cx.assert_editor_state(
6501 &"
6502 ˇ
6503 ˇ]]
6504 ˇ
6505 "
6506 .unindent(),
6507 );
6508
6509 cx.update_editor(|view, cx| {
6510 view.handle_input("{", cx);
6511 view.handle_input("{", cx);
6512 view.move_right(&MoveRight, cx);
6513 view.move_right(&MoveRight, cx);
6514 view.move_left(&MoveLeft, cx);
6515 view.move_left(&MoveLeft, cx);
6516 view.backspace(&Default::default(), cx);
6517 });
6518
6519 cx.assert_editor_state(
6520 &"
6521 {ˇ}
6522 {ˇ}]]
6523 {ˇ}
6524 "
6525 .unindent(),
6526 );
6527
6528 cx.update_editor(|view, cx| {
6529 view.backspace(&Default::default(), cx);
6530 });
6531
6532 cx.assert_editor_state(
6533 &"
6534 ˇ
6535 ˇ]]
6536 ˇ
6537 "
6538 .unindent(),
6539 );
6540}
6541
6542#[gpui::test]
6543async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6544 init_test(cx, |_| {});
6545
6546 let language = Arc::new(Language::new(
6547 LanguageConfig::default(),
6548 Some(tree_sitter_rust::LANGUAGE.into()),
6549 ));
6550
6551 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6552 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6553 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6554 editor
6555 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6556 .await;
6557
6558 editor.update(cx, |editor, cx| {
6559 editor.set_auto_replace_emoji_shortcode(true);
6560
6561 editor.handle_input("Hello ", cx);
6562 editor.handle_input(":wave", cx);
6563 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6564
6565 editor.handle_input(":", cx);
6566 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6567
6568 editor.handle_input(" :smile", cx);
6569 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6570
6571 editor.handle_input(":", cx);
6572 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6573
6574 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6575 editor.handle_input(":wave", cx);
6576 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6577
6578 editor.handle_input(":", cx);
6579 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6580
6581 editor.handle_input(":1", cx);
6582 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6583
6584 editor.handle_input(":", cx);
6585 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6586
6587 // Ensure shortcode does not get replaced when it is part of a word
6588 editor.handle_input(" Test:wave", cx);
6589 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6590
6591 editor.handle_input(":", cx);
6592 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6593
6594 editor.set_auto_replace_emoji_shortcode(false);
6595
6596 // Ensure shortcode does not get replaced when auto replace is off
6597 editor.handle_input(" :wave", cx);
6598 assert_eq!(
6599 editor.text(cx),
6600 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6601 );
6602
6603 editor.handle_input(":", cx);
6604 assert_eq!(
6605 editor.text(cx),
6606 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6607 );
6608 });
6609}
6610
6611#[gpui::test]
6612async fn test_snippets(cx: &mut gpui::TestAppContext) {
6613 init_test(cx, |_| {});
6614
6615 let (text, insertion_ranges) = marked_text_ranges(
6616 indoc! {"
6617 a.ˇ b
6618 a.ˇ b
6619 a.ˇ b
6620 "},
6621 false,
6622 );
6623
6624 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6625 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6626
6627 editor.update(cx, |editor, cx| {
6628 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6629
6630 editor
6631 .insert_snippet(&insertion_ranges, snippet, cx)
6632 .unwrap();
6633
6634 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6635 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6636 assert_eq!(editor.text(cx), expected_text);
6637 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6638 }
6639
6640 assert(
6641 editor,
6642 cx,
6643 indoc! {"
6644 a.f(«one», two, «three») b
6645 a.f(«one», two, «three») b
6646 a.f(«one», two, «three») b
6647 "},
6648 );
6649
6650 // Can't move earlier than the first tab stop
6651 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6652 assert(
6653 editor,
6654 cx,
6655 indoc! {"
6656 a.f(«one», two, «three») b
6657 a.f(«one», two, «three») b
6658 a.f(«one», two, «three») b
6659 "},
6660 );
6661
6662 assert!(editor.move_to_next_snippet_tabstop(cx));
6663 assert(
6664 editor,
6665 cx,
6666 indoc! {"
6667 a.f(one, «two», three) b
6668 a.f(one, «two», three) b
6669 a.f(one, «two», three) b
6670 "},
6671 );
6672
6673 editor.move_to_prev_snippet_tabstop(cx);
6674 assert(
6675 editor,
6676 cx,
6677 indoc! {"
6678 a.f(«one», two, «three») b
6679 a.f(«one», two, «three») b
6680 a.f(«one», two, «three») b
6681 "},
6682 );
6683
6684 assert!(editor.move_to_next_snippet_tabstop(cx));
6685 assert(
6686 editor,
6687 cx,
6688 indoc! {"
6689 a.f(one, «two», three) b
6690 a.f(one, «two», three) b
6691 a.f(one, «two», three) b
6692 "},
6693 );
6694 assert!(editor.move_to_next_snippet_tabstop(cx));
6695 assert(
6696 editor,
6697 cx,
6698 indoc! {"
6699 a.f(one, two, three)ˇ b
6700 a.f(one, two, three)ˇ b
6701 a.f(one, two, three)ˇ b
6702 "},
6703 );
6704
6705 // As soon as the last tab stop is reached, snippet state is gone
6706 editor.move_to_prev_snippet_tabstop(cx);
6707 assert(
6708 editor,
6709 cx,
6710 indoc! {"
6711 a.f(one, two, three)ˇ b
6712 a.f(one, two, three)ˇ b
6713 a.f(one, two, three)ˇ b
6714 "},
6715 );
6716 });
6717}
6718
6719#[gpui::test]
6720async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6721 init_test(cx, |_| {});
6722
6723 let fs = FakeFs::new(cx.executor());
6724 fs.insert_file("/file.rs", Default::default()).await;
6725
6726 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6727
6728 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6729 language_registry.add(rust_lang());
6730 let mut fake_servers = language_registry.register_fake_lsp(
6731 "Rust",
6732 FakeLspAdapter {
6733 capabilities: lsp::ServerCapabilities {
6734 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6735 ..Default::default()
6736 },
6737 ..Default::default()
6738 },
6739 );
6740
6741 let buffer = project
6742 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6743 .await
6744 .unwrap();
6745
6746 cx.executor().start_waiting();
6747 let fake_server = fake_servers.next().await.unwrap();
6748
6749 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6750 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6751 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6752 assert!(cx.read(|cx| editor.is_dirty(cx)));
6753
6754 let save = editor
6755 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6756 .unwrap();
6757 fake_server
6758 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6759 assert_eq!(
6760 params.text_document.uri,
6761 lsp::Url::from_file_path("/file.rs").unwrap()
6762 );
6763 assert_eq!(params.options.tab_size, 4);
6764 Ok(Some(vec![lsp::TextEdit::new(
6765 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6766 ", ".to_string(),
6767 )]))
6768 })
6769 .next()
6770 .await;
6771 cx.executor().start_waiting();
6772 save.await;
6773
6774 assert_eq!(
6775 editor.update(cx, |editor, cx| editor.text(cx)),
6776 "one, two\nthree\n"
6777 );
6778 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6779
6780 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6781 assert!(cx.read(|cx| editor.is_dirty(cx)));
6782
6783 // Ensure we can still save even if formatting hangs.
6784 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6785 assert_eq!(
6786 params.text_document.uri,
6787 lsp::Url::from_file_path("/file.rs").unwrap()
6788 );
6789 futures::future::pending::<()>().await;
6790 unreachable!()
6791 });
6792 let save = editor
6793 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6794 .unwrap();
6795 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6796 cx.executor().start_waiting();
6797 save.await;
6798 assert_eq!(
6799 editor.update(cx, |editor, cx| editor.text(cx)),
6800 "one\ntwo\nthree\n"
6801 );
6802 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6803
6804 // For non-dirty buffer, no formatting request should be sent
6805 let save = editor
6806 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6807 .unwrap();
6808 let _pending_format_request = fake_server
6809 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6810 panic!("Should not be invoked on non-dirty buffer");
6811 })
6812 .next();
6813 cx.executor().start_waiting();
6814 save.await;
6815
6816 // Set rust language override and assert overridden tabsize is sent to language server
6817 update_test_language_settings(cx, |settings| {
6818 settings.languages.insert(
6819 "Rust".into(),
6820 LanguageSettingsContent {
6821 tab_size: NonZeroU32::new(8),
6822 ..Default::default()
6823 },
6824 );
6825 });
6826
6827 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6828 assert!(cx.read(|cx| editor.is_dirty(cx)));
6829 let save = editor
6830 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6831 .unwrap();
6832 fake_server
6833 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6834 assert_eq!(
6835 params.text_document.uri,
6836 lsp::Url::from_file_path("/file.rs").unwrap()
6837 );
6838 assert_eq!(params.options.tab_size, 8);
6839 Ok(Some(vec![]))
6840 })
6841 .next()
6842 .await;
6843 cx.executor().start_waiting();
6844 save.await;
6845}
6846
6847#[gpui::test]
6848async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6849 init_test(cx, |_| {});
6850
6851 let cols = 4;
6852 let rows = 10;
6853 let sample_text_1 = sample_text(rows, cols, 'a');
6854 assert_eq!(
6855 sample_text_1,
6856 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6857 );
6858 let sample_text_2 = sample_text(rows, cols, 'l');
6859 assert_eq!(
6860 sample_text_2,
6861 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6862 );
6863 let sample_text_3 = sample_text(rows, cols, 'v');
6864 assert_eq!(
6865 sample_text_3,
6866 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6867 );
6868
6869 let fs = FakeFs::new(cx.executor());
6870 fs.insert_tree(
6871 "/a",
6872 json!({
6873 "main.rs": sample_text_1,
6874 "other.rs": sample_text_2,
6875 "lib.rs": sample_text_3,
6876 }),
6877 )
6878 .await;
6879
6880 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6881 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6882 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6883
6884 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6885 language_registry.add(rust_lang());
6886 let mut fake_servers = language_registry.register_fake_lsp(
6887 "Rust",
6888 FakeLspAdapter {
6889 capabilities: lsp::ServerCapabilities {
6890 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6891 ..Default::default()
6892 },
6893 ..Default::default()
6894 },
6895 );
6896
6897 let worktree = project.update(cx, |project, cx| {
6898 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
6899 assert_eq!(worktrees.len(), 1);
6900 worktrees.pop().unwrap()
6901 });
6902 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6903
6904 let buffer_1 = project
6905 .update(cx, |project, cx| {
6906 project.open_buffer((worktree_id, "main.rs"), cx)
6907 })
6908 .await
6909 .unwrap();
6910 let buffer_2 = project
6911 .update(cx, |project, cx| {
6912 project.open_buffer((worktree_id, "other.rs"), cx)
6913 })
6914 .await
6915 .unwrap();
6916 let buffer_3 = project
6917 .update(cx, |project, cx| {
6918 project.open_buffer((worktree_id, "lib.rs"), cx)
6919 })
6920 .await
6921 .unwrap();
6922
6923 let multi_buffer = cx.new_model(|cx| {
6924 let mut multi_buffer = MultiBuffer::new(ReadWrite);
6925 multi_buffer.push_excerpts(
6926 buffer_1.clone(),
6927 [
6928 ExcerptRange {
6929 context: Point::new(0, 0)..Point::new(3, 0),
6930 primary: None,
6931 },
6932 ExcerptRange {
6933 context: Point::new(5, 0)..Point::new(7, 0),
6934 primary: None,
6935 },
6936 ExcerptRange {
6937 context: Point::new(9, 0)..Point::new(10, 4),
6938 primary: None,
6939 },
6940 ],
6941 cx,
6942 );
6943 multi_buffer.push_excerpts(
6944 buffer_2.clone(),
6945 [
6946 ExcerptRange {
6947 context: Point::new(0, 0)..Point::new(3, 0),
6948 primary: None,
6949 },
6950 ExcerptRange {
6951 context: Point::new(5, 0)..Point::new(7, 0),
6952 primary: None,
6953 },
6954 ExcerptRange {
6955 context: Point::new(9, 0)..Point::new(10, 4),
6956 primary: None,
6957 },
6958 ],
6959 cx,
6960 );
6961 multi_buffer.push_excerpts(
6962 buffer_3.clone(),
6963 [
6964 ExcerptRange {
6965 context: Point::new(0, 0)..Point::new(3, 0),
6966 primary: None,
6967 },
6968 ExcerptRange {
6969 context: Point::new(5, 0)..Point::new(7, 0),
6970 primary: None,
6971 },
6972 ExcerptRange {
6973 context: Point::new(9, 0)..Point::new(10, 4),
6974 primary: None,
6975 },
6976 ],
6977 cx,
6978 );
6979 multi_buffer
6980 });
6981 let multi_buffer_editor = cx.new_view(|cx| {
6982 Editor::new(
6983 EditorMode::Full,
6984 multi_buffer,
6985 Some(project.clone()),
6986 true,
6987 cx,
6988 )
6989 });
6990
6991 multi_buffer_editor.update(cx, |editor, cx| {
6992 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
6993 editor.insert("|one|two|three|", cx);
6994 });
6995 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6996 multi_buffer_editor.update(cx, |editor, cx| {
6997 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
6998 s.select_ranges(Some(60..70))
6999 });
7000 editor.insert("|four|five|six|", cx);
7001 });
7002 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7003
7004 // First two buffers should be edited, but not the third one.
7005 assert_eq!(
7006 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7007 "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}",
7008 );
7009 buffer_1.update(cx, |buffer, _| {
7010 assert!(buffer.is_dirty());
7011 assert_eq!(
7012 buffer.text(),
7013 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7014 )
7015 });
7016 buffer_2.update(cx, |buffer, _| {
7017 assert!(buffer.is_dirty());
7018 assert_eq!(
7019 buffer.text(),
7020 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7021 )
7022 });
7023 buffer_3.update(cx, |buffer, _| {
7024 assert!(!buffer.is_dirty());
7025 assert_eq!(buffer.text(), sample_text_3,)
7026 });
7027
7028 cx.executor().start_waiting();
7029 let save = multi_buffer_editor
7030 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7031 .unwrap();
7032
7033 let fake_server = fake_servers.next().await.unwrap();
7034 fake_server
7035 .server
7036 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7037 Ok(Some(vec![lsp::TextEdit::new(
7038 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7039 format!("[{} formatted]", params.text_document.uri),
7040 )]))
7041 })
7042 .detach();
7043 save.await;
7044
7045 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7046 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7047 assert_eq!(
7048 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7049 "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}",
7050 );
7051 buffer_1.update(cx, |buffer, _| {
7052 assert!(!buffer.is_dirty());
7053 assert_eq!(
7054 buffer.text(),
7055 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
7056 )
7057 });
7058 buffer_2.update(cx, |buffer, _| {
7059 assert!(!buffer.is_dirty());
7060 assert_eq!(
7061 buffer.text(),
7062 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
7063 )
7064 });
7065 buffer_3.update(cx, |buffer, _| {
7066 assert!(!buffer.is_dirty());
7067 assert_eq!(buffer.text(), sample_text_3,)
7068 });
7069}
7070
7071#[gpui::test]
7072async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
7073 init_test(cx, |_| {});
7074
7075 let fs = FakeFs::new(cx.executor());
7076 fs.insert_file("/file.rs", Default::default()).await;
7077
7078 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7079
7080 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7081 language_registry.add(rust_lang());
7082 let mut fake_servers = language_registry.register_fake_lsp(
7083 "Rust",
7084 FakeLspAdapter {
7085 capabilities: lsp::ServerCapabilities {
7086 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7087 ..Default::default()
7088 },
7089 ..Default::default()
7090 },
7091 );
7092
7093 let buffer = project
7094 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7095 .await
7096 .unwrap();
7097
7098 cx.executor().start_waiting();
7099 let fake_server = fake_servers.next().await.unwrap();
7100
7101 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7102 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7103 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7104 assert!(cx.read(|cx| editor.is_dirty(cx)));
7105
7106 let save = editor
7107 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7108 .unwrap();
7109 fake_server
7110 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7111 assert_eq!(
7112 params.text_document.uri,
7113 lsp::Url::from_file_path("/file.rs").unwrap()
7114 );
7115 assert_eq!(params.options.tab_size, 4);
7116 Ok(Some(vec![lsp::TextEdit::new(
7117 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7118 ", ".to_string(),
7119 )]))
7120 })
7121 .next()
7122 .await;
7123 cx.executor().start_waiting();
7124 save.await;
7125 assert_eq!(
7126 editor.update(cx, |editor, cx| editor.text(cx)),
7127 "one, two\nthree\n"
7128 );
7129 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7130
7131 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7132 assert!(cx.read(|cx| editor.is_dirty(cx)));
7133
7134 // Ensure we can still save even if formatting hangs.
7135 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7136 move |params, _| async move {
7137 assert_eq!(
7138 params.text_document.uri,
7139 lsp::Url::from_file_path("/file.rs").unwrap()
7140 );
7141 futures::future::pending::<()>().await;
7142 unreachable!()
7143 },
7144 );
7145 let save = editor
7146 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7147 .unwrap();
7148 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7149 cx.executor().start_waiting();
7150 save.await;
7151 assert_eq!(
7152 editor.update(cx, |editor, cx| editor.text(cx)),
7153 "one\ntwo\nthree\n"
7154 );
7155 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7156
7157 // For non-dirty buffer, no formatting request should be sent
7158 let save = editor
7159 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7160 .unwrap();
7161 let _pending_format_request = fake_server
7162 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7163 panic!("Should not be invoked on non-dirty buffer");
7164 })
7165 .next();
7166 cx.executor().start_waiting();
7167 save.await;
7168
7169 // Set Rust language override and assert overridden tabsize is sent to language server
7170 update_test_language_settings(cx, |settings| {
7171 settings.languages.insert(
7172 "Rust".into(),
7173 LanguageSettingsContent {
7174 tab_size: NonZeroU32::new(8),
7175 ..Default::default()
7176 },
7177 );
7178 });
7179
7180 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
7181 assert!(cx.read(|cx| editor.is_dirty(cx)));
7182 let save = editor
7183 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7184 .unwrap();
7185 fake_server
7186 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7187 assert_eq!(
7188 params.text_document.uri,
7189 lsp::Url::from_file_path("/file.rs").unwrap()
7190 );
7191 assert_eq!(params.options.tab_size, 8);
7192 Ok(Some(vec![]))
7193 })
7194 .next()
7195 .await;
7196 cx.executor().start_waiting();
7197 save.await;
7198}
7199
7200#[gpui::test]
7201async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7202 init_test(cx, |settings| {
7203 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7204 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7205 ))
7206 });
7207
7208 let fs = FakeFs::new(cx.executor());
7209 fs.insert_file("/file.rs", Default::default()).await;
7210
7211 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7212
7213 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7214 language_registry.add(Arc::new(Language::new(
7215 LanguageConfig {
7216 name: "Rust".into(),
7217 matcher: LanguageMatcher {
7218 path_suffixes: vec!["rs".to_string()],
7219 ..Default::default()
7220 },
7221 ..LanguageConfig::default()
7222 },
7223 Some(tree_sitter_rust::LANGUAGE.into()),
7224 )));
7225 update_test_language_settings(cx, |settings| {
7226 // Enable Prettier formatting for the same buffer, and ensure
7227 // LSP is called instead of Prettier.
7228 settings.defaults.prettier = Some(PrettierSettings {
7229 allowed: true,
7230 ..PrettierSettings::default()
7231 });
7232 });
7233 let mut fake_servers = language_registry.register_fake_lsp(
7234 "Rust",
7235 FakeLspAdapter {
7236 capabilities: lsp::ServerCapabilities {
7237 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7238 ..Default::default()
7239 },
7240 ..Default::default()
7241 },
7242 );
7243
7244 let buffer = project
7245 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7246 .await
7247 .unwrap();
7248
7249 cx.executor().start_waiting();
7250 let fake_server = fake_servers.next().await.unwrap();
7251
7252 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7253 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7254 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7255
7256 let format = editor
7257 .update(cx, |editor, cx| {
7258 editor.perform_format(
7259 project.clone(),
7260 FormatTrigger::Manual,
7261 FormatTarget::Buffer,
7262 cx,
7263 )
7264 })
7265 .unwrap();
7266 fake_server
7267 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7268 assert_eq!(
7269 params.text_document.uri,
7270 lsp::Url::from_file_path("/file.rs").unwrap()
7271 );
7272 assert_eq!(params.options.tab_size, 4);
7273 Ok(Some(vec![lsp::TextEdit::new(
7274 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7275 ", ".to_string(),
7276 )]))
7277 })
7278 .next()
7279 .await;
7280 cx.executor().start_waiting();
7281 format.await;
7282 assert_eq!(
7283 editor.update(cx, |editor, cx| editor.text(cx)),
7284 "one, two\nthree\n"
7285 );
7286
7287 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7288 // Ensure we don't lock if formatting hangs.
7289 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7290 assert_eq!(
7291 params.text_document.uri,
7292 lsp::Url::from_file_path("/file.rs").unwrap()
7293 );
7294 futures::future::pending::<()>().await;
7295 unreachable!()
7296 });
7297 let format = editor
7298 .update(cx, |editor, cx| {
7299 editor.perform_format(project, FormatTrigger::Manual, FormatTarget::Buffer, cx)
7300 })
7301 .unwrap();
7302 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7303 cx.executor().start_waiting();
7304 format.await;
7305 assert_eq!(
7306 editor.update(cx, |editor, cx| editor.text(cx)),
7307 "one\ntwo\nthree\n"
7308 );
7309}
7310
7311#[gpui::test]
7312async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7313 init_test(cx, |_| {});
7314
7315 let mut cx = EditorLspTestContext::new_rust(
7316 lsp::ServerCapabilities {
7317 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7318 ..Default::default()
7319 },
7320 cx,
7321 )
7322 .await;
7323
7324 cx.set_state(indoc! {"
7325 one.twoˇ
7326 "});
7327
7328 // The format request takes a long time. When it completes, it inserts
7329 // a newline and an indent before the `.`
7330 cx.lsp
7331 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7332 let executor = cx.background_executor().clone();
7333 async move {
7334 executor.timer(Duration::from_millis(100)).await;
7335 Ok(Some(vec![lsp::TextEdit {
7336 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7337 new_text: "\n ".into(),
7338 }]))
7339 }
7340 });
7341
7342 // Submit a format request.
7343 let format_1 = cx
7344 .update_editor(|editor, cx| editor.format(&Format, cx))
7345 .unwrap();
7346 cx.executor().run_until_parked();
7347
7348 // Submit a second format request.
7349 let format_2 = cx
7350 .update_editor(|editor, cx| editor.format(&Format, cx))
7351 .unwrap();
7352 cx.executor().run_until_parked();
7353
7354 // Wait for both format requests to complete
7355 cx.executor().advance_clock(Duration::from_millis(200));
7356 cx.executor().start_waiting();
7357 format_1.await.unwrap();
7358 cx.executor().start_waiting();
7359 format_2.await.unwrap();
7360
7361 // The formatting edits only happens once.
7362 cx.assert_editor_state(indoc! {"
7363 one
7364 .twoˇ
7365 "});
7366}
7367
7368#[gpui::test]
7369async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7370 init_test(cx, |settings| {
7371 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7372 });
7373
7374 let mut cx = EditorLspTestContext::new_rust(
7375 lsp::ServerCapabilities {
7376 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7377 ..Default::default()
7378 },
7379 cx,
7380 )
7381 .await;
7382
7383 // Set up a buffer white some trailing whitespace and no trailing newline.
7384 cx.set_state(
7385 &[
7386 "one ", //
7387 "twoˇ", //
7388 "three ", //
7389 "four", //
7390 ]
7391 .join("\n"),
7392 );
7393
7394 // Submit a format request.
7395 let format = cx
7396 .update_editor(|editor, cx| editor.format(&Format, cx))
7397 .unwrap();
7398
7399 // Record which buffer changes have been sent to the language server
7400 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7401 cx.lsp
7402 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7403 let buffer_changes = buffer_changes.clone();
7404 move |params, _| {
7405 buffer_changes.lock().extend(
7406 params
7407 .content_changes
7408 .into_iter()
7409 .map(|e| (e.range.unwrap(), e.text)),
7410 );
7411 }
7412 });
7413
7414 // Handle formatting requests to the language server.
7415 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7416 let buffer_changes = buffer_changes.clone();
7417 move |_, _| {
7418 // When formatting is requested, trailing whitespace has already been stripped,
7419 // and the trailing newline has already been added.
7420 assert_eq!(
7421 &buffer_changes.lock()[1..],
7422 &[
7423 (
7424 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7425 "".into()
7426 ),
7427 (
7428 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7429 "".into()
7430 ),
7431 (
7432 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7433 "\n".into()
7434 ),
7435 ]
7436 );
7437
7438 // Insert blank lines between each line of the buffer.
7439 async move {
7440 Ok(Some(vec![
7441 lsp::TextEdit {
7442 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7443 new_text: "\n".into(),
7444 },
7445 lsp::TextEdit {
7446 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7447 new_text: "\n".into(),
7448 },
7449 ]))
7450 }
7451 }
7452 });
7453
7454 // After formatting the buffer, the trailing whitespace is stripped,
7455 // a newline is appended, and the edits provided by the language server
7456 // have been applied.
7457 format.await.unwrap();
7458 cx.assert_editor_state(
7459 &[
7460 "one", //
7461 "", //
7462 "twoˇ", //
7463 "", //
7464 "three", //
7465 "four", //
7466 "", //
7467 ]
7468 .join("\n"),
7469 );
7470
7471 // Undoing the formatting undoes the trailing whitespace removal, the
7472 // trailing newline, and the LSP edits.
7473 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7474 cx.assert_editor_state(
7475 &[
7476 "one ", //
7477 "twoˇ", //
7478 "three ", //
7479 "four", //
7480 ]
7481 .join("\n"),
7482 );
7483}
7484
7485#[gpui::test]
7486async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7487 cx: &mut gpui::TestAppContext,
7488) {
7489 init_test(cx, |_| {});
7490
7491 cx.update(|cx| {
7492 cx.update_global::<SettingsStore, _>(|settings, cx| {
7493 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7494 settings.auto_signature_help = Some(true);
7495 });
7496 });
7497 });
7498
7499 let mut cx = EditorLspTestContext::new_rust(
7500 lsp::ServerCapabilities {
7501 signature_help_provider: Some(lsp::SignatureHelpOptions {
7502 ..Default::default()
7503 }),
7504 ..Default::default()
7505 },
7506 cx,
7507 )
7508 .await;
7509
7510 let language = Language::new(
7511 LanguageConfig {
7512 name: "Rust".into(),
7513 brackets: BracketPairConfig {
7514 pairs: vec![
7515 BracketPair {
7516 start: "{".to_string(),
7517 end: "}".to_string(),
7518 close: true,
7519 surround: true,
7520 newline: true,
7521 },
7522 BracketPair {
7523 start: "(".to_string(),
7524 end: ")".to_string(),
7525 close: true,
7526 surround: true,
7527 newline: true,
7528 },
7529 BracketPair {
7530 start: "/*".to_string(),
7531 end: " */".to_string(),
7532 close: true,
7533 surround: true,
7534 newline: true,
7535 },
7536 BracketPair {
7537 start: "[".to_string(),
7538 end: "]".to_string(),
7539 close: false,
7540 surround: false,
7541 newline: true,
7542 },
7543 BracketPair {
7544 start: "\"".to_string(),
7545 end: "\"".to_string(),
7546 close: true,
7547 surround: true,
7548 newline: false,
7549 },
7550 BracketPair {
7551 start: "<".to_string(),
7552 end: ">".to_string(),
7553 close: false,
7554 surround: true,
7555 newline: true,
7556 },
7557 ],
7558 ..Default::default()
7559 },
7560 autoclose_before: "})]".to_string(),
7561 ..Default::default()
7562 },
7563 Some(tree_sitter_rust::LANGUAGE.into()),
7564 );
7565 let language = Arc::new(language);
7566
7567 cx.language_registry().add(language.clone());
7568 cx.update_buffer(|buffer, cx| {
7569 buffer.set_language(Some(language), cx);
7570 });
7571
7572 cx.set_state(
7573 &r#"
7574 fn main() {
7575 sampleˇ
7576 }
7577 "#
7578 .unindent(),
7579 );
7580
7581 cx.update_editor(|view, cx| {
7582 view.handle_input("(", cx);
7583 });
7584 cx.assert_editor_state(
7585 &"
7586 fn main() {
7587 sample(ˇ)
7588 }
7589 "
7590 .unindent(),
7591 );
7592
7593 let mocked_response = lsp::SignatureHelp {
7594 signatures: vec![lsp::SignatureInformation {
7595 label: "fn sample(param1: u8, param2: u8)".to_string(),
7596 documentation: None,
7597 parameters: Some(vec![
7598 lsp::ParameterInformation {
7599 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7600 documentation: None,
7601 },
7602 lsp::ParameterInformation {
7603 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7604 documentation: None,
7605 },
7606 ]),
7607 active_parameter: None,
7608 }],
7609 active_signature: Some(0),
7610 active_parameter: Some(0),
7611 };
7612 handle_signature_help_request(&mut cx, mocked_response).await;
7613
7614 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7615 .await;
7616
7617 cx.editor(|editor, _| {
7618 let signature_help_state = editor.signature_help_state.popover().cloned();
7619 assert!(signature_help_state.is_some());
7620 let ParsedMarkdown {
7621 text, highlights, ..
7622 } = signature_help_state.unwrap().parsed_content;
7623 assert_eq!(text, "param1: u8, param2: u8");
7624 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7625 });
7626}
7627
7628#[gpui::test]
7629async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7630 init_test(cx, |_| {});
7631
7632 cx.update(|cx| {
7633 cx.update_global::<SettingsStore, _>(|settings, cx| {
7634 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7635 settings.auto_signature_help = Some(false);
7636 settings.show_signature_help_after_edits = Some(false);
7637 });
7638 });
7639 });
7640
7641 let mut cx = EditorLspTestContext::new_rust(
7642 lsp::ServerCapabilities {
7643 signature_help_provider: Some(lsp::SignatureHelpOptions {
7644 ..Default::default()
7645 }),
7646 ..Default::default()
7647 },
7648 cx,
7649 )
7650 .await;
7651
7652 let language = Language::new(
7653 LanguageConfig {
7654 name: "Rust".into(),
7655 brackets: BracketPairConfig {
7656 pairs: vec![
7657 BracketPair {
7658 start: "{".to_string(),
7659 end: "}".to_string(),
7660 close: true,
7661 surround: true,
7662 newline: true,
7663 },
7664 BracketPair {
7665 start: "(".to_string(),
7666 end: ")".to_string(),
7667 close: true,
7668 surround: true,
7669 newline: true,
7670 },
7671 BracketPair {
7672 start: "/*".to_string(),
7673 end: " */".to_string(),
7674 close: true,
7675 surround: true,
7676 newline: true,
7677 },
7678 BracketPair {
7679 start: "[".to_string(),
7680 end: "]".to_string(),
7681 close: false,
7682 surround: false,
7683 newline: true,
7684 },
7685 BracketPair {
7686 start: "\"".to_string(),
7687 end: "\"".to_string(),
7688 close: true,
7689 surround: true,
7690 newline: false,
7691 },
7692 BracketPair {
7693 start: "<".to_string(),
7694 end: ">".to_string(),
7695 close: false,
7696 surround: true,
7697 newline: true,
7698 },
7699 ],
7700 ..Default::default()
7701 },
7702 autoclose_before: "})]".to_string(),
7703 ..Default::default()
7704 },
7705 Some(tree_sitter_rust::LANGUAGE.into()),
7706 );
7707 let language = Arc::new(language);
7708
7709 cx.language_registry().add(language.clone());
7710 cx.update_buffer(|buffer, cx| {
7711 buffer.set_language(Some(language), cx);
7712 });
7713
7714 // Ensure that signature_help is not called when no signature help is enabled.
7715 cx.set_state(
7716 &r#"
7717 fn main() {
7718 sampleˇ
7719 }
7720 "#
7721 .unindent(),
7722 );
7723 cx.update_editor(|view, cx| {
7724 view.handle_input("(", cx);
7725 });
7726 cx.assert_editor_state(
7727 &"
7728 fn main() {
7729 sample(ˇ)
7730 }
7731 "
7732 .unindent(),
7733 );
7734 cx.editor(|editor, _| {
7735 assert!(editor.signature_help_state.task().is_none());
7736 });
7737
7738 let mocked_response = lsp::SignatureHelp {
7739 signatures: vec![lsp::SignatureInformation {
7740 label: "fn sample(param1: u8, param2: u8)".to_string(),
7741 documentation: None,
7742 parameters: Some(vec![
7743 lsp::ParameterInformation {
7744 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7745 documentation: None,
7746 },
7747 lsp::ParameterInformation {
7748 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7749 documentation: None,
7750 },
7751 ]),
7752 active_parameter: None,
7753 }],
7754 active_signature: Some(0),
7755 active_parameter: Some(0),
7756 };
7757
7758 // Ensure that signature_help is called when enabled afte edits
7759 cx.update(|cx| {
7760 cx.update_global::<SettingsStore, _>(|settings, cx| {
7761 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7762 settings.auto_signature_help = Some(false);
7763 settings.show_signature_help_after_edits = Some(true);
7764 });
7765 });
7766 });
7767 cx.set_state(
7768 &r#"
7769 fn main() {
7770 sampleˇ
7771 }
7772 "#
7773 .unindent(),
7774 );
7775 cx.update_editor(|view, cx| {
7776 view.handle_input("(", cx);
7777 });
7778 cx.assert_editor_state(
7779 &"
7780 fn main() {
7781 sample(ˇ)
7782 }
7783 "
7784 .unindent(),
7785 );
7786 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7787 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7788 .await;
7789 cx.update_editor(|editor, _| {
7790 let signature_help_state = editor.signature_help_state.popover().cloned();
7791 assert!(signature_help_state.is_some());
7792 let ParsedMarkdown {
7793 text, highlights, ..
7794 } = signature_help_state.unwrap().parsed_content;
7795 assert_eq!(text, "param1: u8, param2: u8");
7796 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7797 editor.signature_help_state = SignatureHelpState::default();
7798 });
7799
7800 // Ensure that signature_help is called when auto signature help override is enabled
7801 cx.update(|cx| {
7802 cx.update_global::<SettingsStore, _>(|settings, cx| {
7803 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7804 settings.auto_signature_help = Some(true);
7805 settings.show_signature_help_after_edits = Some(false);
7806 });
7807 });
7808 });
7809 cx.set_state(
7810 &r#"
7811 fn main() {
7812 sampleˇ
7813 }
7814 "#
7815 .unindent(),
7816 );
7817 cx.update_editor(|view, cx| {
7818 view.handle_input("(", cx);
7819 });
7820 cx.assert_editor_state(
7821 &"
7822 fn main() {
7823 sample(ˇ)
7824 }
7825 "
7826 .unindent(),
7827 );
7828 handle_signature_help_request(&mut cx, mocked_response).await;
7829 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7830 .await;
7831 cx.editor(|editor, _| {
7832 let signature_help_state = editor.signature_help_state.popover().cloned();
7833 assert!(signature_help_state.is_some());
7834 let ParsedMarkdown {
7835 text, highlights, ..
7836 } = signature_help_state.unwrap().parsed_content;
7837 assert_eq!(text, "param1: u8, param2: u8");
7838 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7839 });
7840}
7841
7842#[gpui::test]
7843async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7844 init_test(cx, |_| {});
7845 cx.update(|cx| {
7846 cx.update_global::<SettingsStore, _>(|settings, cx| {
7847 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7848 settings.auto_signature_help = Some(true);
7849 });
7850 });
7851 });
7852
7853 let mut cx = EditorLspTestContext::new_rust(
7854 lsp::ServerCapabilities {
7855 signature_help_provider: Some(lsp::SignatureHelpOptions {
7856 ..Default::default()
7857 }),
7858 ..Default::default()
7859 },
7860 cx,
7861 )
7862 .await;
7863
7864 // A test that directly calls `show_signature_help`
7865 cx.update_editor(|editor, cx| {
7866 editor.show_signature_help(&ShowSignatureHelp, cx);
7867 });
7868
7869 let mocked_response = lsp::SignatureHelp {
7870 signatures: vec![lsp::SignatureInformation {
7871 label: "fn sample(param1: u8, param2: u8)".to_string(),
7872 documentation: None,
7873 parameters: Some(vec![
7874 lsp::ParameterInformation {
7875 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7876 documentation: None,
7877 },
7878 lsp::ParameterInformation {
7879 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7880 documentation: None,
7881 },
7882 ]),
7883 active_parameter: None,
7884 }],
7885 active_signature: Some(0),
7886 active_parameter: Some(0),
7887 };
7888 handle_signature_help_request(&mut cx, mocked_response).await;
7889
7890 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7891 .await;
7892
7893 cx.editor(|editor, _| {
7894 let signature_help_state = editor.signature_help_state.popover().cloned();
7895 assert!(signature_help_state.is_some());
7896 let ParsedMarkdown {
7897 text, highlights, ..
7898 } = signature_help_state.unwrap().parsed_content;
7899 assert_eq!(text, "param1: u8, param2: u8");
7900 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7901 });
7902
7903 // When exiting outside from inside the brackets, `signature_help` is closed.
7904 cx.set_state(indoc! {"
7905 fn main() {
7906 sample(ˇ);
7907 }
7908
7909 fn sample(param1: u8, param2: u8) {}
7910 "});
7911
7912 cx.update_editor(|editor, cx| {
7913 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
7914 });
7915
7916 let mocked_response = lsp::SignatureHelp {
7917 signatures: Vec::new(),
7918 active_signature: None,
7919 active_parameter: None,
7920 };
7921 handle_signature_help_request(&mut cx, mocked_response).await;
7922
7923 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7924 .await;
7925
7926 cx.editor(|editor, _| {
7927 assert!(!editor.signature_help_state.is_shown());
7928 });
7929
7930 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
7931 cx.set_state(indoc! {"
7932 fn main() {
7933 sample(ˇ);
7934 }
7935
7936 fn sample(param1: u8, param2: u8) {}
7937 "});
7938
7939 let mocked_response = lsp::SignatureHelp {
7940 signatures: vec![lsp::SignatureInformation {
7941 label: "fn sample(param1: u8, param2: u8)".to_string(),
7942 documentation: None,
7943 parameters: Some(vec![
7944 lsp::ParameterInformation {
7945 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7946 documentation: None,
7947 },
7948 lsp::ParameterInformation {
7949 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7950 documentation: None,
7951 },
7952 ]),
7953 active_parameter: None,
7954 }],
7955 active_signature: Some(0),
7956 active_parameter: Some(0),
7957 };
7958 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7959 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7960 .await;
7961 cx.editor(|editor, _| {
7962 assert!(editor.signature_help_state.is_shown());
7963 });
7964
7965 // Restore the popover with more parameter input
7966 cx.set_state(indoc! {"
7967 fn main() {
7968 sample(param1, param2ˇ);
7969 }
7970
7971 fn sample(param1: u8, param2: u8) {}
7972 "});
7973
7974 let mocked_response = lsp::SignatureHelp {
7975 signatures: vec![lsp::SignatureInformation {
7976 label: "fn sample(param1: u8, param2: u8)".to_string(),
7977 documentation: None,
7978 parameters: Some(vec![
7979 lsp::ParameterInformation {
7980 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7981 documentation: None,
7982 },
7983 lsp::ParameterInformation {
7984 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7985 documentation: None,
7986 },
7987 ]),
7988 active_parameter: None,
7989 }],
7990 active_signature: Some(0),
7991 active_parameter: Some(1),
7992 };
7993 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7994 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7995 .await;
7996
7997 // When selecting a range, the popover is gone.
7998 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
7999 cx.update_editor(|editor, cx| {
8000 editor.change_selections(None, cx, |s| {
8001 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8002 })
8003 });
8004 cx.assert_editor_state(indoc! {"
8005 fn main() {
8006 sample(param1, «ˇparam2»);
8007 }
8008
8009 fn sample(param1: u8, param2: u8) {}
8010 "});
8011 cx.editor(|editor, _| {
8012 assert!(!editor.signature_help_state.is_shown());
8013 });
8014
8015 // When unselecting again, the popover is back if within the brackets.
8016 cx.update_editor(|editor, cx| {
8017 editor.change_selections(None, cx, |s| {
8018 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8019 })
8020 });
8021 cx.assert_editor_state(indoc! {"
8022 fn main() {
8023 sample(param1, ˇparam2);
8024 }
8025
8026 fn sample(param1: u8, param2: u8) {}
8027 "});
8028 handle_signature_help_request(&mut cx, mocked_response).await;
8029 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8030 .await;
8031 cx.editor(|editor, _| {
8032 assert!(editor.signature_help_state.is_shown());
8033 });
8034
8035 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8036 cx.update_editor(|editor, cx| {
8037 editor.change_selections(None, cx, |s| {
8038 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8039 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8040 })
8041 });
8042 cx.assert_editor_state(indoc! {"
8043 fn main() {
8044 sample(param1, ˇparam2);
8045 }
8046
8047 fn sample(param1: u8, param2: u8) {}
8048 "});
8049
8050 let mocked_response = lsp::SignatureHelp {
8051 signatures: vec![lsp::SignatureInformation {
8052 label: "fn sample(param1: u8, param2: u8)".to_string(),
8053 documentation: None,
8054 parameters: Some(vec![
8055 lsp::ParameterInformation {
8056 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8057 documentation: None,
8058 },
8059 lsp::ParameterInformation {
8060 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8061 documentation: None,
8062 },
8063 ]),
8064 active_parameter: None,
8065 }],
8066 active_signature: Some(0),
8067 active_parameter: Some(1),
8068 };
8069 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8070 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8071 .await;
8072 cx.update_editor(|editor, cx| {
8073 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8074 });
8075 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8076 .await;
8077 cx.update_editor(|editor, cx| {
8078 editor.change_selections(None, cx, |s| {
8079 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8080 })
8081 });
8082 cx.assert_editor_state(indoc! {"
8083 fn main() {
8084 sample(param1, «ˇparam2»);
8085 }
8086
8087 fn sample(param1: u8, param2: u8) {}
8088 "});
8089 cx.update_editor(|editor, cx| {
8090 editor.change_selections(None, cx, |s| {
8091 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8092 })
8093 });
8094 cx.assert_editor_state(indoc! {"
8095 fn main() {
8096 sample(param1, ˇparam2);
8097 }
8098
8099 fn sample(param1: u8, param2: u8) {}
8100 "});
8101 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8102 .await;
8103}
8104
8105#[gpui::test]
8106async fn test_completion(cx: &mut gpui::TestAppContext) {
8107 init_test(cx, |_| {});
8108
8109 let mut cx = EditorLspTestContext::new_rust(
8110 lsp::ServerCapabilities {
8111 completion_provider: Some(lsp::CompletionOptions {
8112 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8113 resolve_provider: Some(true),
8114 ..Default::default()
8115 }),
8116 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8117 ..Default::default()
8118 },
8119 cx,
8120 )
8121 .await;
8122 let counter = Arc::new(AtomicUsize::new(0));
8123
8124 cx.set_state(indoc! {"
8125 oneˇ
8126 two
8127 three
8128 "});
8129 cx.simulate_keystroke(".");
8130 handle_completion_request(
8131 &mut cx,
8132 indoc! {"
8133 one.|<>
8134 two
8135 three
8136 "},
8137 vec!["first_completion", "second_completion"],
8138 counter.clone(),
8139 )
8140 .await;
8141 cx.condition(|editor, _| editor.context_menu_visible())
8142 .await;
8143 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8144
8145 let _handler = handle_signature_help_request(
8146 &mut cx,
8147 lsp::SignatureHelp {
8148 signatures: vec![lsp::SignatureInformation {
8149 label: "test signature".to_string(),
8150 documentation: None,
8151 parameters: Some(vec![lsp::ParameterInformation {
8152 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8153 documentation: None,
8154 }]),
8155 active_parameter: None,
8156 }],
8157 active_signature: None,
8158 active_parameter: None,
8159 },
8160 );
8161 cx.update_editor(|editor, cx| {
8162 assert!(
8163 !editor.signature_help_state.is_shown(),
8164 "No signature help was called for"
8165 );
8166 editor.show_signature_help(&ShowSignatureHelp, cx);
8167 });
8168 cx.run_until_parked();
8169 cx.update_editor(|editor, _| {
8170 assert!(
8171 !editor.signature_help_state.is_shown(),
8172 "No signature help should be shown when completions menu is open"
8173 );
8174 });
8175
8176 let apply_additional_edits = cx.update_editor(|editor, cx| {
8177 editor.context_menu_next(&Default::default(), cx);
8178 editor
8179 .confirm_completion(&ConfirmCompletion::default(), cx)
8180 .unwrap()
8181 });
8182 cx.assert_editor_state(indoc! {"
8183 one.second_completionˇ
8184 two
8185 three
8186 "});
8187
8188 handle_resolve_completion_request(
8189 &mut cx,
8190 Some(vec![
8191 (
8192 //This overlaps with the primary completion edit which is
8193 //misbehavior from the LSP spec, test that we filter it out
8194 indoc! {"
8195 one.second_ˇcompletion
8196 two
8197 threeˇ
8198 "},
8199 "overlapping additional edit",
8200 ),
8201 (
8202 indoc! {"
8203 one.second_completion
8204 two
8205 threeˇ
8206 "},
8207 "\nadditional edit",
8208 ),
8209 ]),
8210 )
8211 .await;
8212 apply_additional_edits.await.unwrap();
8213 cx.assert_editor_state(indoc! {"
8214 one.second_completionˇ
8215 two
8216 three
8217 additional edit
8218 "});
8219
8220 cx.set_state(indoc! {"
8221 one.second_completion
8222 twoˇ
8223 threeˇ
8224 additional edit
8225 "});
8226 cx.simulate_keystroke(" ");
8227 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8228 cx.simulate_keystroke("s");
8229 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8230
8231 cx.assert_editor_state(indoc! {"
8232 one.second_completion
8233 two sˇ
8234 three sˇ
8235 additional edit
8236 "});
8237 handle_completion_request(
8238 &mut cx,
8239 indoc! {"
8240 one.second_completion
8241 two s
8242 three <s|>
8243 additional edit
8244 "},
8245 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8246 counter.clone(),
8247 )
8248 .await;
8249 cx.condition(|editor, _| editor.context_menu_visible())
8250 .await;
8251 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8252
8253 cx.simulate_keystroke("i");
8254
8255 handle_completion_request(
8256 &mut cx,
8257 indoc! {"
8258 one.second_completion
8259 two si
8260 three <si|>
8261 additional edit
8262 "},
8263 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8264 counter.clone(),
8265 )
8266 .await;
8267 cx.condition(|editor, _| editor.context_menu_visible())
8268 .await;
8269 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8270
8271 let apply_additional_edits = cx.update_editor(|editor, cx| {
8272 editor
8273 .confirm_completion(&ConfirmCompletion::default(), cx)
8274 .unwrap()
8275 });
8276 cx.assert_editor_state(indoc! {"
8277 one.second_completion
8278 two sixth_completionˇ
8279 three sixth_completionˇ
8280 additional edit
8281 "});
8282
8283 handle_resolve_completion_request(&mut cx, None).await;
8284 apply_additional_edits.await.unwrap();
8285
8286 cx.update(|cx| {
8287 cx.update_global::<SettingsStore, _>(|settings, cx| {
8288 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8289 settings.show_completions_on_input = Some(false);
8290 });
8291 })
8292 });
8293 cx.set_state("editorˇ");
8294 cx.simulate_keystroke(".");
8295 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8296 cx.simulate_keystroke("c");
8297 cx.simulate_keystroke("l");
8298 cx.simulate_keystroke("o");
8299 cx.assert_editor_state("editor.cloˇ");
8300 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8301 cx.update_editor(|editor, cx| {
8302 editor.show_completions(&ShowCompletions { trigger: None }, cx);
8303 });
8304 handle_completion_request(
8305 &mut cx,
8306 "editor.<clo|>",
8307 vec!["close", "clobber"],
8308 counter.clone(),
8309 )
8310 .await;
8311 cx.condition(|editor, _| editor.context_menu_visible())
8312 .await;
8313 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8314
8315 let apply_additional_edits = cx.update_editor(|editor, cx| {
8316 editor
8317 .confirm_completion(&ConfirmCompletion::default(), cx)
8318 .unwrap()
8319 });
8320 cx.assert_editor_state("editor.closeˇ");
8321 handle_resolve_completion_request(&mut cx, None).await;
8322 apply_additional_edits.await.unwrap();
8323}
8324
8325#[gpui::test]
8326async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8327 init_test(cx, |_| {});
8328 let mut cx = EditorLspTestContext::new_rust(
8329 lsp::ServerCapabilities {
8330 completion_provider: Some(lsp::CompletionOptions {
8331 trigger_characters: Some(vec![".".to_string()]),
8332 ..Default::default()
8333 }),
8334 ..Default::default()
8335 },
8336 cx,
8337 )
8338 .await;
8339 cx.lsp
8340 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8341 Ok(Some(lsp::CompletionResponse::Array(vec![
8342 lsp::CompletionItem {
8343 label: "first".into(),
8344 ..Default::default()
8345 },
8346 lsp::CompletionItem {
8347 label: "last".into(),
8348 ..Default::default()
8349 },
8350 ])))
8351 });
8352 cx.set_state("variableˇ");
8353 cx.simulate_keystroke(".");
8354 cx.executor().run_until_parked();
8355
8356 cx.update_editor(|editor, _| {
8357 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8358 assert_eq!(
8359 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8360 &["first", "last"]
8361 );
8362 } else {
8363 panic!("expected completion menu to be open");
8364 }
8365 });
8366
8367 cx.update_editor(|editor, cx| {
8368 editor.move_page_down(&MovePageDown::default(), cx);
8369 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8370 assert!(
8371 menu.selected_item == 1,
8372 "expected PageDown to select the last item from the context menu"
8373 );
8374 } else {
8375 panic!("expected completion menu to stay open after PageDown");
8376 }
8377 });
8378
8379 cx.update_editor(|editor, cx| {
8380 editor.move_page_up(&MovePageUp::default(), cx);
8381 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8382 assert!(
8383 menu.selected_item == 0,
8384 "expected PageUp to select the first item from the context menu"
8385 );
8386 } else {
8387 panic!("expected completion menu to stay open after PageUp");
8388 }
8389 });
8390}
8391
8392#[gpui::test]
8393async fn test_completion_sort(cx: &mut gpui::TestAppContext) {
8394 init_test(cx, |_| {});
8395 let mut cx = EditorLspTestContext::new_rust(
8396 lsp::ServerCapabilities {
8397 completion_provider: Some(lsp::CompletionOptions {
8398 trigger_characters: Some(vec![".".to_string()]),
8399 ..Default::default()
8400 }),
8401 ..Default::default()
8402 },
8403 cx,
8404 )
8405 .await;
8406 cx.lsp
8407 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8408 Ok(Some(lsp::CompletionResponse::Array(vec![
8409 lsp::CompletionItem {
8410 label: "Range".into(),
8411 sort_text: Some("a".into()),
8412 ..Default::default()
8413 },
8414 lsp::CompletionItem {
8415 label: "r".into(),
8416 sort_text: Some("b".into()),
8417 ..Default::default()
8418 },
8419 lsp::CompletionItem {
8420 label: "ret".into(),
8421 sort_text: Some("c".into()),
8422 ..Default::default()
8423 },
8424 lsp::CompletionItem {
8425 label: "return".into(),
8426 sort_text: Some("d".into()),
8427 ..Default::default()
8428 },
8429 lsp::CompletionItem {
8430 label: "slice".into(),
8431 sort_text: Some("d".into()),
8432 ..Default::default()
8433 },
8434 ])))
8435 });
8436 cx.set_state("rˇ");
8437 cx.executor().run_until_parked();
8438 cx.update_editor(|editor, cx| {
8439 editor.show_completions(
8440 &ShowCompletions {
8441 trigger: Some("r".into()),
8442 },
8443 cx,
8444 );
8445 });
8446 cx.executor().run_until_parked();
8447
8448 cx.update_editor(|editor, _| {
8449 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8450 assert_eq!(
8451 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8452 &["r", "ret", "Range", "return"]
8453 );
8454 } else {
8455 panic!("expected completion menu to be open");
8456 }
8457 });
8458}
8459
8460#[gpui::test]
8461async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8462 init_test(cx, |_| {});
8463
8464 let mut cx = EditorLspTestContext::new_rust(
8465 lsp::ServerCapabilities {
8466 completion_provider: Some(lsp::CompletionOptions {
8467 trigger_characters: Some(vec![".".to_string()]),
8468 resolve_provider: Some(true),
8469 ..Default::default()
8470 }),
8471 ..Default::default()
8472 },
8473 cx,
8474 )
8475 .await;
8476
8477 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8478 cx.simulate_keystroke(".");
8479 let completion_item = lsp::CompletionItem {
8480 label: "Some".into(),
8481 kind: Some(lsp::CompletionItemKind::SNIPPET),
8482 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8483 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8484 kind: lsp::MarkupKind::Markdown,
8485 value: "```rust\nSome(2)\n```".to_string(),
8486 })),
8487 deprecated: Some(false),
8488 sort_text: Some("Some".to_string()),
8489 filter_text: Some("Some".to_string()),
8490 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8491 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8492 range: lsp::Range {
8493 start: lsp::Position {
8494 line: 0,
8495 character: 22,
8496 },
8497 end: lsp::Position {
8498 line: 0,
8499 character: 22,
8500 },
8501 },
8502 new_text: "Some(2)".to_string(),
8503 })),
8504 additional_text_edits: Some(vec![lsp::TextEdit {
8505 range: lsp::Range {
8506 start: lsp::Position {
8507 line: 0,
8508 character: 20,
8509 },
8510 end: lsp::Position {
8511 line: 0,
8512 character: 22,
8513 },
8514 },
8515 new_text: "".to_string(),
8516 }]),
8517 ..Default::default()
8518 };
8519
8520 let closure_completion_item = completion_item.clone();
8521 let counter = Arc::new(AtomicUsize::new(0));
8522 let counter_clone = counter.clone();
8523 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8524 let task_completion_item = closure_completion_item.clone();
8525 counter_clone.fetch_add(1, atomic::Ordering::Release);
8526 async move {
8527 Ok(Some(lsp::CompletionResponse::Array(vec![
8528 task_completion_item,
8529 ])))
8530 }
8531 });
8532
8533 cx.condition(|editor, _| editor.context_menu_visible())
8534 .await;
8535 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8536 assert!(request.next().await.is_some());
8537 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8538
8539 cx.simulate_keystroke("S");
8540 cx.simulate_keystroke("o");
8541 cx.simulate_keystroke("m");
8542 cx.condition(|editor, _| editor.context_menu_visible())
8543 .await;
8544 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
8545 assert!(request.next().await.is_some());
8546 assert!(request.next().await.is_some());
8547 assert!(request.next().await.is_some());
8548 request.close();
8549 assert!(request.next().await.is_none());
8550 assert_eq!(
8551 counter.load(atomic::Ordering::Acquire),
8552 4,
8553 "With the completions menu open, only one LSP request should happen per input"
8554 );
8555}
8556
8557#[gpui::test]
8558async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
8559 init_test(cx, |_| {});
8560 let mut cx = EditorTestContext::new(cx).await;
8561 let language = Arc::new(Language::new(
8562 LanguageConfig {
8563 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8564 ..Default::default()
8565 },
8566 Some(tree_sitter_rust::LANGUAGE.into()),
8567 ));
8568 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8569
8570 // If multiple selections intersect a line, the line is only toggled once.
8571 cx.set_state(indoc! {"
8572 fn a() {
8573 «//b();
8574 ˇ»// «c();
8575 //ˇ» d();
8576 }
8577 "});
8578
8579 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8580
8581 cx.assert_editor_state(indoc! {"
8582 fn a() {
8583 «b();
8584 c();
8585 ˇ» d();
8586 }
8587 "});
8588
8589 // The comment prefix is inserted at the same column for every line in a
8590 // selection.
8591 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8592
8593 cx.assert_editor_state(indoc! {"
8594 fn a() {
8595 // «b();
8596 // c();
8597 ˇ»// d();
8598 }
8599 "});
8600
8601 // If a selection ends at the beginning of a line, that line is not toggled.
8602 cx.set_selections_state(indoc! {"
8603 fn a() {
8604 // b();
8605 «// c();
8606 ˇ» // d();
8607 }
8608 "});
8609
8610 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8611
8612 cx.assert_editor_state(indoc! {"
8613 fn a() {
8614 // b();
8615 «c();
8616 ˇ» // d();
8617 }
8618 "});
8619
8620 // If a selection span a single line and is empty, the line is toggled.
8621 cx.set_state(indoc! {"
8622 fn a() {
8623 a();
8624 b();
8625 ˇ
8626 }
8627 "});
8628
8629 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8630
8631 cx.assert_editor_state(indoc! {"
8632 fn a() {
8633 a();
8634 b();
8635 //•ˇ
8636 }
8637 "});
8638
8639 // If a selection span multiple lines, empty lines are not toggled.
8640 cx.set_state(indoc! {"
8641 fn a() {
8642 «a();
8643
8644 c();ˇ»
8645 }
8646 "});
8647
8648 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8649
8650 cx.assert_editor_state(indoc! {"
8651 fn a() {
8652 // «a();
8653
8654 // c();ˇ»
8655 }
8656 "});
8657
8658 // If a selection includes multiple comment prefixes, all lines are uncommented.
8659 cx.set_state(indoc! {"
8660 fn a() {
8661 «// a();
8662 /// b();
8663 //! c();ˇ»
8664 }
8665 "});
8666
8667 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8668
8669 cx.assert_editor_state(indoc! {"
8670 fn a() {
8671 «a();
8672 b();
8673 c();ˇ»
8674 }
8675 "});
8676}
8677
8678#[gpui::test]
8679async fn test_toggle_comment_ignore_indent(cx: &mut gpui::TestAppContext) {
8680 init_test(cx, |_| {});
8681 let mut cx = EditorTestContext::new(cx).await;
8682 let language = Arc::new(Language::new(
8683 LanguageConfig {
8684 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8685 ..Default::default()
8686 },
8687 Some(tree_sitter_rust::LANGUAGE.into()),
8688 ));
8689 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8690
8691 let toggle_comments = &ToggleComments {
8692 advance_downwards: false,
8693 ignore_indent: true,
8694 };
8695
8696 // If multiple selections intersect a line, the line is only toggled once.
8697 cx.set_state(indoc! {"
8698 fn a() {
8699 // «b();
8700 // c();
8701 // ˇ» d();
8702 }
8703 "});
8704
8705 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8706
8707 cx.assert_editor_state(indoc! {"
8708 fn a() {
8709 «b();
8710 c();
8711 ˇ» d();
8712 }
8713 "});
8714
8715 // The comment prefix is inserted at the beginning of each line
8716 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8717
8718 cx.assert_editor_state(indoc! {"
8719 fn a() {
8720 // «b();
8721 // c();
8722 // ˇ» d();
8723 }
8724 "});
8725
8726 // If a selection ends at the beginning of a line, that line is not toggled.
8727 cx.set_selections_state(indoc! {"
8728 fn a() {
8729 // b();
8730 // «c();
8731 ˇ»// d();
8732 }
8733 "});
8734
8735 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8736
8737 cx.assert_editor_state(indoc! {"
8738 fn a() {
8739 // b();
8740 «c();
8741 ˇ»// d();
8742 }
8743 "});
8744
8745 // If a selection span a single line and is empty, the line is toggled.
8746 cx.set_state(indoc! {"
8747 fn a() {
8748 a();
8749 b();
8750 ˇ
8751 }
8752 "});
8753
8754 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8755
8756 cx.assert_editor_state(indoc! {"
8757 fn a() {
8758 a();
8759 b();
8760 //ˇ
8761 }
8762 "});
8763
8764 // If a selection span multiple lines, empty lines are not toggled.
8765 cx.set_state(indoc! {"
8766 fn a() {
8767 «a();
8768
8769 c();ˇ»
8770 }
8771 "});
8772
8773 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8774
8775 cx.assert_editor_state(indoc! {"
8776 fn a() {
8777 // «a();
8778
8779 // c();ˇ»
8780 }
8781 "});
8782
8783 // If a selection includes multiple comment prefixes, all lines are uncommented.
8784 cx.set_state(indoc! {"
8785 fn a() {
8786 // «a();
8787 /// b();
8788 //! c();ˇ»
8789 }
8790 "});
8791
8792 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8793
8794 cx.assert_editor_state(indoc! {"
8795 fn a() {
8796 «a();
8797 b();
8798 c();ˇ»
8799 }
8800 "});
8801}
8802
8803#[gpui::test]
8804async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8805 init_test(cx, |_| {});
8806
8807 let language = Arc::new(Language::new(
8808 LanguageConfig {
8809 line_comments: vec!["// ".into()],
8810 ..Default::default()
8811 },
8812 Some(tree_sitter_rust::LANGUAGE.into()),
8813 ));
8814
8815 let mut cx = EditorTestContext::new(cx).await;
8816
8817 cx.language_registry().add(language.clone());
8818 cx.update_buffer(|buffer, cx| {
8819 buffer.set_language(Some(language), cx);
8820 });
8821
8822 let toggle_comments = &ToggleComments {
8823 advance_downwards: true,
8824 ignore_indent: false,
8825 };
8826
8827 // Single cursor on one line -> advance
8828 // Cursor moves horizontally 3 characters as well on non-blank line
8829 cx.set_state(indoc!(
8830 "fn a() {
8831 ˇdog();
8832 cat();
8833 }"
8834 ));
8835 cx.update_editor(|editor, cx| {
8836 editor.toggle_comments(toggle_comments, cx);
8837 });
8838 cx.assert_editor_state(indoc!(
8839 "fn a() {
8840 // dog();
8841 catˇ();
8842 }"
8843 ));
8844
8845 // Single selection on one line -> don't advance
8846 cx.set_state(indoc!(
8847 "fn a() {
8848 «dog()ˇ»;
8849 cat();
8850 }"
8851 ));
8852 cx.update_editor(|editor, cx| {
8853 editor.toggle_comments(toggle_comments, cx);
8854 });
8855 cx.assert_editor_state(indoc!(
8856 "fn a() {
8857 // «dog()ˇ»;
8858 cat();
8859 }"
8860 ));
8861
8862 // Multiple cursors on one line -> advance
8863 cx.set_state(indoc!(
8864 "fn a() {
8865 ˇdˇog();
8866 cat();
8867 }"
8868 ));
8869 cx.update_editor(|editor, cx| {
8870 editor.toggle_comments(toggle_comments, cx);
8871 });
8872 cx.assert_editor_state(indoc!(
8873 "fn a() {
8874 // dog();
8875 catˇ(ˇ);
8876 }"
8877 ));
8878
8879 // Multiple cursors on one line, with selection -> don't advance
8880 cx.set_state(indoc!(
8881 "fn a() {
8882 ˇdˇog«()ˇ»;
8883 cat();
8884 }"
8885 ));
8886 cx.update_editor(|editor, cx| {
8887 editor.toggle_comments(toggle_comments, cx);
8888 });
8889 cx.assert_editor_state(indoc!(
8890 "fn a() {
8891 // ˇdˇog«()ˇ»;
8892 cat();
8893 }"
8894 ));
8895
8896 // Single cursor on one line -> advance
8897 // Cursor moves to column 0 on blank line
8898 cx.set_state(indoc!(
8899 "fn a() {
8900 ˇdog();
8901
8902 cat();
8903 }"
8904 ));
8905 cx.update_editor(|editor, cx| {
8906 editor.toggle_comments(toggle_comments, cx);
8907 });
8908 cx.assert_editor_state(indoc!(
8909 "fn a() {
8910 // dog();
8911 ˇ
8912 cat();
8913 }"
8914 ));
8915
8916 // Single cursor on one line -> advance
8917 // Cursor starts and ends at column 0
8918 cx.set_state(indoc!(
8919 "fn a() {
8920 ˇ dog();
8921 cat();
8922 }"
8923 ));
8924 cx.update_editor(|editor, cx| {
8925 editor.toggle_comments(toggle_comments, cx);
8926 });
8927 cx.assert_editor_state(indoc!(
8928 "fn a() {
8929 // dog();
8930 ˇ cat();
8931 }"
8932 ));
8933}
8934
8935#[gpui::test]
8936async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
8937 init_test(cx, |_| {});
8938
8939 let mut cx = EditorTestContext::new(cx).await;
8940
8941 let html_language = Arc::new(
8942 Language::new(
8943 LanguageConfig {
8944 name: "HTML".into(),
8945 block_comment: Some(("<!-- ".into(), " -->".into())),
8946 ..Default::default()
8947 },
8948 Some(tree_sitter_html::language()),
8949 )
8950 .with_injection_query(
8951 r#"
8952 (script_element
8953 (raw_text) @content
8954 (#set! "language" "javascript"))
8955 "#,
8956 )
8957 .unwrap(),
8958 );
8959
8960 let javascript_language = Arc::new(Language::new(
8961 LanguageConfig {
8962 name: "JavaScript".into(),
8963 line_comments: vec!["// ".into()],
8964 ..Default::default()
8965 },
8966 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
8967 ));
8968
8969 cx.language_registry().add(html_language.clone());
8970 cx.language_registry().add(javascript_language.clone());
8971 cx.update_buffer(|buffer, cx| {
8972 buffer.set_language(Some(html_language), cx);
8973 });
8974
8975 // Toggle comments for empty selections
8976 cx.set_state(
8977 &r#"
8978 <p>A</p>ˇ
8979 <p>B</p>ˇ
8980 <p>C</p>ˇ
8981 "#
8982 .unindent(),
8983 );
8984 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8985 cx.assert_editor_state(
8986 &r#"
8987 <!-- <p>A</p>ˇ -->
8988 <!-- <p>B</p>ˇ -->
8989 <!-- <p>C</p>ˇ -->
8990 "#
8991 .unindent(),
8992 );
8993 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8994 cx.assert_editor_state(
8995 &r#"
8996 <p>A</p>ˇ
8997 <p>B</p>ˇ
8998 <p>C</p>ˇ
8999 "#
9000 .unindent(),
9001 );
9002
9003 // Toggle comments for mixture of empty and non-empty selections, where
9004 // multiple selections occupy a given line.
9005 cx.set_state(
9006 &r#"
9007 <p>A«</p>
9008 <p>ˇ»B</p>ˇ
9009 <p>C«</p>
9010 <p>ˇ»D</p>ˇ
9011 "#
9012 .unindent(),
9013 );
9014
9015 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9016 cx.assert_editor_state(
9017 &r#"
9018 <!-- <p>A«</p>
9019 <p>ˇ»B</p>ˇ -->
9020 <!-- <p>C«</p>
9021 <p>ˇ»D</p>ˇ -->
9022 "#
9023 .unindent(),
9024 );
9025 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9026 cx.assert_editor_state(
9027 &r#"
9028 <p>A«</p>
9029 <p>ˇ»B</p>ˇ
9030 <p>C«</p>
9031 <p>ˇ»D</p>ˇ
9032 "#
9033 .unindent(),
9034 );
9035
9036 // Toggle comments when different languages are active for different
9037 // selections.
9038 cx.set_state(
9039 &r#"
9040 ˇ<script>
9041 ˇvar x = new Y();
9042 ˇ</script>
9043 "#
9044 .unindent(),
9045 );
9046 cx.executor().run_until_parked();
9047 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9048 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
9049 // Uncommenting and commenting from this position brings in even more wrong artifacts.
9050 cx.assert_editor_state(
9051 &r#"
9052 <!-- ˇ<script> -->
9053 // ˇvar x = new Y();
9054 // ˇ</script>
9055 "#
9056 .unindent(),
9057 );
9058}
9059
9060#[gpui::test]
9061fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
9062 init_test(cx, |_| {});
9063
9064 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9065 let multibuffer = cx.new_model(|cx| {
9066 let mut multibuffer = MultiBuffer::new(ReadWrite);
9067 multibuffer.push_excerpts(
9068 buffer.clone(),
9069 [
9070 ExcerptRange {
9071 context: Point::new(0, 0)..Point::new(0, 4),
9072 primary: None,
9073 },
9074 ExcerptRange {
9075 context: Point::new(1, 0)..Point::new(1, 4),
9076 primary: None,
9077 },
9078 ],
9079 cx,
9080 );
9081 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
9082 multibuffer
9083 });
9084
9085 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9086 view.update(cx, |view, cx| {
9087 assert_eq!(view.text(cx), "aaaa\nbbbb");
9088 view.change_selections(None, cx, |s| {
9089 s.select_ranges([
9090 Point::new(0, 0)..Point::new(0, 0),
9091 Point::new(1, 0)..Point::new(1, 0),
9092 ])
9093 });
9094
9095 view.handle_input("X", cx);
9096 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
9097 assert_eq!(
9098 view.selections.ranges(cx),
9099 [
9100 Point::new(0, 1)..Point::new(0, 1),
9101 Point::new(1, 1)..Point::new(1, 1),
9102 ]
9103 );
9104
9105 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
9106 view.change_selections(None, cx, |s| {
9107 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
9108 });
9109 view.backspace(&Default::default(), cx);
9110 assert_eq!(view.text(cx), "Xa\nbbb");
9111 assert_eq!(
9112 view.selections.ranges(cx),
9113 [Point::new(1, 0)..Point::new(1, 0)]
9114 );
9115
9116 view.change_selections(None, cx, |s| {
9117 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
9118 });
9119 view.backspace(&Default::default(), cx);
9120 assert_eq!(view.text(cx), "X\nbb");
9121 assert_eq!(
9122 view.selections.ranges(cx),
9123 [Point::new(0, 1)..Point::new(0, 1)]
9124 );
9125 });
9126}
9127
9128#[gpui::test]
9129fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
9130 init_test(cx, |_| {});
9131
9132 let markers = vec![('[', ']').into(), ('(', ')').into()];
9133 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
9134 indoc! {"
9135 [aaaa
9136 (bbbb]
9137 cccc)",
9138 },
9139 markers.clone(),
9140 );
9141 let excerpt_ranges = markers.into_iter().map(|marker| {
9142 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9143 ExcerptRange {
9144 context,
9145 primary: None,
9146 }
9147 });
9148 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
9149 let multibuffer = cx.new_model(|cx| {
9150 let mut multibuffer = MultiBuffer::new(ReadWrite);
9151 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9152 multibuffer
9153 });
9154
9155 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9156 view.update(cx, |view, cx| {
9157 let (expected_text, selection_ranges) = marked_text_ranges(
9158 indoc! {"
9159 aaaa
9160 bˇbbb
9161 bˇbbˇb
9162 cccc"
9163 },
9164 true,
9165 );
9166 assert_eq!(view.text(cx), expected_text);
9167 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
9168
9169 view.handle_input("X", cx);
9170
9171 let (expected_text, expected_selections) = marked_text_ranges(
9172 indoc! {"
9173 aaaa
9174 bXˇbbXb
9175 bXˇbbXˇb
9176 cccc"
9177 },
9178 false,
9179 );
9180 assert_eq!(view.text(cx), expected_text);
9181 assert_eq!(view.selections.ranges(cx), expected_selections);
9182
9183 view.newline(&Newline, cx);
9184 let (expected_text, expected_selections) = marked_text_ranges(
9185 indoc! {"
9186 aaaa
9187 bX
9188 ˇbbX
9189 b
9190 bX
9191 ˇbbX
9192 ˇb
9193 cccc"
9194 },
9195 false,
9196 );
9197 assert_eq!(view.text(cx), expected_text);
9198 assert_eq!(view.selections.ranges(cx), expected_selections);
9199 });
9200}
9201
9202#[gpui::test]
9203fn test_refresh_selections(cx: &mut TestAppContext) {
9204 init_test(cx, |_| {});
9205
9206 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9207 let mut excerpt1_id = None;
9208 let multibuffer = cx.new_model(|cx| {
9209 let mut multibuffer = MultiBuffer::new(ReadWrite);
9210 excerpt1_id = multibuffer
9211 .push_excerpts(
9212 buffer.clone(),
9213 [
9214 ExcerptRange {
9215 context: Point::new(0, 0)..Point::new(1, 4),
9216 primary: None,
9217 },
9218 ExcerptRange {
9219 context: Point::new(1, 0)..Point::new(2, 4),
9220 primary: None,
9221 },
9222 ],
9223 cx,
9224 )
9225 .into_iter()
9226 .next();
9227 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9228 multibuffer
9229 });
9230
9231 let editor = cx.add_window(|cx| {
9232 let mut editor = build_editor(multibuffer.clone(), cx);
9233 let snapshot = editor.snapshot(cx);
9234 editor.change_selections(None, cx, |s| {
9235 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
9236 });
9237 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
9238 assert_eq!(
9239 editor.selections.ranges(cx),
9240 [
9241 Point::new(1, 3)..Point::new(1, 3),
9242 Point::new(2, 1)..Point::new(2, 1),
9243 ]
9244 );
9245 editor
9246 });
9247
9248 // Refreshing selections is a no-op when excerpts haven't changed.
9249 _ = editor.update(cx, |editor, cx| {
9250 editor.change_selections(None, cx, |s| s.refresh());
9251 assert_eq!(
9252 editor.selections.ranges(cx),
9253 [
9254 Point::new(1, 3)..Point::new(1, 3),
9255 Point::new(2, 1)..Point::new(2, 1),
9256 ]
9257 );
9258 });
9259
9260 multibuffer.update(cx, |multibuffer, cx| {
9261 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9262 });
9263 _ = editor.update(cx, |editor, cx| {
9264 // Removing an excerpt causes the first selection to become degenerate.
9265 assert_eq!(
9266 editor.selections.ranges(cx),
9267 [
9268 Point::new(0, 0)..Point::new(0, 0),
9269 Point::new(0, 1)..Point::new(0, 1)
9270 ]
9271 );
9272
9273 // Refreshing selections will relocate the first selection to the original buffer
9274 // location.
9275 editor.change_selections(None, cx, |s| s.refresh());
9276 assert_eq!(
9277 editor.selections.ranges(cx),
9278 [
9279 Point::new(0, 1)..Point::new(0, 1),
9280 Point::new(0, 3)..Point::new(0, 3)
9281 ]
9282 );
9283 assert!(editor.selections.pending_anchor().is_some());
9284 });
9285}
9286
9287#[gpui::test]
9288fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
9289 init_test(cx, |_| {});
9290
9291 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9292 let mut excerpt1_id = None;
9293 let multibuffer = cx.new_model(|cx| {
9294 let mut multibuffer = MultiBuffer::new(ReadWrite);
9295 excerpt1_id = multibuffer
9296 .push_excerpts(
9297 buffer.clone(),
9298 [
9299 ExcerptRange {
9300 context: Point::new(0, 0)..Point::new(1, 4),
9301 primary: None,
9302 },
9303 ExcerptRange {
9304 context: Point::new(1, 0)..Point::new(2, 4),
9305 primary: None,
9306 },
9307 ],
9308 cx,
9309 )
9310 .into_iter()
9311 .next();
9312 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9313 multibuffer
9314 });
9315
9316 let editor = cx.add_window(|cx| {
9317 let mut editor = build_editor(multibuffer.clone(), cx);
9318 let snapshot = editor.snapshot(cx);
9319 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
9320 assert_eq!(
9321 editor.selections.ranges(cx),
9322 [Point::new(1, 3)..Point::new(1, 3)]
9323 );
9324 editor
9325 });
9326
9327 multibuffer.update(cx, |multibuffer, cx| {
9328 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9329 });
9330 _ = editor.update(cx, |editor, cx| {
9331 assert_eq!(
9332 editor.selections.ranges(cx),
9333 [Point::new(0, 0)..Point::new(0, 0)]
9334 );
9335
9336 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
9337 editor.change_selections(None, cx, |s| s.refresh());
9338 assert_eq!(
9339 editor.selections.ranges(cx),
9340 [Point::new(0, 3)..Point::new(0, 3)]
9341 );
9342 assert!(editor.selections.pending_anchor().is_some());
9343 });
9344}
9345
9346#[gpui::test]
9347async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
9348 init_test(cx, |_| {});
9349
9350 let language = Arc::new(
9351 Language::new(
9352 LanguageConfig {
9353 brackets: BracketPairConfig {
9354 pairs: vec![
9355 BracketPair {
9356 start: "{".to_string(),
9357 end: "}".to_string(),
9358 close: true,
9359 surround: true,
9360 newline: true,
9361 },
9362 BracketPair {
9363 start: "/* ".to_string(),
9364 end: " */".to_string(),
9365 close: true,
9366 surround: true,
9367 newline: true,
9368 },
9369 ],
9370 ..Default::default()
9371 },
9372 ..Default::default()
9373 },
9374 Some(tree_sitter_rust::LANGUAGE.into()),
9375 )
9376 .with_indents_query("")
9377 .unwrap(),
9378 );
9379
9380 let text = concat!(
9381 "{ }\n", //
9382 " x\n", //
9383 " /* */\n", //
9384 "x\n", //
9385 "{{} }\n", //
9386 );
9387
9388 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
9389 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9390 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9391 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
9392 .await;
9393
9394 view.update(cx, |view, cx| {
9395 view.change_selections(None, cx, |s| {
9396 s.select_display_ranges([
9397 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
9398 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
9399 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
9400 ])
9401 });
9402 view.newline(&Newline, cx);
9403
9404 assert_eq!(
9405 view.buffer().read(cx).read(cx).text(),
9406 concat!(
9407 "{ \n", // Suppress rustfmt
9408 "\n", //
9409 "}\n", //
9410 " x\n", //
9411 " /* \n", //
9412 " \n", //
9413 " */\n", //
9414 "x\n", //
9415 "{{} \n", //
9416 "}\n", //
9417 )
9418 );
9419 });
9420}
9421
9422#[gpui::test]
9423fn test_highlighted_ranges(cx: &mut TestAppContext) {
9424 init_test(cx, |_| {});
9425
9426 let editor = cx.add_window(|cx| {
9427 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
9428 build_editor(buffer.clone(), cx)
9429 });
9430
9431 _ = editor.update(cx, |editor, cx| {
9432 struct Type1;
9433 struct Type2;
9434
9435 let buffer = editor.buffer.read(cx).snapshot(cx);
9436
9437 let anchor_range =
9438 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
9439
9440 editor.highlight_background::<Type1>(
9441 &[
9442 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
9443 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
9444 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
9445 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
9446 ],
9447 |_| Hsla::red(),
9448 cx,
9449 );
9450 editor.highlight_background::<Type2>(
9451 &[
9452 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
9453 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
9454 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
9455 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
9456 ],
9457 |_| Hsla::green(),
9458 cx,
9459 );
9460
9461 let snapshot = editor.snapshot(cx);
9462 let mut highlighted_ranges = editor.background_highlights_in_range(
9463 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
9464 &snapshot,
9465 cx.theme().colors(),
9466 );
9467 // Enforce a consistent ordering based on color without relying on the ordering of the
9468 // highlight's `TypeId` which is non-executor.
9469 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
9470 assert_eq!(
9471 highlighted_ranges,
9472 &[
9473 (
9474 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
9475 Hsla::red(),
9476 ),
9477 (
9478 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9479 Hsla::red(),
9480 ),
9481 (
9482 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9483 Hsla::green(),
9484 ),
9485 (
9486 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9487 Hsla::green(),
9488 ),
9489 ]
9490 );
9491 assert_eq!(
9492 editor.background_highlights_in_range(
9493 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9494 &snapshot,
9495 cx.theme().colors(),
9496 ),
9497 &[(
9498 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9499 Hsla::red(),
9500 )]
9501 );
9502 });
9503}
9504
9505#[gpui::test]
9506async fn test_following(cx: &mut gpui::TestAppContext) {
9507 init_test(cx, |_| {});
9508
9509 let fs = FakeFs::new(cx.executor());
9510 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9511
9512 let buffer = project.update(cx, |project, cx| {
9513 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9514 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9515 });
9516 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9517 let follower = cx.update(|cx| {
9518 cx.open_window(
9519 WindowOptions {
9520 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9521 gpui::Point::new(px(0.), px(0.)),
9522 gpui::Point::new(px(10.), px(80.)),
9523 ))),
9524 ..Default::default()
9525 },
9526 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9527 )
9528 .unwrap()
9529 });
9530
9531 let is_still_following = Rc::new(RefCell::new(true));
9532 let follower_edit_event_count = Rc::new(RefCell::new(0));
9533 let pending_update = Rc::new(RefCell::new(None));
9534 _ = follower.update(cx, {
9535 let update = pending_update.clone();
9536 let is_still_following = is_still_following.clone();
9537 let follower_edit_event_count = follower_edit_event_count.clone();
9538 |_, cx| {
9539 cx.subscribe(
9540 &leader.root_view(cx).unwrap(),
9541 move |_, leader, event, cx| {
9542 leader
9543 .read(cx)
9544 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9545 },
9546 )
9547 .detach();
9548
9549 cx.subscribe(
9550 &follower.root_view(cx).unwrap(),
9551 move |_, _, event: &EditorEvent, _cx| {
9552 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
9553 *is_still_following.borrow_mut() = false;
9554 }
9555
9556 if let EditorEvent::BufferEdited = event {
9557 *follower_edit_event_count.borrow_mut() += 1;
9558 }
9559 },
9560 )
9561 .detach();
9562 }
9563 });
9564
9565 // Update the selections only
9566 _ = leader.update(cx, |leader, cx| {
9567 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9568 });
9569 follower
9570 .update(cx, |follower, cx| {
9571 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9572 })
9573 .unwrap()
9574 .await
9575 .unwrap();
9576 _ = follower.update(cx, |follower, cx| {
9577 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
9578 });
9579 assert!(*is_still_following.borrow());
9580 assert_eq!(*follower_edit_event_count.borrow(), 0);
9581
9582 // Update the scroll position only
9583 _ = leader.update(cx, |leader, cx| {
9584 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9585 });
9586 follower
9587 .update(cx, |follower, cx| {
9588 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9589 })
9590 .unwrap()
9591 .await
9592 .unwrap();
9593 assert_eq!(
9594 follower
9595 .update(cx, |follower, cx| follower.scroll_position(cx))
9596 .unwrap(),
9597 gpui::Point::new(1.5, 3.5)
9598 );
9599 assert!(*is_still_following.borrow());
9600 assert_eq!(*follower_edit_event_count.borrow(), 0);
9601
9602 // Update the selections and scroll position. The follower's scroll position is updated
9603 // via autoscroll, not via the leader's exact scroll position.
9604 _ = leader.update(cx, |leader, cx| {
9605 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
9606 leader.request_autoscroll(Autoscroll::newest(), cx);
9607 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9608 });
9609 follower
9610 .update(cx, |follower, cx| {
9611 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9612 })
9613 .unwrap()
9614 .await
9615 .unwrap();
9616 _ = follower.update(cx, |follower, cx| {
9617 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
9618 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
9619 });
9620 assert!(*is_still_following.borrow());
9621
9622 // Creating a pending selection that precedes another selection
9623 _ = leader.update(cx, |leader, cx| {
9624 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9625 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
9626 });
9627 follower
9628 .update(cx, |follower, cx| {
9629 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9630 })
9631 .unwrap()
9632 .await
9633 .unwrap();
9634 _ = follower.update(cx, |follower, cx| {
9635 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
9636 });
9637 assert!(*is_still_following.borrow());
9638
9639 // Extend the pending selection so that it surrounds another selection
9640 _ = leader.update(cx, |leader, cx| {
9641 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
9642 });
9643 follower
9644 .update(cx, |follower, cx| {
9645 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9646 })
9647 .unwrap()
9648 .await
9649 .unwrap();
9650 _ = follower.update(cx, |follower, cx| {
9651 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
9652 });
9653
9654 // Scrolling locally breaks the follow
9655 _ = follower.update(cx, |follower, cx| {
9656 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
9657 follower.set_scroll_anchor(
9658 ScrollAnchor {
9659 anchor: top_anchor,
9660 offset: gpui::Point::new(0.0, 0.5),
9661 },
9662 cx,
9663 );
9664 });
9665 assert!(!(*is_still_following.borrow()));
9666}
9667
9668#[gpui::test]
9669async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
9670 init_test(cx, |_| {});
9671
9672 let fs = FakeFs::new(cx.executor());
9673 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9674 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9675 let pane = workspace
9676 .update(cx, |workspace, _| workspace.active_pane().clone())
9677 .unwrap();
9678
9679 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9680
9681 let leader = pane.update(cx, |_, cx| {
9682 let multibuffer = cx.new_model(|_| MultiBuffer::new(ReadWrite));
9683 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
9684 });
9685
9686 // Start following the editor when it has no excerpts.
9687 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9688 let follower_1 = cx
9689 .update_window(*workspace.deref(), |_, cx| {
9690 Editor::from_state_proto(
9691 workspace.root_view(cx).unwrap(),
9692 ViewId {
9693 creator: Default::default(),
9694 id: 0,
9695 },
9696 &mut state_message,
9697 cx,
9698 )
9699 })
9700 .unwrap()
9701 .unwrap()
9702 .await
9703 .unwrap();
9704
9705 let update_message = Rc::new(RefCell::new(None));
9706 follower_1.update(cx, {
9707 let update = update_message.clone();
9708 |_, cx| {
9709 cx.subscribe(&leader, move |_, leader, event, cx| {
9710 leader
9711 .read(cx)
9712 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9713 })
9714 .detach();
9715 }
9716 });
9717
9718 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
9719 (
9720 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9721 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9722 )
9723 });
9724
9725 // Insert some excerpts.
9726 leader.update(cx, |leader, cx| {
9727 leader.buffer.update(cx, |multibuffer, cx| {
9728 let excerpt_ids = multibuffer.push_excerpts(
9729 buffer_1.clone(),
9730 [
9731 ExcerptRange {
9732 context: 1..6,
9733 primary: None,
9734 },
9735 ExcerptRange {
9736 context: 12..15,
9737 primary: None,
9738 },
9739 ExcerptRange {
9740 context: 0..3,
9741 primary: None,
9742 },
9743 ],
9744 cx,
9745 );
9746 multibuffer.insert_excerpts_after(
9747 excerpt_ids[0],
9748 buffer_2.clone(),
9749 [
9750 ExcerptRange {
9751 context: 8..12,
9752 primary: None,
9753 },
9754 ExcerptRange {
9755 context: 0..6,
9756 primary: None,
9757 },
9758 ],
9759 cx,
9760 );
9761 });
9762 });
9763
9764 // Apply the update of adding the excerpts.
9765 follower_1
9766 .update(cx, |follower, cx| {
9767 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9768 })
9769 .await
9770 .unwrap();
9771 assert_eq!(
9772 follower_1.update(cx, |editor, cx| editor.text(cx)),
9773 leader.update(cx, |editor, cx| editor.text(cx))
9774 );
9775 update_message.borrow_mut().take();
9776
9777 // Start following separately after it already has excerpts.
9778 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9779 let follower_2 = cx
9780 .update_window(*workspace.deref(), |_, cx| {
9781 Editor::from_state_proto(
9782 workspace.root_view(cx).unwrap().clone(),
9783 ViewId {
9784 creator: Default::default(),
9785 id: 0,
9786 },
9787 &mut state_message,
9788 cx,
9789 )
9790 })
9791 .unwrap()
9792 .unwrap()
9793 .await
9794 .unwrap();
9795 assert_eq!(
9796 follower_2.update(cx, |editor, cx| editor.text(cx)),
9797 leader.update(cx, |editor, cx| editor.text(cx))
9798 );
9799
9800 // Remove some excerpts.
9801 leader.update(cx, |leader, cx| {
9802 leader.buffer.update(cx, |multibuffer, cx| {
9803 let excerpt_ids = multibuffer.excerpt_ids();
9804 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9805 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9806 });
9807 });
9808
9809 // Apply the update of removing the excerpts.
9810 follower_1
9811 .update(cx, |follower, cx| {
9812 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9813 })
9814 .await
9815 .unwrap();
9816 follower_2
9817 .update(cx, |follower, cx| {
9818 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9819 })
9820 .await
9821 .unwrap();
9822 update_message.borrow_mut().take();
9823 assert_eq!(
9824 follower_1.update(cx, |editor, cx| editor.text(cx)),
9825 leader.update(cx, |editor, cx| editor.text(cx))
9826 );
9827}
9828
9829#[gpui::test]
9830async fn go_to_prev_overlapping_diagnostic(
9831 executor: BackgroundExecutor,
9832 cx: &mut gpui::TestAppContext,
9833) {
9834 init_test(cx, |_| {});
9835
9836 let mut cx = EditorTestContext::new(cx).await;
9837 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9838
9839 cx.set_state(indoc! {"
9840 ˇfn func(abc def: i32) -> u32 {
9841 }
9842 "});
9843
9844 cx.update(|cx| {
9845 project.update(cx, |project, cx| {
9846 project
9847 .update_diagnostics(
9848 LanguageServerId(0),
9849 lsp::PublishDiagnosticsParams {
9850 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9851 version: None,
9852 diagnostics: vec![
9853 lsp::Diagnostic {
9854 range: lsp::Range::new(
9855 lsp::Position::new(0, 11),
9856 lsp::Position::new(0, 12),
9857 ),
9858 severity: Some(lsp::DiagnosticSeverity::ERROR),
9859 ..Default::default()
9860 },
9861 lsp::Diagnostic {
9862 range: lsp::Range::new(
9863 lsp::Position::new(0, 12),
9864 lsp::Position::new(0, 15),
9865 ),
9866 severity: Some(lsp::DiagnosticSeverity::ERROR),
9867 ..Default::default()
9868 },
9869 lsp::Diagnostic {
9870 range: lsp::Range::new(
9871 lsp::Position::new(0, 25),
9872 lsp::Position::new(0, 28),
9873 ),
9874 severity: Some(lsp::DiagnosticSeverity::ERROR),
9875 ..Default::default()
9876 },
9877 ],
9878 },
9879 &[],
9880 cx,
9881 )
9882 .unwrap()
9883 });
9884 });
9885
9886 executor.run_until_parked();
9887
9888 cx.update_editor(|editor, cx| {
9889 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9890 });
9891
9892 cx.assert_editor_state(indoc! {"
9893 fn func(abc def: i32) -> ˇu32 {
9894 }
9895 "});
9896
9897 cx.update_editor(|editor, cx| {
9898 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9899 });
9900
9901 cx.assert_editor_state(indoc! {"
9902 fn func(abc ˇdef: i32) -> u32 {
9903 }
9904 "});
9905
9906 cx.update_editor(|editor, cx| {
9907 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9908 });
9909
9910 cx.assert_editor_state(indoc! {"
9911 fn func(abcˇ def: i32) -> u32 {
9912 }
9913 "});
9914
9915 cx.update_editor(|editor, cx| {
9916 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9917 });
9918
9919 cx.assert_editor_state(indoc! {"
9920 fn func(abc def: i32) -> ˇu32 {
9921 }
9922 "});
9923}
9924
9925#[gpui::test]
9926async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
9927 init_test(cx, |_| {});
9928
9929 let mut cx = EditorTestContext::new(cx).await;
9930
9931 cx.set_state(indoc! {"
9932 fn func(abˇc def: i32) -> u32 {
9933 }
9934 "});
9935 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9936
9937 cx.update(|cx| {
9938 project.update(cx, |project, cx| {
9939 project.update_diagnostics(
9940 LanguageServerId(0),
9941 lsp::PublishDiagnosticsParams {
9942 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9943 version: None,
9944 diagnostics: vec![lsp::Diagnostic {
9945 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
9946 severity: Some(lsp::DiagnosticSeverity::ERROR),
9947 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
9948 ..Default::default()
9949 }],
9950 },
9951 &[],
9952 cx,
9953 )
9954 })
9955 }).unwrap();
9956 cx.run_until_parked();
9957 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
9958 cx.run_until_parked();
9959 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
9960}
9961
9962#[gpui::test]
9963async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9964 init_test(cx, |_| {});
9965
9966 let mut cx = EditorTestContext::new(cx).await;
9967
9968 let diff_base = r#"
9969 use some::mod;
9970
9971 const A: u32 = 42;
9972
9973 fn main() {
9974 println!("hello");
9975
9976 println!("world");
9977 }
9978 "#
9979 .unindent();
9980
9981 // Edits are modified, removed, modified, added
9982 cx.set_state(
9983 &r#"
9984 use some::modified;
9985
9986 ˇ
9987 fn main() {
9988 println!("hello there");
9989
9990 println!("around the");
9991 println!("world");
9992 }
9993 "#
9994 .unindent(),
9995 );
9996
9997 cx.set_diff_base(Some(&diff_base));
9998 executor.run_until_parked();
9999
10000 cx.update_editor(|editor, cx| {
10001 //Wrap around the bottom of the buffer
10002 for _ in 0..3 {
10003 editor.go_to_next_hunk(&GoToHunk, cx);
10004 }
10005 });
10006
10007 cx.assert_editor_state(
10008 &r#"
10009 ˇuse some::modified;
10010
10011
10012 fn main() {
10013 println!("hello there");
10014
10015 println!("around the");
10016 println!("world");
10017 }
10018 "#
10019 .unindent(),
10020 );
10021
10022 cx.update_editor(|editor, cx| {
10023 //Wrap around the top of the buffer
10024 for _ in 0..2 {
10025 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10026 }
10027 });
10028
10029 cx.assert_editor_state(
10030 &r#"
10031 use some::modified;
10032
10033
10034 fn main() {
10035 ˇ println!("hello there");
10036
10037 println!("around the");
10038 println!("world");
10039 }
10040 "#
10041 .unindent(),
10042 );
10043
10044 cx.update_editor(|editor, cx| {
10045 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10046 });
10047
10048 cx.assert_editor_state(
10049 &r#"
10050 use some::modified;
10051
10052 ˇ
10053 fn main() {
10054 println!("hello there");
10055
10056 println!("around the");
10057 println!("world");
10058 }
10059 "#
10060 .unindent(),
10061 );
10062
10063 cx.update_editor(|editor, cx| {
10064 for _ in 0..3 {
10065 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10066 }
10067 });
10068
10069 cx.assert_editor_state(
10070 &r#"
10071 use some::modified;
10072
10073
10074 fn main() {
10075 ˇ println!("hello there");
10076
10077 println!("around the");
10078 println!("world");
10079 }
10080 "#
10081 .unindent(),
10082 );
10083
10084 cx.update_editor(|editor, cx| {
10085 editor.fold(&Fold, cx);
10086
10087 //Make sure that the fold only gets one hunk
10088 for _ in 0..4 {
10089 editor.go_to_next_hunk(&GoToHunk, cx);
10090 }
10091 });
10092
10093 cx.assert_editor_state(
10094 &r#"
10095 ˇuse some::modified;
10096
10097
10098 fn main() {
10099 println!("hello there");
10100
10101 println!("around the");
10102 println!("world");
10103 }
10104 "#
10105 .unindent(),
10106 );
10107}
10108
10109#[test]
10110fn test_split_words() {
10111 fn split(text: &str) -> Vec<&str> {
10112 split_words(text).collect()
10113 }
10114
10115 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
10116 assert_eq!(split("hello_world"), &["hello_", "world"]);
10117 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
10118 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
10119 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
10120 assert_eq!(split("helloworld"), &["helloworld"]);
10121
10122 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
10123}
10124
10125#[gpui::test]
10126async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
10127 init_test(cx, |_| {});
10128
10129 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
10130 let mut assert = |before, after| {
10131 let _state_context = cx.set_state(before);
10132 cx.update_editor(|editor, cx| {
10133 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
10134 });
10135 cx.assert_editor_state(after);
10136 };
10137
10138 // Outside bracket jumps to outside of matching bracket
10139 assert("console.logˇ(var);", "console.log(var)ˇ;");
10140 assert("console.log(var)ˇ;", "console.logˇ(var);");
10141
10142 // Inside bracket jumps to inside of matching bracket
10143 assert("console.log(ˇvar);", "console.log(varˇ);");
10144 assert("console.log(varˇ);", "console.log(ˇvar);");
10145
10146 // When outside a bracket and inside, favor jumping to the inside bracket
10147 assert(
10148 "console.log('foo', [1, 2, 3]ˇ);",
10149 "console.log(ˇ'foo', [1, 2, 3]);",
10150 );
10151 assert(
10152 "console.log(ˇ'foo', [1, 2, 3]);",
10153 "console.log('foo', [1, 2, 3]ˇ);",
10154 );
10155
10156 // Bias forward if two options are equally likely
10157 assert(
10158 "let result = curried_fun()ˇ();",
10159 "let result = curried_fun()()ˇ;",
10160 );
10161
10162 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
10163 assert(
10164 indoc! {"
10165 function test() {
10166 console.log('test')ˇ
10167 }"},
10168 indoc! {"
10169 function test() {
10170 console.logˇ('test')
10171 }"},
10172 );
10173}
10174
10175#[gpui::test]
10176async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
10177 init_test(cx, |_| {});
10178
10179 let fs = FakeFs::new(cx.executor());
10180 fs.insert_tree(
10181 "/a",
10182 json!({
10183 "main.rs": "fn main() { let a = 5; }",
10184 "other.rs": "// Test file",
10185 }),
10186 )
10187 .await;
10188 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10189
10190 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10191 language_registry.add(Arc::new(Language::new(
10192 LanguageConfig {
10193 name: "Rust".into(),
10194 matcher: LanguageMatcher {
10195 path_suffixes: vec!["rs".to_string()],
10196 ..Default::default()
10197 },
10198 brackets: BracketPairConfig {
10199 pairs: vec![BracketPair {
10200 start: "{".to_string(),
10201 end: "}".to_string(),
10202 close: true,
10203 surround: true,
10204 newline: true,
10205 }],
10206 disabled_scopes_by_bracket_ix: Vec::new(),
10207 },
10208 ..Default::default()
10209 },
10210 Some(tree_sitter_rust::LANGUAGE.into()),
10211 )));
10212 let mut fake_servers = language_registry.register_fake_lsp(
10213 "Rust",
10214 FakeLspAdapter {
10215 capabilities: lsp::ServerCapabilities {
10216 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
10217 first_trigger_character: "{".to_string(),
10218 more_trigger_character: None,
10219 }),
10220 ..Default::default()
10221 },
10222 ..Default::default()
10223 },
10224 );
10225
10226 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10227
10228 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10229
10230 let worktree_id = workspace
10231 .update(cx, |workspace, cx| {
10232 workspace.project().update(cx, |project, cx| {
10233 project.worktrees(cx).next().unwrap().read(cx).id()
10234 })
10235 })
10236 .unwrap();
10237
10238 let buffer = project
10239 .update(cx, |project, cx| {
10240 project.open_local_buffer("/a/main.rs", cx)
10241 })
10242 .await
10243 .unwrap();
10244 cx.executor().run_until_parked();
10245 cx.executor().start_waiting();
10246 let fake_server = fake_servers.next().await.unwrap();
10247 let editor_handle = workspace
10248 .update(cx, |workspace, cx| {
10249 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
10250 })
10251 .unwrap()
10252 .await
10253 .unwrap()
10254 .downcast::<Editor>()
10255 .unwrap();
10256
10257 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
10258 assert_eq!(
10259 params.text_document_position.text_document.uri,
10260 lsp::Url::from_file_path("/a/main.rs").unwrap(),
10261 );
10262 assert_eq!(
10263 params.text_document_position.position,
10264 lsp::Position::new(0, 21),
10265 );
10266
10267 Ok(Some(vec![lsp::TextEdit {
10268 new_text: "]".to_string(),
10269 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10270 }]))
10271 });
10272
10273 editor_handle.update(cx, |editor, cx| {
10274 editor.focus(cx);
10275 editor.change_selections(None, cx, |s| {
10276 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
10277 });
10278 editor.handle_input("{", cx);
10279 });
10280
10281 cx.executor().run_until_parked();
10282
10283 buffer.update(cx, |buffer, _| {
10284 assert_eq!(
10285 buffer.text(),
10286 "fn main() { let a = {5}; }",
10287 "No extra braces from on type formatting should appear in the buffer"
10288 )
10289 });
10290}
10291
10292#[gpui::test]
10293async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
10294 init_test(cx, |_| {});
10295
10296 let fs = FakeFs::new(cx.executor());
10297 fs.insert_tree(
10298 "/a",
10299 json!({
10300 "main.rs": "fn main() { let a = 5; }",
10301 "other.rs": "// Test file",
10302 }),
10303 )
10304 .await;
10305
10306 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10307
10308 let server_restarts = Arc::new(AtomicUsize::new(0));
10309 let closure_restarts = Arc::clone(&server_restarts);
10310 let language_server_name = "test language server";
10311 let language_name: LanguageName = "Rust".into();
10312
10313 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10314 language_registry.add(Arc::new(Language::new(
10315 LanguageConfig {
10316 name: language_name.clone(),
10317 matcher: LanguageMatcher {
10318 path_suffixes: vec!["rs".to_string()],
10319 ..Default::default()
10320 },
10321 ..Default::default()
10322 },
10323 Some(tree_sitter_rust::LANGUAGE.into()),
10324 )));
10325 let mut fake_servers = language_registry.register_fake_lsp(
10326 "Rust",
10327 FakeLspAdapter {
10328 name: language_server_name,
10329 initialization_options: Some(json!({
10330 "testOptionValue": true
10331 })),
10332 initializer: Some(Box::new(move |fake_server| {
10333 let task_restarts = Arc::clone(&closure_restarts);
10334 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
10335 task_restarts.fetch_add(1, atomic::Ordering::Release);
10336 futures::future::ready(Ok(()))
10337 });
10338 })),
10339 ..Default::default()
10340 },
10341 );
10342
10343 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10344 let _buffer = project
10345 .update(cx, |project, cx| {
10346 project.open_local_buffer("/a/main.rs", cx)
10347 })
10348 .await
10349 .unwrap();
10350 let _fake_server = fake_servers.next().await.unwrap();
10351 update_test_language_settings(cx, |language_settings| {
10352 language_settings.languages.insert(
10353 language_name.clone(),
10354 LanguageSettingsContent {
10355 tab_size: NonZeroU32::new(8),
10356 ..Default::default()
10357 },
10358 );
10359 });
10360 cx.executor().run_until_parked();
10361 assert_eq!(
10362 server_restarts.load(atomic::Ordering::Acquire),
10363 0,
10364 "Should not restart LSP server on an unrelated change"
10365 );
10366
10367 update_test_project_settings(cx, |project_settings| {
10368 project_settings.lsp.insert(
10369 "Some other server name".into(),
10370 LspSettings {
10371 binary: None,
10372 settings: None,
10373 initialization_options: Some(json!({
10374 "some other init value": false
10375 })),
10376 },
10377 );
10378 });
10379 cx.executor().run_until_parked();
10380 assert_eq!(
10381 server_restarts.load(atomic::Ordering::Acquire),
10382 0,
10383 "Should not restart LSP server on an unrelated LSP settings change"
10384 );
10385
10386 update_test_project_settings(cx, |project_settings| {
10387 project_settings.lsp.insert(
10388 language_server_name.into(),
10389 LspSettings {
10390 binary: None,
10391 settings: None,
10392 initialization_options: Some(json!({
10393 "anotherInitValue": false
10394 })),
10395 },
10396 );
10397 });
10398 cx.executor().run_until_parked();
10399 assert_eq!(
10400 server_restarts.load(atomic::Ordering::Acquire),
10401 1,
10402 "Should restart LSP server on a related LSP settings change"
10403 );
10404
10405 update_test_project_settings(cx, |project_settings| {
10406 project_settings.lsp.insert(
10407 language_server_name.into(),
10408 LspSettings {
10409 binary: None,
10410 settings: None,
10411 initialization_options: Some(json!({
10412 "anotherInitValue": false
10413 })),
10414 },
10415 );
10416 });
10417 cx.executor().run_until_parked();
10418 assert_eq!(
10419 server_restarts.load(atomic::Ordering::Acquire),
10420 1,
10421 "Should not restart LSP server on a related LSP settings change that is the same"
10422 );
10423
10424 update_test_project_settings(cx, |project_settings| {
10425 project_settings.lsp.insert(
10426 language_server_name.into(),
10427 LspSettings {
10428 binary: None,
10429 settings: None,
10430 initialization_options: None,
10431 },
10432 );
10433 });
10434 cx.executor().run_until_parked();
10435 assert_eq!(
10436 server_restarts.load(atomic::Ordering::Acquire),
10437 2,
10438 "Should restart LSP server on another related LSP settings change"
10439 );
10440}
10441
10442#[gpui::test]
10443async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10444 init_test(cx, |_| {});
10445
10446 let mut cx = EditorLspTestContext::new_rust(
10447 lsp::ServerCapabilities {
10448 completion_provider: Some(lsp::CompletionOptions {
10449 trigger_characters: Some(vec![".".to_string()]),
10450 resolve_provider: Some(true),
10451 ..Default::default()
10452 }),
10453 ..Default::default()
10454 },
10455 cx,
10456 )
10457 .await;
10458
10459 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10460 cx.simulate_keystroke(".");
10461 let completion_item = lsp::CompletionItem {
10462 label: "some".into(),
10463 kind: Some(lsp::CompletionItemKind::SNIPPET),
10464 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10465 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10466 kind: lsp::MarkupKind::Markdown,
10467 value: "```rust\nSome(2)\n```".to_string(),
10468 })),
10469 deprecated: Some(false),
10470 sort_text: Some("fffffff2".to_string()),
10471 filter_text: Some("some".to_string()),
10472 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10473 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10474 range: lsp::Range {
10475 start: lsp::Position {
10476 line: 0,
10477 character: 22,
10478 },
10479 end: lsp::Position {
10480 line: 0,
10481 character: 22,
10482 },
10483 },
10484 new_text: "Some(2)".to_string(),
10485 })),
10486 additional_text_edits: Some(vec![lsp::TextEdit {
10487 range: lsp::Range {
10488 start: lsp::Position {
10489 line: 0,
10490 character: 20,
10491 },
10492 end: lsp::Position {
10493 line: 0,
10494 character: 22,
10495 },
10496 },
10497 new_text: "".to_string(),
10498 }]),
10499 ..Default::default()
10500 };
10501
10502 let closure_completion_item = completion_item.clone();
10503 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10504 let task_completion_item = closure_completion_item.clone();
10505 async move {
10506 Ok(Some(lsp::CompletionResponse::Array(vec![
10507 task_completion_item,
10508 ])))
10509 }
10510 });
10511
10512 request.next().await;
10513
10514 cx.condition(|editor, _| editor.context_menu_visible())
10515 .await;
10516 let apply_additional_edits = cx.update_editor(|editor, cx| {
10517 editor
10518 .confirm_completion(&ConfirmCompletion::default(), cx)
10519 .unwrap()
10520 });
10521 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
10522
10523 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10524 let task_completion_item = completion_item.clone();
10525 async move { Ok(task_completion_item) }
10526 })
10527 .next()
10528 .await
10529 .unwrap();
10530 apply_additional_edits.await.unwrap();
10531 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10532}
10533
10534#[gpui::test]
10535async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
10536 init_test(cx, |_| {});
10537
10538 let mut cx = EditorLspTestContext::new(
10539 Language::new(
10540 LanguageConfig {
10541 matcher: LanguageMatcher {
10542 path_suffixes: vec!["jsx".into()],
10543 ..Default::default()
10544 },
10545 overrides: [(
10546 "element".into(),
10547 LanguageConfigOverride {
10548 word_characters: Override::Set(['-'].into_iter().collect()),
10549 ..Default::default()
10550 },
10551 )]
10552 .into_iter()
10553 .collect(),
10554 ..Default::default()
10555 },
10556 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10557 )
10558 .with_override_query("(jsx_self_closing_element) @element")
10559 .unwrap(),
10560 lsp::ServerCapabilities {
10561 completion_provider: Some(lsp::CompletionOptions {
10562 trigger_characters: Some(vec![":".to_string()]),
10563 ..Default::default()
10564 }),
10565 ..Default::default()
10566 },
10567 cx,
10568 )
10569 .await;
10570
10571 cx.lsp
10572 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10573 Ok(Some(lsp::CompletionResponse::Array(vec![
10574 lsp::CompletionItem {
10575 label: "bg-blue".into(),
10576 ..Default::default()
10577 },
10578 lsp::CompletionItem {
10579 label: "bg-red".into(),
10580 ..Default::default()
10581 },
10582 lsp::CompletionItem {
10583 label: "bg-yellow".into(),
10584 ..Default::default()
10585 },
10586 ])))
10587 });
10588
10589 cx.set_state(r#"<p class="bgˇ" />"#);
10590
10591 // Trigger completion when typing a dash, because the dash is an extra
10592 // word character in the 'element' scope, which contains the cursor.
10593 cx.simulate_keystroke("-");
10594 cx.executor().run_until_parked();
10595 cx.update_editor(|editor, _| {
10596 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10597 assert_eq!(
10598 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10599 &["bg-red", "bg-blue", "bg-yellow"]
10600 );
10601 } else {
10602 panic!("expected completion menu to be open");
10603 }
10604 });
10605
10606 cx.simulate_keystroke("l");
10607 cx.executor().run_until_parked();
10608 cx.update_editor(|editor, _| {
10609 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10610 assert_eq!(
10611 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10612 &["bg-blue", "bg-yellow"]
10613 );
10614 } else {
10615 panic!("expected completion menu to be open");
10616 }
10617 });
10618
10619 // When filtering completions, consider the character after the '-' to
10620 // be the start of a subword.
10621 cx.set_state(r#"<p class="yelˇ" />"#);
10622 cx.simulate_keystroke("l");
10623 cx.executor().run_until_parked();
10624 cx.update_editor(|editor, _| {
10625 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10626 assert_eq!(
10627 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10628 &["bg-yellow"]
10629 );
10630 } else {
10631 panic!("expected completion menu to be open");
10632 }
10633 });
10634}
10635
10636#[gpui::test]
10637async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
10638 init_test(cx, |settings| {
10639 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
10640 FormatterList(vec![Formatter::Prettier].into()),
10641 ))
10642 });
10643
10644 let fs = FakeFs::new(cx.executor());
10645 fs.insert_file("/file.ts", Default::default()).await;
10646
10647 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
10648 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10649
10650 language_registry.add(Arc::new(Language::new(
10651 LanguageConfig {
10652 name: "TypeScript".into(),
10653 matcher: LanguageMatcher {
10654 path_suffixes: vec!["ts".to_string()],
10655 ..Default::default()
10656 },
10657 ..Default::default()
10658 },
10659 Some(tree_sitter_rust::LANGUAGE.into()),
10660 )));
10661 update_test_language_settings(cx, |settings| {
10662 settings.defaults.prettier = Some(PrettierSettings {
10663 allowed: true,
10664 ..PrettierSettings::default()
10665 });
10666 });
10667
10668 let test_plugin = "test_plugin";
10669 let _ = language_registry.register_fake_lsp(
10670 "TypeScript",
10671 FakeLspAdapter {
10672 prettier_plugins: vec![test_plugin],
10673 ..Default::default()
10674 },
10675 );
10676
10677 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
10678 let buffer = project
10679 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
10680 .await
10681 .unwrap();
10682
10683 let buffer_text = "one\ntwo\nthree\n";
10684 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
10685 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
10686 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
10687
10688 editor
10689 .update(cx, |editor, cx| {
10690 editor.perform_format(
10691 project.clone(),
10692 FormatTrigger::Manual,
10693 FormatTarget::Buffer,
10694 cx,
10695 )
10696 })
10697 .unwrap()
10698 .await;
10699 assert_eq!(
10700 editor.update(cx, |editor, cx| editor.text(cx)),
10701 buffer_text.to_string() + prettier_format_suffix,
10702 "Test prettier formatting was not applied to the original buffer text",
10703 );
10704
10705 update_test_language_settings(cx, |settings| {
10706 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
10707 });
10708 let format = editor.update(cx, |editor, cx| {
10709 editor.perform_format(
10710 project.clone(),
10711 FormatTrigger::Manual,
10712 FormatTarget::Buffer,
10713 cx,
10714 )
10715 });
10716 format.await.unwrap();
10717 assert_eq!(
10718 editor.update(cx, |editor, cx| editor.text(cx)),
10719 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
10720 "Autoformatting (via test prettier) was not applied to the original buffer text",
10721 );
10722}
10723
10724#[gpui::test]
10725async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
10726 init_test(cx, |_| {});
10727 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10728 let base_text = indoc! {r#"struct Row;
10729struct Row1;
10730struct Row2;
10731
10732struct Row4;
10733struct Row5;
10734struct Row6;
10735
10736struct Row8;
10737struct Row9;
10738struct Row10;"#};
10739
10740 // When addition hunks are not adjacent to carets, no hunk revert is performed
10741 assert_hunk_revert(
10742 indoc! {r#"struct Row;
10743 struct Row1;
10744 struct Row1.1;
10745 struct Row1.2;
10746 struct Row2;ˇ
10747
10748 struct Row4;
10749 struct Row5;
10750 struct Row6;
10751
10752 struct Row8;
10753 ˇstruct Row9;
10754 struct Row9.1;
10755 struct Row9.2;
10756 struct Row9.3;
10757 struct Row10;"#},
10758 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10759 indoc! {r#"struct Row;
10760 struct Row1;
10761 struct Row1.1;
10762 struct Row1.2;
10763 struct Row2;ˇ
10764
10765 struct Row4;
10766 struct Row5;
10767 struct Row6;
10768
10769 struct Row8;
10770 ˇstruct Row9;
10771 struct Row9.1;
10772 struct Row9.2;
10773 struct Row9.3;
10774 struct Row10;"#},
10775 base_text,
10776 &mut cx,
10777 );
10778 // Same for selections
10779 assert_hunk_revert(
10780 indoc! {r#"struct Row;
10781 struct Row1;
10782 struct Row2;
10783 struct Row2.1;
10784 struct Row2.2;
10785 «ˇ
10786 struct Row4;
10787 struct» Row5;
10788 «struct Row6;
10789 ˇ»
10790 struct Row9.1;
10791 struct Row9.2;
10792 struct Row9.3;
10793 struct Row8;
10794 struct Row9;
10795 struct Row10;"#},
10796 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10797 indoc! {r#"struct Row;
10798 struct Row1;
10799 struct Row2;
10800 struct Row2.1;
10801 struct Row2.2;
10802 «ˇ
10803 struct Row4;
10804 struct» Row5;
10805 «struct Row6;
10806 ˇ»
10807 struct Row9.1;
10808 struct Row9.2;
10809 struct Row9.3;
10810 struct Row8;
10811 struct Row9;
10812 struct Row10;"#},
10813 base_text,
10814 &mut cx,
10815 );
10816
10817 // When carets and selections intersect the addition hunks, those are reverted.
10818 // Adjacent carets got merged.
10819 assert_hunk_revert(
10820 indoc! {r#"struct Row;
10821 ˇ// something on the top
10822 struct Row1;
10823 struct Row2;
10824 struct Roˇw3.1;
10825 struct Row2.2;
10826 struct Row2.3;ˇ
10827
10828 struct Row4;
10829 struct ˇRow5.1;
10830 struct Row5.2;
10831 struct «Rowˇ»5.3;
10832 struct Row5;
10833 struct Row6;
10834 ˇ
10835 struct Row9.1;
10836 struct «Rowˇ»9.2;
10837 struct «ˇRow»9.3;
10838 struct Row8;
10839 struct Row9;
10840 «ˇ// something on bottom»
10841 struct Row10;"#},
10842 vec![
10843 DiffHunkStatus::Added,
10844 DiffHunkStatus::Added,
10845 DiffHunkStatus::Added,
10846 DiffHunkStatus::Added,
10847 DiffHunkStatus::Added,
10848 ],
10849 indoc! {r#"struct Row;
10850 ˇstruct Row1;
10851 struct Row2;
10852 ˇ
10853 struct Row4;
10854 ˇstruct Row5;
10855 struct Row6;
10856 ˇ
10857 ˇstruct Row8;
10858 struct Row9;
10859 ˇstruct Row10;"#},
10860 base_text,
10861 &mut cx,
10862 );
10863}
10864
10865#[gpui::test]
10866async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
10867 init_test(cx, |_| {});
10868 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10869 let base_text = indoc! {r#"struct Row;
10870struct Row1;
10871struct Row2;
10872
10873struct Row4;
10874struct Row5;
10875struct Row6;
10876
10877struct Row8;
10878struct Row9;
10879struct Row10;"#};
10880
10881 // Modification hunks behave the same as the addition ones.
10882 assert_hunk_revert(
10883 indoc! {r#"struct Row;
10884 struct Row1;
10885 struct Row33;
10886 ˇ
10887 struct Row4;
10888 struct Row5;
10889 struct Row6;
10890 ˇ
10891 struct Row99;
10892 struct Row9;
10893 struct Row10;"#},
10894 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10895 indoc! {r#"struct Row;
10896 struct Row1;
10897 struct Row33;
10898 ˇ
10899 struct Row4;
10900 struct Row5;
10901 struct Row6;
10902 ˇ
10903 struct Row99;
10904 struct Row9;
10905 struct Row10;"#},
10906 base_text,
10907 &mut cx,
10908 );
10909 assert_hunk_revert(
10910 indoc! {r#"struct Row;
10911 struct Row1;
10912 struct Row33;
10913 «ˇ
10914 struct Row4;
10915 struct» Row5;
10916 «struct Row6;
10917 ˇ»
10918 struct Row99;
10919 struct Row9;
10920 struct Row10;"#},
10921 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10922 indoc! {r#"struct Row;
10923 struct Row1;
10924 struct Row33;
10925 «ˇ
10926 struct Row4;
10927 struct» Row5;
10928 «struct Row6;
10929 ˇ»
10930 struct Row99;
10931 struct Row9;
10932 struct Row10;"#},
10933 base_text,
10934 &mut cx,
10935 );
10936
10937 assert_hunk_revert(
10938 indoc! {r#"ˇstruct Row1.1;
10939 struct Row1;
10940 «ˇstr»uct Row22;
10941
10942 struct ˇRow44;
10943 struct Row5;
10944 struct «Rˇ»ow66;ˇ
10945
10946 «struˇ»ct Row88;
10947 struct Row9;
10948 struct Row1011;ˇ"#},
10949 vec![
10950 DiffHunkStatus::Modified,
10951 DiffHunkStatus::Modified,
10952 DiffHunkStatus::Modified,
10953 DiffHunkStatus::Modified,
10954 DiffHunkStatus::Modified,
10955 DiffHunkStatus::Modified,
10956 ],
10957 indoc! {r#"struct Row;
10958 ˇstruct Row1;
10959 struct Row2;
10960 ˇ
10961 struct Row4;
10962 ˇstruct Row5;
10963 struct Row6;
10964 ˇ
10965 struct Row8;
10966 ˇstruct Row9;
10967 struct Row10;ˇ"#},
10968 base_text,
10969 &mut cx,
10970 );
10971}
10972
10973#[gpui::test]
10974async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
10975 init_test(cx, |_| {});
10976 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10977 let base_text = indoc! {r#"struct Row;
10978struct Row1;
10979struct Row2;
10980
10981struct Row4;
10982struct Row5;
10983struct Row6;
10984
10985struct Row8;
10986struct Row9;
10987struct Row10;"#};
10988
10989 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
10990 assert_hunk_revert(
10991 indoc! {r#"struct Row;
10992 struct Row2;
10993
10994 ˇstruct Row4;
10995 struct Row5;
10996 struct Row6;
10997 ˇ
10998 struct Row8;
10999 struct Row10;"#},
11000 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11001 indoc! {r#"struct Row;
11002 struct Row2;
11003
11004 ˇstruct Row4;
11005 struct Row5;
11006 struct Row6;
11007 ˇ
11008 struct Row8;
11009 struct Row10;"#},
11010 base_text,
11011 &mut cx,
11012 );
11013 assert_hunk_revert(
11014 indoc! {r#"struct Row;
11015 struct Row2;
11016
11017 «ˇstruct Row4;
11018 struct» Row5;
11019 «struct Row6;
11020 ˇ»
11021 struct Row8;
11022 struct Row10;"#},
11023 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11024 indoc! {r#"struct Row;
11025 struct Row2;
11026
11027 «ˇstruct Row4;
11028 struct» Row5;
11029 «struct Row6;
11030 ˇ»
11031 struct Row8;
11032 struct Row10;"#},
11033 base_text,
11034 &mut cx,
11035 );
11036
11037 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
11038 assert_hunk_revert(
11039 indoc! {r#"struct Row;
11040 ˇstruct Row2;
11041
11042 struct Row4;
11043 struct Row5;
11044 struct Row6;
11045
11046 struct Row8;ˇ
11047 struct Row10;"#},
11048 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11049 indoc! {r#"struct Row;
11050 struct Row1;
11051 ˇstruct Row2;
11052
11053 struct Row4;
11054 struct Row5;
11055 struct Row6;
11056
11057 struct Row8;ˇ
11058 struct Row9;
11059 struct Row10;"#},
11060 base_text,
11061 &mut cx,
11062 );
11063 assert_hunk_revert(
11064 indoc! {r#"struct Row;
11065 struct Row2«ˇ;
11066 struct Row4;
11067 struct» Row5;
11068 «struct Row6;
11069
11070 struct Row8;ˇ»
11071 struct Row10;"#},
11072 vec![
11073 DiffHunkStatus::Removed,
11074 DiffHunkStatus::Removed,
11075 DiffHunkStatus::Removed,
11076 ],
11077 indoc! {r#"struct Row;
11078 struct Row1;
11079 struct Row2«ˇ;
11080
11081 struct Row4;
11082 struct» Row5;
11083 «struct Row6;
11084
11085 struct Row8;ˇ»
11086 struct Row9;
11087 struct Row10;"#},
11088 base_text,
11089 &mut cx,
11090 );
11091}
11092
11093#[gpui::test]
11094async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
11095 init_test(cx, |_| {});
11096
11097 let cols = 4;
11098 let rows = 10;
11099 let sample_text_1 = sample_text(rows, cols, 'a');
11100 assert_eq!(
11101 sample_text_1,
11102 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11103 );
11104 let sample_text_2 = sample_text(rows, cols, 'l');
11105 assert_eq!(
11106 sample_text_2,
11107 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11108 );
11109 let sample_text_3 = sample_text(rows, cols, 'v');
11110 assert_eq!(
11111 sample_text_3,
11112 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11113 );
11114
11115 fn diff_every_buffer_row(
11116 buffer: &Model<Buffer>,
11117 sample_text: String,
11118 cols: usize,
11119 cx: &mut gpui::TestAppContext,
11120 ) {
11121 // revert first character in each row, creating one large diff hunk per buffer
11122 let is_first_char = |offset: usize| offset % cols == 0;
11123 buffer.update(cx, |buffer, cx| {
11124 buffer.set_text(
11125 sample_text
11126 .chars()
11127 .enumerate()
11128 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
11129 .collect::<String>(),
11130 cx,
11131 );
11132 buffer.set_diff_base(Some(sample_text), cx);
11133 });
11134 cx.executor().run_until_parked();
11135 }
11136
11137 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11138 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
11139
11140 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11141 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
11142
11143 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11144 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
11145
11146 let multibuffer = cx.new_model(|cx| {
11147 let mut multibuffer = MultiBuffer::new(ReadWrite);
11148 multibuffer.push_excerpts(
11149 buffer_1.clone(),
11150 [
11151 ExcerptRange {
11152 context: Point::new(0, 0)..Point::new(3, 0),
11153 primary: None,
11154 },
11155 ExcerptRange {
11156 context: Point::new(5, 0)..Point::new(7, 0),
11157 primary: None,
11158 },
11159 ExcerptRange {
11160 context: Point::new(9, 0)..Point::new(10, 4),
11161 primary: None,
11162 },
11163 ],
11164 cx,
11165 );
11166 multibuffer.push_excerpts(
11167 buffer_2.clone(),
11168 [
11169 ExcerptRange {
11170 context: Point::new(0, 0)..Point::new(3, 0),
11171 primary: None,
11172 },
11173 ExcerptRange {
11174 context: Point::new(5, 0)..Point::new(7, 0),
11175 primary: None,
11176 },
11177 ExcerptRange {
11178 context: Point::new(9, 0)..Point::new(10, 4),
11179 primary: None,
11180 },
11181 ],
11182 cx,
11183 );
11184 multibuffer.push_excerpts(
11185 buffer_3.clone(),
11186 [
11187 ExcerptRange {
11188 context: Point::new(0, 0)..Point::new(3, 0),
11189 primary: None,
11190 },
11191 ExcerptRange {
11192 context: Point::new(5, 0)..Point::new(7, 0),
11193 primary: None,
11194 },
11195 ExcerptRange {
11196 context: Point::new(9, 0)..Point::new(10, 4),
11197 primary: None,
11198 },
11199 ],
11200 cx,
11201 );
11202 multibuffer
11203 });
11204
11205 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
11206 editor.update(cx, |editor, cx| {
11207 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");
11208 editor.select_all(&SelectAll, cx);
11209 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11210 });
11211 cx.executor().run_until_parked();
11212 // When all ranges are selected, all buffer hunks are reverted.
11213 editor.update(cx, |editor, cx| {
11214 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");
11215 });
11216 buffer_1.update(cx, |buffer, _| {
11217 assert_eq!(buffer.text(), sample_text_1);
11218 });
11219 buffer_2.update(cx, |buffer, _| {
11220 assert_eq!(buffer.text(), sample_text_2);
11221 });
11222 buffer_3.update(cx, |buffer, _| {
11223 assert_eq!(buffer.text(), sample_text_3);
11224 });
11225
11226 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
11227 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
11228 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
11229 editor.update(cx, |editor, cx| {
11230 editor.change_selections(None, cx, |s| {
11231 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
11232 });
11233 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11234 });
11235 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
11236 // but not affect buffer_2 and its related excerpts.
11237 editor.update(cx, |editor, cx| {
11238 assert_eq!(
11239 editor.text(cx),
11240 "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"
11241 );
11242 });
11243 buffer_1.update(cx, |buffer, _| {
11244 assert_eq!(buffer.text(), sample_text_1);
11245 });
11246 buffer_2.update(cx, |buffer, _| {
11247 assert_eq!(
11248 buffer.text(),
11249 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
11250 );
11251 });
11252 buffer_3.update(cx, |buffer, _| {
11253 assert_eq!(
11254 buffer.text(),
11255 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
11256 );
11257 });
11258}
11259
11260#[gpui::test]
11261async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
11262 init_test(cx, |_| {});
11263
11264 let cols = 4;
11265 let rows = 10;
11266 let sample_text_1 = sample_text(rows, cols, 'a');
11267 assert_eq!(
11268 sample_text_1,
11269 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11270 );
11271 let sample_text_2 = sample_text(rows, cols, 'l');
11272 assert_eq!(
11273 sample_text_2,
11274 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11275 );
11276 let sample_text_3 = sample_text(rows, cols, 'v');
11277 assert_eq!(
11278 sample_text_3,
11279 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11280 );
11281
11282 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11283 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11284 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11285
11286 let multi_buffer = cx.new_model(|cx| {
11287 let mut multibuffer = MultiBuffer::new(ReadWrite);
11288 multibuffer.push_excerpts(
11289 buffer_1.clone(),
11290 [
11291 ExcerptRange {
11292 context: Point::new(0, 0)..Point::new(3, 0),
11293 primary: None,
11294 },
11295 ExcerptRange {
11296 context: Point::new(5, 0)..Point::new(7, 0),
11297 primary: None,
11298 },
11299 ExcerptRange {
11300 context: Point::new(9, 0)..Point::new(10, 4),
11301 primary: None,
11302 },
11303 ],
11304 cx,
11305 );
11306 multibuffer.push_excerpts(
11307 buffer_2.clone(),
11308 [
11309 ExcerptRange {
11310 context: Point::new(0, 0)..Point::new(3, 0),
11311 primary: None,
11312 },
11313 ExcerptRange {
11314 context: Point::new(5, 0)..Point::new(7, 0),
11315 primary: None,
11316 },
11317 ExcerptRange {
11318 context: Point::new(9, 0)..Point::new(10, 4),
11319 primary: None,
11320 },
11321 ],
11322 cx,
11323 );
11324 multibuffer.push_excerpts(
11325 buffer_3.clone(),
11326 [
11327 ExcerptRange {
11328 context: Point::new(0, 0)..Point::new(3, 0),
11329 primary: None,
11330 },
11331 ExcerptRange {
11332 context: Point::new(5, 0)..Point::new(7, 0),
11333 primary: None,
11334 },
11335 ExcerptRange {
11336 context: Point::new(9, 0)..Point::new(10, 4),
11337 primary: None,
11338 },
11339 ],
11340 cx,
11341 );
11342 multibuffer
11343 });
11344
11345 let fs = FakeFs::new(cx.executor());
11346 fs.insert_tree(
11347 "/a",
11348 json!({
11349 "main.rs": sample_text_1,
11350 "other.rs": sample_text_2,
11351 "lib.rs": sample_text_3,
11352 }),
11353 )
11354 .await;
11355 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11356 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11357 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11358 let multi_buffer_editor = cx.new_view(|cx| {
11359 Editor::new(
11360 EditorMode::Full,
11361 multi_buffer,
11362 Some(project.clone()),
11363 true,
11364 cx,
11365 )
11366 });
11367 let multibuffer_item_id = workspace
11368 .update(cx, |workspace, cx| {
11369 assert!(
11370 workspace.active_item(cx).is_none(),
11371 "active item should be None before the first item is added"
11372 );
11373 workspace.add_item_to_active_pane(
11374 Box::new(multi_buffer_editor.clone()),
11375 None,
11376 true,
11377 cx,
11378 );
11379 let active_item = workspace
11380 .active_item(cx)
11381 .expect("should have an active item after adding the multi buffer");
11382 assert!(
11383 !active_item.is_singleton(cx),
11384 "A multi buffer was expected to active after adding"
11385 );
11386 active_item.item_id()
11387 })
11388 .unwrap();
11389 cx.executor().run_until_parked();
11390
11391 multi_buffer_editor.update(cx, |editor, cx| {
11392 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
11393 editor.open_excerpts(&OpenExcerpts, cx);
11394 });
11395 cx.executor().run_until_parked();
11396 let first_item_id = workspace
11397 .update(cx, |workspace, cx| {
11398 let active_item = workspace
11399 .active_item(cx)
11400 .expect("should have an active item after navigating into the 1st buffer");
11401 let first_item_id = active_item.item_id();
11402 assert_ne!(
11403 first_item_id, multibuffer_item_id,
11404 "Should navigate into the 1st buffer and activate it"
11405 );
11406 assert!(
11407 active_item.is_singleton(cx),
11408 "New active item should be a singleton buffer"
11409 );
11410 assert_eq!(
11411 active_item
11412 .act_as::<Editor>(cx)
11413 .expect("should have navigated into an editor for the 1st buffer")
11414 .read(cx)
11415 .text(cx),
11416 sample_text_1
11417 );
11418
11419 workspace
11420 .go_back(workspace.active_pane().downgrade(), cx)
11421 .detach_and_log_err(cx);
11422
11423 first_item_id
11424 })
11425 .unwrap();
11426 cx.executor().run_until_parked();
11427 workspace
11428 .update(cx, |workspace, cx| {
11429 let active_item = workspace
11430 .active_item(cx)
11431 .expect("should have an active item after navigating back");
11432 assert_eq!(
11433 active_item.item_id(),
11434 multibuffer_item_id,
11435 "Should navigate back to the multi buffer"
11436 );
11437 assert!(!active_item.is_singleton(cx));
11438 })
11439 .unwrap();
11440
11441 multi_buffer_editor.update(cx, |editor, cx| {
11442 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11443 s.select_ranges(Some(39..40))
11444 });
11445 editor.open_excerpts(&OpenExcerpts, cx);
11446 });
11447 cx.executor().run_until_parked();
11448 let second_item_id = workspace
11449 .update(cx, |workspace, cx| {
11450 let active_item = workspace
11451 .active_item(cx)
11452 .expect("should have an active item after navigating into the 2nd buffer");
11453 let second_item_id = active_item.item_id();
11454 assert_ne!(
11455 second_item_id, multibuffer_item_id,
11456 "Should navigate away from the multibuffer"
11457 );
11458 assert_ne!(
11459 second_item_id, first_item_id,
11460 "Should navigate into the 2nd buffer and activate it"
11461 );
11462 assert!(
11463 active_item.is_singleton(cx),
11464 "New active item should be a singleton buffer"
11465 );
11466 assert_eq!(
11467 active_item
11468 .act_as::<Editor>(cx)
11469 .expect("should have navigated into an editor")
11470 .read(cx)
11471 .text(cx),
11472 sample_text_2
11473 );
11474
11475 workspace
11476 .go_back(workspace.active_pane().downgrade(), cx)
11477 .detach_and_log_err(cx);
11478
11479 second_item_id
11480 })
11481 .unwrap();
11482 cx.executor().run_until_parked();
11483 workspace
11484 .update(cx, |workspace, cx| {
11485 let active_item = workspace
11486 .active_item(cx)
11487 .expect("should have an active item after navigating back from the 2nd buffer");
11488 assert_eq!(
11489 active_item.item_id(),
11490 multibuffer_item_id,
11491 "Should navigate back from the 2nd buffer to the multi buffer"
11492 );
11493 assert!(!active_item.is_singleton(cx));
11494 })
11495 .unwrap();
11496
11497 multi_buffer_editor.update(cx, |editor, cx| {
11498 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11499 s.select_ranges(Some(60..70))
11500 });
11501 editor.open_excerpts(&OpenExcerpts, cx);
11502 });
11503 cx.executor().run_until_parked();
11504 workspace
11505 .update(cx, |workspace, cx| {
11506 let active_item = workspace
11507 .active_item(cx)
11508 .expect("should have an active item after navigating into the 3rd buffer");
11509 let third_item_id = active_item.item_id();
11510 assert_ne!(
11511 third_item_id, multibuffer_item_id,
11512 "Should navigate into the 3rd buffer and activate it"
11513 );
11514 assert_ne!(third_item_id, first_item_id);
11515 assert_ne!(third_item_id, second_item_id);
11516 assert!(
11517 active_item.is_singleton(cx),
11518 "New active item should be a singleton buffer"
11519 );
11520 assert_eq!(
11521 active_item
11522 .act_as::<Editor>(cx)
11523 .expect("should have navigated into an editor")
11524 .read(cx)
11525 .text(cx),
11526 sample_text_3
11527 );
11528
11529 workspace
11530 .go_back(workspace.active_pane().downgrade(), cx)
11531 .detach_and_log_err(cx);
11532 })
11533 .unwrap();
11534 cx.executor().run_until_parked();
11535 workspace
11536 .update(cx, |workspace, cx| {
11537 let active_item = workspace
11538 .active_item(cx)
11539 .expect("should have an active item after navigating back from the 3rd buffer");
11540 assert_eq!(
11541 active_item.item_id(),
11542 multibuffer_item_id,
11543 "Should navigate back from the 3rd buffer to the multi buffer"
11544 );
11545 assert!(!active_item.is_singleton(cx));
11546 })
11547 .unwrap();
11548}
11549
11550#[gpui::test]
11551async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11552 init_test(cx, |_| {});
11553
11554 let mut cx = EditorTestContext::new(cx).await;
11555
11556 let diff_base = r#"
11557 use some::mod;
11558
11559 const A: u32 = 42;
11560
11561 fn main() {
11562 println!("hello");
11563
11564 println!("world");
11565 }
11566 "#
11567 .unindent();
11568
11569 cx.set_state(
11570 &r#"
11571 use some::modified;
11572
11573 ˇ
11574 fn main() {
11575 println!("hello there");
11576
11577 println!("around the");
11578 println!("world");
11579 }
11580 "#
11581 .unindent(),
11582 );
11583
11584 cx.set_diff_base(Some(&diff_base));
11585 executor.run_until_parked();
11586
11587 cx.update_editor(|editor, cx| {
11588 editor.go_to_next_hunk(&GoToHunk, cx);
11589 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11590 });
11591 executor.run_until_parked();
11592 cx.assert_diff_hunks(
11593 r#"
11594 use some::modified;
11595
11596
11597 fn main() {
11598 - println!("hello");
11599 + println!("hello there");
11600
11601 println!("around the");
11602 println!("world");
11603 }
11604 "#
11605 .unindent(),
11606 );
11607
11608 cx.update_editor(|editor, cx| {
11609 for _ in 0..3 {
11610 editor.go_to_next_hunk(&GoToHunk, cx);
11611 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11612 }
11613 });
11614 executor.run_until_parked();
11615 cx.assert_editor_state(
11616 &r#"
11617 use some::modified;
11618
11619 ˇ
11620 fn main() {
11621 println!("hello there");
11622
11623 println!("around the");
11624 println!("world");
11625 }
11626 "#
11627 .unindent(),
11628 );
11629
11630 cx.assert_diff_hunks(
11631 r#"
11632 - use some::mod;
11633 + use some::modified;
11634
11635 - const A: u32 = 42;
11636
11637 fn main() {
11638 - println!("hello");
11639 + println!("hello there");
11640
11641 + println!("around the");
11642 println!("world");
11643 }
11644 "#
11645 .unindent(),
11646 );
11647
11648 cx.update_editor(|editor, cx| {
11649 editor.cancel(&Cancel, cx);
11650 });
11651
11652 cx.assert_diff_hunks(
11653 r#"
11654 use some::modified;
11655
11656
11657 fn main() {
11658 println!("hello there");
11659
11660 println!("around the");
11661 println!("world");
11662 }
11663 "#
11664 .unindent(),
11665 );
11666}
11667
11668#[gpui::test]
11669async fn test_diff_base_change_with_expanded_diff_hunks(
11670 executor: BackgroundExecutor,
11671 cx: &mut gpui::TestAppContext,
11672) {
11673 init_test(cx, |_| {});
11674
11675 let mut cx = EditorTestContext::new(cx).await;
11676
11677 let diff_base = r#"
11678 use some::mod1;
11679 use some::mod2;
11680
11681 const A: u32 = 42;
11682 const B: u32 = 42;
11683 const C: u32 = 42;
11684
11685 fn main() {
11686 println!("hello");
11687
11688 println!("world");
11689 }
11690 "#
11691 .unindent();
11692
11693 cx.set_state(
11694 &r#"
11695 use some::mod2;
11696
11697 const A: u32 = 42;
11698 const C: u32 = 42;
11699
11700 fn main(ˇ) {
11701 //println!("hello");
11702
11703 println!("world");
11704 //
11705 //
11706 }
11707 "#
11708 .unindent(),
11709 );
11710
11711 cx.set_diff_base(Some(&diff_base));
11712 executor.run_until_parked();
11713
11714 cx.update_editor(|editor, cx| {
11715 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11716 });
11717 executor.run_until_parked();
11718 cx.assert_diff_hunks(
11719 r#"
11720 - use some::mod1;
11721 use some::mod2;
11722
11723 const A: u32 = 42;
11724 - const B: u32 = 42;
11725 const C: u32 = 42;
11726
11727 fn main() {
11728 - println!("hello");
11729 + //println!("hello");
11730
11731 println!("world");
11732 + //
11733 + //
11734 }
11735 "#
11736 .unindent(),
11737 );
11738
11739 cx.set_diff_base(Some("new diff base!"));
11740 executor.run_until_parked();
11741 cx.assert_diff_hunks(
11742 r#"
11743 use some::mod2;
11744
11745 const A: u32 = 42;
11746 const C: u32 = 42;
11747
11748 fn main() {
11749 //println!("hello");
11750
11751 println!("world");
11752 //
11753 //
11754 }
11755 "#
11756 .unindent(),
11757 );
11758
11759 cx.update_editor(|editor, cx| {
11760 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11761 });
11762 executor.run_until_parked();
11763 cx.assert_diff_hunks(
11764 r#"
11765 - new diff base!
11766 + use some::mod2;
11767 +
11768 + const A: u32 = 42;
11769 + const C: u32 = 42;
11770 +
11771 + fn main() {
11772 + //println!("hello");
11773 +
11774 + println!("world");
11775 + //
11776 + //
11777 + }
11778 "#
11779 .unindent(),
11780 );
11781}
11782
11783#[gpui::test]
11784async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11785 init_test(cx, |_| {});
11786
11787 let mut cx = EditorTestContext::new(cx).await;
11788
11789 let diff_base = r#"
11790 use some::mod1;
11791 use some::mod2;
11792
11793 const A: u32 = 42;
11794 const B: u32 = 42;
11795 const C: u32 = 42;
11796
11797 fn main() {
11798 println!("hello");
11799
11800 println!("world");
11801 }
11802
11803 fn another() {
11804 println!("another");
11805 }
11806
11807 fn another2() {
11808 println!("another2");
11809 }
11810 "#
11811 .unindent();
11812
11813 cx.set_state(
11814 &r#"
11815 «use some::mod2;
11816
11817 const A: u32 = 42;
11818 const C: u32 = 42;
11819
11820 fn main() {
11821 //println!("hello");
11822
11823 println!("world");
11824 //
11825 //ˇ»
11826 }
11827
11828 fn another() {
11829 println!("another");
11830 println!("another");
11831 }
11832
11833 println!("another2");
11834 }
11835 "#
11836 .unindent(),
11837 );
11838
11839 cx.set_diff_base(Some(&diff_base));
11840 executor.run_until_parked();
11841
11842 cx.update_editor(|editor, cx| {
11843 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11844 });
11845 executor.run_until_parked();
11846
11847 cx.assert_diff_hunks(
11848 r#"
11849 - use some::mod1;
11850 use some::mod2;
11851
11852 const A: u32 = 42;
11853 - const B: u32 = 42;
11854 const C: u32 = 42;
11855
11856 fn main() {
11857 - println!("hello");
11858 + //println!("hello");
11859
11860 println!("world");
11861 + //
11862 + //
11863 }
11864
11865 fn another() {
11866 println!("another");
11867 + println!("another");
11868 }
11869
11870 - fn another2() {
11871 println!("another2");
11872 }
11873 "#
11874 .unindent(),
11875 );
11876
11877 // Fold across some of the diff hunks. They should no longer appear expanded.
11878 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
11879 cx.executor().run_until_parked();
11880
11881 // Hunks are not shown if their position is within a fold
11882 cx.assert_diff_hunks(
11883 r#"
11884 use some::mod2;
11885
11886 const A: u32 = 42;
11887 const C: u32 = 42;
11888
11889 fn main() {
11890 //println!("hello");
11891
11892 println!("world");
11893 //
11894 //
11895 }
11896
11897 fn another() {
11898 println!("another");
11899 + println!("another");
11900 }
11901
11902 - fn another2() {
11903 println!("another2");
11904 }
11905 "#
11906 .unindent(),
11907 );
11908
11909 cx.update_editor(|editor, cx| {
11910 editor.select_all(&SelectAll, cx);
11911 editor.unfold_lines(&UnfoldLines, cx);
11912 });
11913 cx.executor().run_until_parked();
11914
11915 // The deletions reappear when unfolding.
11916 cx.assert_diff_hunks(
11917 r#"
11918 - use some::mod1;
11919 use some::mod2;
11920
11921 const A: u32 = 42;
11922 - const B: u32 = 42;
11923 const C: u32 = 42;
11924
11925 fn main() {
11926 - println!("hello");
11927 + //println!("hello");
11928
11929 println!("world");
11930 + //
11931 + //
11932 }
11933
11934 fn another() {
11935 println!("another");
11936 + println!("another");
11937 }
11938
11939 - fn another2() {
11940 println!("another2");
11941 }
11942 "#
11943 .unindent(),
11944 );
11945}
11946
11947#[gpui::test]
11948async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
11949 init_test(cx, |_| {});
11950
11951 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
11952 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
11953 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
11954 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
11955 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
11956 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
11957
11958 let buffer_1 = cx.new_model(|cx| {
11959 let mut buffer = Buffer::local(file_1_new.to_string(), cx);
11960 buffer.set_diff_base(Some(file_1_old.into()), cx);
11961 buffer
11962 });
11963 let buffer_2 = cx.new_model(|cx| {
11964 let mut buffer = Buffer::local(file_2_new.to_string(), cx);
11965 buffer.set_diff_base(Some(file_2_old.into()), cx);
11966 buffer
11967 });
11968 let buffer_3 = cx.new_model(|cx| {
11969 let mut buffer = Buffer::local(file_3_new.to_string(), cx);
11970 buffer.set_diff_base(Some(file_3_old.into()), cx);
11971 buffer
11972 });
11973
11974 let multi_buffer = cx.new_model(|cx| {
11975 let mut multibuffer = MultiBuffer::new(ReadWrite);
11976 multibuffer.push_excerpts(
11977 buffer_1.clone(),
11978 [
11979 ExcerptRange {
11980 context: Point::new(0, 0)..Point::new(3, 0),
11981 primary: None,
11982 },
11983 ExcerptRange {
11984 context: Point::new(5, 0)..Point::new(7, 0),
11985 primary: None,
11986 },
11987 ExcerptRange {
11988 context: Point::new(9, 0)..Point::new(10, 3),
11989 primary: None,
11990 },
11991 ],
11992 cx,
11993 );
11994 multibuffer.push_excerpts(
11995 buffer_2.clone(),
11996 [
11997 ExcerptRange {
11998 context: Point::new(0, 0)..Point::new(3, 0),
11999 primary: None,
12000 },
12001 ExcerptRange {
12002 context: Point::new(5, 0)..Point::new(7, 0),
12003 primary: None,
12004 },
12005 ExcerptRange {
12006 context: Point::new(9, 0)..Point::new(10, 3),
12007 primary: None,
12008 },
12009 ],
12010 cx,
12011 );
12012 multibuffer.push_excerpts(
12013 buffer_3.clone(),
12014 [
12015 ExcerptRange {
12016 context: Point::new(0, 0)..Point::new(3, 0),
12017 primary: None,
12018 },
12019 ExcerptRange {
12020 context: Point::new(5, 0)..Point::new(7, 0),
12021 primary: None,
12022 },
12023 ExcerptRange {
12024 context: Point::new(9, 0)..Point::new(10, 3),
12025 primary: None,
12026 },
12027 ],
12028 cx,
12029 );
12030 multibuffer
12031 });
12032
12033 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12034 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12035 cx.run_until_parked();
12036
12037 cx.assert_editor_state(
12038 &"
12039 ˇaaa
12040 ccc
12041 ddd
12042
12043 ggg
12044 hhh
12045
12046
12047 lll
12048 mmm
12049 NNN
12050
12051 qqq
12052 rrr
12053
12054 uuu
12055 111
12056 222
12057 333
12058
12059 666
12060 777
12061
12062 000
12063 !!!"
12064 .unindent(),
12065 );
12066
12067 cx.update_editor(|editor, cx| {
12068 editor.select_all(&SelectAll, cx);
12069 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12070 });
12071 cx.executor().run_until_parked();
12072
12073 cx.assert_diff_hunks(
12074 "
12075 aaa
12076 - bbb
12077 ccc
12078 ddd
12079
12080 ggg
12081 hhh
12082
12083
12084 lll
12085 mmm
12086 - nnn
12087 + NNN
12088
12089 qqq
12090 rrr
12091
12092 uuu
12093 111
12094 222
12095 333
12096
12097 + 666
12098 777
12099
12100 000
12101 !!!"
12102 .unindent(),
12103 );
12104}
12105
12106#[gpui::test]
12107async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
12108 init_test(cx, |_| {});
12109
12110 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
12111 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
12112
12113 let buffer = cx.new_model(|cx| {
12114 let mut buffer = Buffer::local(text.to_string(), cx);
12115 buffer.set_diff_base(Some(base.into()), cx);
12116 buffer
12117 });
12118
12119 let multi_buffer = cx.new_model(|cx| {
12120 let mut multibuffer = MultiBuffer::new(ReadWrite);
12121 multibuffer.push_excerpts(
12122 buffer.clone(),
12123 [
12124 ExcerptRange {
12125 context: Point::new(0, 0)..Point::new(2, 0),
12126 primary: None,
12127 },
12128 ExcerptRange {
12129 context: Point::new(5, 0)..Point::new(7, 0),
12130 primary: None,
12131 },
12132 ],
12133 cx,
12134 );
12135 multibuffer
12136 });
12137
12138 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12139 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12140 cx.run_until_parked();
12141
12142 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
12143 cx.executor().run_until_parked();
12144
12145 cx.assert_diff_hunks(
12146 "
12147 aaa
12148 - bbb
12149 + BBB
12150
12151 - ddd
12152 - eee
12153 + EEE
12154 fff
12155 "
12156 .unindent(),
12157 );
12158}
12159
12160#[gpui::test]
12161async fn test_edits_around_expanded_insertion_hunks(
12162 executor: BackgroundExecutor,
12163 cx: &mut gpui::TestAppContext,
12164) {
12165 init_test(cx, |_| {});
12166
12167 let mut cx = EditorTestContext::new(cx).await;
12168
12169 let diff_base = r#"
12170 use some::mod1;
12171 use some::mod2;
12172
12173 const A: u32 = 42;
12174
12175 fn main() {
12176 println!("hello");
12177
12178 println!("world");
12179 }
12180 "#
12181 .unindent();
12182 executor.run_until_parked();
12183 cx.set_state(
12184 &r#"
12185 use some::mod1;
12186 use some::mod2;
12187
12188 const A: u32 = 42;
12189 const B: u32 = 42;
12190 const C: u32 = 42;
12191 ˇ
12192
12193 fn main() {
12194 println!("hello");
12195
12196 println!("world");
12197 }
12198 "#
12199 .unindent(),
12200 );
12201
12202 cx.set_diff_base(Some(&diff_base));
12203 executor.run_until_parked();
12204
12205 cx.update_editor(|editor, cx| {
12206 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12207 });
12208 executor.run_until_parked();
12209
12210 cx.assert_diff_hunks(
12211 r#"
12212 use some::mod1;
12213 use some::mod2;
12214
12215 const A: u32 = 42;
12216 + const B: u32 = 42;
12217 + const C: u32 = 42;
12218 +
12219
12220 fn main() {
12221 println!("hello");
12222
12223 println!("world");
12224 }
12225 "#
12226 .unindent(),
12227 );
12228
12229 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
12230 executor.run_until_parked();
12231
12232 cx.assert_diff_hunks(
12233 r#"
12234 use some::mod1;
12235 use some::mod2;
12236
12237 const A: u32 = 42;
12238 + const B: u32 = 42;
12239 + const C: u32 = 42;
12240 + const D: u32 = 42;
12241 +
12242
12243 fn main() {
12244 println!("hello");
12245
12246 println!("world");
12247 }
12248 "#
12249 .unindent(),
12250 );
12251
12252 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
12253 executor.run_until_parked();
12254
12255 cx.assert_diff_hunks(
12256 r#"
12257 use some::mod1;
12258 use some::mod2;
12259
12260 const A: u32 = 42;
12261 + const B: u32 = 42;
12262 + const C: u32 = 42;
12263 + const D: u32 = 42;
12264 + const E: u32 = 42;
12265 +
12266
12267 fn main() {
12268 println!("hello");
12269
12270 println!("world");
12271 }
12272 "#
12273 .unindent(),
12274 );
12275
12276 cx.update_editor(|editor, cx| {
12277 editor.delete_line(&DeleteLine, cx);
12278 });
12279 executor.run_until_parked();
12280
12281 cx.assert_diff_hunks(
12282 r#"
12283 use some::mod1;
12284 use some::mod2;
12285
12286 const A: u32 = 42;
12287 + const B: u32 = 42;
12288 + const C: u32 = 42;
12289 + const D: u32 = 42;
12290 + const E: u32 = 42;
12291
12292 fn main() {
12293 println!("hello");
12294
12295 println!("world");
12296 }
12297 "#
12298 .unindent(),
12299 );
12300
12301 cx.update_editor(|editor, cx| {
12302 editor.move_up(&MoveUp, cx);
12303 editor.delete_line(&DeleteLine, cx);
12304 editor.move_up(&MoveUp, cx);
12305 editor.delete_line(&DeleteLine, cx);
12306 editor.move_up(&MoveUp, cx);
12307 editor.delete_line(&DeleteLine, cx);
12308 });
12309 executor.run_until_parked();
12310 cx.assert_editor_state(
12311 &r#"
12312 use some::mod1;
12313 use some::mod2;
12314
12315 const A: u32 = 42;
12316 const B: u32 = 42;
12317 ˇ
12318 fn main() {
12319 println!("hello");
12320
12321 println!("world");
12322 }
12323 "#
12324 .unindent(),
12325 );
12326
12327 cx.assert_diff_hunks(
12328 r#"
12329 use some::mod1;
12330 use some::mod2;
12331
12332 const A: u32 = 42;
12333 + const B: u32 = 42;
12334
12335 fn main() {
12336 println!("hello");
12337
12338 println!("world");
12339 }
12340 "#
12341 .unindent(),
12342 );
12343
12344 cx.update_editor(|editor, cx| {
12345 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
12346 editor.delete_line(&DeleteLine, cx);
12347 });
12348 executor.run_until_parked();
12349 cx.assert_diff_hunks(
12350 r#"
12351 use some::mod1;
12352 - use some::mod2;
12353 -
12354 - const A: u32 = 42;
12355
12356 fn main() {
12357 println!("hello");
12358
12359 println!("world");
12360 }
12361 "#
12362 .unindent(),
12363 );
12364}
12365
12366#[gpui::test]
12367async fn test_edits_around_expanded_deletion_hunks(
12368 executor: BackgroundExecutor,
12369 cx: &mut gpui::TestAppContext,
12370) {
12371 init_test(cx, |_| {});
12372
12373 let mut cx = EditorTestContext::new(cx).await;
12374
12375 let diff_base = r#"
12376 use some::mod1;
12377 use some::mod2;
12378
12379 const A: u32 = 42;
12380 const B: u32 = 42;
12381 const C: u32 = 42;
12382
12383
12384 fn main() {
12385 println!("hello");
12386
12387 println!("world");
12388 }
12389 "#
12390 .unindent();
12391 executor.run_until_parked();
12392 cx.set_state(
12393 &r#"
12394 use some::mod1;
12395 use some::mod2;
12396
12397 ˇconst B: u32 = 42;
12398 const C: u32 = 42;
12399
12400
12401 fn main() {
12402 println!("hello");
12403
12404 println!("world");
12405 }
12406 "#
12407 .unindent(),
12408 );
12409
12410 cx.set_diff_base(Some(&diff_base));
12411 executor.run_until_parked();
12412
12413 cx.update_editor(|editor, cx| {
12414 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12415 });
12416 executor.run_until_parked();
12417
12418 cx.assert_diff_hunks(
12419 r#"
12420 use some::mod1;
12421 use some::mod2;
12422
12423 - const A: u32 = 42;
12424 const B: u32 = 42;
12425 const C: u32 = 42;
12426
12427
12428 fn main() {
12429 println!("hello");
12430
12431 println!("world");
12432 }
12433 "#
12434 .unindent(),
12435 );
12436
12437 cx.update_editor(|editor, cx| {
12438 editor.delete_line(&DeleteLine, cx);
12439 });
12440 executor.run_until_parked();
12441 cx.assert_editor_state(
12442 &r#"
12443 use some::mod1;
12444 use some::mod2;
12445
12446 ˇconst C: u32 = 42;
12447
12448
12449 fn main() {
12450 println!("hello");
12451
12452 println!("world");
12453 }
12454 "#
12455 .unindent(),
12456 );
12457 cx.assert_diff_hunks(
12458 r#"
12459 use some::mod1;
12460 use some::mod2;
12461
12462 - const A: u32 = 42;
12463 - const B: u32 = 42;
12464 const C: u32 = 42;
12465
12466
12467 fn main() {
12468 println!("hello");
12469
12470 println!("world");
12471 }
12472 "#
12473 .unindent(),
12474 );
12475
12476 cx.update_editor(|editor, cx| {
12477 editor.delete_line(&DeleteLine, cx);
12478 });
12479 executor.run_until_parked();
12480 cx.assert_editor_state(
12481 &r#"
12482 use some::mod1;
12483 use some::mod2;
12484
12485 ˇ
12486
12487 fn main() {
12488 println!("hello");
12489
12490 println!("world");
12491 }
12492 "#
12493 .unindent(),
12494 );
12495 cx.assert_diff_hunks(
12496 r#"
12497 use some::mod1;
12498 use some::mod2;
12499
12500 - const A: u32 = 42;
12501 - const B: u32 = 42;
12502 - const C: u32 = 42;
12503
12504
12505 fn main() {
12506 println!("hello");
12507
12508 println!("world");
12509 }
12510 "#
12511 .unindent(),
12512 );
12513
12514 cx.update_editor(|editor, cx| {
12515 editor.handle_input("replacement", cx);
12516 });
12517 executor.run_until_parked();
12518 cx.assert_editor_state(
12519 &r#"
12520 use some::mod1;
12521 use some::mod2;
12522
12523 replacementˇ
12524
12525 fn main() {
12526 println!("hello");
12527
12528 println!("world");
12529 }
12530 "#
12531 .unindent(),
12532 );
12533 cx.assert_diff_hunks(
12534 r#"
12535 use some::mod1;
12536 use some::mod2;
12537
12538 - const A: u32 = 42;
12539 - const B: u32 = 42;
12540 - const C: u32 = 42;
12541 -
12542 + replacement
12543
12544 fn main() {
12545 println!("hello");
12546
12547 println!("world");
12548 }
12549 "#
12550 .unindent(),
12551 );
12552}
12553
12554#[gpui::test]
12555async fn test_edit_after_expanded_modification_hunk(
12556 executor: BackgroundExecutor,
12557 cx: &mut gpui::TestAppContext,
12558) {
12559 init_test(cx, |_| {});
12560
12561 let mut cx = EditorTestContext::new(cx).await;
12562
12563 let diff_base = r#"
12564 use some::mod1;
12565 use some::mod2;
12566
12567 const A: u32 = 42;
12568 const B: u32 = 42;
12569 const C: u32 = 42;
12570 const D: u32 = 42;
12571
12572
12573 fn main() {
12574 println!("hello");
12575
12576 println!("world");
12577 }"#
12578 .unindent();
12579
12580 cx.set_state(
12581 &r#"
12582 use some::mod1;
12583 use some::mod2;
12584
12585 const A: u32 = 42;
12586 const B: u32 = 42;
12587 const C: u32 = 43ˇ
12588 const D: u32 = 42;
12589
12590
12591 fn main() {
12592 println!("hello");
12593
12594 println!("world");
12595 }"#
12596 .unindent(),
12597 );
12598
12599 cx.set_diff_base(Some(&diff_base));
12600 executor.run_until_parked();
12601 cx.update_editor(|editor, cx| {
12602 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12603 });
12604 executor.run_until_parked();
12605
12606 cx.assert_diff_hunks(
12607 r#"
12608 use some::mod1;
12609 use some::mod2;
12610
12611 const A: u32 = 42;
12612 const B: u32 = 42;
12613 - const C: u32 = 42;
12614 + const C: u32 = 43
12615 const D: u32 = 42;
12616
12617
12618 fn main() {
12619 println!("hello");
12620
12621 println!("world");
12622 }"#
12623 .unindent(),
12624 );
12625
12626 cx.update_editor(|editor, cx| {
12627 editor.handle_input("\nnew_line\n", cx);
12628 });
12629 executor.run_until_parked();
12630
12631 cx.assert_diff_hunks(
12632 r#"
12633 use some::mod1;
12634 use some::mod2;
12635
12636 const A: u32 = 42;
12637 const B: u32 = 42;
12638 - const C: u32 = 42;
12639 + const C: u32 = 43
12640 + new_line
12641 +
12642 const D: u32 = 42;
12643
12644
12645 fn main() {
12646 println!("hello");
12647
12648 println!("world");
12649 }"#
12650 .unindent(),
12651 );
12652}
12653
12654async fn setup_indent_guides_editor(
12655 text: &str,
12656 cx: &mut gpui::TestAppContext,
12657) -> (BufferId, EditorTestContext) {
12658 init_test(cx, |_| {});
12659
12660 let mut cx = EditorTestContext::new(cx).await;
12661
12662 let buffer_id = cx.update_editor(|editor, cx| {
12663 editor.set_text(text, cx);
12664 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12665
12666 buffer_ids[0]
12667 });
12668
12669 (buffer_id, cx)
12670}
12671
12672fn assert_indent_guides(
12673 range: Range<u32>,
12674 expected: Vec<IndentGuide>,
12675 active_indices: Option<Vec<usize>>,
12676 cx: &mut EditorTestContext,
12677) {
12678 let indent_guides = cx.update_editor(|editor, cx| {
12679 let snapshot = editor.snapshot(cx).display_snapshot;
12680 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12681 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12682 true,
12683 &snapshot,
12684 cx,
12685 );
12686
12687 indent_guides.sort_by(|a, b| {
12688 a.depth.cmp(&b.depth).then(
12689 a.start_row
12690 .cmp(&b.start_row)
12691 .then(a.end_row.cmp(&b.end_row)),
12692 )
12693 });
12694 indent_guides
12695 });
12696
12697 if let Some(expected) = active_indices {
12698 let active_indices = cx.update_editor(|editor, cx| {
12699 let snapshot = editor.snapshot(cx).display_snapshot;
12700 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
12701 });
12702
12703 assert_eq!(
12704 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
12705 expected,
12706 "Active indent guide indices do not match"
12707 );
12708 }
12709
12710 let expected: Vec<_> = expected
12711 .into_iter()
12712 .map(|guide| MultiBufferIndentGuide {
12713 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
12714 buffer: guide,
12715 })
12716 .collect();
12717
12718 assert_eq!(indent_guides, expected, "Indent guides do not match");
12719}
12720
12721fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
12722 IndentGuide {
12723 buffer_id,
12724 start_row,
12725 end_row,
12726 depth,
12727 tab_size: 4,
12728 settings: IndentGuideSettings {
12729 enabled: true,
12730 line_width: 1,
12731 active_line_width: 1,
12732 ..Default::default()
12733 },
12734 }
12735}
12736
12737#[gpui::test]
12738async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12739 let (buffer_id, mut cx) = setup_indent_guides_editor(
12740 &"
12741 fn main() {
12742 let a = 1;
12743 }"
12744 .unindent(),
12745 cx,
12746 )
12747 .await;
12748
12749 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12750}
12751
12752#[gpui::test]
12753async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
12754 let (buffer_id, mut cx) = setup_indent_guides_editor(
12755 &"
12756 fn main() {
12757 let a = 1;
12758 let b = 2;
12759 }"
12760 .unindent(),
12761 cx,
12762 )
12763 .await;
12764
12765 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
12766}
12767
12768#[gpui::test]
12769async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
12770 let (buffer_id, mut cx) = setup_indent_guides_editor(
12771 &"
12772 fn main() {
12773 let a = 1;
12774 if a == 3 {
12775 let b = 2;
12776 } else {
12777 let c = 3;
12778 }
12779 }"
12780 .unindent(),
12781 cx,
12782 )
12783 .await;
12784
12785 assert_indent_guides(
12786 0..8,
12787 vec![
12788 indent_guide(buffer_id, 1, 6, 0),
12789 indent_guide(buffer_id, 3, 3, 1),
12790 indent_guide(buffer_id, 5, 5, 1),
12791 ],
12792 None,
12793 &mut cx,
12794 );
12795}
12796
12797#[gpui::test]
12798async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
12799 let (buffer_id, mut cx) = setup_indent_guides_editor(
12800 &"
12801 fn main() {
12802 let a = 1;
12803 let b = 2;
12804 let c = 3;
12805 }"
12806 .unindent(),
12807 cx,
12808 )
12809 .await;
12810
12811 assert_indent_guides(
12812 0..5,
12813 vec![
12814 indent_guide(buffer_id, 1, 3, 0),
12815 indent_guide(buffer_id, 2, 2, 1),
12816 ],
12817 None,
12818 &mut cx,
12819 );
12820}
12821
12822#[gpui::test]
12823async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
12824 let (buffer_id, mut cx) = setup_indent_guides_editor(
12825 &"
12826 fn main() {
12827 let a = 1;
12828
12829 let c = 3;
12830 }"
12831 .unindent(),
12832 cx,
12833 )
12834 .await;
12835
12836 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
12837}
12838
12839#[gpui::test]
12840async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
12841 let (buffer_id, mut cx) = setup_indent_guides_editor(
12842 &"
12843 fn main() {
12844 let a = 1;
12845
12846 let c = 3;
12847
12848 if a == 3 {
12849 let b = 2;
12850 } else {
12851 let c = 3;
12852 }
12853 }"
12854 .unindent(),
12855 cx,
12856 )
12857 .await;
12858
12859 assert_indent_guides(
12860 0..11,
12861 vec![
12862 indent_guide(buffer_id, 1, 9, 0),
12863 indent_guide(buffer_id, 6, 6, 1),
12864 indent_guide(buffer_id, 8, 8, 1),
12865 ],
12866 None,
12867 &mut cx,
12868 );
12869}
12870
12871#[gpui::test]
12872async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
12873 let (buffer_id, mut cx) = setup_indent_guides_editor(
12874 &"
12875 fn main() {
12876 let a = 1;
12877
12878 let c = 3;
12879
12880 if a == 3 {
12881 let b = 2;
12882 } else {
12883 let c = 3;
12884 }
12885 }"
12886 .unindent(),
12887 cx,
12888 )
12889 .await;
12890
12891 assert_indent_guides(
12892 1..11,
12893 vec![
12894 indent_guide(buffer_id, 1, 9, 0),
12895 indent_guide(buffer_id, 6, 6, 1),
12896 indent_guide(buffer_id, 8, 8, 1),
12897 ],
12898 None,
12899 &mut cx,
12900 );
12901}
12902
12903#[gpui::test]
12904async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
12905 let (buffer_id, mut cx) = setup_indent_guides_editor(
12906 &"
12907 fn main() {
12908 let a = 1;
12909
12910 let c = 3;
12911
12912 if a == 3 {
12913 let b = 2;
12914 } else {
12915 let c = 3;
12916 }
12917 }"
12918 .unindent(),
12919 cx,
12920 )
12921 .await;
12922
12923 assert_indent_guides(
12924 1..10,
12925 vec![
12926 indent_guide(buffer_id, 1, 9, 0),
12927 indent_guide(buffer_id, 6, 6, 1),
12928 indent_guide(buffer_id, 8, 8, 1),
12929 ],
12930 None,
12931 &mut cx,
12932 );
12933}
12934
12935#[gpui::test]
12936async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
12937 let (buffer_id, mut cx) = setup_indent_guides_editor(
12938 &"
12939 block1
12940 block2
12941 block3
12942 block4
12943 block2
12944 block1
12945 block1"
12946 .unindent(),
12947 cx,
12948 )
12949 .await;
12950
12951 assert_indent_guides(
12952 1..10,
12953 vec![
12954 indent_guide(buffer_id, 1, 4, 0),
12955 indent_guide(buffer_id, 2, 3, 1),
12956 indent_guide(buffer_id, 3, 3, 2),
12957 ],
12958 None,
12959 &mut cx,
12960 );
12961}
12962
12963#[gpui::test]
12964async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
12965 let (buffer_id, mut cx) = setup_indent_guides_editor(
12966 &"
12967 block1
12968 block2
12969 block3
12970
12971 block1
12972 block1"
12973 .unindent(),
12974 cx,
12975 )
12976 .await;
12977
12978 assert_indent_guides(
12979 0..6,
12980 vec![
12981 indent_guide(buffer_id, 1, 2, 0),
12982 indent_guide(buffer_id, 2, 2, 1),
12983 ],
12984 None,
12985 &mut cx,
12986 );
12987}
12988
12989#[gpui::test]
12990async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
12991 let (buffer_id, mut cx) = setup_indent_guides_editor(
12992 &"
12993 block1
12994
12995
12996
12997 block2
12998 "
12999 .unindent(),
13000 cx,
13001 )
13002 .await;
13003
13004 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13005}
13006
13007#[gpui::test]
13008async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
13009 let (buffer_id, mut cx) = setup_indent_guides_editor(
13010 &"
13011 def a:
13012 \tb = 3
13013 \tif True:
13014 \t\tc = 4
13015 \t\td = 5
13016 \tprint(b)
13017 "
13018 .unindent(),
13019 cx,
13020 )
13021 .await;
13022
13023 assert_indent_guides(
13024 0..6,
13025 vec![
13026 indent_guide(buffer_id, 1, 6, 0),
13027 indent_guide(buffer_id, 3, 4, 1),
13028 ],
13029 None,
13030 &mut cx,
13031 );
13032}
13033
13034#[gpui::test]
13035async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13036 let (buffer_id, mut cx) = setup_indent_guides_editor(
13037 &"
13038 fn main() {
13039 let a = 1;
13040 }"
13041 .unindent(),
13042 cx,
13043 )
13044 .await;
13045
13046 cx.update_editor(|editor, cx| {
13047 editor.change_selections(None, cx, |s| {
13048 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13049 });
13050 });
13051
13052 assert_indent_guides(
13053 0..3,
13054 vec![indent_guide(buffer_id, 1, 1, 0)],
13055 Some(vec![0]),
13056 &mut cx,
13057 );
13058}
13059
13060#[gpui::test]
13061async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13062 let (buffer_id, mut cx) = setup_indent_guides_editor(
13063 &"
13064 fn main() {
13065 if 1 == 2 {
13066 let a = 1;
13067 }
13068 }"
13069 .unindent(),
13070 cx,
13071 )
13072 .await;
13073
13074 cx.update_editor(|editor, cx| {
13075 editor.change_selections(None, cx, |s| {
13076 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13077 });
13078 });
13079
13080 assert_indent_guides(
13081 0..4,
13082 vec![
13083 indent_guide(buffer_id, 1, 3, 0),
13084 indent_guide(buffer_id, 2, 2, 1),
13085 ],
13086 Some(vec![1]),
13087 &mut cx,
13088 );
13089
13090 cx.update_editor(|editor, cx| {
13091 editor.change_selections(None, cx, |s| {
13092 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13093 });
13094 });
13095
13096 assert_indent_guides(
13097 0..4,
13098 vec![
13099 indent_guide(buffer_id, 1, 3, 0),
13100 indent_guide(buffer_id, 2, 2, 1),
13101 ],
13102 Some(vec![1]),
13103 &mut cx,
13104 );
13105
13106 cx.update_editor(|editor, cx| {
13107 editor.change_selections(None, cx, |s| {
13108 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13109 });
13110 });
13111
13112 assert_indent_guides(
13113 0..4,
13114 vec![
13115 indent_guide(buffer_id, 1, 3, 0),
13116 indent_guide(buffer_id, 2, 2, 1),
13117 ],
13118 Some(vec![0]),
13119 &mut cx,
13120 );
13121}
13122
13123#[gpui::test]
13124async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13125 let (buffer_id, mut cx) = setup_indent_guides_editor(
13126 &"
13127 fn main() {
13128 let a = 1;
13129
13130 let b = 2;
13131 }"
13132 .unindent(),
13133 cx,
13134 )
13135 .await;
13136
13137 cx.update_editor(|editor, cx| {
13138 editor.change_selections(None, cx, |s| {
13139 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13140 });
13141 });
13142
13143 assert_indent_guides(
13144 0..5,
13145 vec![indent_guide(buffer_id, 1, 3, 0)],
13146 Some(vec![0]),
13147 &mut cx,
13148 );
13149}
13150
13151#[gpui::test]
13152async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13153 let (buffer_id, mut cx) = setup_indent_guides_editor(
13154 &"
13155 def m:
13156 a = 1
13157 pass"
13158 .unindent(),
13159 cx,
13160 )
13161 .await;
13162
13163 cx.update_editor(|editor, cx| {
13164 editor.change_selections(None, cx, |s| {
13165 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13166 });
13167 });
13168
13169 assert_indent_guides(
13170 0..3,
13171 vec![indent_guide(buffer_id, 1, 2, 0)],
13172 Some(vec![0]),
13173 &mut cx,
13174 );
13175}
13176
13177#[gpui::test]
13178fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13179 init_test(cx, |_| {});
13180
13181 let editor = cx.add_window(|cx| {
13182 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13183 build_editor(buffer, cx)
13184 });
13185
13186 let render_args = Arc::new(Mutex::new(None));
13187 let snapshot = editor
13188 .update(cx, |editor, cx| {
13189 let snapshot = editor.buffer().read(cx).snapshot(cx);
13190 let range =
13191 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13192
13193 struct RenderArgs {
13194 row: MultiBufferRow,
13195 folded: bool,
13196 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13197 }
13198
13199 let crease = Crease::new(
13200 range,
13201 FoldPlaceholder::test(),
13202 {
13203 let toggle_callback = render_args.clone();
13204 move |row, folded, callback, _cx| {
13205 *toggle_callback.lock() = Some(RenderArgs {
13206 row,
13207 folded,
13208 callback,
13209 });
13210 div()
13211 }
13212 },
13213 |_row, _folded, _cx| div(),
13214 );
13215
13216 editor.insert_creases(Some(crease), cx);
13217 let snapshot = editor.snapshot(cx);
13218 let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13219 snapshot
13220 })
13221 .unwrap();
13222
13223 let render_args = render_args.lock().take().unwrap();
13224 assert_eq!(render_args.row, MultiBufferRow(1));
13225 assert!(!render_args.folded);
13226 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13227
13228 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13229 .unwrap();
13230 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13231 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13232
13233 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13234 .unwrap();
13235 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13236 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13237}
13238
13239#[gpui::test]
13240async fn test_input_text(cx: &mut gpui::TestAppContext) {
13241 init_test(cx, |_| {});
13242 let mut cx = EditorTestContext::new(cx).await;
13243
13244 cx.set_state(
13245 &r#"ˇone
13246 two
13247
13248 three
13249 fourˇ
13250 five
13251
13252 siˇx"#
13253 .unindent(),
13254 );
13255
13256 cx.dispatch_action(HandleInput(String::new()));
13257 cx.assert_editor_state(
13258 &r#"ˇone
13259 two
13260
13261 three
13262 fourˇ
13263 five
13264
13265 siˇx"#
13266 .unindent(),
13267 );
13268
13269 cx.dispatch_action(HandleInput("AAAA".to_string()));
13270 cx.assert_editor_state(
13271 &r#"AAAAˇone
13272 two
13273
13274 three
13275 fourAAAAˇ
13276 five
13277
13278 siAAAAˇx"#
13279 .unindent(),
13280 );
13281}
13282
13283#[gpui::test]
13284async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13285 init_test(cx, |_| {});
13286
13287 let mut cx = EditorTestContext::new(cx).await;
13288 cx.set_state(
13289 r#"let foo = 1;
13290let foo = 2;
13291let foo = 3;
13292let fooˇ = 4;
13293let foo = 5;
13294let foo = 6;
13295let foo = 7;
13296let foo = 8;
13297let foo = 9;
13298let foo = 10;
13299let foo = 11;
13300let foo = 12;
13301let foo = 13;
13302let foo = 14;
13303let foo = 15;"#,
13304 );
13305
13306 cx.update_editor(|e, cx| {
13307 assert_eq!(
13308 e.next_scroll_position,
13309 NextScrollCursorCenterTopBottom::Center,
13310 "Default next scroll direction is center",
13311 );
13312
13313 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13314 assert_eq!(
13315 e.next_scroll_position,
13316 NextScrollCursorCenterTopBottom::Top,
13317 "After center, next scroll direction should be top",
13318 );
13319
13320 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13321 assert_eq!(
13322 e.next_scroll_position,
13323 NextScrollCursorCenterTopBottom::Bottom,
13324 "After top, next scroll direction should be bottom",
13325 );
13326
13327 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13328 assert_eq!(
13329 e.next_scroll_position,
13330 NextScrollCursorCenterTopBottom::Center,
13331 "After bottom, scrolling should start over",
13332 );
13333
13334 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13335 assert_eq!(
13336 e.next_scroll_position,
13337 NextScrollCursorCenterTopBottom::Top,
13338 "Scrolling continues if retriggered fast enough"
13339 );
13340 });
13341
13342 cx.executor()
13343 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13344 cx.executor().run_until_parked();
13345 cx.update_editor(|e, _| {
13346 assert_eq!(
13347 e.next_scroll_position,
13348 NextScrollCursorCenterTopBottom::Center,
13349 "If scrolling is not triggered fast enough, it should reset"
13350 );
13351 });
13352}
13353
13354#[gpui::test]
13355async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13356 init_test(cx, |_| {});
13357 let mut cx = EditorLspTestContext::new_rust(
13358 lsp::ServerCapabilities {
13359 definition_provider: Some(lsp::OneOf::Left(true)),
13360 references_provider: Some(lsp::OneOf::Left(true)),
13361 ..lsp::ServerCapabilities::default()
13362 },
13363 cx,
13364 )
13365 .await;
13366
13367 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13368 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13369 move |params, _| async move {
13370 if empty_go_to_definition {
13371 Ok(None)
13372 } else {
13373 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13374 uri: params.text_document_position_params.text_document.uri,
13375 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13376 })))
13377 }
13378 },
13379 );
13380 let references =
13381 cx.lsp
13382 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13383 Ok(Some(vec![lsp::Location {
13384 uri: params.text_document_position.text_document.uri,
13385 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13386 }]))
13387 });
13388 (go_to_definition, references)
13389 };
13390
13391 cx.set_state(
13392 &r#"fn one() {
13393 let mut a = ˇtwo();
13394 }
13395
13396 fn two() {}"#
13397 .unindent(),
13398 );
13399 set_up_lsp_handlers(false, &mut cx);
13400 let navigated = cx
13401 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13402 .await
13403 .expect("Failed to navigate to definition");
13404 assert_eq!(
13405 navigated,
13406 Navigated::Yes,
13407 "Should have navigated to definition from the GetDefinition response"
13408 );
13409 cx.assert_editor_state(
13410 &r#"fn one() {
13411 let mut a = two();
13412 }
13413
13414 fn «twoˇ»() {}"#
13415 .unindent(),
13416 );
13417
13418 let editors = cx.update_workspace(|workspace, cx| {
13419 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13420 });
13421 cx.update_editor(|_, test_editor_cx| {
13422 assert_eq!(
13423 editors.len(),
13424 1,
13425 "Initially, only one, test, editor should be open in the workspace"
13426 );
13427 assert_eq!(
13428 test_editor_cx.view(),
13429 editors.last().expect("Asserted len is 1")
13430 );
13431 });
13432
13433 set_up_lsp_handlers(true, &mut cx);
13434 let navigated = cx
13435 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13436 .await
13437 .expect("Failed to navigate to lookup references");
13438 assert_eq!(
13439 navigated,
13440 Navigated::Yes,
13441 "Should have navigated to references as a fallback after empty GoToDefinition response"
13442 );
13443 // We should not change the selections in the existing file,
13444 // if opening another milti buffer with the references
13445 cx.assert_editor_state(
13446 &r#"fn one() {
13447 let mut a = two();
13448 }
13449
13450 fn «twoˇ»() {}"#
13451 .unindent(),
13452 );
13453 let editors = cx.update_workspace(|workspace, cx| {
13454 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13455 });
13456 cx.update_editor(|_, test_editor_cx| {
13457 assert_eq!(
13458 editors.len(),
13459 2,
13460 "After falling back to references search, we open a new editor with the results"
13461 );
13462 let references_fallback_text = editors
13463 .into_iter()
13464 .find(|new_editor| new_editor != test_editor_cx.view())
13465 .expect("Should have one non-test editor now")
13466 .read(test_editor_cx)
13467 .text(test_editor_cx);
13468 assert_eq!(
13469 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13470 "Should use the range from the references response and not the GoToDefinition one"
13471 );
13472 });
13473}
13474
13475#[gpui::test]
13476async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
13477 init_test(cx, |_| {});
13478
13479 let language = Arc::new(Language::new(
13480 LanguageConfig::default(),
13481 Some(tree_sitter_rust::LANGUAGE.into()),
13482 ));
13483
13484 let text = r#"
13485 #[cfg(test)]
13486 mod tests() {
13487 #[test]
13488 fn runnable_1() {
13489 let a = 1;
13490 }
13491
13492 #[test]
13493 fn runnable_2() {
13494 let a = 1;
13495 let b = 2;
13496 }
13497 }
13498 "#
13499 .unindent();
13500
13501 let fs = FakeFs::new(cx.executor());
13502 fs.insert_file("/file.rs", Default::default()).await;
13503
13504 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13505 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
13506 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13507 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
13508 let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
13509
13510 let editor = cx.new_view(|cx| {
13511 Editor::new(
13512 EditorMode::Full,
13513 multi_buffer,
13514 Some(project.clone()),
13515 true,
13516 cx,
13517 )
13518 });
13519
13520 editor.update(cx, |editor, cx| {
13521 editor.tasks.insert(
13522 (buffer.read(cx).remote_id(), 3),
13523 RunnableTasks {
13524 templates: vec![],
13525 offset: MultiBufferOffset(43),
13526 column: 0,
13527 extra_variables: HashMap::default(),
13528 context_range: BufferOffset(43)..BufferOffset(85),
13529 },
13530 );
13531 editor.tasks.insert(
13532 (buffer.read(cx).remote_id(), 8),
13533 RunnableTasks {
13534 templates: vec![],
13535 offset: MultiBufferOffset(86),
13536 column: 0,
13537 extra_variables: HashMap::default(),
13538 context_range: BufferOffset(86)..BufferOffset(191),
13539 },
13540 );
13541
13542 // Test finding task when cursor is inside function body
13543 editor.change_selections(None, cx, |s| {
13544 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
13545 });
13546 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13547 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
13548
13549 // Test finding task when cursor is on function name
13550 editor.change_selections(None, cx, |s| {
13551 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
13552 });
13553 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13554 assert_eq!(row, 8, "Should find task when cursor is on function name");
13555 });
13556}
13557
13558fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13559 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13560 point..point
13561}
13562
13563fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13564 let (text, ranges) = marked_text_ranges(marked_text, true);
13565 assert_eq!(view.text(cx), text);
13566 assert_eq!(
13567 view.selections.ranges(cx),
13568 ranges,
13569 "Assert selections are {}",
13570 marked_text
13571 );
13572}
13573
13574pub fn handle_signature_help_request(
13575 cx: &mut EditorLspTestContext,
13576 mocked_response: lsp::SignatureHelp,
13577) -> impl Future<Output = ()> {
13578 let mut request =
13579 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13580 let mocked_response = mocked_response.clone();
13581 async move { Ok(Some(mocked_response)) }
13582 });
13583
13584 async move {
13585 request.next().await;
13586 }
13587}
13588
13589/// Handle completion request passing a marked string specifying where the completion
13590/// should be triggered from using '|' character, what range should be replaced, and what completions
13591/// should be returned using '<' and '>' to delimit the range
13592pub fn handle_completion_request(
13593 cx: &mut EditorLspTestContext,
13594 marked_string: &str,
13595 completions: Vec<&'static str>,
13596 counter: Arc<AtomicUsize>,
13597) -> impl Future<Output = ()> {
13598 let complete_from_marker: TextRangeMarker = '|'.into();
13599 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13600 let (_, mut marked_ranges) = marked_text_ranges_by(
13601 marked_string,
13602 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13603 );
13604
13605 let complete_from_position =
13606 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13607 let replace_range =
13608 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13609
13610 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13611 let completions = completions.clone();
13612 counter.fetch_add(1, atomic::Ordering::Release);
13613 async move {
13614 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13615 assert_eq!(
13616 params.text_document_position.position,
13617 complete_from_position
13618 );
13619 Ok(Some(lsp::CompletionResponse::Array(
13620 completions
13621 .iter()
13622 .map(|completion_text| lsp::CompletionItem {
13623 label: completion_text.to_string(),
13624 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13625 range: replace_range,
13626 new_text: completion_text.to_string(),
13627 })),
13628 ..Default::default()
13629 })
13630 .collect(),
13631 )))
13632 }
13633 });
13634
13635 async move {
13636 request.next().await;
13637 }
13638}
13639
13640fn handle_resolve_completion_request(
13641 cx: &mut EditorLspTestContext,
13642 edits: Option<Vec<(&'static str, &'static str)>>,
13643) -> impl Future<Output = ()> {
13644 let edits = edits.map(|edits| {
13645 edits
13646 .iter()
13647 .map(|(marked_string, new_text)| {
13648 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13649 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13650 lsp::TextEdit::new(replace_range, new_text.to_string())
13651 })
13652 .collect::<Vec<_>>()
13653 });
13654
13655 let mut request =
13656 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13657 let edits = edits.clone();
13658 async move {
13659 Ok(lsp::CompletionItem {
13660 additional_text_edits: edits,
13661 ..Default::default()
13662 })
13663 }
13664 });
13665
13666 async move {
13667 request.next().await;
13668 }
13669}
13670
13671pub(crate) fn update_test_language_settings(
13672 cx: &mut TestAppContext,
13673 f: impl Fn(&mut AllLanguageSettingsContent),
13674) {
13675 cx.update(|cx| {
13676 SettingsStore::update_global(cx, |store, cx| {
13677 store.update_user_settings::<AllLanguageSettings>(cx, f);
13678 });
13679 });
13680}
13681
13682pub(crate) fn update_test_project_settings(
13683 cx: &mut TestAppContext,
13684 f: impl Fn(&mut ProjectSettings),
13685) {
13686 cx.update(|cx| {
13687 SettingsStore::update_global(cx, |store, cx| {
13688 store.update_user_settings::<ProjectSettings>(cx, f);
13689 });
13690 });
13691}
13692
13693pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
13694 cx.update(|cx| {
13695 assets::Assets.load_test_fonts(cx);
13696 let store = SettingsStore::test(cx);
13697 cx.set_global(store);
13698 theme::init(theme::LoadThemes::JustBase, cx);
13699 release_channel::init(SemanticVersion::default(), cx);
13700 client::init_settings(cx);
13701 language::init(cx);
13702 Project::init_settings(cx);
13703 workspace::init_settings(cx);
13704 crate::init(cx);
13705 });
13706
13707 update_test_language_settings(cx, f);
13708}
13709
13710pub(crate) fn rust_lang() -> Arc<Language> {
13711 Arc::new(Language::new(
13712 LanguageConfig {
13713 name: "Rust".into(),
13714 matcher: LanguageMatcher {
13715 path_suffixes: vec!["rs".to_string()],
13716 ..Default::default()
13717 },
13718 ..Default::default()
13719 },
13720 Some(tree_sitter_rust::LANGUAGE.into()),
13721 ))
13722}
13723
13724#[track_caller]
13725fn assert_hunk_revert(
13726 not_reverted_text_with_selections: &str,
13727 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
13728 expected_reverted_text_with_selections: &str,
13729 base_text: &str,
13730 cx: &mut EditorLspTestContext,
13731) {
13732 cx.set_state(not_reverted_text_with_selections);
13733 cx.update_editor(|editor, cx| {
13734 editor
13735 .buffer()
13736 .read(cx)
13737 .as_singleton()
13738 .unwrap()
13739 .update(cx, |buffer, cx| {
13740 buffer.set_diff_base(Some(base_text.into()), cx);
13741 });
13742 });
13743 cx.executor().run_until_parked();
13744
13745 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
13746 let snapshot = editor.buffer().read(cx).snapshot(cx);
13747 let reverted_hunk_statuses = snapshot
13748 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
13749 .map(|hunk| hunk_status(&hunk))
13750 .collect::<Vec<_>>();
13751
13752 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
13753 reverted_hunk_statuses
13754 });
13755 cx.executor().run_until_parked();
13756 cx.assert_editor_state(expected_reverted_text_with_selections);
13757 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
13758}