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 buffer = cx.new_model(|cx| language::Buffer::local("123456", cx));
173 let group_interval = buffer.update(cx, |buffer, _| buffer.transaction_group_interval());
174 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
175 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
176
177 _ = editor.update(cx, |editor, cx| {
178 editor.start_transaction_at(now, cx);
179 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
180
181 editor.insert("cd", cx);
182 editor.end_transaction_at(now, cx);
183 assert_eq!(editor.text(cx), "12cd56");
184 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
185
186 editor.start_transaction_at(now, cx);
187 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
188 editor.insert("e", cx);
189 editor.end_transaction_at(now, cx);
190 assert_eq!(editor.text(cx), "12cde6");
191 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
192
193 now += group_interval + Duration::from_millis(1);
194 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
195
196 // Simulate an edit in another editor
197 buffer.update(cx, |buffer, cx| {
198 buffer.start_transaction_at(now, cx);
199 buffer.edit([(0..1, "a")], None, cx);
200 buffer.edit([(1..1, "b")], None, cx);
201 buffer.end_transaction_at(now, cx);
202 });
203
204 assert_eq!(editor.text(cx), "ab2cde6");
205 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
206
207 // Last transaction happened past the group interval in a different editor.
208 // Undo it individually and don't restore selections.
209 editor.undo(&Undo, cx);
210 assert_eq!(editor.text(cx), "12cde6");
211 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
212
213 // First two transactions happened within the group interval in this editor.
214 // Undo them together and restore selections.
215 editor.undo(&Undo, cx);
216 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
217 assert_eq!(editor.text(cx), "123456");
218 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
219
220 // Redo the first two transactions together.
221 editor.redo(&Redo, cx);
222 assert_eq!(editor.text(cx), "12cde6");
223 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
224
225 // Redo the last transaction on its own.
226 editor.redo(&Redo, cx);
227 assert_eq!(editor.text(cx), "ab2cde6");
228 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
229
230 // Test empty transactions.
231 editor.start_transaction_at(now, cx);
232 editor.end_transaction_at(now, cx);
233 editor.undo(&Undo, cx);
234 assert_eq!(editor.text(cx), "12cde6");
235 });
236}
237
238#[gpui::test]
239fn test_ime_composition(cx: &mut TestAppContext) {
240 init_test(cx, |_| {});
241
242 let buffer = cx.new_model(|cx| {
243 let mut buffer = language::Buffer::local("abcde", cx);
244 // Ensure automatic grouping doesn't occur.
245 buffer.set_group_interval(Duration::ZERO);
246 buffer
247 });
248
249 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
250 cx.add_window(|cx| {
251 let mut editor = build_editor(buffer.clone(), cx);
252
253 // Start a new IME composition.
254 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
255 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
256 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
257 assert_eq!(editor.text(cx), "äbcde");
258 assert_eq!(
259 editor.marked_text_ranges(cx),
260 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
261 );
262
263 // Finalize IME composition.
264 editor.replace_text_in_range(None, "ā", cx);
265 assert_eq!(editor.text(cx), "ābcde");
266 assert_eq!(editor.marked_text_ranges(cx), None);
267
268 // IME composition edits are grouped and are undone/redone at once.
269 editor.undo(&Default::default(), cx);
270 assert_eq!(editor.text(cx), "abcde");
271 assert_eq!(editor.marked_text_ranges(cx), None);
272 editor.redo(&Default::default(), cx);
273 assert_eq!(editor.text(cx), "ābcde");
274 assert_eq!(editor.marked_text_ranges(cx), None);
275
276 // Start a new IME composition.
277 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
278 assert_eq!(
279 editor.marked_text_ranges(cx),
280 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
281 );
282
283 // Undoing during an IME composition cancels it.
284 editor.undo(&Default::default(), cx);
285 assert_eq!(editor.text(cx), "ābcde");
286 assert_eq!(editor.marked_text_ranges(cx), None);
287
288 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
289 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
290 assert_eq!(editor.text(cx), "ābcdè");
291 assert_eq!(
292 editor.marked_text_ranges(cx),
293 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
294 );
295
296 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
297 editor.replace_text_in_range(Some(4..999), "ę", cx);
298 assert_eq!(editor.text(cx), "ābcdę");
299 assert_eq!(editor.marked_text_ranges(cx), None);
300
301 // Start a new IME composition with multiple cursors.
302 editor.change_selections(None, cx, |s| {
303 s.select_ranges([
304 OffsetUtf16(1)..OffsetUtf16(1),
305 OffsetUtf16(3)..OffsetUtf16(3),
306 OffsetUtf16(5)..OffsetUtf16(5),
307 ])
308 });
309 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
310 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
311 assert_eq!(
312 editor.marked_text_ranges(cx),
313 Some(vec![
314 OffsetUtf16(0)..OffsetUtf16(3),
315 OffsetUtf16(4)..OffsetUtf16(7),
316 OffsetUtf16(8)..OffsetUtf16(11)
317 ])
318 );
319
320 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
321 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
322 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
323 assert_eq!(
324 editor.marked_text_ranges(cx),
325 Some(vec![
326 OffsetUtf16(1)..OffsetUtf16(2),
327 OffsetUtf16(5)..OffsetUtf16(6),
328 OffsetUtf16(9)..OffsetUtf16(10)
329 ])
330 );
331
332 // Finalize IME composition with multiple cursors.
333 editor.replace_text_in_range(Some(9..10), "2", cx);
334 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
335 assert_eq!(editor.marked_text_ranges(cx), None);
336
337 editor
338 });
339}
340
341#[gpui::test]
342fn test_selection_with_mouse(cx: &mut TestAppContext) {
343 init_test(cx, |_| {});
344
345 let editor = cx.add_window(|cx| {
346 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
347 build_editor(buffer, cx)
348 });
349
350 _ = editor.update(cx, |view, cx| {
351 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
352 });
353 assert_eq!(
354 editor
355 .update(cx, |view, cx| view.selections.display_ranges(cx))
356 .unwrap(),
357 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
358 );
359
360 _ = editor.update(cx, |view, cx| {
361 view.update_selection(
362 DisplayPoint::new(DisplayRow(3), 3),
363 0,
364 gpui::Point::<f32>::default(),
365 cx,
366 );
367 });
368
369 assert_eq!(
370 editor
371 .update(cx, |view, cx| view.selections.display_ranges(cx))
372 .unwrap(),
373 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
374 );
375
376 _ = editor.update(cx, |view, cx| {
377 view.update_selection(
378 DisplayPoint::new(DisplayRow(1), 1),
379 0,
380 gpui::Point::<f32>::default(),
381 cx,
382 );
383 });
384
385 assert_eq!(
386 editor
387 .update(cx, |view, cx| view.selections.display_ranges(cx))
388 .unwrap(),
389 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
390 );
391
392 _ = editor.update(cx, |view, cx| {
393 view.end_selection(cx);
394 view.update_selection(
395 DisplayPoint::new(DisplayRow(3), 3),
396 0,
397 gpui::Point::<f32>::default(),
398 cx,
399 );
400 });
401
402 assert_eq!(
403 editor
404 .update(cx, |view, cx| view.selections.display_ranges(cx))
405 .unwrap(),
406 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
407 );
408
409 _ = editor.update(cx, |view, cx| {
410 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
411 view.update_selection(
412 DisplayPoint::new(DisplayRow(0), 0),
413 0,
414 gpui::Point::<f32>::default(),
415 cx,
416 );
417 });
418
419 assert_eq!(
420 editor
421 .update(cx, |view, cx| view.selections.display_ranges(cx))
422 .unwrap(),
423 [
424 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
425 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
426 ]
427 );
428
429 _ = editor.update(cx, |view, cx| {
430 view.end_selection(cx);
431 });
432
433 assert_eq!(
434 editor
435 .update(cx, |view, cx| view.selections.display_ranges(cx))
436 .unwrap(),
437 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
438 );
439}
440
441#[gpui::test]
442fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
443 init_test(cx, |_| {});
444
445 let editor = cx.add_window(|cx| {
446 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
447 build_editor(buffer, cx)
448 });
449
450 _ = editor.update(cx, |view, cx| {
451 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
452 });
453
454 _ = editor.update(cx, |view, cx| {
455 view.end_selection(cx);
456 });
457
458 _ = editor.update(cx, |view, cx| {
459 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
460 });
461
462 _ = editor.update(cx, |view, cx| {
463 view.end_selection(cx);
464 });
465
466 assert_eq!(
467 editor
468 .update(cx, |view, cx| view.selections.display_ranges(cx))
469 .unwrap(),
470 [
471 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
472 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
473 ]
474 );
475
476 _ = editor.update(cx, |view, cx| {
477 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
478 });
479
480 _ = editor.update(cx, |view, cx| {
481 view.end_selection(cx);
482 });
483
484 assert_eq!(
485 editor
486 .update(cx, |view, cx| view.selections.display_ranges(cx))
487 .unwrap(),
488 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
489 );
490}
491
492#[gpui::test]
493fn test_canceling_pending_selection(cx: &mut TestAppContext) {
494 init_test(cx, |_| {});
495
496 let view = cx.add_window(|cx| {
497 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
498 build_editor(buffer, cx)
499 });
500
501 _ = view.update(cx, |view, cx| {
502 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
503 assert_eq!(
504 view.selections.display_ranges(cx),
505 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
506 );
507 });
508
509 _ = view.update(cx, |view, cx| {
510 view.update_selection(
511 DisplayPoint::new(DisplayRow(3), 3),
512 0,
513 gpui::Point::<f32>::default(),
514 cx,
515 );
516 assert_eq!(
517 view.selections.display_ranges(cx),
518 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
519 );
520 });
521
522 _ = view.update(cx, |view, cx| {
523 view.cancel(&Cancel, cx);
524 view.update_selection(
525 DisplayPoint::new(DisplayRow(1), 1),
526 0,
527 gpui::Point::<f32>::default(),
528 cx,
529 );
530 assert_eq!(
531 view.selections.display_ranges(cx),
532 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
533 );
534 });
535}
536
537#[gpui::test]
538fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
539 init_test(cx, |_| {});
540
541 let view = cx.add_window(|cx| {
542 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
543 build_editor(buffer, cx)
544 });
545
546 _ = view.update(cx, |view, cx| {
547 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
548 assert_eq!(
549 view.selections.display_ranges(cx),
550 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
551 );
552
553 view.move_down(&Default::default(), cx);
554 assert_eq!(
555 view.selections.display_ranges(cx),
556 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
557 );
558
559 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
560 assert_eq!(
561 view.selections.display_ranges(cx),
562 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
563 );
564
565 view.move_up(&Default::default(), cx);
566 assert_eq!(
567 view.selections.display_ranges(cx),
568 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
569 );
570 });
571}
572
573#[gpui::test]
574fn test_clone(cx: &mut TestAppContext) {
575 init_test(cx, |_| {});
576
577 let (text, selection_ranges) = marked_text_ranges(
578 indoc! {"
579 one
580 two
581 threeˇ
582 four
583 fiveˇ
584 "},
585 true,
586 );
587
588 let editor = cx.add_window(|cx| {
589 let buffer = MultiBuffer::build_simple(&text, cx);
590 build_editor(buffer, cx)
591 });
592
593 _ = editor.update(cx, |editor, cx| {
594 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
595 editor.fold_ranges(
596 [
597 (Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
598 (Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
599 ],
600 true,
601 cx,
602 );
603 });
604
605 let cloned_editor = editor
606 .update(cx, |editor, cx| {
607 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
608 })
609 .unwrap()
610 .unwrap();
611
612 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
613 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
614
615 assert_eq!(
616 cloned_editor
617 .update(cx, |e, cx| e.display_text(cx))
618 .unwrap(),
619 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
620 );
621 assert_eq!(
622 cloned_snapshot
623 .folds_in_range(0..text.len())
624 .collect::<Vec<_>>(),
625 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
626 );
627 assert_set_eq!(
628 cloned_editor
629 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
630 .unwrap(),
631 editor
632 .update(cx, |editor, cx| editor.selections.ranges(cx))
633 .unwrap()
634 );
635 assert_set_eq!(
636 cloned_editor
637 .update(cx, |e, cx| e.selections.display_ranges(cx))
638 .unwrap(),
639 editor
640 .update(cx, |e, cx| e.selections.display_ranges(cx))
641 .unwrap()
642 );
643}
644
645#[gpui::test]
646async fn test_navigation_history(cx: &mut TestAppContext) {
647 init_test(cx, |_| {});
648
649 use workspace::item::Item;
650
651 let fs = FakeFs::new(cx.executor());
652 let project = Project::test(fs, [], cx).await;
653 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
654 let pane = workspace
655 .update(cx, |workspace, _| workspace.active_pane().clone())
656 .unwrap();
657
658 _ = workspace.update(cx, |_v, cx| {
659 cx.new_view(|cx| {
660 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
661 let mut editor = build_editor(buffer.clone(), cx);
662 let handle = cx.view();
663 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(handle)));
664
665 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
666 editor.nav_history.as_mut().unwrap().pop_backward(cx)
667 }
668
669 // Move the cursor a small distance.
670 // Nothing is added to the navigation history.
671 editor.change_selections(None, cx, |s| {
672 s.select_display_ranges([
673 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
674 ])
675 });
676 editor.change_selections(None, cx, |s| {
677 s.select_display_ranges([
678 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
679 ])
680 });
681 assert!(pop_history(&mut editor, cx).is_none());
682
683 // Move the cursor a large distance.
684 // The history can jump back to the previous position.
685 editor.change_selections(None, cx, |s| {
686 s.select_display_ranges([
687 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
688 ])
689 });
690 let nav_entry = pop_history(&mut editor, cx).unwrap();
691 editor.navigate(nav_entry.data.unwrap(), cx);
692 assert_eq!(nav_entry.item.id(), cx.entity_id());
693 assert_eq!(
694 editor.selections.display_ranges(cx),
695 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
696 );
697 assert!(pop_history(&mut editor, cx).is_none());
698
699 // Move the cursor a small distance via the mouse.
700 // Nothing is added to the navigation history.
701 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
702 editor.end_selection(cx);
703 assert_eq!(
704 editor.selections.display_ranges(cx),
705 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
706 );
707 assert!(pop_history(&mut editor, cx).is_none());
708
709 // Move the cursor a large distance via the mouse.
710 // The history can jump back to the previous position.
711 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
712 editor.end_selection(cx);
713 assert_eq!(
714 editor.selections.display_ranges(cx),
715 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
716 );
717 let nav_entry = pop_history(&mut editor, cx).unwrap();
718 editor.navigate(nav_entry.data.unwrap(), cx);
719 assert_eq!(nav_entry.item.id(), cx.entity_id());
720 assert_eq!(
721 editor.selections.display_ranges(cx),
722 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
723 );
724 assert!(pop_history(&mut editor, cx).is_none());
725
726 // Set scroll position to check later
727 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
728 let original_scroll_position = editor.scroll_manager.anchor();
729
730 // Jump to the end of the document and adjust scroll
731 editor.move_to_end(&MoveToEnd, cx);
732 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
733 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
734
735 let nav_entry = pop_history(&mut editor, cx).unwrap();
736 editor.navigate(nav_entry.data.unwrap(), cx);
737 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
738
739 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
740 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
741 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
742 let invalid_point = Point::new(9999, 0);
743 editor.navigate(
744 Box::new(NavigationData {
745 cursor_anchor: invalid_anchor,
746 cursor_position: invalid_point,
747 scroll_anchor: ScrollAnchor {
748 anchor: invalid_anchor,
749 offset: Default::default(),
750 },
751 scroll_top_row: invalid_point.row,
752 }),
753 cx,
754 );
755 assert_eq!(
756 editor.selections.display_ranges(cx),
757 &[editor.max_point(cx)..editor.max_point(cx)]
758 );
759 assert_eq!(
760 editor.scroll_position(cx),
761 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
762 );
763
764 editor
765 })
766 });
767}
768
769#[gpui::test]
770fn test_cancel(cx: &mut TestAppContext) {
771 init_test(cx, |_| {});
772
773 let view = cx.add_window(|cx| {
774 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
775 build_editor(buffer, cx)
776 });
777
778 _ = view.update(cx, |view, cx| {
779 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
780 view.update_selection(
781 DisplayPoint::new(DisplayRow(1), 1),
782 0,
783 gpui::Point::<f32>::default(),
784 cx,
785 );
786 view.end_selection(cx);
787
788 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
789 view.update_selection(
790 DisplayPoint::new(DisplayRow(0), 3),
791 0,
792 gpui::Point::<f32>::default(),
793 cx,
794 );
795 view.end_selection(cx);
796 assert_eq!(
797 view.selections.display_ranges(cx),
798 [
799 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
800 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
801 ]
802 );
803 });
804
805 _ = view.update(cx, |view, cx| {
806 view.cancel(&Cancel, cx);
807 assert_eq!(
808 view.selections.display_ranges(cx),
809 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
810 );
811 });
812
813 _ = view.update(cx, |view, cx| {
814 view.cancel(&Cancel, cx);
815 assert_eq!(
816 view.selections.display_ranges(cx),
817 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
818 );
819 });
820}
821
822#[gpui::test]
823fn test_fold_action(cx: &mut TestAppContext) {
824 init_test(cx, |_| {});
825
826 let view = cx.add_window(|cx| {
827 let buffer = MultiBuffer::build_simple(
828 &"
829 impl Foo {
830 // Hello!
831
832 fn a() {
833 1
834 }
835
836 fn b() {
837 2
838 }
839
840 fn c() {
841 3
842 }
843 }
844 "
845 .unindent(),
846 cx,
847 );
848 build_editor(buffer.clone(), cx)
849 });
850
851 _ = view.update(cx, |view, cx| {
852 view.change_selections(None, cx, |s| {
853 s.select_display_ranges([
854 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
855 ]);
856 });
857 view.fold(&Fold, cx);
858 assert_eq!(
859 view.display_text(cx),
860 "
861 impl Foo {
862 // Hello!
863
864 fn a() {
865 1
866 }
867
868 fn b() {⋯
869 }
870
871 fn c() {⋯
872 }
873 }
874 "
875 .unindent(),
876 );
877
878 view.fold(&Fold, cx);
879 assert_eq!(
880 view.display_text(cx),
881 "
882 impl Foo {⋯
883 }
884 "
885 .unindent(),
886 );
887
888 view.unfold_lines(&UnfoldLines, cx);
889 assert_eq!(
890 view.display_text(cx),
891 "
892 impl Foo {
893 // Hello!
894
895 fn a() {
896 1
897 }
898
899 fn b() {⋯
900 }
901
902 fn c() {⋯
903 }
904 }
905 "
906 .unindent(),
907 );
908
909 view.unfold_lines(&UnfoldLines, cx);
910 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
911 });
912}
913
914#[gpui::test]
915fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
916 init_test(cx, |_| {});
917
918 let view = cx.add_window(|cx| {
919 let buffer = MultiBuffer::build_simple(
920 &"
921 class Foo:
922 # Hello!
923
924 def a():
925 print(1)
926
927 def b():
928 print(2)
929
930 def c():
931 print(3)
932 "
933 .unindent(),
934 cx,
935 );
936 build_editor(buffer.clone(), cx)
937 });
938
939 _ = view.update(cx, |view, cx| {
940 view.change_selections(None, cx, |s| {
941 s.select_display_ranges([
942 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
943 ]);
944 });
945 view.fold(&Fold, cx);
946 assert_eq!(
947 view.display_text(cx),
948 "
949 class Foo:
950 # Hello!
951
952 def a():
953 print(1)
954
955 def b():⋯
956
957 def c():⋯
958 "
959 .unindent(),
960 );
961
962 view.fold(&Fold, cx);
963 assert_eq!(
964 view.display_text(cx),
965 "
966 class Foo:⋯
967 "
968 .unindent(),
969 );
970
971 view.unfold_lines(&UnfoldLines, cx);
972 assert_eq!(
973 view.display_text(cx),
974 "
975 class Foo:
976 # Hello!
977
978 def a():
979 print(1)
980
981 def b():⋯
982
983 def c():⋯
984 "
985 .unindent(),
986 );
987
988 view.unfold_lines(&UnfoldLines, cx);
989 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
990 });
991}
992
993#[gpui::test]
994fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
995 init_test(cx, |_| {});
996
997 let view = cx.add_window(|cx| {
998 let buffer = MultiBuffer::build_simple(
999 &"
1000 class Foo:
1001 # Hello!
1002
1003 def a():
1004 print(1)
1005
1006 def b():
1007 print(2)
1008
1009
1010 def c():
1011 print(3)
1012
1013
1014 "
1015 .unindent(),
1016 cx,
1017 );
1018 build_editor(buffer.clone(), cx)
1019 });
1020
1021 _ = view.update(cx, |view, cx| {
1022 view.change_selections(None, cx, |s| {
1023 s.select_display_ranges([
1024 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1025 ]);
1026 });
1027 view.fold(&Fold, cx);
1028 assert_eq!(
1029 view.display_text(cx),
1030 "
1031 class Foo:
1032 # Hello!
1033
1034 def a():
1035 print(1)
1036
1037 def b():⋯
1038
1039
1040 def c():⋯
1041
1042
1043 "
1044 .unindent(),
1045 );
1046
1047 view.fold(&Fold, cx);
1048 assert_eq!(
1049 view.display_text(cx),
1050 "
1051 class Foo:⋯
1052
1053
1054 "
1055 .unindent(),
1056 );
1057
1058 view.unfold_lines(&UnfoldLines, cx);
1059 assert_eq!(
1060 view.display_text(cx),
1061 "
1062 class Foo:
1063 # Hello!
1064
1065 def a():
1066 print(1)
1067
1068 def b():⋯
1069
1070
1071 def c():⋯
1072
1073
1074 "
1075 .unindent(),
1076 );
1077
1078 view.unfold_lines(&UnfoldLines, cx);
1079 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1080 });
1081}
1082
1083#[gpui::test]
1084fn test_fold_at_level(cx: &mut TestAppContext) {
1085 init_test(cx, |_| {});
1086
1087 let view = cx.add_window(|cx| {
1088 let buffer = MultiBuffer::build_simple(
1089 &"
1090 class Foo:
1091 # Hello!
1092
1093 def a():
1094 print(1)
1095
1096 def b():
1097 print(2)
1098
1099
1100 class Bar:
1101 # World!
1102
1103 def a():
1104 print(1)
1105
1106 def b():
1107 print(2)
1108
1109
1110 "
1111 .unindent(),
1112 cx,
1113 );
1114 build_editor(buffer.clone(), cx)
1115 });
1116
1117 _ = view.update(cx, |view, cx| {
1118 view.fold_at_level(&FoldAtLevel { level: 2 }, cx);
1119 assert_eq!(
1120 view.display_text(cx),
1121 "
1122 class Foo:
1123 # Hello!
1124
1125 def a():⋯
1126
1127 def b():⋯
1128
1129
1130 class Bar:
1131 # World!
1132
1133 def a():⋯
1134
1135 def b():⋯
1136
1137
1138 "
1139 .unindent(),
1140 );
1141
1142 view.fold_at_level(&FoldAtLevel { level: 1 }, cx);
1143 assert_eq!(
1144 view.display_text(cx),
1145 "
1146 class Foo:⋯
1147
1148
1149 class Bar:⋯
1150
1151
1152 "
1153 .unindent(),
1154 );
1155
1156 view.unfold_all(&UnfoldAll, cx);
1157 view.fold_at_level(&FoldAtLevel { level: 0 }, cx);
1158 assert_eq!(
1159 view.display_text(cx),
1160 "
1161 class Foo:
1162 # Hello!
1163
1164 def a():
1165 print(1)
1166
1167 def b():
1168 print(2)
1169
1170
1171 class Bar:
1172 # World!
1173
1174 def a():
1175 print(1)
1176
1177 def b():
1178 print(2)
1179
1180
1181 "
1182 .unindent(),
1183 );
1184
1185 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1186 });
1187}
1188
1189#[gpui::test]
1190fn test_move_cursor(cx: &mut TestAppContext) {
1191 init_test(cx, |_| {});
1192
1193 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1194 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1195
1196 buffer.update(cx, |buffer, cx| {
1197 buffer.edit(
1198 vec![
1199 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1200 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1201 ],
1202 None,
1203 cx,
1204 );
1205 });
1206 _ = view.update(cx, |view, cx| {
1207 assert_eq!(
1208 view.selections.display_ranges(cx),
1209 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1210 );
1211
1212 view.move_down(&MoveDown, cx);
1213 assert_eq!(
1214 view.selections.display_ranges(cx),
1215 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1216 );
1217
1218 view.move_right(&MoveRight, cx);
1219 assert_eq!(
1220 view.selections.display_ranges(cx),
1221 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1222 );
1223
1224 view.move_left(&MoveLeft, cx);
1225 assert_eq!(
1226 view.selections.display_ranges(cx),
1227 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1228 );
1229
1230 view.move_up(&MoveUp, cx);
1231 assert_eq!(
1232 view.selections.display_ranges(cx),
1233 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1234 );
1235
1236 view.move_to_end(&MoveToEnd, cx);
1237 assert_eq!(
1238 view.selections.display_ranges(cx),
1239 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1240 );
1241
1242 view.move_to_beginning(&MoveToBeginning, cx);
1243 assert_eq!(
1244 view.selections.display_ranges(cx),
1245 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1246 );
1247
1248 view.change_selections(None, cx, |s| {
1249 s.select_display_ranges([
1250 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1251 ]);
1252 });
1253 view.select_to_beginning(&SelectToBeginning, cx);
1254 assert_eq!(
1255 view.selections.display_ranges(cx),
1256 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1257 );
1258
1259 view.select_to_end(&SelectToEnd, cx);
1260 assert_eq!(
1261 view.selections.display_ranges(cx),
1262 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1263 );
1264 });
1265}
1266
1267// TODO: Re-enable this test
1268#[cfg(target_os = "macos")]
1269#[gpui::test]
1270fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1271 init_test(cx, |_| {});
1272
1273 let view = cx.add_window(|cx| {
1274 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1275 build_editor(buffer.clone(), cx)
1276 });
1277
1278 assert_eq!('ⓐ'.len_utf8(), 3);
1279 assert_eq!('α'.len_utf8(), 2);
1280
1281 _ = view.update(cx, |view, cx| {
1282 view.fold_ranges(
1283 vec![
1284 (Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1285 (Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1286 (Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1287 ],
1288 true,
1289 cx,
1290 );
1291 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1292
1293 view.move_right(&MoveRight, cx);
1294 assert_eq!(
1295 view.selections.display_ranges(cx),
1296 &[empty_range(0, "ⓐ".len())]
1297 );
1298 view.move_right(&MoveRight, cx);
1299 assert_eq!(
1300 view.selections.display_ranges(cx),
1301 &[empty_range(0, "ⓐⓑ".len())]
1302 );
1303 view.move_right(&MoveRight, cx);
1304 assert_eq!(
1305 view.selections.display_ranges(cx),
1306 &[empty_range(0, "ⓐⓑ⋯".len())]
1307 );
1308
1309 view.move_down(&MoveDown, cx);
1310 assert_eq!(
1311 view.selections.display_ranges(cx),
1312 &[empty_range(1, "ab⋯e".len())]
1313 );
1314 view.move_left(&MoveLeft, cx);
1315 assert_eq!(
1316 view.selections.display_ranges(cx),
1317 &[empty_range(1, "ab⋯".len())]
1318 );
1319 view.move_left(&MoveLeft, cx);
1320 assert_eq!(
1321 view.selections.display_ranges(cx),
1322 &[empty_range(1, "ab".len())]
1323 );
1324 view.move_left(&MoveLeft, cx);
1325 assert_eq!(
1326 view.selections.display_ranges(cx),
1327 &[empty_range(1, "a".len())]
1328 );
1329
1330 view.move_down(&MoveDown, cx);
1331 assert_eq!(
1332 view.selections.display_ranges(cx),
1333 &[empty_range(2, "α".len())]
1334 );
1335 view.move_right(&MoveRight, cx);
1336 assert_eq!(
1337 view.selections.display_ranges(cx),
1338 &[empty_range(2, "αβ".len())]
1339 );
1340 view.move_right(&MoveRight, cx);
1341 assert_eq!(
1342 view.selections.display_ranges(cx),
1343 &[empty_range(2, "αβ⋯".len())]
1344 );
1345 view.move_right(&MoveRight, cx);
1346 assert_eq!(
1347 view.selections.display_ranges(cx),
1348 &[empty_range(2, "αβ⋯ε".len())]
1349 );
1350
1351 view.move_up(&MoveUp, cx);
1352 assert_eq!(
1353 view.selections.display_ranges(cx),
1354 &[empty_range(1, "ab⋯e".len())]
1355 );
1356 view.move_down(&MoveDown, cx);
1357 assert_eq!(
1358 view.selections.display_ranges(cx),
1359 &[empty_range(2, "αβ⋯ε".len())]
1360 );
1361 view.move_up(&MoveUp, cx);
1362 assert_eq!(
1363 view.selections.display_ranges(cx),
1364 &[empty_range(1, "ab⋯e".len())]
1365 );
1366
1367 view.move_up(&MoveUp, cx);
1368 assert_eq!(
1369 view.selections.display_ranges(cx),
1370 &[empty_range(0, "ⓐⓑ".len())]
1371 );
1372 view.move_left(&MoveLeft, cx);
1373 assert_eq!(
1374 view.selections.display_ranges(cx),
1375 &[empty_range(0, "ⓐ".len())]
1376 );
1377 view.move_left(&MoveLeft, cx);
1378 assert_eq!(
1379 view.selections.display_ranges(cx),
1380 &[empty_range(0, "".len())]
1381 );
1382 });
1383}
1384
1385#[gpui::test]
1386fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1387 init_test(cx, |_| {});
1388
1389 let view = cx.add_window(|cx| {
1390 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1391 build_editor(buffer.clone(), cx)
1392 });
1393 _ = view.update(cx, |view, cx| {
1394 view.change_selections(None, cx, |s| {
1395 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1396 });
1397 view.move_down(&MoveDown, cx);
1398 assert_eq!(
1399 view.selections.display_ranges(cx),
1400 &[empty_range(1, "abcd".len())]
1401 );
1402
1403 view.move_down(&MoveDown, cx);
1404 assert_eq!(
1405 view.selections.display_ranges(cx),
1406 &[empty_range(2, "αβγ".len())]
1407 );
1408
1409 view.move_down(&MoveDown, cx);
1410 assert_eq!(
1411 view.selections.display_ranges(cx),
1412 &[empty_range(3, "abcd".len())]
1413 );
1414
1415 view.move_down(&MoveDown, cx);
1416 assert_eq!(
1417 view.selections.display_ranges(cx),
1418 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1419 );
1420
1421 view.move_up(&MoveUp, cx);
1422 assert_eq!(
1423 view.selections.display_ranges(cx),
1424 &[empty_range(3, "abcd".len())]
1425 );
1426
1427 view.move_up(&MoveUp, cx);
1428 assert_eq!(
1429 view.selections.display_ranges(cx),
1430 &[empty_range(2, "αβγ".len())]
1431 );
1432 });
1433}
1434
1435#[gpui::test]
1436fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1437 init_test(cx, |_| {});
1438 let move_to_beg = MoveToBeginningOfLine {
1439 stop_at_soft_wraps: true,
1440 };
1441
1442 let move_to_end = MoveToEndOfLine {
1443 stop_at_soft_wraps: true,
1444 };
1445
1446 let view = cx.add_window(|cx| {
1447 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1448 build_editor(buffer, cx)
1449 });
1450 _ = view.update(cx, |view, cx| {
1451 view.change_selections(None, cx, |s| {
1452 s.select_display_ranges([
1453 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1454 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1455 ]);
1456 });
1457 });
1458
1459 _ = view.update(cx, |view, cx| {
1460 view.move_to_beginning_of_line(&move_to_beg, cx);
1461 assert_eq!(
1462 view.selections.display_ranges(cx),
1463 &[
1464 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1465 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1466 ]
1467 );
1468 });
1469
1470 _ = view.update(cx, |view, cx| {
1471 view.move_to_beginning_of_line(&move_to_beg, cx);
1472 assert_eq!(
1473 view.selections.display_ranges(cx),
1474 &[
1475 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1476 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1477 ]
1478 );
1479 });
1480
1481 _ = view.update(cx, |view, cx| {
1482 view.move_to_beginning_of_line(&move_to_beg, cx);
1483 assert_eq!(
1484 view.selections.display_ranges(cx),
1485 &[
1486 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1487 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1488 ]
1489 );
1490 });
1491
1492 _ = view.update(cx, |view, cx| {
1493 view.move_to_end_of_line(&move_to_end, cx);
1494 assert_eq!(
1495 view.selections.display_ranges(cx),
1496 &[
1497 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1498 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1499 ]
1500 );
1501 });
1502
1503 // Moving to the end of line again is a no-op.
1504 _ = view.update(cx, |view, cx| {
1505 view.move_to_end_of_line(&move_to_end, cx);
1506 assert_eq!(
1507 view.selections.display_ranges(cx),
1508 &[
1509 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1510 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1511 ]
1512 );
1513 });
1514
1515 _ = view.update(cx, |view, cx| {
1516 view.move_left(&MoveLeft, cx);
1517 view.select_to_beginning_of_line(
1518 &SelectToBeginningOfLine {
1519 stop_at_soft_wraps: true,
1520 },
1521 cx,
1522 );
1523 assert_eq!(
1524 view.selections.display_ranges(cx),
1525 &[
1526 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1527 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1528 ]
1529 );
1530 });
1531
1532 _ = view.update(cx, |view, cx| {
1533 view.select_to_beginning_of_line(
1534 &SelectToBeginningOfLine {
1535 stop_at_soft_wraps: true,
1536 },
1537 cx,
1538 );
1539 assert_eq!(
1540 view.selections.display_ranges(cx),
1541 &[
1542 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1543 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1544 ]
1545 );
1546 });
1547
1548 _ = view.update(cx, |view, cx| {
1549 view.select_to_beginning_of_line(
1550 &SelectToBeginningOfLine {
1551 stop_at_soft_wraps: true,
1552 },
1553 cx,
1554 );
1555 assert_eq!(
1556 view.selections.display_ranges(cx),
1557 &[
1558 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1559 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1560 ]
1561 );
1562 });
1563
1564 _ = view.update(cx, |view, cx| {
1565 view.select_to_end_of_line(
1566 &SelectToEndOfLine {
1567 stop_at_soft_wraps: true,
1568 },
1569 cx,
1570 );
1571 assert_eq!(
1572 view.selections.display_ranges(cx),
1573 &[
1574 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1575 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1576 ]
1577 );
1578 });
1579
1580 _ = view.update(cx, |view, cx| {
1581 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1582 assert_eq!(view.display_text(cx), "ab\n de");
1583 assert_eq!(
1584 view.selections.display_ranges(cx),
1585 &[
1586 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1587 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1588 ]
1589 );
1590 });
1591
1592 _ = view.update(cx, |view, cx| {
1593 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1594 assert_eq!(view.display_text(cx), "\n");
1595 assert_eq!(
1596 view.selections.display_ranges(cx),
1597 &[
1598 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1599 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1600 ]
1601 );
1602 });
1603}
1604
1605#[gpui::test]
1606fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1607 init_test(cx, |_| {});
1608 let move_to_beg = MoveToBeginningOfLine {
1609 stop_at_soft_wraps: false,
1610 };
1611
1612 let move_to_end = MoveToEndOfLine {
1613 stop_at_soft_wraps: false,
1614 };
1615
1616 let view = cx.add_window(|cx| {
1617 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1618 build_editor(buffer, cx)
1619 });
1620
1621 _ = view.update(cx, |view, cx| {
1622 view.set_wrap_width(Some(140.0.into()), cx);
1623
1624 // We expect the following lines after wrapping
1625 // ```
1626 // thequickbrownfox
1627 // jumpedoverthelazydo
1628 // gs
1629 // ```
1630 // The final `gs` was soft-wrapped onto a new line.
1631 assert_eq!(
1632 "thequickbrownfox\njumpedoverthelaz\nydogs",
1633 view.display_text(cx),
1634 );
1635
1636 // First, let's assert behavior on the first line, that was not soft-wrapped.
1637 // Start the cursor at the `k` on the first line
1638 view.change_selections(None, cx, |s| {
1639 s.select_display_ranges([
1640 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1641 ]);
1642 });
1643
1644 // Moving to the beginning of the line should put us at the beginning of the line.
1645 view.move_to_beginning_of_line(&move_to_beg, cx);
1646 assert_eq!(
1647 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1648 view.selections.display_ranges(cx)
1649 );
1650
1651 // Moving to the end of the line should put us at the end of the line.
1652 view.move_to_end_of_line(&move_to_end, cx);
1653 assert_eq!(
1654 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1655 view.selections.display_ranges(cx)
1656 );
1657
1658 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1659 // Start the cursor at the last line (`y` that was wrapped to a new line)
1660 view.change_selections(None, cx, |s| {
1661 s.select_display_ranges([
1662 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1663 ]);
1664 });
1665
1666 // Moving to the beginning of the line should put us at the start of the second line of
1667 // display text, i.e., the `j`.
1668 view.move_to_beginning_of_line(&move_to_beg, cx);
1669 assert_eq!(
1670 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1671 view.selections.display_ranges(cx)
1672 );
1673
1674 // Moving to the beginning of the line again should be a no-op.
1675 view.move_to_beginning_of_line(&move_to_beg, cx);
1676 assert_eq!(
1677 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1678 view.selections.display_ranges(cx)
1679 );
1680
1681 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1682 // next display line.
1683 view.move_to_end_of_line(&move_to_end, cx);
1684 assert_eq!(
1685 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1686 view.selections.display_ranges(cx)
1687 );
1688
1689 // Moving to the end of the line again should be a no-op.
1690 view.move_to_end_of_line(&move_to_end, cx);
1691 assert_eq!(
1692 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1693 view.selections.display_ranges(cx)
1694 );
1695 });
1696}
1697
1698#[gpui::test]
1699fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1700 init_test(cx, |_| {});
1701
1702 let view = cx.add_window(|cx| {
1703 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1704 build_editor(buffer, cx)
1705 });
1706 _ = view.update(cx, |view, cx| {
1707 view.change_selections(None, cx, |s| {
1708 s.select_display_ranges([
1709 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1710 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1711 ])
1712 });
1713
1714 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1715 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1716
1717 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1718 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1719
1720 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1721 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1722
1723 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1724 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1725
1726 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1727 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1728
1729 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1730 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1731
1732 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1733 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1734
1735 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1736 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1737
1738 view.move_right(&MoveRight, cx);
1739 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1740 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1741
1742 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1743 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1744
1745 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1746 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1747 });
1748}
1749
1750#[gpui::test]
1751fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1752 init_test(cx, |_| {});
1753
1754 let view = cx.add_window(|cx| {
1755 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1756 build_editor(buffer, cx)
1757 });
1758
1759 _ = view.update(cx, |view, cx| {
1760 view.set_wrap_width(Some(140.0.into()), cx);
1761 assert_eq!(
1762 view.display_text(cx),
1763 "use one::{\n two::three::\n four::five\n};"
1764 );
1765
1766 view.change_selections(None, cx, |s| {
1767 s.select_display_ranges([
1768 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1769 ]);
1770 });
1771
1772 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1773 assert_eq!(
1774 view.selections.display_ranges(cx),
1775 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1776 );
1777
1778 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1779 assert_eq!(
1780 view.selections.display_ranges(cx),
1781 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1782 );
1783
1784 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1785 assert_eq!(
1786 view.selections.display_ranges(cx),
1787 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1788 );
1789
1790 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1791 assert_eq!(
1792 view.selections.display_ranges(cx),
1793 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1794 );
1795
1796 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1797 assert_eq!(
1798 view.selections.display_ranges(cx),
1799 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1800 );
1801
1802 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1803 assert_eq!(
1804 view.selections.display_ranges(cx),
1805 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1806 );
1807 });
1808}
1809
1810#[gpui::test]
1811async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1812 init_test(cx, |_| {});
1813 let mut cx = EditorTestContext::new(cx).await;
1814
1815 let line_height = cx.editor(|editor, cx| {
1816 editor
1817 .style()
1818 .unwrap()
1819 .text
1820 .line_height_in_pixels(cx.rem_size())
1821 });
1822 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1823
1824 cx.set_state(
1825 &r#"ˇone
1826 two
1827
1828 three
1829 fourˇ
1830 five
1831
1832 six"#
1833 .unindent(),
1834 );
1835
1836 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1837 cx.assert_editor_state(
1838 &r#"one
1839 two
1840 ˇ
1841 three
1842 four
1843 five
1844 ˇ
1845 six"#
1846 .unindent(),
1847 );
1848
1849 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1850 cx.assert_editor_state(
1851 &r#"one
1852 two
1853
1854 three
1855 four
1856 five
1857 ˇ
1858 sixˇ"#
1859 .unindent(),
1860 );
1861
1862 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1863 cx.assert_editor_state(
1864 &r#"one
1865 two
1866
1867 three
1868 four
1869 five
1870
1871 sixˇ"#
1872 .unindent(),
1873 );
1874
1875 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1876 cx.assert_editor_state(
1877 &r#"one
1878 two
1879
1880 three
1881 four
1882 five
1883 ˇ
1884 six"#
1885 .unindent(),
1886 );
1887
1888 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1889 cx.assert_editor_state(
1890 &r#"one
1891 two
1892 ˇ
1893 three
1894 four
1895 five
1896
1897 six"#
1898 .unindent(),
1899 );
1900
1901 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1902 cx.assert_editor_state(
1903 &r#"ˇone
1904 two
1905
1906 three
1907 four
1908 five
1909
1910 six"#
1911 .unindent(),
1912 );
1913}
1914
1915#[gpui::test]
1916async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1917 init_test(cx, |_| {});
1918 let mut cx = EditorTestContext::new(cx).await;
1919 let line_height = cx.editor(|editor, cx| {
1920 editor
1921 .style()
1922 .unwrap()
1923 .text
1924 .line_height_in_pixels(cx.rem_size())
1925 });
1926 let window = cx.window;
1927 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1928
1929 cx.set_state(
1930 r#"ˇone
1931 two
1932 three
1933 four
1934 five
1935 six
1936 seven
1937 eight
1938 nine
1939 ten
1940 "#,
1941 );
1942
1943 cx.update_editor(|editor, cx| {
1944 assert_eq!(
1945 editor.snapshot(cx).scroll_position(),
1946 gpui::Point::new(0., 0.)
1947 );
1948 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1949 assert_eq!(
1950 editor.snapshot(cx).scroll_position(),
1951 gpui::Point::new(0., 3.)
1952 );
1953 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1954 assert_eq!(
1955 editor.snapshot(cx).scroll_position(),
1956 gpui::Point::new(0., 6.)
1957 );
1958 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1959 assert_eq!(
1960 editor.snapshot(cx).scroll_position(),
1961 gpui::Point::new(0., 3.)
1962 );
1963
1964 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1965 assert_eq!(
1966 editor.snapshot(cx).scroll_position(),
1967 gpui::Point::new(0., 1.)
1968 );
1969 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
1970 assert_eq!(
1971 editor.snapshot(cx).scroll_position(),
1972 gpui::Point::new(0., 3.)
1973 );
1974 });
1975}
1976
1977#[gpui::test]
1978async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
1979 init_test(cx, |_| {});
1980 let mut cx = EditorTestContext::new(cx).await;
1981
1982 let line_height = cx.update_editor(|editor, cx| {
1983 editor.set_vertical_scroll_margin(2, cx);
1984 editor
1985 .style()
1986 .unwrap()
1987 .text
1988 .line_height_in_pixels(cx.rem_size())
1989 });
1990 let window = cx.window;
1991 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
1992
1993 cx.set_state(
1994 r#"ˇone
1995 two
1996 three
1997 four
1998 five
1999 six
2000 seven
2001 eight
2002 nine
2003 ten
2004 "#,
2005 );
2006 cx.update_editor(|editor, cx| {
2007 assert_eq!(
2008 editor.snapshot(cx).scroll_position(),
2009 gpui::Point::new(0., 0.0)
2010 );
2011 });
2012
2013 // Add a cursor below the visible area. Since both cursors cannot fit
2014 // on screen, the editor autoscrolls to reveal the newest cursor, and
2015 // allows the vertical scroll margin below that cursor.
2016 cx.update_editor(|editor, cx| {
2017 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2018 selections.select_ranges([
2019 Point::new(0, 0)..Point::new(0, 0),
2020 Point::new(6, 0)..Point::new(6, 0),
2021 ]);
2022 })
2023 });
2024 cx.update_editor(|editor, cx| {
2025 assert_eq!(
2026 editor.snapshot(cx).scroll_position(),
2027 gpui::Point::new(0., 3.0)
2028 );
2029 });
2030
2031 // Move down. The editor cursor scrolls down to track the newest cursor.
2032 cx.update_editor(|editor, cx| {
2033 editor.move_down(&Default::default(), cx);
2034 });
2035 cx.update_editor(|editor, cx| {
2036 assert_eq!(
2037 editor.snapshot(cx).scroll_position(),
2038 gpui::Point::new(0., 4.0)
2039 );
2040 });
2041
2042 // Add a cursor above the visible area. Since both cursors fit on screen,
2043 // the editor scrolls to show both.
2044 cx.update_editor(|editor, cx| {
2045 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2046 selections.select_ranges([
2047 Point::new(1, 0)..Point::new(1, 0),
2048 Point::new(6, 0)..Point::new(6, 0),
2049 ]);
2050 })
2051 });
2052 cx.update_editor(|editor, cx| {
2053 assert_eq!(
2054 editor.snapshot(cx).scroll_position(),
2055 gpui::Point::new(0., 1.0)
2056 );
2057 });
2058}
2059
2060#[gpui::test]
2061async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
2062 init_test(cx, |_| {});
2063 let mut cx = EditorTestContext::new(cx).await;
2064
2065 let line_height = cx.editor(|editor, cx| {
2066 editor
2067 .style()
2068 .unwrap()
2069 .text
2070 .line_height_in_pixels(cx.rem_size())
2071 });
2072 let window = cx.window;
2073 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2074 cx.set_state(
2075 &r#"
2076 ˇone
2077 two
2078 threeˇ
2079 four
2080 five
2081 six
2082 seven
2083 eight
2084 nine
2085 ten
2086 "#
2087 .unindent(),
2088 );
2089
2090 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2091 cx.assert_editor_state(
2092 &r#"
2093 one
2094 two
2095 three
2096 ˇfour
2097 five
2098 sixˇ
2099 seven
2100 eight
2101 nine
2102 ten
2103 "#
2104 .unindent(),
2105 );
2106
2107 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2108 cx.assert_editor_state(
2109 &r#"
2110 one
2111 two
2112 three
2113 four
2114 five
2115 six
2116 ˇseven
2117 eight
2118 nineˇ
2119 ten
2120 "#
2121 .unindent(),
2122 );
2123
2124 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2125 cx.assert_editor_state(
2126 &r#"
2127 one
2128 two
2129 three
2130 ˇfour
2131 five
2132 sixˇ
2133 seven
2134 eight
2135 nine
2136 ten
2137 "#
2138 .unindent(),
2139 );
2140
2141 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2142 cx.assert_editor_state(
2143 &r#"
2144 ˇone
2145 two
2146 threeˇ
2147 four
2148 five
2149 six
2150 seven
2151 eight
2152 nine
2153 ten
2154 "#
2155 .unindent(),
2156 );
2157
2158 // Test select collapsing
2159 cx.update_editor(|editor, cx| {
2160 editor.move_page_down(&MovePageDown::default(), cx);
2161 editor.move_page_down(&MovePageDown::default(), cx);
2162 editor.move_page_down(&MovePageDown::default(), cx);
2163 });
2164 cx.assert_editor_state(
2165 &r#"
2166 one
2167 two
2168 three
2169 four
2170 five
2171 six
2172 seven
2173 eight
2174 nine
2175 ˇten
2176 ˇ"#
2177 .unindent(),
2178 );
2179}
2180
2181#[gpui::test]
2182async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2183 init_test(cx, |_| {});
2184 let mut cx = EditorTestContext::new(cx).await;
2185 cx.set_state("one «two threeˇ» four");
2186 cx.update_editor(|editor, cx| {
2187 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2188 assert_eq!(editor.text(cx), " four");
2189 });
2190}
2191
2192#[gpui::test]
2193fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2194 init_test(cx, |_| {});
2195
2196 let view = cx.add_window(|cx| {
2197 let buffer = MultiBuffer::build_simple("one two three four", cx);
2198 build_editor(buffer.clone(), cx)
2199 });
2200
2201 _ = view.update(cx, |view, cx| {
2202 view.change_selections(None, cx, |s| {
2203 s.select_display_ranges([
2204 // an empty selection - the preceding word fragment is deleted
2205 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2206 // characters selected - they are deleted
2207 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2208 ])
2209 });
2210 view.delete_to_previous_word_start(
2211 &DeleteToPreviousWordStart {
2212 ignore_newlines: false,
2213 },
2214 cx,
2215 );
2216 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2217 });
2218
2219 _ = view.update(cx, |view, cx| {
2220 view.change_selections(None, cx, |s| {
2221 s.select_display_ranges([
2222 // an empty selection - the following word fragment is deleted
2223 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2224 // characters selected - they are deleted
2225 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2226 ])
2227 });
2228 view.delete_to_next_word_end(
2229 &DeleteToNextWordEnd {
2230 ignore_newlines: false,
2231 },
2232 cx,
2233 );
2234 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2235 });
2236}
2237
2238#[gpui::test]
2239fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2240 init_test(cx, |_| {});
2241
2242 let view = cx.add_window(|cx| {
2243 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2244 build_editor(buffer.clone(), cx)
2245 });
2246 let del_to_prev_word_start = DeleteToPreviousWordStart {
2247 ignore_newlines: false,
2248 };
2249 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2250 ignore_newlines: true,
2251 };
2252
2253 _ = view.update(cx, |view, cx| {
2254 view.change_selections(None, cx, |s| {
2255 s.select_display_ranges([
2256 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2257 ])
2258 });
2259 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2260 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2261 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2262 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree");
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\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");
2267 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2268 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n");
2269 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2270 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2271 });
2272}
2273
2274#[gpui::test]
2275fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2276 init_test(cx, |_| {});
2277
2278 let view = cx.add_window(|cx| {
2279 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2280 build_editor(buffer.clone(), cx)
2281 });
2282 let del_to_next_word_end = DeleteToNextWordEnd {
2283 ignore_newlines: false,
2284 };
2285 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2286 ignore_newlines: true,
2287 };
2288
2289 _ = view.update(cx, |view, cx| {
2290 view.change_selections(None, cx, |s| {
2291 s.select_display_ranges([
2292 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2293 ])
2294 });
2295 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2296 assert_eq!(
2297 view.buffer.read(cx).read(cx).text(),
2298 "one\n two\nthree\n four"
2299 );
2300 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2301 assert_eq!(
2302 view.buffer.read(cx).read(cx).text(),
2303 "\n two\nthree\n four"
2304 );
2305 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2306 assert_eq!(view.buffer.read(cx).read(cx).text(), "two\nthree\n four");
2307 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2308 assert_eq!(view.buffer.read(cx).read(cx).text(), "\nthree\n four");
2309 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2310 assert_eq!(view.buffer.read(cx).read(cx).text(), "\n four");
2311 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2312 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2313 });
2314}
2315
2316#[gpui::test]
2317fn test_newline(cx: &mut TestAppContext) {
2318 init_test(cx, |_| {});
2319
2320 let view = cx.add_window(|cx| {
2321 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2322 build_editor(buffer.clone(), cx)
2323 });
2324
2325 _ = view.update(cx, |view, cx| {
2326 view.change_selections(None, cx, |s| {
2327 s.select_display_ranges([
2328 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2329 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2330 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2331 ])
2332 });
2333
2334 view.newline(&Newline, cx);
2335 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2336 });
2337}
2338
2339#[gpui::test]
2340fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2341 init_test(cx, |_| {});
2342
2343 let editor = cx.add_window(|cx| {
2344 let buffer = MultiBuffer::build_simple(
2345 "
2346 a
2347 b(
2348 X
2349 )
2350 c(
2351 X
2352 )
2353 "
2354 .unindent()
2355 .as_str(),
2356 cx,
2357 );
2358 let mut editor = build_editor(buffer.clone(), cx);
2359 editor.change_selections(None, cx, |s| {
2360 s.select_ranges([
2361 Point::new(2, 4)..Point::new(2, 5),
2362 Point::new(5, 4)..Point::new(5, 5),
2363 ])
2364 });
2365 editor
2366 });
2367
2368 _ = editor.update(cx, |editor, cx| {
2369 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2370 editor.buffer.update(cx, |buffer, cx| {
2371 buffer.edit(
2372 [
2373 (Point::new(1, 2)..Point::new(3, 0), ""),
2374 (Point::new(4, 2)..Point::new(6, 0), ""),
2375 ],
2376 None,
2377 cx,
2378 );
2379 assert_eq!(
2380 buffer.read(cx).text(),
2381 "
2382 a
2383 b()
2384 c()
2385 "
2386 .unindent()
2387 );
2388 });
2389 assert_eq!(
2390 editor.selections.ranges(cx),
2391 &[
2392 Point::new(1, 2)..Point::new(1, 2),
2393 Point::new(2, 2)..Point::new(2, 2),
2394 ],
2395 );
2396
2397 editor.newline(&Newline, cx);
2398 assert_eq!(
2399 editor.text(cx),
2400 "
2401 a
2402 b(
2403 )
2404 c(
2405 )
2406 "
2407 .unindent()
2408 );
2409
2410 // The selections are moved after the inserted newlines
2411 assert_eq!(
2412 editor.selections.ranges(cx),
2413 &[
2414 Point::new(2, 0)..Point::new(2, 0),
2415 Point::new(4, 0)..Point::new(4, 0),
2416 ],
2417 );
2418 });
2419}
2420
2421#[gpui::test]
2422async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2423 init_test(cx, |settings| {
2424 settings.defaults.tab_size = NonZeroU32::new(4)
2425 });
2426
2427 let language = Arc::new(
2428 Language::new(
2429 LanguageConfig::default(),
2430 Some(tree_sitter_rust::LANGUAGE.into()),
2431 )
2432 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2433 .unwrap(),
2434 );
2435
2436 let mut cx = EditorTestContext::new(cx).await;
2437 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2438 cx.set_state(indoc! {"
2439 const a: ˇA = (
2440 (ˇ
2441 «const_functionˇ»(ˇ),
2442 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2443 )ˇ
2444 ˇ);ˇ
2445 "});
2446
2447 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2448 cx.assert_editor_state(indoc! {"
2449 ˇ
2450 const a: A = (
2451 ˇ
2452 (
2453 ˇ
2454 ˇ
2455 const_function(),
2456 ˇ
2457 ˇ
2458 ˇ
2459 ˇ
2460 something_else,
2461 ˇ
2462 )
2463 ˇ
2464 ˇ
2465 );
2466 "});
2467}
2468
2469#[gpui::test]
2470async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2471 init_test(cx, |settings| {
2472 settings.defaults.tab_size = NonZeroU32::new(4)
2473 });
2474
2475 let language = Arc::new(
2476 Language::new(
2477 LanguageConfig::default(),
2478 Some(tree_sitter_rust::LANGUAGE.into()),
2479 )
2480 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2481 .unwrap(),
2482 );
2483
2484 let mut cx = EditorTestContext::new(cx).await;
2485 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2486 cx.set_state(indoc! {"
2487 const a: ˇA = (
2488 (ˇ
2489 «const_functionˇ»(ˇ),
2490 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2491 )ˇ
2492 ˇ);ˇ
2493 "});
2494
2495 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2496 cx.assert_editor_state(indoc! {"
2497 const a: A = (
2498 ˇ
2499 (
2500 ˇ
2501 const_function(),
2502 ˇ
2503 ˇ
2504 something_else,
2505 ˇ
2506 ˇ
2507 ˇ
2508 ˇ
2509 )
2510 ˇ
2511 );
2512 ˇ
2513 ˇ
2514 "});
2515}
2516
2517#[gpui::test]
2518async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2519 init_test(cx, |settings| {
2520 settings.defaults.tab_size = NonZeroU32::new(4)
2521 });
2522
2523 let language = Arc::new(Language::new(
2524 LanguageConfig {
2525 line_comments: vec!["//".into()],
2526 ..LanguageConfig::default()
2527 },
2528 None,
2529 ));
2530 {
2531 let mut cx = EditorTestContext::new(cx).await;
2532 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2533 cx.set_state(indoc! {"
2534 // Fooˇ
2535 "});
2536
2537 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2538 cx.assert_editor_state(indoc! {"
2539 // Foo
2540 //ˇ
2541 "});
2542 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2543 cx.set_state(indoc! {"
2544 ˇ// Foo
2545 "});
2546 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2547 cx.assert_editor_state(indoc! {"
2548
2549 ˇ// Foo
2550 "});
2551 }
2552 // Ensure that comment continuations can be disabled.
2553 update_test_language_settings(cx, |settings| {
2554 settings.defaults.extend_comment_on_newline = Some(false);
2555 });
2556 let mut cx = EditorTestContext::new(cx).await;
2557 cx.set_state(indoc! {"
2558 // Fooˇ
2559 "});
2560 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2561 cx.assert_editor_state(indoc! {"
2562 // Foo
2563 ˇ
2564 "});
2565}
2566
2567#[gpui::test]
2568fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2569 init_test(cx, |_| {});
2570
2571 let editor = cx.add_window(|cx| {
2572 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2573 let mut editor = build_editor(buffer.clone(), cx);
2574 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2575 editor
2576 });
2577
2578 _ = editor.update(cx, |editor, cx| {
2579 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2580 editor.buffer.update(cx, |buffer, cx| {
2581 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2582 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2583 });
2584 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2585
2586 editor.insert("Z", cx);
2587 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2588
2589 // The selections are moved after the inserted characters
2590 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2591 });
2592}
2593
2594#[gpui::test]
2595async fn test_tab(cx: &mut gpui::TestAppContext) {
2596 init_test(cx, |settings| {
2597 settings.defaults.tab_size = NonZeroU32::new(3)
2598 });
2599
2600 let mut cx = EditorTestContext::new(cx).await;
2601 cx.set_state(indoc! {"
2602 ˇabˇc
2603 ˇ🏀ˇ🏀ˇefg
2604 dˇ
2605 "});
2606 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2607 cx.assert_editor_state(indoc! {"
2608 ˇab ˇc
2609 ˇ🏀 ˇ🏀 ˇefg
2610 d ˇ
2611 "});
2612
2613 cx.set_state(indoc! {"
2614 a
2615 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2616 "});
2617 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2618 cx.assert_editor_state(indoc! {"
2619 a
2620 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2621 "});
2622}
2623
2624#[gpui::test]
2625async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2626 init_test(cx, |_| {});
2627
2628 let mut cx = EditorTestContext::new(cx).await;
2629 let language = Arc::new(
2630 Language::new(
2631 LanguageConfig::default(),
2632 Some(tree_sitter_rust::LANGUAGE.into()),
2633 )
2634 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2635 .unwrap(),
2636 );
2637 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2638
2639 // cursors that are already at the suggested indent level insert
2640 // a soft tab. cursors that are to the left of the suggested indent
2641 // auto-indent their line.
2642 cx.set_state(indoc! {"
2643 ˇ
2644 const a: B = (
2645 c(
2646 d(
2647 ˇ
2648 )
2649 ˇ
2650 ˇ )
2651 );
2652 "});
2653 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2654 cx.assert_editor_state(indoc! {"
2655 ˇ
2656 const a: B = (
2657 c(
2658 d(
2659 ˇ
2660 )
2661 ˇ
2662 ˇ)
2663 );
2664 "});
2665
2666 // handle auto-indent when there are multiple cursors on the same line
2667 cx.set_state(indoc! {"
2668 const a: B = (
2669 c(
2670 ˇ ˇ
2671 ˇ )
2672 );
2673 "});
2674 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2675 cx.assert_editor_state(indoc! {"
2676 const a: B = (
2677 c(
2678 ˇ
2679 ˇ)
2680 );
2681 "});
2682}
2683
2684#[gpui::test]
2685async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2686 init_test(cx, |settings| {
2687 settings.defaults.tab_size = NonZeroU32::new(4)
2688 });
2689
2690 let language = Arc::new(
2691 Language::new(
2692 LanguageConfig::default(),
2693 Some(tree_sitter_rust::LANGUAGE.into()),
2694 )
2695 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2696 .unwrap(),
2697 );
2698
2699 let mut cx = EditorTestContext::new(cx).await;
2700 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2701 cx.set_state(indoc! {"
2702 fn a() {
2703 if b {
2704 \t ˇc
2705 }
2706 }
2707 "});
2708
2709 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2710 cx.assert_editor_state(indoc! {"
2711 fn a() {
2712 if b {
2713 ˇc
2714 }
2715 }
2716 "});
2717}
2718
2719#[gpui::test]
2720async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2721 init_test(cx, |settings| {
2722 settings.defaults.tab_size = NonZeroU32::new(4);
2723 });
2724
2725 let mut cx = EditorTestContext::new(cx).await;
2726
2727 cx.set_state(indoc! {"
2728 «oneˇ» «twoˇ»
2729 three
2730 four
2731 "});
2732 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2733 cx.assert_editor_state(indoc! {"
2734 «oneˇ» «twoˇ»
2735 three
2736 four
2737 "});
2738
2739 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2740 cx.assert_editor_state(indoc! {"
2741 «oneˇ» «twoˇ»
2742 three
2743 four
2744 "});
2745
2746 // select across line ending
2747 cx.set_state(indoc! {"
2748 one two
2749 t«hree
2750 ˇ» four
2751 "});
2752 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2753 cx.assert_editor_state(indoc! {"
2754 one two
2755 t«hree
2756 ˇ» four
2757 "});
2758
2759 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2760 cx.assert_editor_state(indoc! {"
2761 one two
2762 t«hree
2763 ˇ» four
2764 "});
2765
2766 // Ensure that indenting/outdenting works when the cursor is at column 0.
2767 cx.set_state(indoc! {"
2768 one two
2769 ˇthree
2770 four
2771 "});
2772 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2773 cx.assert_editor_state(indoc! {"
2774 one two
2775 ˇthree
2776 four
2777 "});
2778
2779 cx.set_state(indoc! {"
2780 one two
2781 ˇ three
2782 four
2783 "});
2784 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2785 cx.assert_editor_state(indoc! {"
2786 one two
2787 ˇthree
2788 four
2789 "});
2790}
2791
2792#[gpui::test]
2793async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2794 init_test(cx, |settings| {
2795 settings.defaults.hard_tabs = Some(true);
2796 });
2797
2798 let mut cx = EditorTestContext::new(cx).await;
2799
2800 // select two ranges on one line
2801 cx.set_state(indoc! {"
2802 «oneˇ» «twoˇ»
2803 three
2804 four
2805 "});
2806 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2807 cx.assert_editor_state(indoc! {"
2808 \t«oneˇ» «twoˇ»
2809 three
2810 four
2811 "});
2812 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2813 cx.assert_editor_state(indoc! {"
2814 \t\t«oneˇ» «twoˇ»
2815 three
2816 four
2817 "});
2818 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2819 cx.assert_editor_state(indoc! {"
2820 \t«oneˇ» «twoˇ»
2821 three
2822 four
2823 "});
2824 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2825 cx.assert_editor_state(indoc! {"
2826 «oneˇ» «twoˇ»
2827 three
2828 four
2829 "});
2830
2831 // select across a line ending
2832 cx.set_state(indoc! {"
2833 one two
2834 t«hree
2835 ˇ»four
2836 "});
2837 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2838 cx.assert_editor_state(indoc! {"
2839 one two
2840 \tt«hree
2841 ˇ»four
2842 "});
2843 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2844 cx.assert_editor_state(indoc! {"
2845 one two
2846 \t\tt«hree
2847 ˇ»four
2848 "});
2849 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2850 cx.assert_editor_state(indoc! {"
2851 one two
2852 \tt«hree
2853 ˇ»four
2854 "});
2855 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2856 cx.assert_editor_state(indoc! {"
2857 one two
2858 t«hree
2859 ˇ»four
2860 "});
2861
2862 // Ensure that indenting/outdenting works when the cursor is at column 0.
2863 cx.set_state(indoc! {"
2864 one two
2865 ˇthree
2866 four
2867 "});
2868 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2869 cx.assert_editor_state(indoc! {"
2870 one two
2871 ˇthree
2872 four
2873 "});
2874 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2875 cx.assert_editor_state(indoc! {"
2876 one two
2877 \tˇthree
2878 four
2879 "});
2880 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2881 cx.assert_editor_state(indoc! {"
2882 one two
2883 ˇthree
2884 four
2885 "});
2886}
2887
2888#[gpui::test]
2889fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2890 init_test(cx, |settings| {
2891 settings.languages.extend([
2892 (
2893 "TOML".into(),
2894 LanguageSettingsContent {
2895 tab_size: NonZeroU32::new(2),
2896 ..Default::default()
2897 },
2898 ),
2899 (
2900 "Rust".into(),
2901 LanguageSettingsContent {
2902 tab_size: NonZeroU32::new(4),
2903 ..Default::default()
2904 },
2905 ),
2906 ]);
2907 });
2908
2909 let toml_language = Arc::new(Language::new(
2910 LanguageConfig {
2911 name: "TOML".into(),
2912 ..Default::default()
2913 },
2914 None,
2915 ));
2916 let rust_language = Arc::new(Language::new(
2917 LanguageConfig {
2918 name: "Rust".into(),
2919 ..Default::default()
2920 },
2921 None,
2922 ));
2923
2924 let toml_buffer =
2925 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2926 let rust_buffer = cx.new_model(|cx| {
2927 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2928 });
2929 let multibuffer = cx.new_model(|cx| {
2930 let mut multibuffer = MultiBuffer::new(ReadWrite);
2931 multibuffer.push_excerpts(
2932 toml_buffer.clone(),
2933 [ExcerptRange {
2934 context: Point::new(0, 0)..Point::new(2, 0),
2935 primary: None,
2936 }],
2937 cx,
2938 );
2939 multibuffer.push_excerpts(
2940 rust_buffer.clone(),
2941 [ExcerptRange {
2942 context: Point::new(0, 0)..Point::new(1, 0),
2943 primary: None,
2944 }],
2945 cx,
2946 );
2947 multibuffer
2948 });
2949
2950 cx.add_window(|cx| {
2951 let mut editor = build_editor(multibuffer, cx);
2952
2953 assert_eq!(
2954 editor.text(cx),
2955 indoc! {"
2956 a = 1
2957 b = 2
2958
2959 const c: usize = 3;
2960 "}
2961 );
2962
2963 select_ranges(
2964 &mut editor,
2965 indoc! {"
2966 «aˇ» = 1
2967 b = 2
2968
2969 «const c:ˇ» usize = 3;
2970 "},
2971 cx,
2972 );
2973
2974 editor.tab(&Tab, cx);
2975 assert_text_with_selections(
2976 &mut editor,
2977 indoc! {"
2978 «aˇ» = 1
2979 b = 2
2980
2981 «const c:ˇ» usize = 3;
2982 "},
2983 cx,
2984 );
2985 editor.tab_prev(&TabPrev, cx);
2986 assert_text_with_selections(
2987 &mut editor,
2988 indoc! {"
2989 «aˇ» = 1
2990 b = 2
2991
2992 «const c:ˇ» usize = 3;
2993 "},
2994 cx,
2995 );
2996
2997 editor
2998 });
2999}
3000
3001#[gpui::test]
3002async fn test_backspace(cx: &mut gpui::TestAppContext) {
3003 init_test(cx, |_| {});
3004
3005 let mut cx = EditorTestContext::new(cx).await;
3006
3007 // Basic backspace
3008 cx.set_state(indoc! {"
3009 onˇe two three
3010 fou«rˇ» five six
3011 seven «ˇeight nine
3012 »ten
3013 "});
3014 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3015 cx.assert_editor_state(indoc! {"
3016 oˇe two three
3017 fouˇ five six
3018 seven ˇten
3019 "});
3020
3021 // Test backspace inside and around indents
3022 cx.set_state(indoc! {"
3023 zero
3024 ˇone
3025 ˇtwo
3026 ˇ ˇ ˇ three
3027 ˇ ˇ four
3028 "});
3029 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3030 cx.assert_editor_state(indoc! {"
3031 zero
3032 ˇone
3033 ˇtwo
3034 ˇ threeˇ four
3035 "});
3036
3037 // Test backspace with line_mode set to true
3038 cx.update_editor(|e, _| e.selections.line_mode = true);
3039 cx.set_state(indoc! {"
3040 The ˇquick ˇbrown
3041 fox jumps over
3042 the lazy dog
3043 ˇThe qu«ick bˇ»rown"});
3044 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3045 cx.assert_editor_state(indoc! {"
3046 ˇfox jumps over
3047 the lazy dogˇ"});
3048}
3049
3050#[gpui::test]
3051async fn test_delete(cx: &mut gpui::TestAppContext) {
3052 init_test(cx, |_| {});
3053
3054 let mut cx = EditorTestContext::new(cx).await;
3055 cx.set_state(indoc! {"
3056 onˇe two three
3057 fou«rˇ» five six
3058 seven «ˇeight nine
3059 »ten
3060 "});
3061 cx.update_editor(|e, cx| e.delete(&Delete, cx));
3062 cx.assert_editor_state(indoc! {"
3063 onˇ two three
3064 fouˇ five six
3065 seven ˇten
3066 "});
3067
3068 // Test backspace with line_mode set to true
3069 cx.update_editor(|e, _| e.selections.line_mode = true);
3070 cx.set_state(indoc! {"
3071 The ˇquick ˇbrown
3072 fox «ˇjum»ps over
3073 the lazy dog
3074 ˇThe qu«ick bˇ»rown"});
3075 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3076 cx.assert_editor_state("ˇthe lazy dogˇ");
3077}
3078
3079#[gpui::test]
3080fn test_delete_line(cx: &mut TestAppContext) {
3081 init_test(cx, |_| {});
3082
3083 let view = cx.add_window(|cx| {
3084 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3085 build_editor(buffer, cx)
3086 });
3087 _ = view.update(cx, |view, cx| {
3088 view.change_selections(None, cx, |s| {
3089 s.select_display_ranges([
3090 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3091 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3092 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3093 ])
3094 });
3095 view.delete_line(&DeleteLine, cx);
3096 assert_eq!(view.display_text(cx), "ghi");
3097 assert_eq!(
3098 view.selections.display_ranges(cx),
3099 vec![
3100 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3101 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3102 ]
3103 );
3104 });
3105
3106 let view = cx.add_window(|cx| {
3107 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3108 build_editor(buffer, cx)
3109 });
3110 _ = view.update(cx, |view, cx| {
3111 view.change_selections(None, cx, |s| {
3112 s.select_display_ranges([
3113 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3114 ])
3115 });
3116 view.delete_line(&DeleteLine, cx);
3117 assert_eq!(view.display_text(cx), "ghi\n");
3118 assert_eq!(
3119 view.selections.display_ranges(cx),
3120 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3121 );
3122 });
3123}
3124
3125#[gpui::test]
3126fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3127 init_test(cx, |_| {});
3128
3129 cx.add_window(|cx| {
3130 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3131 let mut editor = build_editor(buffer.clone(), cx);
3132 let buffer = buffer.read(cx).as_singleton().unwrap();
3133
3134 assert_eq!(
3135 editor.selections.ranges::<Point>(cx),
3136 &[Point::new(0, 0)..Point::new(0, 0)]
3137 );
3138
3139 // When on single line, replace newline at end by space
3140 editor.join_lines(&JoinLines, cx);
3141 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3142 assert_eq!(
3143 editor.selections.ranges::<Point>(cx),
3144 &[Point::new(0, 3)..Point::new(0, 3)]
3145 );
3146
3147 // When multiple lines are selected, remove newlines that are spanned by the selection
3148 editor.change_selections(None, cx, |s| {
3149 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3150 });
3151 editor.join_lines(&JoinLines, cx);
3152 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3153 assert_eq!(
3154 editor.selections.ranges::<Point>(cx),
3155 &[Point::new(0, 11)..Point::new(0, 11)]
3156 );
3157
3158 // Undo should be transactional
3159 editor.undo(&Undo, cx);
3160 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3161 assert_eq!(
3162 editor.selections.ranges::<Point>(cx),
3163 &[Point::new(0, 5)..Point::new(2, 2)]
3164 );
3165
3166 // When joining an empty line don't insert a space
3167 editor.change_selections(None, cx, |s| {
3168 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3169 });
3170 editor.join_lines(&JoinLines, cx);
3171 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3172 assert_eq!(
3173 editor.selections.ranges::<Point>(cx),
3174 [Point::new(2, 3)..Point::new(2, 3)]
3175 );
3176
3177 // We can remove trailing newlines
3178 editor.join_lines(&JoinLines, cx);
3179 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3180 assert_eq!(
3181 editor.selections.ranges::<Point>(cx),
3182 [Point::new(2, 3)..Point::new(2, 3)]
3183 );
3184
3185 // We don't blow up on the last line
3186 editor.join_lines(&JoinLines, cx);
3187 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3188 assert_eq!(
3189 editor.selections.ranges::<Point>(cx),
3190 [Point::new(2, 3)..Point::new(2, 3)]
3191 );
3192
3193 // reset to test indentation
3194 editor.buffer.update(cx, |buffer, cx| {
3195 buffer.edit(
3196 [
3197 (Point::new(1, 0)..Point::new(1, 2), " "),
3198 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3199 ],
3200 None,
3201 cx,
3202 )
3203 });
3204
3205 // We remove any leading spaces
3206 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3207 editor.change_selections(None, cx, |s| {
3208 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3209 });
3210 editor.join_lines(&JoinLines, cx);
3211 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3212
3213 // We don't insert a space for a line containing only spaces
3214 editor.join_lines(&JoinLines, cx);
3215 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3216
3217 // We ignore any leading tabs
3218 editor.join_lines(&JoinLines, cx);
3219 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3220
3221 editor
3222 });
3223}
3224
3225#[gpui::test]
3226fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3227 init_test(cx, |_| {});
3228
3229 cx.add_window(|cx| {
3230 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3231 let mut editor = build_editor(buffer.clone(), cx);
3232 let buffer = buffer.read(cx).as_singleton().unwrap();
3233
3234 editor.change_selections(None, cx, |s| {
3235 s.select_ranges([
3236 Point::new(0, 2)..Point::new(1, 1),
3237 Point::new(1, 2)..Point::new(1, 2),
3238 Point::new(3, 1)..Point::new(3, 2),
3239 ])
3240 });
3241
3242 editor.join_lines(&JoinLines, cx);
3243 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3244
3245 assert_eq!(
3246 editor.selections.ranges::<Point>(cx),
3247 [
3248 Point::new(0, 7)..Point::new(0, 7),
3249 Point::new(1, 3)..Point::new(1, 3)
3250 ]
3251 );
3252 editor
3253 });
3254}
3255
3256#[gpui::test]
3257async fn test_join_lines_with_git_diff_base(
3258 executor: BackgroundExecutor,
3259 cx: &mut gpui::TestAppContext,
3260) {
3261 init_test(cx, |_| {});
3262
3263 let mut cx = EditorTestContext::new(cx).await;
3264
3265 let diff_base = r#"
3266 Line 0
3267 Line 1
3268 Line 2
3269 Line 3
3270 "#
3271 .unindent();
3272
3273 cx.set_state(
3274 &r#"
3275 ˇLine 0
3276 Line 1
3277 Line 2
3278 Line 3
3279 "#
3280 .unindent(),
3281 );
3282
3283 cx.set_diff_base(Some(&diff_base));
3284 executor.run_until_parked();
3285
3286 // Join lines
3287 cx.update_editor(|editor, cx| {
3288 editor.join_lines(&JoinLines, cx);
3289 });
3290 executor.run_until_parked();
3291
3292 cx.assert_editor_state(
3293 &r#"
3294 Line 0ˇ Line 1
3295 Line 2
3296 Line 3
3297 "#
3298 .unindent(),
3299 );
3300 // Join again
3301 cx.update_editor(|editor, cx| {
3302 editor.join_lines(&JoinLines, cx);
3303 });
3304 executor.run_until_parked();
3305
3306 cx.assert_editor_state(
3307 &r#"
3308 Line 0 Line 1ˇ Line 2
3309 Line 3
3310 "#
3311 .unindent(),
3312 );
3313}
3314
3315#[gpui::test]
3316async fn test_custom_newlines_cause_no_false_positive_diffs(
3317 executor: BackgroundExecutor,
3318 cx: &mut gpui::TestAppContext,
3319) {
3320 init_test(cx, |_| {});
3321 let mut cx = EditorTestContext::new(cx).await;
3322 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3323 cx.set_diff_base(Some("Line 0\r\nLine 1\r\nLine 2\r\nLine 3"));
3324 executor.run_until_parked();
3325
3326 cx.update_editor(|editor, cx| {
3327 assert_eq!(
3328 editor
3329 .buffer()
3330 .read(cx)
3331 .snapshot(cx)
3332 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
3333 .collect::<Vec<_>>(),
3334 Vec::new(),
3335 "Should not have any diffs for files with custom newlines"
3336 );
3337 });
3338}
3339
3340#[gpui::test]
3341async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3342 init_test(cx, |_| {});
3343
3344 let mut cx = EditorTestContext::new(cx).await;
3345
3346 // Test sort_lines_case_insensitive()
3347 cx.set_state(indoc! {"
3348 «z
3349 y
3350 x
3351 Z
3352 Y
3353 Xˇ»
3354 "});
3355 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3356 cx.assert_editor_state(indoc! {"
3357 «x
3358 X
3359 y
3360 Y
3361 z
3362 Zˇ»
3363 "});
3364
3365 // Test reverse_lines()
3366 cx.set_state(indoc! {"
3367 «5
3368 4
3369 3
3370 2
3371 1ˇ»
3372 "});
3373 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3374 cx.assert_editor_state(indoc! {"
3375 «1
3376 2
3377 3
3378 4
3379 5ˇ»
3380 "});
3381
3382 // Skip testing shuffle_line()
3383
3384 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3385 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3386
3387 // Don't manipulate when cursor is on single line, but expand the selection
3388 cx.set_state(indoc! {"
3389 ddˇdd
3390 ccc
3391 bb
3392 a
3393 "});
3394 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3395 cx.assert_editor_state(indoc! {"
3396 «ddddˇ»
3397 ccc
3398 bb
3399 a
3400 "});
3401
3402 // Basic manipulate case
3403 // Start selection moves to column 0
3404 // End of selection shrinks to fit shorter line
3405 cx.set_state(indoc! {"
3406 dd«d
3407 ccc
3408 bb
3409 aaaaaˇ»
3410 "});
3411 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3412 cx.assert_editor_state(indoc! {"
3413 «aaaaa
3414 bb
3415 ccc
3416 dddˇ»
3417 "});
3418
3419 // Manipulate case with newlines
3420 cx.set_state(indoc! {"
3421 dd«d
3422 ccc
3423
3424 bb
3425 aaaaa
3426
3427 ˇ»
3428 "});
3429 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3430 cx.assert_editor_state(indoc! {"
3431 «
3432
3433 aaaaa
3434 bb
3435 ccc
3436 dddˇ»
3437
3438 "});
3439
3440 // Adding new line
3441 cx.set_state(indoc! {"
3442 aa«a
3443 bbˇ»b
3444 "});
3445 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3446 cx.assert_editor_state(indoc! {"
3447 «aaa
3448 bbb
3449 added_lineˇ»
3450 "});
3451
3452 // Removing line
3453 cx.set_state(indoc! {"
3454 aa«a
3455 bbbˇ»
3456 "});
3457 cx.update_editor(|e, cx| {
3458 e.manipulate_lines(cx, |lines| {
3459 lines.pop();
3460 })
3461 });
3462 cx.assert_editor_state(indoc! {"
3463 «aaaˇ»
3464 "});
3465
3466 // Removing all lines
3467 cx.set_state(indoc! {"
3468 aa«a
3469 bbbˇ»
3470 "});
3471 cx.update_editor(|e, cx| {
3472 e.manipulate_lines(cx, |lines| {
3473 lines.drain(..);
3474 })
3475 });
3476 cx.assert_editor_state(indoc! {"
3477 ˇ
3478 "});
3479}
3480
3481#[gpui::test]
3482async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3483 init_test(cx, |_| {});
3484
3485 let mut cx = EditorTestContext::new(cx).await;
3486
3487 // Consider continuous selection as single selection
3488 cx.set_state(indoc! {"
3489 Aaa«aa
3490 cˇ»c«c
3491 bb
3492 aaaˇ»aa
3493 "});
3494 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3495 cx.assert_editor_state(indoc! {"
3496 «Aaaaa
3497 ccc
3498 bb
3499 aaaaaˇ»
3500 "});
3501
3502 cx.set_state(indoc! {"
3503 Aaa«aa
3504 cˇ»c«c
3505 bb
3506 aaaˇ»aa
3507 "});
3508 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3509 cx.assert_editor_state(indoc! {"
3510 «Aaaaa
3511 ccc
3512 bbˇ»
3513 "});
3514
3515 // Consider non continuous selection as distinct dedup operations
3516 cx.set_state(indoc! {"
3517 «aaaaa
3518 bb
3519 aaaaa
3520 aaaaaˇ»
3521
3522 aaa«aaˇ»
3523 "});
3524 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3525 cx.assert_editor_state(indoc! {"
3526 «aaaaa
3527 bbˇ»
3528
3529 «aaaaaˇ»
3530 "});
3531}
3532
3533#[gpui::test]
3534async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3535 init_test(cx, |_| {});
3536
3537 let mut cx = EditorTestContext::new(cx).await;
3538
3539 cx.set_state(indoc! {"
3540 «Aaa
3541 aAa
3542 Aaaˇ»
3543 "});
3544 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3545 cx.assert_editor_state(indoc! {"
3546 «Aaa
3547 aAaˇ»
3548 "});
3549
3550 cx.set_state(indoc! {"
3551 «Aaa
3552 aAa
3553 aaAˇ»
3554 "});
3555 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3556 cx.assert_editor_state(indoc! {"
3557 «Aaaˇ»
3558 "});
3559}
3560
3561#[gpui::test]
3562async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3563 init_test(cx, |_| {});
3564
3565 let mut cx = EditorTestContext::new(cx).await;
3566
3567 // Manipulate with multiple selections on a single line
3568 cx.set_state(indoc! {"
3569 dd«dd
3570 cˇ»c«c
3571 bb
3572 aaaˇ»aa
3573 "});
3574 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3575 cx.assert_editor_state(indoc! {"
3576 «aaaaa
3577 bb
3578 ccc
3579 ddddˇ»
3580 "});
3581
3582 // Manipulate with multiple disjoin selections
3583 cx.set_state(indoc! {"
3584 5«
3585 4
3586 3
3587 2
3588 1ˇ»
3589
3590 dd«dd
3591 ccc
3592 bb
3593 aaaˇ»aa
3594 "});
3595 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3596 cx.assert_editor_state(indoc! {"
3597 «1
3598 2
3599 3
3600 4
3601 5ˇ»
3602
3603 «aaaaa
3604 bb
3605 ccc
3606 ddddˇ»
3607 "});
3608
3609 // Adding lines on each selection
3610 cx.set_state(indoc! {"
3611 2«
3612 1ˇ»
3613
3614 bb«bb
3615 aaaˇ»aa
3616 "});
3617 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3618 cx.assert_editor_state(indoc! {"
3619 «2
3620 1
3621 added lineˇ»
3622
3623 «bbbb
3624 aaaaa
3625 added lineˇ»
3626 "});
3627
3628 // Removing lines on each selection
3629 cx.set_state(indoc! {"
3630 2«
3631 1ˇ»
3632
3633 bb«bb
3634 aaaˇ»aa
3635 "});
3636 cx.update_editor(|e, cx| {
3637 e.manipulate_lines(cx, |lines| {
3638 lines.pop();
3639 })
3640 });
3641 cx.assert_editor_state(indoc! {"
3642 «2ˇ»
3643
3644 «bbbbˇ»
3645 "});
3646}
3647
3648#[gpui::test]
3649async fn test_manipulate_text(cx: &mut TestAppContext) {
3650 init_test(cx, |_| {});
3651
3652 let mut cx = EditorTestContext::new(cx).await;
3653
3654 // Test convert_to_upper_case()
3655 cx.set_state(indoc! {"
3656 «hello worldˇ»
3657 "});
3658 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3659 cx.assert_editor_state(indoc! {"
3660 «HELLO WORLDˇ»
3661 "});
3662
3663 // Test convert_to_lower_case()
3664 cx.set_state(indoc! {"
3665 «HELLO WORLDˇ»
3666 "});
3667 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3668 cx.assert_editor_state(indoc! {"
3669 «hello worldˇ»
3670 "});
3671
3672 // Test multiple line, single selection case
3673 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3674 cx.set_state(indoc! {"
3675 «The quick brown
3676 fox jumps over
3677 the lazy dogˇ»
3678 "});
3679 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3680 cx.assert_editor_state(indoc! {"
3681 «The Quick Brown
3682 Fox Jumps Over
3683 The Lazy Dogˇ»
3684 "});
3685
3686 // Test multiple line, single selection case
3687 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3688 cx.set_state(indoc! {"
3689 «The quick brown
3690 fox jumps over
3691 the lazy dogˇ»
3692 "});
3693 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3694 cx.assert_editor_state(indoc! {"
3695 «TheQuickBrown
3696 FoxJumpsOver
3697 TheLazyDogˇ»
3698 "});
3699
3700 // From here on out, test more complex cases of manipulate_text()
3701
3702 // Test no selection case - should affect words cursors are in
3703 // Cursor at beginning, middle, and end of word
3704 cx.set_state(indoc! {"
3705 ˇhello big beauˇtiful worldˇ
3706 "});
3707 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3708 cx.assert_editor_state(indoc! {"
3709 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3710 "});
3711
3712 // Test multiple selections on a single line and across multiple lines
3713 cx.set_state(indoc! {"
3714 «Theˇ» quick «brown
3715 foxˇ» jumps «overˇ»
3716 the «lazyˇ» dog
3717 "});
3718 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3719 cx.assert_editor_state(indoc! {"
3720 «THEˇ» quick «BROWN
3721 FOXˇ» jumps «OVERˇ»
3722 the «LAZYˇ» dog
3723 "});
3724
3725 // Test case where text length grows
3726 cx.set_state(indoc! {"
3727 «tschüߡ»
3728 "});
3729 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3730 cx.assert_editor_state(indoc! {"
3731 «TSCHÜSSˇ»
3732 "});
3733
3734 // Test to make sure we don't crash when text shrinks
3735 cx.set_state(indoc! {"
3736 aaa_bbbˇ
3737 "});
3738 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3739 cx.assert_editor_state(indoc! {"
3740 «aaaBbbˇ»
3741 "});
3742
3743 // Test to make sure we all aware of the fact that each word can grow and shrink
3744 // Final selections should be aware of this fact
3745 cx.set_state(indoc! {"
3746 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3747 "});
3748 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3749 cx.assert_editor_state(indoc! {"
3750 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3751 "});
3752
3753 cx.set_state(indoc! {"
3754 «hElLo, WoRld!ˇ»
3755 "});
3756 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3757 cx.assert_editor_state(indoc! {"
3758 «HeLlO, wOrLD!ˇ»
3759 "});
3760}
3761
3762#[gpui::test]
3763fn test_duplicate_line(cx: &mut TestAppContext) {
3764 init_test(cx, |_| {});
3765
3766 let view = cx.add_window(|cx| {
3767 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3768 build_editor(buffer, cx)
3769 });
3770 _ = view.update(cx, |view, cx| {
3771 view.change_selections(None, cx, |s| {
3772 s.select_display_ranges([
3773 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3774 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3775 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3776 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3777 ])
3778 });
3779 view.duplicate_line_down(&DuplicateLineDown, cx);
3780 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3781 assert_eq!(
3782 view.selections.display_ranges(cx),
3783 vec![
3784 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3785 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3786 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3787 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3788 ]
3789 );
3790 });
3791
3792 let view = cx.add_window(|cx| {
3793 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3794 build_editor(buffer, cx)
3795 });
3796 _ = view.update(cx, |view, cx| {
3797 view.change_selections(None, cx, |s| {
3798 s.select_display_ranges([
3799 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3800 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3801 ])
3802 });
3803 view.duplicate_line_down(&DuplicateLineDown, cx);
3804 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3805 assert_eq!(
3806 view.selections.display_ranges(cx),
3807 vec![
3808 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3809 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3810 ]
3811 );
3812 });
3813
3814 // With `move_upwards` the selections stay in place, except for
3815 // the lines inserted above them
3816 let view = cx.add_window(|cx| {
3817 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3818 build_editor(buffer, cx)
3819 });
3820 _ = view.update(cx, |view, cx| {
3821 view.change_selections(None, cx, |s| {
3822 s.select_display_ranges([
3823 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3824 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3825 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3826 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3827 ])
3828 });
3829 view.duplicate_line_up(&DuplicateLineUp, cx);
3830 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3831 assert_eq!(
3832 view.selections.display_ranges(cx),
3833 vec![
3834 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3835 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3836 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3837 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3838 ]
3839 );
3840 });
3841
3842 let view = cx.add_window(|cx| {
3843 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3844 build_editor(buffer, cx)
3845 });
3846 _ = view.update(cx, |view, cx| {
3847 view.change_selections(None, cx, |s| {
3848 s.select_display_ranges([
3849 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3850 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3851 ])
3852 });
3853 view.duplicate_line_up(&DuplicateLineUp, cx);
3854 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3855 assert_eq!(
3856 view.selections.display_ranges(cx),
3857 vec![
3858 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3859 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3860 ]
3861 );
3862 });
3863}
3864
3865#[gpui::test]
3866fn test_move_line_up_down(cx: &mut TestAppContext) {
3867 init_test(cx, |_| {});
3868
3869 let view = cx.add_window(|cx| {
3870 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3871 build_editor(buffer, cx)
3872 });
3873 _ = view.update(cx, |view, cx| {
3874 view.fold_ranges(
3875 vec![
3876 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3877 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3878 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3879 ],
3880 true,
3881 cx,
3882 );
3883 view.change_selections(None, cx, |s| {
3884 s.select_display_ranges([
3885 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3886 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3887 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3888 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3889 ])
3890 });
3891 assert_eq!(
3892 view.display_text(cx),
3893 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3894 );
3895
3896 view.move_line_up(&MoveLineUp, cx);
3897 assert_eq!(
3898 view.display_text(cx),
3899 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3900 );
3901 assert_eq!(
3902 view.selections.display_ranges(cx),
3903 vec![
3904 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3905 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3906 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3907 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3908 ]
3909 );
3910 });
3911
3912 _ = view.update(cx, |view, cx| {
3913 view.move_line_down(&MoveLineDown, cx);
3914 assert_eq!(
3915 view.display_text(cx),
3916 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3917 );
3918 assert_eq!(
3919 view.selections.display_ranges(cx),
3920 vec![
3921 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3922 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3923 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3924 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3925 ]
3926 );
3927 });
3928
3929 _ = view.update(cx, |view, cx| {
3930 view.move_line_down(&MoveLineDown, cx);
3931 assert_eq!(
3932 view.display_text(cx),
3933 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3934 );
3935 assert_eq!(
3936 view.selections.display_ranges(cx),
3937 vec![
3938 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3939 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3940 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3941 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3942 ]
3943 );
3944 });
3945
3946 _ = view.update(cx, |view, cx| {
3947 view.move_line_up(&MoveLineUp, cx);
3948 assert_eq!(
3949 view.display_text(cx),
3950 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3951 );
3952 assert_eq!(
3953 view.selections.display_ranges(cx),
3954 vec![
3955 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3956 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3957 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3958 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3959 ]
3960 );
3961 });
3962}
3963
3964#[gpui::test]
3965fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3966 init_test(cx, |_| {});
3967
3968 let editor = cx.add_window(|cx| {
3969 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3970 build_editor(buffer, cx)
3971 });
3972 _ = editor.update(cx, |editor, cx| {
3973 let snapshot = editor.buffer.read(cx).snapshot(cx);
3974 editor.insert_blocks(
3975 [BlockProperties {
3976 style: BlockStyle::Fixed,
3977 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
3978 height: 1,
3979 render: Box::new(|_| div().into_any()),
3980 priority: 0,
3981 }],
3982 Some(Autoscroll::fit()),
3983 cx,
3984 );
3985 editor.change_selections(None, cx, |s| {
3986 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
3987 });
3988 editor.move_line_down(&MoveLineDown, cx);
3989 });
3990}
3991
3992#[gpui::test]
3993async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
3994 init_test(cx, |_| {});
3995
3996 let mut cx = EditorTestContext::new(cx).await;
3997 cx.set_state(
3998 &"
3999 ˇzero
4000 one
4001 two
4002 three
4003 four
4004 five
4005 "
4006 .unindent(),
4007 );
4008
4009 // Create a four-line block that replaces three lines of text.
4010 cx.update_editor(|editor, cx| {
4011 let snapshot = editor.snapshot(cx);
4012 let snapshot = &snapshot.buffer_snapshot;
4013 let placement = BlockPlacement::Replace(
4014 snapshot.anchor_after(Point::new(1, 0))..snapshot.anchor_after(Point::new(3, 0)),
4015 );
4016 editor.insert_blocks(
4017 [BlockProperties {
4018 placement,
4019 height: 4,
4020 style: BlockStyle::Sticky,
4021 render: Box::new(|_| gpui::div().into_any_element()),
4022 priority: 0,
4023 }],
4024 None,
4025 cx,
4026 );
4027 });
4028
4029 // Move down so that the cursor touches the block.
4030 cx.update_editor(|editor, cx| {
4031 editor.move_down(&Default::default(), cx);
4032 });
4033 cx.assert_editor_state(
4034 &"
4035 zero
4036 «one
4037 two
4038 threeˇ»
4039 four
4040 five
4041 "
4042 .unindent(),
4043 );
4044
4045 // Move down past the block.
4046 cx.update_editor(|editor, cx| {
4047 editor.move_down(&Default::default(), cx);
4048 });
4049 cx.assert_editor_state(
4050 &"
4051 zero
4052 one
4053 two
4054 three
4055 ˇfour
4056 five
4057 "
4058 .unindent(),
4059 );
4060}
4061
4062#[gpui::test]
4063fn test_transpose(cx: &mut TestAppContext) {
4064 init_test(cx, |_| {});
4065
4066 _ = cx.add_window(|cx| {
4067 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
4068 editor.set_style(EditorStyle::default(), cx);
4069 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
4070 editor.transpose(&Default::default(), cx);
4071 assert_eq!(editor.text(cx), "bac");
4072 assert_eq!(editor.selections.ranges(cx), [2..2]);
4073
4074 editor.transpose(&Default::default(), cx);
4075 assert_eq!(editor.text(cx), "bca");
4076 assert_eq!(editor.selections.ranges(cx), [3..3]);
4077
4078 editor.transpose(&Default::default(), cx);
4079 assert_eq!(editor.text(cx), "bac");
4080 assert_eq!(editor.selections.ranges(cx), [3..3]);
4081
4082 editor
4083 });
4084
4085 _ = cx.add_window(|cx| {
4086 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4087 editor.set_style(EditorStyle::default(), cx);
4088 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
4089 editor.transpose(&Default::default(), cx);
4090 assert_eq!(editor.text(cx), "acb\nde");
4091 assert_eq!(editor.selections.ranges(cx), [3..3]);
4092
4093 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4094 editor.transpose(&Default::default(), cx);
4095 assert_eq!(editor.text(cx), "acbd\ne");
4096 assert_eq!(editor.selections.ranges(cx), [5..5]);
4097
4098 editor.transpose(&Default::default(), cx);
4099 assert_eq!(editor.text(cx), "acbde\n");
4100 assert_eq!(editor.selections.ranges(cx), [6..6]);
4101
4102 editor.transpose(&Default::default(), cx);
4103 assert_eq!(editor.text(cx), "acbd\ne");
4104 assert_eq!(editor.selections.ranges(cx), [6..6]);
4105
4106 editor
4107 });
4108
4109 _ = cx.add_window(|cx| {
4110 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4111 editor.set_style(EditorStyle::default(), cx);
4112 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4113 editor.transpose(&Default::default(), cx);
4114 assert_eq!(editor.text(cx), "bacd\ne");
4115 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4116
4117 editor.transpose(&Default::default(), cx);
4118 assert_eq!(editor.text(cx), "bcade\n");
4119 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4120
4121 editor.transpose(&Default::default(), cx);
4122 assert_eq!(editor.text(cx), "bcda\ne");
4123 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4124
4125 editor.transpose(&Default::default(), cx);
4126 assert_eq!(editor.text(cx), "bcade\n");
4127 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4128
4129 editor.transpose(&Default::default(), cx);
4130 assert_eq!(editor.text(cx), "bcaed\n");
4131 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4132
4133 editor
4134 });
4135
4136 _ = cx.add_window(|cx| {
4137 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
4138 editor.set_style(EditorStyle::default(), cx);
4139 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4140 editor.transpose(&Default::default(), cx);
4141 assert_eq!(editor.text(cx), "🏀🍐✋");
4142 assert_eq!(editor.selections.ranges(cx), [8..8]);
4143
4144 editor.transpose(&Default::default(), cx);
4145 assert_eq!(editor.text(cx), "🏀✋🍐");
4146 assert_eq!(editor.selections.ranges(cx), [11..11]);
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
4153 });
4154}
4155
4156#[gpui::test]
4157async fn test_rewrap(cx: &mut TestAppContext) {
4158 init_test(cx, |_| {});
4159
4160 let mut cx = EditorTestContext::new(cx).await;
4161
4162 {
4163 let language = Arc::new(Language::new(
4164 LanguageConfig {
4165 line_comments: vec!["// ".into()],
4166 ..LanguageConfig::default()
4167 },
4168 None,
4169 ));
4170 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4171
4172 let unwrapped_text = indoc! {"
4173 // ˇ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.
4174 "};
4175
4176 let wrapped_text = indoc! {"
4177 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4178 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4179 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4180 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4181 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4182 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4183 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4184 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4185 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4186 // porttitor id. Aliquam id accumsan eros.ˇ
4187 "};
4188
4189 cx.set_state(unwrapped_text);
4190 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4191 cx.assert_editor_state(wrapped_text);
4192 }
4193
4194 // Test that rewrapping works inside of a selection
4195 {
4196 let language = Arc::new(Language::new(
4197 LanguageConfig {
4198 line_comments: vec!["// ".into()],
4199 ..LanguageConfig::default()
4200 },
4201 None,
4202 ));
4203 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4204
4205 let unwrapped_text = indoc! {"
4206 «// 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.ˇ»
4207 "};
4208
4209 let wrapped_text = indoc! {"
4210 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4211 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4212 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4213 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4214 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4215 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4216 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4217 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4218 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4219 // porttitor id. Aliquam id accumsan eros.ˇ
4220 "};
4221
4222 cx.set_state(unwrapped_text);
4223 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4224 cx.assert_editor_state(wrapped_text);
4225 }
4226
4227 // Test that cursors that expand to the same region are collapsed.
4228 {
4229 let language = Arc::new(Language::new(
4230 LanguageConfig {
4231 line_comments: vec!["// ".into()],
4232 ..LanguageConfig::default()
4233 },
4234 None,
4235 ));
4236 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4237
4238 let unwrapped_text = indoc! {"
4239 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4240 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4241 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4242 // ˇ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.
4243 "};
4244
4245 let wrapped_text = indoc! {"
4246 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4247 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4248 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4249 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4250 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4251 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4252 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4253 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4254 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4255 // porttitor id. Aliquam id accumsan eros.ˇ
4256 "};
4257
4258 cx.set_state(unwrapped_text);
4259 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4260 cx.assert_editor_state(wrapped_text);
4261 }
4262
4263 // Test that non-contiguous selections are treated separately.
4264 {
4265 let language = Arc::new(Language::new(
4266 LanguageConfig {
4267 line_comments: vec!["// ".into()],
4268 ..LanguageConfig::default()
4269 },
4270 None,
4271 ));
4272 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4273
4274 let unwrapped_text = indoc! {"
4275 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4276 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4277 //
4278 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4279 // ˇ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.
4280 "};
4281
4282 let wrapped_text = indoc! {"
4283 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4284 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4285 // auctor, eu lacinia sapien scelerisque.ˇ
4286 //
4287 // Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4288 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4289 // blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4290 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4291 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4292 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4293 // vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ
4294 "};
4295
4296 cx.set_state(unwrapped_text);
4297 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4298 cx.assert_editor_state(wrapped_text);
4299 }
4300
4301 // Test that different comment prefixes are supported.
4302 {
4303 let language = Arc::new(Language::new(
4304 LanguageConfig {
4305 line_comments: vec!["# ".into()],
4306 ..LanguageConfig::default()
4307 },
4308 None,
4309 ));
4310 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4311
4312 let unwrapped_text = indoc! {"
4313 # ˇ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.
4314 "};
4315
4316 let wrapped_text = indoc! {"
4317 # Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4318 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4319 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4320 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4321 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4322 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4323 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4324 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4325 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4326 # accumsan eros.ˇ
4327 "};
4328
4329 cx.set_state(unwrapped_text);
4330 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4331 cx.assert_editor_state(wrapped_text);
4332 }
4333
4334 // Test that rewrapping is ignored outside of comments in most languages.
4335 {
4336 let language = Arc::new(Language::new(
4337 LanguageConfig {
4338 line_comments: vec!["// ".into(), "/// ".into()],
4339 ..LanguageConfig::default()
4340 },
4341 Some(tree_sitter_rust::LANGUAGE.into()),
4342 ));
4343 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4344
4345 let unwrapped_text = indoc! {"
4346 /// Adds two numbers.
4347 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4348 fn add(a: u32, b: u32) -> u32 {
4349 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ˇ
4350 }
4351 "};
4352
4353 let wrapped_text = indoc! {"
4354 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4355 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4356 fn add(a: u32, b: u32) -> u32 {
4357 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ˇ
4358 }
4359 "};
4360
4361 cx.set_state(unwrapped_text);
4362 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4363 cx.assert_editor_state(wrapped_text);
4364 }
4365
4366 // Test that rewrapping works in Markdown and Plain Text languages.
4367 {
4368 let markdown_language = Arc::new(Language::new(
4369 LanguageConfig {
4370 name: "Markdown".into(),
4371 ..LanguageConfig::default()
4372 },
4373 None,
4374 ));
4375 cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
4376
4377 let unwrapped_text = indoc! {"
4378 # Hello
4379
4380 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.
4381 "};
4382
4383 let wrapped_text = indoc! {"
4384 # Hello
4385
4386 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4387 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4388 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4389 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4390 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4391 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4392 Integer sit amet scelerisque nisi.ˇ
4393 "};
4394
4395 cx.set_state(unwrapped_text);
4396 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4397 cx.assert_editor_state(wrapped_text);
4398
4399 let plaintext_language = Arc::new(Language::new(
4400 LanguageConfig {
4401 name: "Plain Text".into(),
4402 ..LanguageConfig::default()
4403 },
4404 None,
4405 ));
4406 cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
4407
4408 let unwrapped_text = indoc! {"
4409 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.
4410 "};
4411
4412 let wrapped_text = indoc! {"
4413 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4414 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4415 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4416 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4417 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4418 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4419 Integer sit amet scelerisque nisi.ˇ
4420 "};
4421
4422 cx.set_state(unwrapped_text);
4423 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4424 cx.assert_editor_state(wrapped_text);
4425 }
4426
4427 // Test rewrapping unaligned comments in a selection.
4428 {
4429 let language = Arc::new(Language::new(
4430 LanguageConfig {
4431 line_comments: vec!["// ".into(), "/// ".into()],
4432 ..LanguageConfig::default()
4433 },
4434 Some(tree_sitter_rust::LANGUAGE.into()),
4435 ));
4436 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4437
4438 let unwrapped_text = indoc! {"
4439 fn foo() {
4440 if true {
4441 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4442 // Praesent semper egestas tellus id dignissim.ˇ»
4443 do_something();
4444 } else {
4445 //
4446 }
4447 }
4448 "};
4449
4450 let wrapped_text = indoc! {"
4451 fn foo() {
4452 if true {
4453 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4454 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4455 // egestas tellus id dignissim.ˇ
4456 do_something();
4457 } else {
4458 //
4459 }
4460 }
4461 "};
4462
4463 cx.set_state(unwrapped_text);
4464 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4465 cx.assert_editor_state(wrapped_text);
4466
4467 let unwrapped_text = indoc! {"
4468 fn foo() {
4469 if true {
4470 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4471 // Praesent semper egestas tellus id dignissim.»
4472 do_something();
4473 } else {
4474 //
4475 }
4476
4477 }
4478 "};
4479
4480 let wrapped_text = indoc! {"
4481 fn foo() {
4482 if true {
4483 // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4484 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4485 // egestas tellus id dignissim.ˇ
4486 do_something();
4487 } else {
4488 //
4489 }
4490
4491 }
4492 "};
4493
4494 cx.set_state(unwrapped_text);
4495 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4496 cx.assert_editor_state(wrapped_text);
4497 }
4498}
4499
4500#[gpui::test]
4501async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4502 init_test(cx, |_| {});
4503
4504 let mut cx = EditorTestContext::new(cx).await;
4505
4506 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4507 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4508 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4509
4510 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4511 cx.set_state("two ˇfour ˇsix ˇ");
4512 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4513 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4514
4515 // Paste again but with only two cursors. Since the number of cursors doesn't
4516 // match the number of slices in the clipboard, the entire clipboard text
4517 // is pasted at each cursor.
4518 cx.set_state("ˇtwo one✅ four three six five ˇ");
4519 cx.update_editor(|e, cx| {
4520 e.handle_input("( ", cx);
4521 e.paste(&Paste, cx);
4522 e.handle_input(") ", cx);
4523 });
4524 cx.assert_editor_state(
4525 &([
4526 "( one✅ ",
4527 "three ",
4528 "five ) ˇtwo one✅ four three six five ( one✅ ",
4529 "three ",
4530 "five ) ˇ",
4531 ]
4532 .join("\n")),
4533 );
4534
4535 // Cut with three selections, one of which is full-line.
4536 cx.set_state(indoc! {"
4537 1«2ˇ»3
4538 4ˇ567
4539 «8ˇ»9"});
4540 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4541 cx.assert_editor_state(indoc! {"
4542 1ˇ3
4543 ˇ9"});
4544
4545 // Paste with three selections, noticing how the copied selection that was full-line
4546 // gets inserted before the second cursor.
4547 cx.set_state(indoc! {"
4548 1ˇ3
4549 9ˇ
4550 «oˇ»ne"});
4551 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4552 cx.assert_editor_state(indoc! {"
4553 12ˇ3
4554 4567
4555 9ˇ
4556 8ˇne"});
4557
4558 // Copy with a single cursor only, which writes the whole line into the clipboard.
4559 cx.set_state(indoc! {"
4560 The quick brown
4561 fox juˇmps over
4562 the lazy dog"});
4563 cx.update_editor(|e, cx| e.copy(&Copy, cx));
4564 assert_eq!(
4565 cx.read_from_clipboard()
4566 .and_then(|item| item.text().as_deref().map(str::to_string)),
4567 Some("fox jumps over\n".to_string())
4568 );
4569
4570 // Paste with three selections, noticing how the copied full-line selection is inserted
4571 // before the empty selections but replaces the selection that is non-empty.
4572 cx.set_state(indoc! {"
4573 Tˇhe quick brown
4574 «foˇ»x jumps over
4575 tˇhe lazy dog"});
4576 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4577 cx.assert_editor_state(indoc! {"
4578 fox jumps over
4579 Tˇhe quick brown
4580 fox jumps over
4581 ˇx jumps over
4582 fox jumps over
4583 tˇhe lazy dog"});
4584}
4585
4586#[gpui::test]
4587async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4588 init_test(cx, |_| {});
4589
4590 let mut cx = EditorTestContext::new(cx).await;
4591 let language = Arc::new(Language::new(
4592 LanguageConfig::default(),
4593 Some(tree_sitter_rust::LANGUAGE.into()),
4594 ));
4595 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4596
4597 // Cut an indented block, without the leading whitespace.
4598 cx.set_state(indoc! {"
4599 const a: B = (
4600 c(),
4601 «d(
4602 e,
4603 f
4604 )ˇ»
4605 );
4606 "});
4607 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4608 cx.assert_editor_state(indoc! {"
4609 const a: B = (
4610 c(),
4611 ˇ
4612 );
4613 "});
4614
4615 // Paste it at the same position.
4616 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4617 cx.assert_editor_state(indoc! {"
4618 const a: B = (
4619 c(),
4620 d(
4621 e,
4622 f
4623 )ˇ
4624 );
4625 "});
4626
4627 // Paste it at a line with a lower indent level.
4628 cx.set_state(indoc! {"
4629 ˇ
4630 const a: B = (
4631 c(),
4632 );
4633 "});
4634 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4635 cx.assert_editor_state(indoc! {"
4636 d(
4637 e,
4638 f
4639 )ˇ
4640 const a: B = (
4641 c(),
4642 );
4643 "});
4644
4645 // Cut an indented block, with the leading whitespace.
4646 cx.set_state(indoc! {"
4647 const a: B = (
4648 c(),
4649 « d(
4650 e,
4651 f
4652 )
4653 ˇ»);
4654 "});
4655 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4656 cx.assert_editor_state(indoc! {"
4657 const a: B = (
4658 c(),
4659 ˇ);
4660 "});
4661
4662 // Paste it at the same position.
4663 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4664 cx.assert_editor_state(indoc! {"
4665 const a: B = (
4666 c(),
4667 d(
4668 e,
4669 f
4670 )
4671 ˇ);
4672 "});
4673
4674 // Paste it at a line with a higher indent level.
4675 cx.set_state(indoc! {"
4676 const a: B = (
4677 c(),
4678 d(
4679 e,
4680 fˇ
4681 )
4682 );
4683 "});
4684 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4685 cx.assert_editor_state(indoc! {"
4686 const a: B = (
4687 c(),
4688 d(
4689 e,
4690 f d(
4691 e,
4692 f
4693 )
4694 ˇ
4695 )
4696 );
4697 "});
4698}
4699
4700#[gpui::test]
4701fn test_select_all(cx: &mut TestAppContext) {
4702 init_test(cx, |_| {});
4703
4704 let view = cx.add_window(|cx| {
4705 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4706 build_editor(buffer, cx)
4707 });
4708 _ = view.update(cx, |view, cx| {
4709 view.select_all(&SelectAll, cx);
4710 assert_eq!(
4711 view.selections.display_ranges(cx),
4712 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4713 );
4714 });
4715}
4716
4717#[gpui::test]
4718fn test_select_line(cx: &mut TestAppContext) {
4719 init_test(cx, |_| {});
4720
4721 let view = cx.add_window(|cx| {
4722 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4723 build_editor(buffer, cx)
4724 });
4725 _ = view.update(cx, |view, cx| {
4726 view.change_selections(None, cx, |s| {
4727 s.select_display_ranges([
4728 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4729 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4730 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4731 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4732 ])
4733 });
4734 view.select_line(&SelectLine, cx);
4735 assert_eq!(
4736 view.selections.display_ranges(cx),
4737 vec![
4738 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4739 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4740 ]
4741 );
4742 });
4743
4744 _ = view.update(cx, |view, cx| {
4745 view.select_line(&SelectLine, cx);
4746 assert_eq!(
4747 view.selections.display_ranges(cx),
4748 vec![
4749 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4750 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4751 ]
4752 );
4753 });
4754
4755 _ = view.update(cx, |view, cx| {
4756 view.select_line(&SelectLine, cx);
4757 assert_eq!(
4758 view.selections.display_ranges(cx),
4759 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4760 );
4761 });
4762}
4763
4764#[gpui::test]
4765fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4766 init_test(cx, |_| {});
4767
4768 let view = cx.add_window(|cx| {
4769 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4770 build_editor(buffer, cx)
4771 });
4772 _ = view.update(cx, |view, cx| {
4773 view.fold_ranges(
4774 vec![
4775 (Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4776 (Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4777 (Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4778 ],
4779 true,
4780 cx,
4781 );
4782 view.change_selections(None, cx, |s| {
4783 s.select_display_ranges([
4784 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4785 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4786 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4787 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4788 ])
4789 });
4790 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4791 });
4792
4793 _ = view.update(cx, |view, cx| {
4794 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4795 assert_eq!(
4796 view.display_text(cx),
4797 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4798 );
4799 assert_eq!(
4800 view.selections.display_ranges(cx),
4801 [
4802 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4803 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4804 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4805 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4806 ]
4807 );
4808 });
4809
4810 _ = view.update(cx, |view, cx| {
4811 view.change_selections(None, cx, |s| {
4812 s.select_display_ranges([
4813 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4814 ])
4815 });
4816 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4817 assert_eq!(
4818 view.display_text(cx),
4819 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4820 );
4821 assert_eq!(
4822 view.selections.display_ranges(cx),
4823 [
4824 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4825 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4826 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4827 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4828 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4829 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4830 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4831 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4832 ]
4833 );
4834 });
4835}
4836
4837#[gpui::test]
4838async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4839 init_test(cx, |_| {});
4840
4841 let mut cx = EditorTestContext::new(cx).await;
4842
4843 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4844 cx.set_state(indoc!(
4845 r#"abc
4846 defˇghi
4847
4848 jk
4849 nlmo
4850 "#
4851 ));
4852
4853 cx.update_editor(|editor, cx| {
4854 editor.add_selection_above(&Default::default(), cx);
4855 });
4856
4857 cx.assert_editor_state(indoc!(
4858 r#"abcˇ
4859 defˇghi
4860
4861 jk
4862 nlmo
4863 "#
4864 ));
4865
4866 cx.update_editor(|editor, cx| {
4867 editor.add_selection_above(&Default::default(), cx);
4868 });
4869
4870 cx.assert_editor_state(indoc!(
4871 r#"abcˇ
4872 defˇghi
4873
4874 jk
4875 nlmo
4876 "#
4877 ));
4878
4879 cx.update_editor(|view, cx| {
4880 view.add_selection_below(&Default::default(), cx);
4881 });
4882
4883 cx.assert_editor_state(indoc!(
4884 r#"abc
4885 defˇghi
4886
4887 jk
4888 nlmo
4889 "#
4890 ));
4891
4892 cx.update_editor(|view, cx| {
4893 view.undo_selection(&Default::default(), cx);
4894 });
4895
4896 cx.assert_editor_state(indoc!(
4897 r#"abcˇ
4898 defˇghi
4899
4900 jk
4901 nlmo
4902 "#
4903 ));
4904
4905 cx.update_editor(|view, cx| {
4906 view.redo_selection(&Default::default(), cx);
4907 });
4908
4909 cx.assert_editor_state(indoc!(
4910 r#"abc
4911 defˇghi
4912
4913 jk
4914 nlmo
4915 "#
4916 ));
4917
4918 cx.update_editor(|view, cx| {
4919 view.add_selection_below(&Default::default(), cx);
4920 });
4921
4922 cx.assert_editor_state(indoc!(
4923 r#"abc
4924 defˇghi
4925
4926 jk
4927 nlmˇo
4928 "#
4929 ));
4930
4931 cx.update_editor(|view, cx| {
4932 view.add_selection_below(&Default::default(), cx);
4933 });
4934
4935 cx.assert_editor_state(indoc!(
4936 r#"abc
4937 defˇghi
4938
4939 jk
4940 nlmˇo
4941 "#
4942 ));
4943
4944 // change selections
4945 cx.set_state(indoc!(
4946 r#"abc
4947 def«ˇg»hi
4948
4949 jk
4950 nlmo
4951 "#
4952 ));
4953
4954 cx.update_editor(|view, cx| {
4955 view.add_selection_below(&Default::default(), cx);
4956 });
4957
4958 cx.assert_editor_state(indoc!(
4959 r#"abc
4960 def«ˇg»hi
4961
4962 jk
4963 nlm«ˇo»
4964 "#
4965 ));
4966
4967 cx.update_editor(|view, cx| {
4968 view.add_selection_below(&Default::default(), cx);
4969 });
4970
4971 cx.assert_editor_state(indoc!(
4972 r#"abc
4973 def«ˇg»hi
4974
4975 jk
4976 nlm«ˇo»
4977 "#
4978 ));
4979
4980 cx.update_editor(|view, cx| {
4981 view.add_selection_above(&Default::default(), cx);
4982 });
4983
4984 cx.assert_editor_state(indoc!(
4985 r#"abc
4986 def«ˇg»hi
4987
4988 jk
4989 nlmo
4990 "#
4991 ));
4992
4993 cx.update_editor(|view, cx| {
4994 view.add_selection_above(&Default::default(), cx);
4995 });
4996
4997 cx.assert_editor_state(indoc!(
4998 r#"abc
4999 def«ˇg»hi
5000
5001 jk
5002 nlmo
5003 "#
5004 ));
5005
5006 // Change selections again
5007 cx.set_state(indoc!(
5008 r#"a«bc
5009 defgˇ»hi
5010
5011 jk
5012 nlmo
5013 "#
5014 ));
5015
5016 cx.update_editor(|view, cx| {
5017 view.add_selection_below(&Default::default(), cx);
5018 });
5019
5020 cx.assert_editor_state(indoc!(
5021 r#"a«bcˇ»
5022 d«efgˇ»hi
5023
5024 j«kˇ»
5025 nlmo
5026 "#
5027 ));
5028
5029 cx.update_editor(|view, cx| {
5030 view.add_selection_below(&Default::default(), cx);
5031 });
5032 cx.assert_editor_state(indoc!(
5033 r#"a«bcˇ»
5034 d«efgˇ»hi
5035
5036 j«kˇ»
5037 n«lmoˇ»
5038 "#
5039 ));
5040 cx.update_editor(|view, cx| {
5041 view.add_selection_above(&Default::default(), cx);
5042 });
5043
5044 cx.assert_editor_state(indoc!(
5045 r#"a«bcˇ»
5046 d«efgˇ»hi
5047
5048 j«kˇ»
5049 nlmo
5050 "#
5051 ));
5052
5053 // Change selections again
5054 cx.set_state(indoc!(
5055 r#"abc
5056 d«ˇefghi
5057
5058 jk
5059 nlm»o
5060 "#
5061 ));
5062
5063 cx.update_editor(|view, cx| {
5064 view.add_selection_above(&Default::default(), cx);
5065 });
5066
5067 cx.assert_editor_state(indoc!(
5068 r#"a«ˇbc»
5069 d«ˇef»ghi
5070
5071 j«ˇk»
5072 n«ˇlm»o
5073 "#
5074 ));
5075
5076 cx.update_editor(|view, cx| {
5077 view.add_selection_below(&Default::default(), cx);
5078 });
5079
5080 cx.assert_editor_state(indoc!(
5081 r#"abc
5082 d«ˇef»ghi
5083
5084 j«ˇk»
5085 n«ˇlm»o
5086 "#
5087 ));
5088}
5089
5090#[gpui::test]
5091async fn test_select_next(cx: &mut gpui::TestAppContext) {
5092 init_test(cx, |_| {});
5093
5094 let mut cx = EditorTestContext::new(cx).await;
5095 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5096
5097 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5098 .unwrap();
5099 cx.assert_editor_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(|view, cx| view.undo_selection(&UndoSelection, cx));
5106 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5107
5108 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5109 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5110
5111 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5112 .unwrap();
5113 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
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
5120#[gpui::test]
5121async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
5122 init_test(cx, |_| {});
5123
5124 let mut cx = EditorTestContext::new(cx).await;
5125 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5126
5127 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
5128 .unwrap();
5129 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5130}
5131
5132#[gpui::test]
5133async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5134 init_test(cx, |_| {});
5135
5136 let mut cx = EditorTestContext::new(cx).await;
5137 cx.set_state(
5138 r#"let foo = 2;
5139lˇet foo = 2;
5140let fooˇ = 2;
5141let foo = 2;
5142let foo = ˇ2;"#,
5143 );
5144
5145 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5146 .unwrap();
5147 cx.assert_editor_state(
5148 r#"let foo = 2;
5149«letˇ» foo = 2;
5150let «fooˇ» = 2;
5151let foo = 2;
5152let foo = «2ˇ»;"#,
5153 );
5154
5155 // noop for multiple selections with different contents
5156 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5157 .unwrap();
5158 cx.assert_editor_state(
5159 r#"let foo = 2;
5160«letˇ» foo = 2;
5161let «fooˇ» = 2;
5162let foo = 2;
5163let foo = «2ˇ»;"#,
5164 );
5165}
5166
5167#[gpui::test]
5168async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
5169 init_test(cx, |_| {});
5170
5171 let mut cx =
5172 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5173
5174 cx.assert_editor_state(indoc! {"
5175 ˇbbb
5176 ccc
5177
5178 bbb
5179 ccc
5180 "});
5181 cx.dispatch_action(SelectPrevious::default());
5182 cx.assert_editor_state(indoc! {"
5183 «bbbˇ»
5184 ccc
5185
5186 bbb
5187 ccc
5188 "});
5189 cx.dispatch_action(SelectPrevious::default());
5190 cx.assert_editor_state(indoc! {"
5191 «bbbˇ»
5192 ccc
5193
5194 «bbbˇ»
5195 ccc
5196 "});
5197}
5198
5199#[gpui::test]
5200async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
5201 init_test(cx, |_| {});
5202
5203 let mut cx = EditorTestContext::new(cx).await;
5204 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5205
5206 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5207 .unwrap();
5208 cx.assert_editor_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(|view, cx| view.undo_selection(&UndoSelection, cx));
5215 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5216
5217 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5218 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5219
5220 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5221 .unwrap();
5222 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5223
5224 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5225 .unwrap();
5226 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\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
5233#[gpui::test]
5234async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5235 init_test(cx, |_| {});
5236
5237 let mut cx = EditorTestContext::new(cx).await;
5238 cx.set_state(
5239 r#"let foo = 2;
5240lˇet foo = 2;
5241let fooˇ = 2;
5242let foo = 2;
5243let foo = ˇ2;"#,
5244 );
5245
5246 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5247 .unwrap();
5248 cx.assert_editor_state(
5249 r#"let foo = 2;
5250«letˇ» foo = 2;
5251let «fooˇ» = 2;
5252let foo = 2;
5253let foo = «2ˇ»;"#,
5254 );
5255
5256 // noop for multiple selections with different contents
5257 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5258 .unwrap();
5259 cx.assert_editor_state(
5260 r#"let foo = 2;
5261«letˇ» foo = 2;
5262let «fooˇ» = 2;
5263let foo = 2;
5264let foo = «2ˇ»;"#,
5265 );
5266}
5267
5268#[gpui::test]
5269async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
5270 init_test(cx, |_| {});
5271
5272 let mut cx = EditorTestContext::new(cx).await;
5273 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5274
5275 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5276 .unwrap();
5277 cx.assert_editor_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\n«abcˇ»");
5282
5283 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5284 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5285
5286 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5287 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5288
5289 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5290 .unwrap();
5291 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\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
5298#[gpui::test]
5299async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5300 init_test(cx, |_| {});
5301
5302 let language = Arc::new(Language::new(
5303 LanguageConfig::default(),
5304 Some(tree_sitter_rust::LANGUAGE.into()),
5305 ));
5306
5307 let text = r#"
5308 use mod1::mod2::{mod3, mod4};
5309
5310 fn fn_1(param1: bool, param2: &str) {
5311 let var1 = "text";
5312 }
5313 "#
5314 .unindent();
5315
5316 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5317 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5318 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5319
5320 editor
5321 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5322 .await;
5323
5324 editor.update(cx, |view, cx| {
5325 view.change_selections(None, cx, |s| {
5326 s.select_display_ranges([
5327 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5328 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5329 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5330 ]);
5331 });
5332 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5333 });
5334 editor.update(cx, |editor, cx| {
5335 assert_text_with_selections(
5336 editor,
5337 indoc! {r#"
5338 use mod1::mod2::{mod3, «mod4ˇ»};
5339
5340 fn fn_1«ˇ(param1: bool, param2: &str)» {
5341 let var1 = "«textˇ»";
5342 }
5343 "#},
5344 cx,
5345 );
5346 });
5347
5348 editor.update(cx, |view, cx| {
5349 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5350 });
5351 editor.update(cx, |editor, cx| {
5352 assert_text_with_selections(
5353 editor,
5354 indoc! {r#"
5355 use mod1::mod2::«{mod3, mod4}ˇ»;
5356
5357 «ˇfn fn_1(param1: bool, param2: &str) {
5358 let var1 = "text";
5359 }»
5360 "#},
5361 cx,
5362 );
5363 });
5364
5365 editor.update(cx, |view, cx| {
5366 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5367 });
5368 assert_eq!(
5369 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5370 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5371 );
5372
5373 // Trying to expand the selected syntax node one more time has no effect.
5374 editor.update(cx, |view, cx| {
5375 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5376 });
5377 assert_eq!(
5378 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5379 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5380 );
5381
5382 editor.update(cx, |view, cx| {
5383 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5384 });
5385 editor.update(cx, |editor, cx| {
5386 assert_text_with_selections(
5387 editor,
5388 indoc! {r#"
5389 use mod1::mod2::«{mod3, mod4}ˇ»;
5390
5391 «ˇfn fn_1(param1: bool, param2: &str) {
5392 let var1 = "text";
5393 }»
5394 "#},
5395 cx,
5396 );
5397 });
5398
5399 editor.update(cx, |view, cx| {
5400 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5401 });
5402 editor.update(cx, |editor, cx| {
5403 assert_text_with_selections(
5404 editor,
5405 indoc! {r#"
5406 use mod1::mod2::{mod3, «mod4ˇ»};
5407
5408 fn fn_1«ˇ(param1: bool, param2: &str)» {
5409 let var1 = "«textˇ»";
5410 }
5411 "#},
5412 cx,
5413 );
5414 });
5415
5416 editor.update(cx, |view, cx| {
5417 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5418 });
5419 editor.update(cx, |editor, cx| {
5420 assert_text_with_selections(
5421 editor,
5422 indoc! {r#"
5423 use mod1::mod2::{mod3, mo«ˇ»d4};
5424
5425 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5426 let var1 = "te«ˇ»xt";
5427 }
5428 "#},
5429 cx,
5430 );
5431 });
5432
5433 // Trying to shrink the selected syntax node one more time has no effect.
5434 editor.update(cx, |view, cx| {
5435 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5436 });
5437 editor.update(cx, |editor, cx| {
5438 assert_text_with_selections(
5439 editor,
5440 indoc! {r#"
5441 use mod1::mod2::{mod3, mo«ˇ»d4};
5442
5443 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5444 let var1 = "te«ˇ»xt";
5445 }
5446 "#},
5447 cx,
5448 );
5449 });
5450
5451 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5452 // a fold.
5453 editor.update(cx, |view, cx| {
5454 view.fold_ranges(
5455 vec![
5456 (
5457 Point::new(0, 21)..Point::new(0, 24),
5458 FoldPlaceholder::test(),
5459 ),
5460 (
5461 Point::new(3, 20)..Point::new(3, 22),
5462 FoldPlaceholder::test(),
5463 ),
5464 ],
5465 true,
5466 cx,
5467 );
5468 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5469 });
5470 editor.update(cx, |editor, cx| {
5471 assert_text_with_selections(
5472 editor,
5473 indoc! {r#"
5474 use mod1::mod2::«{mod3, mod4}ˇ»;
5475
5476 fn fn_1«ˇ(param1: bool, param2: &str)» {
5477 «let var1 = "text";ˇ»
5478 }
5479 "#},
5480 cx,
5481 );
5482 });
5483}
5484
5485#[gpui::test]
5486async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5487 init_test(cx, |_| {});
5488
5489 let language = Arc::new(
5490 Language::new(
5491 LanguageConfig {
5492 brackets: BracketPairConfig {
5493 pairs: vec![
5494 BracketPair {
5495 start: "{".to_string(),
5496 end: "}".to_string(),
5497 close: false,
5498 surround: false,
5499 newline: true,
5500 },
5501 BracketPair {
5502 start: "(".to_string(),
5503 end: ")".to_string(),
5504 close: false,
5505 surround: false,
5506 newline: true,
5507 },
5508 ],
5509 ..Default::default()
5510 },
5511 ..Default::default()
5512 },
5513 Some(tree_sitter_rust::LANGUAGE.into()),
5514 )
5515 .with_indents_query(
5516 r#"
5517 (_ "(" ")" @end) @indent
5518 (_ "{" "}" @end) @indent
5519 "#,
5520 )
5521 .unwrap(),
5522 );
5523
5524 let text = "fn a() {}";
5525
5526 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5527 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5528 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5529 editor
5530 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5531 .await;
5532
5533 editor.update(cx, |editor, cx| {
5534 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5535 editor.newline(&Newline, cx);
5536 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5537 assert_eq!(
5538 editor.selections.ranges(cx),
5539 &[
5540 Point::new(1, 4)..Point::new(1, 4),
5541 Point::new(3, 4)..Point::new(3, 4),
5542 Point::new(5, 0)..Point::new(5, 0)
5543 ]
5544 );
5545 });
5546}
5547
5548#[gpui::test]
5549async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5550 init_test(cx, |_| {});
5551
5552 let mut cx = EditorTestContext::new(cx).await;
5553
5554 let language = Arc::new(Language::new(
5555 LanguageConfig {
5556 brackets: BracketPairConfig {
5557 pairs: vec![
5558 BracketPair {
5559 start: "{".to_string(),
5560 end: "}".to_string(),
5561 close: true,
5562 surround: true,
5563 newline: true,
5564 },
5565 BracketPair {
5566 start: "(".to_string(),
5567 end: ")".to_string(),
5568 close: true,
5569 surround: true,
5570 newline: true,
5571 },
5572 BracketPair {
5573 start: "/*".to_string(),
5574 end: " */".to_string(),
5575 close: true,
5576 surround: true,
5577 newline: true,
5578 },
5579 BracketPair {
5580 start: "[".to_string(),
5581 end: "]".to_string(),
5582 close: false,
5583 surround: false,
5584 newline: true,
5585 },
5586 BracketPair {
5587 start: "\"".to_string(),
5588 end: "\"".to_string(),
5589 close: true,
5590 surround: true,
5591 newline: false,
5592 },
5593 BracketPair {
5594 start: "<".to_string(),
5595 end: ">".to_string(),
5596 close: false,
5597 surround: true,
5598 newline: true,
5599 },
5600 ],
5601 ..Default::default()
5602 },
5603 autoclose_before: "})]".to_string(),
5604 ..Default::default()
5605 },
5606 Some(tree_sitter_rust::LANGUAGE.into()),
5607 ));
5608
5609 cx.language_registry().add(language.clone());
5610 cx.update_buffer(|buffer, cx| {
5611 buffer.set_language(Some(language), cx);
5612 });
5613
5614 cx.set_state(
5615 &r#"
5616 🏀ˇ
5617 εˇ
5618 ❤️ˇ
5619 "#
5620 .unindent(),
5621 );
5622
5623 // autoclose multiple nested brackets at multiple cursors
5624 cx.update_editor(|view, cx| {
5625 view.handle_input("{", cx);
5626 view.handle_input("{", cx);
5627 view.handle_input("{", cx);
5628 });
5629 cx.assert_editor_state(
5630 &"
5631 🏀{{{ˇ}}}
5632 ε{{{ˇ}}}
5633 ❤️{{{ˇ}}}
5634 "
5635 .unindent(),
5636 );
5637
5638 // insert a different closing bracket
5639 cx.update_editor(|view, cx| {
5640 view.handle_input(")", cx);
5641 });
5642 cx.assert_editor_state(
5643 &"
5644 🏀{{{)ˇ}}}
5645 ε{{{)ˇ}}}
5646 ❤️{{{)ˇ}}}
5647 "
5648 .unindent(),
5649 );
5650
5651 // skip over the auto-closed brackets when typing a closing bracket
5652 cx.update_editor(|view, cx| {
5653 view.move_right(&MoveRight, cx);
5654 view.handle_input("}", cx);
5655 view.handle_input("}", cx);
5656 view.handle_input("}", cx);
5657 });
5658 cx.assert_editor_state(
5659 &"
5660 🏀{{{)}}}}ˇ
5661 ε{{{)}}}}ˇ
5662 ❤️{{{)}}}}ˇ
5663 "
5664 .unindent(),
5665 );
5666
5667 // autoclose multi-character pairs
5668 cx.set_state(
5669 &"
5670 ˇ
5671 ˇ
5672 "
5673 .unindent(),
5674 );
5675 cx.update_editor(|view, cx| {
5676 view.handle_input("/", cx);
5677 view.handle_input("*", cx);
5678 });
5679 cx.assert_editor_state(
5680 &"
5681 /*ˇ */
5682 /*ˇ */
5683 "
5684 .unindent(),
5685 );
5686
5687 // one cursor autocloses a multi-character pair, one cursor
5688 // does not autoclose.
5689 cx.set_state(
5690 &"
5691 /ˇ
5692 ˇ
5693 "
5694 .unindent(),
5695 );
5696 cx.update_editor(|view, cx| view.handle_input("*", cx));
5697 cx.assert_editor_state(
5698 &"
5699 /*ˇ */
5700 *ˇ
5701 "
5702 .unindent(),
5703 );
5704
5705 // Don't autoclose if the next character isn't whitespace and isn't
5706 // listed in the language's "autoclose_before" section.
5707 cx.set_state("ˇa b");
5708 cx.update_editor(|view, cx| view.handle_input("{", cx));
5709 cx.assert_editor_state("{ˇa b");
5710
5711 // Don't autoclose if `close` is false for the bracket pair
5712 cx.set_state("ˇ");
5713 cx.update_editor(|view, cx| view.handle_input("[", cx));
5714 cx.assert_editor_state("[ˇ");
5715
5716 // Surround with brackets if text is selected
5717 cx.set_state("«aˇ» b");
5718 cx.update_editor(|view, cx| view.handle_input("{", cx));
5719 cx.assert_editor_state("{«aˇ»} b");
5720
5721 // Autclose pair where the start and end characters are the same
5722 cx.set_state("aˇ");
5723 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5724 cx.assert_editor_state("a\"ˇ\"");
5725 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5726 cx.assert_editor_state("a\"\"ˇ");
5727
5728 // Don't autoclose pair if autoclose is disabled
5729 cx.set_state("ˇ");
5730 cx.update_editor(|view, cx| view.handle_input("<", cx));
5731 cx.assert_editor_state("<ˇ");
5732
5733 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5734 cx.set_state("«aˇ» b");
5735 cx.update_editor(|view, cx| view.handle_input("<", cx));
5736 cx.assert_editor_state("<«aˇ»> b");
5737}
5738
5739#[gpui::test]
5740async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5741 init_test(cx, |settings| {
5742 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5743 });
5744
5745 let mut cx = EditorTestContext::new(cx).await;
5746
5747 let language = Arc::new(Language::new(
5748 LanguageConfig {
5749 brackets: BracketPairConfig {
5750 pairs: vec![
5751 BracketPair {
5752 start: "{".to_string(),
5753 end: "}".to_string(),
5754 close: true,
5755 surround: true,
5756 newline: true,
5757 },
5758 BracketPair {
5759 start: "(".to_string(),
5760 end: ")".to_string(),
5761 close: true,
5762 surround: true,
5763 newline: true,
5764 },
5765 BracketPair {
5766 start: "[".to_string(),
5767 end: "]".to_string(),
5768 close: false,
5769 surround: false,
5770 newline: true,
5771 },
5772 ],
5773 ..Default::default()
5774 },
5775 autoclose_before: "})]".to_string(),
5776 ..Default::default()
5777 },
5778 Some(tree_sitter_rust::LANGUAGE.into()),
5779 ));
5780
5781 cx.language_registry().add(language.clone());
5782 cx.update_buffer(|buffer, cx| {
5783 buffer.set_language(Some(language), cx);
5784 });
5785
5786 cx.set_state(
5787 &"
5788 ˇ
5789 ˇ
5790 ˇ
5791 "
5792 .unindent(),
5793 );
5794
5795 // ensure only matching closing brackets are skipped over
5796 cx.update_editor(|view, cx| {
5797 view.handle_input("}", cx);
5798 view.move_left(&MoveLeft, cx);
5799 view.handle_input(")", cx);
5800 view.move_left(&MoveLeft, cx);
5801 });
5802 cx.assert_editor_state(
5803 &"
5804 ˇ)}
5805 ˇ)}
5806 ˇ)}
5807 "
5808 .unindent(),
5809 );
5810
5811 // skip-over closing brackets at multiple cursors
5812 cx.update_editor(|view, cx| {
5813 view.handle_input(")", cx);
5814 view.handle_input("}", cx);
5815 });
5816 cx.assert_editor_state(
5817 &"
5818 )}ˇ
5819 )}ˇ
5820 )}ˇ
5821 "
5822 .unindent(),
5823 );
5824
5825 // ignore non-close brackets
5826 cx.update_editor(|view, cx| {
5827 view.handle_input("]", cx);
5828 view.move_left(&MoveLeft, cx);
5829 view.handle_input("]", cx);
5830 });
5831 cx.assert_editor_state(
5832 &"
5833 )}]ˇ]
5834 )}]ˇ]
5835 )}]ˇ]
5836 "
5837 .unindent(),
5838 );
5839}
5840
5841#[gpui::test]
5842async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5843 init_test(cx, |_| {});
5844
5845 let mut cx = EditorTestContext::new(cx).await;
5846
5847 let html_language = Arc::new(
5848 Language::new(
5849 LanguageConfig {
5850 name: "HTML".into(),
5851 brackets: BracketPairConfig {
5852 pairs: vec![
5853 BracketPair {
5854 start: "<".into(),
5855 end: ">".into(),
5856 close: true,
5857 ..Default::default()
5858 },
5859 BracketPair {
5860 start: "{".into(),
5861 end: "}".into(),
5862 close: true,
5863 ..Default::default()
5864 },
5865 BracketPair {
5866 start: "(".into(),
5867 end: ")".into(),
5868 close: true,
5869 ..Default::default()
5870 },
5871 ],
5872 ..Default::default()
5873 },
5874 autoclose_before: "})]>".into(),
5875 ..Default::default()
5876 },
5877 Some(tree_sitter_html::language()),
5878 )
5879 .with_injection_query(
5880 r#"
5881 (script_element
5882 (raw_text) @content
5883 (#set! "language" "javascript"))
5884 "#,
5885 )
5886 .unwrap(),
5887 );
5888
5889 let javascript_language = Arc::new(Language::new(
5890 LanguageConfig {
5891 name: "JavaScript".into(),
5892 brackets: BracketPairConfig {
5893 pairs: vec![
5894 BracketPair {
5895 start: "/*".into(),
5896 end: " */".into(),
5897 close: true,
5898 ..Default::default()
5899 },
5900 BracketPair {
5901 start: "{".into(),
5902 end: "}".into(),
5903 close: true,
5904 ..Default::default()
5905 },
5906 BracketPair {
5907 start: "(".into(),
5908 end: ")".into(),
5909 close: true,
5910 ..Default::default()
5911 },
5912 ],
5913 ..Default::default()
5914 },
5915 autoclose_before: "})]>".into(),
5916 ..Default::default()
5917 },
5918 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
5919 ));
5920
5921 cx.language_registry().add(html_language.clone());
5922 cx.language_registry().add(javascript_language.clone());
5923
5924 cx.update_buffer(|buffer, cx| {
5925 buffer.set_language(Some(html_language), cx);
5926 });
5927
5928 cx.set_state(
5929 &r#"
5930 <body>ˇ
5931 <script>
5932 var x = 1;ˇ
5933 </script>
5934 </body>ˇ
5935 "#
5936 .unindent(),
5937 );
5938
5939 // Precondition: different languages are active at different locations.
5940 cx.update_editor(|editor, cx| {
5941 let snapshot = editor.snapshot(cx);
5942 let cursors = editor.selections.ranges::<usize>(cx);
5943 let languages = cursors
5944 .iter()
5945 .map(|c| snapshot.language_at(c.start).unwrap().name())
5946 .collect::<Vec<_>>();
5947 assert_eq!(
5948 languages,
5949 &["HTML".into(), "JavaScript".into(), "HTML".into()]
5950 );
5951 });
5952
5953 // Angle brackets autoclose in HTML, but not JavaScript.
5954 cx.update_editor(|editor, cx| {
5955 editor.handle_input("<", cx);
5956 editor.handle_input("a", cx);
5957 });
5958 cx.assert_editor_state(
5959 &r#"
5960 <body><aˇ>
5961 <script>
5962 var x = 1;<aˇ
5963 </script>
5964 </body><aˇ>
5965 "#
5966 .unindent(),
5967 );
5968
5969 // Curly braces and parens autoclose in both HTML and JavaScript.
5970 cx.update_editor(|editor, cx| {
5971 editor.handle_input(" b=", cx);
5972 editor.handle_input("{", cx);
5973 editor.handle_input("c", cx);
5974 editor.handle_input("(", cx);
5975 });
5976 cx.assert_editor_state(
5977 &r#"
5978 <body><a b={c(ˇ)}>
5979 <script>
5980 var x = 1;<a b={c(ˇ)}
5981 </script>
5982 </body><a b={c(ˇ)}>
5983 "#
5984 .unindent(),
5985 );
5986
5987 // Brackets that were already autoclosed are skipped.
5988 cx.update_editor(|editor, cx| {
5989 editor.handle_input(")", cx);
5990 editor.handle_input("d", cx);
5991 editor.handle_input("}", cx);
5992 });
5993 cx.assert_editor_state(
5994 &r#"
5995 <body><a b={c()d}ˇ>
5996 <script>
5997 var x = 1;<a b={c()d}ˇ
5998 </script>
5999 </body><a b={c()d}ˇ>
6000 "#
6001 .unindent(),
6002 );
6003 cx.update_editor(|editor, cx| {
6004 editor.handle_input(">", cx);
6005 });
6006 cx.assert_editor_state(
6007 &r#"
6008 <body><a b={c()d}>ˇ
6009 <script>
6010 var x = 1;<a b={c()d}>ˇ
6011 </script>
6012 </body><a b={c()d}>ˇ
6013 "#
6014 .unindent(),
6015 );
6016
6017 // Reset
6018 cx.set_state(
6019 &r#"
6020 <body>ˇ
6021 <script>
6022 var x = 1;ˇ
6023 </script>
6024 </body>ˇ
6025 "#
6026 .unindent(),
6027 );
6028
6029 cx.update_editor(|editor, cx| {
6030 editor.handle_input("<", cx);
6031 });
6032 cx.assert_editor_state(
6033 &r#"
6034 <body><ˇ>
6035 <script>
6036 var x = 1;<ˇ
6037 </script>
6038 </body><ˇ>
6039 "#
6040 .unindent(),
6041 );
6042
6043 // When backspacing, the closing angle brackets are removed.
6044 cx.update_editor(|editor, cx| {
6045 editor.backspace(&Backspace, cx);
6046 });
6047 cx.assert_editor_state(
6048 &r#"
6049 <body>ˇ
6050 <script>
6051 var x = 1;ˇ
6052 </script>
6053 </body>ˇ
6054 "#
6055 .unindent(),
6056 );
6057
6058 // Block comments autoclose in JavaScript, but not HTML.
6059 cx.update_editor(|editor, cx| {
6060 editor.handle_input("/", cx);
6061 editor.handle_input("*", cx);
6062 });
6063 cx.assert_editor_state(
6064 &r#"
6065 <body>/*ˇ
6066 <script>
6067 var x = 1;/*ˇ */
6068 </script>
6069 </body>/*ˇ
6070 "#
6071 .unindent(),
6072 );
6073}
6074
6075#[gpui::test]
6076async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
6077 init_test(cx, |_| {});
6078
6079 let mut cx = EditorTestContext::new(cx).await;
6080
6081 let rust_language = Arc::new(
6082 Language::new(
6083 LanguageConfig {
6084 name: "Rust".into(),
6085 brackets: serde_json::from_value(json!([
6086 { "start": "{", "end": "}", "close": true, "newline": true },
6087 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6088 ]))
6089 .unwrap(),
6090 autoclose_before: "})]>".into(),
6091 ..Default::default()
6092 },
6093 Some(tree_sitter_rust::LANGUAGE.into()),
6094 )
6095 .with_override_query("(string_literal) @string")
6096 .unwrap(),
6097 );
6098
6099 cx.language_registry().add(rust_language.clone());
6100 cx.update_buffer(|buffer, cx| {
6101 buffer.set_language(Some(rust_language), cx);
6102 });
6103
6104 cx.set_state(
6105 &r#"
6106 let x = ˇ
6107 "#
6108 .unindent(),
6109 );
6110
6111 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6112 cx.update_editor(|editor, cx| {
6113 editor.handle_input("\"", cx);
6114 });
6115 cx.assert_editor_state(
6116 &r#"
6117 let x = "ˇ"
6118 "#
6119 .unindent(),
6120 );
6121
6122 // Inserting another quotation mark. The cursor moves across the existing
6123 // automatically-inserted quotation mark.
6124 cx.update_editor(|editor, cx| {
6125 editor.handle_input("\"", cx);
6126 });
6127 cx.assert_editor_state(
6128 &r#"
6129 let x = ""ˇ
6130 "#
6131 .unindent(),
6132 );
6133
6134 // Reset
6135 cx.set_state(
6136 &r#"
6137 let x = ˇ
6138 "#
6139 .unindent(),
6140 );
6141
6142 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6143 cx.update_editor(|editor, cx| {
6144 editor.handle_input("\"", cx);
6145 editor.handle_input(" ", cx);
6146 editor.move_left(&Default::default(), cx);
6147 editor.handle_input("\\", cx);
6148 editor.handle_input("\"", cx);
6149 });
6150 cx.assert_editor_state(
6151 &r#"
6152 let x = "\"ˇ "
6153 "#
6154 .unindent(),
6155 );
6156
6157 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6158 // mark. Nothing is inserted.
6159 cx.update_editor(|editor, cx| {
6160 editor.move_right(&Default::default(), cx);
6161 editor.handle_input("\"", cx);
6162 });
6163 cx.assert_editor_state(
6164 &r#"
6165 let x = "\" "ˇ
6166 "#
6167 .unindent(),
6168 );
6169}
6170
6171#[gpui::test]
6172async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
6173 init_test(cx, |_| {});
6174
6175 let language = Arc::new(Language::new(
6176 LanguageConfig {
6177 brackets: BracketPairConfig {
6178 pairs: vec![
6179 BracketPair {
6180 start: "{".to_string(),
6181 end: "}".to_string(),
6182 close: true,
6183 surround: true,
6184 newline: true,
6185 },
6186 BracketPair {
6187 start: "/* ".to_string(),
6188 end: "*/".to_string(),
6189 close: true,
6190 surround: true,
6191 ..Default::default()
6192 },
6193 ],
6194 ..Default::default()
6195 },
6196 ..Default::default()
6197 },
6198 Some(tree_sitter_rust::LANGUAGE.into()),
6199 ));
6200
6201 let text = r#"
6202 a
6203 b
6204 c
6205 "#
6206 .unindent();
6207
6208 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6209 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6210 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6211 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6212 .await;
6213
6214 view.update(cx, |view, cx| {
6215 view.change_selections(None, cx, |s| {
6216 s.select_display_ranges([
6217 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6218 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6219 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6220 ])
6221 });
6222
6223 view.handle_input("{", cx);
6224 view.handle_input("{", cx);
6225 view.handle_input("{", cx);
6226 assert_eq!(
6227 view.text(cx),
6228 "
6229 {{{a}}}
6230 {{{b}}}
6231 {{{c}}}
6232 "
6233 .unindent()
6234 );
6235 assert_eq!(
6236 view.selections.display_ranges(cx),
6237 [
6238 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6239 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6240 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6241 ]
6242 );
6243
6244 view.undo(&Undo, cx);
6245 view.undo(&Undo, cx);
6246 view.undo(&Undo, cx);
6247 assert_eq!(
6248 view.text(cx),
6249 "
6250 a
6251 b
6252 c
6253 "
6254 .unindent()
6255 );
6256 assert_eq!(
6257 view.selections.display_ranges(cx),
6258 [
6259 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6260 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6261 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6262 ]
6263 );
6264
6265 // Ensure inserting the first character of a multi-byte bracket pair
6266 // doesn't surround the selections with the bracket.
6267 view.handle_input("/", cx);
6268 assert_eq!(
6269 view.text(cx),
6270 "
6271 /
6272 /
6273 /
6274 "
6275 .unindent()
6276 );
6277 assert_eq!(
6278 view.selections.display_ranges(cx),
6279 [
6280 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6281 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6282 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6283 ]
6284 );
6285
6286 view.undo(&Undo, cx);
6287 assert_eq!(
6288 view.text(cx),
6289 "
6290 a
6291 b
6292 c
6293 "
6294 .unindent()
6295 );
6296 assert_eq!(
6297 view.selections.display_ranges(cx),
6298 [
6299 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6300 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6301 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6302 ]
6303 );
6304
6305 // Ensure inserting the last character of a multi-byte bracket pair
6306 // doesn't surround the selections with the bracket.
6307 view.handle_input("*", cx);
6308 assert_eq!(
6309 view.text(cx),
6310 "
6311 *
6312 *
6313 *
6314 "
6315 .unindent()
6316 );
6317 assert_eq!(
6318 view.selections.display_ranges(cx),
6319 [
6320 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6321 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6322 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6323 ]
6324 );
6325 });
6326}
6327
6328#[gpui::test]
6329async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6330 init_test(cx, |_| {});
6331
6332 let language = Arc::new(Language::new(
6333 LanguageConfig {
6334 brackets: BracketPairConfig {
6335 pairs: vec![BracketPair {
6336 start: "{".to_string(),
6337 end: "}".to_string(),
6338 close: true,
6339 surround: true,
6340 newline: true,
6341 }],
6342 ..Default::default()
6343 },
6344 autoclose_before: "}".to_string(),
6345 ..Default::default()
6346 },
6347 Some(tree_sitter_rust::LANGUAGE.into()),
6348 ));
6349
6350 let text = r#"
6351 a
6352 b
6353 c
6354 "#
6355 .unindent();
6356
6357 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6358 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6359 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6360 editor
6361 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6362 .await;
6363
6364 editor.update(cx, |editor, cx| {
6365 editor.change_selections(None, cx, |s| {
6366 s.select_ranges([
6367 Point::new(0, 1)..Point::new(0, 1),
6368 Point::new(1, 1)..Point::new(1, 1),
6369 Point::new(2, 1)..Point::new(2, 1),
6370 ])
6371 });
6372
6373 editor.handle_input("{", cx);
6374 editor.handle_input("{", cx);
6375 editor.handle_input("_", cx);
6376 assert_eq!(
6377 editor.text(cx),
6378 "
6379 a{{_}}
6380 b{{_}}
6381 c{{_}}
6382 "
6383 .unindent()
6384 );
6385 assert_eq!(
6386 editor.selections.ranges::<Point>(cx),
6387 [
6388 Point::new(0, 4)..Point::new(0, 4),
6389 Point::new(1, 4)..Point::new(1, 4),
6390 Point::new(2, 4)..Point::new(2, 4)
6391 ]
6392 );
6393
6394 editor.backspace(&Default::default(), cx);
6395 editor.backspace(&Default::default(), cx);
6396 assert_eq!(
6397 editor.text(cx),
6398 "
6399 a{}
6400 b{}
6401 c{}
6402 "
6403 .unindent()
6404 );
6405 assert_eq!(
6406 editor.selections.ranges::<Point>(cx),
6407 [
6408 Point::new(0, 2)..Point::new(0, 2),
6409 Point::new(1, 2)..Point::new(1, 2),
6410 Point::new(2, 2)..Point::new(2, 2)
6411 ]
6412 );
6413
6414 editor.delete_to_previous_word_start(&Default::default(), cx);
6415 assert_eq!(
6416 editor.text(cx),
6417 "
6418 a
6419 b
6420 c
6421 "
6422 .unindent()
6423 );
6424 assert_eq!(
6425 editor.selections.ranges::<Point>(cx),
6426 [
6427 Point::new(0, 1)..Point::new(0, 1),
6428 Point::new(1, 1)..Point::new(1, 1),
6429 Point::new(2, 1)..Point::new(2, 1)
6430 ]
6431 );
6432 });
6433}
6434
6435#[gpui::test]
6436async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6437 init_test(cx, |settings| {
6438 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6439 });
6440
6441 let mut cx = EditorTestContext::new(cx).await;
6442
6443 let language = Arc::new(Language::new(
6444 LanguageConfig {
6445 brackets: BracketPairConfig {
6446 pairs: vec![
6447 BracketPair {
6448 start: "{".to_string(),
6449 end: "}".to_string(),
6450 close: true,
6451 surround: true,
6452 newline: true,
6453 },
6454 BracketPair {
6455 start: "(".to_string(),
6456 end: ")".to_string(),
6457 close: true,
6458 surround: true,
6459 newline: true,
6460 },
6461 BracketPair {
6462 start: "[".to_string(),
6463 end: "]".to_string(),
6464 close: false,
6465 surround: true,
6466 newline: true,
6467 },
6468 ],
6469 ..Default::default()
6470 },
6471 autoclose_before: "})]".to_string(),
6472 ..Default::default()
6473 },
6474 Some(tree_sitter_rust::LANGUAGE.into()),
6475 ));
6476
6477 cx.language_registry().add(language.clone());
6478 cx.update_buffer(|buffer, cx| {
6479 buffer.set_language(Some(language), cx);
6480 });
6481
6482 cx.set_state(
6483 &"
6484 {(ˇ)}
6485 [[ˇ]]
6486 {(ˇ)}
6487 "
6488 .unindent(),
6489 );
6490
6491 cx.update_editor(|view, cx| {
6492 view.backspace(&Default::default(), cx);
6493 view.backspace(&Default::default(), cx);
6494 });
6495
6496 cx.assert_editor_state(
6497 &"
6498 ˇ
6499 ˇ]]
6500 ˇ
6501 "
6502 .unindent(),
6503 );
6504
6505 cx.update_editor(|view, cx| {
6506 view.handle_input("{", cx);
6507 view.handle_input("{", cx);
6508 view.move_right(&MoveRight, cx);
6509 view.move_right(&MoveRight, cx);
6510 view.move_left(&MoveLeft, cx);
6511 view.move_left(&MoveLeft, cx);
6512 view.backspace(&Default::default(), cx);
6513 });
6514
6515 cx.assert_editor_state(
6516 &"
6517 {ˇ}
6518 {ˇ}]]
6519 {ˇ}
6520 "
6521 .unindent(),
6522 );
6523
6524 cx.update_editor(|view, cx| {
6525 view.backspace(&Default::default(), cx);
6526 });
6527
6528 cx.assert_editor_state(
6529 &"
6530 ˇ
6531 ˇ]]
6532 ˇ
6533 "
6534 .unindent(),
6535 );
6536}
6537
6538#[gpui::test]
6539async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6540 init_test(cx, |_| {});
6541
6542 let language = Arc::new(Language::new(
6543 LanguageConfig::default(),
6544 Some(tree_sitter_rust::LANGUAGE.into()),
6545 ));
6546
6547 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6548 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6549 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6550 editor
6551 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6552 .await;
6553
6554 editor.update(cx, |editor, cx| {
6555 editor.set_auto_replace_emoji_shortcode(true);
6556
6557 editor.handle_input("Hello ", cx);
6558 editor.handle_input(":wave", cx);
6559 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6560
6561 editor.handle_input(":", cx);
6562 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6563
6564 editor.handle_input(" :smile", cx);
6565 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6566
6567 editor.handle_input(":", cx);
6568 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6569
6570 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6571 editor.handle_input(":wave", cx);
6572 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6573
6574 editor.handle_input(":", cx);
6575 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6576
6577 editor.handle_input(":1", cx);
6578 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6579
6580 editor.handle_input(":", cx);
6581 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6582
6583 // Ensure shortcode does not get replaced when it is part of a word
6584 editor.handle_input(" Test:wave", cx);
6585 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6586
6587 editor.handle_input(":", cx);
6588 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6589
6590 editor.set_auto_replace_emoji_shortcode(false);
6591
6592 // Ensure shortcode does not get replaced when auto replace is off
6593 editor.handle_input(" :wave", cx);
6594 assert_eq!(
6595 editor.text(cx),
6596 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6597 );
6598
6599 editor.handle_input(":", cx);
6600 assert_eq!(
6601 editor.text(cx),
6602 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6603 );
6604 });
6605}
6606
6607#[gpui::test]
6608async fn test_snippets(cx: &mut gpui::TestAppContext) {
6609 init_test(cx, |_| {});
6610
6611 let (text, insertion_ranges) = marked_text_ranges(
6612 indoc! {"
6613 a.ˇ b
6614 a.ˇ b
6615 a.ˇ b
6616 "},
6617 false,
6618 );
6619
6620 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6621 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6622
6623 editor.update(cx, |editor, cx| {
6624 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6625
6626 editor
6627 .insert_snippet(&insertion_ranges, snippet, cx)
6628 .unwrap();
6629
6630 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6631 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6632 assert_eq!(editor.text(cx), expected_text);
6633 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6634 }
6635
6636 assert(
6637 editor,
6638 cx,
6639 indoc! {"
6640 a.f(«one», two, «three») b
6641 a.f(«one», two, «three») b
6642 a.f(«one», two, «three») b
6643 "},
6644 );
6645
6646 // Can't move earlier than the first tab stop
6647 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6648 assert(
6649 editor,
6650 cx,
6651 indoc! {"
6652 a.f(«one», two, «three») b
6653 a.f(«one», two, «three») b
6654 a.f(«one», two, «three») b
6655 "},
6656 );
6657
6658 assert!(editor.move_to_next_snippet_tabstop(cx));
6659 assert(
6660 editor,
6661 cx,
6662 indoc! {"
6663 a.f(one, «two», three) b
6664 a.f(one, «two», three) b
6665 a.f(one, «two», three) b
6666 "},
6667 );
6668
6669 editor.move_to_prev_snippet_tabstop(cx);
6670 assert(
6671 editor,
6672 cx,
6673 indoc! {"
6674 a.f(«one», two, «three») b
6675 a.f(«one», two, «three») b
6676 a.f(«one», two, «three») b
6677 "},
6678 );
6679
6680 assert!(editor.move_to_next_snippet_tabstop(cx));
6681 assert(
6682 editor,
6683 cx,
6684 indoc! {"
6685 a.f(one, «two», three) b
6686 a.f(one, «two», three) b
6687 a.f(one, «two», three) b
6688 "},
6689 );
6690 assert!(editor.move_to_next_snippet_tabstop(cx));
6691 assert(
6692 editor,
6693 cx,
6694 indoc! {"
6695 a.f(one, two, three)ˇ b
6696 a.f(one, two, three)ˇ b
6697 a.f(one, two, three)ˇ b
6698 "},
6699 );
6700
6701 // As soon as the last tab stop is reached, snippet state is gone
6702 editor.move_to_prev_snippet_tabstop(cx);
6703 assert(
6704 editor,
6705 cx,
6706 indoc! {"
6707 a.f(one, two, three)ˇ b
6708 a.f(one, two, three)ˇ b
6709 a.f(one, two, three)ˇ b
6710 "},
6711 );
6712 });
6713}
6714
6715#[gpui::test]
6716async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6717 init_test(cx, |_| {});
6718
6719 let fs = FakeFs::new(cx.executor());
6720 fs.insert_file("/file.rs", Default::default()).await;
6721
6722 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6723
6724 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6725 language_registry.add(rust_lang());
6726 let mut fake_servers = language_registry.register_fake_lsp(
6727 "Rust",
6728 FakeLspAdapter {
6729 capabilities: lsp::ServerCapabilities {
6730 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6731 ..Default::default()
6732 },
6733 ..Default::default()
6734 },
6735 );
6736
6737 let buffer = project
6738 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6739 .await
6740 .unwrap();
6741
6742 cx.executor().start_waiting();
6743 let fake_server = fake_servers.next().await.unwrap();
6744
6745 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6746 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6747 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6748 assert!(cx.read(|cx| editor.is_dirty(cx)));
6749
6750 let save = editor
6751 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6752 .unwrap();
6753 fake_server
6754 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6755 assert_eq!(
6756 params.text_document.uri,
6757 lsp::Url::from_file_path("/file.rs").unwrap()
6758 );
6759 assert_eq!(params.options.tab_size, 4);
6760 Ok(Some(vec![lsp::TextEdit::new(
6761 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6762 ", ".to_string(),
6763 )]))
6764 })
6765 .next()
6766 .await;
6767 cx.executor().start_waiting();
6768 save.await;
6769
6770 assert_eq!(
6771 editor.update(cx, |editor, cx| editor.text(cx)),
6772 "one, two\nthree\n"
6773 );
6774 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6775
6776 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6777 assert!(cx.read(|cx| editor.is_dirty(cx)));
6778
6779 // Ensure we can still save even if formatting hangs.
6780 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6781 assert_eq!(
6782 params.text_document.uri,
6783 lsp::Url::from_file_path("/file.rs").unwrap()
6784 );
6785 futures::future::pending::<()>().await;
6786 unreachable!()
6787 });
6788 let save = editor
6789 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6790 .unwrap();
6791 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6792 cx.executor().start_waiting();
6793 save.await;
6794 assert_eq!(
6795 editor.update(cx, |editor, cx| editor.text(cx)),
6796 "one\ntwo\nthree\n"
6797 );
6798 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6799
6800 // For non-dirty buffer, no formatting request should be sent
6801 let save = editor
6802 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6803 .unwrap();
6804 let _pending_format_request = fake_server
6805 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6806 panic!("Should not be invoked on non-dirty buffer");
6807 })
6808 .next();
6809 cx.executor().start_waiting();
6810 save.await;
6811
6812 // Set rust language override and assert overridden tabsize is sent to language server
6813 update_test_language_settings(cx, |settings| {
6814 settings.languages.insert(
6815 "Rust".into(),
6816 LanguageSettingsContent {
6817 tab_size: NonZeroU32::new(8),
6818 ..Default::default()
6819 },
6820 );
6821 });
6822
6823 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6824 assert!(cx.read(|cx| editor.is_dirty(cx)));
6825 let save = editor
6826 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6827 .unwrap();
6828 fake_server
6829 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6830 assert_eq!(
6831 params.text_document.uri,
6832 lsp::Url::from_file_path("/file.rs").unwrap()
6833 );
6834 assert_eq!(params.options.tab_size, 8);
6835 Ok(Some(vec![]))
6836 })
6837 .next()
6838 .await;
6839 cx.executor().start_waiting();
6840 save.await;
6841}
6842
6843#[gpui::test]
6844async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6845 init_test(cx, |_| {});
6846
6847 let cols = 4;
6848 let rows = 10;
6849 let sample_text_1 = sample_text(rows, cols, 'a');
6850 assert_eq!(
6851 sample_text_1,
6852 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6853 );
6854 let sample_text_2 = sample_text(rows, cols, 'l');
6855 assert_eq!(
6856 sample_text_2,
6857 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6858 );
6859 let sample_text_3 = sample_text(rows, cols, 'v');
6860 assert_eq!(
6861 sample_text_3,
6862 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6863 );
6864
6865 let fs = FakeFs::new(cx.executor());
6866 fs.insert_tree(
6867 "/a",
6868 json!({
6869 "main.rs": sample_text_1,
6870 "other.rs": sample_text_2,
6871 "lib.rs": sample_text_3,
6872 }),
6873 )
6874 .await;
6875
6876 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6877 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6878 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6879
6880 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6881 language_registry.add(rust_lang());
6882 let mut fake_servers = language_registry.register_fake_lsp(
6883 "Rust",
6884 FakeLspAdapter {
6885 capabilities: lsp::ServerCapabilities {
6886 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6887 ..Default::default()
6888 },
6889 ..Default::default()
6890 },
6891 );
6892
6893 let worktree = project.update(cx, |project, cx| {
6894 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
6895 assert_eq!(worktrees.len(), 1);
6896 worktrees.pop().unwrap()
6897 });
6898 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6899
6900 let buffer_1 = project
6901 .update(cx, |project, cx| {
6902 project.open_buffer((worktree_id, "main.rs"), cx)
6903 })
6904 .await
6905 .unwrap();
6906 let buffer_2 = project
6907 .update(cx, |project, cx| {
6908 project.open_buffer((worktree_id, "other.rs"), cx)
6909 })
6910 .await
6911 .unwrap();
6912 let buffer_3 = project
6913 .update(cx, |project, cx| {
6914 project.open_buffer((worktree_id, "lib.rs"), cx)
6915 })
6916 .await
6917 .unwrap();
6918
6919 let multi_buffer = cx.new_model(|cx| {
6920 let mut multi_buffer = MultiBuffer::new(ReadWrite);
6921 multi_buffer.push_excerpts(
6922 buffer_1.clone(),
6923 [
6924 ExcerptRange {
6925 context: Point::new(0, 0)..Point::new(3, 0),
6926 primary: None,
6927 },
6928 ExcerptRange {
6929 context: Point::new(5, 0)..Point::new(7, 0),
6930 primary: None,
6931 },
6932 ExcerptRange {
6933 context: Point::new(9, 0)..Point::new(10, 4),
6934 primary: None,
6935 },
6936 ],
6937 cx,
6938 );
6939 multi_buffer.push_excerpts(
6940 buffer_2.clone(),
6941 [
6942 ExcerptRange {
6943 context: Point::new(0, 0)..Point::new(3, 0),
6944 primary: None,
6945 },
6946 ExcerptRange {
6947 context: Point::new(5, 0)..Point::new(7, 0),
6948 primary: None,
6949 },
6950 ExcerptRange {
6951 context: Point::new(9, 0)..Point::new(10, 4),
6952 primary: None,
6953 },
6954 ],
6955 cx,
6956 );
6957 multi_buffer.push_excerpts(
6958 buffer_3.clone(),
6959 [
6960 ExcerptRange {
6961 context: Point::new(0, 0)..Point::new(3, 0),
6962 primary: None,
6963 },
6964 ExcerptRange {
6965 context: Point::new(5, 0)..Point::new(7, 0),
6966 primary: None,
6967 },
6968 ExcerptRange {
6969 context: Point::new(9, 0)..Point::new(10, 4),
6970 primary: None,
6971 },
6972 ],
6973 cx,
6974 );
6975 multi_buffer
6976 });
6977 let multi_buffer_editor = cx.new_view(|cx| {
6978 Editor::new(
6979 EditorMode::Full,
6980 multi_buffer,
6981 Some(project.clone()),
6982 true,
6983 cx,
6984 )
6985 });
6986
6987 multi_buffer_editor.update(cx, |editor, cx| {
6988 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
6989 editor.insert("|one|two|three|", cx);
6990 });
6991 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6992 multi_buffer_editor.update(cx, |editor, cx| {
6993 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
6994 s.select_ranges(Some(60..70))
6995 });
6996 editor.insert("|four|five|six|", cx);
6997 });
6998 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
6999
7000 // First two buffers should be edited, but not the third one.
7001 assert_eq!(
7002 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7003 "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}",
7004 );
7005 buffer_1.update(cx, |buffer, _| {
7006 assert!(buffer.is_dirty());
7007 assert_eq!(
7008 buffer.text(),
7009 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7010 )
7011 });
7012 buffer_2.update(cx, |buffer, _| {
7013 assert!(buffer.is_dirty());
7014 assert_eq!(
7015 buffer.text(),
7016 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7017 )
7018 });
7019 buffer_3.update(cx, |buffer, _| {
7020 assert!(!buffer.is_dirty());
7021 assert_eq!(buffer.text(), sample_text_3,)
7022 });
7023
7024 cx.executor().start_waiting();
7025 let save = multi_buffer_editor
7026 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7027 .unwrap();
7028
7029 let fake_server = fake_servers.next().await.unwrap();
7030 fake_server
7031 .server
7032 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7033 Ok(Some(vec![lsp::TextEdit::new(
7034 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7035 format!("[{} formatted]", params.text_document.uri),
7036 )]))
7037 })
7038 .detach();
7039 save.await;
7040
7041 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7042 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7043 assert_eq!(
7044 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7045 "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}",
7046 );
7047 buffer_1.update(cx, |buffer, _| {
7048 assert!(!buffer.is_dirty());
7049 assert_eq!(
7050 buffer.text(),
7051 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
7052 )
7053 });
7054 buffer_2.update(cx, |buffer, _| {
7055 assert!(!buffer.is_dirty());
7056 assert_eq!(
7057 buffer.text(),
7058 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
7059 )
7060 });
7061 buffer_3.update(cx, |buffer, _| {
7062 assert!(!buffer.is_dirty());
7063 assert_eq!(buffer.text(), sample_text_3,)
7064 });
7065}
7066
7067#[gpui::test]
7068async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
7069 init_test(cx, |_| {});
7070
7071 let fs = FakeFs::new(cx.executor());
7072 fs.insert_file("/file.rs", Default::default()).await;
7073
7074 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7075
7076 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7077 language_registry.add(rust_lang());
7078 let mut fake_servers = language_registry.register_fake_lsp(
7079 "Rust",
7080 FakeLspAdapter {
7081 capabilities: lsp::ServerCapabilities {
7082 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7083 ..Default::default()
7084 },
7085 ..Default::default()
7086 },
7087 );
7088
7089 let buffer = project
7090 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7091 .await
7092 .unwrap();
7093
7094 cx.executor().start_waiting();
7095 let fake_server = fake_servers.next().await.unwrap();
7096
7097 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7098 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7099 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7100 assert!(cx.read(|cx| editor.is_dirty(cx)));
7101
7102 let save = editor
7103 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7104 .unwrap();
7105 fake_server
7106 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7107 assert_eq!(
7108 params.text_document.uri,
7109 lsp::Url::from_file_path("/file.rs").unwrap()
7110 );
7111 assert_eq!(params.options.tab_size, 4);
7112 Ok(Some(vec![lsp::TextEdit::new(
7113 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7114 ", ".to_string(),
7115 )]))
7116 })
7117 .next()
7118 .await;
7119 cx.executor().start_waiting();
7120 save.await;
7121 assert_eq!(
7122 editor.update(cx, |editor, cx| editor.text(cx)),
7123 "one, two\nthree\n"
7124 );
7125 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7126
7127 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7128 assert!(cx.read(|cx| editor.is_dirty(cx)));
7129
7130 // Ensure we can still save even if formatting hangs.
7131 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7132 move |params, _| async move {
7133 assert_eq!(
7134 params.text_document.uri,
7135 lsp::Url::from_file_path("/file.rs").unwrap()
7136 );
7137 futures::future::pending::<()>().await;
7138 unreachable!()
7139 },
7140 );
7141 let save = editor
7142 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7143 .unwrap();
7144 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7145 cx.executor().start_waiting();
7146 save.await;
7147 assert_eq!(
7148 editor.update(cx, |editor, cx| editor.text(cx)),
7149 "one\ntwo\nthree\n"
7150 );
7151 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7152
7153 // For non-dirty buffer, no formatting request should be sent
7154 let save = editor
7155 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7156 .unwrap();
7157 let _pending_format_request = fake_server
7158 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7159 panic!("Should not be invoked on non-dirty buffer");
7160 })
7161 .next();
7162 cx.executor().start_waiting();
7163 save.await;
7164
7165 // Set Rust language override and assert overridden tabsize is sent to language server
7166 update_test_language_settings(cx, |settings| {
7167 settings.languages.insert(
7168 "Rust".into(),
7169 LanguageSettingsContent {
7170 tab_size: NonZeroU32::new(8),
7171 ..Default::default()
7172 },
7173 );
7174 });
7175
7176 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
7177 assert!(cx.read(|cx| editor.is_dirty(cx)));
7178 let save = editor
7179 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7180 .unwrap();
7181 fake_server
7182 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7183 assert_eq!(
7184 params.text_document.uri,
7185 lsp::Url::from_file_path("/file.rs").unwrap()
7186 );
7187 assert_eq!(params.options.tab_size, 8);
7188 Ok(Some(vec![]))
7189 })
7190 .next()
7191 .await;
7192 cx.executor().start_waiting();
7193 save.await;
7194}
7195
7196#[gpui::test]
7197async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7198 init_test(cx, |settings| {
7199 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7200 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7201 ))
7202 });
7203
7204 let fs = FakeFs::new(cx.executor());
7205 fs.insert_file("/file.rs", Default::default()).await;
7206
7207 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7208
7209 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7210 language_registry.add(Arc::new(Language::new(
7211 LanguageConfig {
7212 name: "Rust".into(),
7213 matcher: LanguageMatcher {
7214 path_suffixes: vec!["rs".to_string()],
7215 ..Default::default()
7216 },
7217 ..LanguageConfig::default()
7218 },
7219 Some(tree_sitter_rust::LANGUAGE.into()),
7220 )));
7221 update_test_language_settings(cx, |settings| {
7222 // Enable Prettier formatting for the same buffer, and ensure
7223 // LSP is called instead of Prettier.
7224 settings.defaults.prettier = Some(PrettierSettings {
7225 allowed: true,
7226 ..PrettierSettings::default()
7227 });
7228 });
7229 let mut fake_servers = language_registry.register_fake_lsp(
7230 "Rust",
7231 FakeLspAdapter {
7232 capabilities: lsp::ServerCapabilities {
7233 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7234 ..Default::default()
7235 },
7236 ..Default::default()
7237 },
7238 );
7239
7240 let buffer = project
7241 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7242 .await
7243 .unwrap();
7244
7245 cx.executor().start_waiting();
7246 let fake_server = fake_servers.next().await.unwrap();
7247
7248 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7249 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7250 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7251
7252 let format = editor
7253 .update(cx, |editor, cx| {
7254 editor.perform_format(
7255 project.clone(),
7256 FormatTrigger::Manual,
7257 FormatTarget::Buffer,
7258 cx,
7259 )
7260 })
7261 .unwrap();
7262 fake_server
7263 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7264 assert_eq!(
7265 params.text_document.uri,
7266 lsp::Url::from_file_path("/file.rs").unwrap()
7267 );
7268 assert_eq!(params.options.tab_size, 4);
7269 Ok(Some(vec![lsp::TextEdit::new(
7270 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7271 ", ".to_string(),
7272 )]))
7273 })
7274 .next()
7275 .await;
7276 cx.executor().start_waiting();
7277 format.await;
7278 assert_eq!(
7279 editor.update(cx, |editor, cx| editor.text(cx)),
7280 "one, two\nthree\n"
7281 );
7282
7283 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7284 // Ensure we don't lock if formatting hangs.
7285 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7286 assert_eq!(
7287 params.text_document.uri,
7288 lsp::Url::from_file_path("/file.rs").unwrap()
7289 );
7290 futures::future::pending::<()>().await;
7291 unreachable!()
7292 });
7293 let format = editor
7294 .update(cx, |editor, cx| {
7295 editor.perform_format(project, FormatTrigger::Manual, FormatTarget::Buffer, cx)
7296 })
7297 .unwrap();
7298 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7299 cx.executor().start_waiting();
7300 format.await;
7301 assert_eq!(
7302 editor.update(cx, |editor, cx| editor.text(cx)),
7303 "one\ntwo\nthree\n"
7304 );
7305}
7306
7307#[gpui::test]
7308async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7309 init_test(cx, |_| {});
7310
7311 let mut cx = EditorLspTestContext::new_rust(
7312 lsp::ServerCapabilities {
7313 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7314 ..Default::default()
7315 },
7316 cx,
7317 )
7318 .await;
7319
7320 cx.set_state(indoc! {"
7321 one.twoˇ
7322 "});
7323
7324 // The format request takes a long time. When it completes, it inserts
7325 // a newline and an indent before the `.`
7326 cx.lsp
7327 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7328 let executor = cx.background_executor().clone();
7329 async move {
7330 executor.timer(Duration::from_millis(100)).await;
7331 Ok(Some(vec![lsp::TextEdit {
7332 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7333 new_text: "\n ".into(),
7334 }]))
7335 }
7336 });
7337
7338 // Submit a format request.
7339 let format_1 = cx
7340 .update_editor(|editor, cx| editor.format(&Format, cx))
7341 .unwrap();
7342 cx.executor().run_until_parked();
7343
7344 // Submit a second format request.
7345 let format_2 = cx
7346 .update_editor(|editor, cx| editor.format(&Format, cx))
7347 .unwrap();
7348 cx.executor().run_until_parked();
7349
7350 // Wait for both format requests to complete
7351 cx.executor().advance_clock(Duration::from_millis(200));
7352 cx.executor().start_waiting();
7353 format_1.await.unwrap();
7354 cx.executor().start_waiting();
7355 format_2.await.unwrap();
7356
7357 // The formatting edits only happens once.
7358 cx.assert_editor_state(indoc! {"
7359 one
7360 .twoˇ
7361 "});
7362}
7363
7364#[gpui::test]
7365async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7366 init_test(cx, |settings| {
7367 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7368 });
7369
7370 let mut cx = EditorLspTestContext::new_rust(
7371 lsp::ServerCapabilities {
7372 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7373 ..Default::default()
7374 },
7375 cx,
7376 )
7377 .await;
7378
7379 // Set up a buffer white some trailing whitespace and no trailing newline.
7380 cx.set_state(
7381 &[
7382 "one ", //
7383 "twoˇ", //
7384 "three ", //
7385 "four", //
7386 ]
7387 .join("\n"),
7388 );
7389
7390 // Submit a format request.
7391 let format = cx
7392 .update_editor(|editor, cx| editor.format(&Format, cx))
7393 .unwrap();
7394
7395 // Record which buffer changes have been sent to the language server
7396 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7397 cx.lsp
7398 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7399 let buffer_changes = buffer_changes.clone();
7400 move |params, _| {
7401 buffer_changes.lock().extend(
7402 params
7403 .content_changes
7404 .into_iter()
7405 .map(|e| (e.range.unwrap(), e.text)),
7406 );
7407 }
7408 });
7409
7410 // Handle formatting requests to the language server.
7411 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7412 let buffer_changes = buffer_changes.clone();
7413 move |_, _| {
7414 // When formatting is requested, trailing whitespace has already been stripped,
7415 // and the trailing newline has already been added.
7416 assert_eq!(
7417 &buffer_changes.lock()[1..],
7418 &[
7419 (
7420 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7421 "".into()
7422 ),
7423 (
7424 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7425 "".into()
7426 ),
7427 (
7428 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7429 "\n".into()
7430 ),
7431 ]
7432 );
7433
7434 // Insert blank lines between each line of the buffer.
7435 async move {
7436 Ok(Some(vec![
7437 lsp::TextEdit {
7438 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7439 new_text: "\n".into(),
7440 },
7441 lsp::TextEdit {
7442 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7443 new_text: "\n".into(),
7444 },
7445 ]))
7446 }
7447 }
7448 });
7449
7450 // After formatting the buffer, the trailing whitespace is stripped,
7451 // a newline is appended, and the edits provided by the language server
7452 // have been applied.
7453 format.await.unwrap();
7454 cx.assert_editor_state(
7455 &[
7456 "one", //
7457 "", //
7458 "twoˇ", //
7459 "", //
7460 "three", //
7461 "four", //
7462 "", //
7463 ]
7464 .join("\n"),
7465 );
7466
7467 // Undoing the formatting undoes the trailing whitespace removal, the
7468 // trailing newline, and the LSP edits.
7469 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7470 cx.assert_editor_state(
7471 &[
7472 "one ", //
7473 "twoˇ", //
7474 "three ", //
7475 "four", //
7476 ]
7477 .join("\n"),
7478 );
7479}
7480
7481#[gpui::test]
7482async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7483 cx: &mut gpui::TestAppContext,
7484) {
7485 init_test(cx, |_| {});
7486
7487 cx.update(|cx| {
7488 cx.update_global::<SettingsStore, _>(|settings, cx| {
7489 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7490 settings.auto_signature_help = Some(true);
7491 });
7492 });
7493 });
7494
7495 let mut cx = EditorLspTestContext::new_rust(
7496 lsp::ServerCapabilities {
7497 signature_help_provider: Some(lsp::SignatureHelpOptions {
7498 ..Default::default()
7499 }),
7500 ..Default::default()
7501 },
7502 cx,
7503 )
7504 .await;
7505
7506 let language = Language::new(
7507 LanguageConfig {
7508 name: "Rust".into(),
7509 brackets: BracketPairConfig {
7510 pairs: vec![
7511 BracketPair {
7512 start: "{".to_string(),
7513 end: "}".to_string(),
7514 close: true,
7515 surround: true,
7516 newline: true,
7517 },
7518 BracketPair {
7519 start: "(".to_string(),
7520 end: ")".to_string(),
7521 close: true,
7522 surround: true,
7523 newline: true,
7524 },
7525 BracketPair {
7526 start: "/*".to_string(),
7527 end: " */".to_string(),
7528 close: true,
7529 surround: true,
7530 newline: true,
7531 },
7532 BracketPair {
7533 start: "[".to_string(),
7534 end: "]".to_string(),
7535 close: false,
7536 surround: false,
7537 newline: true,
7538 },
7539 BracketPair {
7540 start: "\"".to_string(),
7541 end: "\"".to_string(),
7542 close: true,
7543 surround: true,
7544 newline: false,
7545 },
7546 BracketPair {
7547 start: "<".to_string(),
7548 end: ">".to_string(),
7549 close: false,
7550 surround: true,
7551 newline: true,
7552 },
7553 ],
7554 ..Default::default()
7555 },
7556 autoclose_before: "})]".to_string(),
7557 ..Default::default()
7558 },
7559 Some(tree_sitter_rust::LANGUAGE.into()),
7560 );
7561 let language = Arc::new(language);
7562
7563 cx.language_registry().add(language.clone());
7564 cx.update_buffer(|buffer, cx| {
7565 buffer.set_language(Some(language), cx);
7566 });
7567
7568 cx.set_state(
7569 &r#"
7570 fn main() {
7571 sampleˇ
7572 }
7573 "#
7574 .unindent(),
7575 );
7576
7577 cx.update_editor(|view, cx| {
7578 view.handle_input("(", cx);
7579 });
7580 cx.assert_editor_state(
7581 &"
7582 fn main() {
7583 sample(ˇ)
7584 }
7585 "
7586 .unindent(),
7587 );
7588
7589 let mocked_response = lsp::SignatureHelp {
7590 signatures: vec![lsp::SignatureInformation {
7591 label: "fn sample(param1: u8, param2: u8)".to_string(),
7592 documentation: None,
7593 parameters: Some(vec![
7594 lsp::ParameterInformation {
7595 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7596 documentation: None,
7597 },
7598 lsp::ParameterInformation {
7599 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7600 documentation: None,
7601 },
7602 ]),
7603 active_parameter: None,
7604 }],
7605 active_signature: Some(0),
7606 active_parameter: Some(0),
7607 };
7608 handle_signature_help_request(&mut cx, mocked_response).await;
7609
7610 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7611 .await;
7612
7613 cx.editor(|editor, _| {
7614 let signature_help_state = editor.signature_help_state.popover().cloned();
7615 assert!(signature_help_state.is_some());
7616 let ParsedMarkdown {
7617 text, highlights, ..
7618 } = signature_help_state.unwrap().parsed_content;
7619 assert_eq!(text, "param1: u8, param2: u8");
7620 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7621 });
7622}
7623
7624#[gpui::test]
7625async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7626 init_test(cx, |_| {});
7627
7628 cx.update(|cx| {
7629 cx.update_global::<SettingsStore, _>(|settings, cx| {
7630 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7631 settings.auto_signature_help = Some(false);
7632 settings.show_signature_help_after_edits = Some(false);
7633 });
7634 });
7635 });
7636
7637 let mut cx = EditorLspTestContext::new_rust(
7638 lsp::ServerCapabilities {
7639 signature_help_provider: Some(lsp::SignatureHelpOptions {
7640 ..Default::default()
7641 }),
7642 ..Default::default()
7643 },
7644 cx,
7645 )
7646 .await;
7647
7648 let language = Language::new(
7649 LanguageConfig {
7650 name: "Rust".into(),
7651 brackets: BracketPairConfig {
7652 pairs: vec![
7653 BracketPair {
7654 start: "{".to_string(),
7655 end: "}".to_string(),
7656 close: true,
7657 surround: true,
7658 newline: true,
7659 },
7660 BracketPair {
7661 start: "(".to_string(),
7662 end: ")".to_string(),
7663 close: true,
7664 surround: true,
7665 newline: true,
7666 },
7667 BracketPair {
7668 start: "/*".to_string(),
7669 end: " */".to_string(),
7670 close: true,
7671 surround: true,
7672 newline: true,
7673 },
7674 BracketPair {
7675 start: "[".to_string(),
7676 end: "]".to_string(),
7677 close: false,
7678 surround: false,
7679 newline: true,
7680 },
7681 BracketPair {
7682 start: "\"".to_string(),
7683 end: "\"".to_string(),
7684 close: true,
7685 surround: true,
7686 newline: false,
7687 },
7688 BracketPair {
7689 start: "<".to_string(),
7690 end: ">".to_string(),
7691 close: false,
7692 surround: true,
7693 newline: true,
7694 },
7695 ],
7696 ..Default::default()
7697 },
7698 autoclose_before: "})]".to_string(),
7699 ..Default::default()
7700 },
7701 Some(tree_sitter_rust::LANGUAGE.into()),
7702 );
7703 let language = Arc::new(language);
7704
7705 cx.language_registry().add(language.clone());
7706 cx.update_buffer(|buffer, cx| {
7707 buffer.set_language(Some(language), cx);
7708 });
7709
7710 // Ensure that signature_help is not called when no signature help is enabled.
7711 cx.set_state(
7712 &r#"
7713 fn main() {
7714 sampleˇ
7715 }
7716 "#
7717 .unindent(),
7718 );
7719 cx.update_editor(|view, cx| {
7720 view.handle_input("(", cx);
7721 });
7722 cx.assert_editor_state(
7723 &"
7724 fn main() {
7725 sample(ˇ)
7726 }
7727 "
7728 .unindent(),
7729 );
7730 cx.editor(|editor, _| {
7731 assert!(editor.signature_help_state.task().is_none());
7732 });
7733
7734 let mocked_response = lsp::SignatureHelp {
7735 signatures: vec![lsp::SignatureInformation {
7736 label: "fn sample(param1: u8, param2: u8)".to_string(),
7737 documentation: None,
7738 parameters: Some(vec![
7739 lsp::ParameterInformation {
7740 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7741 documentation: None,
7742 },
7743 lsp::ParameterInformation {
7744 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7745 documentation: None,
7746 },
7747 ]),
7748 active_parameter: None,
7749 }],
7750 active_signature: Some(0),
7751 active_parameter: Some(0),
7752 };
7753
7754 // Ensure that signature_help is called when enabled afte edits
7755 cx.update(|cx| {
7756 cx.update_global::<SettingsStore, _>(|settings, cx| {
7757 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7758 settings.auto_signature_help = Some(false);
7759 settings.show_signature_help_after_edits = Some(true);
7760 });
7761 });
7762 });
7763 cx.set_state(
7764 &r#"
7765 fn main() {
7766 sampleˇ
7767 }
7768 "#
7769 .unindent(),
7770 );
7771 cx.update_editor(|view, cx| {
7772 view.handle_input("(", cx);
7773 });
7774 cx.assert_editor_state(
7775 &"
7776 fn main() {
7777 sample(ˇ)
7778 }
7779 "
7780 .unindent(),
7781 );
7782 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7783 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7784 .await;
7785 cx.update_editor(|editor, _| {
7786 let signature_help_state = editor.signature_help_state.popover().cloned();
7787 assert!(signature_help_state.is_some());
7788 let ParsedMarkdown {
7789 text, highlights, ..
7790 } = signature_help_state.unwrap().parsed_content;
7791 assert_eq!(text, "param1: u8, param2: u8");
7792 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7793 editor.signature_help_state = SignatureHelpState::default();
7794 });
7795
7796 // Ensure that signature_help is called when auto signature help override is enabled
7797 cx.update(|cx| {
7798 cx.update_global::<SettingsStore, _>(|settings, cx| {
7799 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7800 settings.auto_signature_help = Some(true);
7801 settings.show_signature_help_after_edits = Some(false);
7802 });
7803 });
7804 });
7805 cx.set_state(
7806 &r#"
7807 fn main() {
7808 sampleˇ
7809 }
7810 "#
7811 .unindent(),
7812 );
7813 cx.update_editor(|view, cx| {
7814 view.handle_input("(", cx);
7815 });
7816 cx.assert_editor_state(
7817 &"
7818 fn main() {
7819 sample(ˇ)
7820 }
7821 "
7822 .unindent(),
7823 );
7824 handle_signature_help_request(&mut cx, mocked_response).await;
7825 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7826 .await;
7827 cx.editor(|editor, _| {
7828 let signature_help_state = editor.signature_help_state.popover().cloned();
7829 assert!(signature_help_state.is_some());
7830 let ParsedMarkdown {
7831 text, highlights, ..
7832 } = signature_help_state.unwrap().parsed_content;
7833 assert_eq!(text, "param1: u8, param2: u8");
7834 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7835 });
7836}
7837
7838#[gpui::test]
7839async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7840 init_test(cx, |_| {});
7841 cx.update(|cx| {
7842 cx.update_global::<SettingsStore, _>(|settings, cx| {
7843 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7844 settings.auto_signature_help = Some(true);
7845 });
7846 });
7847 });
7848
7849 let mut cx = EditorLspTestContext::new_rust(
7850 lsp::ServerCapabilities {
7851 signature_help_provider: Some(lsp::SignatureHelpOptions {
7852 ..Default::default()
7853 }),
7854 ..Default::default()
7855 },
7856 cx,
7857 )
7858 .await;
7859
7860 // A test that directly calls `show_signature_help`
7861 cx.update_editor(|editor, cx| {
7862 editor.show_signature_help(&ShowSignatureHelp, cx);
7863 });
7864
7865 let mocked_response = lsp::SignatureHelp {
7866 signatures: vec![lsp::SignatureInformation {
7867 label: "fn sample(param1: u8, param2: u8)".to_string(),
7868 documentation: None,
7869 parameters: Some(vec![
7870 lsp::ParameterInformation {
7871 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7872 documentation: None,
7873 },
7874 lsp::ParameterInformation {
7875 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7876 documentation: None,
7877 },
7878 ]),
7879 active_parameter: None,
7880 }],
7881 active_signature: Some(0),
7882 active_parameter: Some(0),
7883 };
7884 handle_signature_help_request(&mut cx, mocked_response).await;
7885
7886 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7887 .await;
7888
7889 cx.editor(|editor, _| {
7890 let signature_help_state = editor.signature_help_state.popover().cloned();
7891 assert!(signature_help_state.is_some());
7892 let ParsedMarkdown {
7893 text, highlights, ..
7894 } = signature_help_state.unwrap().parsed_content;
7895 assert_eq!(text, "param1: u8, param2: u8");
7896 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7897 });
7898
7899 // When exiting outside from inside the brackets, `signature_help` is closed.
7900 cx.set_state(indoc! {"
7901 fn main() {
7902 sample(ˇ);
7903 }
7904
7905 fn sample(param1: u8, param2: u8) {}
7906 "});
7907
7908 cx.update_editor(|editor, cx| {
7909 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
7910 });
7911
7912 let mocked_response = lsp::SignatureHelp {
7913 signatures: Vec::new(),
7914 active_signature: None,
7915 active_parameter: None,
7916 };
7917 handle_signature_help_request(&mut cx, mocked_response).await;
7918
7919 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7920 .await;
7921
7922 cx.editor(|editor, _| {
7923 assert!(!editor.signature_help_state.is_shown());
7924 });
7925
7926 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
7927 cx.set_state(indoc! {"
7928 fn main() {
7929 sample(ˇ);
7930 }
7931
7932 fn sample(param1: u8, param2: u8) {}
7933 "});
7934
7935 let mocked_response = lsp::SignatureHelp {
7936 signatures: vec![lsp::SignatureInformation {
7937 label: "fn sample(param1: u8, param2: u8)".to_string(),
7938 documentation: None,
7939 parameters: Some(vec![
7940 lsp::ParameterInformation {
7941 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7942 documentation: None,
7943 },
7944 lsp::ParameterInformation {
7945 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7946 documentation: None,
7947 },
7948 ]),
7949 active_parameter: None,
7950 }],
7951 active_signature: Some(0),
7952 active_parameter: Some(0),
7953 };
7954 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7955 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7956 .await;
7957 cx.editor(|editor, _| {
7958 assert!(editor.signature_help_state.is_shown());
7959 });
7960
7961 // Restore the popover with more parameter input
7962 cx.set_state(indoc! {"
7963 fn main() {
7964 sample(param1, param2ˇ);
7965 }
7966
7967 fn sample(param1: u8, param2: u8) {}
7968 "});
7969
7970 let mocked_response = lsp::SignatureHelp {
7971 signatures: vec![lsp::SignatureInformation {
7972 label: "fn sample(param1: u8, param2: u8)".to_string(),
7973 documentation: None,
7974 parameters: Some(vec![
7975 lsp::ParameterInformation {
7976 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7977 documentation: None,
7978 },
7979 lsp::ParameterInformation {
7980 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7981 documentation: None,
7982 },
7983 ]),
7984 active_parameter: None,
7985 }],
7986 active_signature: Some(0),
7987 active_parameter: Some(1),
7988 };
7989 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7990 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7991 .await;
7992
7993 // When selecting a range, the popover is gone.
7994 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
7995 cx.update_editor(|editor, cx| {
7996 editor.change_selections(None, cx, |s| {
7997 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
7998 })
7999 });
8000 cx.assert_editor_state(indoc! {"
8001 fn main() {
8002 sample(param1, «ˇparam2»);
8003 }
8004
8005 fn sample(param1: u8, param2: u8) {}
8006 "});
8007 cx.editor(|editor, _| {
8008 assert!(!editor.signature_help_state.is_shown());
8009 });
8010
8011 // When unselecting again, the popover is back if within the brackets.
8012 cx.update_editor(|editor, cx| {
8013 editor.change_selections(None, cx, |s| {
8014 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8015 })
8016 });
8017 cx.assert_editor_state(indoc! {"
8018 fn main() {
8019 sample(param1, ˇparam2);
8020 }
8021
8022 fn sample(param1: u8, param2: u8) {}
8023 "});
8024 handle_signature_help_request(&mut cx, mocked_response).await;
8025 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8026 .await;
8027 cx.editor(|editor, _| {
8028 assert!(editor.signature_help_state.is_shown());
8029 });
8030
8031 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8032 cx.update_editor(|editor, cx| {
8033 editor.change_selections(None, cx, |s| {
8034 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8035 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8036 })
8037 });
8038 cx.assert_editor_state(indoc! {"
8039 fn main() {
8040 sample(param1, ˇparam2);
8041 }
8042
8043 fn sample(param1: u8, param2: u8) {}
8044 "});
8045
8046 let mocked_response = lsp::SignatureHelp {
8047 signatures: vec![lsp::SignatureInformation {
8048 label: "fn sample(param1: u8, param2: u8)".to_string(),
8049 documentation: None,
8050 parameters: Some(vec![
8051 lsp::ParameterInformation {
8052 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8053 documentation: None,
8054 },
8055 lsp::ParameterInformation {
8056 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8057 documentation: None,
8058 },
8059 ]),
8060 active_parameter: None,
8061 }],
8062 active_signature: Some(0),
8063 active_parameter: Some(1),
8064 };
8065 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8066 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8067 .await;
8068 cx.update_editor(|editor, cx| {
8069 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8070 });
8071 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8072 .await;
8073 cx.update_editor(|editor, cx| {
8074 editor.change_selections(None, cx, |s| {
8075 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8076 })
8077 });
8078 cx.assert_editor_state(indoc! {"
8079 fn main() {
8080 sample(param1, «ˇparam2»);
8081 }
8082
8083 fn sample(param1: u8, param2: u8) {}
8084 "});
8085 cx.update_editor(|editor, cx| {
8086 editor.change_selections(None, cx, |s| {
8087 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8088 })
8089 });
8090 cx.assert_editor_state(indoc! {"
8091 fn main() {
8092 sample(param1, ˇparam2);
8093 }
8094
8095 fn sample(param1: u8, param2: u8) {}
8096 "});
8097 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8098 .await;
8099}
8100
8101#[gpui::test]
8102async fn test_completion(cx: &mut gpui::TestAppContext) {
8103 init_test(cx, |_| {});
8104
8105 let mut cx = EditorLspTestContext::new_rust(
8106 lsp::ServerCapabilities {
8107 completion_provider: Some(lsp::CompletionOptions {
8108 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8109 resolve_provider: Some(true),
8110 ..Default::default()
8111 }),
8112 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8113 ..Default::default()
8114 },
8115 cx,
8116 )
8117 .await;
8118 let counter = Arc::new(AtomicUsize::new(0));
8119
8120 cx.set_state(indoc! {"
8121 oneˇ
8122 two
8123 three
8124 "});
8125 cx.simulate_keystroke(".");
8126 handle_completion_request(
8127 &mut cx,
8128 indoc! {"
8129 one.|<>
8130 two
8131 three
8132 "},
8133 vec!["first_completion", "second_completion"],
8134 counter.clone(),
8135 )
8136 .await;
8137 cx.condition(|editor, _| editor.context_menu_visible())
8138 .await;
8139 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8140
8141 let _handler = handle_signature_help_request(
8142 &mut cx,
8143 lsp::SignatureHelp {
8144 signatures: vec![lsp::SignatureInformation {
8145 label: "test signature".to_string(),
8146 documentation: None,
8147 parameters: Some(vec![lsp::ParameterInformation {
8148 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8149 documentation: None,
8150 }]),
8151 active_parameter: None,
8152 }],
8153 active_signature: None,
8154 active_parameter: None,
8155 },
8156 );
8157 cx.update_editor(|editor, cx| {
8158 assert!(
8159 !editor.signature_help_state.is_shown(),
8160 "No signature help was called for"
8161 );
8162 editor.show_signature_help(&ShowSignatureHelp, cx);
8163 });
8164 cx.run_until_parked();
8165 cx.update_editor(|editor, _| {
8166 assert!(
8167 !editor.signature_help_state.is_shown(),
8168 "No signature help should be shown when completions menu is open"
8169 );
8170 });
8171
8172 let apply_additional_edits = cx.update_editor(|editor, cx| {
8173 editor.context_menu_next(&Default::default(), cx);
8174 editor
8175 .confirm_completion(&ConfirmCompletion::default(), cx)
8176 .unwrap()
8177 });
8178 cx.assert_editor_state(indoc! {"
8179 one.second_completionˇ
8180 two
8181 three
8182 "});
8183
8184 handle_resolve_completion_request(
8185 &mut cx,
8186 Some(vec![
8187 (
8188 //This overlaps with the primary completion edit which is
8189 //misbehavior from the LSP spec, test that we filter it out
8190 indoc! {"
8191 one.second_ˇcompletion
8192 two
8193 threeˇ
8194 "},
8195 "overlapping additional edit",
8196 ),
8197 (
8198 indoc! {"
8199 one.second_completion
8200 two
8201 threeˇ
8202 "},
8203 "\nadditional edit",
8204 ),
8205 ]),
8206 )
8207 .await;
8208 apply_additional_edits.await.unwrap();
8209 cx.assert_editor_state(indoc! {"
8210 one.second_completionˇ
8211 two
8212 three
8213 additional edit
8214 "});
8215
8216 cx.set_state(indoc! {"
8217 one.second_completion
8218 twoˇ
8219 threeˇ
8220 additional edit
8221 "});
8222 cx.simulate_keystroke(" ");
8223 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8224 cx.simulate_keystroke("s");
8225 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8226
8227 cx.assert_editor_state(indoc! {"
8228 one.second_completion
8229 two sˇ
8230 three sˇ
8231 additional edit
8232 "});
8233 handle_completion_request(
8234 &mut cx,
8235 indoc! {"
8236 one.second_completion
8237 two s
8238 three <s|>
8239 additional edit
8240 "},
8241 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8242 counter.clone(),
8243 )
8244 .await;
8245 cx.condition(|editor, _| editor.context_menu_visible())
8246 .await;
8247 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8248
8249 cx.simulate_keystroke("i");
8250
8251 handle_completion_request(
8252 &mut cx,
8253 indoc! {"
8254 one.second_completion
8255 two si
8256 three <si|>
8257 additional edit
8258 "},
8259 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8260 counter.clone(),
8261 )
8262 .await;
8263 cx.condition(|editor, _| editor.context_menu_visible())
8264 .await;
8265 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8266
8267 let apply_additional_edits = cx.update_editor(|editor, cx| {
8268 editor
8269 .confirm_completion(&ConfirmCompletion::default(), cx)
8270 .unwrap()
8271 });
8272 cx.assert_editor_state(indoc! {"
8273 one.second_completion
8274 two sixth_completionˇ
8275 three sixth_completionˇ
8276 additional edit
8277 "});
8278
8279 handle_resolve_completion_request(&mut cx, None).await;
8280 apply_additional_edits.await.unwrap();
8281
8282 cx.update(|cx| {
8283 cx.update_global::<SettingsStore, _>(|settings, cx| {
8284 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8285 settings.show_completions_on_input = Some(false);
8286 });
8287 })
8288 });
8289 cx.set_state("editorˇ");
8290 cx.simulate_keystroke(".");
8291 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8292 cx.simulate_keystroke("c");
8293 cx.simulate_keystroke("l");
8294 cx.simulate_keystroke("o");
8295 cx.assert_editor_state("editor.cloˇ");
8296 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8297 cx.update_editor(|editor, cx| {
8298 editor.show_completions(&ShowCompletions { trigger: None }, cx);
8299 });
8300 handle_completion_request(
8301 &mut cx,
8302 "editor.<clo|>",
8303 vec!["close", "clobber"],
8304 counter.clone(),
8305 )
8306 .await;
8307 cx.condition(|editor, _| editor.context_menu_visible())
8308 .await;
8309 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8310
8311 let apply_additional_edits = cx.update_editor(|editor, cx| {
8312 editor
8313 .confirm_completion(&ConfirmCompletion::default(), cx)
8314 .unwrap()
8315 });
8316 cx.assert_editor_state("editor.closeˇ");
8317 handle_resolve_completion_request(&mut cx, None).await;
8318 apply_additional_edits.await.unwrap();
8319}
8320
8321#[gpui::test]
8322async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8323 init_test(cx, |_| {});
8324 let mut cx = EditorLspTestContext::new_rust(
8325 lsp::ServerCapabilities {
8326 completion_provider: Some(lsp::CompletionOptions {
8327 trigger_characters: Some(vec![".".to_string()]),
8328 ..Default::default()
8329 }),
8330 ..Default::default()
8331 },
8332 cx,
8333 )
8334 .await;
8335 cx.lsp
8336 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8337 Ok(Some(lsp::CompletionResponse::Array(vec![
8338 lsp::CompletionItem {
8339 label: "first".into(),
8340 ..Default::default()
8341 },
8342 lsp::CompletionItem {
8343 label: "last".into(),
8344 ..Default::default()
8345 },
8346 ])))
8347 });
8348 cx.set_state("variableˇ");
8349 cx.simulate_keystroke(".");
8350 cx.executor().run_until_parked();
8351
8352 cx.update_editor(|editor, _| {
8353 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8354 assert_eq!(
8355 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8356 &["first", "last"]
8357 );
8358 } else {
8359 panic!("expected completion menu to be open");
8360 }
8361 });
8362
8363 cx.update_editor(|editor, cx| {
8364 editor.move_page_down(&MovePageDown::default(), cx);
8365 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8366 assert!(
8367 menu.selected_item == 1,
8368 "expected PageDown to select the last item from the context menu"
8369 );
8370 } else {
8371 panic!("expected completion menu to stay open after PageDown");
8372 }
8373 });
8374
8375 cx.update_editor(|editor, cx| {
8376 editor.move_page_up(&MovePageUp::default(), cx);
8377 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8378 assert!(
8379 menu.selected_item == 0,
8380 "expected PageUp to select the first item from the context menu"
8381 );
8382 } else {
8383 panic!("expected completion menu to stay open after PageUp");
8384 }
8385 });
8386}
8387
8388#[gpui::test]
8389async fn test_completion_sort(cx: &mut gpui::TestAppContext) {
8390 init_test(cx, |_| {});
8391 let mut cx = EditorLspTestContext::new_rust(
8392 lsp::ServerCapabilities {
8393 completion_provider: Some(lsp::CompletionOptions {
8394 trigger_characters: Some(vec![".".to_string()]),
8395 ..Default::default()
8396 }),
8397 ..Default::default()
8398 },
8399 cx,
8400 )
8401 .await;
8402 cx.lsp
8403 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8404 Ok(Some(lsp::CompletionResponse::Array(vec![
8405 lsp::CompletionItem {
8406 label: "Range".into(),
8407 sort_text: Some("a".into()),
8408 ..Default::default()
8409 },
8410 lsp::CompletionItem {
8411 label: "r".into(),
8412 sort_text: Some("b".into()),
8413 ..Default::default()
8414 },
8415 lsp::CompletionItem {
8416 label: "ret".into(),
8417 sort_text: Some("c".into()),
8418 ..Default::default()
8419 },
8420 lsp::CompletionItem {
8421 label: "return".into(),
8422 sort_text: Some("d".into()),
8423 ..Default::default()
8424 },
8425 lsp::CompletionItem {
8426 label: "slice".into(),
8427 sort_text: Some("d".into()),
8428 ..Default::default()
8429 },
8430 ])))
8431 });
8432 cx.set_state("rˇ");
8433 cx.executor().run_until_parked();
8434 cx.update_editor(|editor, cx| {
8435 editor.show_completions(
8436 &ShowCompletions {
8437 trigger: Some("r".into()),
8438 },
8439 cx,
8440 );
8441 });
8442 cx.executor().run_until_parked();
8443
8444 cx.update_editor(|editor, _| {
8445 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8446 assert_eq!(
8447 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8448 &["r", "ret", "Range", "return"]
8449 );
8450 } else {
8451 panic!("expected completion menu to be open");
8452 }
8453 });
8454}
8455
8456#[gpui::test]
8457async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8458 init_test(cx, |_| {});
8459
8460 let mut cx = EditorLspTestContext::new_rust(
8461 lsp::ServerCapabilities {
8462 completion_provider: Some(lsp::CompletionOptions {
8463 trigger_characters: Some(vec![".".to_string()]),
8464 resolve_provider: Some(true),
8465 ..Default::default()
8466 }),
8467 ..Default::default()
8468 },
8469 cx,
8470 )
8471 .await;
8472
8473 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8474 cx.simulate_keystroke(".");
8475 let completion_item = lsp::CompletionItem {
8476 label: "Some".into(),
8477 kind: Some(lsp::CompletionItemKind::SNIPPET),
8478 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8479 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8480 kind: lsp::MarkupKind::Markdown,
8481 value: "```rust\nSome(2)\n```".to_string(),
8482 })),
8483 deprecated: Some(false),
8484 sort_text: Some("Some".to_string()),
8485 filter_text: Some("Some".to_string()),
8486 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8487 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8488 range: lsp::Range {
8489 start: lsp::Position {
8490 line: 0,
8491 character: 22,
8492 },
8493 end: lsp::Position {
8494 line: 0,
8495 character: 22,
8496 },
8497 },
8498 new_text: "Some(2)".to_string(),
8499 })),
8500 additional_text_edits: Some(vec![lsp::TextEdit {
8501 range: lsp::Range {
8502 start: lsp::Position {
8503 line: 0,
8504 character: 20,
8505 },
8506 end: lsp::Position {
8507 line: 0,
8508 character: 22,
8509 },
8510 },
8511 new_text: "".to_string(),
8512 }]),
8513 ..Default::default()
8514 };
8515
8516 let closure_completion_item = completion_item.clone();
8517 let counter = Arc::new(AtomicUsize::new(0));
8518 let counter_clone = counter.clone();
8519 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8520 let task_completion_item = closure_completion_item.clone();
8521 counter_clone.fetch_add(1, atomic::Ordering::Release);
8522 async move {
8523 Ok(Some(lsp::CompletionResponse::Array(vec![
8524 task_completion_item,
8525 ])))
8526 }
8527 });
8528
8529 cx.condition(|editor, _| editor.context_menu_visible())
8530 .await;
8531 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8532 assert!(request.next().await.is_some());
8533 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8534
8535 cx.simulate_keystroke("S");
8536 cx.simulate_keystroke("o");
8537 cx.simulate_keystroke("m");
8538 cx.condition(|editor, _| editor.context_menu_visible())
8539 .await;
8540 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
8541 assert!(request.next().await.is_some());
8542 assert!(request.next().await.is_some());
8543 assert!(request.next().await.is_some());
8544 request.close();
8545 assert!(request.next().await.is_none());
8546 assert_eq!(
8547 counter.load(atomic::Ordering::Acquire),
8548 4,
8549 "With the completions menu open, only one LSP request should happen per input"
8550 );
8551}
8552
8553#[gpui::test]
8554async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
8555 init_test(cx, |_| {});
8556 let mut cx = EditorTestContext::new(cx).await;
8557 let language = Arc::new(Language::new(
8558 LanguageConfig {
8559 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8560 ..Default::default()
8561 },
8562 Some(tree_sitter_rust::LANGUAGE.into()),
8563 ));
8564 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8565
8566 // If multiple selections intersect a line, the line is only toggled once.
8567 cx.set_state(indoc! {"
8568 fn a() {
8569 «//b();
8570 ˇ»// «c();
8571 //ˇ» d();
8572 }
8573 "});
8574
8575 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8576
8577 cx.assert_editor_state(indoc! {"
8578 fn a() {
8579 «b();
8580 c();
8581 ˇ» d();
8582 }
8583 "});
8584
8585 // The comment prefix is inserted at the same column for every line in a
8586 // selection.
8587 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8588
8589 cx.assert_editor_state(indoc! {"
8590 fn a() {
8591 // «b();
8592 // c();
8593 ˇ»// d();
8594 }
8595 "});
8596
8597 // If a selection ends at the beginning of a line, that line is not toggled.
8598 cx.set_selections_state(indoc! {"
8599 fn a() {
8600 // b();
8601 «// c();
8602 ˇ» // d();
8603 }
8604 "});
8605
8606 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8607
8608 cx.assert_editor_state(indoc! {"
8609 fn a() {
8610 // b();
8611 «c();
8612 ˇ» // d();
8613 }
8614 "});
8615
8616 // If a selection span a single line and is empty, the line is toggled.
8617 cx.set_state(indoc! {"
8618 fn a() {
8619 a();
8620 b();
8621 ˇ
8622 }
8623 "});
8624
8625 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8626
8627 cx.assert_editor_state(indoc! {"
8628 fn a() {
8629 a();
8630 b();
8631 //•ˇ
8632 }
8633 "});
8634
8635 // If a selection span multiple lines, empty lines are not toggled.
8636 cx.set_state(indoc! {"
8637 fn a() {
8638 «a();
8639
8640 c();ˇ»
8641 }
8642 "});
8643
8644 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8645
8646 cx.assert_editor_state(indoc! {"
8647 fn a() {
8648 // «a();
8649
8650 // c();ˇ»
8651 }
8652 "});
8653
8654 // If a selection includes multiple comment prefixes, all lines are uncommented.
8655 cx.set_state(indoc! {"
8656 fn a() {
8657 «// a();
8658 /// b();
8659 //! c();ˇ»
8660 }
8661 "});
8662
8663 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8664
8665 cx.assert_editor_state(indoc! {"
8666 fn a() {
8667 «a();
8668 b();
8669 c();ˇ»
8670 }
8671 "});
8672}
8673
8674#[gpui::test]
8675async fn test_toggle_comment_ignore_indent(cx: &mut gpui::TestAppContext) {
8676 init_test(cx, |_| {});
8677 let mut cx = EditorTestContext::new(cx).await;
8678 let language = Arc::new(Language::new(
8679 LanguageConfig {
8680 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8681 ..Default::default()
8682 },
8683 Some(tree_sitter_rust::LANGUAGE.into()),
8684 ));
8685 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8686
8687 let toggle_comments = &ToggleComments {
8688 advance_downwards: false,
8689 ignore_indent: true,
8690 };
8691
8692 // If multiple selections intersect a line, the line is only toggled once.
8693 cx.set_state(indoc! {"
8694 fn a() {
8695 // «b();
8696 // c();
8697 // ˇ» d();
8698 }
8699 "});
8700
8701 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8702
8703 cx.assert_editor_state(indoc! {"
8704 fn a() {
8705 «b();
8706 c();
8707 ˇ» d();
8708 }
8709 "});
8710
8711 // The comment prefix is inserted at the beginning of each line
8712 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8713
8714 cx.assert_editor_state(indoc! {"
8715 fn a() {
8716 // «b();
8717 // c();
8718 // ˇ» d();
8719 }
8720 "});
8721
8722 // If a selection ends at the beginning of a line, that line is not toggled.
8723 cx.set_selections_state(indoc! {"
8724 fn a() {
8725 // b();
8726 // «c();
8727 ˇ»// d();
8728 }
8729 "});
8730
8731 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8732
8733 cx.assert_editor_state(indoc! {"
8734 fn a() {
8735 // b();
8736 «c();
8737 ˇ»// d();
8738 }
8739 "});
8740
8741 // If a selection span a single line and is empty, the line is toggled.
8742 cx.set_state(indoc! {"
8743 fn a() {
8744 a();
8745 b();
8746 ˇ
8747 }
8748 "});
8749
8750 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8751
8752 cx.assert_editor_state(indoc! {"
8753 fn a() {
8754 a();
8755 b();
8756 //ˇ
8757 }
8758 "});
8759
8760 // If a selection span multiple lines, empty lines are not toggled.
8761 cx.set_state(indoc! {"
8762 fn a() {
8763 «a();
8764
8765 c();ˇ»
8766 }
8767 "});
8768
8769 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8770
8771 cx.assert_editor_state(indoc! {"
8772 fn a() {
8773 // «a();
8774
8775 // c();ˇ»
8776 }
8777 "});
8778
8779 // If a selection includes multiple comment prefixes, all lines are uncommented.
8780 cx.set_state(indoc! {"
8781 fn a() {
8782 // «a();
8783 /// b();
8784 //! c();ˇ»
8785 }
8786 "});
8787
8788 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8789
8790 cx.assert_editor_state(indoc! {"
8791 fn a() {
8792 «a();
8793 b();
8794 c();ˇ»
8795 }
8796 "});
8797}
8798
8799#[gpui::test]
8800async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8801 init_test(cx, |_| {});
8802
8803 let language = Arc::new(Language::new(
8804 LanguageConfig {
8805 line_comments: vec!["// ".into()],
8806 ..Default::default()
8807 },
8808 Some(tree_sitter_rust::LANGUAGE.into()),
8809 ));
8810
8811 let mut cx = EditorTestContext::new(cx).await;
8812
8813 cx.language_registry().add(language.clone());
8814 cx.update_buffer(|buffer, cx| {
8815 buffer.set_language(Some(language), cx);
8816 });
8817
8818 let toggle_comments = &ToggleComments {
8819 advance_downwards: true,
8820 ignore_indent: false,
8821 };
8822
8823 // Single cursor on one line -> advance
8824 // Cursor moves horizontally 3 characters as well on non-blank line
8825 cx.set_state(indoc!(
8826 "fn a() {
8827 ˇdog();
8828 cat();
8829 }"
8830 ));
8831 cx.update_editor(|editor, cx| {
8832 editor.toggle_comments(toggle_comments, cx);
8833 });
8834 cx.assert_editor_state(indoc!(
8835 "fn a() {
8836 // dog();
8837 catˇ();
8838 }"
8839 ));
8840
8841 // Single selection on one line -> don't advance
8842 cx.set_state(indoc!(
8843 "fn a() {
8844 «dog()ˇ»;
8845 cat();
8846 }"
8847 ));
8848 cx.update_editor(|editor, cx| {
8849 editor.toggle_comments(toggle_comments, cx);
8850 });
8851 cx.assert_editor_state(indoc!(
8852 "fn a() {
8853 // «dog()ˇ»;
8854 cat();
8855 }"
8856 ));
8857
8858 // Multiple cursors on one line -> advance
8859 cx.set_state(indoc!(
8860 "fn a() {
8861 ˇdˇog();
8862 cat();
8863 }"
8864 ));
8865 cx.update_editor(|editor, cx| {
8866 editor.toggle_comments(toggle_comments, cx);
8867 });
8868 cx.assert_editor_state(indoc!(
8869 "fn a() {
8870 // dog();
8871 catˇ(ˇ);
8872 }"
8873 ));
8874
8875 // Multiple cursors on one line, with selection -> don't advance
8876 cx.set_state(indoc!(
8877 "fn a() {
8878 ˇdˇog«()ˇ»;
8879 cat();
8880 }"
8881 ));
8882 cx.update_editor(|editor, cx| {
8883 editor.toggle_comments(toggle_comments, cx);
8884 });
8885 cx.assert_editor_state(indoc!(
8886 "fn a() {
8887 // ˇdˇog«()ˇ»;
8888 cat();
8889 }"
8890 ));
8891
8892 // Single cursor on one line -> advance
8893 // Cursor moves to column 0 on blank line
8894 cx.set_state(indoc!(
8895 "fn a() {
8896 ˇdog();
8897
8898 cat();
8899 }"
8900 ));
8901 cx.update_editor(|editor, cx| {
8902 editor.toggle_comments(toggle_comments, cx);
8903 });
8904 cx.assert_editor_state(indoc!(
8905 "fn a() {
8906 // dog();
8907 ˇ
8908 cat();
8909 }"
8910 ));
8911
8912 // Single cursor on one line -> advance
8913 // Cursor starts and ends at column 0
8914 cx.set_state(indoc!(
8915 "fn a() {
8916 ˇ dog();
8917 cat();
8918 }"
8919 ));
8920 cx.update_editor(|editor, cx| {
8921 editor.toggle_comments(toggle_comments, cx);
8922 });
8923 cx.assert_editor_state(indoc!(
8924 "fn a() {
8925 // dog();
8926 ˇ cat();
8927 }"
8928 ));
8929}
8930
8931#[gpui::test]
8932async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
8933 init_test(cx, |_| {});
8934
8935 let mut cx = EditorTestContext::new(cx).await;
8936
8937 let html_language = Arc::new(
8938 Language::new(
8939 LanguageConfig {
8940 name: "HTML".into(),
8941 block_comment: Some(("<!-- ".into(), " -->".into())),
8942 ..Default::default()
8943 },
8944 Some(tree_sitter_html::language()),
8945 )
8946 .with_injection_query(
8947 r#"
8948 (script_element
8949 (raw_text) @content
8950 (#set! "language" "javascript"))
8951 "#,
8952 )
8953 .unwrap(),
8954 );
8955
8956 let javascript_language = Arc::new(Language::new(
8957 LanguageConfig {
8958 name: "JavaScript".into(),
8959 line_comments: vec!["// ".into()],
8960 ..Default::default()
8961 },
8962 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
8963 ));
8964
8965 cx.language_registry().add(html_language.clone());
8966 cx.language_registry().add(javascript_language.clone());
8967 cx.update_buffer(|buffer, cx| {
8968 buffer.set_language(Some(html_language), cx);
8969 });
8970
8971 // Toggle comments for empty selections
8972 cx.set_state(
8973 &r#"
8974 <p>A</p>ˇ
8975 <p>B</p>ˇ
8976 <p>C</p>ˇ
8977 "#
8978 .unindent(),
8979 );
8980 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8981 cx.assert_editor_state(
8982 &r#"
8983 <!-- <p>A</p>ˇ -->
8984 <!-- <p>B</p>ˇ -->
8985 <!-- <p>C</p>ˇ -->
8986 "#
8987 .unindent(),
8988 );
8989 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8990 cx.assert_editor_state(
8991 &r#"
8992 <p>A</p>ˇ
8993 <p>B</p>ˇ
8994 <p>C</p>ˇ
8995 "#
8996 .unindent(),
8997 );
8998
8999 // Toggle comments for mixture of empty and non-empty selections, where
9000 // multiple selections occupy a given line.
9001 cx.set_state(
9002 &r#"
9003 <p>A«</p>
9004 <p>ˇ»B</p>ˇ
9005 <p>C«</p>
9006 <p>ˇ»D</p>ˇ
9007 "#
9008 .unindent(),
9009 );
9010
9011 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9012 cx.assert_editor_state(
9013 &r#"
9014 <!-- <p>A«</p>
9015 <p>ˇ»B</p>ˇ -->
9016 <!-- <p>C«</p>
9017 <p>ˇ»D</p>ˇ -->
9018 "#
9019 .unindent(),
9020 );
9021 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9022 cx.assert_editor_state(
9023 &r#"
9024 <p>A«</p>
9025 <p>ˇ»B</p>ˇ
9026 <p>C«</p>
9027 <p>ˇ»D</p>ˇ
9028 "#
9029 .unindent(),
9030 );
9031
9032 // Toggle comments when different languages are active for different
9033 // selections.
9034 cx.set_state(
9035 &r#"
9036 ˇ<script>
9037 ˇvar x = new Y();
9038 ˇ</script>
9039 "#
9040 .unindent(),
9041 );
9042 cx.executor().run_until_parked();
9043 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9044 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
9045 // Uncommenting and commenting from this position brings in even more wrong artifacts.
9046 cx.assert_editor_state(
9047 &r#"
9048 <!-- ˇ<script> -->
9049 // ˇvar x = new Y();
9050 // ˇ</script>
9051 "#
9052 .unindent(),
9053 );
9054}
9055
9056#[gpui::test]
9057fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
9058 init_test(cx, |_| {});
9059
9060 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9061 let multibuffer = cx.new_model(|cx| {
9062 let mut multibuffer = MultiBuffer::new(ReadWrite);
9063 multibuffer.push_excerpts(
9064 buffer.clone(),
9065 [
9066 ExcerptRange {
9067 context: Point::new(0, 0)..Point::new(0, 4),
9068 primary: None,
9069 },
9070 ExcerptRange {
9071 context: Point::new(1, 0)..Point::new(1, 4),
9072 primary: None,
9073 },
9074 ],
9075 cx,
9076 );
9077 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
9078 multibuffer
9079 });
9080
9081 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9082 view.update(cx, |view, cx| {
9083 assert_eq!(view.text(cx), "aaaa\nbbbb");
9084 view.change_selections(None, cx, |s| {
9085 s.select_ranges([
9086 Point::new(0, 0)..Point::new(0, 0),
9087 Point::new(1, 0)..Point::new(1, 0),
9088 ])
9089 });
9090
9091 view.handle_input("X", cx);
9092 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
9093 assert_eq!(
9094 view.selections.ranges(cx),
9095 [
9096 Point::new(0, 1)..Point::new(0, 1),
9097 Point::new(1, 1)..Point::new(1, 1),
9098 ]
9099 );
9100
9101 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
9102 view.change_selections(None, cx, |s| {
9103 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
9104 });
9105 view.backspace(&Default::default(), cx);
9106 assert_eq!(view.text(cx), "Xa\nbbb");
9107 assert_eq!(
9108 view.selections.ranges(cx),
9109 [Point::new(1, 0)..Point::new(1, 0)]
9110 );
9111
9112 view.change_selections(None, cx, |s| {
9113 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
9114 });
9115 view.backspace(&Default::default(), cx);
9116 assert_eq!(view.text(cx), "X\nbb");
9117 assert_eq!(
9118 view.selections.ranges(cx),
9119 [Point::new(0, 1)..Point::new(0, 1)]
9120 );
9121 });
9122}
9123
9124#[gpui::test]
9125fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
9126 init_test(cx, |_| {});
9127
9128 let markers = vec![('[', ']').into(), ('(', ')').into()];
9129 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
9130 indoc! {"
9131 [aaaa
9132 (bbbb]
9133 cccc)",
9134 },
9135 markers.clone(),
9136 );
9137 let excerpt_ranges = markers.into_iter().map(|marker| {
9138 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9139 ExcerptRange {
9140 context,
9141 primary: None,
9142 }
9143 });
9144 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
9145 let multibuffer = cx.new_model(|cx| {
9146 let mut multibuffer = MultiBuffer::new(ReadWrite);
9147 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9148 multibuffer
9149 });
9150
9151 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9152 view.update(cx, |view, cx| {
9153 let (expected_text, selection_ranges) = marked_text_ranges(
9154 indoc! {"
9155 aaaa
9156 bˇbbb
9157 bˇbbˇb
9158 cccc"
9159 },
9160 true,
9161 );
9162 assert_eq!(view.text(cx), expected_text);
9163 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
9164
9165 view.handle_input("X", cx);
9166
9167 let (expected_text, expected_selections) = marked_text_ranges(
9168 indoc! {"
9169 aaaa
9170 bXˇbbXb
9171 bXˇbbXˇb
9172 cccc"
9173 },
9174 false,
9175 );
9176 assert_eq!(view.text(cx), expected_text);
9177 assert_eq!(view.selections.ranges(cx), expected_selections);
9178
9179 view.newline(&Newline, cx);
9180 let (expected_text, expected_selections) = marked_text_ranges(
9181 indoc! {"
9182 aaaa
9183 bX
9184 ˇbbX
9185 b
9186 bX
9187 ˇbbX
9188 ˇb
9189 cccc"
9190 },
9191 false,
9192 );
9193 assert_eq!(view.text(cx), expected_text);
9194 assert_eq!(view.selections.ranges(cx), expected_selections);
9195 });
9196}
9197
9198#[gpui::test]
9199fn test_refresh_selections(cx: &mut TestAppContext) {
9200 init_test(cx, |_| {});
9201
9202 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9203 let mut excerpt1_id = None;
9204 let multibuffer = cx.new_model(|cx| {
9205 let mut multibuffer = MultiBuffer::new(ReadWrite);
9206 excerpt1_id = multibuffer
9207 .push_excerpts(
9208 buffer.clone(),
9209 [
9210 ExcerptRange {
9211 context: Point::new(0, 0)..Point::new(1, 4),
9212 primary: None,
9213 },
9214 ExcerptRange {
9215 context: Point::new(1, 0)..Point::new(2, 4),
9216 primary: None,
9217 },
9218 ],
9219 cx,
9220 )
9221 .into_iter()
9222 .next();
9223 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9224 multibuffer
9225 });
9226
9227 let editor = cx.add_window(|cx| {
9228 let mut editor = build_editor(multibuffer.clone(), cx);
9229 let snapshot = editor.snapshot(cx);
9230 editor.change_selections(None, cx, |s| {
9231 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
9232 });
9233 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
9234 assert_eq!(
9235 editor.selections.ranges(cx),
9236 [
9237 Point::new(1, 3)..Point::new(1, 3),
9238 Point::new(2, 1)..Point::new(2, 1),
9239 ]
9240 );
9241 editor
9242 });
9243
9244 // Refreshing selections is a no-op when excerpts haven't changed.
9245 _ = editor.update(cx, |editor, cx| {
9246 editor.change_selections(None, cx, |s| s.refresh());
9247 assert_eq!(
9248 editor.selections.ranges(cx),
9249 [
9250 Point::new(1, 3)..Point::new(1, 3),
9251 Point::new(2, 1)..Point::new(2, 1),
9252 ]
9253 );
9254 });
9255
9256 multibuffer.update(cx, |multibuffer, cx| {
9257 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9258 });
9259 _ = editor.update(cx, |editor, cx| {
9260 // Removing an excerpt causes the first selection to become degenerate.
9261 assert_eq!(
9262 editor.selections.ranges(cx),
9263 [
9264 Point::new(0, 0)..Point::new(0, 0),
9265 Point::new(0, 1)..Point::new(0, 1)
9266 ]
9267 );
9268
9269 // Refreshing selections will relocate the first selection to the original buffer
9270 // location.
9271 editor.change_selections(None, cx, |s| s.refresh());
9272 assert_eq!(
9273 editor.selections.ranges(cx),
9274 [
9275 Point::new(0, 1)..Point::new(0, 1),
9276 Point::new(0, 3)..Point::new(0, 3)
9277 ]
9278 );
9279 assert!(editor.selections.pending_anchor().is_some());
9280 });
9281}
9282
9283#[gpui::test]
9284fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
9285 init_test(cx, |_| {});
9286
9287 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9288 let mut excerpt1_id = None;
9289 let multibuffer = cx.new_model(|cx| {
9290 let mut multibuffer = MultiBuffer::new(ReadWrite);
9291 excerpt1_id = multibuffer
9292 .push_excerpts(
9293 buffer.clone(),
9294 [
9295 ExcerptRange {
9296 context: Point::new(0, 0)..Point::new(1, 4),
9297 primary: None,
9298 },
9299 ExcerptRange {
9300 context: Point::new(1, 0)..Point::new(2, 4),
9301 primary: None,
9302 },
9303 ],
9304 cx,
9305 )
9306 .into_iter()
9307 .next();
9308 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9309 multibuffer
9310 });
9311
9312 let editor = cx.add_window(|cx| {
9313 let mut editor = build_editor(multibuffer.clone(), cx);
9314 let snapshot = editor.snapshot(cx);
9315 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
9316 assert_eq!(
9317 editor.selections.ranges(cx),
9318 [Point::new(1, 3)..Point::new(1, 3)]
9319 );
9320 editor
9321 });
9322
9323 multibuffer.update(cx, |multibuffer, cx| {
9324 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9325 });
9326 _ = editor.update(cx, |editor, cx| {
9327 assert_eq!(
9328 editor.selections.ranges(cx),
9329 [Point::new(0, 0)..Point::new(0, 0)]
9330 );
9331
9332 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
9333 editor.change_selections(None, cx, |s| s.refresh());
9334 assert_eq!(
9335 editor.selections.ranges(cx),
9336 [Point::new(0, 3)..Point::new(0, 3)]
9337 );
9338 assert!(editor.selections.pending_anchor().is_some());
9339 });
9340}
9341
9342#[gpui::test]
9343async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
9344 init_test(cx, |_| {});
9345
9346 let language = Arc::new(
9347 Language::new(
9348 LanguageConfig {
9349 brackets: BracketPairConfig {
9350 pairs: vec![
9351 BracketPair {
9352 start: "{".to_string(),
9353 end: "}".to_string(),
9354 close: true,
9355 surround: true,
9356 newline: true,
9357 },
9358 BracketPair {
9359 start: "/* ".to_string(),
9360 end: " */".to_string(),
9361 close: true,
9362 surround: true,
9363 newline: true,
9364 },
9365 ],
9366 ..Default::default()
9367 },
9368 ..Default::default()
9369 },
9370 Some(tree_sitter_rust::LANGUAGE.into()),
9371 )
9372 .with_indents_query("")
9373 .unwrap(),
9374 );
9375
9376 let text = concat!(
9377 "{ }\n", //
9378 " x\n", //
9379 " /* */\n", //
9380 "x\n", //
9381 "{{} }\n", //
9382 );
9383
9384 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
9385 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9386 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9387 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
9388 .await;
9389
9390 view.update(cx, |view, cx| {
9391 view.change_selections(None, cx, |s| {
9392 s.select_display_ranges([
9393 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
9394 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
9395 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
9396 ])
9397 });
9398 view.newline(&Newline, cx);
9399
9400 assert_eq!(
9401 view.buffer().read(cx).read(cx).text(),
9402 concat!(
9403 "{ \n", // Suppress rustfmt
9404 "\n", //
9405 "}\n", //
9406 " x\n", //
9407 " /* \n", //
9408 " \n", //
9409 " */\n", //
9410 "x\n", //
9411 "{{} \n", //
9412 "}\n", //
9413 )
9414 );
9415 });
9416}
9417
9418#[gpui::test]
9419fn test_highlighted_ranges(cx: &mut TestAppContext) {
9420 init_test(cx, |_| {});
9421
9422 let editor = cx.add_window(|cx| {
9423 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
9424 build_editor(buffer.clone(), cx)
9425 });
9426
9427 _ = editor.update(cx, |editor, cx| {
9428 struct Type1;
9429 struct Type2;
9430
9431 let buffer = editor.buffer.read(cx).snapshot(cx);
9432
9433 let anchor_range =
9434 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
9435
9436 editor.highlight_background::<Type1>(
9437 &[
9438 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
9439 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
9440 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
9441 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
9442 ],
9443 |_| Hsla::red(),
9444 cx,
9445 );
9446 editor.highlight_background::<Type2>(
9447 &[
9448 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
9449 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
9450 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
9451 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
9452 ],
9453 |_| Hsla::green(),
9454 cx,
9455 );
9456
9457 let snapshot = editor.snapshot(cx);
9458 let mut highlighted_ranges = editor.background_highlights_in_range(
9459 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
9460 &snapshot,
9461 cx.theme().colors(),
9462 );
9463 // Enforce a consistent ordering based on color without relying on the ordering of the
9464 // highlight's `TypeId` which is non-executor.
9465 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
9466 assert_eq!(
9467 highlighted_ranges,
9468 &[
9469 (
9470 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
9471 Hsla::red(),
9472 ),
9473 (
9474 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9475 Hsla::red(),
9476 ),
9477 (
9478 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9479 Hsla::green(),
9480 ),
9481 (
9482 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9483 Hsla::green(),
9484 ),
9485 ]
9486 );
9487 assert_eq!(
9488 editor.background_highlights_in_range(
9489 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9490 &snapshot,
9491 cx.theme().colors(),
9492 ),
9493 &[(
9494 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9495 Hsla::red(),
9496 )]
9497 );
9498 });
9499}
9500
9501#[gpui::test]
9502async fn test_following(cx: &mut gpui::TestAppContext) {
9503 init_test(cx, |_| {});
9504
9505 let fs = FakeFs::new(cx.executor());
9506 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9507
9508 let buffer = project.update(cx, |project, cx| {
9509 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9510 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9511 });
9512 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9513 let follower = cx.update(|cx| {
9514 cx.open_window(
9515 WindowOptions {
9516 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9517 gpui::Point::new(px(0.), px(0.)),
9518 gpui::Point::new(px(10.), px(80.)),
9519 ))),
9520 ..Default::default()
9521 },
9522 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9523 )
9524 .unwrap()
9525 });
9526
9527 let is_still_following = Rc::new(RefCell::new(true));
9528 let follower_edit_event_count = Rc::new(RefCell::new(0));
9529 let pending_update = Rc::new(RefCell::new(None));
9530 _ = follower.update(cx, {
9531 let update = pending_update.clone();
9532 let is_still_following = is_still_following.clone();
9533 let follower_edit_event_count = follower_edit_event_count.clone();
9534 |_, cx| {
9535 cx.subscribe(
9536 &leader.root_view(cx).unwrap(),
9537 move |_, leader, event, cx| {
9538 leader
9539 .read(cx)
9540 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9541 },
9542 )
9543 .detach();
9544
9545 cx.subscribe(
9546 &follower.root_view(cx).unwrap(),
9547 move |_, _, event: &EditorEvent, _cx| {
9548 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
9549 *is_still_following.borrow_mut() = false;
9550 }
9551
9552 if let EditorEvent::BufferEdited = event {
9553 *follower_edit_event_count.borrow_mut() += 1;
9554 }
9555 },
9556 )
9557 .detach();
9558 }
9559 });
9560
9561 // Update the selections only
9562 _ = leader.update(cx, |leader, cx| {
9563 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9564 });
9565 follower
9566 .update(cx, |follower, cx| {
9567 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9568 })
9569 .unwrap()
9570 .await
9571 .unwrap();
9572 _ = follower.update(cx, |follower, cx| {
9573 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
9574 });
9575 assert!(*is_still_following.borrow());
9576 assert_eq!(*follower_edit_event_count.borrow(), 0);
9577
9578 // Update the scroll position only
9579 _ = leader.update(cx, |leader, cx| {
9580 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9581 });
9582 follower
9583 .update(cx, |follower, cx| {
9584 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9585 })
9586 .unwrap()
9587 .await
9588 .unwrap();
9589 assert_eq!(
9590 follower
9591 .update(cx, |follower, cx| follower.scroll_position(cx))
9592 .unwrap(),
9593 gpui::Point::new(1.5, 3.5)
9594 );
9595 assert!(*is_still_following.borrow());
9596 assert_eq!(*follower_edit_event_count.borrow(), 0);
9597
9598 // Update the selections and scroll position. The follower's scroll position is updated
9599 // via autoscroll, not via the leader's exact scroll position.
9600 _ = leader.update(cx, |leader, cx| {
9601 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
9602 leader.request_autoscroll(Autoscroll::newest(), cx);
9603 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9604 });
9605 follower
9606 .update(cx, |follower, cx| {
9607 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9608 })
9609 .unwrap()
9610 .await
9611 .unwrap();
9612 _ = follower.update(cx, |follower, cx| {
9613 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
9614 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
9615 });
9616 assert!(*is_still_following.borrow());
9617
9618 // Creating a pending selection that precedes another selection
9619 _ = leader.update(cx, |leader, cx| {
9620 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9621 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
9622 });
9623 follower
9624 .update(cx, |follower, cx| {
9625 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9626 })
9627 .unwrap()
9628 .await
9629 .unwrap();
9630 _ = follower.update(cx, |follower, cx| {
9631 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
9632 });
9633 assert!(*is_still_following.borrow());
9634
9635 // Extend the pending selection so that it surrounds another selection
9636 _ = leader.update(cx, |leader, cx| {
9637 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
9638 });
9639 follower
9640 .update(cx, |follower, cx| {
9641 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9642 })
9643 .unwrap()
9644 .await
9645 .unwrap();
9646 _ = follower.update(cx, |follower, cx| {
9647 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
9648 });
9649
9650 // Scrolling locally breaks the follow
9651 _ = follower.update(cx, |follower, cx| {
9652 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
9653 follower.set_scroll_anchor(
9654 ScrollAnchor {
9655 anchor: top_anchor,
9656 offset: gpui::Point::new(0.0, 0.5),
9657 },
9658 cx,
9659 );
9660 });
9661 assert!(!(*is_still_following.borrow()));
9662}
9663
9664#[gpui::test]
9665async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
9666 init_test(cx, |_| {});
9667
9668 let fs = FakeFs::new(cx.executor());
9669 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9670 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9671 let pane = workspace
9672 .update(cx, |workspace, _| workspace.active_pane().clone())
9673 .unwrap();
9674
9675 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9676
9677 let leader = pane.update(cx, |_, cx| {
9678 let multibuffer = cx.new_model(|_| MultiBuffer::new(ReadWrite));
9679 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
9680 });
9681
9682 // Start following the editor when it has no excerpts.
9683 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9684 let follower_1 = cx
9685 .update_window(*workspace.deref(), |_, cx| {
9686 Editor::from_state_proto(
9687 workspace.root_view(cx).unwrap(),
9688 ViewId {
9689 creator: Default::default(),
9690 id: 0,
9691 },
9692 &mut state_message,
9693 cx,
9694 )
9695 })
9696 .unwrap()
9697 .unwrap()
9698 .await
9699 .unwrap();
9700
9701 let update_message = Rc::new(RefCell::new(None));
9702 follower_1.update(cx, {
9703 let update = update_message.clone();
9704 |_, cx| {
9705 cx.subscribe(&leader, move |_, leader, event, cx| {
9706 leader
9707 .read(cx)
9708 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9709 })
9710 .detach();
9711 }
9712 });
9713
9714 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
9715 (
9716 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9717 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9718 )
9719 });
9720
9721 // Insert some excerpts.
9722 leader.update(cx, |leader, cx| {
9723 leader.buffer.update(cx, |multibuffer, cx| {
9724 let excerpt_ids = multibuffer.push_excerpts(
9725 buffer_1.clone(),
9726 [
9727 ExcerptRange {
9728 context: 1..6,
9729 primary: None,
9730 },
9731 ExcerptRange {
9732 context: 12..15,
9733 primary: None,
9734 },
9735 ExcerptRange {
9736 context: 0..3,
9737 primary: None,
9738 },
9739 ],
9740 cx,
9741 );
9742 multibuffer.insert_excerpts_after(
9743 excerpt_ids[0],
9744 buffer_2.clone(),
9745 [
9746 ExcerptRange {
9747 context: 8..12,
9748 primary: None,
9749 },
9750 ExcerptRange {
9751 context: 0..6,
9752 primary: None,
9753 },
9754 ],
9755 cx,
9756 );
9757 });
9758 });
9759
9760 // Apply the update of adding the excerpts.
9761 follower_1
9762 .update(cx, |follower, cx| {
9763 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9764 })
9765 .await
9766 .unwrap();
9767 assert_eq!(
9768 follower_1.update(cx, |editor, cx| editor.text(cx)),
9769 leader.update(cx, |editor, cx| editor.text(cx))
9770 );
9771 update_message.borrow_mut().take();
9772
9773 // Start following separately after it already has excerpts.
9774 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9775 let follower_2 = cx
9776 .update_window(*workspace.deref(), |_, cx| {
9777 Editor::from_state_proto(
9778 workspace.root_view(cx).unwrap().clone(),
9779 ViewId {
9780 creator: Default::default(),
9781 id: 0,
9782 },
9783 &mut state_message,
9784 cx,
9785 )
9786 })
9787 .unwrap()
9788 .unwrap()
9789 .await
9790 .unwrap();
9791 assert_eq!(
9792 follower_2.update(cx, |editor, cx| editor.text(cx)),
9793 leader.update(cx, |editor, cx| editor.text(cx))
9794 );
9795
9796 // Remove some excerpts.
9797 leader.update(cx, |leader, cx| {
9798 leader.buffer.update(cx, |multibuffer, cx| {
9799 let excerpt_ids = multibuffer.excerpt_ids();
9800 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9801 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9802 });
9803 });
9804
9805 // Apply the update of removing the excerpts.
9806 follower_1
9807 .update(cx, |follower, cx| {
9808 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9809 })
9810 .await
9811 .unwrap();
9812 follower_2
9813 .update(cx, |follower, cx| {
9814 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9815 })
9816 .await
9817 .unwrap();
9818 update_message.borrow_mut().take();
9819 assert_eq!(
9820 follower_1.update(cx, |editor, cx| editor.text(cx)),
9821 leader.update(cx, |editor, cx| editor.text(cx))
9822 );
9823}
9824
9825#[gpui::test]
9826async fn go_to_prev_overlapping_diagnostic(
9827 executor: BackgroundExecutor,
9828 cx: &mut gpui::TestAppContext,
9829) {
9830 init_test(cx, |_| {});
9831
9832 let mut cx = EditorTestContext::new(cx).await;
9833 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9834
9835 cx.set_state(indoc! {"
9836 ˇfn func(abc def: i32) -> u32 {
9837 }
9838 "});
9839
9840 cx.update(|cx| {
9841 project.update(cx, |project, cx| {
9842 project
9843 .update_diagnostics(
9844 LanguageServerId(0),
9845 lsp::PublishDiagnosticsParams {
9846 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9847 version: None,
9848 diagnostics: vec![
9849 lsp::Diagnostic {
9850 range: lsp::Range::new(
9851 lsp::Position::new(0, 11),
9852 lsp::Position::new(0, 12),
9853 ),
9854 severity: Some(lsp::DiagnosticSeverity::ERROR),
9855 ..Default::default()
9856 },
9857 lsp::Diagnostic {
9858 range: lsp::Range::new(
9859 lsp::Position::new(0, 12),
9860 lsp::Position::new(0, 15),
9861 ),
9862 severity: Some(lsp::DiagnosticSeverity::ERROR),
9863 ..Default::default()
9864 },
9865 lsp::Diagnostic {
9866 range: lsp::Range::new(
9867 lsp::Position::new(0, 25),
9868 lsp::Position::new(0, 28),
9869 ),
9870 severity: Some(lsp::DiagnosticSeverity::ERROR),
9871 ..Default::default()
9872 },
9873 ],
9874 },
9875 &[],
9876 cx,
9877 )
9878 .unwrap()
9879 });
9880 });
9881
9882 executor.run_until_parked();
9883
9884 cx.update_editor(|editor, cx| {
9885 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9886 });
9887
9888 cx.assert_editor_state(indoc! {"
9889 fn func(abc def: i32) -> ˇu32 {
9890 }
9891 "});
9892
9893 cx.update_editor(|editor, cx| {
9894 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9895 });
9896
9897 cx.assert_editor_state(indoc! {"
9898 fn func(abc ˇdef: i32) -> u32 {
9899 }
9900 "});
9901
9902 cx.update_editor(|editor, cx| {
9903 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9904 });
9905
9906 cx.assert_editor_state(indoc! {"
9907 fn func(abcˇ def: i32) -> u32 {
9908 }
9909 "});
9910
9911 cx.update_editor(|editor, cx| {
9912 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9913 });
9914
9915 cx.assert_editor_state(indoc! {"
9916 fn func(abc def: i32) -> ˇu32 {
9917 }
9918 "});
9919}
9920
9921#[gpui::test]
9922async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
9923 init_test(cx, |_| {});
9924
9925 let mut cx = EditorTestContext::new(cx).await;
9926
9927 cx.set_state(indoc! {"
9928 fn func(abˇc def: i32) -> u32 {
9929 }
9930 "});
9931 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9932
9933 cx.update(|cx| {
9934 project.update(cx, |project, cx| {
9935 project.update_diagnostics(
9936 LanguageServerId(0),
9937 lsp::PublishDiagnosticsParams {
9938 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9939 version: None,
9940 diagnostics: vec![lsp::Diagnostic {
9941 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
9942 severity: Some(lsp::DiagnosticSeverity::ERROR),
9943 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
9944 ..Default::default()
9945 }],
9946 },
9947 &[],
9948 cx,
9949 )
9950 })
9951 }).unwrap();
9952 cx.run_until_parked();
9953 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
9954 cx.run_until_parked();
9955 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
9956}
9957
9958#[gpui::test]
9959async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9960 init_test(cx, |_| {});
9961
9962 let mut cx = EditorTestContext::new(cx).await;
9963
9964 let diff_base = r#"
9965 use some::mod;
9966
9967 const A: u32 = 42;
9968
9969 fn main() {
9970 println!("hello");
9971
9972 println!("world");
9973 }
9974 "#
9975 .unindent();
9976
9977 // Edits are modified, removed, modified, added
9978 cx.set_state(
9979 &r#"
9980 use some::modified;
9981
9982 ˇ
9983 fn main() {
9984 println!("hello there");
9985
9986 println!("around the");
9987 println!("world");
9988 }
9989 "#
9990 .unindent(),
9991 );
9992
9993 cx.set_diff_base(Some(&diff_base));
9994 executor.run_until_parked();
9995
9996 cx.update_editor(|editor, cx| {
9997 //Wrap around the bottom of the buffer
9998 for _ in 0..3 {
9999 editor.go_to_next_hunk(&GoToHunk, cx);
10000 }
10001 });
10002
10003 cx.assert_editor_state(
10004 &r#"
10005 ˇuse some::modified;
10006
10007
10008 fn main() {
10009 println!("hello there");
10010
10011 println!("around the");
10012 println!("world");
10013 }
10014 "#
10015 .unindent(),
10016 );
10017
10018 cx.update_editor(|editor, cx| {
10019 //Wrap around the top of the buffer
10020 for _ in 0..2 {
10021 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10022 }
10023 });
10024
10025 cx.assert_editor_state(
10026 &r#"
10027 use some::modified;
10028
10029
10030 fn main() {
10031 ˇ println!("hello there");
10032
10033 println!("around the");
10034 println!("world");
10035 }
10036 "#
10037 .unindent(),
10038 );
10039
10040 cx.update_editor(|editor, cx| {
10041 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10042 });
10043
10044 cx.assert_editor_state(
10045 &r#"
10046 use some::modified;
10047
10048 ˇ
10049 fn main() {
10050 println!("hello there");
10051
10052 println!("around the");
10053 println!("world");
10054 }
10055 "#
10056 .unindent(),
10057 );
10058
10059 cx.update_editor(|editor, cx| {
10060 for _ in 0..3 {
10061 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10062 }
10063 });
10064
10065 cx.assert_editor_state(
10066 &r#"
10067 use some::modified;
10068
10069
10070 fn main() {
10071 ˇ println!("hello there");
10072
10073 println!("around the");
10074 println!("world");
10075 }
10076 "#
10077 .unindent(),
10078 );
10079
10080 cx.update_editor(|editor, cx| {
10081 editor.fold(&Fold, cx);
10082
10083 //Make sure that the fold only gets one hunk
10084 for _ in 0..4 {
10085 editor.go_to_next_hunk(&GoToHunk, cx);
10086 }
10087 });
10088
10089 cx.assert_editor_state(
10090 &r#"
10091 ˇuse some::modified;
10092
10093
10094 fn main() {
10095 println!("hello there");
10096
10097 println!("around the");
10098 println!("world");
10099 }
10100 "#
10101 .unindent(),
10102 );
10103}
10104
10105#[test]
10106fn test_split_words() {
10107 fn split(text: &str) -> Vec<&str> {
10108 split_words(text).collect()
10109 }
10110
10111 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
10112 assert_eq!(split("hello_world"), &["hello_", "world"]);
10113 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
10114 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
10115 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
10116 assert_eq!(split("helloworld"), &["helloworld"]);
10117
10118 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
10119}
10120
10121#[gpui::test]
10122async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
10123 init_test(cx, |_| {});
10124
10125 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
10126 let mut assert = |before, after| {
10127 let _state_context = cx.set_state(before);
10128 cx.update_editor(|editor, cx| {
10129 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
10130 });
10131 cx.assert_editor_state(after);
10132 };
10133
10134 // Outside bracket jumps to outside of matching bracket
10135 assert("console.logˇ(var);", "console.log(var)ˇ;");
10136 assert("console.log(var)ˇ;", "console.logˇ(var);");
10137
10138 // Inside bracket jumps to inside of matching bracket
10139 assert("console.log(ˇvar);", "console.log(varˇ);");
10140 assert("console.log(varˇ);", "console.log(ˇvar);");
10141
10142 // When outside a bracket and inside, favor jumping to the inside bracket
10143 assert(
10144 "console.log('foo', [1, 2, 3]ˇ);",
10145 "console.log(ˇ'foo', [1, 2, 3]);",
10146 );
10147 assert(
10148 "console.log(ˇ'foo', [1, 2, 3]);",
10149 "console.log('foo', [1, 2, 3]ˇ);",
10150 );
10151
10152 // Bias forward if two options are equally likely
10153 assert(
10154 "let result = curried_fun()ˇ();",
10155 "let result = curried_fun()()ˇ;",
10156 );
10157
10158 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
10159 assert(
10160 indoc! {"
10161 function test() {
10162 console.log('test')ˇ
10163 }"},
10164 indoc! {"
10165 function test() {
10166 console.logˇ('test')
10167 }"},
10168 );
10169}
10170
10171#[gpui::test]
10172async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
10173 init_test(cx, |_| {});
10174
10175 let fs = FakeFs::new(cx.executor());
10176 fs.insert_tree(
10177 "/a",
10178 json!({
10179 "main.rs": "fn main() { let a = 5; }",
10180 "other.rs": "// Test file",
10181 }),
10182 )
10183 .await;
10184 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10185
10186 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10187 language_registry.add(Arc::new(Language::new(
10188 LanguageConfig {
10189 name: "Rust".into(),
10190 matcher: LanguageMatcher {
10191 path_suffixes: vec!["rs".to_string()],
10192 ..Default::default()
10193 },
10194 brackets: BracketPairConfig {
10195 pairs: vec![BracketPair {
10196 start: "{".to_string(),
10197 end: "}".to_string(),
10198 close: true,
10199 surround: true,
10200 newline: true,
10201 }],
10202 disabled_scopes_by_bracket_ix: Vec::new(),
10203 },
10204 ..Default::default()
10205 },
10206 Some(tree_sitter_rust::LANGUAGE.into()),
10207 )));
10208 let mut fake_servers = language_registry.register_fake_lsp(
10209 "Rust",
10210 FakeLspAdapter {
10211 capabilities: lsp::ServerCapabilities {
10212 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
10213 first_trigger_character: "{".to_string(),
10214 more_trigger_character: None,
10215 }),
10216 ..Default::default()
10217 },
10218 ..Default::default()
10219 },
10220 );
10221
10222 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10223
10224 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10225
10226 let worktree_id = workspace
10227 .update(cx, |workspace, cx| {
10228 workspace.project().update(cx, |project, cx| {
10229 project.worktrees(cx).next().unwrap().read(cx).id()
10230 })
10231 })
10232 .unwrap();
10233
10234 let buffer = project
10235 .update(cx, |project, cx| {
10236 project.open_local_buffer("/a/main.rs", cx)
10237 })
10238 .await
10239 .unwrap();
10240 cx.executor().run_until_parked();
10241 cx.executor().start_waiting();
10242 let fake_server = fake_servers.next().await.unwrap();
10243 let editor_handle = workspace
10244 .update(cx, |workspace, cx| {
10245 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
10246 })
10247 .unwrap()
10248 .await
10249 .unwrap()
10250 .downcast::<Editor>()
10251 .unwrap();
10252
10253 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
10254 assert_eq!(
10255 params.text_document_position.text_document.uri,
10256 lsp::Url::from_file_path("/a/main.rs").unwrap(),
10257 );
10258 assert_eq!(
10259 params.text_document_position.position,
10260 lsp::Position::new(0, 21),
10261 );
10262
10263 Ok(Some(vec![lsp::TextEdit {
10264 new_text: "]".to_string(),
10265 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10266 }]))
10267 });
10268
10269 editor_handle.update(cx, |editor, cx| {
10270 editor.focus(cx);
10271 editor.change_selections(None, cx, |s| {
10272 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
10273 });
10274 editor.handle_input("{", cx);
10275 });
10276
10277 cx.executor().run_until_parked();
10278
10279 buffer.update(cx, |buffer, _| {
10280 assert_eq!(
10281 buffer.text(),
10282 "fn main() { let a = {5}; }",
10283 "No extra braces from on type formatting should appear in the buffer"
10284 )
10285 });
10286}
10287
10288#[gpui::test]
10289async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
10290 init_test(cx, |_| {});
10291
10292 let fs = FakeFs::new(cx.executor());
10293 fs.insert_tree(
10294 "/a",
10295 json!({
10296 "main.rs": "fn main() { let a = 5; }",
10297 "other.rs": "// Test file",
10298 }),
10299 )
10300 .await;
10301
10302 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10303
10304 let server_restarts = Arc::new(AtomicUsize::new(0));
10305 let closure_restarts = Arc::clone(&server_restarts);
10306 let language_server_name = "test language server";
10307 let language_name: LanguageName = "Rust".into();
10308
10309 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10310 language_registry.add(Arc::new(Language::new(
10311 LanguageConfig {
10312 name: language_name.clone(),
10313 matcher: LanguageMatcher {
10314 path_suffixes: vec!["rs".to_string()],
10315 ..Default::default()
10316 },
10317 ..Default::default()
10318 },
10319 Some(tree_sitter_rust::LANGUAGE.into()),
10320 )));
10321 let mut fake_servers = language_registry.register_fake_lsp(
10322 "Rust",
10323 FakeLspAdapter {
10324 name: language_server_name,
10325 initialization_options: Some(json!({
10326 "testOptionValue": true
10327 })),
10328 initializer: Some(Box::new(move |fake_server| {
10329 let task_restarts = Arc::clone(&closure_restarts);
10330 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
10331 task_restarts.fetch_add(1, atomic::Ordering::Release);
10332 futures::future::ready(Ok(()))
10333 });
10334 })),
10335 ..Default::default()
10336 },
10337 );
10338
10339 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10340 let _buffer = project
10341 .update(cx, |project, cx| {
10342 project.open_local_buffer("/a/main.rs", cx)
10343 })
10344 .await
10345 .unwrap();
10346 let _fake_server = fake_servers.next().await.unwrap();
10347 update_test_language_settings(cx, |language_settings| {
10348 language_settings.languages.insert(
10349 language_name.clone(),
10350 LanguageSettingsContent {
10351 tab_size: NonZeroU32::new(8),
10352 ..Default::default()
10353 },
10354 );
10355 });
10356 cx.executor().run_until_parked();
10357 assert_eq!(
10358 server_restarts.load(atomic::Ordering::Acquire),
10359 0,
10360 "Should not restart LSP server on an unrelated change"
10361 );
10362
10363 update_test_project_settings(cx, |project_settings| {
10364 project_settings.lsp.insert(
10365 "Some other server name".into(),
10366 LspSettings {
10367 binary: None,
10368 settings: None,
10369 initialization_options: Some(json!({
10370 "some other init value": false
10371 })),
10372 },
10373 );
10374 });
10375 cx.executor().run_until_parked();
10376 assert_eq!(
10377 server_restarts.load(atomic::Ordering::Acquire),
10378 0,
10379 "Should not restart LSP server on an unrelated LSP settings change"
10380 );
10381
10382 update_test_project_settings(cx, |project_settings| {
10383 project_settings.lsp.insert(
10384 language_server_name.into(),
10385 LspSettings {
10386 binary: None,
10387 settings: None,
10388 initialization_options: Some(json!({
10389 "anotherInitValue": false
10390 })),
10391 },
10392 );
10393 });
10394 cx.executor().run_until_parked();
10395 assert_eq!(
10396 server_restarts.load(atomic::Ordering::Acquire),
10397 1,
10398 "Should restart LSP server on a related LSP settings change"
10399 );
10400
10401 update_test_project_settings(cx, |project_settings| {
10402 project_settings.lsp.insert(
10403 language_server_name.into(),
10404 LspSettings {
10405 binary: None,
10406 settings: None,
10407 initialization_options: Some(json!({
10408 "anotherInitValue": false
10409 })),
10410 },
10411 );
10412 });
10413 cx.executor().run_until_parked();
10414 assert_eq!(
10415 server_restarts.load(atomic::Ordering::Acquire),
10416 1,
10417 "Should not restart LSP server on a related LSP settings change that is the same"
10418 );
10419
10420 update_test_project_settings(cx, |project_settings| {
10421 project_settings.lsp.insert(
10422 language_server_name.into(),
10423 LspSettings {
10424 binary: None,
10425 settings: None,
10426 initialization_options: None,
10427 },
10428 );
10429 });
10430 cx.executor().run_until_parked();
10431 assert_eq!(
10432 server_restarts.load(atomic::Ordering::Acquire),
10433 2,
10434 "Should restart LSP server on another related LSP settings change"
10435 );
10436}
10437
10438#[gpui::test]
10439async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10440 init_test(cx, |_| {});
10441
10442 let mut cx = EditorLspTestContext::new_rust(
10443 lsp::ServerCapabilities {
10444 completion_provider: Some(lsp::CompletionOptions {
10445 trigger_characters: Some(vec![".".to_string()]),
10446 resolve_provider: Some(true),
10447 ..Default::default()
10448 }),
10449 ..Default::default()
10450 },
10451 cx,
10452 )
10453 .await;
10454
10455 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10456 cx.simulate_keystroke(".");
10457 let completion_item = lsp::CompletionItem {
10458 label: "some".into(),
10459 kind: Some(lsp::CompletionItemKind::SNIPPET),
10460 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10461 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10462 kind: lsp::MarkupKind::Markdown,
10463 value: "```rust\nSome(2)\n```".to_string(),
10464 })),
10465 deprecated: Some(false),
10466 sort_text: Some("fffffff2".to_string()),
10467 filter_text: Some("some".to_string()),
10468 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10469 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10470 range: lsp::Range {
10471 start: lsp::Position {
10472 line: 0,
10473 character: 22,
10474 },
10475 end: lsp::Position {
10476 line: 0,
10477 character: 22,
10478 },
10479 },
10480 new_text: "Some(2)".to_string(),
10481 })),
10482 additional_text_edits: Some(vec![lsp::TextEdit {
10483 range: lsp::Range {
10484 start: lsp::Position {
10485 line: 0,
10486 character: 20,
10487 },
10488 end: lsp::Position {
10489 line: 0,
10490 character: 22,
10491 },
10492 },
10493 new_text: "".to_string(),
10494 }]),
10495 ..Default::default()
10496 };
10497
10498 let closure_completion_item = completion_item.clone();
10499 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10500 let task_completion_item = closure_completion_item.clone();
10501 async move {
10502 Ok(Some(lsp::CompletionResponse::Array(vec![
10503 task_completion_item,
10504 ])))
10505 }
10506 });
10507
10508 request.next().await;
10509
10510 cx.condition(|editor, _| editor.context_menu_visible())
10511 .await;
10512 let apply_additional_edits = cx.update_editor(|editor, cx| {
10513 editor
10514 .confirm_completion(&ConfirmCompletion::default(), cx)
10515 .unwrap()
10516 });
10517 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
10518
10519 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10520 let task_completion_item = completion_item.clone();
10521 async move { Ok(task_completion_item) }
10522 })
10523 .next()
10524 .await
10525 .unwrap();
10526 apply_additional_edits.await.unwrap();
10527 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10528}
10529
10530#[gpui::test]
10531async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
10532 init_test(cx, |_| {});
10533
10534 let mut cx = EditorLspTestContext::new(
10535 Language::new(
10536 LanguageConfig {
10537 matcher: LanguageMatcher {
10538 path_suffixes: vec!["jsx".into()],
10539 ..Default::default()
10540 },
10541 overrides: [(
10542 "element".into(),
10543 LanguageConfigOverride {
10544 word_characters: Override::Set(['-'].into_iter().collect()),
10545 ..Default::default()
10546 },
10547 )]
10548 .into_iter()
10549 .collect(),
10550 ..Default::default()
10551 },
10552 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10553 )
10554 .with_override_query("(jsx_self_closing_element) @element")
10555 .unwrap(),
10556 lsp::ServerCapabilities {
10557 completion_provider: Some(lsp::CompletionOptions {
10558 trigger_characters: Some(vec![":".to_string()]),
10559 ..Default::default()
10560 }),
10561 ..Default::default()
10562 },
10563 cx,
10564 )
10565 .await;
10566
10567 cx.lsp
10568 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10569 Ok(Some(lsp::CompletionResponse::Array(vec![
10570 lsp::CompletionItem {
10571 label: "bg-blue".into(),
10572 ..Default::default()
10573 },
10574 lsp::CompletionItem {
10575 label: "bg-red".into(),
10576 ..Default::default()
10577 },
10578 lsp::CompletionItem {
10579 label: "bg-yellow".into(),
10580 ..Default::default()
10581 },
10582 ])))
10583 });
10584
10585 cx.set_state(r#"<p class="bgˇ" />"#);
10586
10587 // Trigger completion when typing a dash, because the dash is an extra
10588 // word character in the 'element' scope, which contains the cursor.
10589 cx.simulate_keystroke("-");
10590 cx.executor().run_until_parked();
10591 cx.update_editor(|editor, _| {
10592 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10593 assert_eq!(
10594 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10595 &["bg-red", "bg-blue", "bg-yellow"]
10596 );
10597 } else {
10598 panic!("expected completion menu to be open");
10599 }
10600 });
10601
10602 cx.simulate_keystroke("l");
10603 cx.executor().run_until_parked();
10604 cx.update_editor(|editor, _| {
10605 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10606 assert_eq!(
10607 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10608 &["bg-blue", "bg-yellow"]
10609 );
10610 } else {
10611 panic!("expected completion menu to be open");
10612 }
10613 });
10614
10615 // When filtering completions, consider the character after the '-' to
10616 // be the start of a subword.
10617 cx.set_state(r#"<p class="yelˇ" />"#);
10618 cx.simulate_keystroke("l");
10619 cx.executor().run_until_parked();
10620 cx.update_editor(|editor, _| {
10621 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10622 assert_eq!(
10623 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10624 &["bg-yellow"]
10625 );
10626 } else {
10627 panic!("expected completion menu to be open");
10628 }
10629 });
10630}
10631
10632#[gpui::test]
10633async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
10634 init_test(cx, |settings| {
10635 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
10636 FormatterList(vec![Formatter::Prettier].into()),
10637 ))
10638 });
10639
10640 let fs = FakeFs::new(cx.executor());
10641 fs.insert_file("/file.ts", Default::default()).await;
10642
10643 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
10644 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10645
10646 language_registry.add(Arc::new(Language::new(
10647 LanguageConfig {
10648 name: "TypeScript".into(),
10649 matcher: LanguageMatcher {
10650 path_suffixes: vec!["ts".to_string()],
10651 ..Default::default()
10652 },
10653 ..Default::default()
10654 },
10655 Some(tree_sitter_rust::LANGUAGE.into()),
10656 )));
10657 update_test_language_settings(cx, |settings| {
10658 settings.defaults.prettier = Some(PrettierSettings {
10659 allowed: true,
10660 ..PrettierSettings::default()
10661 });
10662 });
10663
10664 let test_plugin = "test_plugin";
10665 let _ = language_registry.register_fake_lsp(
10666 "TypeScript",
10667 FakeLspAdapter {
10668 prettier_plugins: vec![test_plugin],
10669 ..Default::default()
10670 },
10671 );
10672
10673 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
10674 let buffer = project
10675 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
10676 .await
10677 .unwrap();
10678
10679 let buffer_text = "one\ntwo\nthree\n";
10680 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
10681 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
10682 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
10683
10684 editor
10685 .update(cx, |editor, cx| {
10686 editor.perform_format(
10687 project.clone(),
10688 FormatTrigger::Manual,
10689 FormatTarget::Buffer,
10690 cx,
10691 )
10692 })
10693 .unwrap()
10694 .await;
10695 assert_eq!(
10696 editor.update(cx, |editor, cx| editor.text(cx)),
10697 buffer_text.to_string() + prettier_format_suffix,
10698 "Test prettier formatting was not applied to the original buffer text",
10699 );
10700
10701 update_test_language_settings(cx, |settings| {
10702 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
10703 });
10704 let format = editor.update(cx, |editor, cx| {
10705 editor.perform_format(
10706 project.clone(),
10707 FormatTrigger::Manual,
10708 FormatTarget::Buffer,
10709 cx,
10710 )
10711 });
10712 format.await.unwrap();
10713 assert_eq!(
10714 editor.update(cx, |editor, cx| editor.text(cx)),
10715 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
10716 "Autoformatting (via test prettier) was not applied to the original buffer text",
10717 );
10718}
10719
10720#[gpui::test]
10721async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
10722 init_test(cx, |_| {});
10723 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10724 let base_text = indoc! {r#"struct Row;
10725struct Row1;
10726struct Row2;
10727
10728struct Row4;
10729struct Row5;
10730struct Row6;
10731
10732struct Row8;
10733struct Row9;
10734struct Row10;"#};
10735
10736 // When addition hunks are not adjacent to carets, no hunk revert is performed
10737 assert_hunk_revert(
10738 indoc! {r#"struct Row;
10739 struct Row1;
10740 struct Row1.1;
10741 struct Row1.2;
10742 struct Row2;ˇ
10743
10744 struct Row4;
10745 struct Row5;
10746 struct Row6;
10747
10748 struct Row8;
10749 ˇstruct Row9;
10750 struct Row9.1;
10751 struct Row9.2;
10752 struct Row9.3;
10753 struct Row10;"#},
10754 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10755 indoc! {r#"struct Row;
10756 struct Row1;
10757 struct Row1.1;
10758 struct Row1.2;
10759 struct Row2;ˇ
10760
10761 struct Row4;
10762 struct Row5;
10763 struct Row6;
10764
10765 struct Row8;
10766 ˇstruct Row9;
10767 struct Row9.1;
10768 struct Row9.2;
10769 struct Row9.3;
10770 struct Row10;"#},
10771 base_text,
10772 &mut cx,
10773 );
10774 // Same for selections
10775 assert_hunk_revert(
10776 indoc! {r#"struct Row;
10777 struct Row1;
10778 struct Row2;
10779 struct Row2.1;
10780 struct Row2.2;
10781 «ˇ
10782 struct Row4;
10783 struct» Row5;
10784 «struct Row6;
10785 ˇ»
10786 struct Row9.1;
10787 struct Row9.2;
10788 struct Row9.3;
10789 struct Row8;
10790 struct Row9;
10791 struct Row10;"#},
10792 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10793 indoc! {r#"struct Row;
10794 struct Row1;
10795 struct Row2;
10796 struct Row2.1;
10797 struct Row2.2;
10798 «ˇ
10799 struct Row4;
10800 struct» Row5;
10801 «struct Row6;
10802 ˇ»
10803 struct Row9.1;
10804 struct Row9.2;
10805 struct Row9.3;
10806 struct Row8;
10807 struct Row9;
10808 struct Row10;"#},
10809 base_text,
10810 &mut cx,
10811 );
10812
10813 // When carets and selections intersect the addition hunks, those are reverted.
10814 // Adjacent carets got merged.
10815 assert_hunk_revert(
10816 indoc! {r#"struct Row;
10817 ˇ// something on the top
10818 struct Row1;
10819 struct Row2;
10820 struct Roˇw3.1;
10821 struct Row2.2;
10822 struct Row2.3;ˇ
10823
10824 struct Row4;
10825 struct ˇRow5.1;
10826 struct Row5.2;
10827 struct «Rowˇ»5.3;
10828 struct Row5;
10829 struct Row6;
10830 ˇ
10831 struct Row9.1;
10832 struct «Rowˇ»9.2;
10833 struct «ˇRow»9.3;
10834 struct Row8;
10835 struct Row9;
10836 «ˇ// something on bottom»
10837 struct Row10;"#},
10838 vec![
10839 DiffHunkStatus::Added,
10840 DiffHunkStatus::Added,
10841 DiffHunkStatus::Added,
10842 DiffHunkStatus::Added,
10843 DiffHunkStatus::Added,
10844 ],
10845 indoc! {r#"struct Row;
10846 ˇstruct Row1;
10847 struct Row2;
10848 ˇ
10849 struct Row4;
10850 ˇstruct Row5;
10851 struct Row6;
10852 ˇ
10853 ˇstruct Row8;
10854 struct Row9;
10855 ˇstruct Row10;"#},
10856 base_text,
10857 &mut cx,
10858 );
10859}
10860
10861#[gpui::test]
10862async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
10863 init_test(cx, |_| {});
10864 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10865 let base_text = indoc! {r#"struct Row;
10866struct Row1;
10867struct Row2;
10868
10869struct Row4;
10870struct Row5;
10871struct Row6;
10872
10873struct Row8;
10874struct Row9;
10875struct Row10;"#};
10876
10877 // Modification hunks behave the same as the addition ones.
10878 assert_hunk_revert(
10879 indoc! {r#"struct Row;
10880 struct Row1;
10881 struct Row33;
10882 ˇ
10883 struct Row4;
10884 struct Row5;
10885 struct Row6;
10886 ˇ
10887 struct Row99;
10888 struct Row9;
10889 struct Row10;"#},
10890 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10891 indoc! {r#"struct Row;
10892 struct Row1;
10893 struct Row33;
10894 ˇ
10895 struct Row4;
10896 struct Row5;
10897 struct Row6;
10898 ˇ
10899 struct Row99;
10900 struct Row9;
10901 struct Row10;"#},
10902 base_text,
10903 &mut cx,
10904 );
10905 assert_hunk_revert(
10906 indoc! {r#"struct Row;
10907 struct Row1;
10908 struct Row33;
10909 «ˇ
10910 struct Row4;
10911 struct» Row5;
10912 «struct Row6;
10913 ˇ»
10914 struct Row99;
10915 struct Row9;
10916 struct Row10;"#},
10917 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
10918 indoc! {r#"struct Row;
10919 struct Row1;
10920 struct Row33;
10921 «ˇ
10922 struct Row4;
10923 struct» Row5;
10924 «struct Row6;
10925 ˇ»
10926 struct Row99;
10927 struct Row9;
10928 struct Row10;"#},
10929 base_text,
10930 &mut cx,
10931 );
10932
10933 assert_hunk_revert(
10934 indoc! {r#"ˇstruct Row1.1;
10935 struct Row1;
10936 «ˇstr»uct Row22;
10937
10938 struct ˇRow44;
10939 struct Row5;
10940 struct «Rˇ»ow66;ˇ
10941
10942 «struˇ»ct Row88;
10943 struct Row9;
10944 struct Row1011;ˇ"#},
10945 vec![
10946 DiffHunkStatus::Modified,
10947 DiffHunkStatus::Modified,
10948 DiffHunkStatus::Modified,
10949 DiffHunkStatus::Modified,
10950 DiffHunkStatus::Modified,
10951 DiffHunkStatus::Modified,
10952 ],
10953 indoc! {r#"struct Row;
10954 ˇstruct Row1;
10955 struct Row2;
10956 ˇ
10957 struct Row4;
10958 ˇstruct Row5;
10959 struct Row6;
10960 ˇ
10961 struct Row8;
10962 ˇstruct Row9;
10963 struct Row10;ˇ"#},
10964 base_text,
10965 &mut cx,
10966 );
10967}
10968
10969#[gpui::test]
10970async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
10971 init_test(cx, |_| {});
10972 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10973 let base_text = indoc! {r#"struct Row;
10974struct Row1;
10975struct Row2;
10976
10977struct Row4;
10978struct Row5;
10979struct Row6;
10980
10981struct Row8;
10982struct Row9;
10983struct Row10;"#};
10984
10985 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
10986 assert_hunk_revert(
10987 indoc! {r#"struct Row;
10988 struct Row2;
10989
10990 ˇstruct Row4;
10991 struct Row5;
10992 struct Row6;
10993 ˇ
10994 struct Row8;
10995 struct Row10;"#},
10996 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
10997 indoc! {r#"struct Row;
10998 struct Row2;
10999
11000 ˇstruct Row4;
11001 struct Row5;
11002 struct Row6;
11003 ˇ
11004 struct Row8;
11005 struct Row10;"#},
11006 base_text,
11007 &mut cx,
11008 );
11009 assert_hunk_revert(
11010 indoc! {r#"struct Row;
11011 struct Row2;
11012
11013 «ˇstruct Row4;
11014 struct» Row5;
11015 «struct Row6;
11016 ˇ»
11017 struct Row8;
11018 struct Row10;"#},
11019 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11020 indoc! {r#"struct Row;
11021 struct Row2;
11022
11023 «ˇstruct Row4;
11024 struct» Row5;
11025 «struct Row6;
11026 ˇ»
11027 struct Row8;
11028 struct Row10;"#},
11029 base_text,
11030 &mut cx,
11031 );
11032
11033 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
11034 assert_hunk_revert(
11035 indoc! {r#"struct Row;
11036 ˇstruct Row2;
11037
11038 struct Row4;
11039 struct Row5;
11040 struct Row6;
11041
11042 struct Row8;ˇ
11043 struct Row10;"#},
11044 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11045 indoc! {r#"struct Row;
11046 struct Row1;
11047 ˇstruct Row2;
11048
11049 struct Row4;
11050 struct Row5;
11051 struct Row6;
11052
11053 struct Row8;ˇ
11054 struct Row9;
11055 struct Row10;"#},
11056 base_text,
11057 &mut cx,
11058 );
11059 assert_hunk_revert(
11060 indoc! {r#"struct Row;
11061 struct Row2«ˇ;
11062 struct Row4;
11063 struct» Row5;
11064 «struct Row6;
11065
11066 struct Row8;ˇ»
11067 struct Row10;"#},
11068 vec![
11069 DiffHunkStatus::Removed,
11070 DiffHunkStatus::Removed,
11071 DiffHunkStatus::Removed,
11072 ],
11073 indoc! {r#"struct Row;
11074 struct Row1;
11075 struct Row2«ˇ;
11076
11077 struct Row4;
11078 struct» Row5;
11079 «struct Row6;
11080
11081 struct Row8;ˇ»
11082 struct Row9;
11083 struct Row10;"#},
11084 base_text,
11085 &mut cx,
11086 );
11087}
11088
11089#[gpui::test]
11090async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
11091 init_test(cx, |_| {});
11092
11093 let cols = 4;
11094 let rows = 10;
11095 let sample_text_1 = sample_text(rows, cols, 'a');
11096 assert_eq!(
11097 sample_text_1,
11098 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11099 );
11100 let sample_text_2 = sample_text(rows, cols, 'l');
11101 assert_eq!(
11102 sample_text_2,
11103 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11104 );
11105 let sample_text_3 = sample_text(rows, cols, 'v');
11106 assert_eq!(
11107 sample_text_3,
11108 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11109 );
11110
11111 fn diff_every_buffer_row(
11112 buffer: &Model<Buffer>,
11113 sample_text: String,
11114 cols: usize,
11115 cx: &mut gpui::TestAppContext,
11116 ) {
11117 // revert first character in each row, creating one large diff hunk per buffer
11118 let is_first_char = |offset: usize| offset % cols == 0;
11119 buffer.update(cx, |buffer, cx| {
11120 buffer.set_text(
11121 sample_text
11122 .chars()
11123 .enumerate()
11124 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
11125 .collect::<String>(),
11126 cx,
11127 );
11128 buffer.set_diff_base(Some(sample_text), cx);
11129 });
11130 cx.executor().run_until_parked();
11131 }
11132
11133 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11134 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
11135
11136 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11137 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
11138
11139 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11140 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
11141
11142 let multibuffer = cx.new_model(|cx| {
11143 let mut multibuffer = MultiBuffer::new(ReadWrite);
11144 multibuffer.push_excerpts(
11145 buffer_1.clone(),
11146 [
11147 ExcerptRange {
11148 context: Point::new(0, 0)..Point::new(3, 0),
11149 primary: None,
11150 },
11151 ExcerptRange {
11152 context: Point::new(5, 0)..Point::new(7, 0),
11153 primary: None,
11154 },
11155 ExcerptRange {
11156 context: Point::new(9, 0)..Point::new(10, 4),
11157 primary: None,
11158 },
11159 ],
11160 cx,
11161 );
11162 multibuffer.push_excerpts(
11163 buffer_2.clone(),
11164 [
11165 ExcerptRange {
11166 context: Point::new(0, 0)..Point::new(3, 0),
11167 primary: None,
11168 },
11169 ExcerptRange {
11170 context: Point::new(5, 0)..Point::new(7, 0),
11171 primary: None,
11172 },
11173 ExcerptRange {
11174 context: Point::new(9, 0)..Point::new(10, 4),
11175 primary: None,
11176 },
11177 ],
11178 cx,
11179 );
11180 multibuffer.push_excerpts(
11181 buffer_3.clone(),
11182 [
11183 ExcerptRange {
11184 context: Point::new(0, 0)..Point::new(3, 0),
11185 primary: None,
11186 },
11187 ExcerptRange {
11188 context: Point::new(5, 0)..Point::new(7, 0),
11189 primary: None,
11190 },
11191 ExcerptRange {
11192 context: Point::new(9, 0)..Point::new(10, 4),
11193 primary: None,
11194 },
11195 ],
11196 cx,
11197 );
11198 multibuffer
11199 });
11200
11201 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
11202 editor.update(cx, |editor, cx| {
11203 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");
11204 editor.select_all(&SelectAll, cx);
11205 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11206 });
11207 cx.executor().run_until_parked();
11208 // When all ranges are selected, all buffer hunks are reverted.
11209 editor.update(cx, |editor, cx| {
11210 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");
11211 });
11212 buffer_1.update(cx, |buffer, _| {
11213 assert_eq!(buffer.text(), sample_text_1);
11214 });
11215 buffer_2.update(cx, |buffer, _| {
11216 assert_eq!(buffer.text(), sample_text_2);
11217 });
11218 buffer_3.update(cx, |buffer, _| {
11219 assert_eq!(buffer.text(), sample_text_3);
11220 });
11221
11222 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
11223 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
11224 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
11225 editor.update(cx, |editor, cx| {
11226 editor.change_selections(None, cx, |s| {
11227 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
11228 });
11229 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11230 });
11231 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
11232 // but not affect buffer_2 and its related excerpts.
11233 editor.update(cx, |editor, cx| {
11234 assert_eq!(
11235 editor.text(cx),
11236 "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"
11237 );
11238 });
11239 buffer_1.update(cx, |buffer, _| {
11240 assert_eq!(buffer.text(), sample_text_1);
11241 });
11242 buffer_2.update(cx, |buffer, _| {
11243 assert_eq!(
11244 buffer.text(),
11245 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
11246 );
11247 });
11248 buffer_3.update(cx, |buffer, _| {
11249 assert_eq!(
11250 buffer.text(),
11251 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
11252 );
11253 });
11254}
11255
11256#[gpui::test]
11257async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
11258 init_test(cx, |_| {});
11259
11260 let cols = 4;
11261 let rows = 10;
11262 let sample_text_1 = sample_text(rows, cols, 'a');
11263 assert_eq!(
11264 sample_text_1,
11265 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11266 );
11267 let sample_text_2 = sample_text(rows, cols, 'l');
11268 assert_eq!(
11269 sample_text_2,
11270 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11271 );
11272 let sample_text_3 = sample_text(rows, cols, 'v');
11273 assert_eq!(
11274 sample_text_3,
11275 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11276 );
11277
11278 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11279 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11280 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11281
11282 let multi_buffer = cx.new_model(|cx| {
11283 let mut multibuffer = MultiBuffer::new(ReadWrite);
11284 multibuffer.push_excerpts(
11285 buffer_1.clone(),
11286 [
11287 ExcerptRange {
11288 context: Point::new(0, 0)..Point::new(3, 0),
11289 primary: None,
11290 },
11291 ExcerptRange {
11292 context: Point::new(5, 0)..Point::new(7, 0),
11293 primary: None,
11294 },
11295 ExcerptRange {
11296 context: Point::new(9, 0)..Point::new(10, 4),
11297 primary: None,
11298 },
11299 ],
11300 cx,
11301 );
11302 multibuffer.push_excerpts(
11303 buffer_2.clone(),
11304 [
11305 ExcerptRange {
11306 context: Point::new(0, 0)..Point::new(3, 0),
11307 primary: None,
11308 },
11309 ExcerptRange {
11310 context: Point::new(5, 0)..Point::new(7, 0),
11311 primary: None,
11312 },
11313 ExcerptRange {
11314 context: Point::new(9, 0)..Point::new(10, 4),
11315 primary: None,
11316 },
11317 ],
11318 cx,
11319 );
11320 multibuffer.push_excerpts(
11321 buffer_3.clone(),
11322 [
11323 ExcerptRange {
11324 context: Point::new(0, 0)..Point::new(3, 0),
11325 primary: None,
11326 },
11327 ExcerptRange {
11328 context: Point::new(5, 0)..Point::new(7, 0),
11329 primary: None,
11330 },
11331 ExcerptRange {
11332 context: Point::new(9, 0)..Point::new(10, 4),
11333 primary: None,
11334 },
11335 ],
11336 cx,
11337 );
11338 multibuffer
11339 });
11340
11341 let fs = FakeFs::new(cx.executor());
11342 fs.insert_tree(
11343 "/a",
11344 json!({
11345 "main.rs": sample_text_1,
11346 "other.rs": sample_text_2,
11347 "lib.rs": sample_text_3,
11348 }),
11349 )
11350 .await;
11351 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11352 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11353 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11354 let multi_buffer_editor = cx.new_view(|cx| {
11355 Editor::new(
11356 EditorMode::Full,
11357 multi_buffer,
11358 Some(project.clone()),
11359 true,
11360 cx,
11361 )
11362 });
11363 let multibuffer_item_id = workspace
11364 .update(cx, |workspace, cx| {
11365 assert!(
11366 workspace.active_item(cx).is_none(),
11367 "active item should be None before the first item is added"
11368 );
11369 workspace.add_item_to_active_pane(
11370 Box::new(multi_buffer_editor.clone()),
11371 None,
11372 true,
11373 cx,
11374 );
11375 let active_item = workspace
11376 .active_item(cx)
11377 .expect("should have an active item after adding the multi buffer");
11378 assert!(
11379 !active_item.is_singleton(cx),
11380 "A multi buffer was expected to active after adding"
11381 );
11382 active_item.item_id()
11383 })
11384 .unwrap();
11385 cx.executor().run_until_parked();
11386
11387 multi_buffer_editor.update(cx, |editor, cx| {
11388 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
11389 editor.open_excerpts(&OpenExcerpts, cx);
11390 });
11391 cx.executor().run_until_parked();
11392 let first_item_id = workspace
11393 .update(cx, |workspace, cx| {
11394 let active_item = workspace
11395 .active_item(cx)
11396 .expect("should have an active item after navigating into the 1st buffer");
11397 let first_item_id = active_item.item_id();
11398 assert_ne!(
11399 first_item_id, multibuffer_item_id,
11400 "Should navigate into the 1st buffer and activate it"
11401 );
11402 assert!(
11403 active_item.is_singleton(cx),
11404 "New active item should be a singleton buffer"
11405 );
11406 assert_eq!(
11407 active_item
11408 .act_as::<Editor>(cx)
11409 .expect("should have navigated into an editor for the 1st buffer")
11410 .read(cx)
11411 .text(cx),
11412 sample_text_1
11413 );
11414
11415 workspace
11416 .go_back(workspace.active_pane().downgrade(), cx)
11417 .detach_and_log_err(cx);
11418
11419 first_item_id
11420 })
11421 .unwrap();
11422 cx.executor().run_until_parked();
11423 workspace
11424 .update(cx, |workspace, cx| {
11425 let active_item = workspace
11426 .active_item(cx)
11427 .expect("should have an active item after navigating back");
11428 assert_eq!(
11429 active_item.item_id(),
11430 multibuffer_item_id,
11431 "Should navigate back to the multi buffer"
11432 );
11433 assert!(!active_item.is_singleton(cx));
11434 })
11435 .unwrap();
11436
11437 multi_buffer_editor.update(cx, |editor, cx| {
11438 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11439 s.select_ranges(Some(39..40))
11440 });
11441 editor.open_excerpts(&OpenExcerpts, cx);
11442 });
11443 cx.executor().run_until_parked();
11444 let second_item_id = workspace
11445 .update(cx, |workspace, cx| {
11446 let active_item = workspace
11447 .active_item(cx)
11448 .expect("should have an active item after navigating into the 2nd buffer");
11449 let second_item_id = active_item.item_id();
11450 assert_ne!(
11451 second_item_id, multibuffer_item_id,
11452 "Should navigate away from the multibuffer"
11453 );
11454 assert_ne!(
11455 second_item_id, first_item_id,
11456 "Should navigate into the 2nd buffer and activate it"
11457 );
11458 assert!(
11459 active_item.is_singleton(cx),
11460 "New active item should be a singleton buffer"
11461 );
11462 assert_eq!(
11463 active_item
11464 .act_as::<Editor>(cx)
11465 .expect("should have navigated into an editor")
11466 .read(cx)
11467 .text(cx),
11468 sample_text_2
11469 );
11470
11471 workspace
11472 .go_back(workspace.active_pane().downgrade(), cx)
11473 .detach_and_log_err(cx);
11474
11475 second_item_id
11476 })
11477 .unwrap();
11478 cx.executor().run_until_parked();
11479 workspace
11480 .update(cx, |workspace, cx| {
11481 let active_item = workspace
11482 .active_item(cx)
11483 .expect("should have an active item after navigating back from the 2nd buffer");
11484 assert_eq!(
11485 active_item.item_id(),
11486 multibuffer_item_id,
11487 "Should navigate back from the 2nd buffer to the multi buffer"
11488 );
11489 assert!(!active_item.is_singleton(cx));
11490 })
11491 .unwrap();
11492
11493 multi_buffer_editor.update(cx, |editor, cx| {
11494 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11495 s.select_ranges(Some(60..70))
11496 });
11497 editor.open_excerpts(&OpenExcerpts, cx);
11498 });
11499 cx.executor().run_until_parked();
11500 workspace
11501 .update(cx, |workspace, cx| {
11502 let active_item = workspace
11503 .active_item(cx)
11504 .expect("should have an active item after navigating into the 3rd buffer");
11505 let third_item_id = active_item.item_id();
11506 assert_ne!(
11507 third_item_id, multibuffer_item_id,
11508 "Should navigate into the 3rd buffer and activate it"
11509 );
11510 assert_ne!(third_item_id, first_item_id);
11511 assert_ne!(third_item_id, second_item_id);
11512 assert!(
11513 active_item.is_singleton(cx),
11514 "New active item should be a singleton buffer"
11515 );
11516 assert_eq!(
11517 active_item
11518 .act_as::<Editor>(cx)
11519 .expect("should have navigated into an editor")
11520 .read(cx)
11521 .text(cx),
11522 sample_text_3
11523 );
11524
11525 workspace
11526 .go_back(workspace.active_pane().downgrade(), cx)
11527 .detach_and_log_err(cx);
11528 })
11529 .unwrap();
11530 cx.executor().run_until_parked();
11531 workspace
11532 .update(cx, |workspace, cx| {
11533 let active_item = workspace
11534 .active_item(cx)
11535 .expect("should have an active item after navigating back from the 3rd buffer");
11536 assert_eq!(
11537 active_item.item_id(),
11538 multibuffer_item_id,
11539 "Should navigate back from the 3rd buffer to the multi buffer"
11540 );
11541 assert!(!active_item.is_singleton(cx));
11542 })
11543 .unwrap();
11544}
11545
11546#[gpui::test]
11547async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11548 init_test(cx, |_| {});
11549
11550 let mut cx = EditorTestContext::new(cx).await;
11551
11552 let diff_base = r#"
11553 use some::mod;
11554
11555 const A: u32 = 42;
11556
11557 fn main() {
11558 println!("hello");
11559
11560 println!("world");
11561 }
11562 "#
11563 .unindent();
11564
11565 cx.set_state(
11566 &r#"
11567 use some::modified;
11568
11569 ˇ
11570 fn main() {
11571 println!("hello there");
11572
11573 println!("around the");
11574 println!("world");
11575 }
11576 "#
11577 .unindent(),
11578 );
11579
11580 cx.set_diff_base(Some(&diff_base));
11581 executor.run_until_parked();
11582
11583 cx.update_editor(|editor, cx| {
11584 editor.go_to_next_hunk(&GoToHunk, cx);
11585 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11586 });
11587 executor.run_until_parked();
11588 cx.assert_diff_hunks(
11589 r#"
11590 use some::modified;
11591
11592
11593 fn main() {
11594 - println!("hello");
11595 + println!("hello there");
11596
11597 println!("around the");
11598 println!("world");
11599 }
11600 "#
11601 .unindent(),
11602 );
11603
11604 cx.update_editor(|editor, cx| {
11605 for _ in 0..3 {
11606 editor.go_to_next_hunk(&GoToHunk, cx);
11607 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11608 }
11609 });
11610 executor.run_until_parked();
11611 cx.assert_editor_state(
11612 &r#"
11613 use some::modified;
11614
11615 ˇ
11616 fn main() {
11617 println!("hello there");
11618
11619 println!("around the");
11620 println!("world");
11621 }
11622 "#
11623 .unindent(),
11624 );
11625
11626 cx.assert_diff_hunks(
11627 r#"
11628 - use some::mod;
11629 + use some::modified;
11630
11631 - const A: u32 = 42;
11632
11633 fn main() {
11634 - println!("hello");
11635 + println!("hello there");
11636
11637 + println!("around the");
11638 println!("world");
11639 }
11640 "#
11641 .unindent(),
11642 );
11643
11644 cx.update_editor(|editor, cx| {
11645 editor.cancel(&Cancel, cx);
11646 });
11647
11648 cx.assert_diff_hunks(
11649 r#"
11650 use some::modified;
11651
11652
11653 fn main() {
11654 println!("hello there");
11655
11656 println!("around the");
11657 println!("world");
11658 }
11659 "#
11660 .unindent(),
11661 );
11662}
11663
11664#[gpui::test]
11665async fn test_diff_base_change_with_expanded_diff_hunks(
11666 executor: BackgroundExecutor,
11667 cx: &mut gpui::TestAppContext,
11668) {
11669 init_test(cx, |_| {});
11670
11671 let mut cx = EditorTestContext::new(cx).await;
11672
11673 let diff_base = r#"
11674 use some::mod1;
11675 use some::mod2;
11676
11677 const A: u32 = 42;
11678 const B: u32 = 42;
11679 const C: u32 = 42;
11680
11681 fn main() {
11682 println!("hello");
11683
11684 println!("world");
11685 }
11686 "#
11687 .unindent();
11688
11689 cx.set_state(
11690 &r#"
11691 use some::mod2;
11692
11693 const A: u32 = 42;
11694 const C: u32 = 42;
11695
11696 fn main(ˇ) {
11697 //println!("hello");
11698
11699 println!("world");
11700 //
11701 //
11702 }
11703 "#
11704 .unindent(),
11705 );
11706
11707 cx.set_diff_base(Some(&diff_base));
11708 executor.run_until_parked();
11709
11710 cx.update_editor(|editor, cx| {
11711 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11712 });
11713 executor.run_until_parked();
11714 cx.assert_diff_hunks(
11715 r#"
11716 - use some::mod1;
11717 use some::mod2;
11718
11719 const A: u32 = 42;
11720 - const B: u32 = 42;
11721 const C: u32 = 42;
11722
11723 fn main() {
11724 - println!("hello");
11725 + //println!("hello");
11726
11727 println!("world");
11728 + //
11729 + //
11730 }
11731 "#
11732 .unindent(),
11733 );
11734
11735 cx.set_diff_base(Some("new diff base!"));
11736 executor.run_until_parked();
11737 cx.assert_diff_hunks(
11738 r#"
11739 use some::mod2;
11740
11741 const A: u32 = 42;
11742 const C: u32 = 42;
11743
11744 fn main() {
11745 //println!("hello");
11746
11747 println!("world");
11748 //
11749 //
11750 }
11751 "#
11752 .unindent(),
11753 );
11754
11755 cx.update_editor(|editor, cx| {
11756 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11757 });
11758 executor.run_until_parked();
11759 cx.assert_diff_hunks(
11760 r#"
11761 - new diff base!
11762 + use some::mod2;
11763 +
11764 + const A: u32 = 42;
11765 + const C: u32 = 42;
11766 +
11767 + fn main() {
11768 + //println!("hello");
11769 +
11770 + println!("world");
11771 + //
11772 + //
11773 + }
11774 "#
11775 .unindent(),
11776 );
11777}
11778
11779#[gpui::test]
11780async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11781 init_test(cx, |_| {});
11782
11783 let mut cx = EditorTestContext::new(cx).await;
11784
11785 let diff_base = r#"
11786 use some::mod1;
11787 use some::mod2;
11788
11789 const A: u32 = 42;
11790 const B: u32 = 42;
11791 const C: u32 = 42;
11792
11793 fn main() {
11794 println!("hello");
11795
11796 println!("world");
11797 }
11798
11799 fn another() {
11800 println!("another");
11801 }
11802
11803 fn another2() {
11804 println!("another2");
11805 }
11806 "#
11807 .unindent();
11808
11809 cx.set_state(
11810 &r#"
11811 «use some::mod2;
11812
11813 const A: u32 = 42;
11814 const C: u32 = 42;
11815
11816 fn main() {
11817 //println!("hello");
11818
11819 println!("world");
11820 //
11821 //ˇ»
11822 }
11823
11824 fn another() {
11825 println!("another");
11826 println!("another");
11827 }
11828
11829 println!("another2");
11830 }
11831 "#
11832 .unindent(),
11833 );
11834
11835 cx.set_diff_base(Some(&diff_base));
11836 executor.run_until_parked();
11837
11838 cx.update_editor(|editor, cx| {
11839 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11840 });
11841 executor.run_until_parked();
11842
11843 cx.assert_diff_hunks(
11844 r#"
11845 - use some::mod1;
11846 use some::mod2;
11847
11848 const A: u32 = 42;
11849 - const B: u32 = 42;
11850 const C: u32 = 42;
11851
11852 fn main() {
11853 - println!("hello");
11854 + //println!("hello");
11855
11856 println!("world");
11857 + //
11858 + //
11859 }
11860
11861 fn another() {
11862 println!("another");
11863 + println!("another");
11864 }
11865
11866 - fn another2() {
11867 println!("another2");
11868 }
11869 "#
11870 .unindent(),
11871 );
11872
11873 // Fold across some of the diff hunks. They should no longer appear expanded.
11874 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
11875 cx.executor().run_until_parked();
11876
11877 // Hunks are not shown if their position is within a fold
11878 cx.assert_diff_hunks(
11879 r#"
11880 use some::mod2;
11881
11882 const A: u32 = 42;
11883 const C: u32 = 42;
11884
11885 fn main() {
11886 //println!("hello");
11887
11888 println!("world");
11889 //
11890 //
11891 }
11892
11893 fn another() {
11894 println!("another");
11895 + println!("another");
11896 }
11897
11898 - fn another2() {
11899 println!("another2");
11900 }
11901 "#
11902 .unindent(),
11903 );
11904
11905 cx.update_editor(|editor, cx| {
11906 editor.select_all(&SelectAll, cx);
11907 editor.unfold_lines(&UnfoldLines, cx);
11908 });
11909 cx.executor().run_until_parked();
11910
11911 // The deletions reappear when unfolding.
11912 cx.assert_diff_hunks(
11913 r#"
11914 - use some::mod1;
11915 use some::mod2;
11916
11917 const A: u32 = 42;
11918 - const B: u32 = 42;
11919 const C: u32 = 42;
11920
11921 fn main() {
11922 - println!("hello");
11923 + //println!("hello");
11924
11925 println!("world");
11926 + //
11927 + //
11928 }
11929
11930 fn another() {
11931 println!("another");
11932 + println!("another");
11933 }
11934
11935 - fn another2() {
11936 println!("another2");
11937 }
11938 "#
11939 .unindent(),
11940 );
11941}
11942
11943#[gpui::test]
11944async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
11945 init_test(cx, |_| {});
11946
11947 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
11948 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
11949 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
11950 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
11951 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
11952 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
11953
11954 let buffer_1 = cx.new_model(|cx| {
11955 let mut buffer = Buffer::local(file_1_new.to_string(), cx);
11956 buffer.set_diff_base(Some(file_1_old.into()), cx);
11957 buffer
11958 });
11959 let buffer_2 = cx.new_model(|cx| {
11960 let mut buffer = Buffer::local(file_2_new.to_string(), cx);
11961 buffer.set_diff_base(Some(file_2_old.into()), cx);
11962 buffer
11963 });
11964 let buffer_3 = cx.new_model(|cx| {
11965 let mut buffer = Buffer::local(file_3_new.to_string(), cx);
11966 buffer.set_diff_base(Some(file_3_old.into()), cx);
11967 buffer
11968 });
11969
11970 let multi_buffer = cx.new_model(|cx| {
11971 let mut multibuffer = MultiBuffer::new(ReadWrite);
11972 multibuffer.push_excerpts(
11973 buffer_1.clone(),
11974 [
11975 ExcerptRange {
11976 context: Point::new(0, 0)..Point::new(3, 0),
11977 primary: None,
11978 },
11979 ExcerptRange {
11980 context: Point::new(5, 0)..Point::new(7, 0),
11981 primary: None,
11982 },
11983 ExcerptRange {
11984 context: Point::new(9, 0)..Point::new(10, 3),
11985 primary: None,
11986 },
11987 ],
11988 cx,
11989 );
11990 multibuffer.push_excerpts(
11991 buffer_2.clone(),
11992 [
11993 ExcerptRange {
11994 context: Point::new(0, 0)..Point::new(3, 0),
11995 primary: None,
11996 },
11997 ExcerptRange {
11998 context: Point::new(5, 0)..Point::new(7, 0),
11999 primary: None,
12000 },
12001 ExcerptRange {
12002 context: Point::new(9, 0)..Point::new(10, 3),
12003 primary: None,
12004 },
12005 ],
12006 cx,
12007 );
12008 multibuffer.push_excerpts(
12009 buffer_3.clone(),
12010 [
12011 ExcerptRange {
12012 context: Point::new(0, 0)..Point::new(3, 0),
12013 primary: None,
12014 },
12015 ExcerptRange {
12016 context: Point::new(5, 0)..Point::new(7, 0),
12017 primary: None,
12018 },
12019 ExcerptRange {
12020 context: Point::new(9, 0)..Point::new(10, 3),
12021 primary: None,
12022 },
12023 ],
12024 cx,
12025 );
12026 multibuffer
12027 });
12028
12029 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12030 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12031 cx.run_until_parked();
12032
12033 cx.assert_editor_state(
12034 &"
12035 ˇaaa
12036 ccc
12037 ddd
12038
12039 ggg
12040 hhh
12041
12042
12043 lll
12044 mmm
12045 NNN
12046
12047 qqq
12048 rrr
12049
12050 uuu
12051 111
12052 222
12053 333
12054
12055 666
12056 777
12057
12058 000
12059 !!!"
12060 .unindent(),
12061 );
12062
12063 cx.update_editor(|editor, cx| {
12064 editor.select_all(&SelectAll, cx);
12065 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12066 });
12067 cx.executor().run_until_parked();
12068
12069 cx.assert_diff_hunks(
12070 "
12071 aaa
12072 - bbb
12073 ccc
12074 ddd
12075
12076 ggg
12077 hhh
12078
12079
12080 lll
12081 mmm
12082 - nnn
12083 + NNN
12084
12085 qqq
12086 rrr
12087
12088 uuu
12089 111
12090 222
12091 333
12092
12093 + 666
12094 777
12095
12096 000
12097 !!!"
12098 .unindent(),
12099 );
12100}
12101
12102#[gpui::test]
12103async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
12104 init_test(cx, |_| {});
12105
12106 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
12107 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
12108
12109 let buffer = cx.new_model(|cx| {
12110 let mut buffer = Buffer::local(text.to_string(), cx);
12111 buffer.set_diff_base(Some(base.into()), cx);
12112 buffer
12113 });
12114
12115 let multi_buffer = cx.new_model(|cx| {
12116 let mut multibuffer = MultiBuffer::new(ReadWrite);
12117 multibuffer.push_excerpts(
12118 buffer.clone(),
12119 [
12120 ExcerptRange {
12121 context: Point::new(0, 0)..Point::new(2, 0),
12122 primary: None,
12123 },
12124 ExcerptRange {
12125 context: Point::new(5, 0)..Point::new(7, 0),
12126 primary: None,
12127 },
12128 ],
12129 cx,
12130 );
12131 multibuffer
12132 });
12133
12134 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12135 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12136 cx.run_until_parked();
12137
12138 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
12139 cx.executor().run_until_parked();
12140
12141 cx.assert_diff_hunks(
12142 "
12143 aaa
12144 - bbb
12145 + BBB
12146
12147 - ddd
12148 - eee
12149 + EEE
12150 fff
12151 "
12152 .unindent(),
12153 );
12154}
12155
12156#[gpui::test]
12157async fn test_edits_around_expanded_insertion_hunks(
12158 executor: BackgroundExecutor,
12159 cx: &mut gpui::TestAppContext,
12160) {
12161 init_test(cx, |_| {});
12162
12163 let mut cx = EditorTestContext::new(cx).await;
12164
12165 let diff_base = r#"
12166 use some::mod1;
12167 use some::mod2;
12168
12169 const A: u32 = 42;
12170
12171 fn main() {
12172 println!("hello");
12173
12174 println!("world");
12175 }
12176 "#
12177 .unindent();
12178 executor.run_until_parked();
12179 cx.set_state(
12180 &r#"
12181 use some::mod1;
12182 use some::mod2;
12183
12184 const A: u32 = 42;
12185 const B: u32 = 42;
12186 const C: u32 = 42;
12187 ˇ
12188
12189 fn main() {
12190 println!("hello");
12191
12192 println!("world");
12193 }
12194 "#
12195 .unindent(),
12196 );
12197
12198 cx.set_diff_base(Some(&diff_base));
12199 executor.run_until_parked();
12200
12201 cx.update_editor(|editor, cx| {
12202 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12203 });
12204 executor.run_until_parked();
12205
12206 cx.assert_diff_hunks(
12207 r#"
12208 use some::mod1;
12209 use some::mod2;
12210
12211 const A: u32 = 42;
12212 + const B: u32 = 42;
12213 + const C: u32 = 42;
12214 +
12215
12216 fn main() {
12217 println!("hello");
12218
12219 println!("world");
12220 }
12221 "#
12222 .unindent(),
12223 );
12224
12225 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
12226 executor.run_until_parked();
12227
12228 cx.assert_diff_hunks(
12229 r#"
12230 use some::mod1;
12231 use some::mod2;
12232
12233 const A: u32 = 42;
12234 + const B: u32 = 42;
12235 + const C: u32 = 42;
12236 + const D: u32 = 42;
12237 +
12238
12239 fn main() {
12240 println!("hello");
12241
12242 println!("world");
12243 }
12244 "#
12245 .unindent(),
12246 );
12247
12248 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
12249 executor.run_until_parked();
12250
12251 cx.assert_diff_hunks(
12252 r#"
12253 use some::mod1;
12254 use some::mod2;
12255
12256 const A: u32 = 42;
12257 + const B: u32 = 42;
12258 + const C: u32 = 42;
12259 + const D: u32 = 42;
12260 + const E: u32 = 42;
12261 +
12262
12263 fn main() {
12264 println!("hello");
12265
12266 println!("world");
12267 }
12268 "#
12269 .unindent(),
12270 );
12271
12272 cx.update_editor(|editor, cx| {
12273 editor.delete_line(&DeleteLine, cx);
12274 });
12275 executor.run_until_parked();
12276
12277 cx.assert_diff_hunks(
12278 r#"
12279 use some::mod1;
12280 use some::mod2;
12281
12282 const A: u32 = 42;
12283 + const B: u32 = 42;
12284 + const C: u32 = 42;
12285 + const D: u32 = 42;
12286 + const E: u32 = 42;
12287
12288 fn main() {
12289 println!("hello");
12290
12291 println!("world");
12292 }
12293 "#
12294 .unindent(),
12295 );
12296
12297 cx.update_editor(|editor, cx| {
12298 editor.move_up(&MoveUp, cx);
12299 editor.delete_line(&DeleteLine, cx);
12300 editor.move_up(&MoveUp, cx);
12301 editor.delete_line(&DeleteLine, cx);
12302 editor.move_up(&MoveUp, cx);
12303 editor.delete_line(&DeleteLine, cx);
12304 });
12305 executor.run_until_parked();
12306 cx.assert_editor_state(
12307 &r#"
12308 use some::mod1;
12309 use some::mod2;
12310
12311 const A: u32 = 42;
12312 const B: u32 = 42;
12313 ˇ
12314 fn main() {
12315 println!("hello");
12316
12317 println!("world");
12318 }
12319 "#
12320 .unindent(),
12321 );
12322
12323 cx.assert_diff_hunks(
12324 r#"
12325 use some::mod1;
12326 use some::mod2;
12327
12328 const A: u32 = 42;
12329 + const B: u32 = 42;
12330
12331 fn main() {
12332 println!("hello");
12333
12334 println!("world");
12335 }
12336 "#
12337 .unindent(),
12338 );
12339
12340 cx.update_editor(|editor, cx| {
12341 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
12342 editor.delete_line(&DeleteLine, cx);
12343 });
12344 executor.run_until_parked();
12345 cx.assert_diff_hunks(
12346 r#"
12347 use some::mod1;
12348 - use some::mod2;
12349 -
12350 - const A: u32 = 42;
12351
12352 fn main() {
12353 println!("hello");
12354
12355 println!("world");
12356 }
12357 "#
12358 .unindent(),
12359 );
12360}
12361
12362#[gpui::test]
12363async fn test_edits_around_expanded_deletion_hunks(
12364 executor: BackgroundExecutor,
12365 cx: &mut gpui::TestAppContext,
12366) {
12367 init_test(cx, |_| {});
12368
12369 let mut cx = EditorTestContext::new(cx).await;
12370
12371 let diff_base = r#"
12372 use some::mod1;
12373 use some::mod2;
12374
12375 const A: u32 = 42;
12376 const B: u32 = 42;
12377 const C: u32 = 42;
12378
12379
12380 fn main() {
12381 println!("hello");
12382
12383 println!("world");
12384 }
12385 "#
12386 .unindent();
12387 executor.run_until_parked();
12388 cx.set_state(
12389 &r#"
12390 use some::mod1;
12391 use some::mod2;
12392
12393 ˇconst B: u32 = 42;
12394 const C: u32 = 42;
12395
12396
12397 fn main() {
12398 println!("hello");
12399
12400 println!("world");
12401 }
12402 "#
12403 .unindent(),
12404 );
12405
12406 cx.set_diff_base(Some(&diff_base));
12407 executor.run_until_parked();
12408
12409 cx.update_editor(|editor, cx| {
12410 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12411 });
12412 executor.run_until_parked();
12413
12414 cx.assert_diff_hunks(
12415 r#"
12416 use some::mod1;
12417 use some::mod2;
12418
12419 - const A: u32 = 42;
12420 const B: u32 = 42;
12421 const C: u32 = 42;
12422
12423
12424 fn main() {
12425 println!("hello");
12426
12427 println!("world");
12428 }
12429 "#
12430 .unindent(),
12431 );
12432
12433 cx.update_editor(|editor, cx| {
12434 editor.delete_line(&DeleteLine, cx);
12435 });
12436 executor.run_until_parked();
12437 cx.assert_editor_state(
12438 &r#"
12439 use some::mod1;
12440 use some::mod2;
12441
12442 ˇconst C: u32 = 42;
12443
12444
12445 fn main() {
12446 println!("hello");
12447
12448 println!("world");
12449 }
12450 "#
12451 .unindent(),
12452 );
12453 cx.assert_diff_hunks(
12454 r#"
12455 use some::mod1;
12456 use some::mod2;
12457
12458 - const A: u32 = 42;
12459 - const B: u32 = 42;
12460 const C: u32 = 42;
12461
12462
12463 fn main() {
12464 println!("hello");
12465
12466 println!("world");
12467 }
12468 "#
12469 .unindent(),
12470 );
12471
12472 cx.update_editor(|editor, cx| {
12473 editor.delete_line(&DeleteLine, cx);
12474 });
12475 executor.run_until_parked();
12476 cx.assert_editor_state(
12477 &r#"
12478 use some::mod1;
12479 use some::mod2;
12480
12481 ˇ
12482
12483 fn main() {
12484 println!("hello");
12485
12486 println!("world");
12487 }
12488 "#
12489 .unindent(),
12490 );
12491 cx.assert_diff_hunks(
12492 r#"
12493 use some::mod1;
12494 use some::mod2;
12495
12496 - const A: u32 = 42;
12497 - const B: u32 = 42;
12498 - const C: u32 = 42;
12499
12500
12501 fn main() {
12502 println!("hello");
12503
12504 println!("world");
12505 }
12506 "#
12507 .unindent(),
12508 );
12509
12510 cx.update_editor(|editor, cx| {
12511 editor.handle_input("replacement", cx);
12512 });
12513 executor.run_until_parked();
12514 cx.assert_editor_state(
12515 &r#"
12516 use some::mod1;
12517 use some::mod2;
12518
12519 replacementˇ
12520
12521 fn main() {
12522 println!("hello");
12523
12524 println!("world");
12525 }
12526 "#
12527 .unindent(),
12528 );
12529 cx.assert_diff_hunks(
12530 r#"
12531 use some::mod1;
12532 use some::mod2;
12533
12534 - const A: u32 = 42;
12535 - const B: u32 = 42;
12536 - const C: u32 = 42;
12537 -
12538 + replacement
12539
12540 fn main() {
12541 println!("hello");
12542
12543 println!("world");
12544 }
12545 "#
12546 .unindent(),
12547 );
12548}
12549
12550#[gpui::test]
12551async fn test_edit_after_expanded_modification_hunk(
12552 executor: BackgroundExecutor,
12553 cx: &mut gpui::TestAppContext,
12554) {
12555 init_test(cx, |_| {});
12556
12557 let mut cx = EditorTestContext::new(cx).await;
12558
12559 let diff_base = r#"
12560 use some::mod1;
12561 use some::mod2;
12562
12563 const A: u32 = 42;
12564 const B: u32 = 42;
12565 const C: u32 = 42;
12566 const D: u32 = 42;
12567
12568
12569 fn main() {
12570 println!("hello");
12571
12572 println!("world");
12573 }"#
12574 .unindent();
12575
12576 cx.set_state(
12577 &r#"
12578 use some::mod1;
12579 use some::mod2;
12580
12581 const A: u32 = 42;
12582 const B: u32 = 42;
12583 const C: u32 = 43ˇ
12584 const D: u32 = 42;
12585
12586
12587 fn main() {
12588 println!("hello");
12589
12590 println!("world");
12591 }"#
12592 .unindent(),
12593 );
12594
12595 cx.set_diff_base(Some(&diff_base));
12596 executor.run_until_parked();
12597 cx.update_editor(|editor, cx| {
12598 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12599 });
12600 executor.run_until_parked();
12601
12602 cx.assert_diff_hunks(
12603 r#"
12604 use some::mod1;
12605 use some::mod2;
12606
12607 const A: u32 = 42;
12608 const B: u32 = 42;
12609 - const C: u32 = 42;
12610 + const C: u32 = 43
12611 const D: u32 = 42;
12612
12613
12614 fn main() {
12615 println!("hello");
12616
12617 println!("world");
12618 }"#
12619 .unindent(),
12620 );
12621
12622 cx.update_editor(|editor, cx| {
12623 editor.handle_input("\nnew_line\n", cx);
12624 });
12625 executor.run_until_parked();
12626
12627 cx.assert_diff_hunks(
12628 r#"
12629 use some::mod1;
12630 use some::mod2;
12631
12632 const A: u32 = 42;
12633 const B: u32 = 42;
12634 - const C: u32 = 42;
12635 + const C: u32 = 43
12636 + new_line
12637 +
12638 const D: u32 = 42;
12639
12640
12641 fn main() {
12642 println!("hello");
12643
12644 println!("world");
12645 }"#
12646 .unindent(),
12647 );
12648}
12649
12650async fn setup_indent_guides_editor(
12651 text: &str,
12652 cx: &mut gpui::TestAppContext,
12653) -> (BufferId, EditorTestContext) {
12654 init_test(cx, |_| {});
12655
12656 let mut cx = EditorTestContext::new(cx).await;
12657
12658 let buffer_id = cx.update_editor(|editor, cx| {
12659 editor.set_text(text, cx);
12660 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12661
12662 buffer_ids[0]
12663 });
12664
12665 (buffer_id, cx)
12666}
12667
12668fn assert_indent_guides(
12669 range: Range<u32>,
12670 expected: Vec<IndentGuide>,
12671 active_indices: Option<Vec<usize>>,
12672 cx: &mut EditorTestContext,
12673) {
12674 let indent_guides = cx.update_editor(|editor, cx| {
12675 let snapshot = editor.snapshot(cx).display_snapshot;
12676 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12677 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12678 true,
12679 &snapshot,
12680 cx,
12681 );
12682
12683 indent_guides.sort_by(|a, b| {
12684 a.depth.cmp(&b.depth).then(
12685 a.start_row
12686 .cmp(&b.start_row)
12687 .then(a.end_row.cmp(&b.end_row)),
12688 )
12689 });
12690 indent_guides
12691 });
12692
12693 if let Some(expected) = active_indices {
12694 let active_indices = cx.update_editor(|editor, cx| {
12695 let snapshot = editor.snapshot(cx).display_snapshot;
12696 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
12697 });
12698
12699 assert_eq!(
12700 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
12701 expected,
12702 "Active indent guide indices do not match"
12703 );
12704 }
12705
12706 let expected: Vec<_> = expected
12707 .into_iter()
12708 .map(|guide| MultiBufferIndentGuide {
12709 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
12710 buffer: guide,
12711 })
12712 .collect();
12713
12714 assert_eq!(indent_guides, expected, "Indent guides do not match");
12715}
12716
12717fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
12718 IndentGuide {
12719 buffer_id,
12720 start_row,
12721 end_row,
12722 depth,
12723 tab_size: 4,
12724 settings: IndentGuideSettings {
12725 enabled: true,
12726 line_width: 1,
12727 active_line_width: 1,
12728 ..Default::default()
12729 },
12730 }
12731}
12732
12733#[gpui::test]
12734async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12735 let (buffer_id, mut cx) = setup_indent_guides_editor(
12736 &"
12737 fn main() {
12738 let a = 1;
12739 }"
12740 .unindent(),
12741 cx,
12742 )
12743 .await;
12744
12745 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12746}
12747
12748#[gpui::test]
12749async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
12750 let (buffer_id, mut cx) = setup_indent_guides_editor(
12751 &"
12752 fn main() {
12753 let a = 1;
12754 let b = 2;
12755 }"
12756 .unindent(),
12757 cx,
12758 )
12759 .await;
12760
12761 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
12762}
12763
12764#[gpui::test]
12765async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
12766 let (buffer_id, mut cx) = setup_indent_guides_editor(
12767 &"
12768 fn main() {
12769 let a = 1;
12770 if a == 3 {
12771 let b = 2;
12772 } else {
12773 let c = 3;
12774 }
12775 }"
12776 .unindent(),
12777 cx,
12778 )
12779 .await;
12780
12781 assert_indent_guides(
12782 0..8,
12783 vec![
12784 indent_guide(buffer_id, 1, 6, 0),
12785 indent_guide(buffer_id, 3, 3, 1),
12786 indent_guide(buffer_id, 5, 5, 1),
12787 ],
12788 None,
12789 &mut cx,
12790 );
12791}
12792
12793#[gpui::test]
12794async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
12795 let (buffer_id, mut cx) = setup_indent_guides_editor(
12796 &"
12797 fn main() {
12798 let a = 1;
12799 let b = 2;
12800 let c = 3;
12801 }"
12802 .unindent(),
12803 cx,
12804 )
12805 .await;
12806
12807 assert_indent_guides(
12808 0..5,
12809 vec![
12810 indent_guide(buffer_id, 1, 3, 0),
12811 indent_guide(buffer_id, 2, 2, 1),
12812 ],
12813 None,
12814 &mut cx,
12815 );
12816}
12817
12818#[gpui::test]
12819async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
12820 let (buffer_id, mut cx) = setup_indent_guides_editor(
12821 &"
12822 fn main() {
12823 let a = 1;
12824
12825 let c = 3;
12826 }"
12827 .unindent(),
12828 cx,
12829 )
12830 .await;
12831
12832 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
12833}
12834
12835#[gpui::test]
12836async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
12837 let (buffer_id, mut cx) = setup_indent_guides_editor(
12838 &"
12839 fn main() {
12840 let a = 1;
12841
12842 let c = 3;
12843
12844 if a == 3 {
12845 let b = 2;
12846 } else {
12847 let c = 3;
12848 }
12849 }"
12850 .unindent(),
12851 cx,
12852 )
12853 .await;
12854
12855 assert_indent_guides(
12856 0..11,
12857 vec![
12858 indent_guide(buffer_id, 1, 9, 0),
12859 indent_guide(buffer_id, 6, 6, 1),
12860 indent_guide(buffer_id, 8, 8, 1),
12861 ],
12862 None,
12863 &mut cx,
12864 );
12865}
12866
12867#[gpui::test]
12868async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
12869 let (buffer_id, mut cx) = setup_indent_guides_editor(
12870 &"
12871 fn main() {
12872 let a = 1;
12873
12874 let c = 3;
12875
12876 if a == 3 {
12877 let b = 2;
12878 } else {
12879 let c = 3;
12880 }
12881 }"
12882 .unindent(),
12883 cx,
12884 )
12885 .await;
12886
12887 assert_indent_guides(
12888 1..11,
12889 vec![
12890 indent_guide(buffer_id, 1, 9, 0),
12891 indent_guide(buffer_id, 6, 6, 1),
12892 indent_guide(buffer_id, 8, 8, 1),
12893 ],
12894 None,
12895 &mut cx,
12896 );
12897}
12898
12899#[gpui::test]
12900async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
12901 let (buffer_id, mut cx) = setup_indent_guides_editor(
12902 &"
12903 fn main() {
12904 let a = 1;
12905
12906 let c = 3;
12907
12908 if a == 3 {
12909 let b = 2;
12910 } else {
12911 let c = 3;
12912 }
12913 }"
12914 .unindent(),
12915 cx,
12916 )
12917 .await;
12918
12919 assert_indent_guides(
12920 1..10,
12921 vec![
12922 indent_guide(buffer_id, 1, 9, 0),
12923 indent_guide(buffer_id, 6, 6, 1),
12924 indent_guide(buffer_id, 8, 8, 1),
12925 ],
12926 None,
12927 &mut cx,
12928 );
12929}
12930
12931#[gpui::test]
12932async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
12933 let (buffer_id, mut cx) = setup_indent_guides_editor(
12934 &"
12935 block1
12936 block2
12937 block3
12938 block4
12939 block2
12940 block1
12941 block1"
12942 .unindent(),
12943 cx,
12944 )
12945 .await;
12946
12947 assert_indent_guides(
12948 1..10,
12949 vec![
12950 indent_guide(buffer_id, 1, 4, 0),
12951 indent_guide(buffer_id, 2, 3, 1),
12952 indent_guide(buffer_id, 3, 3, 2),
12953 ],
12954 None,
12955 &mut cx,
12956 );
12957}
12958
12959#[gpui::test]
12960async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
12961 let (buffer_id, mut cx) = setup_indent_guides_editor(
12962 &"
12963 block1
12964 block2
12965 block3
12966
12967 block1
12968 block1"
12969 .unindent(),
12970 cx,
12971 )
12972 .await;
12973
12974 assert_indent_guides(
12975 0..6,
12976 vec![
12977 indent_guide(buffer_id, 1, 2, 0),
12978 indent_guide(buffer_id, 2, 2, 1),
12979 ],
12980 None,
12981 &mut cx,
12982 );
12983}
12984
12985#[gpui::test]
12986async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
12987 let (buffer_id, mut cx) = setup_indent_guides_editor(
12988 &"
12989 block1
12990
12991
12992
12993 block2
12994 "
12995 .unindent(),
12996 cx,
12997 )
12998 .await;
12999
13000 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13001}
13002
13003#[gpui::test]
13004async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
13005 let (buffer_id, mut cx) = setup_indent_guides_editor(
13006 &"
13007 def a:
13008 \tb = 3
13009 \tif True:
13010 \t\tc = 4
13011 \t\td = 5
13012 \tprint(b)
13013 "
13014 .unindent(),
13015 cx,
13016 )
13017 .await;
13018
13019 assert_indent_guides(
13020 0..6,
13021 vec![
13022 indent_guide(buffer_id, 1, 6, 0),
13023 indent_guide(buffer_id, 3, 4, 1),
13024 ],
13025 None,
13026 &mut cx,
13027 );
13028}
13029
13030#[gpui::test]
13031async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13032 let (buffer_id, mut cx) = setup_indent_guides_editor(
13033 &"
13034 fn main() {
13035 let a = 1;
13036 }"
13037 .unindent(),
13038 cx,
13039 )
13040 .await;
13041
13042 cx.update_editor(|editor, cx| {
13043 editor.change_selections(None, cx, |s| {
13044 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13045 });
13046 });
13047
13048 assert_indent_guides(
13049 0..3,
13050 vec![indent_guide(buffer_id, 1, 1, 0)],
13051 Some(vec![0]),
13052 &mut cx,
13053 );
13054}
13055
13056#[gpui::test]
13057async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13058 let (buffer_id, mut cx) = setup_indent_guides_editor(
13059 &"
13060 fn main() {
13061 if 1 == 2 {
13062 let a = 1;
13063 }
13064 }"
13065 .unindent(),
13066 cx,
13067 )
13068 .await;
13069
13070 cx.update_editor(|editor, cx| {
13071 editor.change_selections(None, cx, |s| {
13072 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13073 });
13074 });
13075
13076 assert_indent_guides(
13077 0..4,
13078 vec![
13079 indent_guide(buffer_id, 1, 3, 0),
13080 indent_guide(buffer_id, 2, 2, 1),
13081 ],
13082 Some(vec![1]),
13083 &mut cx,
13084 );
13085
13086 cx.update_editor(|editor, cx| {
13087 editor.change_selections(None, cx, |s| {
13088 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13089 });
13090 });
13091
13092 assert_indent_guides(
13093 0..4,
13094 vec![
13095 indent_guide(buffer_id, 1, 3, 0),
13096 indent_guide(buffer_id, 2, 2, 1),
13097 ],
13098 Some(vec![1]),
13099 &mut cx,
13100 );
13101
13102 cx.update_editor(|editor, cx| {
13103 editor.change_selections(None, cx, |s| {
13104 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13105 });
13106 });
13107
13108 assert_indent_guides(
13109 0..4,
13110 vec![
13111 indent_guide(buffer_id, 1, 3, 0),
13112 indent_guide(buffer_id, 2, 2, 1),
13113 ],
13114 Some(vec![0]),
13115 &mut cx,
13116 );
13117}
13118
13119#[gpui::test]
13120async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13121 let (buffer_id, mut cx) = setup_indent_guides_editor(
13122 &"
13123 fn main() {
13124 let a = 1;
13125
13126 let b = 2;
13127 }"
13128 .unindent(),
13129 cx,
13130 )
13131 .await;
13132
13133 cx.update_editor(|editor, cx| {
13134 editor.change_selections(None, cx, |s| {
13135 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13136 });
13137 });
13138
13139 assert_indent_guides(
13140 0..5,
13141 vec![indent_guide(buffer_id, 1, 3, 0)],
13142 Some(vec![0]),
13143 &mut cx,
13144 );
13145}
13146
13147#[gpui::test]
13148async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13149 let (buffer_id, mut cx) = setup_indent_guides_editor(
13150 &"
13151 def m:
13152 a = 1
13153 pass"
13154 .unindent(),
13155 cx,
13156 )
13157 .await;
13158
13159 cx.update_editor(|editor, cx| {
13160 editor.change_selections(None, cx, |s| {
13161 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13162 });
13163 });
13164
13165 assert_indent_guides(
13166 0..3,
13167 vec![indent_guide(buffer_id, 1, 2, 0)],
13168 Some(vec![0]),
13169 &mut cx,
13170 );
13171}
13172
13173#[gpui::test]
13174fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13175 init_test(cx, |_| {});
13176
13177 let editor = cx.add_window(|cx| {
13178 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13179 build_editor(buffer, cx)
13180 });
13181
13182 let render_args = Arc::new(Mutex::new(None));
13183 let snapshot = editor
13184 .update(cx, |editor, cx| {
13185 let snapshot = editor.buffer().read(cx).snapshot(cx);
13186 let range =
13187 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13188
13189 struct RenderArgs {
13190 row: MultiBufferRow,
13191 folded: bool,
13192 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13193 }
13194
13195 let crease = Crease::new(
13196 range,
13197 FoldPlaceholder::test(),
13198 {
13199 let toggle_callback = render_args.clone();
13200 move |row, folded, callback, _cx| {
13201 *toggle_callback.lock() = Some(RenderArgs {
13202 row,
13203 folded,
13204 callback,
13205 });
13206 div()
13207 }
13208 },
13209 |_row, _folded, _cx| div(),
13210 );
13211
13212 editor.insert_creases(Some(crease), cx);
13213 let snapshot = editor.snapshot(cx);
13214 let _div = snapshot.render_fold_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13215 snapshot
13216 })
13217 .unwrap();
13218
13219 let render_args = render_args.lock().take().unwrap();
13220 assert_eq!(render_args.row, MultiBufferRow(1));
13221 assert!(!render_args.folded);
13222 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13223
13224 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13225 .unwrap();
13226 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13227 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13228
13229 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13230 .unwrap();
13231 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13232 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13233}
13234
13235#[gpui::test]
13236async fn test_input_text(cx: &mut gpui::TestAppContext) {
13237 init_test(cx, |_| {});
13238 let mut cx = EditorTestContext::new(cx).await;
13239
13240 cx.set_state(
13241 &r#"ˇone
13242 two
13243
13244 three
13245 fourˇ
13246 five
13247
13248 siˇx"#
13249 .unindent(),
13250 );
13251
13252 cx.dispatch_action(HandleInput(String::new()));
13253 cx.assert_editor_state(
13254 &r#"ˇone
13255 two
13256
13257 three
13258 fourˇ
13259 five
13260
13261 siˇx"#
13262 .unindent(),
13263 );
13264
13265 cx.dispatch_action(HandleInput("AAAA".to_string()));
13266 cx.assert_editor_state(
13267 &r#"AAAAˇone
13268 two
13269
13270 three
13271 fourAAAAˇ
13272 five
13273
13274 siAAAAˇx"#
13275 .unindent(),
13276 );
13277}
13278
13279#[gpui::test]
13280async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13281 init_test(cx, |_| {});
13282
13283 let mut cx = EditorTestContext::new(cx).await;
13284 cx.set_state(
13285 r#"let foo = 1;
13286let foo = 2;
13287let foo = 3;
13288let fooˇ = 4;
13289let foo = 5;
13290let foo = 6;
13291let foo = 7;
13292let foo = 8;
13293let foo = 9;
13294let foo = 10;
13295let foo = 11;
13296let foo = 12;
13297let foo = 13;
13298let foo = 14;
13299let foo = 15;"#,
13300 );
13301
13302 cx.update_editor(|e, cx| {
13303 assert_eq!(
13304 e.next_scroll_position,
13305 NextScrollCursorCenterTopBottom::Center,
13306 "Default next scroll direction is center",
13307 );
13308
13309 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13310 assert_eq!(
13311 e.next_scroll_position,
13312 NextScrollCursorCenterTopBottom::Top,
13313 "After center, next scroll direction should be top",
13314 );
13315
13316 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13317 assert_eq!(
13318 e.next_scroll_position,
13319 NextScrollCursorCenterTopBottom::Bottom,
13320 "After top, next scroll direction should be bottom",
13321 );
13322
13323 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13324 assert_eq!(
13325 e.next_scroll_position,
13326 NextScrollCursorCenterTopBottom::Center,
13327 "After bottom, scrolling should start over",
13328 );
13329
13330 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13331 assert_eq!(
13332 e.next_scroll_position,
13333 NextScrollCursorCenterTopBottom::Top,
13334 "Scrolling continues if retriggered fast enough"
13335 );
13336 });
13337
13338 cx.executor()
13339 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13340 cx.executor().run_until_parked();
13341 cx.update_editor(|e, _| {
13342 assert_eq!(
13343 e.next_scroll_position,
13344 NextScrollCursorCenterTopBottom::Center,
13345 "If scrolling is not triggered fast enough, it should reset"
13346 );
13347 });
13348}
13349
13350#[gpui::test]
13351async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13352 init_test(cx, |_| {});
13353 let mut cx = EditorLspTestContext::new_rust(
13354 lsp::ServerCapabilities {
13355 definition_provider: Some(lsp::OneOf::Left(true)),
13356 references_provider: Some(lsp::OneOf::Left(true)),
13357 ..lsp::ServerCapabilities::default()
13358 },
13359 cx,
13360 )
13361 .await;
13362
13363 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13364 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13365 move |params, _| async move {
13366 if empty_go_to_definition {
13367 Ok(None)
13368 } else {
13369 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13370 uri: params.text_document_position_params.text_document.uri,
13371 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13372 })))
13373 }
13374 },
13375 );
13376 let references =
13377 cx.lsp
13378 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13379 Ok(Some(vec![lsp::Location {
13380 uri: params.text_document_position.text_document.uri,
13381 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13382 }]))
13383 });
13384 (go_to_definition, references)
13385 };
13386
13387 cx.set_state(
13388 &r#"fn one() {
13389 let mut a = ˇtwo();
13390 }
13391
13392 fn two() {}"#
13393 .unindent(),
13394 );
13395 set_up_lsp_handlers(false, &mut cx);
13396 let navigated = cx
13397 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13398 .await
13399 .expect("Failed to navigate to definition");
13400 assert_eq!(
13401 navigated,
13402 Navigated::Yes,
13403 "Should have navigated to definition from the GetDefinition response"
13404 );
13405 cx.assert_editor_state(
13406 &r#"fn one() {
13407 let mut a = two();
13408 }
13409
13410 fn «twoˇ»() {}"#
13411 .unindent(),
13412 );
13413
13414 let editors = cx.update_workspace(|workspace, cx| {
13415 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13416 });
13417 cx.update_editor(|_, test_editor_cx| {
13418 assert_eq!(
13419 editors.len(),
13420 1,
13421 "Initially, only one, test, editor should be open in the workspace"
13422 );
13423 assert_eq!(
13424 test_editor_cx.view(),
13425 editors.last().expect("Asserted len is 1")
13426 );
13427 });
13428
13429 set_up_lsp_handlers(true, &mut cx);
13430 let navigated = cx
13431 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13432 .await
13433 .expect("Failed to navigate to lookup references");
13434 assert_eq!(
13435 navigated,
13436 Navigated::Yes,
13437 "Should have navigated to references as a fallback after empty GoToDefinition response"
13438 );
13439 // We should not change the selections in the existing file,
13440 // if opening another milti buffer with the references
13441 cx.assert_editor_state(
13442 &r#"fn one() {
13443 let mut a = two();
13444 }
13445
13446 fn «twoˇ»() {}"#
13447 .unindent(),
13448 );
13449 let editors = cx.update_workspace(|workspace, cx| {
13450 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13451 });
13452 cx.update_editor(|_, test_editor_cx| {
13453 assert_eq!(
13454 editors.len(),
13455 2,
13456 "After falling back to references search, we open a new editor with the results"
13457 );
13458 let references_fallback_text = editors
13459 .into_iter()
13460 .find(|new_editor| new_editor != test_editor_cx.view())
13461 .expect("Should have one non-test editor now")
13462 .read(test_editor_cx)
13463 .text(test_editor_cx);
13464 assert_eq!(
13465 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13466 "Should use the range from the references response and not the GoToDefinition one"
13467 );
13468 });
13469}
13470
13471#[gpui::test]
13472async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
13473 init_test(cx, |_| {});
13474
13475 let language = Arc::new(Language::new(
13476 LanguageConfig::default(),
13477 Some(tree_sitter_rust::LANGUAGE.into()),
13478 ));
13479
13480 let text = r#"
13481 #[cfg(test)]
13482 mod tests() {
13483 #[test]
13484 fn runnable_1() {
13485 let a = 1;
13486 }
13487
13488 #[test]
13489 fn runnable_2() {
13490 let a = 1;
13491 let b = 2;
13492 }
13493 }
13494 "#
13495 .unindent();
13496
13497 let fs = FakeFs::new(cx.executor());
13498 fs.insert_file("/file.rs", Default::default()).await;
13499
13500 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13501 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
13502 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13503 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
13504 let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
13505
13506 let editor = cx.new_view(|cx| {
13507 Editor::new(
13508 EditorMode::Full,
13509 multi_buffer,
13510 Some(project.clone()),
13511 true,
13512 cx,
13513 )
13514 });
13515
13516 editor.update(cx, |editor, cx| {
13517 editor.tasks.insert(
13518 (buffer.read(cx).remote_id(), 3),
13519 RunnableTasks {
13520 templates: vec![],
13521 offset: MultiBufferOffset(43),
13522 column: 0,
13523 extra_variables: HashMap::default(),
13524 context_range: BufferOffset(43)..BufferOffset(85),
13525 },
13526 );
13527 editor.tasks.insert(
13528 (buffer.read(cx).remote_id(), 8),
13529 RunnableTasks {
13530 templates: vec![],
13531 offset: MultiBufferOffset(86),
13532 column: 0,
13533 extra_variables: HashMap::default(),
13534 context_range: BufferOffset(86)..BufferOffset(191),
13535 },
13536 );
13537
13538 // Test finding task when cursor is inside function body
13539 editor.change_selections(None, cx, |s| {
13540 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
13541 });
13542 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13543 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
13544
13545 // Test finding task when cursor is on function name
13546 editor.change_selections(None, cx, |s| {
13547 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
13548 });
13549 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13550 assert_eq!(row, 8, "Should find task when cursor is on function name");
13551 });
13552}
13553
13554fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13555 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13556 point..point
13557}
13558
13559fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13560 let (text, ranges) = marked_text_ranges(marked_text, true);
13561 assert_eq!(view.text(cx), text);
13562 assert_eq!(
13563 view.selections.ranges(cx),
13564 ranges,
13565 "Assert selections are {}",
13566 marked_text
13567 );
13568}
13569
13570pub fn handle_signature_help_request(
13571 cx: &mut EditorLspTestContext,
13572 mocked_response: lsp::SignatureHelp,
13573) -> impl Future<Output = ()> {
13574 let mut request =
13575 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13576 let mocked_response = mocked_response.clone();
13577 async move { Ok(Some(mocked_response)) }
13578 });
13579
13580 async move {
13581 request.next().await;
13582 }
13583}
13584
13585/// Handle completion request passing a marked string specifying where the completion
13586/// should be triggered from using '|' character, what range should be replaced, and what completions
13587/// should be returned using '<' and '>' to delimit the range
13588pub fn handle_completion_request(
13589 cx: &mut EditorLspTestContext,
13590 marked_string: &str,
13591 completions: Vec<&'static str>,
13592 counter: Arc<AtomicUsize>,
13593) -> impl Future<Output = ()> {
13594 let complete_from_marker: TextRangeMarker = '|'.into();
13595 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13596 let (_, mut marked_ranges) = marked_text_ranges_by(
13597 marked_string,
13598 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13599 );
13600
13601 let complete_from_position =
13602 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13603 let replace_range =
13604 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13605
13606 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13607 let completions = completions.clone();
13608 counter.fetch_add(1, atomic::Ordering::Release);
13609 async move {
13610 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13611 assert_eq!(
13612 params.text_document_position.position,
13613 complete_from_position
13614 );
13615 Ok(Some(lsp::CompletionResponse::Array(
13616 completions
13617 .iter()
13618 .map(|completion_text| lsp::CompletionItem {
13619 label: completion_text.to_string(),
13620 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13621 range: replace_range,
13622 new_text: completion_text.to_string(),
13623 })),
13624 ..Default::default()
13625 })
13626 .collect(),
13627 )))
13628 }
13629 });
13630
13631 async move {
13632 request.next().await;
13633 }
13634}
13635
13636fn handle_resolve_completion_request(
13637 cx: &mut EditorLspTestContext,
13638 edits: Option<Vec<(&'static str, &'static str)>>,
13639) -> impl Future<Output = ()> {
13640 let edits = edits.map(|edits| {
13641 edits
13642 .iter()
13643 .map(|(marked_string, new_text)| {
13644 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13645 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13646 lsp::TextEdit::new(replace_range, new_text.to_string())
13647 })
13648 .collect::<Vec<_>>()
13649 });
13650
13651 let mut request =
13652 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13653 let edits = edits.clone();
13654 async move {
13655 Ok(lsp::CompletionItem {
13656 additional_text_edits: edits,
13657 ..Default::default()
13658 })
13659 }
13660 });
13661
13662 async move {
13663 request.next().await;
13664 }
13665}
13666
13667pub(crate) fn update_test_language_settings(
13668 cx: &mut TestAppContext,
13669 f: impl Fn(&mut AllLanguageSettingsContent),
13670) {
13671 cx.update(|cx| {
13672 SettingsStore::update_global(cx, |store, cx| {
13673 store.update_user_settings::<AllLanguageSettings>(cx, f);
13674 });
13675 });
13676}
13677
13678pub(crate) fn update_test_project_settings(
13679 cx: &mut TestAppContext,
13680 f: impl Fn(&mut ProjectSettings),
13681) {
13682 cx.update(|cx| {
13683 SettingsStore::update_global(cx, |store, cx| {
13684 store.update_user_settings::<ProjectSettings>(cx, f);
13685 });
13686 });
13687}
13688
13689pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
13690 cx.update(|cx| {
13691 assets::Assets.load_test_fonts(cx);
13692 let store = SettingsStore::test(cx);
13693 cx.set_global(store);
13694 theme::init(theme::LoadThemes::JustBase, cx);
13695 release_channel::init(SemanticVersion::default(), cx);
13696 client::init_settings(cx);
13697 language::init(cx);
13698 Project::init_settings(cx);
13699 workspace::init_settings(cx);
13700 crate::init(cx);
13701 });
13702
13703 update_test_language_settings(cx, f);
13704}
13705
13706pub(crate) fn rust_lang() -> Arc<Language> {
13707 Arc::new(Language::new(
13708 LanguageConfig {
13709 name: "Rust".into(),
13710 matcher: LanguageMatcher {
13711 path_suffixes: vec!["rs".to_string()],
13712 ..Default::default()
13713 },
13714 ..Default::default()
13715 },
13716 Some(tree_sitter_rust::LANGUAGE.into()),
13717 ))
13718}
13719
13720#[track_caller]
13721fn assert_hunk_revert(
13722 not_reverted_text_with_selections: &str,
13723 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
13724 expected_reverted_text_with_selections: &str,
13725 base_text: &str,
13726 cx: &mut EditorLspTestContext,
13727) {
13728 cx.set_state(not_reverted_text_with_selections);
13729 cx.update_editor(|editor, cx| {
13730 editor
13731 .buffer()
13732 .read(cx)
13733 .as_singleton()
13734 .unwrap()
13735 .update(cx, |buffer, cx| {
13736 buffer.set_diff_base(Some(base_text.into()), cx);
13737 });
13738 });
13739 cx.executor().run_until_parked();
13740
13741 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
13742 let snapshot = editor.buffer().read(cx).snapshot(cx);
13743 let reverted_hunk_statuses = snapshot
13744 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
13745 .map(|hunk| hunk_status(&hunk))
13746 .collect::<Vec<_>>();
13747
13748 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
13749 reverted_hunk_statuses
13750 });
13751 cx.executor().run_until_parked();
13752 cx.assert_editor_state(expected_reverted_text_with_selections);
13753 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
13754}