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::AtomicUsize;
35use std::sync::atomic::{self, AtomicBool};
36use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
37use unindent::Unindent;
38use util::{
39 assert_set_eq,
40 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
41};
42use workspace::{
43 item::{FollowEvent, FollowableItem, Item, ItemHandle},
44 NavigationEntry, ViewId,
45};
46
47#[gpui::test]
48fn test_edit_events(cx: &mut TestAppContext) {
49 init_test(cx, |_| {});
50
51 let buffer = cx.new_model(|cx| {
52 let mut buffer = language::Buffer::local("123456", cx);
53 buffer.set_group_interval(Duration::from_secs(1));
54 buffer
55 });
56
57 let events = Rc::new(RefCell::new(Vec::new()));
58 let editor1 = cx.add_window({
59 let events = events.clone();
60 |cx| {
61 let view = cx.view().clone();
62 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
63 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
64 EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
65 _ => {}
66 })
67 .detach();
68 Editor::for_buffer(buffer.clone(), None, cx)
69 }
70 });
71
72 let editor2 = cx.add_window({
73 let events = events.clone();
74 |cx| {
75 cx.subscribe(
76 &cx.view().clone(),
77 move |_, _, event: &EditorEvent, _| match event {
78 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
79 EditorEvent::BufferEdited => {
80 events.borrow_mut().push(("editor2", "buffer edited"))
81 }
82 _ => {}
83 },
84 )
85 .detach();
86 Editor::for_buffer(buffer.clone(), None, cx)
87 }
88 });
89
90 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
91
92 // Mutating editor 1 will emit an `Edited` event only for that editor.
93 _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
94 assert_eq!(
95 mem::take(&mut *events.borrow_mut()),
96 [
97 ("editor1", "edited"),
98 ("editor1", "buffer edited"),
99 ("editor2", "buffer edited"),
100 ]
101 );
102
103 // Mutating editor 2 will emit an `Edited` event only for that editor.
104 _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
105 assert_eq!(
106 mem::take(&mut *events.borrow_mut()),
107 [
108 ("editor2", "edited"),
109 ("editor1", "buffer edited"),
110 ("editor2", "buffer edited"),
111 ]
112 );
113
114 // Undoing on editor 1 will emit an `Edited` event only for that editor.
115 _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
116 assert_eq!(
117 mem::take(&mut *events.borrow_mut()),
118 [
119 ("editor1", "edited"),
120 ("editor1", "buffer edited"),
121 ("editor2", "buffer edited"),
122 ]
123 );
124
125 // Redoing on editor 1 will emit an `Edited` event only for that editor.
126 _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
127 assert_eq!(
128 mem::take(&mut *events.borrow_mut()),
129 [
130 ("editor1", "edited"),
131 ("editor1", "buffer edited"),
132 ("editor2", "buffer edited"),
133 ]
134 );
135
136 // Undoing on editor 2 will emit an `Edited` event only for that editor.
137 _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
138 assert_eq!(
139 mem::take(&mut *events.borrow_mut()),
140 [
141 ("editor2", "edited"),
142 ("editor1", "buffer edited"),
143 ("editor2", "buffer edited"),
144 ]
145 );
146
147 // Redoing on editor 2 will emit an `Edited` event only for that editor.
148 _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
149 assert_eq!(
150 mem::take(&mut *events.borrow_mut()),
151 [
152 ("editor2", "edited"),
153 ("editor1", "buffer edited"),
154 ("editor2", "buffer edited"),
155 ]
156 );
157
158 // No event is emitted when the mutation is a no-op.
159 _ = editor2.update(cx, |editor, cx| {
160 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
161
162 editor.backspace(&Backspace, cx);
163 });
164 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
165}
166
167#[gpui::test]
168fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
169 init_test(cx, |_| {});
170
171 let mut now = Instant::now();
172 let group_interval = Duration::from_millis(1);
173 let buffer = cx.new_model(|cx| {
174 let mut buf = language::Buffer::local("123456", cx);
175 buf.set_group_interval(group_interval);
176 buf
177 });
178 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
179 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
180
181 _ = editor.update(cx, |editor, cx| {
182 editor.start_transaction_at(now, cx);
183 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
184
185 editor.insert("cd", cx);
186 editor.end_transaction_at(now, cx);
187 assert_eq!(editor.text(cx), "12cd56");
188 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
189
190 editor.start_transaction_at(now, cx);
191 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
192 editor.insert("e", cx);
193 editor.end_transaction_at(now, cx);
194 assert_eq!(editor.text(cx), "12cde6");
195 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
196
197 now += group_interval + Duration::from_millis(1);
198 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
199
200 // Simulate an edit in another editor
201 buffer.update(cx, |buffer, cx| {
202 buffer.start_transaction_at(now, cx);
203 buffer.edit([(0..1, "a")], None, cx);
204 buffer.edit([(1..1, "b")], None, cx);
205 buffer.end_transaction_at(now, cx);
206 });
207
208 assert_eq!(editor.text(cx), "ab2cde6");
209 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
210
211 // Last transaction happened past the group interval in a different editor.
212 // Undo it individually and don't restore selections.
213 editor.undo(&Undo, cx);
214 assert_eq!(editor.text(cx), "12cde6");
215 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
216
217 // First two transactions happened within the group interval in this editor.
218 // Undo them together and restore selections.
219 editor.undo(&Undo, cx);
220 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
221 assert_eq!(editor.text(cx), "123456");
222 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
223
224 // Redo the first two transactions together.
225 editor.redo(&Redo, cx);
226 assert_eq!(editor.text(cx), "12cde6");
227 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
228
229 // Redo the last transaction on its own.
230 editor.redo(&Redo, cx);
231 assert_eq!(editor.text(cx), "ab2cde6");
232 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
233
234 // Test empty transactions.
235 editor.start_transaction_at(now, cx);
236 editor.end_transaction_at(now, cx);
237 editor.undo(&Undo, cx);
238 assert_eq!(editor.text(cx), "12cde6");
239 });
240}
241
242#[gpui::test]
243fn test_ime_composition(cx: &mut TestAppContext) {
244 init_test(cx, |_| {});
245
246 let buffer = cx.new_model(|cx| {
247 let mut buffer = language::Buffer::local("abcde", cx);
248 // Ensure automatic grouping doesn't occur.
249 buffer.set_group_interval(Duration::ZERO);
250 buffer
251 });
252
253 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
254 cx.add_window(|cx| {
255 let mut editor = build_editor(buffer.clone(), cx);
256
257 // Start a new IME composition.
258 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
259 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
260 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
261 assert_eq!(editor.text(cx), "äbcde");
262 assert_eq!(
263 editor.marked_text_ranges(cx),
264 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
265 );
266
267 // Finalize IME composition.
268 editor.replace_text_in_range(None, "ā", cx);
269 assert_eq!(editor.text(cx), "ābcde");
270 assert_eq!(editor.marked_text_ranges(cx), None);
271
272 // IME composition edits are grouped and are undone/redone at once.
273 editor.undo(&Default::default(), cx);
274 assert_eq!(editor.text(cx), "abcde");
275 assert_eq!(editor.marked_text_ranges(cx), None);
276 editor.redo(&Default::default(), cx);
277 assert_eq!(editor.text(cx), "ābcde");
278 assert_eq!(editor.marked_text_ranges(cx), None);
279
280 // Start a new IME composition.
281 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
282 assert_eq!(
283 editor.marked_text_ranges(cx),
284 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
285 );
286
287 // Undoing during an IME composition cancels it.
288 editor.undo(&Default::default(), cx);
289 assert_eq!(editor.text(cx), "ābcde");
290 assert_eq!(editor.marked_text_ranges(cx), None);
291
292 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
293 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
294 assert_eq!(editor.text(cx), "ābcdè");
295 assert_eq!(
296 editor.marked_text_ranges(cx),
297 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
298 );
299
300 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
301 editor.replace_text_in_range(Some(4..999), "ę", cx);
302 assert_eq!(editor.text(cx), "ābcdę");
303 assert_eq!(editor.marked_text_ranges(cx), None);
304
305 // Start a new IME composition with multiple cursors.
306 editor.change_selections(None, cx, |s| {
307 s.select_ranges([
308 OffsetUtf16(1)..OffsetUtf16(1),
309 OffsetUtf16(3)..OffsetUtf16(3),
310 OffsetUtf16(5)..OffsetUtf16(5),
311 ])
312 });
313 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
314 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
315 assert_eq!(
316 editor.marked_text_ranges(cx),
317 Some(vec![
318 OffsetUtf16(0)..OffsetUtf16(3),
319 OffsetUtf16(4)..OffsetUtf16(7),
320 OffsetUtf16(8)..OffsetUtf16(11)
321 ])
322 );
323
324 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
325 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
326 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
327 assert_eq!(
328 editor.marked_text_ranges(cx),
329 Some(vec![
330 OffsetUtf16(1)..OffsetUtf16(2),
331 OffsetUtf16(5)..OffsetUtf16(6),
332 OffsetUtf16(9)..OffsetUtf16(10)
333 ])
334 );
335
336 // Finalize IME composition with multiple cursors.
337 editor.replace_text_in_range(Some(9..10), "2", cx);
338 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
339 assert_eq!(editor.marked_text_ranges(cx), None);
340
341 editor
342 });
343}
344
345#[gpui::test]
346fn test_selection_with_mouse(cx: &mut TestAppContext) {
347 init_test(cx, |_| {});
348
349 let editor = cx.add_window(|cx| {
350 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
351 build_editor(buffer, cx)
352 });
353
354 _ = editor.update(cx, |view, cx| {
355 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
356 });
357 assert_eq!(
358 editor
359 .update(cx, |view, cx| view.selections.display_ranges(cx))
360 .unwrap(),
361 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
362 );
363
364 _ = editor.update(cx, |view, cx| {
365 view.update_selection(
366 DisplayPoint::new(DisplayRow(3), 3),
367 0,
368 gpui::Point::<f32>::default(),
369 cx,
370 );
371 });
372
373 assert_eq!(
374 editor
375 .update(cx, |view, cx| view.selections.display_ranges(cx))
376 .unwrap(),
377 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
378 );
379
380 _ = editor.update(cx, |view, cx| {
381 view.update_selection(
382 DisplayPoint::new(DisplayRow(1), 1),
383 0,
384 gpui::Point::<f32>::default(),
385 cx,
386 );
387 });
388
389 assert_eq!(
390 editor
391 .update(cx, |view, cx| view.selections.display_ranges(cx))
392 .unwrap(),
393 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
394 );
395
396 _ = editor.update(cx, |view, cx| {
397 view.end_selection(cx);
398 view.update_selection(
399 DisplayPoint::new(DisplayRow(3), 3),
400 0,
401 gpui::Point::<f32>::default(),
402 cx,
403 );
404 });
405
406 assert_eq!(
407 editor
408 .update(cx, |view, cx| view.selections.display_ranges(cx))
409 .unwrap(),
410 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
411 );
412
413 _ = editor.update(cx, |view, cx| {
414 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
415 view.update_selection(
416 DisplayPoint::new(DisplayRow(0), 0),
417 0,
418 gpui::Point::<f32>::default(),
419 cx,
420 );
421 });
422
423 assert_eq!(
424 editor
425 .update(cx, |view, cx| view.selections.display_ranges(cx))
426 .unwrap(),
427 [
428 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
429 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
430 ]
431 );
432
433 _ = editor.update(cx, |view, cx| {
434 view.end_selection(cx);
435 });
436
437 assert_eq!(
438 editor
439 .update(cx, |view, cx| view.selections.display_ranges(cx))
440 .unwrap(),
441 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
442 );
443}
444
445#[gpui::test]
446fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
447 init_test(cx, |_| {});
448
449 let editor = cx.add_window(|cx| {
450 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
451 build_editor(buffer, cx)
452 });
453
454 _ = editor.update(cx, |view, cx| {
455 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
456 });
457
458 _ = editor.update(cx, |view, cx| {
459 view.end_selection(cx);
460 });
461
462 _ = editor.update(cx, |view, cx| {
463 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
464 });
465
466 _ = editor.update(cx, |view, cx| {
467 view.end_selection(cx);
468 });
469
470 assert_eq!(
471 editor
472 .update(cx, |view, cx| view.selections.display_ranges(cx))
473 .unwrap(),
474 [
475 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
476 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
477 ]
478 );
479
480 _ = editor.update(cx, |view, cx| {
481 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
482 });
483
484 _ = editor.update(cx, |view, cx| {
485 view.end_selection(cx);
486 });
487
488 assert_eq!(
489 editor
490 .update(cx, |view, cx| view.selections.display_ranges(cx))
491 .unwrap(),
492 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
493 );
494}
495
496#[gpui::test]
497fn test_canceling_pending_selection(cx: &mut TestAppContext) {
498 init_test(cx, |_| {});
499
500 let view = cx.add_window(|cx| {
501 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
502 build_editor(buffer, cx)
503 });
504
505 _ = view.update(cx, |view, cx| {
506 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
507 assert_eq!(
508 view.selections.display_ranges(cx),
509 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
510 );
511 });
512
513 _ = view.update(cx, |view, cx| {
514 view.update_selection(
515 DisplayPoint::new(DisplayRow(3), 3),
516 0,
517 gpui::Point::<f32>::default(),
518 cx,
519 );
520 assert_eq!(
521 view.selections.display_ranges(cx),
522 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
523 );
524 });
525
526 _ = view.update(cx, |view, cx| {
527 view.cancel(&Cancel, cx);
528 view.update_selection(
529 DisplayPoint::new(DisplayRow(1), 1),
530 0,
531 gpui::Point::<f32>::default(),
532 cx,
533 );
534 assert_eq!(
535 view.selections.display_ranges(cx),
536 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
537 );
538 });
539}
540
541#[gpui::test]
542fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
543 init_test(cx, |_| {});
544
545 let view = cx.add_window(|cx| {
546 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
547 build_editor(buffer, cx)
548 });
549
550 _ = view.update(cx, |view, cx| {
551 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
552 assert_eq!(
553 view.selections.display_ranges(cx),
554 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
555 );
556
557 view.move_down(&Default::default(), cx);
558 assert_eq!(
559 view.selections.display_ranges(cx),
560 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
561 );
562
563 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
564 assert_eq!(
565 view.selections.display_ranges(cx),
566 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
567 );
568
569 view.move_up(&Default::default(), cx);
570 assert_eq!(
571 view.selections.display_ranges(cx),
572 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
573 );
574 });
575}
576
577#[gpui::test]
578fn test_clone(cx: &mut TestAppContext) {
579 init_test(cx, |_| {});
580
581 let (text, selection_ranges) = marked_text_ranges(
582 indoc! {"
583 one
584 two
585 threeˇ
586 four
587 fiveˇ
588 "},
589 true,
590 );
591
592 let editor = cx.add_window(|cx| {
593 let buffer = MultiBuffer::build_simple(&text, cx);
594 build_editor(buffer, cx)
595 });
596
597 _ = editor.update(cx, |editor, cx| {
598 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
599 editor.fold_creases(
600 vec![
601 Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
602 Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
603 ],
604 true,
605 cx,
606 );
607 });
608
609 let cloned_editor = editor
610 .update(cx, |editor, cx| {
611 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
612 })
613 .unwrap()
614 .unwrap();
615
616 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
617 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
618
619 assert_eq!(
620 cloned_editor
621 .update(cx, |e, cx| e.display_text(cx))
622 .unwrap(),
623 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
624 );
625 assert_eq!(
626 cloned_snapshot
627 .folds_in_range(0..text.len())
628 .collect::<Vec<_>>(),
629 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
630 );
631 assert_set_eq!(
632 cloned_editor
633 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
634 .unwrap(),
635 editor
636 .update(cx, |editor, cx| editor.selections.ranges(cx))
637 .unwrap()
638 );
639 assert_set_eq!(
640 cloned_editor
641 .update(cx, |e, cx| e.selections.display_ranges(cx))
642 .unwrap(),
643 editor
644 .update(cx, |e, cx| e.selections.display_ranges(cx))
645 .unwrap()
646 );
647}
648
649#[gpui::test]
650async fn test_navigation_history(cx: &mut TestAppContext) {
651 init_test(cx, |_| {});
652
653 use workspace::item::Item;
654
655 let fs = FakeFs::new(cx.executor());
656 let project = Project::test(fs, [], cx).await;
657 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
658 let pane = workspace
659 .update(cx, |workspace, _| workspace.active_pane().clone())
660 .unwrap();
661
662 _ = workspace.update(cx, |_v, cx| {
663 cx.new_view(|cx| {
664 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
665 let mut editor = build_editor(buffer.clone(), cx);
666 let handle = cx.view();
667 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(handle)));
668
669 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
670 editor.nav_history.as_mut().unwrap().pop_backward(cx)
671 }
672
673 // Move the cursor a small distance.
674 // Nothing is added to the navigation history.
675 editor.change_selections(None, cx, |s| {
676 s.select_display_ranges([
677 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
678 ])
679 });
680 editor.change_selections(None, cx, |s| {
681 s.select_display_ranges([
682 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
683 ])
684 });
685 assert!(pop_history(&mut editor, cx).is_none());
686
687 // Move the cursor a large distance.
688 // The history can jump back to the previous position.
689 editor.change_selections(None, cx, |s| {
690 s.select_display_ranges([
691 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
692 ])
693 });
694 let nav_entry = pop_history(&mut editor, cx).unwrap();
695 editor.navigate(nav_entry.data.unwrap(), cx);
696 assert_eq!(nav_entry.item.id(), cx.entity_id());
697 assert_eq!(
698 editor.selections.display_ranges(cx),
699 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
700 );
701 assert!(pop_history(&mut editor, cx).is_none());
702
703 // Move the cursor a small distance via the mouse.
704 // Nothing is added to the navigation history.
705 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
706 editor.end_selection(cx);
707 assert_eq!(
708 editor.selections.display_ranges(cx),
709 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
710 );
711 assert!(pop_history(&mut editor, cx).is_none());
712
713 // Move the cursor a large distance via the mouse.
714 // The history can jump back to the previous position.
715 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
716 editor.end_selection(cx);
717 assert_eq!(
718 editor.selections.display_ranges(cx),
719 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
720 );
721 let nav_entry = pop_history(&mut editor, cx).unwrap();
722 editor.navigate(nav_entry.data.unwrap(), cx);
723 assert_eq!(nav_entry.item.id(), cx.entity_id());
724 assert_eq!(
725 editor.selections.display_ranges(cx),
726 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
727 );
728 assert!(pop_history(&mut editor, cx).is_none());
729
730 // Set scroll position to check later
731 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
732 let original_scroll_position = editor.scroll_manager.anchor();
733
734 // Jump to the end of the document and adjust scroll
735 editor.move_to_end(&MoveToEnd, cx);
736 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
737 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
738
739 let nav_entry = pop_history(&mut editor, cx).unwrap();
740 editor.navigate(nav_entry.data.unwrap(), cx);
741 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
742
743 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
744 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
745 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
746 let invalid_point = Point::new(9999, 0);
747 editor.navigate(
748 Box::new(NavigationData {
749 cursor_anchor: invalid_anchor,
750 cursor_position: invalid_point,
751 scroll_anchor: ScrollAnchor {
752 anchor: invalid_anchor,
753 offset: Default::default(),
754 },
755 scroll_top_row: invalid_point.row,
756 }),
757 cx,
758 );
759 assert_eq!(
760 editor.selections.display_ranges(cx),
761 &[editor.max_point(cx)..editor.max_point(cx)]
762 );
763 assert_eq!(
764 editor.scroll_position(cx),
765 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
766 );
767
768 editor
769 })
770 });
771}
772
773#[gpui::test]
774fn test_cancel(cx: &mut TestAppContext) {
775 init_test(cx, |_| {});
776
777 let view = cx.add_window(|cx| {
778 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
779 build_editor(buffer, cx)
780 });
781
782 _ = view.update(cx, |view, cx| {
783 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
784 view.update_selection(
785 DisplayPoint::new(DisplayRow(1), 1),
786 0,
787 gpui::Point::<f32>::default(),
788 cx,
789 );
790 view.end_selection(cx);
791
792 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
793 view.update_selection(
794 DisplayPoint::new(DisplayRow(0), 3),
795 0,
796 gpui::Point::<f32>::default(),
797 cx,
798 );
799 view.end_selection(cx);
800 assert_eq!(
801 view.selections.display_ranges(cx),
802 [
803 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
804 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
805 ]
806 );
807 });
808
809 _ = view.update(cx, |view, cx| {
810 view.cancel(&Cancel, cx);
811 assert_eq!(
812 view.selections.display_ranges(cx),
813 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
814 );
815 });
816
817 _ = view.update(cx, |view, cx| {
818 view.cancel(&Cancel, cx);
819 assert_eq!(
820 view.selections.display_ranges(cx),
821 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
822 );
823 });
824}
825
826#[gpui::test]
827fn test_fold_action(cx: &mut TestAppContext) {
828 init_test(cx, |_| {});
829
830 let view = cx.add_window(|cx| {
831 let buffer = MultiBuffer::build_simple(
832 &"
833 impl Foo {
834 // Hello!
835
836 fn a() {
837 1
838 }
839
840 fn b() {
841 2
842 }
843
844 fn c() {
845 3
846 }
847 }
848 "
849 .unindent(),
850 cx,
851 );
852 build_editor(buffer.clone(), cx)
853 });
854
855 _ = view.update(cx, |view, cx| {
856 view.change_selections(None, cx, |s| {
857 s.select_display_ranges([
858 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
859 ]);
860 });
861 view.fold(&Fold, cx);
862 assert_eq!(
863 view.display_text(cx),
864 "
865 impl Foo {
866 // Hello!
867
868 fn a() {
869 1
870 }
871
872 fn b() {⋯
873 }
874
875 fn c() {⋯
876 }
877 }
878 "
879 .unindent(),
880 );
881
882 view.fold(&Fold, cx);
883 assert_eq!(
884 view.display_text(cx),
885 "
886 impl Foo {⋯
887 }
888 "
889 .unindent(),
890 );
891
892 view.unfold_lines(&UnfoldLines, cx);
893 assert_eq!(
894 view.display_text(cx),
895 "
896 impl Foo {
897 // Hello!
898
899 fn a() {
900 1
901 }
902
903 fn b() {⋯
904 }
905
906 fn c() {⋯
907 }
908 }
909 "
910 .unindent(),
911 );
912
913 view.unfold_lines(&UnfoldLines, cx);
914 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
915 });
916}
917
918#[gpui::test]
919fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
920 init_test(cx, |_| {});
921
922 let view = cx.add_window(|cx| {
923 let buffer = MultiBuffer::build_simple(
924 &"
925 class Foo:
926 # Hello!
927
928 def a():
929 print(1)
930
931 def b():
932 print(2)
933
934 def c():
935 print(3)
936 "
937 .unindent(),
938 cx,
939 );
940 build_editor(buffer.clone(), cx)
941 });
942
943 _ = view.update(cx, |view, cx| {
944 view.change_selections(None, cx, |s| {
945 s.select_display_ranges([
946 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
947 ]);
948 });
949 view.fold(&Fold, cx);
950 assert_eq!(
951 view.display_text(cx),
952 "
953 class Foo:
954 # Hello!
955
956 def a():
957 print(1)
958
959 def b():⋯
960
961 def c():⋯
962 "
963 .unindent(),
964 );
965
966 view.fold(&Fold, cx);
967 assert_eq!(
968 view.display_text(cx),
969 "
970 class Foo:⋯
971 "
972 .unindent(),
973 );
974
975 view.unfold_lines(&UnfoldLines, cx);
976 assert_eq!(
977 view.display_text(cx),
978 "
979 class Foo:
980 # Hello!
981
982 def a():
983 print(1)
984
985 def b():⋯
986
987 def c():⋯
988 "
989 .unindent(),
990 );
991
992 view.unfold_lines(&UnfoldLines, cx);
993 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
994 });
995}
996
997#[gpui::test]
998fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
999 init_test(cx, |_| {});
1000
1001 let view = cx.add_window(|cx| {
1002 let buffer = MultiBuffer::build_simple(
1003 &"
1004 class Foo:
1005 # Hello!
1006
1007 def a():
1008 print(1)
1009
1010 def b():
1011 print(2)
1012
1013
1014 def c():
1015 print(3)
1016
1017
1018 "
1019 .unindent(),
1020 cx,
1021 );
1022 build_editor(buffer.clone(), cx)
1023 });
1024
1025 _ = view.update(cx, |view, cx| {
1026 view.change_selections(None, cx, |s| {
1027 s.select_display_ranges([
1028 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1029 ]);
1030 });
1031 view.fold(&Fold, cx);
1032 assert_eq!(
1033 view.display_text(cx),
1034 "
1035 class Foo:
1036 # Hello!
1037
1038 def a():
1039 print(1)
1040
1041 def b():⋯
1042
1043
1044 def c():⋯
1045
1046
1047 "
1048 .unindent(),
1049 );
1050
1051 view.fold(&Fold, cx);
1052 assert_eq!(
1053 view.display_text(cx),
1054 "
1055 class Foo:⋯
1056
1057
1058 "
1059 .unindent(),
1060 );
1061
1062 view.unfold_lines(&UnfoldLines, cx);
1063 assert_eq!(
1064 view.display_text(cx),
1065 "
1066 class Foo:
1067 # Hello!
1068
1069 def a():
1070 print(1)
1071
1072 def b():⋯
1073
1074
1075 def c():⋯
1076
1077
1078 "
1079 .unindent(),
1080 );
1081
1082 view.unfold_lines(&UnfoldLines, cx);
1083 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1084 });
1085}
1086
1087#[gpui::test]
1088fn test_fold_at_level(cx: &mut TestAppContext) {
1089 init_test(cx, |_| {});
1090
1091 let view = cx.add_window(|cx| {
1092 let buffer = MultiBuffer::build_simple(
1093 &"
1094 class Foo:
1095 # Hello!
1096
1097 def a():
1098 print(1)
1099
1100 def b():
1101 print(2)
1102
1103
1104 class Bar:
1105 # World!
1106
1107 def a():
1108 print(1)
1109
1110 def b():
1111 print(2)
1112
1113
1114 "
1115 .unindent(),
1116 cx,
1117 );
1118 build_editor(buffer.clone(), cx)
1119 });
1120
1121 _ = view.update(cx, |view, cx| {
1122 view.fold_at_level(&FoldAtLevel { level: 2 }, cx);
1123 assert_eq!(
1124 view.display_text(cx),
1125 "
1126 class Foo:
1127 # Hello!
1128
1129 def a():⋯
1130
1131 def b():⋯
1132
1133
1134 class Bar:
1135 # World!
1136
1137 def a():⋯
1138
1139 def b():⋯
1140
1141
1142 "
1143 .unindent(),
1144 );
1145
1146 view.fold_at_level(&FoldAtLevel { level: 1 }, cx);
1147 assert_eq!(
1148 view.display_text(cx),
1149 "
1150 class Foo:⋯
1151
1152
1153 class Bar:⋯
1154
1155
1156 "
1157 .unindent(),
1158 );
1159
1160 view.unfold_all(&UnfoldAll, cx);
1161 view.fold_at_level(&FoldAtLevel { level: 0 }, cx);
1162 assert_eq!(
1163 view.display_text(cx),
1164 "
1165 class Foo:
1166 # Hello!
1167
1168 def a():
1169 print(1)
1170
1171 def b():
1172 print(2)
1173
1174
1175 class Bar:
1176 # World!
1177
1178 def a():
1179 print(1)
1180
1181 def b():
1182 print(2)
1183
1184
1185 "
1186 .unindent(),
1187 );
1188
1189 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1190 });
1191}
1192
1193#[gpui::test]
1194fn test_move_cursor(cx: &mut TestAppContext) {
1195 init_test(cx, |_| {});
1196
1197 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1198 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1199
1200 buffer.update(cx, |buffer, cx| {
1201 buffer.edit(
1202 vec![
1203 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1204 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1205 ],
1206 None,
1207 cx,
1208 );
1209 });
1210 _ = view.update(cx, |view, cx| {
1211 assert_eq!(
1212 view.selections.display_ranges(cx),
1213 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1214 );
1215
1216 view.move_down(&MoveDown, cx);
1217 assert_eq!(
1218 view.selections.display_ranges(cx),
1219 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1220 );
1221
1222 view.move_right(&MoveRight, cx);
1223 assert_eq!(
1224 view.selections.display_ranges(cx),
1225 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1226 );
1227
1228 view.move_left(&MoveLeft, cx);
1229 assert_eq!(
1230 view.selections.display_ranges(cx),
1231 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1232 );
1233
1234 view.move_up(&MoveUp, cx);
1235 assert_eq!(
1236 view.selections.display_ranges(cx),
1237 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1238 );
1239
1240 view.move_to_end(&MoveToEnd, cx);
1241 assert_eq!(
1242 view.selections.display_ranges(cx),
1243 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1244 );
1245
1246 view.move_to_beginning(&MoveToBeginning, cx);
1247 assert_eq!(
1248 view.selections.display_ranges(cx),
1249 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1250 );
1251
1252 view.change_selections(None, cx, |s| {
1253 s.select_display_ranges([
1254 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1255 ]);
1256 });
1257 view.select_to_beginning(&SelectToBeginning, cx);
1258 assert_eq!(
1259 view.selections.display_ranges(cx),
1260 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1261 );
1262
1263 view.select_to_end(&SelectToEnd, cx);
1264 assert_eq!(
1265 view.selections.display_ranges(cx),
1266 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1267 );
1268 });
1269}
1270
1271// TODO: Re-enable this test
1272#[cfg(target_os = "macos")]
1273#[gpui::test]
1274fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1275 init_test(cx, |_| {});
1276
1277 let view = cx.add_window(|cx| {
1278 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1279 build_editor(buffer.clone(), cx)
1280 });
1281
1282 assert_eq!('ⓐ'.len_utf8(), 3);
1283 assert_eq!('α'.len_utf8(), 2);
1284
1285 _ = view.update(cx, |view, cx| {
1286 view.fold_creases(
1287 vec![
1288 Crease::simple(Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1289 Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1290 Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1291 ],
1292 true,
1293 cx,
1294 );
1295 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1296
1297 view.move_right(&MoveRight, cx);
1298 assert_eq!(
1299 view.selections.display_ranges(cx),
1300 &[empty_range(0, "ⓐ".len())]
1301 );
1302 view.move_right(&MoveRight, cx);
1303 assert_eq!(
1304 view.selections.display_ranges(cx),
1305 &[empty_range(0, "ⓐⓑ".len())]
1306 );
1307 view.move_right(&MoveRight, cx);
1308 assert_eq!(
1309 view.selections.display_ranges(cx),
1310 &[empty_range(0, "ⓐⓑ⋯".len())]
1311 );
1312
1313 view.move_down(&MoveDown, cx);
1314 assert_eq!(
1315 view.selections.display_ranges(cx),
1316 &[empty_range(1, "ab⋯e".len())]
1317 );
1318 view.move_left(&MoveLeft, cx);
1319 assert_eq!(
1320 view.selections.display_ranges(cx),
1321 &[empty_range(1, "ab⋯".len())]
1322 );
1323 view.move_left(&MoveLeft, cx);
1324 assert_eq!(
1325 view.selections.display_ranges(cx),
1326 &[empty_range(1, "ab".len())]
1327 );
1328 view.move_left(&MoveLeft, cx);
1329 assert_eq!(
1330 view.selections.display_ranges(cx),
1331 &[empty_range(1, "a".len())]
1332 );
1333
1334 view.move_down(&MoveDown, cx);
1335 assert_eq!(
1336 view.selections.display_ranges(cx),
1337 &[empty_range(2, "α".len())]
1338 );
1339 view.move_right(&MoveRight, cx);
1340 assert_eq!(
1341 view.selections.display_ranges(cx),
1342 &[empty_range(2, "αβ".len())]
1343 );
1344 view.move_right(&MoveRight, cx);
1345 assert_eq!(
1346 view.selections.display_ranges(cx),
1347 &[empty_range(2, "αβ⋯".len())]
1348 );
1349 view.move_right(&MoveRight, cx);
1350 assert_eq!(
1351 view.selections.display_ranges(cx),
1352 &[empty_range(2, "αβ⋯ε".len())]
1353 );
1354
1355 view.move_up(&MoveUp, cx);
1356 assert_eq!(
1357 view.selections.display_ranges(cx),
1358 &[empty_range(1, "ab⋯e".len())]
1359 );
1360 view.move_down(&MoveDown, cx);
1361 assert_eq!(
1362 view.selections.display_ranges(cx),
1363 &[empty_range(2, "αβ⋯ε".len())]
1364 );
1365 view.move_up(&MoveUp, cx);
1366 assert_eq!(
1367 view.selections.display_ranges(cx),
1368 &[empty_range(1, "ab⋯e".len())]
1369 );
1370
1371 view.move_up(&MoveUp, cx);
1372 assert_eq!(
1373 view.selections.display_ranges(cx),
1374 &[empty_range(0, "ⓐⓑ".len())]
1375 );
1376 view.move_left(&MoveLeft, cx);
1377 assert_eq!(
1378 view.selections.display_ranges(cx),
1379 &[empty_range(0, "ⓐ".len())]
1380 );
1381 view.move_left(&MoveLeft, cx);
1382 assert_eq!(
1383 view.selections.display_ranges(cx),
1384 &[empty_range(0, "".len())]
1385 );
1386 });
1387}
1388
1389#[gpui::test]
1390fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1391 init_test(cx, |_| {});
1392
1393 let view = cx.add_window(|cx| {
1394 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1395 build_editor(buffer.clone(), cx)
1396 });
1397 _ = view.update(cx, |view, cx| {
1398 view.change_selections(None, cx, |s| {
1399 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1400 });
1401
1402 // moving above start of document should move selection to start of document,
1403 // but the next move down should still be at the original goal_x
1404 view.move_up(&MoveUp, cx);
1405 assert_eq!(
1406 view.selections.display_ranges(cx),
1407 &[empty_range(0, "".len())]
1408 );
1409
1410 view.move_down(&MoveDown, cx);
1411 assert_eq!(
1412 view.selections.display_ranges(cx),
1413 &[empty_range(1, "abcd".len())]
1414 );
1415
1416 view.move_down(&MoveDown, cx);
1417 assert_eq!(
1418 view.selections.display_ranges(cx),
1419 &[empty_range(2, "αβγ".len())]
1420 );
1421
1422 view.move_down(&MoveDown, cx);
1423 assert_eq!(
1424 view.selections.display_ranges(cx),
1425 &[empty_range(3, "abcd".len())]
1426 );
1427
1428 view.move_down(&MoveDown, cx);
1429 assert_eq!(
1430 view.selections.display_ranges(cx),
1431 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1432 );
1433
1434 // moving past end of document should not change goal_x
1435 view.move_down(&MoveDown, cx);
1436 assert_eq!(
1437 view.selections.display_ranges(cx),
1438 &[empty_range(5, "".len())]
1439 );
1440
1441 view.move_down(&MoveDown, cx);
1442 assert_eq!(
1443 view.selections.display_ranges(cx),
1444 &[empty_range(5, "".len())]
1445 );
1446
1447 view.move_up(&MoveUp, cx);
1448 assert_eq!(
1449 view.selections.display_ranges(cx),
1450 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1451 );
1452
1453 view.move_up(&MoveUp, cx);
1454 assert_eq!(
1455 view.selections.display_ranges(cx),
1456 &[empty_range(3, "abcd".len())]
1457 );
1458
1459 view.move_up(&MoveUp, cx);
1460 assert_eq!(
1461 view.selections.display_ranges(cx),
1462 &[empty_range(2, "αβγ".len())]
1463 );
1464 });
1465}
1466
1467#[gpui::test]
1468fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1469 init_test(cx, |_| {});
1470 let move_to_beg = MoveToBeginningOfLine {
1471 stop_at_soft_wraps: true,
1472 };
1473
1474 let move_to_end = MoveToEndOfLine {
1475 stop_at_soft_wraps: true,
1476 };
1477
1478 let view = cx.add_window(|cx| {
1479 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1480 build_editor(buffer, cx)
1481 });
1482 _ = view.update(cx, |view, cx| {
1483 view.change_selections(None, cx, |s| {
1484 s.select_display_ranges([
1485 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1486 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1487 ]);
1488 });
1489 });
1490
1491 _ = view.update(cx, |view, cx| {
1492 view.move_to_beginning_of_line(&move_to_beg, cx);
1493 assert_eq!(
1494 view.selections.display_ranges(cx),
1495 &[
1496 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1497 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1498 ]
1499 );
1500 });
1501
1502 _ = view.update(cx, |view, cx| {
1503 view.move_to_beginning_of_line(&move_to_beg, cx);
1504 assert_eq!(
1505 view.selections.display_ranges(cx),
1506 &[
1507 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1508 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1509 ]
1510 );
1511 });
1512
1513 _ = view.update(cx, |view, cx| {
1514 view.move_to_beginning_of_line(&move_to_beg, cx);
1515 assert_eq!(
1516 view.selections.display_ranges(cx),
1517 &[
1518 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1519 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1520 ]
1521 );
1522 });
1523
1524 _ = view.update(cx, |view, cx| {
1525 view.move_to_end_of_line(&move_to_end, cx);
1526 assert_eq!(
1527 view.selections.display_ranges(cx),
1528 &[
1529 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1530 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1531 ]
1532 );
1533 });
1534
1535 // Moving to the end of line again is a no-op.
1536 _ = view.update(cx, |view, cx| {
1537 view.move_to_end_of_line(&move_to_end, cx);
1538 assert_eq!(
1539 view.selections.display_ranges(cx),
1540 &[
1541 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1542 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1543 ]
1544 );
1545 });
1546
1547 _ = view.update(cx, |view, cx| {
1548 view.move_left(&MoveLeft, 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_beginning_of_line(
1566 &SelectToBeginningOfLine {
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), 0),
1575 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1576 ]
1577 );
1578 });
1579
1580 _ = view.update(cx, |view, cx| {
1581 view.select_to_beginning_of_line(
1582 &SelectToBeginningOfLine {
1583 stop_at_soft_wraps: true,
1584 },
1585 cx,
1586 );
1587 assert_eq!(
1588 view.selections.display_ranges(cx),
1589 &[
1590 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1591 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1592 ]
1593 );
1594 });
1595
1596 _ = view.update(cx, |view, cx| {
1597 view.select_to_end_of_line(
1598 &SelectToEndOfLine {
1599 stop_at_soft_wraps: true,
1600 },
1601 cx,
1602 );
1603 assert_eq!(
1604 view.selections.display_ranges(cx),
1605 &[
1606 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1607 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1608 ]
1609 );
1610 });
1611
1612 _ = view.update(cx, |view, cx| {
1613 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1614 assert_eq!(view.display_text(cx), "ab\n de");
1615 assert_eq!(
1616 view.selections.display_ranges(cx),
1617 &[
1618 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1619 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1620 ]
1621 );
1622 });
1623
1624 _ = view.update(cx, |view, cx| {
1625 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1626 assert_eq!(view.display_text(cx), "\n");
1627 assert_eq!(
1628 view.selections.display_ranges(cx),
1629 &[
1630 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1631 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1632 ]
1633 );
1634 });
1635}
1636
1637#[gpui::test]
1638fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1639 init_test(cx, |_| {});
1640 let move_to_beg = MoveToBeginningOfLine {
1641 stop_at_soft_wraps: false,
1642 };
1643
1644 let move_to_end = MoveToEndOfLine {
1645 stop_at_soft_wraps: false,
1646 };
1647
1648 let view = cx.add_window(|cx| {
1649 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1650 build_editor(buffer, cx)
1651 });
1652
1653 _ = view.update(cx, |view, cx| {
1654 view.set_wrap_width(Some(140.0.into()), cx);
1655
1656 // We expect the following lines after wrapping
1657 // ```
1658 // thequickbrownfox
1659 // jumpedoverthelazydo
1660 // gs
1661 // ```
1662 // The final `gs` was soft-wrapped onto a new line.
1663 assert_eq!(
1664 "thequickbrownfox\njumpedoverthelaz\nydogs",
1665 view.display_text(cx),
1666 );
1667
1668 // First, let's assert behavior on the first line, that was not soft-wrapped.
1669 // Start the cursor at the `k` on the first line
1670 view.change_selections(None, cx, |s| {
1671 s.select_display_ranges([
1672 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1673 ]);
1674 });
1675
1676 // Moving to the beginning of the line should put us at the beginning of the line.
1677 view.move_to_beginning_of_line(&move_to_beg, cx);
1678 assert_eq!(
1679 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1680 view.selections.display_ranges(cx)
1681 );
1682
1683 // Moving to the end of the line should put us at the end of the line.
1684 view.move_to_end_of_line(&move_to_end, cx);
1685 assert_eq!(
1686 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1687 view.selections.display_ranges(cx)
1688 );
1689
1690 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1691 // Start the cursor at the last line (`y` that was wrapped to a new line)
1692 view.change_selections(None, cx, |s| {
1693 s.select_display_ranges([
1694 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1695 ]);
1696 });
1697
1698 // Moving to the beginning of the line should put us at the start of the second line of
1699 // display text, i.e., the `j`.
1700 view.move_to_beginning_of_line(&move_to_beg, cx);
1701 assert_eq!(
1702 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1703 view.selections.display_ranges(cx)
1704 );
1705
1706 // Moving to the beginning of the line again should be a no-op.
1707 view.move_to_beginning_of_line(&move_to_beg, cx);
1708 assert_eq!(
1709 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1710 view.selections.display_ranges(cx)
1711 );
1712
1713 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1714 // next display line.
1715 view.move_to_end_of_line(&move_to_end, cx);
1716 assert_eq!(
1717 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1718 view.selections.display_ranges(cx)
1719 );
1720
1721 // Moving to the end of the line again should be a no-op.
1722 view.move_to_end_of_line(&move_to_end, cx);
1723 assert_eq!(
1724 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1725 view.selections.display_ranges(cx)
1726 );
1727 });
1728}
1729
1730#[gpui::test]
1731fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1732 init_test(cx, |_| {});
1733
1734 let view = cx.add_window(|cx| {
1735 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1736 build_editor(buffer, cx)
1737 });
1738 _ = view.update(cx, |view, cx| {
1739 view.change_selections(None, cx, |s| {
1740 s.select_display_ranges([
1741 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1742 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1743 ])
1744 });
1745
1746 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1747 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1748
1749 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1750 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1751
1752 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1753 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1754
1755 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1756 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1757
1758 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1759 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1760
1761 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1762 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1763
1764 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1765 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1766
1767 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1768 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1769
1770 view.move_right(&MoveRight, cx);
1771 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1772 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1773
1774 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1775 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1776
1777 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1778 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1779 });
1780}
1781
1782#[gpui::test]
1783fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1784 init_test(cx, |_| {});
1785
1786 let view = cx.add_window(|cx| {
1787 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1788 build_editor(buffer, cx)
1789 });
1790
1791 _ = view.update(cx, |view, cx| {
1792 view.set_wrap_width(Some(140.0.into()), cx);
1793 assert_eq!(
1794 view.display_text(cx),
1795 "use one::{\n two::three::\n four::five\n};"
1796 );
1797
1798 view.change_selections(None, cx, |s| {
1799 s.select_display_ranges([
1800 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1801 ]);
1802 });
1803
1804 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1805 assert_eq!(
1806 view.selections.display_ranges(cx),
1807 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1808 );
1809
1810 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1811 assert_eq!(
1812 view.selections.display_ranges(cx),
1813 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1814 );
1815
1816 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1817 assert_eq!(
1818 view.selections.display_ranges(cx),
1819 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1820 );
1821
1822 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1823 assert_eq!(
1824 view.selections.display_ranges(cx),
1825 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1826 );
1827
1828 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1829 assert_eq!(
1830 view.selections.display_ranges(cx),
1831 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1832 );
1833
1834 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1835 assert_eq!(
1836 view.selections.display_ranges(cx),
1837 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1838 );
1839 });
1840}
1841
1842#[gpui::test]
1843async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1844 init_test(cx, |_| {});
1845 let mut cx = EditorTestContext::new(cx).await;
1846
1847 let line_height = cx.editor(|editor, cx| {
1848 editor
1849 .style()
1850 .unwrap()
1851 .text
1852 .line_height_in_pixels(cx.rem_size())
1853 });
1854 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1855
1856 cx.set_state(
1857 &r#"ˇone
1858 two
1859
1860 three
1861 fourˇ
1862 five
1863
1864 six"#
1865 .unindent(),
1866 );
1867
1868 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1869 cx.assert_editor_state(
1870 &r#"one
1871 two
1872 ˇ
1873 three
1874 four
1875 five
1876 ˇ
1877 six"#
1878 .unindent(),
1879 );
1880
1881 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1882 cx.assert_editor_state(
1883 &r#"one
1884 two
1885
1886 three
1887 four
1888 five
1889 ˇ
1890 sixˇ"#
1891 .unindent(),
1892 );
1893
1894 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1895 cx.assert_editor_state(
1896 &r#"one
1897 two
1898
1899 three
1900 four
1901 five
1902
1903 sixˇ"#
1904 .unindent(),
1905 );
1906
1907 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1908 cx.assert_editor_state(
1909 &r#"one
1910 two
1911
1912 three
1913 four
1914 five
1915 ˇ
1916 six"#
1917 .unindent(),
1918 );
1919
1920 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1921 cx.assert_editor_state(
1922 &r#"one
1923 two
1924 ˇ
1925 three
1926 four
1927 five
1928
1929 six"#
1930 .unindent(),
1931 );
1932
1933 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1934 cx.assert_editor_state(
1935 &r#"ˇone
1936 two
1937
1938 three
1939 four
1940 five
1941
1942 six"#
1943 .unindent(),
1944 );
1945}
1946
1947#[gpui::test]
1948async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1949 init_test(cx, |_| {});
1950 let mut cx = EditorTestContext::new(cx).await;
1951 let line_height = cx.editor(|editor, cx| {
1952 editor
1953 .style()
1954 .unwrap()
1955 .text
1956 .line_height_in_pixels(cx.rem_size())
1957 });
1958 let window = cx.window;
1959 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1960
1961 cx.set_state(
1962 r#"ˇone
1963 two
1964 three
1965 four
1966 five
1967 six
1968 seven
1969 eight
1970 nine
1971 ten
1972 "#,
1973 );
1974
1975 cx.update_editor(|editor, cx| {
1976 assert_eq!(
1977 editor.snapshot(cx).scroll_position(),
1978 gpui::Point::new(0., 0.)
1979 );
1980 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1981 assert_eq!(
1982 editor.snapshot(cx).scroll_position(),
1983 gpui::Point::new(0., 3.)
1984 );
1985 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1986 assert_eq!(
1987 editor.snapshot(cx).scroll_position(),
1988 gpui::Point::new(0., 6.)
1989 );
1990 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1991 assert_eq!(
1992 editor.snapshot(cx).scroll_position(),
1993 gpui::Point::new(0., 3.)
1994 );
1995
1996 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1997 assert_eq!(
1998 editor.snapshot(cx).scroll_position(),
1999 gpui::Point::new(0., 1.)
2000 );
2001 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
2002 assert_eq!(
2003 editor.snapshot(cx).scroll_position(),
2004 gpui::Point::new(0., 3.)
2005 );
2006 });
2007}
2008
2009#[gpui::test]
2010async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
2011 init_test(cx, |_| {});
2012 let mut cx = EditorTestContext::new(cx).await;
2013
2014 let line_height = cx.update_editor(|editor, cx| {
2015 editor.set_vertical_scroll_margin(2, cx);
2016 editor
2017 .style()
2018 .unwrap()
2019 .text
2020 .line_height_in_pixels(cx.rem_size())
2021 });
2022 let window = cx.window;
2023 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2024
2025 cx.set_state(
2026 r#"ˇone
2027 two
2028 three
2029 four
2030 five
2031 six
2032 seven
2033 eight
2034 nine
2035 ten
2036 "#,
2037 );
2038 cx.update_editor(|editor, cx| {
2039 assert_eq!(
2040 editor.snapshot(cx).scroll_position(),
2041 gpui::Point::new(0., 0.0)
2042 );
2043 });
2044
2045 // Add a cursor below the visible area. Since both cursors cannot fit
2046 // on screen, the editor autoscrolls to reveal the newest cursor, and
2047 // allows the vertical scroll margin below that cursor.
2048 cx.update_editor(|editor, cx| {
2049 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2050 selections.select_ranges([
2051 Point::new(0, 0)..Point::new(0, 0),
2052 Point::new(6, 0)..Point::new(6, 0),
2053 ]);
2054 })
2055 });
2056 cx.update_editor(|editor, cx| {
2057 assert_eq!(
2058 editor.snapshot(cx).scroll_position(),
2059 gpui::Point::new(0., 3.0)
2060 );
2061 });
2062
2063 // Move down. The editor cursor scrolls down to track the newest cursor.
2064 cx.update_editor(|editor, cx| {
2065 editor.move_down(&Default::default(), cx);
2066 });
2067 cx.update_editor(|editor, cx| {
2068 assert_eq!(
2069 editor.snapshot(cx).scroll_position(),
2070 gpui::Point::new(0., 4.0)
2071 );
2072 });
2073
2074 // Add a cursor above the visible area. Since both cursors fit on screen,
2075 // the editor scrolls to show both.
2076 cx.update_editor(|editor, cx| {
2077 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2078 selections.select_ranges([
2079 Point::new(1, 0)..Point::new(1, 0),
2080 Point::new(6, 0)..Point::new(6, 0),
2081 ]);
2082 })
2083 });
2084 cx.update_editor(|editor, cx| {
2085 assert_eq!(
2086 editor.snapshot(cx).scroll_position(),
2087 gpui::Point::new(0., 1.0)
2088 );
2089 });
2090}
2091
2092#[gpui::test]
2093async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
2094 init_test(cx, |_| {});
2095 let mut cx = EditorTestContext::new(cx).await;
2096
2097 let line_height = cx.editor(|editor, cx| {
2098 editor
2099 .style()
2100 .unwrap()
2101 .text
2102 .line_height_in_pixels(cx.rem_size())
2103 });
2104 let window = cx.window;
2105 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2106 cx.set_state(
2107 &r#"
2108 ˇone
2109 two
2110 threeˇ
2111 four
2112 five
2113 six
2114 seven
2115 eight
2116 nine
2117 ten
2118 "#
2119 .unindent(),
2120 );
2121
2122 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2123 cx.assert_editor_state(
2124 &r#"
2125 one
2126 two
2127 three
2128 ˇfour
2129 five
2130 sixˇ
2131 seven
2132 eight
2133 nine
2134 ten
2135 "#
2136 .unindent(),
2137 );
2138
2139 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2140 cx.assert_editor_state(
2141 &r#"
2142 one
2143 two
2144 three
2145 four
2146 five
2147 six
2148 ˇseven
2149 eight
2150 nineˇ
2151 ten
2152 "#
2153 .unindent(),
2154 );
2155
2156 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2157 cx.assert_editor_state(
2158 &r#"
2159 one
2160 two
2161 three
2162 ˇfour
2163 five
2164 sixˇ
2165 seven
2166 eight
2167 nine
2168 ten
2169 "#
2170 .unindent(),
2171 );
2172
2173 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2174 cx.assert_editor_state(
2175 &r#"
2176 ˇone
2177 two
2178 threeˇ
2179 four
2180 five
2181 six
2182 seven
2183 eight
2184 nine
2185 ten
2186 "#
2187 .unindent(),
2188 );
2189
2190 // Test select collapsing
2191 cx.update_editor(|editor, cx| {
2192 editor.move_page_down(&MovePageDown::default(), cx);
2193 editor.move_page_down(&MovePageDown::default(), cx);
2194 editor.move_page_down(&MovePageDown::default(), cx);
2195 });
2196 cx.assert_editor_state(
2197 &r#"
2198 one
2199 two
2200 three
2201 four
2202 five
2203 six
2204 seven
2205 eight
2206 nine
2207 ˇten
2208 ˇ"#
2209 .unindent(),
2210 );
2211}
2212
2213#[gpui::test]
2214async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2215 init_test(cx, |_| {});
2216 let mut cx = EditorTestContext::new(cx).await;
2217 cx.set_state("one «two threeˇ» four");
2218 cx.update_editor(|editor, cx| {
2219 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2220 assert_eq!(editor.text(cx), " four");
2221 });
2222}
2223
2224#[gpui::test]
2225fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2226 init_test(cx, |_| {});
2227
2228 let view = cx.add_window(|cx| {
2229 let buffer = MultiBuffer::build_simple("one two three four", cx);
2230 build_editor(buffer.clone(), cx)
2231 });
2232
2233 _ = view.update(cx, |view, cx| {
2234 view.change_selections(None, cx, |s| {
2235 s.select_display_ranges([
2236 // an empty selection - the preceding word fragment is deleted
2237 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2238 // characters selected - they are deleted
2239 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2240 ])
2241 });
2242 view.delete_to_previous_word_start(
2243 &DeleteToPreviousWordStart {
2244 ignore_newlines: false,
2245 },
2246 cx,
2247 );
2248 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2249 });
2250
2251 _ = view.update(cx, |view, cx| {
2252 view.change_selections(None, cx, |s| {
2253 s.select_display_ranges([
2254 // an empty selection - the following word fragment is deleted
2255 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2256 // characters selected - they are deleted
2257 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2258 ])
2259 });
2260 view.delete_to_next_word_end(
2261 &DeleteToNextWordEnd {
2262 ignore_newlines: false,
2263 },
2264 cx,
2265 );
2266 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2267 });
2268}
2269
2270#[gpui::test]
2271fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2272 init_test(cx, |_| {});
2273
2274 let view = cx.add_window(|cx| {
2275 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2276 build_editor(buffer.clone(), cx)
2277 });
2278 let del_to_prev_word_start = DeleteToPreviousWordStart {
2279 ignore_newlines: false,
2280 };
2281 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2282 ignore_newlines: true,
2283 };
2284
2285 _ = view.update(cx, |view, cx| {
2286 view.change_selections(None, cx, |s| {
2287 s.select_display_ranges([
2288 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2289 ])
2290 });
2291 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2292 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2293 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2294 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2295 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2296 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\n");
2297 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2298 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2");
2299 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2300 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n");
2301 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2302 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2303 });
2304}
2305
2306#[gpui::test]
2307fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2308 init_test(cx, |_| {});
2309
2310 let view = cx.add_window(|cx| {
2311 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2312 build_editor(buffer.clone(), cx)
2313 });
2314 let del_to_next_word_end = DeleteToNextWordEnd {
2315 ignore_newlines: false,
2316 };
2317 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2318 ignore_newlines: true,
2319 };
2320
2321 _ = view.update(cx, |view, cx| {
2322 view.change_selections(None, cx, |s| {
2323 s.select_display_ranges([
2324 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2325 ])
2326 });
2327 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2328 assert_eq!(
2329 view.buffer.read(cx).read(cx).text(),
2330 "one\n two\nthree\n four"
2331 );
2332 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2333 assert_eq!(
2334 view.buffer.read(cx).read(cx).text(),
2335 "\n two\nthree\n four"
2336 );
2337 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2338 assert_eq!(view.buffer.read(cx).read(cx).text(), "two\nthree\n four");
2339 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2340 assert_eq!(view.buffer.read(cx).read(cx).text(), "\nthree\n four");
2341 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2342 assert_eq!(view.buffer.read(cx).read(cx).text(), "\n four");
2343 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2344 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2345 });
2346}
2347
2348#[gpui::test]
2349fn test_newline(cx: &mut TestAppContext) {
2350 init_test(cx, |_| {});
2351
2352 let view = cx.add_window(|cx| {
2353 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2354 build_editor(buffer.clone(), cx)
2355 });
2356
2357 _ = view.update(cx, |view, cx| {
2358 view.change_selections(None, cx, |s| {
2359 s.select_display_ranges([
2360 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2361 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2362 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2363 ])
2364 });
2365
2366 view.newline(&Newline, cx);
2367 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2368 });
2369}
2370
2371#[gpui::test]
2372fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2373 init_test(cx, |_| {});
2374
2375 let editor = cx.add_window(|cx| {
2376 let buffer = MultiBuffer::build_simple(
2377 "
2378 a
2379 b(
2380 X
2381 )
2382 c(
2383 X
2384 )
2385 "
2386 .unindent()
2387 .as_str(),
2388 cx,
2389 );
2390 let mut editor = build_editor(buffer.clone(), cx);
2391 editor.change_selections(None, cx, |s| {
2392 s.select_ranges([
2393 Point::new(2, 4)..Point::new(2, 5),
2394 Point::new(5, 4)..Point::new(5, 5),
2395 ])
2396 });
2397 editor
2398 });
2399
2400 _ = editor.update(cx, |editor, cx| {
2401 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2402 editor.buffer.update(cx, |buffer, cx| {
2403 buffer.edit(
2404 [
2405 (Point::new(1, 2)..Point::new(3, 0), ""),
2406 (Point::new(4, 2)..Point::new(6, 0), ""),
2407 ],
2408 None,
2409 cx,
2410 );
2411 assert_eq!(
2412 buffer.read(cx).text(),
2413 "
2414 a
2415 b()
2416 c()
2417 "
2418 .unindent()
2419 );
2420 });
2421 assert_eq!(
2422 editor.selections.ranges(cx),
2423 &[
2424 Point::new(1, 2)..Point::new(1, 2),
2425 Point::new(2, 2)..Point::new(2, 2),
2426 ],
2427 );
2428
2429 editor.newline(&Newline, cx);
2430 assert_eq!(
2431 editor.text(cx),
2432 "
2433 a
2434 b(
2435 )
2436 c(
2437 )
2438 "
2439 .unindent()
2440 );
2441
2442 // The selections are moved after the inserted newlines
2443 assert_eq!(
2444 editor.selections.ranges(cx),
2445 &[
2446 Point::new(2, 0)..Point::new(2, 0),
2447 Point::new(4, 0)..Point::new(4, 0),
2448 ],
2449 );
2450 });
2451}
2452
2453#[gpui::test]
2454async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2455 init_test(cx, |settings| {
2456 settings.defaults.tab_size = NonZeroU32::new(4)
2457 });
2458
2459 let language = Arc::new(
2460 Language::new(
2461 LanguageConfig::default(),
2462 Some(tree_sitter_rust::LANGUAGE.into()),
2463 )
2464 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2465 .unwrap(),
2466 );
2467
2468 let mut cx = EditorTestContext::new(cx).await;
2469 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2470 cx.set_state(indoc! {"
2471 const a: ˇA = (
2472 (ˇ
2473 «const_functionˇ»(ˇ),
2474 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2475 )ˇ
2476 ˇ);ˇ
2477 "});
2478
2479 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2480 cx.assert_editor_state(indoc! {"
2481 ˇ
2482 const a: A = (
2483 ˇ
2484 (
2485 ˇ
2486 ˇ
2487 const_function(),
2488 ˇ
2489 ˇ
2490 ˇ
2491 ˇ
2492 something_else,
2493 ˇ
2494 )
2495 ˇ
2496 ˇ
2497 );
2498 "});
2499}
2500
2501#[gpui::test]
2502async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2503 init_test(cx, |settings| {
2504 settings.defaults.tab_size = NonZeroU32::new(4)
2505 });
2506
2507 let language = Arc::new(
2508 Language::new(
2509 LanguageConfig::default(),
2510 Some(tree_sitter_rust::LANGUAGE.into()),
2511 )
2512 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2513 .unwrap(),
2514 );
2515
2516 let mut cx = EditorTestContext::new(cx).await;
2517 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2518 cx.set_state(indoc! {"
2519 const a: ˇA = (
2520 (ˇ
2521 «const_functionˇ»(ˇ),
2522 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2523 )ˇ
2524 ˇ);ˇ
2525 "});
2526
2527 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2528 cx.assert_editor_state(indoc! {"
2529 const a: A = (
2530 ˇ
2531 (
2532 ˇ
2533 const_function(),
2534 ˇ
2535 ˇ
2536 something_else,
2537 ˇ
2538 ˇ
2539 ˇ
2540 ˇ
2541 )
2542 ˇ
2543 );
2544 ˇ
2545 ˇ
2546 "});
2547}
2548
2549#[gpui::test]
2550async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2551 init_test(cx, |settings| {
2552 settings.defaults.tab_size = NonZeroU32::new(4)
2553 });
2554
2555 let language = Arc::new(Language::new(
2556 LanguageConfig {
2557 line_comments: vec!["//".into()],
2558 ..LanguageConfig::default()
2559 },
2560 None,
2561 ));
2562 {
2563 let mut cx = EditorTestContext::new(cx).await;
2564 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2565 cx.set_state(indoc! {"
2566 // Fooˇ
2567 "});
2568
2569 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2570 cx.assert_editor_state(indoc! {"
2571 // Foo
2572 //ˇ
2573 "});
2574 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2575 cx.set_state(indoc! {"
2576 ˇ// Foo
2577 "});
2578 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2579 cx.assert_editor_state(indoc! {"
2580
2581 ˇ// Foo
2582 "});
2583 }
2584 // Ensure that comment continuations can be disabled.
2585 update_test_language_settings(cx, |settings| {
2586 settings.defaults.extend_comment_on_newline = Some(false);
2587 });
2588 let mut cx = EditorTestContext::new(cx).await;
2589 cx.set_state(indoc! {"
2590 // Fooˇ
2591 "});
2592 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2593 cx.assert_editor_state(indoc! {"
2594 // Foo
2595 ˇ
2596 "});
2597}
2598
2599#[gpui::test]
2600fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2601 init_test(cx, |_| {});
2602
2603 let editor = cx.add_window(|cx| {
2604 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2605 let mut editor = build_editor(buffer.clone(), cx);
2606 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2607 editor
2608 });
2609
2610 _ = editor.update(cx, |editor, cx| {
2611 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2612 editor.buffer.update(cx, |buffer, cx| {
2613 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2614 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2615 });
2616 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2617
2618 editor.insert("Z", cx);
2619 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2620
2621 // The selections are moved after the inserted characters
2622 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2623 });
2624}
2625
2626#[gpui::test]
2627async fn test_tab(cx: &mut gpui::TestAppContext) {
2628 init_test(cx, |settings| {
2629 settings.defaults.tab_size = NonZeroU32::new(3)
2630 });
2631
2632 let mut cx = EditorTestContext::new(cx).await;
2633 cx.set_state(indoc! {"
2634 ˇabˇc
2635 ˇ🏀ˇ🏀ˇefg
2636 dˇ
2637 "});
2638 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2639 cx.assert_editor_state(indoc! {"
2640 ˇab ˇc
2641 ˇ🏀 ˇ🏀 ˇefg
2642 d ˇ
2643 "});
2644
2645 cx.set_state(indoc! {"
2646 a
2647 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2648 "});
2649 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2650 cx.assert_editor_state(indoc! {"
2651 a
2652 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2653 "});
2654}
2655
2656#[gpui::test]
2657async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2658 init_test(cx, |_| {});
2659
2660 let mut cx = EditorTestContext::new(cx).await;
2661 let language = Arc::new(
2662 Language::new(
2663 LanguageConfig::default(),
2664 Some(tree_sitter_rust::LANGUAGE.into()),
2665 )
2666 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2667 .unwrap(),
2668 );
2669 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2670
2671 // cursors that are already at the suggested indent level insert
2672 // a soft tab. cursors that are to the left of the suggested indent
2673 // auto-indent their line.
2674 cx.set_state(indoc! {"
2675 ˇ
2676 const a: B = (
2677 c(
2678 d(
2679 ˇ
2680 )
2681 ˇ
2682 ˇ )
2683 );
2684 "});
2685 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2686 cx.assert_editor_state(indoc! {"
2687 ˇ
2688 const a: B = (
2689 c(
2690 d(
2691 ˇ
2692 )
2693 ˇ
2694 ˇ)
2695 );
2696 "});
2697
2698 // handle auto-indent when there are multiple cursors on the same line
2699 cx.set_state(indoc! {"
2700 const a: B = (
2701 c(
2702 ˇ ˇ
2703 ˇ )
2704 );
2705 "});
2706 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2707 cx.assert_editor_state(indoc! {"
2708 const a: B = (
2709 c(
2710 ˇ
2711 ˇ)
2712 );
2713 "});
2714}
2715
2716#[gpui::test]
2717async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2718 init_test(cx, |settings| {
2719 settings.defaults.tab_size = NonZeroU32::new(4)
2720 });
2721
2722 let language = Arc::new(
2723 Language::new(
2724 LanguageConfig::default(),
2725 Some(tree_sitter_rust::LANGUAGE.into()),
2726 )
2727 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2728 .unwrap(),
2729 );
2730
2731 let mut cx = EditorTestContext::new(cx).await;
2732 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2733 cx.set_state(indoc! {"
2734 fn a() {
2735 if b {
2736 \t ˇc
2737 }
2738 }
2739 "});
2740
2741 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2742 cx.assert_editor_state(indoc! {"
2743 fn a() {
2744 if b {
2745 ˇc
2746 }
2747 }
2748 "});
2749}
2750
2751#[gpui::test]
2752async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2753 init_test(cx, |settings| {
2754 settings.defaults.tab_size = NonZeroU32::new(4);
2755 });
2756
2757 let mut cx = EditorTestContext::new(cx).await;
2758
2759 cx.set_state(indoc! {"
2760 «oneˇ» «twoˇ»
2761 three
2762 four
2763 "});
2764 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2765 cx.assert_editor_state(indoc! {"
2766 «oneˇ» «twoˇ»
2767 three
2768 four
2769 "});
2770
2771 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2772 cx.assert_editor_state(indoc! {"
2773 «oneˇ» «twoˇ»
2774 three
2775 four
2776 "});
2777
2778 // select across line ending
2779 cx.set_state(indoc! {"
2780 one two
2781 t«hree
2782 ˇ» four
2783 "});
2784 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2785 cx.assert_editor_state(indoc! {"
2786 one two
2787 t«hree
2788 ˇ» four
2789 "});
2790
2791 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2792 cx.assert_editor_state(indoc! {"
2793 one two
2794 t«hree
2795 ˇ» four
2796 "});
2797
2798 // Ensure that indenting/outdenting works when the cursor is at column 0.
2799 cx.set_state(indoc! {"
2800 one two
2801 ˇthree
2802 four
2803 "});
2804 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2805 cx.assert_editor_state(indoc! {"
2806 one two
2807 ˇthree
2808 four
2809 "});
2810
2811 cx.set_state(indoc! {"
2812 one two
2813 ˇ three
2814 four
2815 "});
2816 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2817 cx.assert_editor_state(indoc! {"
2818 one two
2819 ˇthree
2820 four
2821 "});
2822}
2823
2824#[gpui::test]
2825async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2826 init_test(cx, |settings| {
2827 settings.defaults.hard_tabs = Some(true);
2828 });
2829
2830 let mut cx = EditorTestContext::new(cx).await;
2831
2832 // select two ranges on one line
2833 cx.set_state(indoc! {"
2834 «oneˇ» «twoˇ»
2835 three
2836 four
2837 "});
2838 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2839 cx.assert_editor_state(indoc! {"
2840 \t«oneˇ» «twoˇ»
2841 three
2842 four
2843 "});
2844 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2845 cx.assert_editor_state(indoc! {"
2846 \t\t«oneˇ» «twoˇ»
2847 three
2848 four
2849 "});
2850 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2851 cx.assert_editor_state(indoc! {"
2852 \t«oneˇ» «twoˇ»
2853 three
2854 four
2855 "});
2856 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2857 cx.assert_editor_state(indoc! {"
2858 «oneˇ» «twoˇ»
2859 three
2860 four
2861 "});
2862
2863 // select across a line ending
2864 cx.set_state(indoc! {"
2865 one two
2866 t«hree
2867 ˇ»four
2868 "});
2869 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2870 cx.assert_editor_state(indoc! {"
2871 one two
2872 \tt«hree
2873 ˇ»four
2874 "});
2875 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2876 cx.assert_editor_state(indoc! {"
2877 one two
2878 \t\tt«hree
2879 ˇ»four
2880 "});
2881 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2882 cx.assert_editor_state(indoc! {"
2883 one two
2884 \tt«hree
2885 ˇ»four
2886 "});
2887 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2888 cx.assert_editor_state(indoc! {"
2889 one two
2890 t«hree
2891 ˇ»four
2892 "});
2893
2894 // Ensure that indenting/outdenting works when the cursor is at column 0.
2895 cx.set_state(indoc! {"
2896 one two
2897 ˇthree
2898 four
2899 "});
2900 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2901 cx.assert_editor_state(indoc! {"
2902 one two
2903 ˇthree
2904 four
2905 "});
2906 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2907 cx.assert_editor_state(indoc! {"
2908 one two
2909 \tˇthree
2910 four
2911 "});
2912 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2913 cx.assert_editor_state(indoc! {"
2914 one two
2915 ˇthree
2916 four
2917 "});
2918}
2919
2920#[gpui::test]
2921fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2922 init_test(cx, |settings| {
2923 settings.languages.extend([
2924 (
2925 "TOML".into(),
2926 LanguageSettingsContent {
2927 tab_size: NonZeroU32::new(2),
2928 ..Default::default()
2929 },
2930 ),
2931 (
2932 "Rust".into(),
2933 LanguageSettingsContent {
2934 tab_size: NonZeroU32::new(4),
2935 ..Default::default()
2936 },
2937 ),
2938 ]);
2939 });
2940
2941 let toml_language = Arc::new(Language::new(
2942 LanguageConfig {
2943 name: "TOML".into(),
2944 ..Default::default()
2945 },
2946 None,
2947 ));
2948 let rust_language = Arc::new(Language::new(
2949 LanguageConfig {
2950 name: "Rust".into(),
2951 ..Default::default()
2952 },
2953 None,
2954 ));
2955
2956 let toml_buffer =
2957 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2958 let rust_buffer = cx.new_model(|cx| {
2959 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2960 });
2961 let multibuffer = cx.new_model(|cx| {
2962 let mut multibuffer = MultiBuffer::new(ReadWrite);
2963 multibuffer.push_excerpts(
2964 toml_buffer.clone(),
2965 [ExcerptRange {
2966 context: Point::new(0, 0)..Point::new(2, 0),
2967 primary: None,
2968 }],
2969 cx,
2970 );
2971 multibuffer.push_excerpts(
2972 rust_buffer.clone(),
2973 [ExcerptRange {
2974 context: Point::new(0, 0)..Point::new(1, 0),
2975 primary: None,
2976 }],
2977 cx,
2978 );
2979 multibuffer
2980 });
2981
2982 cx.add_window(|cx| {
2983 let mut editor = build_editor(multibuffer, cx);
2984
2985 assert_eq!(
2986 editor.text(cx),
2987 indoc! {"
2988 a = 1
2989 b = 2
2990
2991 const c: usize = 3;
2992 "}
2993 );
2994
2995 select_ranges(
2996 &mut editor,
2997 indoc! {"
2998 «aˇ» = 1
2999 b = 2
3000
3001 «const c:ˇ» usize = 3;
3002 "},
3003 cx,
3004 );
3005
3006 editor.tab(&Tab, cx);
3007 assert_text_with_selections(
3008 &mut editor,
3009 indoc! {"
3010 «aˇ» = 1
3011 b = 2
3012
3013 «const c:ˇ» usize = 3;
3014 "},
3015 cx,
3016 );
3017 editor.tab_prev(&TabPrev, cx);
3018 assert_text_with_selections(
3019 &mut editor,
3020 indoc! {"
3021 «aˇ» = 1
3022 b = 2
3023
3024 «const c:ˇ» usize = 3;
3025 "},
3026 cx,
3027 );
3028
3029 editor
3030 });
3031}
3032
3033#[gpui::test]
3034async fn test_backspace(cx: &mut gpui::TestAppContext) {
3035 init_test(cx, |_| {});
3036
3037 let mut cx = EditorTestContext::new(cx).await;
3038
3039 // Basic backspace
3040 cx.set_state(indoc! {"
3041 onˇe two three
3042 fou«rˇ» five six
3043 seven «ˇeight nine
3044 »ten
3045 "});
3046 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3047 cx.assert_editor_state(indoc! {"
3048 oˇe two three
3049 fouˇ five six
3050 seven ˇten
3051 "});
3052
3053 // Test backspace inside and around indents
3054 cx.set_state(indoc! {"
3055 zero
3056 ˇone
3057 ˇtwo
3058 ˇ ˇ ˇ three
3059 ˇ ˇ four
3060 "});
3061 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3062 cx.assert_editor_state(indoc! {"
3063 zero
3064 ˇone
3065 ˇtwo
3066 ˇ threeˇ four
3067 "});
3068
3069 // Test backspace with line_mode set to true
3070 cx.update_editor(|e, _| e.selections.line_mode = true);
3071 cx.set_state(indoc! {"
3072 The ˇquick ˇbrown
3073 fox jumps over
3074 the lazy dog
3075 ˇThe qu«ick bˇ»rown"});
3076 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3077 cx.assert_editor_state(indoc! {"
3078 ˇfox jumps over
3079 the lazy dogˇ"});
3080}
3081
3082#[gpui::test]
3083async fn test_delete(cx: &mut gpui::TestAppContext) {
3084 init_test(cx, |_| {});
3085
3086 let mut cx = EditorTestContext::new(cx).await;
3087 cx.set_state(indoc! {"
3088 onˇe two three
3089 fou«rˇ» five six
3090 seven «ˇeight nine
3091 »ten
3092 "});
3093 cx.update_editor(|e, cx| e.delete(&Delete, cx));
3094 cx.assert_editor_state(indoc! {"
3095 onˇ two three
3096 fouˇ five six
3097 seven ˇten
3098 "});
3099
3100 // Test backspace with line_mode set to true
3101 cx.update_editor(|e, _| e.selections.line_mode = true);
3102 cx.set_state(indoc! {"
3103 The ˇquick ˇbrown
3104 fox «ˇjum»ps over
3105 the lazy dog
3106 ˇThe qu«ick bˇ»rown"});
3107 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3108 cx.assert_editor_state("ˇthe lazy dogˇ");
3109}
3110
3111#[gpui::test]
3112fn test_delete_line(cx: &mut TestAppContext) {
3113 init_test(cx, |_| {});
3114
3115 let view = cx.add_window(|cx| {
3116 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3117 build_editor(buffer, cx)
3118 });
3119 _ = view.update(cx, |view, cx| {
3120 view.change_selections(None, cx, |s| {
3121 s.select_display_ranges([
3122 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3123 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3124 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3125 ])
3126 });
3127 view.delete_line(&DeleteLine, cx);
3128 assert_eq!(view.display_text(cx), "ghi");
3129 assert_eq!(
3130 view.selections.display_ranges(cx),
3131 vec![
3132 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3133 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3134 ]
3135 );
3136 });
3137
3138 let view = cx.add_window(|cx| {
3139 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3140 build_editor(buffer, cx)
3141 });
3142 _ = view.update(cx, |view, cx| {
3143 view.change_selections(None, cx, |s| {
3144 s.select_display_ranges([
3145 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3146 ])
3147 });
3148 view.delete_line(&DeleteLine, cx);
3149 assert_eq!(view.display_text(cx), "ghi\n");
3150 assert_eq!(
3151 view.selections.display_ranges(cx),
3152 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3153 );
3154 });
3155}
3156
3157#[gpui::test]
3158fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3159 init_test(cx, |_| {});
3160
3161 cx.add_window(|cx| {
3162 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3163 let mut editor = build_editor(buffer.clone(), cx);
3164 let buffer = buffer.read(cx).as_singleton().unwrap();
3165
3166 assert_eq!(
3167 editor.selections.ranges::<Point>(cx),
3168 &[Point::new(0, 0)..Point::new(0, 0)]
3169 );
3170
3171 // When on single line, replace newline at end by space
3172 editor.join_lines(&JoinLines, cx);
3173 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3174 assert_eq!(
3175 editor.selections.ranges::<Point>(cx),
3176 &[Point::new(0, 3)..Point::new(0, 3)]
3177 );
3178
3179 // When multiple lines are selected, remove newlines that are spanned by the selection
3180 editor.change_selections(None, cx, |s| {
3181 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3182 });
3183 editor.join_lines(&JoinLines, cx);
3184 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3185 assert_eq!(
3186 editor.selections.ranges::<Point>(cx),
3187 &[Point::new(0, 11)..Point::new(0, 11)]
3188 );
3189
3190 // Undo should be transactional
3191 editor.undo(&Undo, cx);
3192 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3193 assert_eq!(
3194 editor.selections.ranges::<Point>(cx),
3195 &[Point::new(0, 5)..Point::new(2, 2)]
3196 );
3197
3198 // When joining an empty line don't insert a space
3199 editor.change_selections(None, cx, |s| {
3200 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3201 });
3202 editor.join_lines(&JoinLines, cx);
3203 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3204 assert_eq!(
3205 editor.selections.ranges::<Point>(cx),
3206 [Point::new(2, 3)..Point::new(2, 3)]
3207 );
3208
3209 // We can remove trailing newlines
3210 editor.join_lines(&JoinLines, cx);
3211 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3212 assert_eq!(
3213 editor.selections.ranges::<Point>(cx),
3214 [Point::new(2, 3)..Point::new(2, 3)]
3215 );
3216
3217 // We don't blow up on the last line
3218 editor.join_lines(&JoinLines, cx);
3219 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3220 assert_eq!(
3221 editor.selections.ranges::<Point>(cx),
3222 [Point::new(2, 3)..Point::new(2, 3)]
3223 );
3224
3225 // reset to test indentation
3226 editor.buffer.update(cx, |buffer, cx| {
3227 buffer.edit(
3228 [
3229 (Point::new(1, 0)..Point::new(1, 2), " "),
3230 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3231 ],
3232 None,
3233 cx,
3234 )
3235 });
3236
3237 // We remove any leading spaces
3238 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3239 editor.change_selections(None, cx, |s| {
3240 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3241 });
3242 editor.join_lines(&JoinLines, cx);
3243 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3244
3245 // We don't insert a space for a line containing only spaces
3246 editor.join_lines(&JoinLines, cx);
3247 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3248
3249 // We ignore any leading tabs
3250 editor.join_lines(&JoinLines, cx);
3251 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3252
3253 editor
3254 });
3255}
3256
3257#[gpui::test]
3258fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3259 init_test(cx, |_| {});
3260
3261 cx.add_window(|cx| {
3262 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3263 let mut editor = build_editor(buffer.clone(), cx);
3264 let buffer = buffer.read(cx).as_singleton().unwrap();
3265
3266 editor.change_selections(None, cx, |s| {
3267 s.select_ranges([
3268 Point::new(0, 2)..Point::new(1, 1),
3269 Point::new(1, 2)..Point::new(1, 2),
3270 Point::new(3, 1)..Point::new(3, 2),
3271 ])
3272 });
3273
3274 editor.join_lines(&JoinLines, cx);
3275 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3276
3277 assert_eq!(
3278 editor.selections.ranges::<Point>(cx),
3279 [
3280 Point::new(0, 7)..Point::new(0, 7),
3281 Point::new(1, 3)..Point::new(1, 3)
3282 ]
3283 );
3284 editor
3285 });
3286}
3287
3288#[gpui::test]
3289async fn test_join_lines_with_git_diff_base(
3290 executor: BackgroundExecutor,
3291 cx: &mut gpui::TestAppContext,
3292) {
3293 init_test(cx, |_| {});
3294
3295 let mut cx = EditorTestContext::new(cx).await;
3296
3297 let diff_base = r#"
3298 Line 0
3299 Line 1
3300 Line 2
3301 Line 3
3302 "#
3303 .unindent();
3304
3305 cx.set_state(
3306 &r#"
3307 ˇLine 0
3308 Line 1
3309 Line 2
3310 Line 3
3311 "#
3312 .unindent(),
3313 );
3314
3315 cx.set_diff_base(Some(&diff_base));
3316 executor.run_until_parked();
3317
3318 // Join lines
3319 cx.update_editor(|editor, cx| {
3320 editor.join_lines(&JoinLines, cx);
3321 });
3322 executor.run_until_parked();
3323
3324 cx.assert_editor_state(
3325 &r#"
3326 Line 0ˇ Line 1
3327 Line 2
3328 Line 3
3329 "#
3330 .unindent(),
3331 );
3332 // Join again
3333 cx.update_editor(|editor, cx| {
3334 editor.join_lines(&JoinLines, cx);
3335 });
3336 executor.run_until_parked();
3337
3338 cx.assert_editor_state(
3339 &r#"
3340 Line 0 Line 1ˇ Line 2
3341 Line 3
3342 "#
3343 .unindent(),
3344 );
3345}
3346
3347#[gpui::test]
3348async fn test_custom_newlines_cause_no_false_positive_diffs(
3349 executor: BackgroundExecutor,
3350 cx: &mut gpui::TestAppContext,
3351) {
3352 init_test(cx, |_| {});
3353 let mut cx = EditorTestContext::new(cx).await;
3354 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3355 cx.set_diff_base(Some("Line 0\r\nLine 1\r\nLine 2\r\nLine 3"));
3356 executor.run_until_parked();
3357
3358 cx.update_editor(|editor, cx| {
3359 assert_eq!(
3360 editor
3361 .buffer()
3362 .read(cx)
3363 .snapshot(cx)
3364 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
3365 .collect::<Vec<_>>(),
3366 Vec::new(),
3367 "Should not have any diffs for files with custom newlines"
3368 );
3369 });
3370}
3371
3372#[gpui::test]
3373async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3374 init_test(cx, |_| {});
3375
3376 let mut cx = EditorTestContext::new(cx).await;
3377
3378 // Test sort_lines_case_insensitive()
3379 cx.set_state(indoc! {"
3380 «z
3381 y
3382 x
3383 Z
3384 Y
3385 Xˇ»
3386 "});
3387 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3388 cx.assert_editor_state(indoc! {"
3389 «x
3390 X
3391 y
3392 Y
3393 z
3394 Zˇ»
3395 "});
3396
3397 // Test reverse_lines()
3398 cx.set_state(indoc! {"
3399 «5
3400 4
3401 3
3402 2
3403 1ˇ»
3404 "});
3405 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3406 cx.assert_editor_state(indoc! {"
3407 «1
3408 2
3409 3
3410 4
3411 5ˇ»
3412 "});
3413
3414 // Skip testing shuffle_line()
3415
3416 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3417 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3418
3419 // Don't manipulate when cursor is on single line, but expand the selection
3420 cx.set_state(indoc! {"
3421 ddˇdd
3422 ccc
3423 bb
3424 a
3425 "});
3426 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3427 cx.assert_editor_state(indoc! {"
3428 «ddddˇ»
3429 ccc
3430 bb
3431 a
3432 "});
3433
3434 // Basic manipulate case
3435 // Start selection moves to column 0
3436 // End of selection shrinks to fit shorter line
3437 cx.set_state(indoc! {"
3438 dd«d
3439 ccc
3440 bb
3441 aaaaaˇ»
3442 "});
3443 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3444 cx.assert_editor_state(indoc! {"
3445 «aaaaa
3446 bb
3447 ccc
3448 dddˇ»
3449 "});
3450
3451 // Manipulate case with newlines
3452 cx.set_state(indoc! {"
3453 dd«d
3454 ccc
3455
3456 bb
3457 aaaaa
3458
3459 ˇ»
3460 "});
3461 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3462 cx.assert_editor_state(indoc! {"
3463 «
3464
3465 aaaaa
3466 bb
3467 ccc
3468 dddˇ»
3469
3470 "});
3471
3472 // Adding new line
3473 cx.set_state(indoc! {"
3474 aa«a
3475 bbˇ»b
3476 "});
3477 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3478 cx.assert_editor_state(indoc! {"
3479 «aaa
3480 bbb
3481 added_lineˇ»
3482 "});
3483
3484 // Removing line
3485 cx.set_state(indoc! {"
3486 aa«a
3487 bbbˇ»
3488 "});
3489 cx.update_editor(|e, cx| {
3490 e.manipulate_lines(cx, |lines| {
3491 lines.pop();
3492 })
3493 });
3494 cx.assert_editor_state(indoc! {"
3495 «aaaˇ»
3496 "});
3497
3498 // Removing all lines
3499 cx.set_state(indoc! {"
3500 aa«a
3501 bbbˇ»
3502 "});
3503 cx.update_editor(|e, cx| {
3504 e.manipulate_lines(cx, |lines| {
3505 lines.drain(..);
3506 })
3507 });
3508 cx.assert_editor_state(indoc! {"
3509 ˇ
3510 "});
3511}
3512
3513#[gpui::test]
3514async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3515 init_test(cx, |_| {});
3516
3517 let mut cx = EditorTestContext::new(cx).await;
3518
3519 // Consider continuous selection as single selection
3520 cx.set_state(indoc! {"
3521 Aaa«aa
3522 cˇ»c«c
3523 bb
3524 aaaˇ»aa
3525 "});
3526 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3527 cx.assert_editor_state(indoc! {"
3528 «Aaaaa
3529 ccc
3530 bb
3531 aaaaaˇ»
3532 "});
3533
3534 cx.set_state(indoc! {"
3535 Aaa«aa
3536 cˇ»c«c
3537 bb
3538 aaaˇ»aa
3539 "});
3540 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3541 cx.assert_editor_state(indoc! {"
3542 «Aaaaa
3543 ccc
3544 bbˇ»
3545 "});
3546
3547 // Consider non continuous selection as distinct dedup operations
3548 cx.set_state(indoc! {"
3549 «aaaaa
3550 bb
3551 aaaaa
3552 aaaaaˇ»
3553
3554 aaa«aaˇ»
3555 "});
3556 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3557 cx.assert_editor_state(indoc! {"
3558 «aaaaa
3559 bbˇ»
3560
3561 «aaaaaˇ»
3562 "});
3563}
3564
3565#[gpui::test]
3566async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3567 init_test(cx, |_| {});
3568
3569 let mut cx = EditorTestContext::new(cx).await;
3570
3571 cx.set_state(indoc! {"
3572 «Aaa
3573 aAa
3574 Aaaˇ»
3575 "});
3576 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3577 cx.assert_editor_state(indoc! {"
3578 «Aaa
3579 aAaˇ»
3580 "});
3581
3582 cx.set_state(indoc! {"
3583 «Aaa
3584 aAa
3585 aaAˇ»
3586 "});
3587 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3588 cx.assert_editor_state(indoc! {"
3589 «Aaaˇ»
3590 "});
3591}
3592
3593#[gpui::test]
3594async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3595 init_test(cx, |_| {});
3596
3597 let mut cx = EditorTestContext::new(cx).await;
3598
3599 // Manipulate with multiple selections on a single line
3600 cx.set_state(indoc! {"
3601 dd«dd
3602 cˇ»c«c
3603 bb
3604 aaaˇ»aa
3605 "});
3606 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3607 cx.assert_editor_state(indoc! {"
3608 «aaaaa
3609 bb
3610 ccc
3611 ddddˇ»
3612 "});
3613
3614 // Manipulate with multiple disjoin selections
3615 cx.set_state(indoc! {"
3616 5«
3617 4
3618 3
3619 2
3620 1ˇ»
3621
3622 dd«dd
3623 ccc
3624 bb
3625 aaaˇ»aa
3626 "});
3627 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3628 cx.assert_editor_state(indoc! {"
3629 «1
3630 2
3631 3
3632 4
3633 5ˇ»
3634
3635 «aaaaa
3636 bb
3637 ccc
3638 ddddˇ»
3639 "});
3640
3641 // Adding lines on each selection
3642 cx.set_state(indoc! {"
3643 2«
3644 1ˇ»
3645
3646 bb«bb
3647 aaaˇ»aa
3648 "});
3649 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3650 cx.assert_editor_state(indoc! {"
3651 «2
3652 1
3653 added lineˇ»
3654
3655 «bbbb
3656 aaaaa
3657 added lineˇ»
3658 "});
3659
3660 // Removing lines on each selection
3661 cx.set_state(indoc! {"
3662 2«
3663 1ˇ»
3664
3665 bb«bb
3666 aaaˇ»aa
3667 "});
3668 cx.update_editor(|e, cx| {
3669 e.manipulate_lines(cx, |lines| {
3670 lines.pop();
3671 })
3672 });
3673 cx.assert_editor_state(indoc! {"
3674 «2ˇ»
3675
3676 «bbbbˇ»
3677 "});
3678}
3679
3680#[gpui::test]
3681async fn test_manipulate_text(cx: &mut TestAppContext) {
3682 init_test(cx, |_| {});
3683
3684 let mut cx = EditorTestContext::new(cx).await;
3685
3686 // Test convert_to_upper_case()
3687 cx.set_state(indoc! {"
3688 «hello worldˇ»
3689 "});
3690 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3691 cx.assert_editor_state(indoc! {"
3692 «HELLO WORLDˇ»
3693 "});
3694
3695 // Test convert_to_lower_case()
3696 cx.set_state(indoc! {"
3697 «HELLO WORLDˇ»
3698 "});
3699 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3700 cx.assert_editor_state(indoc! {"
3701 «hello worldˇ»
3702 "});
3703
3704 // Test multiple line, single selection case
3705 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3706 cx.set_state(indoc! {"
3707 «The quick brown
3708 fox jumps over
3709 the lazy dogˇ»
3710 "});
3711 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3712 cx.assert_editor_state(indoc! {"
3713 «The Quick Brown
3714 Fox Jumps Over
3715 The Lazy Dogˇ»
3716 "});
3717
3718 // Test multiple line, single selection case
3719 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3720 cx.set_state(indoc! {"
3721 «The quick brown
3722 fox jumps over
3723 the lazy dogˇ»
3724 "});
3725 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3726 cx.assert_editor_state(indoc! {"
3727 «TheQuickBrown
3728 FoxJumpsOver
3729 TheLazyDogˇ»
3730 "});
3731
3732 // From here on out, test more complex cases of manipulate_text()
3733
3734 // Test no selection case - should affect words cursors are in
3735 // Cursor at beginning, middle, and end of word
3736 cx.set_state(indoc! {"
3737 ˇhello big beauˇtiful worldˇ
3738 "});
3739 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3740 cx.assert_editor_state(indoc! {"
3741 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3742 "});
3743
3744 // Test multiple selections on a single line and across multiple lines
3745 cx.set_state(indoc! {"
3746 «Theˇ» quick «brown
3747 foxˇ» jumps «overˇ»
3748 the «lazyˇ» dog
3749 "});
3750 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3751 cx.assert_editor_state(indoc! {"
3752 «THEˇ» quick «BROWN
3753 FOXˇ» jumps «OVERˇ»
3754 the «LAZYˇ» dog
3755 "});
3756
3757 // Test case where text length grows
3758 cx.set_state(indoc! {"
3759 «tschüߡ»
3760 "});
3761 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3762 cx.assert_editor_state(indoc! {"
3763 «TSCHÜSSˇ»
3764 "});
3765
3766 // Test to make sure we don't crash when text shrinks
3767 cx.set_state(indoc! {"
3768 aaa_bbbˇ
3769 "});
3770 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3771 cx.assert_editor_state(indoc! {"
3772 «aaaBbbˇ»
3773 "});
3774
3775 // Test to make sure we all aware of the fact that each word can grow and shrink
3776 // Final selections should be aware of this fact
3777 cx.set_state(indoc! {"
3778 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3779 "});
3780 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3781 cx.assert_editor_state(indoc! {"
3782 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3783 "});
3784
3785 cx.set_state(indoc! {"
3786 «hElLo, WoRld!ˇ»
3787 "});
3788 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3789 cx.assert_editor_state(indoc! {"
3790 «HeLlO, wOrLD!ˇ»
3791 "});
3792}
3793
3794#[gpui::test]
3795fn test_duplicate_line(cx: &mut TestAppContext) {
3796 init_test(cx, |_| {});
3797
3798 let view = cx.add_window(|cx| {
3799 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3800 build_editor(buffer, cx)
3801 });
3802 _ = view.update(cx, |view, cx| {
3803 view.change_selections(None, cx, |s| {
3804 s.select_display_ranges([
3805 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3806 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3807 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3808 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3809 ])
3810 });
3811 view.duplicate_line_down(&DuplicateLineDown, cx);
3812 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3813 assert_eq!(
3814 view.selections.display_ranges(cx),
3815 vec![
3816 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3817 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3818 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3819 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3820 ]
3821 );
3822 });
3823
3824 let view = cx.add_window(|cx| {
3825 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3826 build_editor(buffer, cx)
3827 });
3828 _ = view.update(cx, |view, cx| {
3829 view.change_selections(None, cx, |s| {
3830 s.select_display_ranges([
3831 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3832 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3833 ])
3834 });
3835 view.duplicate_line_down(&DuplicateLineDown, cx);
3836 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3837 assert_eq!(
3838 view.selections.display_ranges(cx),
3839 vec![
3840 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3841 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3842 ]
3843 );
3844 });
3845
3846 // With `move_upwards` the selections stay in place, except for
3847 // the lines inserted above them
3848 let view = cx.add_window(|cx| {
3849 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3850 build_editor(buffer, cx)
3851 });
3852 _ = view.update(cx, |view, cx| {
3853 view.change_selections(None, cx, |s| {
3854 s.select_display_ranges([
3855 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3856 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3857 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3858 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3859 ])
3860 });
3861 view.duplicate_line_up(&DuplicateLineUp, cx);
3862 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3863 assert_eq!(
3864 view.selections.display_ranges(cx),
3865 vec![
3866 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3867 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3868 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3869 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3870 ]
3871 );
3872 });
3873
3874 let view = cx.add_window(|cx| {
3875 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3876 build_editor(buffer, cx)
3877 });
3878 _ = view.update(cx, |view, cx| {
3879 view.change_selections(None, cx, |s| {
3880 s.select_display_ranges([
3881 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3882 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3883 ])
3884 });
3885 view.duplicate_line_up(&DuplicateLineUp, cx);
3886 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3887 assert_eq!(
3888 view.selections.display_ranges(cx),
3889 vec![
3890 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3891 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3892 ]
3893 );
3894 });
3895}
3896
3897#[gpui::test]
3898fn test_move_line_up_down(cx: &mut TestAppContext) {
3899 init_test(cx, |_| {});
3900
3901 let view = cx.add_window(|cx| {
3902 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3903 build_editor(buffer, cx)
3904 });
3905 _ = view.update(cx, |view, cx| {
3906 view.fold_creases(
3907 vec![
3908 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3909 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3910 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3911 ],
3912 true,
3913 cx,
3914 );
3915 view.change_selections(None, cx, |s| {
3916 s.select_display_ranges([
3917 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3918 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3919 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3920 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3921 ])
3922 });
3923 assert_eq!(
3924 view.display_text(cx),
3925 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3926 );
3927
3928 view.move_line_up(&MoveLineUp, cx);
3929 assert_eq!(
3930 view.display_text(cx),
3931 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3932 );
3933 assert_eq!(
3934 view.selections.display_ranges(cx),
3935 vec![
3936 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3937 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3938 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3939 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3940 ]
3941 );
3942 });
3943
3944 _ = view.update(cx, |view, cx| {
3945 view.move_line_down(&MoveLineDown, cx);
3946 assert_eq!(
3947 view.display_text(cx),
3948 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3949 );
3950 assert_eq!(
3951 view.selections.display_ranges(cx),
3952 vec![
3953 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3954 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3955 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3956 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3957 ]
3958 );
3959 });
3960
3961 _ = view.update(cx, |view, cx| {
3962 view.move_line_down(&MoveLineDown, cx);
3963 assert_eq!(
3964 view.display_text(cx),
3965 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3966 );
3967 assert_eq!(
3968 view.selections.display_ranges(cx),
3969 vec![
3970 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3971 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3972 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3973 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3974 ]
3975 );
3976 });
3977
3978 _ = view.update(cx, |view, cx| {
3979 view.move_line_up(&MoveLineUp, cx);
3980 assert_eq!(
3981 view.display_text(cx),
3982 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3983 );
3984 assert_eq!(
3985 view.selections.display_ranges(cx),
3986 vec![
3987 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3988 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3989 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3990 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3991 ]
3992 );
3993 });
3994}
3995
3996#[gpui::test]
3997fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3998 init_test(cx, |_| {});
3999
4000 let editor = cx.add_window(|cx| {
4001 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4002 build_editor(buffer, cx)
4003 });
4004 _ = editor.update(cx, |editor, cx| {
4005 let snapshot = editor.buffer.read(cx).snapshot(cx);
4006 editor.insert_blocks(
4007 [BlockProperties {
4008 style: BlockStyle::Fixed,
4009 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4010 height: 1,
4011 render: Arc::new(|_| div().into_any()),
4012 priority: 0,
4013 }],
4014 Some(Autoscroll::fit()),
4015 cx,
4016 );
4017 editor.change_selections(None, cx, |s| {
4018 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4019 });
4020 editor.move_line_down(&MoveLineDown, cx);
4021 });
4022}
4023
4024#[gpui::test]
4025async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4026 init_test(cx, |_| {});
4027
4028 let mut cx = EditorTestContext::new(cx).await;
4029 cx.set_state(
4030 &"
4031 ˇzero
4032 one
4033 two
4034 three
4035 four
4036 five
4037 "
4038 .unindent(),
4039 );
4040
4041 // Create a four-line block that replaces three lines of text.
4042 cx.update_editor(|editor, cx| {
4043 let snapshot = editor.snapshot(cx);
4044 let snapshot = &snapshot.buffer_snapshot;
4045 let placement = BlockPlacement::Replace(
4046 snapshot.anchor_after(Point::new(1, 0))..snapshot.anchor_after(Point::new(3, 0)),
4047 );
4048 editor.insert_blocks(
4049 [BlockProperties {
4050 placement,
4051 height: 4,
4052 style: BlockStyle::Sticky,
4053 render: Arc::new(|_| gpui::div().into_any_element()),
4054 priority: 0,
4055 }],
4056 None,
4057 cx,
4058 );
4059 });
4060
4061 // Move down so that the cursor touches the block.
4062 cx.update_editor(|editor, cx| {
4063 editor.move_down(&Default::default(), cx);
4064 });
4065 cx.assert_editor_state(
4066 &"
4067 zero
4068 «one
4069 two
4070 threeˇ»
4071 four
4072 five
4073 "
4074 .unindent(),
4075 );
4076
4077 // Move down past the block.
4078 cx.update_editor(|editor, cx| {
4079 editor.move_down(&Default::default(), cx);
4080 });
4081 cx.assert_editor_state(
4082 &"
4083 zero
4084 one
4085 two
4086 three
4087 ˇfour
4088 five
4089 "
4090 .unindent(),
4091 );
4092}
4093
4094#[gpui::test]
4095fn test_transpose(cx: &mut TestAppContext) {
4096 init_test(cx, |_| {});
4097
4098 _ = cx.add_window(|cx| {
4099 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
4100 editor.set_style(EditorStyle::default(), cx);
4101 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
4102 editor.transpose(&Default::default(), cx);
4103 assert_eq!(editor.text(cx), "bac");
4104 assert_eq!(editor.selections.ranges(cx), [2..2]);
4105
4106 editor.transpose(&Default::default(), cx);
4107 assert_eq!(editor.text(cx), "bca");
4108 assert_eq!(editor.selections.ranges(cx), [3..3]);
4109
4110 editor.transpose(&Default::default(), cx);
4111 assert_eq!(editor.text(cx), "bac");
4112 assert_eq!(editor.selections.ranges(cx), [3..3]);
4113
4114 editor
4115 });
4116
4117 _ = cx.add_window(|cx| {
4118 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4119 editor.set_style(EditorStyle::default(), cx);
4120 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
4121 editor.transpose(&Default::default(), cx);
4122 assert_eq!(editor.text(cx), "acb\nde");
4123 assert_eq!(editor.selections.ranges(cx), [3..3]);
4124
4125 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4126 editor.transpose(&Default::default(), cx);
4127 assert_eq!(editor.text(cx), "acbd\ne");
4128 assert_eq!(editor.selections.ranges(cx), [5..5]);
4129
4130 editor.transpose(&Default::default(), cx);
4131 assert_eq!(editor.text(cx), "acbde\n");
4132 assert_eq!(editor.selections.ranges(cx), [6..6]);
4133
4134 editor.transpose(&Default::default(), cx);
4135 assert_eq!(editor.text(cx), "acbd\ne");
4136 assert_eq!(editor.selections.ranges(cx), [6..6]);
4137
4138 editor
4139 });
4140
4141 _ = cx.add_window(|cx| {
4142 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4143 editor.set_style(EditorStyle::default(), cx);
4144 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4145 editor.transpose(&Default::default(), cx);
4146 assert_eq!(editor.text(cx), "bacd\ne");
4147 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4148
4149 editor.transpose(&Default::default(), cx);
4150 assert_eq!(editor.text(cx), "bcade\n");
4151 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4152
4153 editor.transpose(&Default::default(), cx);
4154 assert_eq!(editor.text(cx), "bcda\ne");
4155 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4156
4157 editor.transpose(&Default::default(), cx);
4158 assert_eq!(editor.text(cx), "bcade\n");
4159 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4160
4161 editor.transpose(&Default::default(), cx);
4162 assert_eq!(editor.text(cx), "bcaed\n");
4163 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4164
4165 editor
4166 });
4167
4168 _ = cx.add_window(|cx| {
4169 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
4170 editor.set_style(EditorStyle::default(), cx);
4171 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4172 editor.transpose(&Default::default(), cx);
4173 assert_eq!(editor.text(cx), "🏀🍐✋");
4174 assert_eq!(editor.selections.ranges(cx), [8..8]);
4175
4176 editor.transpose(&Default::default(), cx);
4177 assert_eq!(editor.text(cx), "🏀✋🍐");
4178 assert_eq!(editor.selections.ranges(cx), [11..11]);
4179
4180 editor.transpose(&Default::default(), cx);
4181 assert_eq!(editor.text(cx), "🏀🍐✋");
4182 assert_eq!(editor.selections.ranges(cx), [11..11]);
4183
4184 editor
4185 });
4186}
4187
4188#[gpui::test]
4189async fn test_rewrap(cx: &mut TestAppContext) {
4190 init_test(cx, |_| {});
4191
4192 let mut cx = EditorTestContext::new(cx).await;
4193
4194 let language_with_c_comments = Arc::new(Language::new(
4195 LanguageConfig {
4196 line_comments: vec!["// ".into()],
4197 ..LanguageConfig::default()
4198 },
4199 None,
4200 ));
4201 let language_with_pound_comments = Arc::new(Language::new(
4202 LanguageConfig {
4203 line_comments: vec!["# ".into()],
4204 ..LanguageConfig::default()
4205 },
4206 None,
4207 ));
4208 let markdown_language = Arc::new(Language::new(
4209 LanguageConfig {
4210 name: "Markdown".into(),
4211 ..LanguageConfig::default()
4212 },
4213 None,
4214 ));
4215 let language_with_doc_comments = Arc::new(Language::new(
4216 LanguageConfig {
4217 line_comments: vec!["// ".into(), "/// ".into()],
4218 ..LanguageConfig::default()
4219 },
4220 Some(tree_sitter_rust::LANGUAGE.into()),
4221 ));
4222
4223 let plaintext_language = Arc::new(Language::new(
4224 LanguageConfig {
4225 name: "Plain Text".into(),
4226 ..LanguageConfig::default()
4227 },
4228 None,
4229 ));
4230
4231 assert_rewrap(
4232 indoc! {"
4233 // ˇ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.
4234 "},
4235 indoc! {"
4236 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4237 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4238 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4239 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4240 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4241 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4242 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4243 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4244 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4245 // porttitor id. Aliquam id accumsan eros.
4246 "},
4247 language_with_c_comments.clone(),
4248 &mut cx,
4249 );
4250
4251 // Test that rewrapping works inside of a selection
4252 assert_rewrap(
4253 indoc! {"
4254 «// 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.ˇ»
4255 "},
4256 indoc! {"
4257 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4258 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4259 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4260 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4261 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4262 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4263 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4264 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4265 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4266 // porttitor id. Aliquam id accumsan eros.ˇ»
4267 "},
4268 language_with_c_comments.clone(),
4269 &mut cx,
4270 );
4271
4272 // Test that cursors that expand to the same region are collapsed.
4273 assert_rewrap(
4274 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 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4278 // ˇ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.
4279 "},
4280 indoc! {"
4281 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4282 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4283 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4284 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4285 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4286 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4287 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4288 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4289 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4290 // porttitor id. Aliquam id accumsan eros.
4291 "},
4292 language_with_c_comments.clone(),
4293 &mut cx,
4294 );
4295
4296 // Test that non-contiguous selections are treated separately.
4297 assert_rewrap(
4298 indoc! {"
4299 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4300 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4301 //
4302 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4303 // ˇ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.
4304 "},
4305 indoc! {"
4306 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4307 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4308 // auctor, eu lacinia sapien scelerisque.
4309 //
4310 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4311 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4312 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4313 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4314 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4315 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4316 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4317 "},
4318 language_with_c_comments.clone(),
4319 &mut cx,
4320 );
4321
4322 // Test that different comment prefixes are supported.
4323 assert_rewrap(
4324 indoc! {"
4325 # ˇ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.
4326 "},
4327 indoc! {"
4328 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4329 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4330 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4331 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4332 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4333 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4334 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4335 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4336 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4337 # accumsan eros.
4338 "},
4339 language_with_pound_comments.clone(),
4340 &mut cx,
4341 );
4342
4343 // Test that rewrapping is ignored outside of comments in most languages.
4344 assert_rewrap(
4345 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 indoc! {"
4353 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4354 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4355 fn add(a: u32, b: u32) -> u32 {
4356 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ˇ
4357 }
4358 "},
4359 language_with_doc_comments.clone(),
4360 &mut cx,
4361 );
4362
4363 // Test that rewrapping works in Markdown and Plain Text languages.
4364 assert_rewrap(
4365 indoc! {"
4366 # Hello
4367
4368 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.
4369 "},
4370 indoc! {"
4371 # Hello
4372
4373 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4374 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4375 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4376 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4377 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4378 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4379 Integer sit amet scelerisque nisi.
4380 "},
4381 markdown_language,
4382 &mut cx,
4383 );
4384
4385 assert_rewrap(
4386 indoc! {"
4387 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.
4388 "},
4389 indoc! {"
4390 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4391 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4392 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4393 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4394 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4395 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4396 Integer sit amet scelerisque nisi.
4397 "},
4398 plaintext_language,
4399 &mut cx,
4400 );
4401
4402 // Test rewrapping unaligned comments in a selection.
4403 assert_rewrap(
4404 indoc! {"
4405 fn foo() {
4406 if true {
4407 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4408 // Praesent semper egestas tellus id dignissim.ˇ»
4409 do_something();
4410 } else {
4411 //
4412 }
4413 }
4414 "},
4415 indoc! {"
4416 fn foo() {
4417 if true {
4418 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4419 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4420 // egestas tellus id dignissim.ˇ»
4421 do_something();
4422 } else {
4423 //
4424 }
4425 }
4426 "},
4427 language_with_doc_comments.clone(),
4428 &mut cx,
4429 );
4430
4431 assert_rewrap(
4432 indoc! {"
4433 fn foo() {
4434 if true {
4435 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4436 // Praesent semper egestas tellus id dignissim.»
4437 do_something();
4438 } else {
4439 //
4440 }
4441
4442 }
4443 "},
4444 indoc! {"
4445 fn foo() {
4446 if true {
4447 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4448 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4449 // egestas tellus id dignissim.»
4450 do_something();
4451 } else {
4452 //
4453 }
4454
4455 }
4456 "},
4457 language_with_doc_comments.clone(),
4458 &mut cx,
4459 );
4460
4461 #[track_caller]
4462 fn assert_rewrap(
4463 unwrapped_text: &str,
4464 wrapped_text: &str,
4465 language: Arc<Language>,
4466 cx: &mut EditorTestContext,
4467 ) {
4468 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4469 cx.set_state(unwrapped_text);
4470 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4471 cx.assert_editor_state(wrapped_text);
4472 }
4473}
4474
4475#[gpui::test]
4476async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4477 init_test(cx, |_| {});
4478
4479 let mut cx = EditorTestContext::new(cx).await;
4480
4481 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4482 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4483 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4484
4485 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4486 cx.set_state("two ˇfour ˇsix ˇ");
4487 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4488 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4489
4490 // Paste again but with only two cursors. Since the number of cursors doesn't
4491 // match the number of slices in the clipboard, the entire clipboard text
4492 // is pasted at each cursor.
4493 cx.set_state("ˇtwo one✅ four three six five ˇ");
4494 cx.update_editor(|e, cx| {
4495 e.handle_input("( ", cx);
4496 e.paste(&Paste, cx);
4497 e.handle_input(") ", cx);
4498 });
4499 cx.assert_editor_state(
4500 &([
4501 "( one✅ ",
4502 "three ",
4503 "five ) ˇtwo one✅ four three six five ( one✅ ",
4504 "three ",
4505 "five ) ˇ",
4506 ]
4507 .join("\n")),
4508 );
4509
4510 // Cut with three selections, one of which is full-line.
4511 cx.set_state(indoc! {"
4512 1«2ˇ»3
4513 4ˇ567
4514 «8ˇ»9"});
4515 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4516 cx.assert_editor_state(indoc! {"
4517 1ˇ3
4518 ˇ9"});
4519
4520 // Paste with three selections, noticing how the copied selection that was full-line
4521 // gets inserted before the second cursor.
4522 cx.set_state(indoc! {"
4523 1ˇ3
4524 9ˇ
4525 «oˇ»ne"});
4526 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4527 cx.assert_editor_state(indoc! {"
4528 12ˇ3
4529 4567
4530 9ˇ
4531 8ˇne"});
4532
4533 // Copy with a single cursor only, which writes the whole line into the clipboard.
4534 cx.set_state(indoc! {"
4535 The quick brown
4536 fox juˇmps over
4537 the lazy dog"});
4538 cx.update_editor(|e, cx| e.copy(&Copy, cx));
4539 assert_eq!(
4540 cx.read_from_clipboard()
4541 .and_then(|item| item.text().as_deref().map(str::to_string)),
4542 Some("fox jumps over\n".to_string())
4543 );
4544
4545 // Paste with three selections, noticing how the copied full-line selection is inserted
4546 // before the empty selections but replaces the selection that is non-empty.
4547 cx.set_state(indoc! {"
4548 Tˇhe quick brown
4549 «foˇ»x jumps over
4550 tˇhe lazy dog"});
4551 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4552 cx.assert_editor_state(indoc! {"
4553 fox jumps over
4554 Tˇhe quick brown
4555 fox jumps over
4556 ˇx jumps over
4557 fox jumps over
4558 tˇhe lazy dog"});
4559}
4560
4561#[gpui::test]
4562async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4563 init_test(cx, |_| {});
4564
4565 let mut cx = EditorTestContext::new(cx).await;
4566 let language = Arc::new(Language::new(
4567 LanguageConfig::default(),
4568 Some(tree_sitter_rust::LANGUAGE.into()),
4569 ));
4570 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4571
4572 // Cut an indented block, without the leading whitespace.
4573 cx.set_state(indoc! {"
4574 const a: B = (
4575 c(),
4576 «d(
4577 e,
4578 f
4579 )ˇ»
4580 );
4581 "});
4582 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4583 cx.assert_editor_state(indoc! {"
4584 const a: B = (
4585 c(),
4586 ˇ
4587 );
4588 "});
4589
4590 // Paste it at the same position.
4591 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4592 cx.assert_editor_state(indoc! {"
4593 const a: B = (
4594 c(),
4595 d(
4596 e,
4597 f
4598 )ˇ
4599 );
4600 "});
4601
4602 // Paste it at a line with a lower indent level.
4603 cx.set_state(indoc! {"
4604 ˇ
4605 const a: B = (
4606 c(),
4607 );
4608 "});
4609 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4610 cx.assert_editor_state(indoc! {"
4611 d(
4612 e,
4613 f
4614 )ˇ
4615 const a: B = (
4616 c(),
4617 );
4618 "});
4619
4620 // Cut an indented block, with the leading whitespace.
4621 cx.set_state(indoc! {"
4622 const a: B = (
4623 c(),
4624 « d(
4625 e,
4626 f
4627 )
4628 ˇ»);
4629 "});
4630 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4631 cx.assert_editor_state(indoc! {"
4632 const a: B = (
4633 c(),
4634 ˇ);
4635 "});
4636
4637 // Paste it at the same position.
4638 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4639 cx.assert_editor_state(indoc! {"
4640 const a: B = (
4641 c(),
4642 d(
4643 e,
4644 f
4645 )
4646 ˇ);
4647 "});
4648
4649 // Paste it at a line with a higher indent level.
4650 cx.set_state(indoc! {"
4651 const a: B = (
4652 c(),
4653 d(
4654 e,
4655 fˇ
4656 )
4657 );
4658 "});
4659 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4660 cx.assert_editor_state(indoc! {"
4661 const a: B = (
4662 c(),
4663 d(
4664 e,
4665 f d(
4666 e,
4667 f
4668 )
4669 ˇ
4670 )
4671 );
4672 "});
4673}
4674
4675#[gpui::test]
4676fn test_select_all(cx: &mut TestAppContext) {
4677 init_test(cx, |_| {});
4678
4679 let view = cx.add_window(|cx| {
4680 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4681 build_editor(buffer, cx)
4682 });
4683 _ = view.update(cx, |view, cx| {
4684 view.select_all(&SelectAll, cx);
4685 assert_eq!(
4686 view.selections.display_ranges(cx),
4687 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4688 );
4689 });
4690}
4691
4692#[gpui::test]
4693fn test_select_line(cx: &mut TestAppContext) {
4694 init_test(cx, |_| {});
4695
4696 let view = cx.add_window(|cx| {
4697 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4698 build_editor(buffer, cx)
4699 });
4700 _ = view.update(cx, |view, cx| {
4701 view.change_selections(None, cx, |s| {
4702 s.select_display_ranges([
4703 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4704 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4705 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4706 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4707 ])
4708 });
4709 view.select_line(&SelectLine, cx);
4710 assert_eq!(
4711 view.selections.display_ranges(cx),
4712 vec![
4713 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4714 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4715 ]
4716 );
4717 });
4718
4719 _ = view.update(cx, |view, cx| {
4720 view.select_line(&SelectLine, cx);
4721 assert_eq!(
4722 view.selections.display_ranges(cx),
4723 vec![
4724 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4725 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4726 ]
4727 );
4728 });
4729
4730 _ = view.update(cx, |view, cx| {
4731 view.select_line(&SelectLine, cx);
4732 assert_eq!(
4733 view.selections.display_ranges(cx),
4734 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4735 );
4736 });
4737}
4738
4739#[gpui::test]
4740fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4741 init_test(cx, |_| {});
4742
4743 let view = cx.add_window(|cx| {
4744 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4745 build_editor(buffer, cx)
4746 });
4747 _ = view.update(cx, |view, cx| {
4748 view.fold_creases(
4749 vec![
4750 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4751 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4752 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4753 ],
4754 true,
4755 cx,
4756 );
4757 view.change_selections(None, cx, |s| {
4758 s.select_display_ranges([
4759 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4760 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4761 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4762 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4763 ])
4764 });
4765 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4766 });
4767
4768 _ = view.update(cx, |view, cx| {
4769 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4770 assert_eq!(
4771 view.display_text(cx),
4772 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4773 );
4774 assert_eq!(
4775 view.selections.display_ranges(cx),
4776 [
4777 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4778 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4779 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4780 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4781 ]
4782 );
4783 });
4784
4785 _ = view.update(cx, |view, cx| {
4786 view.change_selections(None, cx, |s| {
4787 s.select_display_ranges([
4788 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4789 ])
4790 });
4791 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4792 assert_eq!(
4793 view.display_text(cx),
4794 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4795 );
4796 assert_eq!(
4797 view.selections.display_ranges(cx),
4798 [
4799 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4800 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4801 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4802 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4803 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4804 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4805 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4806 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4807 ]
4808 );
4809 });
4810}
4811
4812#[gpui::test]
4813async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4814 init_test(cx, |_| {});
4815
4816 let mut cx = EditorTestContext::new(cx).await;
4817
4818 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4819 cx.set_state(indoc!(
4820 r#"abc
4821 defˇghi
4822
4823 jk
4824 nlmo
4825 "#
4826 ));
4827
4828 cx.update_editor(|editor, cx| {
4829 editor.add_selection_above(&Default::default(), cx);
4830 });
4831
4832 cx.assert_editor_state(indoc!(
4833 r#"abcˇ
4834 defˇghi
4835
4836 jk
4837 nlmo
4838 "#
4839 ));
4840
4841 cx.update_editor(|editor, cx| {
4842 editor.add_selection_above(&Default::default(), cx);
4843 });
4844
4845 cx.assert_editor_state(indoc!(
4846 r#"abcˇ
4847 defˇghi
4848
4849 jk
4850 nlmo
4851 "#
4852 ));
4853
4854 cx.update_editor(|view, cx| {
4855 view.add_selection_below(&Default::default(), cx);
4856 });
4857
4858 cx.assert_editor_state(indoc!(
4859 r#"abc
4860 defˇghi
4861
4862 jk
4863 nlmo
4864 "#
4865 ));
4866
4867 cx.update_editor(|view, cx| {
4868 view.undo_selection(&Default::default(), cx);
4869 });
4870
4871 cx.assert_editor_state(indoc!(
4872 r#"abcˇ
4873 defˇghi
4874
4875 jk
4876 nlmo
4877 "#
4878 ));
4879
4880 cx.update_editor(|view, cx| {
4881 view.redo_selection(&Default::default(), cx);
4882 });
4883
4884 cx.assert_editor_state(indoc!(
4885 r#"abc
4886 defˇghi
4887
4888 jk
4889 nlmo
4890 "#
4891 ));
4892
4893 cx.update_editor(|view, cx| {
4894 view.add_selection_below(&Default::default(), cx);
4895 });
4896
4897 cx.assert_editor_state(indoc!(
4898 r#"abc
4899 defˇghi
4900
4901 jk
4902 nlmˇo
4903 "#
4904 ));
4905
4906 cx.update_editor(|view, cx| {
4907 view.add_selection_below(&Default::default(), cx);
4908 });
4909
4910 cx.assert_editor_state(indoc!(
4911 r#"abc
4912 defˇghi
4913
4914 jk
4915 nlmˇo
4916 "#
4917 ));
4918
4919 // change selections
4920 cx.set_state(indoc!(
4921 r#"abc
4922 def«ˇg»hi
4923
4924 jk
4925 nlmo
4926 "#
4927 ));
4928
4929 cx.update_editor(|view, cx| {
4930 view.add_selection_below(&Default::default(), cx);
4931 });
4932
4933 cx.assert_editor_state(indoc!(
4934 r#"abc
4935 def«ˇg»hi
4936
4937 jk
4938 nlm«ˇo»
4939 "#
4940 ));
4941
4942 cx.update_editor(|view, cx| {
4943 view.add_selection_below(&Default::default(), cx);
4944 });
4945
4946 cx.assert_editor_state(indoc!(
4947 r#"abc
4948 def«ˇg»hi
4949
4950 jk
4951 nlm«ˇo»
4952 "#
4953 ));
4954
4955 cx.update_editor(|view, cx| {
4956 view.add_selection_above(&Default::default(), cx);
4957 });
4958
4959 cx.assert_editor_state(indoc!(
4960 r#"abc
4961 def«ˇg»hi
4962
4963 jk
4964 nlmo
4965 "#
4966 ));
4967
4968 cx.update_editor(|view, cx| {
4969 view.add_selection_above(&Default::default(), cx);
4970 });
4971
4972 cx.assert_editor_state(indoc!(
4973 r#"abc
4974 def«ˇg»hi
4975
4976 jk
4977 nlmo
4978 "#
4979 ));
4980
4981 // Change selections again
4982 cx.set_state(indoc!(
4983 r#"a«bc
4984 defgˇ»hi
4985
4986 jk
4987 nlmo
4988 "#
4989 ));
4990
4991 cx.update_editor(|view, cx| {
4992 view.add_selection_below(&Default::default(), cx);
4993 });
4994
4995 cx.assert_editor_state(indoc!(
4996 r#"a«bcˇ»
4997 d«efgˇ»hi
4998
4999 j«kˇ»
5000 nlmo
5001 "#
5002 ));
5003
5004 cx.update_editor(|view, cx| {
5005 view.add_selection_below(&Default::default(), cx);
5006 });
5007 cx.assert_editor_state(indoc!(
5008 r#"a«bcˇ»
5009 d«efgˇ»hi
5010
5011 j«kˇ»
5012 n«lmoˇ»
5013 "#
5014 ));
5015 cx.update_editor(|view, cx| {
5016 view.add_selection_above(&Default::default(), cx);
5017 });
5018
5019 cx.assert_editor_state(indoc!(
5020 r#"a«bcˇ»
5021 d«efgˇ»hi
5022
5023 j«kˇ»
5024 nlmo
5025 "#
5026 ));
5027
5028 // Change selections again
5029 cx.set_state(indoc!(
5030 r#"abc
5031 d«ˇefghi
5032
5033 jk
5034 nlm»o
5035 "#
5036 ));
5037
5038 cx.update_editor(|view, cx| {
5039 view.add_selection_above(&Default::default(), cx);
5040 });
5041
5042 cx.assert_editor_state(indoc!(
5043 r#"a«ˇbc»
5044 d«ˇef»ghi
5045
5046 j«ˇk»
5047 n«ˇlm»o
5048 "#
5049 ));
5050
5051 cx.update_editor(|view, cx| {
5052 view.add_selection_below(&Default::default(), cx);
5053 });
5054
5055 cx.assert_editor_state(indoc!(
5056 r#"abc
5057 d«ˇef»ghi
5058
5059 j«ˇk»
5060 n«ˇlm»o
5061 "#
5062 ));
5063}
5064
5065#[gpui::test]
5066async fn test_select_next(cx: &mut gpui::TestAppContext) {
5067 init_test(cx, |_| {});
5068
5069 let mut cx = EditorTestContext::new(cx).await;
5070 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5071
5072 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5073 .unwrap();
5074 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5075
5076 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5077 .unwrap();
5078 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5079
5080 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5081 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5082
5083 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5084 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5085
5086 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5087 .unwrap();
5088 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5089
5090 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5091 .unwrap();
5092 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5093}
5094
5095#[gpui::test]
5096async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
5097 init_test(cx, |_| {});
5098
5099 let mut cx = EditorTestContext::new(cx).await;
5100 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5101
5102 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
5103 .unwrap();
5104 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5105}
5106
5107#[gpui::test]
5108async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5109 init_test(cx, |_| {});
5110
5111 let mut cx = EditorTestContext::new(cx).await;
5112 cx.set_state(
5113 r#"let foo = 2;
5114lˇet foo = 2;
5115let fooˇ = 2;
5116let foo = 2;
5117let foo = ˇ2;"#,
5118 );
5119
5120 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5121 .unwrap();
5122 cx.assert_editor_state(
5123 r#"let foo = 2;
5124«letˇ» foo = 2;
5125let «fooˇ» = 2;
5126let foo = 2;
5127let foo = «2ˇ»;"#,
5128 );
5129
5130 // noop for multiple selections with different contents
5131 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5132 .unwrap();
5133 cx.assert_editor_state(
5134 r#"let foo = 2;
5135«letˇ» foo = 2;
5136let «fooˇ» = 2;
5137let foo = 2;
5138let foo = «2ˇ»;"#,
5139 );
5140}
5141
5142#[gpui::test]
5143async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
5144 init_test(cx, |_| {});
5145
5146 let mut cx =
5147 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5148
5149 cx.assert_editor_state(indoc! {"
5150 ˇbbb
5151 ccc
5152
5153 bbb
5154 ccc
5155 "});
5156 cx.dispatch_action(SelectPrevious::default());
5157 cx.assert_editor_state(indoc! {"
5158 «bbbˇ»
5159 ccc
5160
5161 bbb
5162 ccc
5163 "});
5164 cx.dispatch_action(SelectPrevious::default());
5165 cx.assert_editor_state(indoc! {"
5166 «bbbˇ»
5167 ccc
5168
5169 «bbbˇ»
5170 ccc
5171 "});
5172}
5173
5174#[gpui::test]
5175async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
5176 init_test(cx, |_| {});
5177
5178 let mut cx = EditorTestContext::new(cx).await;
5179 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5180
5181 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5182 .unwrap();
5183 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5184
5185 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5186 .unwrap();
5187 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5188
5189 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5190 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5191
5192 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5193 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5194
5195 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5196 .unwrap();
5197 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5198
5199 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5200 .unwrap();
5201 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5202
5203 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5204 .unwrap();
5205 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5206}
5207
5208#[gpui::test]
5209async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5210 init_test(cx, |_| {});
5211
5212 let mut cx = EditorTestContext::new(cx).await;
5213 cx.set_state(
5214 r#"let foo = 2;
5215lˇet foo = 2;
5216let fooˇ = 2;
5217let foo = 2;
5218let foo = ˇ2;"#,
5219 );
5220
5221 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5222 .unwrap();
5223 cx.assert_editor_state(
5224 r#"let foo = 2;
5225«letˇ» foo = 2;
5226let «fooˇ» = 2;
5227let foo = 2;
5228let foo = «2ˇ»;"#,
5229 );
5230
5231 // noop for multiple selections with different contents
5232 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5233 .unwrap();
5234 cx.assert_editor_state(
5235 r#"let foo = 2;
5236«letˇ» foo = 2;
5237let «fooˇ» = 2;
5238let foo = 2;
5239let foo = «2ˇ»;"#,
5240 );
5241}
5242
5243#[gpui::test]
5244async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
5245 init_test(cx, |_| {});
5246
5247 let mut cx = EditorTestContext::new(cx).await;
5248 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5249
5250 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5251 .unwrap();
5252 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5253
5254 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5255 .unwrap();
5256 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5257
5258 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5259 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5260
5261 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5262 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5263
5264 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5265 .unwrap();
5266 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5267
5268 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5269 .unwrap();
5270 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5271}
5272
5273#[gpui::test]
5274async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5275 init_test(cx, |_| {});
5276
5277 let language = Arc::new(Language::new(
5278 LanguageConfig::default(),
5279 Some(tree_sitter_rust::LANGUAGE.into()),
5280 ));
5281
5282 let text = r#"
5283 use mod1::mod2::{mod3, mod4};
5284
5285 fn fn_1(param1: bool, param2: &str) {
5286 let var1 = "text";
5287 }
5288 "#
5289 .unindent();
5290
5291 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5292 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5293 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5294
5295 editor
5296 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5297 .await;
5298
5299 editor.update(cx, |view, cx| {
5300 view.change_selections(None, cx, |s| {
5301 s.select_display_ranges([
5302 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5303 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5304 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5305 ]);
5306 });
5307 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5308 });
5309 editor.update(cx, |editor, cx| {
5310 assert_text_with_selections(
5311 editor,
5312 indoc! {r#"
5313 use mod1::mod2::{mod3, «mod4ˇ»};
5314
5315 fn fn_1«ˇ(param1: bool, param2: &str)» {
5316 let var1 = "«textˇ»";
5317 }
5318 "#},
5319 cx,
5320 );
5321 });
5322
5323 editor.update(cx, |view, cx| {
5324 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5325 });
5326 editor.update(cx, |editor, cx| {
5327 assert_text_with_selections(
5328 editor,
5329 indoc! {r#"
5330 use mod1::mod2::«{mod3, mod4}ˇ»;
5331
5332 «ˇfn fn_1(param1: bool, param2: &str) {
5333 let var1 = "text";
5334 }»
5335 "#},
5336 cx,
5337 );
5338 });
5339
5340 editor.update(cx, |view, cx| {
5341 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5342 });
5343 assert_eq!(
5344 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5345 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5346 );
5347
5348 // Trying to expand the selected syntax node one more time has no effect.
5349 editor.update(cx, |view, cx| {
5350 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5351 });
5352 assert_eq!(
5353 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5354 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5355 );
5356
5357 editor.update(cx, |view, cx| {
5358 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5359 });
5360 editor.update(cx, |editor, cx| {
5361 assert_text_with_selections(
5362 editor,
5363 indoc! {r#"
5364 use mod1::mod2::«{mod3, mod4}ˇ»;
5365
5366 «ˇfn fn_1(param1: bool, param2: &str) {
5367 let var1 = "text";
5368 }»
5369 "#},
5370 cx,
5371 );
5372 });
5373
5374 editor.update(cx, |view, cx| {
5375 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5376 });
5377 editor.update(cx, |editor, cx| {
5378 assert_text_with_selections(
5379 editor,
5380 indoc! {r#"
5381 use mod1::mod2::{mod3, «mod4ˇ»};
5382
5383 fn fn_1«ˇ(param1: bool, param2: &str)» {
5384 let var1 = "«textˇ»";
5385 }
5386 "#},
5387 cx,
5388 );
5389 });
5390
5391 editor.update(cx, |view, cx| {
5392 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5393 });
5394 editor.update(cx, |editor, cx| {
5395 assert_text_with_selections(
5396 editor,
5397 indoc! {r#"
5398 use mod1::mod2::{mod3, mo«ˇ»d4};
5399
5400 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5401 let var1 = "te«ˇ»xt";
5402 }
5403 "#},
5404 cx,
5405 );
5406 });
5407
5408 // Trying to shrink the selected syntax node one more time has no effect.
5409 editor.update(cx, |view, cx| {
5410 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5411 });
5412 editor.update(cx, |editor, cx| {
5413 assert_text_with_selections(
5414 editor,
5415 indoc! {r#"
5416 use mod1::mod2::{mod3, mo«ˇ»d4};
5417
5418 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5419 let var1 = "te«ˇ»xt";
5420 }
5421 "#},
5422 cx,
5423 );
5424 });
5425
5426 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5427 // a fold.
5428 editor.update(cx, |view, cx| {
5429 view.fold_creases(
5430 vec![
5431 Crease::simple(
5432 Point::new(0, 21)..Point::new(0, 24),
5433 FoldPlaceholder::test(),
5434 ),
5435 Crease::simple(
5436 Point::new(3, 20)..Point::new(3, 22),
5437 FoldPlaceholder::test(),
5438 ),
5439 ],
5440 true,
5441 cx,
5442 );
5443 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5444 });
5445 editor.update(cx, |editor, cx| {
5446 assert_text_with_selections(
5447 editor,
5448 indoc! {r#"
5449 use mod1::mod2::«{mod3, mod4}ˇ»;
5450
5451 fn fn_1«ˇ(param1: bool, param2: &str)» {
5452 «let var1 = "text";ˇ»
5453 }
5454 "#},
5455 cx,
5456 );
5457 });
5458}
5459
5460#[gpui::test]
5461async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5462 init_test(cx, |_| {});
5463
5464 let language = Arc::new(
5465 Language::new(
5466 LanguageConfig {
5467 brackets: BracketPairConfig {
5468 pairs: vec![
5469 BracketPair {
5470 start: "{".to_string(),
5471 end: "}".to_string(),
5472 close: false,
5473 surround: false,
5474 newline: true,
5475 },
5476 BracketPair {
5477 start: "(".to_string(),
5478 end: ")".to_string(),
5479 close: false,
5480 surround: false,
5481 newline: true,
5482 },
5483 ],
5484 ..Default::default()
5485 },
5486 ..Default::default()
5487 },
5488 Some(tree_sitter_rust::LANGUAGE.into()),
5489 )
5490 .with_indents_query(
5491 r#"
5492 (_ "(" ")" @end) @indent
5493 (_ "{" "}" @end) @indent
5494 "#,
5495 )
5496 .unwrap(),
5497 );
5498
5499 let text = "fn a() {}";
5500
5501 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5502 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5503 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5504 editor
5505 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5506 .await;
5507
5508 editor.update(cx, |editor, cx| {
5509 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5510 editor.newline(&Newline, cx);
5511 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5512 assert_eq!(
5513 editor.selections.ranges(cx),
5514 &[
5515 Point::new(1, 4)..Point::new(1, 4),
5516 Point::new(3, 4)..Point::new(3, 4),
5517 Point::new(5, 0)..Point::new(5, 0)
5518 ]
5519 );
5520 });
5521}
5522
5523#[gpui::test]
5524async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5525 init_test(cx, |_| {});
5526
5527 let mut cx = EditorTestContext::new(cx).await;
5528
5529 let language = Arc::new(Language::new(
5530 LanguageConfig {
5531 brackets: BracketPairConfig {
5532 pairs: vec![
5533 BracketPair {
5534 start: "{".to_string(),
5535 end: "}".to_string(),
5536 close: true,
5537 surround: true,
5538 newline: true,
5539 },
5540 BracketPair {
5541 start: "(".to_string(),
5542 end: ")".to_string(),
5543 close: true,
5544 surround: true,
5545 newline: true,
5546 },
5547 BracketPair {
5548 start: "/*".to_string(),
5549 end: " */".to_string(),
5550 close: true,
5551 surround: true,
5552 newline: true,
5553 },
5554 BracketPair {
5555 start: "[".to_string(),
5556 end: "]".to_string(),
5557 close: false,
5558 surround: false,
5559 newline: true,
5560 },
5561 BracketPair {
5562 start: "\"".to_string(),
5563 end: "\"".to_string(),
5564 close: true,
5565 surround: true,
5566 newline: false,
5567 },
5568 BracketPair {
5569 start: "<".to_string(),
5570 end: ">".to_string(),
5571 close: false,
5572 surround: true,
5573 newline: true,
5574 },
5575 ],
5576 ..Default::default()
5577 },
5578 autoclose_before: "})]".to_string(),
5579 ..Default::default()
5580 },
5581 Some(tree_sitter_rust::LANGUAGE.into()),
5582 ));
5583
5584 cx.language_registry().add(language.clone());
5585 cx.update_buffer(|buffer, cx| {
5586 buffer.set_language(Some(language), cx);
5587 });
5588
5589 cx.set_state(
5590 &r#"
5591 🏀ˇ
5592 εˇ
5593 ❤️ˇ
5594 "#
5595 .unindent(),
5596 );
5597
5598 // autoclose multiple nested brackets at multiple cursors
5599 cx.update_editor(|view, cx| {
5600 view.handle_input("{", cx);
5601 view.handle_input("{", cx);
5602 view.handle_input("{", cx);
5603 });
5604 cx.assert_editor_state(
5605 &"
5606 🏀{{{ˇ}}}
5607 ε{{{ˇ}}}
5608 ❤️{{{ˇ}}}
5609 "
5610 .unindent(),
5611 );
5612
5613 // insert a different closing bracket
5614 cx.update_editor(|view, cx| {
5615 view.handle_input(")", cx);
5616 });
5617 cx.assert_editor_state(
5618 &"
5619 🏀{{{)ˇ}}}
5620 ε{{{)ˇ}}}
5621 ❤️{{{)ˇ}}}
5622 "
5623 .unindent(),
5624 );
5625
5626 // skip over the auto-closed brackets when typing a closing bracket
5627 cx.update_editor(|view, cx| {
5628 view.move_right(&MoveRight, cx);
5629 view.handle_input("}", cx);
5630 view.handle_input("}", cx);
5631 view.handle_input("}", cx);
5632 });
5633 cx.assert_editor_state(
5634 &"
5635 🏀{{{)}}}}ˇ
5636 ε{{{)}}}}ˇ
5637 ❤️{{{)}}}}ˇ
5638 "
5639 .unindent(),
5640 );
5641
5642 // autoclose multi-character pairs
5643 cx.set_state(
5644 &"
5645 ˇ
5646 ˇ
5647 "
5648 .unindent(),
5649 );
5650 cx.update_editor(|view, cx| {
5651 view.handle_input("/", cx);
5652 view.handle_input("*", cx);
5653 });
5654 cx.assert_editor_state(
5655 &"
5656 /*ˇ */
5657 /*ˇ */
5658 "
5659 .unindent(),
5660 );
5661
5662 // one cursor autocloses a multi-character pair, one cursor
5663 // does not autoclose.
5664 cx.set_state(
5665 &"
5666 /ˇ
5667 ˇ
5668 "
5669 .unindent(),
5670 );
5671 cx.update_editor(|view, cx| view.handle_input("*", cx));
5672 cx.assert_editor_state(
5673 &"
5674 /*ˇ */
5675 *ˇ
5676 "
5677 .unindent(),
5678 );
5679
5680 // Don't autoclose if the next character isn't whitespace and isn't
5681 // listed in the language's "autoclose_before" section.
5682 cx.set_state("ˇa b");
5683 cx.update_editor(|view, cx| view.handle_input("{", cx));
5684 cx.assert_editor_state("{ˇa b");
5685
5686 // Don't autoclose if `close` is false for the bracket pair
5687 cx.set_state("ˇ");
5688 cx.update_editor(|view, cx| view.handle_input("[", cx));
5689 cx.assert_editor_state("[ˇ");
5690
5691 // Surround with brackets if text is selected
5692 cx.set_state("«aˇ» b");
5693 cx.update_editor(|view, cx| view.handle_input("{", cx));
5694 cx.assert_editor_state("{«aˇ»} b");
5695
5696 // Autclose pair where the start and end characters are the same
5697 cx.set_state("aˇ");
5698 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5699 cx.assert_editor_state("a\"ˇ\"");
5700 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5701 cx.assert_editor_state("a\"\"ˇ");
5702
5703 // Don't autoclose pair if autoclose is disabled
5704 cx.set_state("ˇ");
5705 cx.update_editor(|view, cx| view.handle_input("<", cx));
5706 cx.assert_editor_state("<ˇ");
5707
5708 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5709 cx.set_state("«aˇ» b");
5710 cx.update_editor(|view, cx| view.handle_input("<", cx));
5711 cx.assert_editor_state("<«aˇ»> b");
5712}
5713
5714#[gpui::test]
5715async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5716 init_test(cx, |settings| {
5717 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5718 });
5719
5720 let mut cx = EditorTestContext::new(cx).await;
5721
5722 let language = Arc::new(Language::new(
5723 LanguageConfig {
5724 brackets: BracketPairConfig {
5725 pairs: vec![
5726 BracketPair {
5727 start: "{".to_string(),
5728 end: "}".to_string(),
5729 close: true,
5730 surround: true,
5731 newline: true,
5732 },
5733 BracketPair {
5734 start: "(".to_string(),
5735 end: ")".to_string(),
5736 close: true,
5737 surround: true,
5738 newline: true,
5739 },
5740 BracketPair {
5741 start: "[".to_string(),
5742 end: "]".to_string(),
5743 close: false,
5744 surround: false,
5745 newline: true,
5746 },
5747 ],
5748 ..Default::default()
5749 },
5750 autoclose_before: "})]".to_string(),
5751 ..Default::default()
5752 },
5753 Some(tree_sitter_rust::LANGUAGE.into()),
5754 ));
5755
5756 cx.language_registry().add(language.clone());
5757 cx.update_buffer(|buffer, cx| {
5758 buffer.set_language(Some(language), cx);
5759 });
5760
5761 cx.set_state(
5762 &"
5763 ˇ
5764 ˇ
5765 ˇ
5766 "
5767 .unindent(),
5768 );
5769
5770 // ensure only matching closing brackets are skipped over
5771 cx.update_editor(|view, cx| {
5772 view.handle_input("}", cx);
5773 view.move_left(&MoveLeft, cx);
5774 view.handle_input(")", cx);
5775 view.move_left(&MoveLeft, cx);
5776 });
5777 cx.assert_editor_state(
5778 &"
5779 ˇ)}
5780 ˇ)}
5781 ˇ)}
5782 "
5783 .unindent(),
5784 );
5785
5786 // skip-over closing brackets at multiple cursors
5787 cx.update_editor(|view, cx| {
5788 view.handle_input(")", cx);
5789 view.handle_input("}", cx);
5790 });
5791 cx.assert_editor_state(
5792 &"
5793 )}ˇ
5794 )}ˇ
5795 )}ˇ
5796 "
5797 .unindent(),
5798 );
5799
5800 // ignore non-close brackets
5801 cx.update_editor(|view, cx| {
5802 view.handle_input("]", cx);
5803 view.move_left(&MoveLeft, cx);
5804 view.handle_input("]", cx);
5805 });
5806 cx.assert_editor_state(
5807 &"
5808 )}]ˇ]
5809 )}]ˇ]
5810 )}]ˇ]
5811 "
5812 .unindent(),
5813 );
5814}
5815
5816#[gpui::test]
5817async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5818 init_test(cx, |_| {});
5819
5820 let mut cx = EditorTestContext::new(cx).await;
5821
5822 let html_language = Arc::new(
5823 Language::new(
5824 LanguageConfig {
5825 name: "HTML".into(),
5826 brackets: BracketPairConfig {
5827 pairs: vec![
5828 BracketPair {
5829 start: "<".into(),
5830 end: ">".into(),
5831 close: true,
5832 ..Default::default()
5833 },
5834 BracketPair {
5835 start: "{".into(),
5836 end: "}".into(),
5837 close: true,
5838 ..Default::default()
5839 },
5840 BracketPair {
5841 start: "(".into(),
5842 end: ")".into(),
5843 close: true,
5844 ..Default::default()
5845 },
5846 ],
5847 ..Default::default()
5848 },
5849 autoclose_before: "})]>".into(),
5850 ..Default::default()
5851 },
5852 Some(tree_sitter_html::language()),
5853 )
5854 .with_injection_query(
5855 r#"
5856 (script_element
5857 (raw_text) @content
5858 (#set! "language" "javascript"))
5859 "#,
5860 )
5861 .unwrap(),
5862 );
5863
5864 let javascript_language = Arc::new(Language::new(
5865 LanguageConfig {
5866 name: "JavaScript".into(),
5867 brackets: BracketPairConfig {
5868 pairs: vec![
5869 BracketPair {
5870 start: "/*".into(),
5871 end: " */".into(),
5872 close: true,
5873 ..Default::default()
5874 },
5875 BracketPair {
5876 start: "{".into(),
5877 end: "}".into(),
5878 close: true,
5879 ..Default::default()
5880 },
5881 BracketPair {
5882 start: "(".into(),
5883 end: ")".into(),
5884 close: true,
5885 ..Default::default()
5886 },
5887 ],
5888 ..Default::default()
5889 },
5890 autoclose_before: "})]>".into(),
5891 ..Default::default()
5892 },
5893 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
5894 ));
5895
5896 cx.language_registry().add(html_language.clone());
5897 cx.language_registry().add(javascript_language.clone());
5898
5899 cx.update_buffer(|buffer, cx| {
5900 buffer.set_language(Some(html_language), cx);
5901 });
5902
5903 cx.set_state(
5904 &r#"
5905 <body>ˇ
5906 <script>
5907 var x = 1;ˇ
5908 </script>
5909 </body>ˇ
5910 "#
5911 .unindent(),
5912 );
5913
5914 // Precondition: different languages are active at different locations.
5915 cx.update_editor(|editor, cx| {
5916 let snapshot = editor.snapshot(cx);
5917 let cursors = editor.selections.ranges::<usize>(cx);
5918 let languages = cursors
5919 .iter()
5920 .map(|c| snapshot.language_at(c.start).unwrap().name())
5921 .collect::<Vec<_>>();
5922 assert_eq!(
5923 languages,
5924 &["HTML".into(), "JavaScript".into(), "HTML".into()]
5925 );
5926 });
5927
5928 // Angle brackets autoclose in HTML, but not JavaScript.
5929 cx.update_editor(|editor, cx| {
5930 editor.handle_input("<", cx);
5931 editor.handle_input("a", cx);
5932 });
5933 cx.assert_editor_state(
5934 &r#"
5935 <body><aˇ>
5936 <script>
5937 var x = 1;<aˇ
5938 </script>
5939 </body><aˇ>
5940 "#
5941 .unindent(),
5942 );
5943
5944 // Curly braces and parens autoclose in both HTML and JavaScript.
5945 cx.update_editor(|editor, cx| {
5946 editor.handle_input(" b=", cx);
5947 editor.handle_input("{", cx);
5948 editor.handle_input("c", cx);
5949 editor.handle_input("(", cx);
5950 });
5951 cx.assert_editor_state(
5952 &r#"
5953 <body><a b={c(ˇ)}>
5954 <script>
5955 var x = 1;<a b={c(ˇ)}
5956 </script>
5957 </body><a b={c(ˇ)}>
5958 "#
5959 .unindent(),
5960 );
5961
5962 // Brackets that were already autoclosed are skipped.
5963 cx.update_editor(|editor, cx| {
5964 editor.handle_input(")", cx);
5965 editor.handle_input("d", cx);
5966 editor.handle_input("}", cx);
5967 });
5968 cx.assert_editor_state(
5969 &r#"
5970 <body><a b={c()d}ˇ>
5971 <script>
5972 var x = 1;<a b={c()d}ˇ
5973 </script>
5974 </body><a b={c()d}ˇ>
5975 "#
5976 .unindent(),
5977 );
5978 cx.update_editor(|editor, cx| {
5979 editor.handle_input(">", cx);
5980 });
5981 cx.assert_editor_state(
5982 &r#"
5983 <body><a b={c()d}>ˇ
5984 <script>
5985 var x = 1;<a b={c()d}>ˇ
5986 </script>
5987 </body><a b={c()d}>ˇ
5988 "#
5989 .unindent(),
5990 );
5991
5992 // Reset
5993 cx.set_state(
5994 &r#"
5995 <body>ˇ
5996 <script>
5997 var x = 1;ˇ
5998 </script>
5999 </body>ˇ
6000 "#
6001 .unindent(),
6002 );
6003
6004 cx.update_editor(|editor, cx| {
6005 editor.handle_input("<", cx);
6006 });
6007 cx.assert_editor_state(
6008 &r#"
6009 <body><ˇ>
6010 <script>
6011 var x = 1;<ˇ
6012 </script>
6013 </body><ˇ>
6014 "#
6015 .unindent(),
6016 );
6017
6018 // When backspacing, the closing angle brackets are removed.
6019 cx.update_editor(|editor, cx| {
6020 editor.backspace(&Backspace, cx);
6021 });
6022 cx.assert_editor_state(
6023 &r#"
6024 <body>ˇ
6025 <script>
6026 var x = 1;ˇ
6027 </script>
6028 </body>ˇ
6029 "#
6030 .unindent(),
6031 );
6032
6033 // Block comments autoclose in JavaScript, but not HTML.
6034 cx.update_editor(|editor, cx| {
6035 editor.handle_input("/", cx);
6036 editor.handle_input("*", cx);
6037 });
6038 cx.assert_editor_state(
6039 &r#"
6040 <body>/*ˇ
6041 <script>
6042 var x = 1;/*ˇ */
6043 </script>
6044 </body>/*ˇ
6045 "#
6046 .unindent(),
6047 );
6048}
6049
6050#[gpui::test]
6051async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
6052 init_test(cx, |_| {});
6053
6054 let mut cx = EditorTestContext::new(cx).await;
6055
6056 let rust_language = Arc::new(
6057 Language::new(
6058 LanguageConfig {
6059 name: "Rust".into(),
6060 brackets: serde_json::from_value(json!([
6061 { "start": "{", "end": "}", "close": true, "newline": true },
6062 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6063 ]))
6064 .unwrap(),
6065 autoclose_before: "})]>".into(),
6066 ..Default::default()
6067 },
6068 Some(tree_sitter_rust::LANGUAGE.into()),
6069 )
6070 .with_override_query("(string_literal) @string")
6071 .unwrap(),
6072 );
6073
6074 cx.language_registry().add(rust_language.clone());
6075 cx.update_buffer(|buffer, cx| {
6076 buffer.set_language(Some(rust_language), cx);
6077 });
6078
6079 cx.set_state(
6080 &r#"
6081 let x = ˇ
6082 "#
6083 .unindent(),
6084 );
6085
6086 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6087 cx.update_editor(|editor, cx| {
6088 editor.handle_input("\"", cx);
6089 });
6090 cx.assert_editor_state(
6091 &r#"
6092 let x = "ˇ"
6093 "#
6094 .unindent(),
6095 );
6096
6097 // Inserting another quotation mark. The cursor moves across the existing
6098 // automatically-inserted quotation mark.
6099 cx.update_editor(|editor, cx| {
6100 editor.handle_input("\"", cx);
6101 });
6102 cx.assert_editor_state(
6103 &r#"
6104 let x = ""ˇ
6105 "#
6106 .unindent(),
6107 );
6108
6109 // Reset
6110 cx.set_state(
6111 &r#"
6112 let x = ˇ
6113 "#
6114 .unindent(),
6115 );
6116
6117 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6118 cx.update_editor(|editor, cx| {
6119 editor.handle_input("\"", cx);
6120 editor.handle_input(" ", cx);
6121 editor.move_left(&Default::default(), cx);
6122 editor.handle_input("\\", cx);
6123 editor.handle_input("\"", cx);
6124 });
6125 cx.assert_editor_state(
6126 &r#"
6127 let x = "\"ˇ "
6128 "#
6129 .unindent(),
6130 );
6131
6132 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6133 // mark. Nothing is inserted.
6134 cx.update_editor(|editor, cx| {
6135 editor.move_right(&Default::default(), cx);
6136 editor.handle_input("\"", cx);
6137 });
6138 cx.assert_editor_state(
6139 &r#"
6140 let x = "\" "ˇ
6141 "#
6142 .unindent(),
6143 );
6144}
6145
6146#[gpui::test]
6147async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
6148 init_test(cx, |_| {});
6149
6150 let language = Arc::new(Language::new(
6151 LanguageConfig {
6152 brackets: BracketPairConfig {
6153 pairs: vec![
6154 BracketPair {
6155 start: "{".to_string(),
6156 end: "}".to_string(),
6157 close: true,
6158 surround: true,
6159 newline: true,
6160 },
6161 BracketPair {
6162 start: "/* ".to_string(),
6163 end: "*/".to_string(),
6164 close: true,
6165 surround: true,
6166 ..Default::default()
6167 },
6168 ],
6169 ..Default::default()
6170 },
6171 ..Default::default()
6172 },
6173 Some(tree_sitter_rust::LANGUAGE.into()),
6174 ));
6175
6176 let text = r#"
6177 a
6178 b
6179 c
6180 "#
6181 .unindent();
6182
6183 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6184 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6185 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6186 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6187 .await;
6188
6189 view.update(cx, |view, cx| {
6190 view.change_selections(None, cx, |s| {
6191 s.select_display_ranges([
6192 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6193 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6194 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6195 ])
6196 });
6197
6198 view.handle_input("{", cx);
6199 view.handle_input("{", cx);
6200 view.handle_input("{", cx);
6201 assert_eq!(
6202 view.text(cx),
6203 "
6204 {{{a}}}
6205 {{{b}}}
6206 {{{c}}}
6207 "
6208 .unindent()
6209 );
6210 assert_eq!(
6211 view.selections.display_ranges(cx),
6212 [
6213 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6214 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6215 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6216 ]
6217 );
6218
6219 view.undo(&Undo, cx);
6220 view.undo(&Undo, cx);
6221 view.undo(&Undo, cx);
6222 assert_eq!(
6223 view.text(cx),
6224 "
6225 a
6226 b
6227 c
6228 "
6229 .unindent()
6230 );
6231 assert_eq!(
6232 view.selections.display_ranges(cx),
6233 [
6234 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6235 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6236 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6237 ]
6238 );
6239
6240 // Ensure inserting the first character of a multi-byte bracket pair
6241 // doesn't surround the selections with the bracket.
6242 view.handle_input("/", cx);
6243 assert_eq!(
6244 view.text(cx),
6245 "
6246 /
6247 /
6248 /
6249 "
6250 .unindent()
6251 );
6252 assert_eq!(
6253 view.selections.display_ranges(cx),
6254 [
6255 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6256 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6257 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6258 ]
6259 );
6260
6261 view.undo(&Undo, cx);
6262 assert_eq!(
6263 view.text(cx),
6264 "
6265 a
6266 b
6267 c
6268 "
6269 .unindent()
6270 );
6271 assert_eq!(
6272 view.selections.display_ranges(cx),
6273 [
6274 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6275 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6276 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6277 ]
6278 );
6279
6280 // Ensure inserting the last character of a multi-byte bracket pair
6281 // doesn't surround the selections with the bracket.
6282 view.handle_input("*", cx);
6283 assert_eq!(
6284 view.text(cx),
6285 "
6286 *
6287 *
6288 *
6289 "
6290 .unindent()
6291 );
6292 assert_eq!(
6293 view.selections.display_ranges(cx),
6294 [
6295 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6296 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6297 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6298 ]
6299 );
6300 });
6301}
6302
6303#[gpui::test]
6304async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6305 init_test(cx, |_| {});
6306
6307 let language = Arc::new(Language::new(
6308 LanguageConfig {
6309 brackets: BracketPairConfig {
6310 pairs: vec![BracketPair {
6311 start: "{".to_string(),
6312 end: "}".to_string(),
6313 close: true,
6314 surround: true,
6315 newline: true,
6316 }],
6317 ..Default::default()
6318 },
6319 autoclose_before: "}".to_string(),
6320 ..Default::default()
6321 },
6322 Some(tree_sitter_rust::LANGUAGE.into()),
6323 ));
6324
6325 let text = r#"
6326 a
6327 b
6328 c
6329 "#
6330 .unindent();
6331
6332 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6333 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6334 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6335 editor
6336 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6337 .await;
6338
6339 editor.update(cx, |editor, cx| {
6340 editor.change_selections(None, cx, |s| {
6341 s.select_ranges([
6342 Point::new(0, 1)..Point::new(0, 1),
6343 Point::new(1, 1)..Point::new(1, 1),
6344 Point::new(2, 1)..Point::new(2, 1),
6345 ])
6346 });
6347
6348 editor.handle_input("{", cx);
6349 editor.handle_input("{", cx);
6350 editor.handle_input("_", cx);
6351 assert_eq!(
6352 editor.text(cx),
6353 "
6354 a{{_}}
6355 b{{_}}
6356 c{{_}}
6357 "
6358 .unindent()
6359 );
6360 assert_eq!(
6361 editor.selections.ranges::<Point>(cx),
6362 [
6363 Point::new(0, 4)..Point::new(0, 4),
6364 Point::new(1, 4)..Point::new(1, 4),
6365 Point::new(2, 4)..Point::new(2, 4)
6366 ]
6367 );
6368
6369 editor.backspace(&Default::default(), cx);
6370 editor.backspace(&Default::default(), cx);
6371 assert_eq!(
6372 editor.text(cx),
6373 "
6374 a{}
6375 b{}
6376 c{}
6377 "
6378 .unindent()
6379 );
6380 assert_eq!(
6381 editor.selections.ranges::<Point>(cx),
6382 [
6383 Point::new(0, 2)..Point::new(0, 2),
6384 Point::new(1, 2)..Point::new(1, 2),
6385 Point::new(2, 2)..Point::new(2, 2)
6386 ]
6387 );
6388
6389 editor.delete_to_previous_word_start(&Default::default(), cx);
6390 assert_eq!(
6391 editor.text(cx),
6392 "
6393 a
6394 b
6395 c
6396 "
6397 .unindent()
6398 );
6399 assert_eq!(
6400 editor.selections.ranges::<Point>(cx),
6401 [
6402 Point::new(0, 1)..Point::new(0, 1),
6403 Point::new(1, 1)..Point::new(1, 1),
6404 Point::new(2, 1)..Point::new(2, 1)
6405 ]
6406 );
6407 });
6408}
6409
6410#[gpui::test]
6411async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6412 init_test(cx, |settings| {
6413 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6414 });
6415
6416 let mut cx = EditorTestContext::new(cx).await;
6417
6418 let language = Arc::new(Language::new(
6419 LanguageConfig {
6420 brackets: BracketPairConfig {
6421 pairs: vec![
6422 BracketPair {
6423 start: "{".to_string(),
6424 end: "}".to_string(),
6425 close: true,
6426 surround: true,
6427 newline: true,
6428 },
6429 BracketPair {
6430 start: "(".to_string(),
6431 end: ")".to_string(),
6432 close: true,
6433 surround: true,
6434 newline: true,
6435 },
6436 BracketPair {
6437 start: "[".to_string(),
6438 end: "]".to_string(),
6439 close: false,
6440 surround: true,
6441 newline: true,
6442 },
6443 ],
6444 ..Default::default()
6445 },
6446 autoclose_before: "})]".to_string(),
6447 ..Default::default()
6448 },
6449 Some(tree_sitter_rust::LANGUAGE.into()),
6450 ));
6451
6452 cx.language_registry().add(language.clone());
6453 cx.update_buffer(|buffer, cx| {
6454 buffer.set_language(Some(language), cx);
6455 });
6456
6457 cx.set_state(
6458 &"
6459 {(ˇ)}
6460 [[ˇ]]
6461 {(ˇ)}
6462 "
6463 .unindent(),
6464 );
6465
6466 cx.update_editor(|view, cx| {
6467 view.backspace(&Default::default(), cx);
6468 view.backspace(&Default::default(), cx);
6469 });
6470
6471 cx.assert_editor_state(
6472 &"
6473 ˇ
6474 ˇ]]
6475 ˇ
6476 "
6477 .unindent(),
6478 );
6479
6480 cx.update_editor(|view, cx| {
6481 view.handle_input("{", cx);
6482 view.handle_input("{", cx);
6483 view.move_right(&MoveRight, cx);
6484 view.move_right(&MoveRight, cx);
6485 view.move_left(&MoveLeft, cx);
6486 view.move_left(&MoveLeft, cx);
6487 view.backspace(&Default::default(), cx);
6488 });
6489
6490 cx.assert_editor_state(
6491 &"
6492 {ˇ}
6493 {ˇ}]]
6494 {ˇ}
6495 "
6496 .unindent(),
6497 );
6498
6499 cx.update_editor(|view, cx| {
6500 view.backspace(&Default::default(), cx);
6501 });
6502
6503 cx.assert_editor_state(
6504 &"
6505 ˇ
6506 ˇ]]
6507 ˇ
6508 "
6509 .unindent(),
6510 );
6511}
6512
6513#[gpui::test]
6514async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6515 init_test(cx, |_| {});
6516
6517 let language = Arc::new(Language::new(
6518 LanguageConfig::default(),
6519 Some(tree_sitter_rust::LANGUAGE.into()),
6520 ));
6521
6522 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6523 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6524 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6525 editor
6526 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6527 .await;
6528
6529 editor.update(cx, |editor, cx| {
6530 editor.set_auto_replace_emoji_shortcode(true);
6531
6532 editor.handle_input("Hello ", cx);
6533 editor.handle_input(":wave", cx);
6534 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6535
6536 editor.handle_input(":", cx);
6537 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6538
6539 editor.handle_input(" :smile", cx);
6540 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6541
6542 editor.handle_input(":", cx);
6543 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6544
6545 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6546 editor.handle_input(":wave", cx);
6547 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6548
6549 editor.handle_input(":", cx);
6550 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6551
6552 editor.handle_input(":1", cx);
6553 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6554
6555 editor.handle_input(":", cx);
6556 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6557
6558 // Ensure shortcode does not get replaced when it is part of a word
6559 editor.handle_input(" Test:wave", cx);
6560 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6561
6562 editor.handle_input(":", cx);
6563 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6564
6565 editor.set_auto_replace_emoji_shortcode(false);
6566
6567 // Ensure shortcode does not get replaced when auto replace is off
6568 editor.handle_input(" :wave", cx);
6569 assert_eq!(
6570 editor.text(cx),
6571 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6572 );
6573
6574 editor.handle_input(":", cx);
6575 assert_eq!(
6576 editor.text(cx),
6577 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6578 );
6579 });
6580}
6581
6582#[gpui::test]
6583async fn test_snippet_placeholder_choices(cx: &mut gpui::TestAppContext) {
6584 init_test(cx, |_| {});
6585
6586 let (text, insertion_ranges) = marked_text_ranges(
6587 indoc! {"
6588 ˇ
6589 "},
6590 false,
6591 );
6592
6593 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6594 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6595
6596 _ = editor.update(cx, |editor, cx| {
6597 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
6598
6599 editor
6600 .insert_snippet(&insertion_ranges, snippet, cx)
6601 .unwrap();
6602
6603 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6604 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6605 assert_eq!(editor.text(cx), expected_text);
6606 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6607 }
6608
6609 assert(
6610 editor,
6611 cx,
6612 indoc! {"
6613 type «» =•
6614 "},
6615 );
6616
6617 assert!(editor.context_menu_visible(), "There should be a matches");
6618 });
6619}
6620
6621#[gpui::test]
6622async fn test_snippets(cx: &mut gpui::TestAppContext) {
6623 init_test(cx, |_| {});
6624
6625 let (text, insertion_ranges) = marked_text_ranges(
6626 indoc! {"
6627 a.ˇ b
6628 a.ˇ b
6629 a.ˇ b
6630 "},
6631 false,
6632 );
6633
6634 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6635 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6636
6637 editor.update(cx, |editor, cx| {
6638 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6639
6640 editor
6641 .insert_snippet(&insertion_ranges, snippet, cx)
6642 .unwrap();
6643
6644 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6645 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6646 assert_eq!(editor.text(cx), expected_text);
6647 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6648 }
6649
6650 assert(
6651 editor,
6652 cx,
6653 indoc! {"
6654 a.f(«one», two, «three») b
6655 a.f(«one», two, «three») b
6656 a.f(«one», two, «three») b
6657 "},
6658 );
6659
6660 // Can't move earlier than the first tab stop
6661 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6662 assert(
6663 editor,
6664 cx,
6665 indoc! {"
6666 a.f(«one», two, «three») b
6667 a.f(«one», two, «three») b
6668 a.f(«one», two, «three») b
6669 "},
6670 );
6671
6672 assert!(editor.move_to_next_snippet_tabstop(cx));
6673 assert(
6674 editor,
6675 cx,
6676 indoc! {"
6677 a.f(one, «two», three) b
6678 a.f(one, «two», three) b
6679 a.f(one, «two», three) b
6680 "},
6681 );
6682
6683 editor.move_to_prev_snippet_tabstop(cx);
6684 assert(
6685 editor,
6686 cx,
6687 indoc! {"
6688 a.f(«one», two, «three») b
6689 a.f(«one», two, «three») b
6690 a.f(«one», two, «three») b
6691 "},
6692 );
6693
6694 assert!(editor.move_to_next_snippet_tabstop(cx));
6695 assert(
6696 editor,
6697 cx,
6698 indoc! {"
6699 a.f(one, «two», three) b
6700 a.f(one, «two», three) b
6701 a.f(one, «two», three) b
6702 "},
6703 );
6704 assert!(editor.move_to_next_snippet_tabstop(cx));
6705 assert(
6706 editor,
6707 cx,
6708 indoc! {"
6709 a.f(one, two, three)ˇ b
6710 a.f(one, two, three)ˇ b
6711 a.f(one, two, three)ˇ b
6712 "},
6713 );
6714
6715 // As soon as the last tab stop is reached, snippet state is gone
6716 editor.move_to_prev_snippet_tabstop(cx);
6717 assert(
6718 editor,
6719 cx,
6720 indoc! {"
6721 a.f(one, two, three)ˇ b
6722 a.f(one, two, three)ˇ b
6723 a.f(one, two, three)ˇ b
6724 "},
6725 );
6726 });
6727}
6728
6729#[gpui::test]
6730async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6731 init_test(cx, |_| {});
6732
6733 let fs = FakeFs::new(cx.executor());
6734 fs.insert_file("/file.rs", Default::default()).await;
6735
6736 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6737
6738 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6739 language_registry.add(rust_lang());
6740 let mut fake_servers = language_registry.register_fake_lsp(
6741 "Rust",
6742 FakeLspAdapter {
6743 capabilities: lsp::ServerCapabilities {
6744 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6745 ..Default::default()
6746 },
6747 ..Default::default()
6748 },
6749 );
6750
6751 let buffer = project
6752 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6753 .await
6754 .unwrap();
6755
6756 cx.executor().start_waiting();
6757 let fake_server = fake_servers.next().await.unwrap();
6758
6759 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6760 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6761 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6762 assert!(cx.read(|cx| editor.is_dirty(cx)));
6763
6764 let save = editor
6765 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6766 .unwrap();
6767 fake_server
6768 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6769 assert_eq!(
6770 params.text_document.uri,
6771 lsp::Url::from_file_path("/file.rs").unwrap()
6772 );
6773 assert_eq!(params.options.tab_size, 4);
6774 Ok(Some(vec![lsp::TextEdit::new(
6775 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6776 ", ".to_string(),
6777 )]))
6778 })
6779 .next()
6780 .await;
6781 cx.executor().start_waiting();
6782 save.await;
6783
6784 assert_eq!(
6785 editor.update(cx, |editor, cx| editor.text(cx)),
6786 "one, two\nthree\n"
6787 );
6788 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6789
6790 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6791 assert!(cx.read(|cx| editor.is_dirty(cx)));
6792
6793 // Ensure we can still save even if formatting hangs.
6794 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6795 assert_eq!(
6796 params.text_document.uri,
6797 lsp::Url::from_file_path("/file.rs").unwrap()
6798 );
6799 futures::future::pending::<()>().await;
6800 unreachable!()
6801 });
6802 let save = editor
6803 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6804 .unwrap();
6805 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6806 cx.executor().start_waiting();
6807 save.await;
6808 assert_eq!(
6809 editor.update(cx, |editor, cx| editor.text(cx)),
6810 "one\ntwo\nthree\n"
6811 );
6812 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6813
6814 // For non-dirty buffer, no formatting request should be sent
6815 let save = editor
6816 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6817 .unwrap();
6818 let _pending_format_request = fake_server
6819 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6820 panic!("Should not be invoked on non-dirty buffer");
6821 })
6822 .next();
6823 cx.executor().start_waiting();
6824 save.await;
6825
6826 // Set rust language override and assert overridden tabsize is sent to language server
6827 update_test_language_settings(cx, |settings| {
6828 settings.languages.insert(
6829 "Rust".into(),
6830 LanguageSettingsContent {
6831 tab_size: NonZeroU32::new(8),
6832 ..Default::default()
6833 },
6834 );
6835 });
6836
6837 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6838 assert!(cx.read(|cx| editor.is_dirty(cx)));
6839 let save = editor
6840 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6841 .unwrap();
6842 fake_server
6843 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6844 assert_eq!(
6845 params.text_document.uri,
6846 lsp::Url::from_file_path("/file.rs").unwrap()
6847 );
6848 assert_eq!(params.options.tab_size, 8);
6849 Ok(Some(vec![]))
6850 })
6851 .next()
6852 .await;
6853 cx.executor().start_waiting();
6854 save.await;
6855}
6856
6857#[gpui::test]
6858async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6859 init_test(cx, |_| {});
6860
6861 let cols = 4;
6862 let rows = 10;
6863 let sample_text_1 = sample_text(rows, cols, 'a');
6864 assert_eq!(
6865 sample_text_1,
6866 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6867 );
6868 let sample_text_2 = sample_text(rows, cols, 'l');
6869 assert_eq!(
6870 sample_text_2,
6871 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6872 );
6873 let sample_text_3 = sample_text(rows, cols, 'v');
6874 assert_eq!(
6875 sample_text_3,
6876 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6877 );
6878
6879 let fs = FakeFs::new(cx.executor());
6880 fs.insert_tree(
6881 "/a",
6882 json!({
6883 "main.rs": sample_text_1,
6884 "other.rs": sample_text_2,
6885 "lib.rs": sample_text_3,
6886 }),
6887 )
6888 .await;
6889
6890 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6891 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6892 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6893
6894 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6895 language_registry.add(rust_lang());
6896 let mut fake_servers = language_registry.register_fake_lsp(
6897 "Rust",
6898 FakeLspAdapter {
6899 capabilities: lsp::ServerCapabilities {
6900 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6901 ..Default::default()
6902 },
6903 ..Default::default()
6904 },
6905 );
6906
6907 let worktree = project.update(cx, |project, cx| {
6908 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
6909 assert_eq!(worktrees.len(), 1);
6910 worktrees.pop().unwrap()
6911 });
6912 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6913
6914 let buffer_1 = project
6915 .update(cx, |project, cx| {
6916 project.open_buffer((worktree_id, "main.rs"), cx)
6917 })
6918 .await
6919 .unwrap();
6920 let buffer_2 = project
6921 .update(cx, |project, cx| {
6922 project.open_buffer((worktree_id, "other.rs"), cx)
6923 })
6924 .await
6925 .unwrap();
6926 let buffer_3 = project
6927 .update(cx, |project, cx| {
6928 project.open_buffer((worktree_id, "lib.rs"), cx)
6929 })
6930 .await
6931 .unwrap();
6932
6933 let multi_buffer = cx.new_model(|cx| {
6934 let mut multi_buffer = MultiBuffer::new(ReadWrite);
6935 multi_buffer.push_excerpts(
6936 buffer_1.clone(),
6937 [
6938 ExcerptRange {
6939 context: Point::new(0, 0)..Point::new(3, 0),
6940 primary: None,
6941 },
6942 ExcerptRange {
6943 context: Point::new(5, 0)..Point::new(7, 0),
6944 primary: None,
6945 },
6946 ExcerptRange {
6947 context: Point::new(9, 0)..Point::new(10, 4),
6948 primary: None,
6949 },
6950 ],
6951 cx,
6952 );
6953 multi_buffer.push_excerpts(
6954 buffer_2.clone(),
6955 [
6956 ExcerptRange {
6957 context: Point::new(0, 0)..Point::new(3, 0),
6958 primary: None,
6959 },
6960 ExcerptRange {
6961 context: Point::new(5, 0)..Point::new(7, 0),
6962 primary: None,
6963 },
6964 ExcerptRange {
6965 context: Point::new(9, 0)..Point::new(10, 4),
6966 primary: None,
6967 },
6968 ],
6969 cx,
6970 );
6971 multi_buffer.push_excerpts(
6972 buffer_3.clone(),
6973 [
6974 ExcerptRange {
6975 context: Point::new(0, 0)..Point::new(3, 0),
6976 primary: None,
6977 },
6978 ExcerptRange {
6979 context: Point::new(5, 0)..Point::new(7, 0),
6980 primary: None,
6981 },
6982 ExcerptRange {
6983 context: Point::new(9, 0)..Point::new(10, 4),
6984 primary: None,
6985 },
6986 ],
6987 cx,
6988 );
6989 multi_buffer
6990 });
6991 let multi_buffer_editor = cx.new_view(|cx| {
6992 Editor::new(
6993 EditorMode::Full,
6994 multi_buffer,
6995 Some(project.clone()),
6996 true,
6997 cx,
6998 )
6999 });
7000
7001 multi_buffer_editor.update(cx, |editor, cx| {
7002 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
7003 editor.insert("|one|two|three|", cx);
7004 });
7005 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7006 multi_buffer_editor.update(cx, |editor, cx| {
7007 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
7008 s.select_ranges(Some(60..70))
7009 });
7010 editor.insert("|four|five|six|", cx);
7011 });
7012 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7013
7014 // First two buffers should be edited, but not the third one.
7015 assert_eq!(
7016 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7017 "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}",
7018 );
7019 buffer_1.update(cx, |buffer, _| {
7020 assert!(buffer.is_dirty());
7021 assert_eq!(
7022 buffer.text(),
7023 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7024 )
7025 });
7026 buffer_2.update(cx, |buffer, _| {
7027 assert!(buffer.is_dirty());
7028 assert_eq!(
7029 buffer.text(),
7030 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7031 )
7032 });
7033 buffer_3.update(cx, |buffer, _| {
7034 assert!(!buffer.is_dirty());
7035 assert_eq!(buffer.text(), sample_text_3,)
7036 });
7037
7038 cx.executor().start_waiting();
7039 let save = multi_buffer_editor
7040 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7041 .unwrap();
7042
7043 let fake_server = fake_servers.next().await.unwrap();
7044 fake_server
7045 .server
7046 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7047 Ok(Some(vec![lsp::TextEdit::new(
7048 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7049 format!("[{} formatted]", params.text_document.uri),
7050 )]))
7051 })
7052 .detach();
7053 save.await;
7054
7055 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7056 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7057 assert_eq!(
7058 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7059 "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}",
7060 );
7061 buffer_1.update(cx, |buffer, _| {
7062 assert!(!buffer.is_dirty());
7063 assert_eq!(
7064 buffer.text(),
7065 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
7066 )
7067 });
7068 buffer_2.update(cx, |buffer, _| {
7069 assert!(!buffer.is_dirty());
7070 assert_eq!(
7071 buffer.text(),
7072 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
7073 )
7074 });
7075 buffer_3.update(cx, |buffer, _| {
7076 assert!(!buffer.is_dirty());
7077 assert_eq!(buffer.text(), sample_text_3,)
7078 });
7079}
7080
7081#[gpui::test]
7082async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
7083 init_test(cx, |_| {});
7084
7085 let fs = FakeFs::new(cx.executor());
7086 fs.insert_file("/file.rs", Default::default()).await;
7087
7088 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7089
7090 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7091 language_registry.add(rust_lang());
7092 let mut fake_servers = language_registry.register_fake_lsp(
7093 "Rust",
7094 FakeLspAdapter {
7095 capabilities: lsp::ServerCapabilities {
7096 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7097 ..Default::default()
7098 },
7099 ..Default::default()
7100 },
7101 );
7102
7103 let buffer = project
7104 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7105 .await
7106 .unwrap();
7107
7108 cx.executor().start_waiting();
7109 let fake_server = fake_servers.next().await.unwrap();
7110
7111 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7112 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7113 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7114 assert!(cx.read(|cx| editor.is_dirty(cx)));
7115
7116 let save = editor
7117 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7118 .unwrap();
7119 fake_server
7120 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7121 assert_eq!(
7122 params.text_document.uri,
7123 lsp::Url::from_file_path("/file.rs").unwrap()
7124 );
7125 assert_eq!(params.options.tab_size, 4);
7126 Ok(Some(vec![lsp::TextEdit::new(
7127 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7128 ", ".to_string(),
7129 )]))
7130 })
7131 .next()
7132 .await;
7133 cx.executor().start_waiting();
7134 save.await;
7135 assert_eq!(
7136 editor.update(cx, |editor, cx| editor.text(cx)),
7137 "one, two\nthree\n"
7138 );
7139 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7140
7141 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7142 assert!(cx.read(|cx| editor.is_dirty(cx)));
7143
7144 // Ensure we can still save even if formatting hangs.
7145 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7146 move |params, _| async move {
7147 assert_eq!(
7148 params.text_document.uri,
7149 lsp::Url::from_file_path("/file.rs").unwrap()
7150 );
7151 futures::future::pending::<()>().await;
7152 unreachable!()
7153 },
7154 );
7155 let save = editor
7156 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7157 .unwrap();
7158 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7159 cx.executor().start_waiting();
7160 save.await;
7161 assert_eq!(
7162 editor.update(cx, |editor, cx| editor.text(cx)),
7163 "one\ntwo\nthree\n"
7164 );
7165 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7166
7167 // For non-dirty buffer, no formatting request should be sent
7168 let save = editor
7169 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7170 .unwrap();
7171 let _pending_format_request = fake_server
7172 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7173 panic!("Should not be invoked on non-dirty buffer");
7174 })
7175 .next();
7176 cx.executor().start_waiting();
7177 save.await;
7178
7179 // Set Rust language override and assert overridden tabsize is sent to language server
7180 update_test_language_settings(cx, |settings| {
7181 settings.languages.insert(
7182 "Rust".into(),
7183 LanguageSettingsContent {
7184 tab_size: NonZeroU32::new(8),
7185 ..Default::default()
7186 },
7187 );
7188 });
7189
7190 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
7191 assert!(cx.read(|cx| editor.is_dirty(cx)));
7192 let save = editor
7193 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7194 .unwrap();
7195 fake_server
7196 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7197 assert_eq!(
7198 params.text_document.uri,
7199 lsp::Url::from_file_path("/file.rs").unwrap()
7200 );
7201 assert_eq!(params.options.tab_size, 8);
7202 Ok(Some(vec![]))
7203 })
7204 .next()
7205 .await;
7206 cx.executor().start_waiting();
7207 save.await;
7208}
7209
7210#[gpui::test]
7211async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7212 init_test(cx, |settings| {
7213 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7214 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7215 ))
7216 });
7217
7218 let fs = FakeFs::new(cx.executor());
7219 fs.insert_file("/file.rs", Default::default()).await;
7220
7221 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7222
7223 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7224 language_registry.add(Arc::new(Language::new(
7225 LanguageConfig {
7226 name: "Rust".into(),
7227 matcher: LanguageMatcher {
7228 path_suffixes: vec!["rs".to_string()],
7229 ..Default::default()
7230 },
7231 ..LanguageConfig::default()
7232 },
7233 Some(tree_sitter_rust::LANGUAGE.into()),
7234 )));
7235 update_test_language_settings(cx, |settings| {
7236 // Enable Prettier formatting for the same buffer, and ensure
7237 // LSP is called instead of Prettier.
7238 settings.defaults.prettier = Some(PrettierSettings {
7239 allowed: true,
7240 ..PrettierSettings::default()
7241 });
7242 });
7243 let mut fake_servers = language_registry.register_fake_lsp(
7244 "Rust",
7245 FakeLspAdapter {
7246 capabilities: lsp::ServerCapabilities {
7247 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7248 ..Default::default()
7249 },
7250 ..Default::default()
7251 },
7252 );
7253
7254 let buffer = project
7255 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7256 .await
7257 .unwrap();
7258
7259 cx.executor().start_waiting();
7260 let fake_server = fake_servers.next().await.unwrap();
7261
7262 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7263 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7264 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7265
7266 let format = editor
7267 .update(cx, |editor, cx| {
7268 editor.perform_format(
7269 project.clone(),
7270 FormatTrigger::Manual,
7271 FormatTarget::Buffer,
7272 cx,
7273 )
7274 })
7275 .unwrap();
7276 fake_server
7277 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7278 assert_eq!(
7279 params.text_document.uri,
7280 lsp::Url::from_file_path("/file.rs").unwrap()
7281 );
7282 assert_eq!(params.options.tab_size, 4);
7283 Ok(Some(vec![lsp::TextEdit::new(
7284 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7285 ", ".to_string(),
7286 )]))
7287 })
7288 .next()
7289 .await;
7290 cx.executor().start_waiting();
7291 format.await;
7292 assert_eq!(
7293 editor.update(cx, |editor, cx| editor.text(cx)),
7294 "one, two\nthree\n"
7295 );
7296
7297 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7298 // Ensure we don't lock if formatting hangs.
7299 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7300 assert_eq!(
7301 params.text_document.uri,
7302 lsp::Url::from_file_path("/file.rs").unwrap()
7303 );
7304 futures::future::pending::<()>().await;
7305 unreachable!()
7306 });
7307 let format = editor
7308 .update(cx, |editor, cx| {
7309 editor.perform_format(project, FormatTrigger::Manual, FormatTarget::Buffer, cx)
7310 })
7311 .unwrap();
7312 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7313 cx.executor().start_waiting();
7314 format.await;
7315 assert_eq!(
7316 editor.update(cx, |editor, cx| editor.text(cx)),
7317 "one\ntwo\nthree\n"
7318 );
7319}
7320
7321#[gpui::test]
7322async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7323 init_test(cx, |_| {});
7324
7325 let mut cx = EditorLspTestContext::new_rust(
7326 lsp::ServerCapabilities {
7327 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7328 ..Default::default()
7329 },
7330 cx,
7331 )
7332 .await;
7333
7334 cx.set_state(indoc! {"
7335 one.twoˇ
7336 "});
7337
7338 // The format request takes a long time. When it completes, it inserts
7339 // a newline and an indent before the `.`
7340 cx.lsp
7341 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7342 let executor = cx.background_executor().clone();
7343 async move {
7344 executor.timer(Duration::from_millis(100)).await;
7345 Ok(Some(vec![lsp::TextEdit {
7346 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7347 new_text: "\n ".into(),
7348 }]))
7349 }
7350 });
7351
7352 // Submit a format request.
7353 let format_1 = cx
7354 .update_editor(|editor, cx| editor.format(&Format, cx))
7355 .unwrap();
7356 cx.executor().run_until_parked();
7357
7358 // Submit a second format request.
7359 let format_2 = cx
7360 .update_editor(|editor, cx| editor.format(&Format, cx))
7361 .unwrap();
7362 cx.executor().run_until_parked();
7363
7364 // Wait for both format requests to complete
7365 cx.executor().advance_clock(Duration::from_millis(200));
7366 cx.executor().start_waiting();
7367 format_1.await.unwrap();
7368 cx.executor().start_waiting();
7369 format_2.await.unwrap();
7370
7371 // The formatting edits only happens once.
7372 cx.assert_editor_state(indoc! {"
7373 one
7374 .twoˇ
7375 "});
7376}
7377
7378#[gpui::test]
7379async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7380 init_test(cx, |settings| {
7381 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7382 });
7383
7384 let mut cx = EditorLspTestContext::new_rust(
7385 lsp::ServerCapabilities {
7386 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7387 ..Default::default()
7388 },
7389 cx,
7390 )
7391 .await;
7392
7393 // Set up a buffer white some trailing whitespace and no trailing newline.
7394 cx.set_state(
7395 &[
7396 "one ", //
7397 "twoˇ", //
7398 "three ", //
7399 "four", //
7400 ]
7401 .join("\n"),
7402 );
7403
7404 // Submit a format request.
7405 let format = cx
7406 .update_editor(|editor, cx| editor.format(&Format, cx))
7407 .unwrap();
7408
7409 // Record which buffer changes have been sent to the language server
7410 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7411 cx.lsp
7412 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7413 let buffer_changes = buffer_changes.clone();
7414 move |params, _| {
7415 buffer_changes.lock().extend(
7416 params
7417 .content_changes
7418 .into_iter()
7419 .map(|e| (e.range.unwrap(), e.text)),
7420 );
7421 }
7422 });
7423
7424 // Handle formatting requests to the language server.
7425 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7426 let buffer_changes = buffer_changes.clone();
7427 move |_, _| {
7428 // When formatting is requested, trailing whitespace has already been stripped,
7429 // and the trailing newline has already been added.
7430 assert_eq!(
7431 &buffer_changes.lock()[1..],
7432 &[
7433 (
7434 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7435 "".into()
7436 ),
7437 (
7438 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7439 "".into()
7440 ),
7441 (
7442 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7443 "\n".into()
7444 ),
7445 ]
7446 );
7447
7448 // Insert blank lines between each line of the buffer.
7449 async move {
7450 Ok(Some(vec![
7451 lsp::TextEdit {
7452 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7453 new_text: "\n".into(),
7454 },
7455 lsp::TextEdit {
7456 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7457 new_text: "\n".into(),
7458 },
7459 ]))
7460 }
7461 }
7462 });
7463
7464 // After formatting the buffer, the trailing whitespace is stripped,
7465 // a newline is appended, and the edits provided by the language server
7466 // have been applied.
7467 format.await.unwrap();
7468 cx.assert_editor_state(
7469 &[
7470 "one", //
7471 "", //
7472 "twoˇ", //
7473 "", //
7474 "three", //
7475 "four", //
7476 "", //
7477 ]
7478 .join("\n"),
7479 );
7480
7481 // Undoing the formatting undoes the trailing whitespace removal, the
7482 // trailing newline, and the LSP edits.
7483 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7484 cx.assert_editor_state(
7485 &[
7486 "one ", //
7487 "twoˇ", //
7488 "three ", //
7489 "four", //
7490 ]
7491 .join("\n"),
7492 );
7493}
7494
7495#[gpui::test]
7496async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7497 cx: &mut gpui::TestAppContext,
7498) {
7499 init_test(cx, |_| {});
7500
7501 cx.update(|cx| {
7502 cx.update_global::<SettingsStore, _>(|settings, cx| {
7503 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7504 settings.auto_signature_help = Some(true);
7505 });
7506 });
7507 });
7508
7509 let mut cx = EditorLspTestContext::new_rust(
7510 lsp::ServerCapabilities {
7511 signature_help_provider: Some(lsp::SignatureHelpOptions {
7512 ..Default::default()
7513 }),
7514 ..Default::default()
7515 },
7516 cx,
7517 )
7518 .await;
7519
7520 let language = Language::new(
7521 LanguageConfig {
7522 name: "Rust".into(),
7523 brackets: BracketPairConfig {
7524 pairs: vec![
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: true,
7536 surround: true,
7537 newline: true,
7538 },
7539 BracketPair {
7540 start: "/*".to_string(),
7541 end: " */".to_string(),
7542 close: true,
7543 surround: true,
7544 newline: true,
7545 },
7546 BracketPair {
7547 start: "[".to_string(),
7548 end: "]".to_string(),
7549 close: false,
7550 surround: false,
7551 newline: true,
7552 },
7553 BracketPair {
7554 start: "\"".to_string(),
7555 end: "\"".to_string(),
7556 close: true,
7557 surround: true,
7558 newline: false,
7559 },
7560 BracketPair {
7561 start: "<".to_string(),
7562 end: ">".to_string(),
7563 close: false,
7564 surround: true,
7565 newline: true,
7566 },
7567 ],
7568 ..Default::default()
7569 },
7570 autoclose_before: "})]".to_string(),
7571 ..Default::default()
7572 },
7573 Some(tree_sitter_rust::LANGUAGE.into()),
7574 );
7575 let language = Arc::new(language);
7576
7577 cx.language_registry().add(language.clone());
7578 cx.update_buffer(|buffer, cx| {
7579 buffer.set_language(Some(language), cx);
7580 });
7581
7582 cx.set_state(
7583 &r#"
7584 fn main() {
7585 sampleˇ
7586 }
7587 "#
7588 .unindent(),
7589 );
7590
7591 cx.update_editor(|view, cx| {
7592 view.handle_input("(", cx);
7593 });
7594 cx.assert_editor_state(
7595 &"
7596 fn main() {
7597 sample(ˇ)
7598 }
7599 "
7600 .unindent(),
7601 );
7602
7603 let mocked_response = lsp::SignatureHelp {
7604 signatures: vec![lsp::SignatureInformation {
7605 label: "fn sample(param1: u8, param2: u8)".to_string(),
7606 documentation: None,
7607 parameters: Some(vec![
7608 lsp::ParameterInformation {
7609 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7610 documentation: None,
7611 },
7612 lsp::ParameterInformation {
7613 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7614 documentation: None,
7615 },
7616 ]),
7617 active_parameter: None,
7618 }],
7619 active_signature: Some(0),
7620 active_parameter: Some(0),
7621 };
7622 handle_signature_help_request(&mut cx, mocked_response).await;
7623
7624 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7625 .await;
7626
7627 cx.editor(|editor, _| {
7628 let signature_help_state = editor.signature_help_state.popover().cloned();
7629 assert!(signature_help_state.is_some());
7630 let ParsedMarkdown {
7631 text, highlights, ..
7632 } = signature_help_state.unwrap().parsed_content;
7633 assert_eq!(text, "param1: u8, param2: u8");
7634 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7635 });
7636}
7637
7638#[gpui::test]
7639async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7640 init_test(cx, |_| {});
7641
7642 cx.update(|cx| {
7643 cx.update_global::<SettingsStore, _>(|settings, cx| {
7644 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7645 settings.auto_signature_help = Some(false);
7646 settings.show_signature_help_after_edits = Some(false);
7647 });
7648 });
7649 });
7650
7651 let mut cx = EditorLspTestContext::new_rust(
7652 lsp::ServerCapabilities {
7653 signature_help_provider: Some(lsp::SignatureHelpOptions {
7654 ..Default::default()
7655 }),
7656 ..Default::default()
7657 },
7658 cx,
7659 )
7660 .await;
7661
7662 let language = Language::new(
7663 LanguageConfig {
7664 name: "Rust".into(),
7665 brackets: BracketPairConfig {
7666 pairs: vec![
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: true,
7678 surround: true,
7679 newline: true,
7680 },
7681 BracketPair {
7682 start: "/*".to_string(),
7683 end: " */".to_string(),
7684 close: true,
7685 surround: true,
7686 newline: true,
7687 },
7688 BracketPair {
7689 start: "[".to_string(),
7690 end: "]".to_string(),
7691 close: false,
7692 surround: false,
7693 newline: true,
7694 },
7695 BracketPair {
7696 start: "\"".to_string(),
7697 end: "\"".to_string(),
7698 close: true,
7699 surround: true,
7700 newline: false,
7701 },
7702 BracketPair {
7703 start: "<".to_string(),
7704 end: ">".to_string(),
7705 close: false,
7706 surround: true,
7707 newline: true,
7708 },
7709 ],
7710 ..Default::default()
7711 },
7712 autoclose_before: "})]".to_string(),
7713 ..Default::default()
7714 },
7715 Some(tree_sitter_rust::LANGUAGE.into()),
7716 );
7717 let language = Arc::new(language);
7718
7719 cx.language_registry().add(language.clone());
7720 cx.update_buffer(|buffer, cx| {
7721 buffer.set_language(Some(language), cx);
7722 });
7723
7724 // Ensure that signature_help is not called when no signature help is enabled.
7725 cx.set_state(
7726 &r#"
7727 fn main() {
7728 sampleˇ
7729 }
7730 "#
7731 .unindent(),
7732 );
7733 cx.update_editor(|view, cx| {
7734 view.handle_input("(", cx);
7735 });
7736 cx.assert_editor_state(
7737 &"
7738 fn main() {
7739 sample(ˇ)
7740 }
7741 "
7742 .unindent(),
7743 );
7744 cx.editor(|editor, _| {
7745 assert!(editor.signature_help_state.task().is_none());
7746 });
7747
7748 let mocked_response = lsp::SignatureHelp {
7749 signatures: vec![lsp::SignatureInformation {
7750 label: "fn sample(param1: u8, param2: u8)".to_string(),
7751 documentation: None,
7752 parameters: Some(vec![
7753 lsp::ParameterInformation {
7754 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7755 documentation: None,
7756 },
7757 lsp::ParameterInformation {
7758 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7759 documentation: None,
7760 },
7761 ]),
7762 active_parameter: None,
7763 }],
7764 active_signature: Some(0),
7765 active_parameter: Some(0),
7766 };
7767
7768 // Ensure that signature_help is called when enabled afte edits
7769 cx.update(|cx| {
7770 cx.update_global::<SettingsStore, _>(|settings, cx| {
7771 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7772 settings.auto_signature_help = Some(false);
7773 settings.show_signature_help_after_edits = Some(true);
7774 });
7775 });
7776 });
7777 cx.set_state(
7778 &r#"
7779 fn main() {
7780 sampleˇ
7781 }
7782 "#
7783 .unindent(),
7784 );
7785 cx.update_editor(|view, cx| {
7786 view.handle_input("(", cx);
7787 });
7788 cx.assert_editor_state(
7789 &"
7790 fn main() {
7791 sample(ˇ)
7792 }
7793 "
7794 .unindent(),
7795 );
7796 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7797 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7798 .await;
7799 cx.update_editor(|editor, _| {
7800 let signature_help_state = editor.signature_help_state.popover().cloned();
7801 assert!(signature_help_state.is_some());
7802 let ParsedMarkdown {
7803 text, highlights, ..
7804 } = signature_help_state.unwrap().parsed_content;
7805 assert_eq!(text, "param1: u8, param2: u8");
7806 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7807 editor.signature_help_state = SignatureHelpState::default();
7808 });
7809
7810 // Ensure that signature_help is called when auto signature help override is enabled
7811 cx.update(|cx| {
7812 cx.update_global::<SettingsStore, _>(|settings, cx| {
7813 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7814 settings.auto_signature_help = Some(true);
7815 settings.show_signature_help_after_edits = Some(false);
7816 });
7817 });
7818 });
7819 cx.set_state(
7820 &r#"
7821 fn main() {
7822 sampleˇ
7823 }
7824 "#
7825 .unindent(),
7826 );
7827 cx.update_editor(|view, cx| {
7828 view.handle_input("(", cx);
7829 });
7830 cx.assert_editor_state(
7831 &"
7832 fn main() {
7833 sample(ˇ)
7834 }
7835 "
7836 .unindent(),
7837 );
7838 handle_signature_help_request(&mut cx, mocked_response).await;
7839 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7840 .await;
7841 cx.editor(|editor, _| {
7842 let signature_help_state = editor.signature_help_state.popover().cloned();
7843 assert!(signature_help_state.is_some());
7844 let ParsedMarkdown {
7845 text, highlights, ..
7846 } = signature_help_state.unwrap().parsed_content;
7847 assert_eq!(text, "param1: u8, param2: u8");
7848 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7849 });
7850}
7851
7852#[gpui::test]
7853async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7854 init_test(cx, |_| {});
7855 cx.update(|cx| {
7856 cx.update_global::<SettingsStore, _>(|settings, cx| {
7857 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7858 settings.auto_signature_help = Some(true);
7859 });
7860 });
7861 });
7862
7863 let mut cx = EditorLspTestContext::new_rust(
7864 lsp::ServerCapabilities {
7865 signature_help_provider: Some(lsp::SignatureHelpOptions {
7866 ..Default::default()
7867 }),
7868 ..Default::default()
7869 },
7870 cx,
7871 )
7872 .await;
7873
7874 // A test that directly calls `show_signature_help`
7875 cx.update_editor(|editor, cx| {
7876 editor.show_signature_help(&ShowSignatureHelp, cx);
7877 });
7878
7879 let mocked_response = lsp::SignatureHelp {
7880 signatures: vec![lsp::SignatureInformation {
7881 label: "fn sample(param1: u8, param2: u8)".to_string(),
7882 documentation: None,
7883 parameters: Some(vec![
7884 lsp::ParameterInformation {
7885 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7886 documentation: None,
7887 },
7888 lsp::ParameterInformation {
7889 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7890 documentation: None,
7891 },
7892 ]),
7893 active_parameter: None,
7894 }],
7895 active_signature: Some(0),
7896 active_parameter: Some(0),
7897 };
7898 handle_signature_help_request(&mut cx, mocked_response).await;
7899
7900 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7901 .await;
7902
7903 cx.editor(|editor, _| {
7904 let signature_help_state = editor.signature_help_state.popover().cloned();
7905 assert!(signature_help_state.is_some());
7906 let ParsedMarkdown {
7907 text, highlights, ..
7908 } = signature_help_state.unwrap().parsed_content;
7909 assert_eq!(text, "param1: u8, param2: u8");
7910 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7911 });
7912
7913 // When exiting outside from inside the brackets, `signature_help` is closed.
7914 cx.set_state(indoc! {"
7915 fn main() {
7916 sample(ˇ);
7917 }
7918
7919 fn sample(param1: u8, param2: u8) {}
7920 "});
7921
7922 cx.update_editor(|editor, cx| {
7923 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
7924 });
7925
7926 let mocked_response = lsp::SignatureHelp {
7927 signatures: Vec::new(),
7928 active_signature: None,
7929 active_parameter: None,
7930 };
7931 handle_signature_help_request(&mut cx, mocked_response).await;
7932
7933 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
7934 .await;
7935
7936 cx.editor(|editor, _| {
7937 assert!(!editor.signature_help_state.is_shown());
7938 });
7939
7940 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
7941 cx.set_state(indoc! {"
7942 fn main() {
7943 sample(ˇ);
7944 }
7945
7946 fn sample(param1: u8, param2: u8) {}
7947 "});
7948
7949 let mocked_response = lsp::SignatureHelp {
7950 signatures: vec![lsp::SignatureInformation {
7951 label: "fn sample(param1: u8, param2: u8)".to_string(),
7952 documentation: None,
7953 parameters: Some(vec![
7954 lsp::ParameterInformation {
7955 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7956 documentation: None,
7957 },
7958 lsp::ParameterInformation {
7959 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7960 documentation: None,
7961 },
7962 ]),
7963 active_parameter: None,
7964 }],
7965 active_signature: Some(0),
7966 active_parameter: Some(0),
7967 };
7968 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7969 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7970 .await;
7971 cx.editor(|editor, _| {
7972 assert!(editor.signature_help_state.is_shown());
7973 });
7974
7975 // Restore the popover with more parameter input
7976 cx.set_state(indoc! {"
7977 fn main() {
7978 sample(param1, param2ˇ);
7979 }
7980
7981 fn sample(param1: u8, param2: u8) {}
7982 "});
7983
7984 let mocked_response = lsp::SignatureHelp {
7985 signatures: vec![lsp::SignatureInformation {
7986 label: "fn sample(param1: u8, param2: u8)".to_string(),
7987 documentation: None,
7988 parameters: Some(vec![
7989 lsp::ParameterInformation {
7990 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7991 documentation: None,
7992 },
7993 lsp::ParameterInformation {
7994 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7995 documentation: None,
7996 },
7997 ]),
7998 active_parameter: None,
7999 }],
8000 active_signature: Some(0),
8001 active_parameter: Some(1),
8002 };
8003 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8004 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8005 .await;
8006
8007 // When selecting a range, the popover is gone.
8008 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8009 cx.update_editor(|editor, cx| {
8010 editor.change_selections(None, cx, |s| {
8011 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8012 })
8013 });
8014 cx.assert_editor_state(indoc! {"
8015 fn main() {
8016 sample(param1, «ˇparam2»);
8017 }
8018
8019 fn sample(param1: u8, param2: u8) {}
8020 "});
8021 cx.editor(|editor, _| {
8022 assert!(!editor.signature_help_state.is_shown());
8023 });
8024
8025 // When unselecting again, the popover is back if within the brackets.
8026 cx.update_editor(|editor, cx| {
8027 editor.change_selections(None, cx, |s| {
8028 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8029 })
8030 });
8031 cx.assert_editor_state(indoc! {"
8032 fn main() {
8033 sample(param1, ˇparam2);
8034 }
8035
8036 fn sample(param1: u8, param2: u8) {}
8037 "});
8038 handle_signature_help_request(&mut cx, mocked_response).await;
8039 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8040 .await;
8041 cx.editor(|editor, _| {
8042 assert!(editor.signature_help_state.is_shown());
8043 });
8044
8045 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8046 cx.update_editor(|editor, cx| {
8047 editor.change_selections(None, cx, |s| {
8048 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8049 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8050 })
8051 });
8052 cx.assert_editor_state(indoc! {"
8053 fn main() {
8054 sample(param1, ˇparam2);
8055 }
8056
8057 fn sample(param1: u8, param2: u8) {}
8058 "});
8059
8060 let mocked_response = lsp::SignatureHelp {
8061 signatures: vec![lsp::SignatureInformation {
8062 label: "fn sample(param1: u8, param2: u8)".to_string(),
8063 documentation: None,
8064 parameters: Some(vec![
8065 lsp::ParameterInformation {
8066 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8067 documentation: None,
8068 },
8069 lsp::ParameterInformation {
8070 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8071 documentation: None,
8072 },
8073 ]),
8074 active_parameter: None,
8075 }],
8076 active_signature: Some(0),
8077 active_parameter: Some(1),
8078 };
8079 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8080 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8081 .await;
8082 cx.update_editor(|editor, cx| {
8083 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8084 });
8085 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8086 .await;
8087 cx.update_editor(|editor, cx| {
8088 editor.change_selections(None, cx, |s| {
8089 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8090 })
8091 });
8092 cx.assert_editor_state(indoc! {"
8093 fn main() {
8094 sample(param1, «ˇparam2»);
8095 }
8096
8097 fn sample(param1: u8, param2: u8) {}
8098 "});
8099 cx.update_editor(|editor, cx| {
8100 editor.change_selections(None, cx, |s| {
8101 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8102 })
8103 });
8104 cx.assert_editor_state(indoc! {"
8105 fn main() {
8106 sample(param1, ˇparam2);
8107 }
8108
8109 fn sample(param1: u8, param2: u8) {}
8110 "});
8111 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8112 .await;
8113}
8114
8115#[gpui::test]
8116async fn test_completion(cx: &mut gpui::TestAppContext) {
8117 init_test(cx, |_| {});
8118
8119 let mut cx = EditorLspTestContext::new_rust(
8120 lsp::ServerCapabilities {
8121 completion_provider: Some(lsp::CompletionOptions {
8122 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8123 resolve_provider: Some(true),
8124 ..Default::default()
8125 }),
8126 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8127 ..Default::default()
8128 },
8129 cx,
8130 )
8131 .await;
8132 let counter = Arc::new(AtomicUsize::new(0));
8133
8134 cx.set_state(indoc! {"
8135 oneˇ
8136 two
8137 three
8138 "});
8139 cx.simulate_keystroke(".");
8140 handle_completion_request(
8141 &mut cx,
8142 indoc! {"
8143 one.|<>
8144 two
8145 three
8146 "},
8147 vec!["first_completion", "second_completion"],
8148 counter.clone(),
8149 )
8150 .await;
8151 cx.condition(|editor, _| editor.context_menu_visible())
8152 .await;
8153 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8154
8155 let _handler = handle_signature_help_request(
8156 &mut cx,
8157 lsp::SignatureHelp {
8158 signatures: vec![lsp::SignatureInformation {
8159 label: "test signature".to_string(),
8160 documentation: None,
8161 parameters: Some(vec![lsp::ParameterInformation {
8162 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8163 documentation: None,
8164 }]),
8165 active_parameter: None,
8166 }],
8167 active_signature: None,
8168 active_parameter: None,
8169 },
8170 );
8171 cx.update_editor(|editor, cx| {
8172 assert!(
8173 !editor.signature_help_state.is_shown(),
8174 "No signature help was called for"
8175 );
8176 editor.show_signature_help(&ShowSignatureHelp, cx);
8177 });
8178 cx.run_until_parked();
8179 cx.update_editor(|editor, _| {
8180 assert!(
8181 !editor.signature_help_state.is_shown(),
8182 "No signature help should be shown when completions menu is open"
8183 );
8184 });
8185
8186 let apply_additional_edits = cx.update_editor(|editor, cx| {
8187 editor.context_menu_next(&Default::default(), cx);
8188 editor
8189 .confirm_completion(&ConfirmCompletion::default(), cx)
8190 .unwrap()
8191 });
8192 cx.assert_editor_state(indoc! {"
8193 one.second_completionˇ
8194 two
8195 three
8196 "});
8197
8198 handle_resolve_completion_request(
8199 &mut cx,
8200 Some(vec![
8201 (
8202 //This overlaps with the primary completion edit which is
8203 //misbehavior from the LSP spec, test that we filter it out
8204 indoc! {"
8205 one.second_ˇcompletion
8206 two
8207 threeˇ
8208 "},
8209 "overlapping additional edit",
8210 ),
8211 (
8212 indoc! {"
8213 one.second_completion
8214 two
8215 threeˇ
8216 "},
8217 "\nadditional edit",
8218 ),
8219 ]),
8220 )
8221 .await;
8222 apply_additional_edits.await.unwrap();
8223 cx.assert_editor_state(indoc! {"
8224 one.second_completionˇ
8225 two
8226 three
8227 additional edit
8228 "});
8229
8230 cx.set_state(indoc! {"
8231 one.second_completion
8232 twoˇ
8233 threeˇ
8234 additional edit
8235 "});
8236 cx.simulate_keystroke(" ");
8237 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8238 cx.simulate_keystroke("s");
8239 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8240
8241 cx.assert_editor_state(indoc! {"
8242 one.second_completion
8243 two sˇ
8244 three sˇ
8245 additional edit
8246 "});
8247 handle_completion_request(
8248 &mut cx,
8249 indoc! {"
8250 one.second_completion
8251 two s
8252 three <s|>
8253 additional edit
8254 "},
8255 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8256 counter.clone(),
8257 )
8258 .await;
8259 cx.condition(|editor, _| editor.context_menu_visible())
8260 .await;
8261 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8262
8263 cx.simulate_keystroke("i");
8264
8265 handle_completion_request(
8266 &mut cx,
8267 indoc! {"
8268 one.second_completion
8269 two si
8270 three <si|>
8271 additional edit
8272 "},
8273 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8274 counter.clone(),
8275 )
8276 .await;
8277 cx.condition(|editor, _| editor.context_menu_visible())
8278 .await;
8279 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8280
8281 let apply_additional_edits = cx.update_editor(|editor, cx| {
8282 editor
8283 .confirm_completion(&ConfirmCompletion::default(), cx)
8284 .unwrap()
8285 });
8286 cx.assert_editor_state(indoc! {"
8287 one.second_completion
8288 two sixth_completionˇ
8289 three sixth_completionˇ
8290 additional edit
8291 "});
8292
8293 handle_resolve_completion_request(&mut cx, None).await;
8294 apply_additional_edits.await.unwrap();
8295
8296 cx.update(|cx| {
8297 cx.update_global::<SettingsStore, _>(|settings, cx| {
8298 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8299 settings.show_completions_on_input = Some(false);
8300 });
8301 })
8302 });
8303 cx.set_state("editorˇ");
8304 cx.simulate_keystroke(".");
8305 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8306 cx.simulate_keystroke("c");
8307 cx.simulate_keystroke("l");
8308 cx.simulate_keystroke("o");
8309 cx.assert_editor_state("editor.cloˇ");
8310 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8311 cx.update_editor(|editor, cx| {
8312 editor.show_completions(&ShowCompletions { trigger: None }, cx);
8313 });
8314 handle_completion_request(
8315 &mut cx,
8316 "editor.<clo|>",
8317 vec!["close", "clobber"],
8318 counter.clone(),
8319 )
8320 .await;
8321 cx.condition(|editor, _| editor.context_menu_visible())
8322 .await;
8323 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8324
8325 let apply_additional_edits = cx.update_editor(|editor, cx| {
8326 editor
8327 .confirm_completion(&ConfirmCompletion::default(), cx)
8328 .unwrap()
8329 });
8330 cx.assert_editor_state("editor.closeˇ");
8331 handle_resolve_completion_request(&mut cx, None).await;
8332 apply_additional_edits.await.unwrap();
8333}
8334
8335#[gpui::test]
8336async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8337 init_test(cx, |_| {});
8338 let mut cx = EditorLspTestContext::new_rust(
8339 lsp::ServerCapabilities {
8340 completion_provider: Some(lsp::CompletionOptions {
8341 trigger_characters: Some(vec![".".to_string()]),
8342 ..Default::default()
8343 }),
8344 ..Default::default()
8345 },
8346 cx,
8347 )
8348 .await;
8349 cx.lsp
8350 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8351 Ok(Some(lsp::CompletionResponse::Array(vec![
8352 lsp::CompletionItem {
8353 label: "first".into(),
8354 ..Default::default()
8355 },
8356 lsp::CompletionItem {
8357 label: "last".into(),
8358 ..Default::default()
8359 },
8360 ])))
8361 });
8362 cx.set_state("variableˇ");
8363 cx.simulate_keystroke(".");
8364 cx.executor().run_until_parked();
8365
8366 cx.update_editor(|editor, _| {
8367 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8368 assert_eq!(
8369 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8370 &["first", "last"]
8371 );
8372 } else {
8373 panic!("expected completion menu to be open");
8374 }
8375 });
8376
8377 cx.update_editor(|editor, cx| {
8378 editor.move_page_down(&MovePageDown::default(), cx);
8379 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8380 assert!(
8381 menu.selected_item == 1,
8382 "expected PageDown to select the last item from the context menu"
8383 );
8384 } else {
8385 panic!("expected completion menu to stay open after PageDown");
8386 }
8387 });
8388
8389 cx.update_editor(|editor, cx| {
8390 editor.move_page_up(&MovePageUp::default(), cx);
8391 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8392 assert!(
8393 menu.selected_item == 0,
8394 "expected PageUp to select the first item from the context menu"
8395 );
8396 } else {
8397 panic!("expected completion menu to stay open after PageUp");
8398 }
8399 });
8400}
8401
8402#[gpui::test]
8403async fn test_completion_sort(cx: &mut gpui::TestAppContext) {
8404 init_test(cx, |_| {});
8405 let mut cx = EditorLspTestContext::new_rust(
8406 lsp::ServerCapabilities {
8407 completion_provider: Some(lsp::CompletionOptions {
8408 trigger_characters: Some(vec![".".to_string()]),
8409 ..Default::default()
8410 }),
8411 ..Default::default()
8412 },
8413 cx,
8414 )
8415 .await;
8416 cx.lsp
8417 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8418 Ok(Some(lsp::CompletionResponse::Array(vec![
8419 lsp::CompletionItem {
8420 label: "Range".into(),
8421 sort_text: Some("a".into()),
8422 ..Default::default()
8423 },
8424 lsp::CompletionItem {
8425 label: "r".into(),
8426 sort_text: Some("b".into()),
8427 ..Default::default()
8428 },
8429 lsp::CompletionItem {
8430 label: "ret".into(),
8431 sort_text: Some("c".into()),
8432 ..Default::default()
8433 },
8434 lsp::CompletionItem {
8435 label: "return".into(),
8436 sort_text: Some("d".into()),
8437 ..Default::default()
8438 },
8439 lsp::CompletionItem {
8440 label: "slice".into(),
8441 sort_text: Some("d".into()),
8442 ..Default::default()
8443 },
8444 ])))
8445 });
8446 cx.set_state("rˇ");
8447 cx.executor().run_until_parked();
8448 cx.update_editor(|editor, cx| {
8449 editor.show_completions(
8450 &ShowCompletions {
8451 trigger: Some("r".into()),
8452 },
8453 cx,
8454 );
8455 });
8456 cx.executor().run_until_parked();
8457
8458 cx.update_editor(|editor, _| {
8459 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8460 assert_eq!(
8461 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8462 &["r", "ret", "Range", "return"]
8463 );
8464 } else {
8465 panic!("expected completion menu to be open");
8466 }
8467 });
8468}
8469
8470#[gpui::test]
8471async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8472 init_test(cx, |_| {});
8473
8474 let mut cx = EditorLspTestContext::new_rust(
8475 lsp::ServerCapabilities {
8476 completion_provider: Some(lsp::CompletionOptions {
8477 trigger_characters: Some(vec![".".to_string()]),
8478 resolve_provider: Some(true),
8479 ..Default::default()
8480 }),
8481 ..Default::default()
8482 },
8483 cx,
8484 )
8485 .await;
8486
8487 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8488 cx.simulate_keystroke(".");
8489 let completion_item = lsp::CompletionItem {
8490 label: "Some".into(),
8491 kind: Some(lsp::CompletionItemKind::SNIPPET),
8492 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8493 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8494 kind: lsp::MarkupKind::Markdown,
8495 value: "```rust\nSome(2)\n```".to_string(),
8496 })),
8497 deprecated: Some(false),
8498 sort_text: Some("Some".to_string()),
8499 filter_text: Some("Some".to_string()),
8500 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8501 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8502 range: lsp::Range {
8503 start: lsp::Position {
8504 line: 0,
8505 character: 22,
8506 },
8507 end: lsp::Position {
8508 line: 0,
8509 character: 22,
8510 },
8511 },
8512 new_text: "Some(2)".to_string(),
8513 })),
8514 additional_text_edits: Some(vec![lsp::TextEdit {
8515 range: lsp::Range {
8516 start: lsp::Position {
8517 line: 0,
8518 character: 20,
8519 },
8520 end: lsp::Position {
8521 line: 0,
8522 character: 22,
8523 },
8524 },
8525 new_text: "".to_string(),
8526 }]),
8527 ..Default::default()
8528 };
8529
8530 let closure_completion_item = completion_item.clone();
8531 let counter = Arc::new(AtomicUsize::new(0));
8532 let counter_clone = counter.clone();
8533 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8534 let task_completion_item = closure_completion_item.clone();
8535 counter_clone.fetch_add(1, atomic::Ordering::Release);
8536 async move {
8537 Ok(Some(lsp::CompletionResponse::Array(vec![
8538 task_completion_item,
8539 ])))
8540 }
8541 });
8542
8543 cx.condition(|editor, _| editor.context_menu_visible())
8544 .await;
8545 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8546 assert!(request.next().await.is_some());
8547 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8548
8549 cx.simulate_keystroke("S");
8550 cx.simulate_keystroke("o");
8551 cx.simulate_keystroke("m");
8552 cx.condition(|editor, _| editor.context_menu_visible())
8553 .await;
8554 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
8555 assert!(request.next().await.is_some());
8556 assert!(request.next().await.is_some());
8557 assert!(request.next().await.is_some());
8558 request.close();
8559 assert!(request.next().await.is_none());
8560 assert_eq!(
8561 counter.load(atomic::Ordering::Acquire),
8562 4,
8563 "With the completions menu open, only one LSP request should happen per input"
8564 );
8565}
8566
8567#[gpui::test]
8568async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
8569 init_test(cx, |_| {});
8570 let mut cx = EditorTestContext::new(cx).await;
8571 let language = Arc::new(Language::new(
8572 LanguageConfig {
8573 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8574 ..Default::default()
8575 },
8576 Some(tree_sitter_rust::LANGUAGE.into()),
8577 ));
8578 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8579
8580 // If multiple selections intersect a line, the line is only toggled once.
8581 cx.set_state(indoc! {"
8582 fn a() {
8583 «//b();
8584 ˇ»// «c();
8585 //ˇ» d();
8586 }
8587 "});
8588
8589 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8590
8591 cx.assert_editor_state(indoc! {"
8592 fn a() {
8593 «b();
8594 c();
8595 ˇ» d();
8596 }
8597 "});
8598
8599 // The comment prefix is inserted at the same column for every line in a
8600 // selection.
8601 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8602
8603 cx.assert_editor_state(indoc! {"
8604 fn a() {
8605 // «b();
8606 // c();
8607 ˇ»// d();
8608 }
8609 "});
8610
8611 // If a selection ends at the beginning of a line, that line is not toggled.
8612 cx.set_selections_state(indoc! {"
8613 fn a() {
8614 // b();
8615 «// c();
8616 ˇ» // d();
8617 }
8618 "});
8619
8620 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8621
8622 cx.assert_editor_state(indoc! {"
8623 fn a() {
8624 // b();
8625 «c();
8626 ˇ» // d();
8627 }
8628 "});
8629
8630 // If a selection span a single line and is empty, the line is toggled.
8631 cx.set_state(indoc! {"
8632 fn a() {
8633 a();
8634 b();
8635 ˇ
8636 }
8637 "});
8638
8639 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8640
8641 cx.assert_editor_state(indoc! {"
8642 fn a() {
8643 a();
8644 b();
8645 //•ˇ
8646 }
8647 "});
8648
8649 // If a selection span multiple lines, empty lines are not toggled.
8650 cx.set_state(indoc! {"
8651 fn a() {
8652 «a();
8653
8654 c();ˇ»
8655 }
8656 "});
8657
8658 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8659
8660 cx.assert_editor_state(indoc! {"
8661 fn a() {
8662 // «a();
8663
8664 // c();ˇ»
8665 }
8666 "});
8667
8668 // If a selection includes multiple comment prefixes, all lines are uncommented.
8669 cx.set_state(indoc! {"
8670 fn a() {
8671 «// a();
8672 /// b();
8673 //! c();ˇ»
8674 }
8675 "});
8676
8677 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8678
8679 cx.assert_editor_state(indoc! {"
8680 fn a() {
8681 «a();
8682 b();
8683 c();ˇ»
8684 }
8685 "});
8686}
8687
8688#[gpui::test]
8689async fn test_toggle_comment_ignore_indent(cx: &mut gpui::TestAppContext) {
8690 init_test(cx, |_| {});
8691 let mut cx = EditorTestContext::new(cx).await;
8692 let language = Arc::new(Language::new(
8693 LanguageConfig {
8694 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8695 ..Default::default()
8696 },
8697 Some(tree_sitter_rust::LANGUAGE.into()),
8698 ));
8699 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8700
8701 let toggle_comments = &ToggleComments {
8702 advance_downwards: false,
8703 ignore_indent: true,
8704 };
8705
8706 // If multiple selections intersect a line, the line is only toggled once.
8707 cx.set_state(indoc! {"
8708 fn a() {
8709 // «b();
8710 // c();
8711 // ˇ» d();
8712 }
8713 "});
8714
8715 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8716
8717 cx.assert_editor_state(indoc! {"
8718 fn a() {
8719 «b();
8720 c();
8721 ˇ» d();
8722 }
8723 "});
8724
8725 // The comment prefix is inserted at the beginning of each line
8726 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8727
8728 cx.assert_editor_state(indoc! {"
8729 fn a() {
8730 // «b();
8731 // c();
8732 // ˇ» d();
8733 }
8734 "});
8735
8736 // If a selection ends at the beginning of a line, that line is not toggled.
8737 cx.set_selections_state(indoc! {"
8738 fn a() {
8739 // b();
8740 // «c();
8741 ˇ»// d();
8742 }
8743 "});
8744
8745 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8746
8747 cx.assert_editor_state(indoc! {"
8748 fn a() {
8749 // b();
8750 «c();
8751 ˇ»// d();
8752 }
8753 "});
8754
8755 // If a selection span a single line and is empty, the line is toggled.
8756 cx.set_state(indoc! {"
8757 fn a() {
8758 a();
8759 b();
8760 ˇ
8761 }
8762 "});
8763
8764 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8765
8766 cx.assert_editor_state(indoc! {"
8767 fn a() {
8768 a();
8769 b();
8770 //ˇ
8771 }
8772 "});
8773
8774 // If a selection span multiple lines, empty lines are not toggled.
8775 cx.set_state(indoc! {"
8776 fn a() {
8777 «a();
8778
8779 c();ˇ»
8780 }
8781 "});
8782
8783 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8784
8785 cx.assert_editor_state(indoc! {"
8786 fn a() {
8787 // «a();
8788
8789 // c();ˇ»
8790 }
8791 "});
8792
8793 // If a selection includes multiple comment prefixes, all lines are uncommented.
8794 cx.set_state(indoc! {"
8795 fn a() {
8796 // «a();
8797 /// b();
8798 //! c();ˇ»
8799 }
8800 "});
8801
8802 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8803
8804 cx.assert_editor_state(indoc! {"
8805 fn a() {
8806 «a();
8807 b();
8808 c();ˇ»
8809 }
8810 "});
8811}
8812
8813#[gpui::test]
8814async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8815 init_test(cx, |_| {});
8816
8817 let language = Arc::new(Language::new(
8818 LanguageConfig {
8819 line_comments: vec!["// ".into()],
8820 ..Default::default()
8821 },
8822 Some(tree_sitter_rust::LANGUAGE.into()),
8823 ));
8824
8825 let mut cx = EditorTestContext::new(cx).await;
8826
8827 cx.language_registry().add(language.clone());
8828 cx.update_buffer(|buffer, cx| {
8829 buffer.set_language(Some(language), cx);
8830 });
8831
8832 let toggle_comments = &ToggleComments {
8833 advance_downwards: true,
8834 ignore_indent: false,
8835 };
8836
8837 // Single cursor on one line -> advance
8838 // Cursor moves horizontally 3 characters as well on non-blank line
8839 cx.set_state(indoc!(
8840 "fn a() {
8841 ˇdog();
8842 cat();
8843 }"
8844 ));
8845 cx.update_editor(|editor, cx| {
8846 editor.toggle_comments(toggle_comments, cx);
8847 });
8848 cx.assert_editor_state(indoc!(
8849 "fn a() {
8850 // dog();
8851 catˇ();
8852 }"
8853 ));
8854
8855 // Single selection on one line -> don't advance
8856 cx.set_state(indoc!(
8857 "fn a() {
8858 «dog()ˇ»;
8859 cat();
8860 }"
8861 ));
8862 cx.update_editor(|editor, cx| {
8863 editor.toggle_comments(toggle_comments, cx);
8864 });
8865 cx.assert_editor_state(indoc!(
8866 "fn a() {
8867 // «dog()ˇ»;
8868 cat();
8869 }"
8870 ));
8871
8872 // Multiple cursors on one line -> advance
8873 cx.set_state(indoc!(
8874 "fn a() {
8875 ˇdˇog();
8876 cat();
8877 }"
8878 ));
8879 cx.update_editor(|editor, cx| {
8880 editor.toggle_comments(toggle_comments, cx);
8881 });
8882 cx.assert_editor_state(indoc!(
8883 "fn a() {
8884 // dog();
8885 catˇ(ˇ);
8886 }"
8887 ));
8888
8889 // Multiple cursors on one line, with selection -> don't advance
8890 cx.set_state(indoc!(
8891 "fn a() {
8892 ˇdˇog«()ˇ»;
8893 cat();
8894 }"
8895 ));
8896 cx.update_editor(|editor, cx| {
8897 editor.toggle_comments(toggle_comments, cx);
8898 });
8899 cx.assert_editor_state(indoc!(
8900 "fn a() {
8901 // ˇdˇog«()ˇ»;
8902 cat();
8903 }"
8904 ));
8905
8906 // Single cursor on one line -> advance
8907 // Cursor moves to column 0 on blank line
8908 cx.set_state(indoc!(
8909 "fn a() {
8910 ˇdog();
8911
8912 cat();
8913 }"
8914 ));
8915 cx.update_editor(|editor, cx| {
8916 editor.toggle_comments(toggle_comments, cx);
8917 });
8918 cx.assert_editor_state(indoc!(
8919 "fn a() {
8920 // dog();
8921 ˇ
8922 cat();
8923 }"
8924 ));
8925
8926 // Single cursor on one line -> advance
8927 // Cursor starts and ends at column 0
8928 cx.set_state(indoc!(
8929 "fn a() {
8930 ˇ dog();
8931 cat();
8932 }"
8933 ));
8934 cx.update_editor(|editor, cx| {
8935 editor.toggle_comments(toggle_comments, cx);
8936 });
8937 cx.assert_editor_state(indoc!(
8938 "fn a() {
8939 // dog();
8940 ˇ cat();
8941 }"
8942 ));
8943}
8944
8945#[gpui::test]
8946async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
8947 init_test(cx, |_| {});
8948
8949 let mut cx = EditorTestContext::new(cx).await;
8950
8951 let html_language = Arc::new(
8952 Language::new(
8953 LanguageConfig {
8954 name: "HTML".into(),
8955 block_comment: Some(("<!-- ".into(), " -->".into())),
8956 ..Default::default()
8957 },
8958 Some(tree_sitter_html::language()),
8959 )
8960 .with_injection_query(
8961 r#"
8962 (script_element
8963 (raw_text) @content
8964 (#set! "language" "javascript"))
8965 "#,
8966 )
8967 .unwrap(),
8968 );
8969
8970 let javascript_language = Arc::new(Language::new(
8971 LanguageConfig {
8972 name: "JavaScript".into(),
8973 line_comments: vec!["// ".into()],
8974 ..Default::default()
8975 },
8976 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
8977 ));
8978
8979 cx.language_registry().add(html_language.clone());
8980 cx.language_registry().add(javascript_language.clone());
8981 cx.update_buffer(|buffer, cx| {
8982 buffer.set_language(Some(html_language), cx);
8983 });
8984
8985 // Toggle comments for empty selections
8986 cx.set_state(
8987 &r#"
8988 <p>A</p>ˇ
8989 <p>B</p>ˇ
8990 <p>C</p>ˇ
8991 "#
8992 .unindent(),
8993 );
8994 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
8995 cx.assert_editor_state(
8996 &r#"
8997 <!-- <p>A</p>ˇ -->
8998 <!-- <p>B</p>ˇ -->
8999 <!-- <p>C</p>ˇ -->
9000 "#
9001 .unindent(),
9002 );
9003 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9004 cx.assert_editor_state(
9005 &r#"
9006 <p>A</p>ˇ
9007 <p>B</p>ˇ
9008 <p>C</p>ˇ
9009 "#
9010 .unindent(),
9011 );
9012
9013 // Toggle comments for mixture of empty and non-empty selections, where
9014 // multiple selections occupy a given line.
9015 cx.set_state(
9016 &r#"
9017 <p>A«</p>
9018 <p>ˇ»B</p>ˇ
9019 <p>C«</p>
9020 <p>ˇ»D</p>ˇ
9021 "#
9022 .unindent(),
9023 );
9024
9025 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9026 cx.assert_editor_state(
9027 &r#"
9028 <!-- <p>A«</p>
9029 <p>ˇ»B</p>ˇ -->
9030 <!-- <p>C«</p>
9031 <p>ˇ»D</p>ˇ -->
9032 "#
9033 .unindent(),
9034 );
9035 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9036 cx.assert_editor_state(
9037 &r#"
9038 <p>A«</p>
9039 <p>ˇ»B</p>ˇ
9040 <p>C«</p>
9041 <p>ˇ»D</p>ˇ
9042 "#
9043 .unindent(),
9044 );
9045
9046 // Toggle comments when different languages are active for different
9047 // selections.
9048 cx.set_state(
9049 &r#"
9050 ˇ<script>
9051 ˇvar x = new Y();
9052 ˇ</script>
9053 "#
9054 .unindent(),
9055 );
9056 cx.executor().run_until_parked();
9057 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9058 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
9059 // Uncommenting and commenting from this position brings in even more wrong artifacts.
9060 cx.assert_editor_state(
9061 &r#"
9062 <!-- ˇ<script> -->
9063 // ˇvar x = new Y();
9064 // ˇ</script>
9065 "#
9066 .unindent(),
9067 );
9068}
9069
9070#[gpui::test]
9071fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
9072 init_test(cx, |_| {});
9073
9074 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9075 let multibuffer = cx.new_model(|cx| {
9076 let mut multibuffer = MultiBuffer::new(ReadWrite);
9077 multibuffer.push_excerpts(
9078 buffer.clone(),
9079 [
9080 ExcerptRange {
9081 context: Point::new(0, 0)..Point::new(0, 4),
9082 primary: None,
9083 },
9084 ExcerptRange {
9085 context: Point::new(1, 0)..Point::new(1, 4),
9086 primary: None,
9087 },
9088 ],
9089 cx,
9090 );
9091 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
9092 multibuffer
9093 });
9094
9095 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9096 view.update(cx, |view, cx| {
9097 assert_eq!(view.text(cx), "aaaa\nbbbb");
9098 view.change_selections(None, cx, |s| {
9099 s.select_ranges([
9100 Point::new(0, 0)..Point::new(0, 0),
9101 Point::new(1, 0)..Point::new(1, 0),
9102 ])
9103 });
9104
9105 view.handle_input("X", cx);
9106 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
9107 assert_eq!(
9108 view.selections.ranges(cx),
9109 [
9110 Point::new(0, 1)..Point::new(0, 1),
9111 Point::new(1, 1)..Point::new(1, 1),
9112 ]
9113 );
9114
9115 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
9116 view.change_selections(None, cx, |s| {
9117 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
9118 });
9119 view.backspace(&Default::default(), cx);
9120 assert_eq!(view.text(cx), "Xa\nbbb");
9121 assert_eq!(
9122 view.selections.ranges(cx),
9123 [Point::new(1, 0)..Point::new(1, 0)]
9124 );
9125
9126 view.change_selections(None, cx, |s| {
9127 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
9128 });
9129 view.backspace(&Default::default(), cx);
9130 assert_eq!(view.text(cx), "X\nbb");
9131 assert_eq!(
9132 view.selections.ranges(cx),
9133 [Point::new(0, 1)..Point::new(0, 1)]
9134 );
9135 });
9136}
9137
9138#[gpui::test]
9139fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
9140 init_test(cx, |_| {});
9141
9142 let markers = vec![('[', ']').into(), ('(', ')').into()];
9143 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
9144 indoc! {"
9145 [aaaa
9146 (bbbb]
9147 cccc)",
9148 },
9149 markers.clone(),
9150 );
9151 let excerpt_ranges = markers.into_iter().map(|marker| {
9152 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9153 ExcerptRange {
9154 context,
9155 primary: None,
9156 }
9157 });
9158 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
9159 let multibuffer = cx.new_model(|cx| {
9160 let mut multibuffer = MultiBuffer::new(ReadWrite);
9161 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9162 multibuffer
9163 });
9164
9165 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9166 view.update(cx, |view, cx| {
9167 let (expected_text, selection_ranges) = marked_text_ranges(
9168 indoc! {"
9169 aaaa
9170 bˇbbb
9171 bˇbbˇb
9172 cccc"
9173 },
9174 true,
9175 );
9176 assert_eq!(view.text(cx), expected_text);
9177 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
9178
9179 view.handle_input("X", cx);
9180
9181 let (expected_text, expected_selections) = marked_text_ranges(
9182 indoc! {"
9183 aaaa
9184 bXˇbbXb
9185 bXˇbbXˇb
9186 cccc"
9187 },
9188 false,
9189 );
9190 assert_eq!(view.text(cx), expected_text);
9191 assert_eq!(view.selections.ranges(cx), expected_selections);
9192
9193 view.newline(&Newline, cx);
9194 let (expected_text, expected_selections) = marked_text_ranges(
9195 indoc! {"
9196 aaaa
9197 bX
9198 ˇbbX
9199 b
9200 bX
9201 ˇbbX
9202 ˇb
9203 cccc"
9204 },
9205 false,
9206 );
9207 assert_eq!(view.text(cx), expected_text);
9208 assert_eq!(view.selections.ranges(cx), expected_selections);
9209 });
9210}
9211
9212#[gpui::test]
9213fn test_refresh_selections(cx: &mut TestAppContext) {
9214 init_test(cx, |_| {});
9215
9216 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9217 let mut excerpt1_id = None;
9218 let multibuffer = cx.new_model(|cx| {
9219 let mut multibuffer = MultiBuffer::new(ReadWrite);
9220 excerpt1_id = multibuffer
9221 .push_excerpts(
9222 buffer.clone(),
9223 [
9224 ExcerptRange {
9225 context: Point::new(0, 0)..Point::new(1, 4),
9226 primary: None,
9227 },
9228 ExcerptRange {
9229 context: Point::new(1, 0)..Point::new(2, 4),
9230 primary: None,
9231 },
9232 ],
9233 cx,
9234 )
9235 .into_iter()
9236 .next();
9237 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9238 multibuffer
9239 });
9240
9241 let editor = cx.add_window(|cx| {
9242 let mut editor = build_editor(multibuffer.clone(), cx);
9243 let snapshot = editor.snapshot(cx);
9244 editor.change_selections(None, cx, |s| {
9245 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
9246 });
9247 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
9248 assert_eq!(
9249 editor.selections.ranges(cx),
9250 [
9251 Point::new(1, 3)..Point::new(1, 3),
9252 Point::new(2, 1)..Point::new(2, 1),
9253 ]
9254 );
9255 editor
9256 });
9257
9258 // Refreshing selections is a no-op when excerpts haven't changed.
9259 _ = editor.update(cx, |editor, cx| {
9260 editor.change_selections(None, cx, |s| s.refresh());
9261 assert_eq!(
9262 editor.selections.ranges(cx),
9263 [
9264 Point::new(1, 3)..Point::new(1, 3),
9265 Point::new(2, 1)..Point::new(2, 1),
9266 ]
9267 );
9268 });
9269
9270 multibuffer.update(cx, |multibuffer, cx| {
9271 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9272 });
9273 _ = editor.update(cx, |editor, cx| {
9274 // Removing an excerpt causes the first selection to become degenerate.
9275 assert_eq!(
9276 editor.selections.ranges(cx),
9277 [
9278 Point::new(0, 0)..Point::new(0, 0),
9279 Point::new(0, 1)..Point::new(0, 1)
9280 ]
9281 );
9282
9283 // Refreshing selections will relocate the first selection to the original buffer
9284 // location.
9285 editor.change_selections(None, cx, |s| s.refresh());
9286 assert_eq!(
9287 editor.selections.ranges(cx),
9288 [
9289 Point::new(0, 1)..Point::new(0, 1),
9290 Point::new(0, 3)..Point::new(0, 3)
9291 ]
9292 );
9293 assert!(editor.selections.pending_anchor().is_some());
9294 });
9295}
9296
9297#[gpui::test]
9298fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
9299 init_test(cx, |_| {});
9300
9301 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9302 let mut excerpt1_id = None;
9303 let multibuffer = cx.new_model(|cx| {
9304 let mut multibuffer = MultiBuffer::new(ReadWrite);
9305 excerpt1_id = multibuffer
9306 .push_excerpts(
9307 buffer.clone(),
9308 [
9309 ExcerptRange {
9310 context: Point::new(0, 0)..Point::new(1, 4),
9311 primary: None,
9312 },
9313 ExcerptRange {
9314 context: Point::new(1, 0)..Point::new(2, 4),
9315 primary: None,
9316 },
9317 ],
9318 cx,
9319 )
9320 .into_iter()
9321 .next();
9322 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9323 multibuffer
9324 });
9325
9326 let editor = cx.add_window(|cx| {
9327 let mut editor = build_editor(multibuffer.clone(), cx);
9328 let snapshot = editor.snapshot(cx);
9329 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
9330 assert_eq!(
9331 editor.selections.ranges(cx),
9332 [Point::new(1, 3)..Point::new(1, 3)]
9333 );
9334 editor
9335 });
9336
9337 multibuffer.update(cx, |multibuffer, cx| {
9338 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9339 });
9340 _ = editor.update(cx, |editor, cx| {
9341 assert_eq!(
9342 editor.selections.ranges(cx),
9343 [Point::new(0, 0)..Point::new(0, 0)]
9344 );
9345
9346 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
9347 editor.change_selections(None, cx, |s| s.refresh());
9348 assert_eq!(
9349 editor.selections.ranges(cx),
9350 [Point::new(0, 3)..Point::new(0, 3)]
9351 );
9352 assert!(editor.selections.pending_anchor().is_some());
9353 });
9354}
9355
9356#[gpui::test]
9357async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
9358 init_test(cx, |_| {});
9359
9360 let language = Arc::new(
9361 Language::new(
9362 LanguageConfig {
9363 brackets: BracketPairConfig {
9364 pairs: vec![
9365 BracketPair {
9366 start: "{".to_string(),
9367 end: "}".to_string(),
9368 close: true,
9369 surround: true,
9370 newline: true,
9371 },
9372 BracketPair {
9373 start: "/* ".to_string(),
9374 end: " */".to_string(),
9375 close: true,
9376 surround: true,
9377 newline: true,
9378 },
9379 ],
9380 ..Default::default()
9381 },
9382 ..Default::default()
9383 },
9384 Some(tree_sitter_rust::LANGUAGE.into()),
9385 )
9386 .with_indents_query("")
9387 .unwrap(),
9388 );
9389
9390 let text = concat!(
9391 "{ }\n", //
9392 " x\n", //
9393 " /* */\n", //
9394 "x\n", //
9395 "{{} }\n", //
9396 );
9397
9398 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
9399 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9400 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9401 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
9402 .await;
9403
9404 view.update(cx, |view, cx| {
9405 view.change_selections(None, cx, |s| {
9406 s.select_display_ranges([
9407 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
9408 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
9409 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
9410 ])
9411 });
9412 view.newline(&Newline, cx);
9413
9414 assert_eq!(
9415 view.buffer().read(cx).read(cx).text(),
9416 concat!(
9417 "{ \n", // Suppress rustfmt
9418 "\n", //
9419 "}\n", //
9420 " x\n", //
9421 " /* \n", //
9422 " \n", //
9423 " */\n", //
9424 "x\n", //
9425 "{{} \n", //
9426 "}\n", //
9427 )
9428 );
9429 });
9430}
9431
9432#[gpui::test]
9433fn test_highlighted_ranges(cx: &mut TestAppContext) {
9434 init_test(cx, |_| {});
9435
9436 let editor = cx.add_window(|cx| {
9437 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
9438 build_editor(buffer.clone(), cx)
9439 });
9440
9441 _ = editor.update(cx, |editor, cx| {
9442 struct Type1;
9443 struct Type2;
9444
9445 let buffer = editor.buffer.read(cx).snapshot(cx);
9446
9447 let anchor_range =
9448 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
9449
9450 editor.highlight_background::<Type1>(
9451 &[
9452 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
9453 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
9454 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
9455 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
9456 ],
9457 |_| Hsla::red(),
9458 cx,
9459 );
9460 editor.highlight_background::<Type2>(
9461 &[
9462 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
9463 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
9464 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
9465 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
9466 ],
9467 |_| Hsla::green(),
9468 cx,
9469 );
9470
9471 let snapshot = editor.snapshot(cx);
9472 let mut highlighted_ranges = editor.background_highlights_in_range(
9473 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
9474 &snapshot,
9475 cx.theme().colors(),
9476 );
9477 // Enforce a consistent ordering based on color without relying on the ordering of the
9478 // highlight's `TypeId` which is non-executor.
9479 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
9480 assert_eq!(
9481 highlighted_ranges,
9482 &[
9483 (
9484 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
9485 Hsla::red(),
9486 ),
9487 (
9488 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9489 Hsla::red(),
9490 ),
9491 (
9492 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9493 Hsla::green(),
9494 ),
9495 (
9496 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9497 Hsla::green(),
9498 ),
9499 ]
9500 );
9501 assert_eq!(
9502 editor.background_highlights_in_range(
9503 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9504 &snapshot,
9505 cx.theme().colors(),
9506 ),
9507 &[(
9508 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9509 Hsla::red(),
9510 )]
9511 );
9512 });
9513}
9514
9515#[gpui::test]
9516async fn test_following(cx: &mut gpui::TestAppContext) {
9517 init_test(cx, |_| {});
9518
9519 let fs = FakeFs::new(cx.executor());
9520 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9521
9522 let buffer = project.update(cx, |project, cx| {
9523 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9524 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9525 });
9526 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9527 let follower = cx.update(|cx| {
9528 cx.open_window(
9529 WindowOptions {
9530 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9531 gpui::Point::new(px(0.), px(0.)),
9532 gpui::Point::new(px(10.), px(80.)),
9533 ))),
9534 ..Default::default()
9535 },
9536 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9537 )
9538 .unwrap()
9539 });
9540
9541 let is_still_following = Rc::new(RefCell::new(true));
9542 let follower_edit_event_count = Rc::new(RefCell::new(0));
9543 let pending_update = Rc::new(RefCell::new(None));
9544 _ = follower.update(cx, {
9545 let update = pending_update.clone();
9546 let is_still_following = is_still_following.clone();
9547 let follower_edit_event_count = follower_edit_event_count.clone();
9548 |_, cx| {
9549 cx.subscribe(
9550 &leader.root_view(cx).unwrap(),
9551 move |_, leader, event, cx| {
9552 leader
9553 .read(cx)
9554 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9555 },
9556 )
9557 .detach();
9558
9559 cx.subscribe(
9560 &follower.root_view(cx).unwrap(),
9561 move |_, _, event: &EditorEvent, _cx| {
9562 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
9563 *is_still_following.borrow_mut() = false;
9564 }
9565
9566 if let EditorEvent::BufferEdited = event {
9567 *follower_edit_event_count.borrow_mut() += 1;
9568 }
9569 },
9570 )
9571 .detach();
9572 }
9573 });
9574
9575 // Update the selections only
9576 _ = leader.update(cx, |leader, cx| {
9577 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9578 });
9579 follower
9580 .update(cx, |follower, cx| {
9581 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9582 })
9583 .unwrap()
9584 .await
9585 .unwrap();
9586 _ = follower.update(cx, |follower, cx| {
9587 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
9588 });
9589 assert!(*is_still_following.borrow());
9590 assert_eq!(*follower_edit_event_count.borrow(), 0);
9591
9592 // Update the scroll position only
9593 _ = leader.update(cx, |leader, cx| {
9594 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9595 });
9596 follower
9597 .update(cx, |follower, cx| {
9598 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9599 })
9600 .unwrap()
9601 .await
9602 .unwrap();
9603 assert_eq!(
9604 follower
9605 .update(cx, |follower, cx| follower.scroll_position(cx))
9606 .unwrap(),
9607 gpui::Point::new(1.5, 3.5)
9608 );
9609 assert!(*is_still_following.borrow());
9610 assert_eq!(*follower_edit_event_count.borrow(), 0);
9611
9612 // Update the selections and scroll position. The follower's scroll position is updated
9613 // via autoscroll, not via the leader's exact scroll position.
9614 _ = leader.update(cx, |leader, cx| {
9615 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
9616 leader.request_autoscroll(Autoscroll::newest(), cx);
9617 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9618 });
9619 follower
9620 .update(cx, |follower, cx| {
9621 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9622 })
9623 .unwrap()
9624 .await
9625 .unwrap();
9626 _ = follower.update(cx, |follower, cx| {
9627 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
9628 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
9629 });
9630 assert!(*is_still_following.borrow());
9631
9632 // Creating a pending selection that precedes another selection
9633 _ = leader.update(cx, |leader, cx| {
9634 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9635 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
9636 });
9637 follower
9638 .update(cx, |follower, cx| {
9639 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9640 })
9641 .unwrap()
9642 .await
9643 .unwrap();
9644 _ = follower.update(cx, |follower, cx| {
9645 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
9646 });
9647 assert!(*is_still_following.borrow());
9648
9649 // Extend the pending selection so that it surrounds another selection
9650 _ = leader.update(cx, |leader, cx| {
9651 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
9652 });
9653 follower
9654 .update(cx, |follower, cx| {
9655 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9656 })
9657 .unwrap()
9658 .await
9659 .unwrap();
9660 _ = follower.update(cx, |follower, cx| {
9661 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
9662 });
9663
9664 // Scrolling locally breaks the follow
9665 _ = follower.update(cx, |follower, cx| {
9666 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
9667 follower.set_scroll_anchor(
9668 ScrollAnchor {
9669 anchor: top_anchor,
9670 offset: gpui::Point::new(0.0, 0.5),
9671 },
9672 cx,
9673 );
9674 });
9675 assert!(!(*is_still_following.borrow()));
9676}
9677
9678#[gpui::test]
9679async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
9680 init_test(cx, |_| {});
9681
9682 let fs = FakeFs::new(cx.executor());
9683 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9684 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9685 let pane = workspace
9686 .update(cx, |workspace, _| workspace.active_pane().clone())
9687 .unwrap();
9688
9689 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9690
9691 let leader = pane.update(cx, |_, cx| {
9692 let multibuffer = cx.new_model(|_| MultiBuffer::new(ReadWrite));
9693 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
9694 });
9695
9696 // Start following the editor when it has no excerpts.
9697 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9698 let follower_1 = cx
9699 .update_window(*workspace.deref(), |_, cx| {
9700 Editor::from_state_proto(
9701 workspace.root_view(cx).unwrap(),
9702 ViewId {
9703 creator: Default::default(),
9704 id: 0,
9705 },
9706 &mut state_message,
9707 cx,
9708 )
9709 })
9710 .unwrap()
9711 .unwrap()
9712 .await
9713 .unwrap();
9714
9715 let update_message = Rc::new(RefCell::new(None));
9716 follower_1.update(cx, {
9717 let update = update_message.clone();
9718 |_, cx| {
9719 cx.subscribe(&leader, move |_, leader, event, cx| {
9720 leader
9721 .read(cx)
9722 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9723 })
9724 .detach();
9725 }
9726 });
9727
9728 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
9729 (
9730 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9731 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9732 )
9733 });
9734
9735 // Insert some excerpts.
9736 leader.update(cx, |leader, cx| {
9737 leader.buffer.update(cx, |multibuffer, cx| {
9738 let excerpt_ids = multibuffer.push_excerpts(
9739 buffer_1.clone(),
9740 [
9741 ExcerptRange {
9742 context: 1..6,
9743 primary: None,
9744 },
9745 ExcerptRange {
9746 context: 12..15,
9747 primary: None,
9748 },
9749 ExcerptRange {
9750 context: 0..3,
9751 primary: None,
9752 },
9753 ],
9754 cx,
9755 );
9756 multibuffer.insert_excerpts_after(
9757 excerpt_ids[0],
9758 buffer_2.clone(),
9759 [
9760 ExcerptRange {
9761 context: 8..12,
9762 primary: None,
9763 },
9764 ExcerptRange {
9765 context: 0..6,
9766 primary: None,
9767 },
9768 ],
9769 cx,
9770 );
9771 });
9772 });
9773
9774 // Apply the update of adding the excerpts.
9775 follower_1
9776 .update(cx, |follower, cx| {
9777 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9778 })
9779 .await
9780 .unwrap();
9781 assert_eq!(
9782 follower_1.update(cx, |editor, cx| editor.text(cx)),
9783 leader.update(cx, |editor, cx| editor.text(cx))
9784 );
9785 update_message.borrow_mut().take();
9786
9787 // Start following separately after it already has excerpts.
9788 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9789 let follower_2 = cx
9790 .update_window(*workspace.deref(), |_, cx| {
9791 Editor::from_state_proto(
9792 workspace.root_view(cx).unwrap().clone(),
9793 ViewId {
9794 creator: Default::default(),
9795 id: 0,
9796 },
9797 &mut state_message,
9798 cx,
9799 )
9800 })
9801 .unwrap()
9802 .unwrap()
9803 .await
9804 .unwrap();
9805 assert_eq!(
9806 follower_2.update(cx, |editor, cx| editor.text(cx)),
9807 leader.update(cx, |editor, cx| editor.text(cx))
9808 );
9809
9810 // Remove some excerpts.
9811 leader.update(cx, |leader, cx| {
9812 leader.buffer.update(cx, |multibuffer, cx| {
9813 let excerpt_ids = multibuffer.excerpt_ids();
9814 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9815 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9816 });
9817 });
9818
9819 // Apply the update of removing the excerpts.
9820 follower_1
9821 .update(cx, |follower, cx| {
9822 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9823 })
9824 .await
9825 .unwrap();
9826 follower_2
9827 .update(cx, |follower, cx| {
9828 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9829 })
9830 .await
9831 .unwrap();
9832 update_message.borrow_mut().take();
9833 assert_eq!(
9834 follower_1.update(cx, |editor, cx| editor.text(cx)),
9835 leader.update(cx, |editor, cx| editor.text(cx))
9836 );
9837}
9838
9839#[gpui::test]
9840async fn go_to_prev_overlapping_diagnostic(
9841 executor: BackgroundExecutor,
9842 cx: &mut gpui::TestAppContext,
9843) {
9844 init_test(cx, |_| {});
9845
9846 let mut cx = EditorTestContext::new(cx).await;
9847 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9848
9849 cx.set_state(indoc! {"
9850 ˇfn func(abc def: i32) -> u32 {
9851 }
9852 "});
9853
9854 cx.update(|cx| {
9855 project.update(cx, |project, cx| {
9856 project
9857 .update_diagnostics(
9858 LanguageServerId(0),
9859 lsp::PublishDiagnosticsParams {
9860 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9861 version: None,
9862 diagnostics: vec![
9863 lsp::Diagnostic {
9864 range: lsp::Range::new(
9865 lsp::Position::new(0, 11),
9866 lsp::Position::new(0, 12),
9867 ),
9868 severity: Some(lsp::DiagnosticSeverity::ERROR),
9869 ..Default::default()
9870 },
9871 lsp::Diagnostic {
9872 range: lsp::Range::new(
9873 lsp::Position::new(0, 12),
9874 lsp::Position::new(0, 15),
9875 ),
9876 severity: Some(lsp::DiagnosticSeverity::ERROR),
9877 ..Default::default()
9878 },
9879 lsp::Diagnostic {
9880 range: lsp::Range::new(
9881 lsp::Position::new(0, 25),
9882 lsp::Position::new(0, 28),
9883 ),
9884 severity: Some(lsp::DiagnosticSeverity::ERROR),
9885 ..Default::default()
9886 },
9887 ],
9888 },
9889 &[],
9890 cx,
9891 )
9892 .unwrap()
9893 });
9894 });
9895
9896 executor.run_until_parked();
9897
9898 cx.update_editor(|editor, cx| {
9899 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9900 });
9901
9902 cx.assert_editor_state(indoc! {"
9903 fn func(abc def: i32) -> ˇu32 {
9904 }
9905 "});
9906
9907 cx.update_editor(|editor, cx| {
9908 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9909 });
9910
9911 cx.assert_editor_state(indoc! {"
9912 fn func(abc ˇdef: i32) -> u32 {
9913 }
9914 "});
9915
9916 cx.update_editor(|editor, cx| {
9917 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9918 });
9919
9920 cx.assert_editor_state(indoc! {"
9921 fn func(abcˇ def: i32) -> u32 {
9922 }
9923 "});
9924
9925 cx.update_editor(|editor, cx| {
9926 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9927 });
9928
9929 cx.assert_editor_state(indoc! {"
9930 fn func(abc def: i32) -> ˇu32 {
9931 }
9932 "});
9933}
9934
9935#[gpui::test]
9936async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
9937 init_test(cx, |_| {});
9938
9939 let mut cx = EditorTestContext::new(cx).await;
9940
9941 cx.set_state(indoc! {"
9942 fn func(abˇc def: i32) -> u32 {
9943 }
9944 "});
9945 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9946
9947 cx.update(|cx| {
9948 project.update(cx, |project, cx| {
9949 project.update_diagnostics(
9950 LanguageServerId(0),
9951 lsp::PublishDiagnosticsParams {
9952 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9953 version: None,
9954 diagnostics: vec![lsp::Diagnostic {
9955 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
9956 severity: Some(lsp::DiagnosticSeverity::ERROR),
9957 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
9958 ..Default::default()
9959 }],
9960 },
9961 &[],
9962 cx,
9963 )
9964 })
9965 }).unwrap();
9966 cx.run_until_parked();
9967 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
9968 cx.run_until_parked();
9969 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
9970}
9971
9972#[gpui::test]
9973async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
9974 init_test(cx, |_| {});
9975
9976 let mut cx = EditorTestContext::new(cx).await;
9977
9978 let diff_base = r#"
9979 use some::mod;
9980
9981 const A: u32 = 42;
9982
9983 fn main() {
9984 println!("hello");
9985
9986 println!("world");
9987 }
9988 "#
9989 .unindent();
9990
9991 // Edits are modified, removed, modified, added
9992 cx.set_state(
9993 &r#"
9994 use some::modified;
9995
9996 ˇ
9997 fn main() {
9998 println!("hello there");
9999
10000 println!("around the");
10001 println!("world");
10002 }
10003 "#
10004 .unindent(),
10005 );
10006
10007 cx.set_diff_base(Some(&diff_base));
10008 executor.run_until_parked();
10009
10010 cx.update_editor(|editor, cx| {
10011 //Wrap around the bottom of the buffer
10012 for _ in 0..3 {
10013 editor.go_to_next_hunk(&GoToHunk, cx);
10014 }
10015 });
10016
10017 cx.assert_editor_state(
10018 &r#"
10019 ˇuse some::modified;
10020
10021
10022 fn main() {
10023 println!("hello there");
10024
10025 println!("around the");
10026 println!("world");
10027 }
10028 "#
10029 .unindent(),
10030 );
10031
10032 cx.update_editor(|editor, cx| {
10033 //Wrap around the top of the buffer
10034 for _ in 0..2 {
10035 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10036 }
10037 });
10038
10039 cx.assert_editor_state(
10040 &r#"
10041 use some::modified;
10042
10043
10044 fn main() {
10045 ˇ println!("hello there");
10046
10047 println!("around the");
10048 println!("world");
10049 }
10050 "#
10051 .unindent(),
10052 );
10053
10054 cx.update_editor(|editor, cx| {
10055 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10056 });
10057
10058 cx.assert_editor_state(
10059 &r#"
10060 use some::modified;
10061
10062 ˇ
10063 fn main() {
10064 println!("hello there");
10065
10066 println!("around the");
10067 println!("world");
10068 }
10069 "#
10070 .unindent(),
10071 );
10072
10073 cx.update_editor(|editor, cx| {
10074 for _ in 0..3 {
10075 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10076 }
10077 });
10078
10079 cx.assert_editor_state(
10080 &r#"
10081 use some::modified;
10082
10083
10084 fn main() {
10085 ˇ println!("hello there");
10086
10087 println!("around the");
10088 println!("world");
10089 }
10090 "#
10091 .unindent(),
10092 );
10093
10094 cx.update_editor(|editor, cx| {
10095 editor.fold(&Fold, cx);
10096
10097 //Make sure that the fold only gets one hunk
10098 for _ in 0..4 {
10099 editor.go_to_next_hunk(&GoToHunk, cx);
10100 }
10101 });
10102
10103 cx.assert_editor_state(
10104 &r#"
10105 ˇuse some::modified;
10106
10107
10108 fn main() {
10109 println!("hello there");
10110
10111 println!("around the");
10112 println!("world");
10113 }
10114 "#
10115 .unindent(),
10116 );
10117}
10118
10119#[test]
10120fn test_split_words() {
10121 fn split(text: &str) -> Vec<&str> {
10122 split_words(text).collect()
10123 }
10124
10125 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
10126 assert_eq!(split("hello_world"), &["hello_", "world"]);
10127 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
10128 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
10129 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
10130 assert_eq!(split("helloworld"), &["helloworld"]);
10131
10132 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
10133}
10134
10135#[gpui::test]
10136async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
10137 init_test(cx, |_| {});
10138
10139 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
10140 let mut assert = |before, after| {
10141 let _state_context = cx.set_state(before);
10142 cx.update_editor(|editor, cx| {
10143 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
10144 });
10145 cx.assert_editor_state(after);
10146 };
10147
10148 // Outside bracket jumps to outside of matching bracket
10149 assert("console.logˇ(var);", "console.log(var)ˇ;");
10150 assert("console.log(var)ˇ;", "console.logˇ(var);");
10151
10152 // Inside bracket jumps to inside of matching bracket
10153 assert("console.log(ˇvar);", "console.log(varˇ);");
10154 assert("console.log(varˇ);", "console.log(ˇvar);");
10155
10156 // When outside a bracket and inside, favor jumping to the inside bracket
10157 assert(
10158 "console.log('foo', [1, 2, 3]ˇ);",
10159 "console.log(ˇ'foo', [1, 2, 3]);",
10160 );
10161 assert(
10162 "console.log(ˇ'foo', [1, 2, 3]);",
10163 "console.log('foo', [1, 2, 3]ˇ);",
10164 );
10165
10166 // Bias forward if two options are equally likely
10167 assert(
10168 "let result = curried_fun()ˇ();",
10169 "let result = curried_fun()()ˇ;",
10170 );
10171
10172 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
10173 assert(
10174 indoc! {"
10175 function test() {
10176 console.log('test')ˇ
10177 }"},
10178 indoc! {"
10179 function test() {
10180 console.logˇ('test')
10181 }"},
10182 );
10183}
10184
10185#[gpui::test]
10186async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
10187 init_test(cx, |_| {});
10188
10189 let fs = FakeFs::new(cx.executor());
10190 fs.insert_tree(
10191 "/a",
10192 json!({
10193 "main.rs": "fn main() { let a = 5; }",
10194 "other.rs": "// Test file",
10195 }),
10196 )
10197 .await;
10198 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10199
10200 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10201 language_registry.add(Arc::new(Language::new(
10202 LanguageConfig {
10203 name: "Rust".into(),
10204 matcher: LanguageMatcher {
10205 path_suffixes: vec!["rs".to_string()],
10206 ..Default::default()
10207 },
10208 brackets: BracketPairConfig {
10209 pairs: vec![BracketPair {
10210 start: "{".to_string(),
10211 end: "}".to_string(),
10212 close: true,
10213 surround: true,
10214 newline: true,
10215 }],
10216 disabled_scopes_by_bracket_ix: Vec::new(),
10217 },
10218 ..Default::default()
10219 },
10220 Some(tree_sitter_rust::LANGUAGE.into()),
10221 )));
10222 let mut fake_servers = language_registry.register_fake_lsp(
10223 "Rust",
10224 FakeLspAdapter {
10225 capabilities: lsp::ServerCapabilities {
10226 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
10227 first_trigger_character: "{".to_string(),
10228 more_trigger_character: None,
10229 }),
10230 ..Default::default()
10231 },
10232 ..Default::default()
10233 },
10234 );
10235
10236 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10237
10238 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10239
10240 let worktree_id = workspace
10241 .update(cx, |workspace, cx| {
10242 workspace.project().update(cx, |project, cx| {
10243 project.worktrees(cx).next().unwrap().read(cx).id()
10244 })
10245 })
10246 .unwrap();
10247
10248 let buffer = project
10249 .update(cx, |project, cx| {
10250 project.open_local_buffer("/a/main.rs", cx)
10251 })
10252 .await
10253 .unwrap();
10254 cx.executor().run_until_parked();
10255 cx.executor().start_waiting();
10256 let fake_server = fake_servers.next().await.unwrap();
10257 let editor_handle = workspace
10258 .update(cx, |workspace, cx| {
10259 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
10260 })
10261 .unwrap()
10262 .await
10263 .unwrap()
10264 .downcast::<Editor>()
10265 .unwrap();
10266
10267 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
10268 assert_eq!(
10269 params.text_document_position.text_document.uri,
10270 lsp::Url::from_file_path("/a/main.rs").unwrap(),
10271 );
10272 assert_eq!(
10273 params.text_document_position.position,
10274 lsp::Position::new(0, 21),
10275 );
10276
10277 Ok(Some(vec![lsp::TextEdit {
10278 new_text: "]".to_string(),
10279 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10280 }]))
10281 });
10282
10283 editor_handle.update(cx, |editor, cx| {
10284 editor.focus(cx);
10285 editor.change_selections(None, cx, |s| {
10286 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
10287 });
10288 editor.handle_input("{", cx);
10289 });
10290
10291 cx.executor().run_until_parked();
10292
10293 buffer.update(cx, |buffer, _| {
10294 assert_eq!(
10295 buffer.text(),
10296 "fn main() { let a = {5}; }",
10297 "No extra braces from on type formatting should appear in the buffer"
10298 )
10299 });
10300}
10301
10302#[gpui::test]
10303async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
10304 init_test(cx, |_| {});
10305
10306 let fs = FakeFs::new(cx.executor());
10307 fs.insert_tree(
10308 "/a",
10309 json!({
10310 "main.rs": "fn main() { let a = 5; }",
10311 "other.rs": "// Test file",
10312 }),
10313 )
10314 .await;
10315
10316 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10317
10318 let server_restarts = Arc::new(AtomicUsize::new(0));
10319 let closure_restarts = Arc::clone(&server_restarts);
10320 let language_server_name = "test language server";
10321 let language_name: LanguageName = "Rust".into();
10322
10323 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10324 language_registry.add(Arc::new(Language::new(
10325 LanguageConfig {
10326 name: language_name.clone(),
10327 matcher: LanguageMatcher {
10328 path_suffixes: vec!["rs".to_string()],
10329 ..Default::default()
10330 },
10331 ..Default::default()
10332 },
10333 Some(tree_sitter_rust::LANGUAGE.into()),
10334 )));
10335 let mut fake_servers = language_registry.register_fake_lsp(
10336 "Rust",
10337 FakeLspAdapter {
10338 name: language_server_name,
10339 initialization_options: Some(json!({
10340 "testOptionValue": true
10341 })),
10342 initializer: Some(Box::new(move |fake_server| {
10343 let task_restarts = Arc::clone(&closure_restarts);
10344 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
10345 task_restarts.fetch_add(1, atomic::Ordering::Release);
10346 futures::future::ready(Ok(()))
10347 });
10348 })),
10349 ..Default::default()
10350 },
10351 );
10352
10353 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10354 let _buffer = project
10355 .update(cx, |project, cx| {
10356 project.open_local_buffer("/a/main.rs", cx)
10357 })
10358 .await
10359 .unwrap();
10360 let _fake_server = fake_servers.next().await.unwrap();
10361 update_test_language_settings(cx, |language_settings| {
10362 language_settings.languages.insert(
10363 language_name.clone(),
10364 LanguageSettingsContent {
10365 tab_size: NonZeroU32::new(8),
10366 ..Default::default()
10367 },
10368 );
10369 });
10370 cx.executor().run_until_parked();
10371 assert_eq!(
10372 server_restarts.load(atomic::Ordering::Acquire),
10373 0,
10374 "Should not restart LSP server on an unrelated change"
10375 );
10376
10377 update_test_project_settings(cx, |project_settings| {
10378 project_settings.lsp.insert(
10379 "Some other server name".into(),
10380 LspSettings {
10381 binary: None,
10382 settings: None,
10383 initialization_options: Some(json!({
10384 "some other init value": false
10385 })),
10386 },
10387 );
10388 });
10389 cx.executor().run_until_parked();
10390 assert_eq!(
10391 server_restarts.load(atomic::Ordering::Acquire),
10392 0,
10393 "Should not restart LSP server on an unrelated LSP settings change"
10394 );
10395
10396 update_test_project_settings(cx, |project_settings| {
10397 project_settings.lsp.insert(
10398 language_server_name.into(),
10399 LspSettings {
10400 binary: None,
10401 settings: None,
10402 initialization_options: Some(json!({
10403 "anotherInitValue": false
10404 })),
10405 },
10406 );
10407 });
10408 cx.executor().run_until_parked();
10409 assert_eq!(
10410 server_restarts.load(atomic::Ordering::Acquire),
10411 1,
10412 "Should restart LSP server on a related LSP settings change"
10413 );
10414
10415 update_test_project_settings(cx, |project_settings| {
10416 project_settings.lsp.insert(
10417 language_server_name.into(),
10418 LspSettings {
10419 binary: None,
10420 settings: None,
10421 initialization_options: Some(json!({
10422 "anotherInitValue": false
10423 })),
10424 },
10425 );
10426 });
10427 cx.executor().run_until_parked();
10428 assert_eq!(
10429 server_restarts.load(atomic::Ordering::Acquire),
10430 1,
10431 "Should not restart LSP server on a related LSP settings change that is the same"
10432 );
10433
10434 update_test_project_settings(cx, |project_settings| {
10435 project_settings.lsp.insert(
10436 language_server_name.into(),
10437 LspSettings {
10438 binary: None,
10439 settings: None,
10440 initialization_options: None,
10441 },
10442 );
10443 });
10444 cx.executor().run_until_parked();
10445 assert_eq!(
10446 server_restarts.load(atomic::Ordering::Acquire),
10447 2,
10448 "Should restart LSP server on another related LSP settings change"
10449 );
10450}
10451
10452#[gpui::test]
10453async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10454 init_test(cx, |_| {});
10455
10456 let mut cx = EditorLspTestContext::new_rust(
10457 lsp::ServerCapabilities {
10458 completion_provider: Some(lsp::CompletionOptions {
10459 trigger_characters: Some(vec![".".to_string()]),
10460 resolve_provider: Some(true),
10461 ..Default::default()
10462 }),
10463 ..Default::default()
10464 },
10465 cx,
10466 )
10467 .await;
10468
10469 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10470 cx.simulate_keystroke(".");
10471 let completion_item = lsp::CompletionItem {
10472 label: "some".into(),
10473 kind: Some(lsp::CompletionItemKind::SNIPPET),
10474 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10475 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10476 kind: lsp::MarkupKind::Markdown,
10477 value: "```rust\nSome(2)\n```".to_string(),
10478 })),
10479 deprecated: Some(false),
10480 sort_text: Some("fffffff2".to_string()),
10481 filter_text: Some("some".to_string()),
10482 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10483 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10484 range: lsp::Range {
10485 start: lsp::Position {
10486 line: 0,
10487 character: 22,
10488 },
10489 end: lsp::Position {
10490 line: 0,
10491 character: 22,
10492 },
10493 },
10494 new_text: "Some(2)".to_string(),
10495 })),
10496 additional_text_edits: Some(vec![lsp::TextEdit {
10497 range: lsp::Range {
10498 start: lsp::Position {
10499 line: 0,
10500 character: 20,
10501 },
10502 end: lsp::Position {
10503 line: 0,
10504 character: 22,
10505 },
10506 },
10507 new_text: "".to_string(),
10508 }]),
10509 ..Default::default()
10510 };
10511
10512 let closure_completion_item = completion_item.clone();
10513 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10514 let task_completion_item = closure_completion_item.clone();
10515 async move {
10516 Ok(Some(lsp::CompletionResponse::Array(vec![
10517 task_completion_item,
10518 ])))
10519 }
10520 });
10521
10522 request.next().await;
10523
10524 cx.condition(|editor, _| editor.context_menu_visible())
10525 .await;
10526 let apply_additional_edits = cx.update_editor(|editor, cx| {
10527 editor
10528 .confirm_completion(&ConfirmCompletion::default(), cx)
10529 .unwrap()
10530 });
10531 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
10532
10533 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10534 let task_completion_item = completion_item.clone();
10535 async move { Ok(task_completion_item) }
10536 })
10537 .next()
10538 .await
10539 .unwrap();
10540 apply_additional_edits.await.unwrap();
10541 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10542}
10543
10544#[gpui::test]
10545async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
10546 init_test(cx, |_| {});
10547
10548 let mut cx = EditorLspTestContext::new_rust(
10549 lsp::ServerCapabilities {
10550 completion_provider: Some(lsp::CompletionOptions {
10551 trigger_characters: Some(vec![".".to_string()]),
10552 resolve_provider: Some(true),
10553 ..Default::default()
10554 }),
10555 ..Default::default()
10556 },
10557 cx,
10558 )
10559 .await;
10560
10561 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10562 cx.simulate_keystroke(".");
10563
10564 let default_commit_characters = vec!["?".to_string()];
10565 let default_data = json!({ "very": "special"});
10566 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
10567 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
10568 let default_edit_range = lsp::Range {
10569 start: lsp::Position {
10570 line: 0,
10571 character: 5,
10572 },
10573 end: lsp::Position {
10574 line: 0,
10575 character: 5,
10576 },
10577 };
10578
10579 let resolve_requests_number = Arc::new(AtomicUsize::new(0));
10580 let expect_first_item = Arc::new(AtomicBool::new(true));
10581 cx.lsp
10582 .server
10583 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
10584 let closure_default_data = default_data.clone();
10585 let closure_resolve_requests_number = resolve_requests_number.clone();
10586 let closure_expect_first_item = expect_first_item.clone();
10587 let closure_default_commit_characters = default_commit_characters.clone();
10588 move |item_to_resolve, _| {
10589 closure_resolve_requests_number.fetch_add(1, atomic::Ordering::Release);
10590 let default_data = closure_default_data.clone();
10591 let default_commit_characters = closure_default_commit_characters.clone();
10592 let expect_first_item = closure_expect_first_item.clone();
10593 async move {
10594 if expect_first_item.load(atomic::Ordering::Acquire) {
10595 assert_eq!(
10596 item_to_resolve.label, "Some(2)",
10597 "Should have selected the first item"
10598 );
10599 assert_eq!(
10600 item_to_resolve.data,
10601 Some(json!({ "very": "special"})),
10602 "First item should bring its own data for resolving"
10603 );
10604 assert_eq!(
10605 item_to_resolve.commit_characters,
10606 Some(default_commit_characters),
10607 "First item had no own commit characters and should inherit the default ones"
10608 );
10609 assert!(
10610 matches!(
10611 item_to_resolve.text_edit,
10612 Some(lsp::CompletionTextEdit::InsertAndReplace { .. })
10613 ),
10614 "First item should bring its own edit range for resolving"
10615 );
10616 assert_eq!(
10617 item_to_resolve.insert_text_format,
10618 Some(default_insert_text_format),
10619 "First item had no own insert text format and should inherit the default one"
10620 );
10621 assert_eq!(
10622 item_to_resolve.insert_text_mode,
10623 Some(lsp::InsertTextMode::ADJUST_INDENTATION),
10624 "First item should bring its own insert text mode for resolving"
10625 );
10626 Ok(item_to_resolve)
10627 } else {
10628 assert_eq!(
10629 item_to_resolve.label, "vec![2]",
10630 "Should have selected the last item"
10631 );
10632 assert_eq!(
10633 item_to_resolve.data,
10634 Some(default_data),
10635 "Last item has no own resolve data and should inherit the default one"
10636 );
10637 assert_eq!(
10638 item_to_resolve.commit_characters,
10639 Some(default_commit_characters),
10640 "Last item had no own commit characters and should inherit the default ones"
10641 );
10642 assert_eq!(
10643 item_to_resolve.text_edit,
10644 Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10645 range: default_edit_range,
10646 new_text: "vec![2]".to_string()
10647 })),
10648 "Last item had no own edit range and should inherit the default one"
10649 );
10650 assert_eq!(
10651 item_to_resolve.insert_text_format,
10652 Some(lsp::InsertTextFormat::PLAIN_TEXT),
10653 "Last item should bring its own insert text format for resolving"
10654 );
10655 assert_eq!(
10656 item_to_resolve.insert_text_mode,
10657 Some(default_insert_text_mode),
10658 "Last item had no own insert text mode and should inherit the default one"
10659 );
10660
10661 Ok(item_to_resolve)
10662 }
10663 }
10664 }
10665 }).detach();
10666
10667 let completion_data = default_data.clone();
10668 let completion_characters = default_commit_characters.clone();
10669 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10670 let default_data = completion_data.clone();
10671 let default_commit_characters = completion_characters.clone();
10672 async move {
10673 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
10674 items: vec![
10675 lsp::CompletionItem {
10676 label: "Some(2)".into(),
10677 insert_text: Some("Some(2)".into()),
10678 data: Some(json!({ "very": "special"})),
10679 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
10680 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
10681 lsp::InsertReplaceEdit {
10682 new_text: "Some(2)".to_string(),
10683 insert: lsp::Range::default(),
10684 replace: lsp::Range::default(),
10685 },
10686 )),
10687 ..lsp::CompletionItem::default()
10688 },
10689 lsp::CompletionItem {
10690 label: "vec![2]".into(),
10691 insert_text: Some("vec![2]".into()),
10692 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
10693 ..lsp::CompletionItem::default()
10694 },
10695 ],
10696 item_defaults: Some(lsp::CompletionListItemDefaults {
10697 data: Some(default_data.clone()),
10698 commit_characters: Some(default_commit_characters.clone()),
10699 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
10700 default_edit_range,
10701 )),
10702 insert_text_format: Some(default_insert_text_format),
10703 insert_text_mode: Some(default_insert_text_mode),
10704 }),
10705 ..lsp::CompletionList::default()
10706 })))
10707 }
10708 })
10709 .next()
10710 .await;
10711
10712 cx.condition(|editor, _| editor.context_menu_visible())
10713 .await;
10714 cx.run_until_parked();
10715 cx.update_editor(|editor, _| {
10716 let menu = editor.context_menu.read();
10717 match menu.as_ref().expect("should have the completions menu") {
10718 ContextMenu::Completions(completions_menu) => {
10719 assert_eq!(
10720 completions_menu
10721 .matches
10722 .iter()
10723 .map(|c| c.string.as_str())
10724 .collect::<Vec<_>>(),
10725 vec!["Some(2)", "vec![2]"]
10726 );
10727 }
10728 ContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
10729 }
10730 });
10731 assert_eq!(
10732 resolve_requests_number.load(atomic::Ordering::Acquire),
10733 1,
10734 "While there are 2 items in the completion list, only 1 resolve request should have been sent, for the selected item"
10735 );
10736
10737 cx.update_editor(|editor, cx| {
10738 editor.context_menu_first(&ContextMenuFirst, cx);
10739 });
10740 cx.run_until_parked();
10741 assert_eq!(
10742 resolve_requests_number.load(atomic::Ordering::Acquire),
10743 2,
10744 "After re-selecting the first item, another resolve request should have been sent"
10745 );
10746
10747 expect_first_item.store(false, atomic::Ordering::Release);
10748 cx.update_editor(|editor, cx| {
10749 editor.context_menu_last(&ContextMenuLast, cx);
10750 });
10751 cx.run_until_parked();
10752 assert_eq!(
10753 resolve_requests_number.load(atomic::Ordering::Acquire),
10754 3,
10755 "After selecting the other item, another resolve request should have been sent"
10756 );
10757}
10758
10759#[gpui::test]
10760async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
10761 init_test(cx, |_| {});
10762
10763 let mut cx = EditorLspTestContext::new(
10764 Language::new(
10765 LanguageConfig {
10766 matcher: LanguageMatcher {
10767 path_suffixes: vec!["jsx".into()],
10768 ..Default::default()
10769 },
10770 overrides: [(
10771 "element".into(),
10772 LanguageConfigOverride {
10773 word_characters: Override::Set(['-'].into_iter().collect()),
10774 ..Default::default()
10775 },
10776 )]
10777 .into_iter()
10778 .collect(),
10779 ..Default::default()
10780 },
10781 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10782 )
10783 .with_override_query("(jsx_self_closing_element) @element")
10784 .unwrap(),
10785 lsp::ServerCapabilities {
10786 completion_provider: Some(lsp::CompletionOptions {
10787 trigger_characters: Some(vec![":".to_string()]),
10788 ..Default::default()
10789 }),
10790 ..Default::default()
10791 },
10792 cx,
10793 )
10794 .await;
10795
10796 cx.lsp
10797 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10798 Ok(Some(lsp::CompletionResponse::Array(vec![
10799 lsp::CompletionItem {
10800 label: "bg-blue".into(),
10801 ..Default::default()
10802 },
10803 lsp::CompletionItem {
10804 label: "bg-red".into(),
10805 ..Default::default()
10806 },
10807 lsp::CompletionItem {
10808 label: "bg-yellow".into(),
10809 ..Default::default()
10810 },
10811 ])))
10812 });
10813
10814 cx.set_state(r#"<p class="bgˇ" />"#);
10815
10816 // Trigger completion when typing a dash, because the dash is an extra
10817 // word character in the 'element' scope, which contains the cursor.
10818 cx.simulate_keystroke("-");
10819 cx.executor().run_until_parked();
10820 cx.update_editor(|editor, _| {
10821 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10822 assert_eq!(
10823 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10824 &["bg-red", "bg-blue", "bg-yellow"]
10825 );
10826 } else {
10827 panic!("expected completion menu to be open");
10828 }
10829 });
10830
10831 cx.simulate_keystroke("l");
10832 cx.executor().run_until_parked();
10833 cx.update_editor(|editor, _| {
10834 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10835 assert_eq!(
10836 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10837 &["bg-blue", "bg-yellow"]
10838 );
10839 } else {
10840 panic!("expected completion menu to be open");
10841 }
10842 });
10843
10844 // When filtering completions, consider the character after the '-' to
10845 // be the start of a subword.
10846 cx.set_state(r#"<p class="yelˇ" />"#);
10847 cx.simulate_keystroke("l");
10848 cx.executor().run_until_parked();
10849 cx.update_editor(|editor, _| {
10850 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10851 assert_eq!(
10852 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10853 &["bg-yellow"]
10854 );
10855 } else {
10856 panic!("expected completion menu to be open");
10857 }
10858 });
10859}
10860
10861#[gpui::test]
10862async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
10863 init_test(cx, |settings| {
10864 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
10865 FormatterList(vec![Formatter::Prettier].into()),
10866 ))
10867 });
10868
10869 let fs = FakeFs::new(cx.executor());
10870 fs.insert_file("/file.ts", Default::default()).await;
10871
10872 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
10873 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10874
10875 language_registry.add(Arc::new(Language::new(
10876 LanguageConfig {
10877 name: "TypeScript".into(),
10878 matcher: LanguageMatcher {
10879 path_suffixes: vec!["ts".to_string()],
10880 ..Default::default()
10881 },
10882 ..Default::default()
10883 },
10884 Some(tree_sitter_rust::LANGUAGE.into()),
10885 )));
10886 update_test_language_settings(cx, |settings| {
10887 settings.defaults.prettier = Some(PrettierSettings {
10888 allowed: true,
10889 ..PrettierSettings::default()
10890 });
10891 });
10892
10893 let test_plugin = "test_plugin";
10894 let _ = language_registry.register_fake_lsp(
10895 "TypeScript",
10896 FakeLspAdapter {
10897 prettier_plugins: vec![test_plugin],
10898 ..Default::default()
10899 },
10900 );
10901
10902 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
10903 let buffer = project
10904 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
10905 .await
10906 .unwrap();
10907
10908 let buffer_text = "one\ntwo\nthree\n";
10909 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
10910 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
10911 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
10912
10913 editor
10914 .update(cx, |editor, cx| {
10915 editor.perform_format(
10916 project.clone(),
10917 FormatTrigger::Manual,
10918 FormatTarget::Buffer,
10919 cx,
10920 )
10921 })
10922 .unwrap()
10923 .await;
10924 assert_eq!(
10925 editor.update(cx, |editor, cx| editor.text(cx)),
10926 buffer_text.to_string() + prettier_format_suffix,
10927 "Test prettier formatting was not applied to the original buffer text",
10928 );
10929
10930 update_test_language_settings(cx, |settings| {
10931 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
10932 });
10933 let format = editor.update(cx, |editor, cx| {
10934 editor.perform_format(
10935 project.clone(),
10936 FormatTrigger::Manual,
10937 FormatTarget::Buffer,
10938 cx,
10939 )
10940 });
10941 format.await.unwrap();
10942 assert_eq!(
10943 editor.update(cx, |editor, cx| editor.text(cx)),
10944 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
10945 "Autoformatting (via test prettier) was not applied to the original buffer text",
10946 );
10947}
10948
10949#[gpui::test]
10950async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
10951 init_test(cx, |_| {});
10952 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10953 let base_text = indoc! {r#"struct Row;
10954struct Row1;
10955struct Row2;
10956
10957struct Row4;
10958struct Row5;
10959struct Row6;
10960
10961struct Row8;
10962struct Row9;
10963struct Row10;"#};
10964
10965 // When addition hunks are not adjacent to carets, no hunk revert is performed
10966 assert_hunk_revert(
10967 indoc! {r#"struct Row;
10968 struct Row1;
10969 struct Row1.1;
10970 struct Row1.2;
10971 struct Row2;ˇ
10972
10973 struct Row4;
10974 struct Row5;
10975 struct Row6;
10976
10977 struct Row8;
10978 ˇstruct Row9;
10979 struct Row9.1;
10980 struct Row9.2;
10981 struct Row9.3;
10982 struct Row10;"#},
10983 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10984 indoc! {r#"struct Row;
10985 struct Row1;
10986 struct Row1.1;
10987 struct Row1.2;
10988 struct Row2;ˇ
10989
10990 struct Row4;
10991 struct Row5;
10992 struct Row6;
10993
10994 struct Row8;
10995 ˇstruct Row9;
10996 struct Row9.1;
10997 struct Row9.2;
10998 struct Row9.3;
10999 struct Row10;"#},
11000 base_text,
11001 &mut cx,
11002 );
11003 // Same for selections
11004 assert_hunk_revert(
11005 indoc! {r#"struct Row;
11006 struct Row1;
11007 struct Row2;
11008 struct Row2.1;
11009 struct Row2.2;
11010 «ˇ
11011 struct Row4;
11012 struct» Row5;
11013 «struct Row6;
11014 ˇ»
11015 struct Row9.1;
11016 struct Row9.2;
11017 struct Row9.3;
11018 struct Row8;
11019 struct Row9;
11020 struct Row10;"#},
11021 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11022 indoc! {r#"struct Row;
11023 struct Row1;
11024 struct Row2;
11025 struct Row2.1;
11026 struct Row2.2;
11027 «ˇ
11028 struct Row4;
11029 struct» Row5;
11030 «struct Row6;
11031 ˇ»
11032 struct Row9.1;
11033 struct Row9.2;
11034 struct Row9.3;
11035 struct Row8;
11036 struct Row9;
11037 struct Row10;"#},
11038 base_text,
11039 &mut cx,
11040 );
11041
11042 // When carets and selections intersect the addition hunks, those are reverted.
11043 // Adjacent carets got merged.
11044 assert_hunk_revert(
11045 indoc! {r#"struct Row;
11046 ˇ// something on the top
11047 struct Row1;
11048 struct Row2;
11049 struct Roˇw3.1;
11050 struct Row2.2;
11051 struct Row2.3;ˇ
11052
11053 struct Row4;
11054 struct ˇRow5.1;
11055 struct Row5.2;
11056 struct «Rowˇ»5.3;
11057 struct Row5;
11058 struct Row6;
11059 ˇ
11060 struct Row9.1;
11061 struct «Rowˇ»9.2;
11062 struct «ˇRow»9.3;
11063 struct Row8;
11064 struct Row9;
11065 «ˇ// something on bottom»
11066 struct Row10;"#},
11067 vec![
11068 DiffHunkStatus::Added,
11069 DiffHunkStatus::Added,
11070 DiffHunkStatus::Added,
11071 DiffHunkStatus::Added,
11072 DiffHunkStatus::Added,
11073 ],
11074 indoc! {r#"struct Row;
11075 ˇstruct Row1;
11076 struct Row2;
11077 ˇ
11078 struct Row4;
11079 ˇstruct Row5;
11080 struct Row6;
11081 ˇ
11082 ˇstruct Row8;
11083 struct Row9;
11084 ˇstruct Row10;"#},
11085 base_text,
11086 &mut cx,
11087 );
11088}
11089
11090#[gpui::test]
11091async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
11092 init_test(cx, |_| {});
11093 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11094 let base_text = indoc! {r#"struct Row;
11095struct Row1;
11096struct Row2;
11097
11098struct Row4;
11099struct Row5;
11100struct Row6;
11101
11102struct Row8;
11103struct Row9;
11104struct Row10;"#};
11105
11106 // Modification hunks behave the same as the addition ones.
11107 assert_hunk_revert(
11108 indoc! {r#"struct Row;
11109 struct Row1;
11110 struct Row33;
11111 ˇ
11112 struct Row4;
11113 struct Row5;
11114 struct Row6;
11115 ˇ
11116 struct Row99;
11117 struct Row9;
11118 struct Row10;"#},
11119 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11120 indoc! {r#"struct Row;
11121 struct Row1;
11122 struct Row33;
11123 ˇ
11124 struct Row4;
11125 struct Row5;
11126 struct Row6;
11127 ˇ
11128 struct Row99;
11129 struct Row9;
11130 struct Row10;"#},
11131 base_text,
11132 &mut cx,
11133 );
11134 assert_hunk_revert(
11135 indoc! {r#"struct Row;
11136 struct Row1;
11137 struct Row33;
11138 «ˇ
11139 struct Row4;
11140 struct» Row5;
11141 «struct Row6;
11142 ˇ»
11143 struct Row99;
11144 struct Row9;
11145 struct Row10;"#},
11146 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11147 indoc! {r#"struct Row;
11148 struct Row1;
11149 struct Row33;
11150 «ˇ
11151 struct Row4;
11152 struct» Row5;
11153 «struct Row6;
11154 ˇ»
11155 struct Row99;
11156 struct Row9;
11157 struct Row10;"#},
11158 base_text,
11159 &mut cx,
11160 );
11161
11162 assert_hunk_revert(
11163 indoc! {r#"ˇstruct Row1.1;
11164 struct Row1;
11165 «ˇstr»uct Row22;
11166
11167 struct ˇRow44;
11168 struct Row5;
11169 struct «Rˇ»ow66;ˇ
11170
11171 «struˇ»ct Row88;
11172 struct Row9;
11173 struct Row1011;ˇ"#},
11174 vec![
11175 DiffHunkStatus::Modified,
11176 DiffHunkStatus::Modified,
11177 DiffHunkStatus::Modified,
11178 DiffHunkStatus::Modified,
11179 DiffHunkStatus::Modified,
11180 DiffHunkStatus::Modified,
11181 ],
11182 indoc! {r#"struct Row;
11183 ˇstruct Row1;
11184 struct Row2;
11185 ˇ
11186 struct Row4;
11187 ˇstruct Row5;
11188 struct Row6;
11189 ˇ
11190 struct Row8;
11191 ˇstruct Row9;
11192 struct Row10;ˇ"#},
11193 base_text,
11194 &mut cx,
11195 );
11196}
11197
11198#[gpui::test]
11199async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
11200 init_test(cx, |_| {});
11201 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11202 let base_text = indoc! {r#"struct Row;
11203struct Row1;
11204struct Row2;
11205
11206struct Row4;
11207struct Row5;
11208struct Row6;
11209
11210struct Row8;
11211struct Row9;
11212struct Row10;"#};
11213
11214 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
11215 assert_hunk_revert(
11216 indoc! {r#"struct Row;
11217 struct Row2;
11218
11219 ˇstruct Row4;
11220 struct Row5;
11221 struct Row6;
11222 ˇ
11223 struct Row8;
11224 struct Row10;"#},
11225 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11226 indoc! {r#"struct Row;
11227 struct Row2;
11228
11229 ˇstruct Row4;
11230 struct Row5;
11231 struct Row6;
11232 ˇ
11233 struct Row8;
11234 struct Row10;"#},
11235 base_text,
11236 &mut cx,
11237 );
11238 assert_hunk_revert(
11239 indoc! {r#"struct Row;
11240 struct Row2;
11241
11242 «ˇstruct Row4;
11243 struct» Row5;
11244 «struct Row6;
11245 ˇ»
11246 struct Row8;
11247 struct Row10;"#},
11248 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11249 indoc! {r#"struct Row;
11250 struct Row2;
11251
11252 «ˇstruct Row4;
11253 struct» Row5;
11254 «struct Row6;
11255 ˇ»
11256 struct Row8;
11257 struct Row10;"#},
11258 base_text,
11259 &mut cx,
11260 );
11261
11262 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
11263 assert_hunk_revert(
11264 indoc! {r#"struct Row;
11265 ˇstruct Row2;
11266
11267 struct Row4;
11268 struct Row5;
11269 struct Row6;
11270
11271 struct Row8;ˇ
11272 struct Row10;"#},
11273 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11274 indoc! {r#"struct Row;
11275 struct Row1;
11276 ˇstruct Row2;
11277
11278 struct Row4;
11279 struct Row5;
11280 struct Row6;
11281
11282 struct Row8;ˇ
11283 struct Row9;
11284 struct Row10;"#},
11285 base_text,
11286 &mut cx,
11287 );
11288 assert_hunk_revert(
11289 indoc! {r#"struct Row;
11290 struct Row2«ˇ;
11291 struct Row4;
11292 struct» Row5;
11293 «struct Row6;
11294
11295 struct Row8;ˇ»
11296 struct Row10;"#},
11297 vec![
11298 DiffHunkStatus::Removed,
11299 DiffHunkStatus::Removed,
11300 DiffHunkStatus::Removed,
11301 ],
11302 indoc! {r#"struct Row;
11303 struct Row1;
11304 struct Row2«ˇ;
11305
11306 struct Row4;
11307 struct» Row5;
11308 «struct Row6;
11309
11310 struct Row8;ˇ»
11311 struct Row9;
11312 struct Row10;"#},
11313 base_text,
11314 &mut cx,
11315 );
11316}
11317
11318#[gpui::test]
11319async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
11320 init_test(cx, |_| {});
11321
11322 let cols = 4;
11323 let rows = 10;
11324 let sample_text_1 = sample_text(rows, cols, 'a');
11325 assert_eq!(
11326 sample_text_1,
11327 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11328 );
11329 let sample_text_2 = sample_text(rows, cols, 'l');
11330 assert_eq!(
11331 sample_text_2,
11332 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11333 );
11334 let sample_text_3 = sample_text(rows, cols, 'v');
11335 assert_eq!(
11336 sample_text_3,
11337 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11338 );
11339
11340 fn diff_every_buffer_row(
11341 buffer: &Model<Buffer>,
11342 sample_text: String,
11343 cols: usize,
11344 cx: &mut gpui::TestAppContext,
11345 ) {
11346 // revert first character in each row, creating one large diff hunk per buffer
11347 let is_first_char = |offset: usize| offset % cols == 0;
11348 buffer.update(cx, |buffer, cx| {
11349 buffer.set_text(
11350 sample_text
11351 .chars()
11352 .enumerate()
11353 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
11354 .collect::<String>(),
11355 cx,
11356 );
11357 buffer.set_diff_base(Some(sample_text), cx);
11358 });
11359 cx.executor().run_until_parked();
11360 }
11361
11362 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11363 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
11364
11365 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11366 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
11367
11368 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11369 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
11370
11371 let multibuffer = cx.new_model(|cx| {
11372 let mut multibuffer = MultiBuffer::new(ReadWrite);
11373 multibuffer.push_excerpts(
11374 buffer_1.clone(),
11375 [
11376 ExcerptRange {
11377 context: Point::new(0, 0)..Point::new(3, 0),
11378 primary: None,
11379 },
11380 ExcerptRange {
11381 context: Point::new(5, 0)..Point::new(7, 0),
11382 primary: None,
11383 },
11384 ExcerptRange {
11385 context: Point::new(9, 0)..Point::new(10, 4),
11386 primary: None,
11387 },
11388 ],
11389 cx,
11390 );
11391 multibuffer.push_excerpts(
11392 buffer_2.clone(),
11393 [
11394 ExcerptRange {
11395 context: Point::new(0, 0)..Point::new(3, 0),
11396 primary: None,
11397 },
11398 ExcerptRange {
11399 context: Point::new(5, 0)..Point::new(7, 0),
11400 primary: None,
11401 },
11402 ExcerptRange {
11403 context: Point::new(9, 0)..Point::new(10, 4),
11404 primary: None,
11405 },
11406 ],
11407 cx,
11408 );
11409 multibuffer.push_excerpts(
11410 buffer_3.clone(),
11411 [
11412 ExcerptRange {
11413 context: Point::new(0, 0)..Point::new(3, 0),
11414 primary: None,
11415 },
11416 ExcerptRange {
11417 context: Point::new(5, 0)..Point::new(7, 0),
11418 primary: None,
11419 },
11420 ExcerptRange {
11421 context: Point::new(9, 0)..Point::new(10, 4),
11422 primary: None,
11423 },
11424 ],
11425 cx,
11426 );
11427 multibuffer
11428 });
11429
11430 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
11431 editor.update(cx, |editor, cx| {
11432 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");
11433 editor.select_all(&SelectAll, cx);
11434 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11435 });
11436 cx.executor().run_until_parked();
11437 // When all ranges are selected, all buffer hunks are reverted.
11438 editor.update(cx, |editor, cx| {
11439 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");
11440 });
11441 buffer_1.update(cx, |buffer, _| {
11442 assert_eq!(buffer.text(), sample_text_1);
11443 });
11444 buffer_2.update(cx, |buffer, _| {
11445 assert_eq!(buffer.text(), sample_text_2);
11446 });
11447 buffer_3.update(cx, |buffer, _| {
11448 assert_eq!(buffer.text(), sample_text_3);
11449 });
11450
11451 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
11452 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
11453 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
11454 editor.update(cx, |editor, cx| {
11455 editor.change_selections(None, cx, |s| {
11456 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
11457 });
11458 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11459 });
11460 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
11461 // but not affect buffer_2 and its related excerpts.
11462 editor.update(cx, |editor, cx| {
11463 assert_eq!(
11464 editor.text(cx),
11465 "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"
11466 );
11467 });
11468 buffer_1.update(cx, |buffer, _| {
11469 assert_eq!(buffer.text(), sample_text_1);
11470 });
11471 buffer_2.update(cx, |buffer, _| {
11472 assert_eq!(
11473 buffer.text(),
11474 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
11475 );
11476 });
11477 buffer_3.update(cx, |buffer, _| {
11478 assert_eq!(
11479 buffer.text(),
11480 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
11481 );
11482 });
11483}
11484
11485#[gpui::test]
11486async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
11487 init_test(cx, |_| {});
11488
11489 let cols = 4;
11490 let rows = 10;
11491 let sample_text_1 = sample_text(rows, cols, 'a');
11492 assert_eq!(
11493 sample_text_1,
11494 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11495 );
11496 let sample_text_2 = sample_text(rows, cols, 'l');
11497 assert_eq!(
11498 sample_text_2,
11499 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11500 );
11501 let sample_text_3 = sample_text(rows, cols, 'v');
11502 assert_eq!(
11503 sample_text_3,
11504 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11505 );
11506
11507 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11508 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11509 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11510
11511 let multi_buffer = cx.new_model(|cx| {
11512 let mut multibuffer = MultiBuffer::new(ReadWrite);
11513 multibuffer.push_excerpts(
11514 buffer_1.clone(),
11515 [
11516 ExcerptRange {
11517 context: Point::new(0, 0)..Point::new(3, 0),
11518 primary: None,
11519 },
11520 ExcerptRange {
11521 context: Point::new(5, 0)..Point::new(7, 0),
11522 primary: None,
11523 },
11524 ExcerptRange {
11525 context: Point::new(9, 0)..Point::new(10, 4),
11526 primary: None,
11527 },
11528 ],
11529 cx,
11530 );
11531 multibuffer.push_excerpts(
11532 buffer_2.clone(),
11533 [
11534 ExcerptRange {
11535 context: Point::new(0, 0)..Point::new(3, 0),
11536 primary: None,
11537 },
11538 ExcerptRange {
11539 context: Point::new(5, 0)..Point::new(7, 0),
11540 primary: None,
11541 },
11542 ExcerptRange {
11543 context: Point::new(9, 0)..Point::new(10, 4),
11544 primary: None,
11545 },
11546 ],
11547 cx,
11548 );
11549 multibuffer.push_excerpts(
11550 buffer_3.clone(),
11551 [
11552 ExcerptRange {
11553 context: Point::new(0, 0)..Point::new(3, 0),
11554 primary: None,
11555 },
11556 ExcerptRange {
11557 context: Point::new(5, 0)..Point::new(7, 0),
11558 primary: None,
11559 },
11560 ExcerptRange {
11561 context: Point::new(9, 0)..Point::new(10, 4),
11562 primary: None,
11563 },
11564 ],
11565 cx,
11566 );
11567 multibuffer
11568 });
11569
11570 let fs = FakeFs::new(cx.executor());
11571 fs.insert_tree(
11572 "/a",
11573 json!({
11574 "main.rs": sample_text_1,
11575 "other.rs": sample_text_2,
11576 "lib.rs": sample_text_3,
11577 }),
11578 )
11579 .await;
11580 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11581 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11582 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11583 let multi_buffer_editor = cx.new_view(|cx| {
11584 Editor::new(
11585 EditorMode::Full,
11586 multi_buffer,
11587 Some(project.clone()),
11588 true,
11589 cx,
11590 )
11591 });
11592 let multibuffer_item_id = workspace
11593 .update(cx, |workspace, cx| {
11594 assert!(
11595 workspace.active_item(cx).is_none(),
11596 "active item should be None before the first item is added"
11597 );
11598 workspace.add_item_to_active_pane(
11599 Box::new(multi_buffer_editor.clone()),
11600 None,
11601 true,
11602 cx,
11603 );
11604 let active_item = workspace
11605 .active_item(cx)
11606 .expect("should have an active item after adding the multi buffer");
11607 assert!(
11608 !active_item.is_singleton(cx),
11609 "A multi buffer was expected to active after adding"
11610 );
11611 active_item.item_id()
11612 })
11613 .unwrap();
11614 cx.executor().run_until_parked();
11615
11616 multi_buffer_editor.update(cx, |editor, cx| {
11617 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
11618 editor.open_excerpts(&OpenExcerpts, cx);
11619 });
11620 cx.executor().run_until_parked();
11621 let first_item_id = workspace
11622 .update(cx, |workspace, cx| {
11623 let active_item = workspace
11624 .active_item(cx)
11625 .expect("should have an active item after navigating into the 1st buffer");
11626 let first_item_id = active_item.item_id();
11627 assert_ne!(
11628 first_item_id, multibuffer_item_id,
11629 "Should navigate into the 1st buffer and activate it"
11630 );
11631 assert!(
11632 active_item.is_singleton(cx),
11633 "New active item should be a singleton buffer"
11634 );
11635 assert_eq!(
11636 active_item
11637 .act_as::<Editor>(cx)
11638 .expect("should have navigated into an editor for the 1st buffer")
11639 .read(cx)
11640 .text(cx),
11641 sample_text_1
11642 );
11643
11644 workspace
11645 .go_back(workspace.active_pane().downgrade(), cx)
11646 .detach_and_log_err(cx);
11647
11648 first_item_id
11649 })
11650 .unwrap();
11651 cx.executor().run_until_parked();
11652 workspace
11653 .update(cx, |workspace, cx| {
11654 let active_item = workspace
11655 .active_item(cx)
11656 .expect("should have an active item after navigating back");
11657 assert_eq!(
11658 active_item.item_id(),
11659 multibuffer_item_id,
11660 "Should navigate back to the multi buffer"
11661 );
11662 assert!(!active_item.is_singleton(cx));
11663 })
11664 .unwrap();
11665
11666 multi_buffer_editor.update(cx, |editor, cx| {
11667 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11668 s.select_ranges(Some(39..40))
11669 });
11670 editor.open_excerpts(&OpenExcerpts, cx);
11671 });
11672 cx.executor().run_until_parked();
11673 let second_item_id = workspace
11674 .update(cx, |workspace, cx| {
11675 let active_item = workspace
11676 .active_item(cx)
11677 .expect("should have an active item after navigating into the 2nd buffer");
11678 let second_item_id = active_item.item_id();
11679 assert_ne!(
11680 second_item_id, multibuffer_item_id,
11681 "Should navigate away from the multibuffer"
11682 );
11683 assert_ne!(
11684 second_item_id, first_item_id,
11685 "Should navigate into the 2nd buffer and activate it"
11686 );
11687 assert!(
11688 active_item.is_singleton(cx),
11689 "New active item should be a singleton buffer"
11690 );
11691 assert_eq!(
11692 active_item
11693 .act_as::<Editor>(cx)
11694 .expect("should have navigated into an editor")
11695 .read(cx)
11696 .text(cx),
11697 sample_text_2
11698 );
11699
11700 workspace
11701 .go_back(workspace.active_pane().downgrade(), cx)
11702 .detach_and_log_err(cx);
11703
11704 second_item_id
11705 })
11706 .unwrap();
11707 cx.executor().run_until_parked();
11708 workspace
11709 .update(cx, |workspace, cx| {
11710 let active_item = workspace
11711 .active_item(cx)
11712 .expect("should have an active item after navigating back from the 2nd buffer");
11713 assert_eq!(
11714 active_item.item_id(),
11715 multibuffer_item_id,
11716 "Should navigate back from the 2nd buffer to the multi buffer"
11717 );
11718 assert!(!active_item.is_singleton(cx));
11719 })
11720 .unwrap();
11721
11722 multi_buffer_editor.update(cx, |editor, cx| {
11723 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11724 s.select_ranges(Some(60..70))
11725 });
11726 editor.open_excerpts(&OpenExcerpts, cx);
11727 });
11728 cx.executor().run_until_parked();
11729 workspace
11730 .update(cx, |workspace, cx| {
11731 let active_item = workspace
11732 .active_item(cx)
11733 .expect("should have an active item after navigating into the 3rd buffer");
11734 let third_item_id = active_item.item_id();
11735 assert_ne!(
11736 third_item_id, multibuffer_item_id,
11737 "Should navigate into the 3rd buffer and activate it"
11738 );
11739 assert_ne!(third_item_id, first_item_id);
11740 assert_ne!(third_item_id, second_item_id);
11741 assert!(
11742 active_item.is_singleton(cx),
11743 "New active item should be a singleton buffer"
11744 );
11745 assert_eq!(
11746 active_item
11747 .act_as::<Editor>(cx)
11748 .expect("should have navigated into an editor")
11749 .read(cx)
11750 .text(cx),
11751 sample_text_3
11752 );
11753
11754 workspace
11755 .go_back(workspace.active_pane().downgrade(), cx)
11756 .detach_and_log_err(cx);
11757 })
11758 .unwrap();
11759 cx.executor().run_until_parked();
11760 workspace
11761 .update(cx, |workspace, cx| {
11762 let active_item = workspace
11763 .active_item(cx)
11764 .expect("should have an active item after navigating back from the 3rd buffer");
11765 assert_eq!(
11766 active_item.item_id(),
11767 multibuffer_item_id,
11768 "Should navigate back from the 3rd buffer to the multi buffer"
11769 );
11770 assert!(!active_item.is_singleton(cx));
11771 })
11772 .unwrap();
11773}
11774
11775#[gpui::test]
11776async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11777 init_test(cx, |_| {});
11778
11779 let mut cx = EditorTestContext::new(cx).await;
11780
11781 let diff_base = r#"
11782 use some::mod;
11783
11784 const A: u32 = 42;
11785
11786 fn main() {
11787 println!("hello");
11788
11789 println!("world");
11790 }
11791 "#
11792 .unindent();
11793
11794 cx.set_state(
11795 &r#"
11796 use some::modified;
11797
11798 ˇ
11799 fn main() {
11800 println!("hello there");
11801
11802 println!("around the");
11803 println!("world");
11804 }
11805 "#
11806 .unindent(),
11807 );
11808
11809 cx.set_diff_base(Some(&diff_base));
11810 executor.run_until_parked();
11811
11812 cx.update_editor(|editor, cx| {
11813 editor.go_to_next_hunk(&GoToHunk, cx);
11814 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11815 });
11816 executor.run_until_parked();
11817 cx.assert_diff_hunks(
11818 r#"
11819 use some::modified;
11820
11821
11822 fn main() {
11823 - println!("hello");
11824 + println!("hello there");
11825
11826 println!("around the");
11827 println!("world");
11828 }
11829 "#
11830 .unindent(),
11831 );
11832
11833 cx.update_editor(|editor, cx| {
11834 for _ in 0..3 {
11835 editor.go_to_next_hunk(&GoToHunk, cx);
11836 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11837 }
11838 });
11839 executor.run_until_parked();
11840 cx.assert_editor_state(
11841 &r#"
11842 use some::modified;
11843
11844 ˇ
11845 fn main() {
11846 println!("hello there");
11847
11848 println!("around the");
11849 println!("world");
11850 }
11851 "#
11852 .unindent(),
11853 );
11854
11855 cx.assert_diff_hunks(
11856 r#"
11857 - use some::mod;
11858 + use some::modified;
11859
11860 - const A: u32 = 42;
11861
11862 fn main() {
11863 - println!("hello");
11864 + println!("hello there");
11865
11866 + println!("around the");
11867 println!("world");
11868 }
11869 "#
11870 .unindent(),
11871 );
11872
11873 cx.update_editor(|editor, cx| {
11874 editor.cancel(&Cancel, cx);
11875 });
11876
11877 cx.assert_diff_hunks(
11878 r#"
11879 use some::modified;
11880
11881
11882 fn main() {
11883 println!("hello there");
11884
11885 println!("around the");
11886 println!("world");
11887 }
11888 "#
11889 .unindent(),
11890 );
11891}
11892
11893#[gpui::test]
11894async fn test_diff_base_change_with_expanded_diff_hunks(
11895 executor: BackgroundExecutor,
11896 cx: &mut gpui::TestAppContext,
11897) {
11898 init_test(cx, |_| {});
11899
11900 let mut cx = EditorTestContext::new(cx).await;
11901
11902 let diff_base = r#"
11903 use some::mod1;
11904 use some::mod2;
11905
11906 const A: u32 = 42;
11907 const B: u32 = 42;
11908 const C: u32 = 42;
11909
11910 fn main() {
11911 println!("hello");
11912
11913 println!("world");
11914 }
11915 "#
11916 .unindent();
11917
11918 cx.set_state(
11919 &r#"
11920 use some::mod2;
11921
11922 const A: u32 = 42;
11923 const C: u32 = 42;
11924
11925 fn main(ˇ) {
11926 //println!("hello");
11927
11928 println!("world");
11929 //
11930 //
11931 }
11932 "#
11933 .unindent(),
11934 );
11935
11936 cx.set_diff_base(Some(&diff_base));
11937 executor.run_until_parked();
11938
11939 cx.update_editor(|editor, cx| {
11940 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11941 });
11942 executor.run_until_parked();
11943 cx.assert_diff_hunks(
11944 r#"
11945 - use some::mod1;
11946 use some::mod2;
11947
11948 const A: u32 = 42;
11949 - const B: u32 = 42;
11950 const C: u32 = 42;
11951
11952 fn main() {
11953 - println!("hello");
11954 + //println!("hello");
11955
11956 println!("world");
11957 + //
11958 + //
11959 }
11960 "#
11961 .unindent(),
11962 );
11963
11964 cx.set_diff_base(Some("new diff base!"));
11965 executor.run_until_parked();
11966 cx.assert_diff_hunks(
11967 r#"
11968 use some::mod2;
11969
11970 const A: u32 = 42;
11971 const C: u32 = 42;
11972
11973 fn main() {
11974 //println!("hello");
11975
11976 println!("world");
11977 //
11978 //
11979 }
11980 "#
11981 .unindent(),
11982 );
11983
11984 cx.update_editor(|editor, cx| {
11985 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11986 });
11987 executor.run_until_parked();
11988 cx.assert_diff_hunks(
11989 r#"
11990 - new diff base!
11991 + use some::mod2;
11992 +
11993 + const A: u32 = 42;
11994 + const C: u32 = 42;
11995 +
11996 + fn main() {
11997 + //println!("hello");
11998 +
11999 + println!("world");
12000 + //
12001 + //
12002 + }
12003 "#
12004 .unindent(),
12005 );
12006}
12007
12008#[gpui::test]
12009async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
12010 init_test(cx, |_| {});
12011
12012 let mut cx = EditorTestContext::new(cx).await;
12013
12014 let diff_base = r#"
12015 use some::mod1;
12016 use some::mod2;
12017
12018 const A: u32 = 42;
12019 const B: u32 = 42;
12020 const C: u32 = 42;
12021
12022 fn main() {
12023 println!("hello");
12024
12025 println!("world");
12026 }
12027
12028 fn another() {
12029 println!("another");
12030 }
12031
12032 fn another2() {
12033 println!("another2");
12034 }
12035 "#
12036 .unindent();
12037
12038 cx.set_state(
12039 &r#"
12040 «use some::mod2;
12041
12042 const A: u32 = 42;
12043 const C: u32 = 42;
12044
12045 fn main() {
12046 //println!("hello");
12047
12048 println!("world");
12049 //
12050 //ˇ»
12051 }
12052
12053 fn another() {
12054 println!("another");
12055 println!("another");
12056 }
12057
12058 println!("another2");
12059 }
12060 "#
12061 .unindent(),
12062 );
12063
12064 cx.set_diff_base(Some(&diff_base));
12065 executor.run_until_parked();
12066
12067 cx.update_editor(|editor, cx| {
12068 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12069 });
12070 executor.run_until_parked();
12071
12072 cx.assert_diff_hunks(
12073 r#"
12074 - use some::mod1;
12075 use some::mod2;
12076
12077 const A: u32 = 42;
12078 - const B: u32 = 42;
12079 const C: u32 = 42;
12080
12081 fn main() {
12082 - println!("hello");
12083 + //println!("hello");
12084
12085 println!("world");
12086 + //
12087 + //
12088 }
12089
12090 fn another() {
12091 println!("another");
12092 + println!("another");
12093 }
12094
12095 - fn another2() {
12096 println!("another2");
12097 }
12098 "#
12099 .unindent(),
12100 );
12101
12102 // Fold across some of the diff hunks. They should no longer appear expanded.
12103 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
12104 cx.executor().run_until_parked();
12105
12106 // Hunks are not shown if their position is within a fold
12107 cx.assert_diff_hunks(
12108 r#"
12109 use some::mod2;
12110
12111 const A: u32 = 42;
12112 const C: u32 = 42;
12113
12114 fn main() {
12115 //println!("hello");
12116
12117 println!("world");
12118 //
12119 //
12120 }
12121
12122 fn another() {
12123 println!("another");
12124 + println!("another");
12125 }
12126
12127 - fn another2() {
12128 println!("another2");
12129 }
12130 "#
12131 .unindent(),
12132 );
12133
12134 cx.update_editor(|editor, cx| {
12135 editor.select_all(&SelectAll, cx);
12136 editor.unfold_lines(&UnfoldLines, cx);
12137 });
12138 cx.executor().run_until_parked();
12139
12140 // The deletions reappear when unfolding.
12141 cx.assert_diff_hunks(
12142 r#"
12143 - use some::mod1;
12144 use some::mod2;
12145
12146 const A: u32 = 42;
12147 - const B: u32 = 42;
12148 const C: u32 = 42;
12149
12150 fn main() {
12151 - println!("hello");
12152 + //println!("hello");
12153
12154 println!("world");
12155 + //
12156 + //
12157 }
12158
12159 fn another() {
12160 println!("another");
12161 + println!("another");
12162 }
12163
12164 - fn another2() {
12165 println!("another2");
12166 }
12167 "#
12168 .unindent(),
12169 );
12170}
12171
12172#[gpui::test]
12173async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
12174 init_test(cx, |_| {});
12175
12176 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12177 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12178 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12179 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12180 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
12181 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
12182
12183 let buffer_1 = cx.new_model(|cx| {
12184 let mut buffer = Buffer::local(file_1_new.to_string(), cx);
12185 buffer.set_diff_base(Some(file_1_old.into()), cx);
12186 buffer
12187 });
12188 let buffer_2 = cx.new_model(|cx| {
12189 let mut buffer = Buffer::local(file_2_new.to_string(), cx);
12190 buffer.set_diff_base(Some(file_2_old.into()), cx);
12191 buffer
12192 });
12193 let buffer_3 = cx.new_model(|cx| {
12194 let mut buffer = Buffer::local(file_3_new.to_string(), cx);
12195 buffer.set_diff_base(Some(file_3_old.into()), cx);
12196 buffer
12197 });
12198
12199 let multi_buffer = cx.new_model(|cx| {
12200 let mut multibuffer = MultiBuffer::new(ReadWrite);
12201 multibuffer.push_excerpts(
12202 buffer_1.clone(),
12203 [
12204 ExcerptRange {
12205 context: Point::new(0, 0)..Point::new(3, 0),
12206 primary: None,
12207 },
12208 ExcerptRange {
12209 context: Point::new(5, 0)..Point::new(7, 0),
12210 primary: None,
12211 },
12212 ExcerptRange {
12213 context: Point::new(9, 0)..Point::new(10, 3),
12214 primary: None,
12215 },
12216 ],
12217 cx,
12218 );
12219 multibuffer.push_excerpts(
12220 buffer_2.clone(),
12221 [
12222 ExcerptRange {
12223 context: Point::new(0, 0)..Point::new(3, 0),
12224 primary: None,
12225 },
12226 ExcerptRange {
12227 context: Point::new(5, 0)..Point::new(7, 0),
12228 primary: None,
12229 },
12230 ExcerptRange {
12231 context: Point::new(9, 0)..Point::new(10, 3),
12232 primary: None,
12233 },
12234 ],
12235 cx,
12236 );
12237 multibuffer.push_excerpts(
12238 buffer_3.clone(),
12239 [
12240 ExcerptRange {
12241 context: Point::new(0, 0)..Point::new(3, 0),
12242 primary: None,
12243 },
12244 ExcerptRange {
12245 context: Point::new(5, 0)..Point::new(7, 0),
12246 primary: None,
12247 },
12248 ExcerptRange {
12249 context: Point::new(9, 0)..Point::new(10, 3),
12250 primary: None,
12251 },
12252 ],
12253 cx,
12254 );
12255 multibuffer
12256 });
12257
12258 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12259 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12260 cx.run_until_parked();
12261
12262 cx.assert_editor_state(
12263 &"
12264 ˇaaa
12265 ccc
12266 ddd
12267
12268 ggg
12269 hhh
12270
12271
12272 lll
12273 mmm
12274 NNN
12275
12276 qqq
12277 rrr
12278
12279 uuu
12280 111
12281 222
12282 333
12283
12284 666
12285 777
12286
12287 000
12288 !!!"
12289 .unindent(),
12290 );
12291
12292 cx.update_editor(|editor, cx| {
12293 editor.select_all(&SelectAll, cx);
12294 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12295 });
12296 cx.executor().run_until_parked();
12297
12298 cx.assert_diff_hunks(
12299 "
12300 aaa
12301 - bbb
12302 ccc
12303 ddd
12304
12305 ggg
12306 hhh
12307
12308
12309 lll
12310 mmm
12311 - nnn
12312 + NNN
12313
12314 qqq
12315 rrr
12316
12317 uuu
12318 111
12319 222
12320 333
12321
12322 + 666
12323 777
12324
12325 000
12326 !!!"
12327 .unindent(),
12328 );
12329}
12330
12331#[gpui::test]
12332async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
12333 init_test(cx, |_| {});
12334
12335 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
12336 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
12337
12338 let buffer = cx.new_model(|cx| {
12339 let mut buffer = Buffer::local(text.to_string(), cx);
12340 buffer.set_diff_base(Some(base.into()), cx);
12341 buffer
12342 });
12343
12344 let multi_buffer = cx.new_model(|cx| {
12345 let mut multibuffer = MultiBuffer::new(ReadWrite);
12346 multibuffer.push_excerpts(
12347 buffer.clone(),
12348 [
12349 ExcerptRange {
12350 context: Point::new(0, 0)..Point::new(2, 0),
12351 primary: None,
12352 },
12353 ExcerptRange {
12354 context: Point::new(5, 0)..Point::new(7, 0),
12355 primary: None,
12356 },
12357 ],
12358 cx,
12359 );
12360 multibuffer
12361 });
12362
12363 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12364 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12365 cx.run_until_parked();
12366
12367 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
12368 cx.executor().run_until_parked();
12369
12370 cx.assert_diff_hunks(
12371 "
12372 aaa
12373 - bbb
12374 + BBB
12375
12376 - ddd
12377 - eee
12378 + EEE
12379 fff
12380 "
12381 .unindent(),
12382 );
12383}
12384
12385#[gpui::test]
12386async fn test_edits_around_expanded_insertion_hunks(
12387 executor: BackgroundExecutor,
12388 cx: &mut gpui::TestAppContext,
12389) {
12390 init_test(cx, |_| {});
12391
12392 let mut cx = EditorTestContext::new(cx).await;
12393
12394 let diff_base = r#"
12395 use some::mod1;
12396 use some::mod2;
12397
12398 const A: u32 = 42;
12399
12400 fn main() {
12401 println!("hello");
12402
12403 println!("world");
12404 }
12405 "#
12406 .unindent();
12407 executor.run_until_parked();
12408 cx.set_state(
12409 &r#"
12410 use some::mod1;
12411 use some::mod2;
12412
12413 const A: u32 = 42;
12414 const B: u32 = 42;
12415 const C: u32 = 42;
12416 ˇ
12417
12418 fn main() {
12419 println!("hello");
12420
12421 println!("world");
12422 }
12423 "#
12424 .unindent(),
12425 );
12426
12427 cx.set_diff_base(Some(&diff_base));
12428 executor.run_until_parked();
12429
12430 cx.update_editor(|editor, cx| {
12431 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12432 });
12433 executor.run_until_parked();
12434
12435 cx.assert_diff_hunks(
12436 r#"
12437 use some::mod1;
12438 use some::mod2;
12439
12440 const A: u32 = 42;
12441 + const B: u32 = 42;
12442 + const C: u32 = 42;
12443 +
12444
12445 fn main() {
12446 println!("hello");
12447
12448 println!("world");
12449 }
12450 "#
12451 .unindent(),
12452 );
12453
12454 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
12455 executor.run_until_parked();
12456
12457 cx.assert_diff_hunks(
12458 r#"
12459 use some::mod1;
12460 use some::mod2;
12461
12462 const A: u32 = 42;
12463 + const B: u32 = 42;
12464 + const C: u32 = 42;
12465 + const D: u32 = 42;
12466 +
12467
12468 fn main() {
12469 println!("hello");
12470
12471 println!("world");
12472 }
12473 "#
12474 .unindent(),
12475 );
12476
12477 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
12478 executor.run_until_parked();
12479
12480 cx.assert_diff_hunks(
12481 r#"
12482 use some::mod1;
12483 use some::mod2;
12484
12485 const A: u32 = 42;
12486 + const B: u32 = 42;
12487 + const C: u32 = 42;
12488 + const D: u32 = 42;
12489 + const E: u32 = 42;
12490 +
12491
12492 fn main() {
12493 println!("hello");
12494
12495 println!("world");
12496 }
12497 "#
12498 .unindent(),
12499 );
12500
12501 cx.update_editor(|editor, cx| {
12502 editor.delete_line(&DeleteLine, cx);
12503 });
12504 executor.run_until_parked();
12505
12506 cx.assert_diff_hunks(
12507 r#"
12508 use some::mod1;
12509 use some::mod2;
12510
12511 const A: u32 = 42;
12512 + const B: u32 = 42;
12513 + const C: u32 = 42;
12514 + const D: u32 = 42;
12515 + const E: u32 = 42;
12516
12517 fn main() {
12518 println!("hello");
12519
12520 println!("world");
12521 }
12522 "#
12523 .unindent(),
12524 );
12525
12526 cx.update_editor(|editor, cx| {
12527 editor.move_up(&MoveUp, cx);
12528 editor.delete_line(&DeleteLine, cx);
12529 editor.move_up(&MoveUp, cx);
12530 editor.delete_line(&DeleteLine, cx);
12531 editor.move_up(&MoveUp, cx);
12532 editor.delete_line(&DeleteLine, cx);
12533 });
12534 executor.run_until_parked();
12535 cx.assert_editor_state(
12536 &r#"
12537 use some::mod1;
12538 use some::mod2;
12539
12540 const A: u32 = 42;
12541 const B: u32 = 42;
12542 ˇ
12543 fn main() {
12544 println!("hello");
12545
12546 println!("world");
12547 }
12548 "#
12549 .unindent(),
12550 );
12551
12552 cx.assert_diff_hunks(
12553 r#"
12554 use some::mod1;
12555 use some::mod2;
12556
12557 const A: u32 = 42;
12558 + const B: u32 = 42;
12559
12560 fn main() {
12561 println!("hello");
12562
12563 println!("world");
12564 }
12565 "#
12566 .unindent(),
12567 );
12568
12569 cx.update_editor(|editor, cx| {
12570 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
12571 editor.delete_line(&DeleteLine, cx);
12572 });
12573 executor.run_until_parked();
12574 cx.assert_diff_hunks(
12575 r#"
12576 use some::mod1;
12577 - use some::mod2;
12578 -
12579 - const A: u32 = 42;
12580
12581 fn main() {
12582 println!("hello");
12583
12584 println!("world");
12585 }
12586 "#
12587 .unindent(),
12588 );
12589}
12590
12591#[gpui::test]
12592async fn test_edits_around_expanded_deletion_hunks(
12593 executor: BackgroundExecutor,
12594 cx: &mut gpui::TestAppContext,
12595) {
12596 init_test(cx, |_| {});
12597
12598 let mut cx = EditorTestContext::new(cx).await;
12599
12600 let diff_base = r#"
12601 use some::mod1;
12602 use some::mod2;
12603
12604 const A: u32 = 42;
12605 const B: u32 = 42;
12606 const C: u32 = 42;
12607
12608
12609 fn main() {
12610 println!("hello");
12611
12612 println!("world");
12613 }
12614 "#
12615 .unindent();
12616 executor.run_until_parked();
12617 cx.set_state(
12618 &r#"
12619 use some::mod1;
12620 use some::mod2;
12621
12622 ˇconst B: u32 = 42;
12623 const C: u32 = 42;
12624
12625
12626 fn main() {
12627 println!("hello");
12628
12629 println!("world");
12630 }
12631 "#
12632 .unindent(),
12633 );
12634
12635 cx.set_diff_base(Some(&diff_base));
12636 executor.run_until_parked();
12637
12638 cx.update_editor(|editor, cx| {
12639 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12640 });
12641 executor.run_until_parked();
12642
12643 cx.assert_diff_hunks(
12644 r#"
12645 use some::mod1;
12646 use some::mod2;
12647
12648 - const A: u32 = 42;
12649 const B: u32 = 42;
12650 const C: u32 = 42;
12651
12652
12653 fn main() {
12654 println!("hello");
12655
12656 println!("world");
12657 }
12658 "#
12659 .unindent(),
12660 );
12661
12662 cx.update_editor(|editor, cx| {
12663 editor.delete_line(&DeleteLine, cx);
12664 });
12665 executor.run_until_parked();
12666 cx.assert_editor_state(
12667 &r#"
12668 use some::mod1;
12669 use some::mod2;
12670
12671 ˇconst C: u32 = 42;
12672
12673
12674 fn main() {
12675 println!("hello");
12676
12677 println!("world");
12678 }
12679 "#
12680 .unindent(),
12681 );
12682 cx.assert_diff_hunks(
12683 r#"
12684 use some::mod1;
12685 use some::mod2;
12686
12687 - const A: u32 = 42;
12688 - const B: u32 = 42;
12689 const C: u32 = 42;
12690
12691
12692 fn main() {
12693 println!("hello");
12694
12695 println!("world");
12696 }
12697 "#
12698 .unindent(),
12699 );
12700
12701 cx.update_editor(|editor, cx| {
12702 editor.delete_line(&DeleteLine, cx);
12703 });
12704 executor.run_until_parked();
12705 cx.assert_editor_state(
12706 &r#"
12707 use some::mod1;
12708 use some::mod2;
12709
12710 ˇ
12711
12712 fn main() {
12713 println!("hello");
12714
12715 println!("world");
12716 }
12717 "#
12718 .unindent(),
12719 );
12720 cx.assert_diff_hunks(
12721 r#"
12722 use some::mod1;
12723 use some::mod2;
12724
12725 - const A: u32 = 42;
12726 - const B: u32 = 42;
12727 - const C: u32 = 42;
12728
12729
12730 fn main() {
12731 println!("hello");
12732
12733 println!("world");
12734 }
12735 "#
12736 .unindent(),
12737 );
12738
12739 cx.update_editor(|editor, cx| {
12740 editor.handle_input("replacement", cx);
12741 });
12742 executor.run_until_parked();
12743 cx.assert_editor_state(
12744 &r#"
12745 use some::mod1;
12746 use some::mod2;
12747
12748 replacementˇ
12749
12750 fn main() {
12751 println!("hello");
12752
12753 println!("world");
12754 }
12755 "#
12756 .unindent(),
12757 );
12758 cx.assert_diff_hunks(
12759 r#"
12760 use some::mod1;
12761 use some::mod2;
12762
12763 - const A: u32 = 42;
12764 - const B: u32 = 42;
12765 - const C: u32 = 42;
12766 -
12767 + replacement
12768
12769 fn main() {
12770 println!("hello");
12771
12772 println!("world");
12773 }
12774 "#
12775 .unindent(),
12776 );
12777}
12778
12779#[gpui::test]
12780async fn test_edit_after_expanded_modification_hunk(
12781 executor: BackgroundExecutor,
12782 cx: &mut gpui::TestAppContext,
12783) {
12784 init_test(cx, |_| {});
12785
12786 let mut cx = EditorTestContext::new(cx).await;
12787
12788 let diff_base = r#"
12789 use some::mod1;
12790 use some::mod2;
12791
12792 const A: u32 = 42;
12793 const B: u32 = 42;
12794 const C: u32 = 42;
12795 const D: u32 = 42;
12796
12797
12798 fn main() {
12799 println!("hello");
12800
12801 println!("world");
12802 }"#
12803 .unindent();
12804
12805 cx.set_state(
12806 &r#"
12807 use some::mod1;
12808 use some::mod2;
12809
12810 const A: u32 = 42;
12811 const B: u32 = 42;
12812 const C: u32 = 43ˇ
12813 const D: u32 = 42;
12814
12815
12816 fn main() {
12817 println!("hello");
12818
12819 println!("world");
12820 }"#
12821 .unindent(),
12822 );
12823
12824 cx.set_diff_base(Some(&diff_base));
12825 executor.run_until_parked();
12826 cx.update_editor(|editor, cx| {
12827 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12828 });
12829 executor.run_until_parked();
12830
12831 cx.assert_diff_hunks(
12832 r#"
12833 use some::mod1;
12834 use some::mod2;
12835
12836 const A: u32 = 42;
12837 const B: u32 = 42;
12838 - const C: u32 = 42;
12839 + const C: u32 = 43
12840 const D: u32 = 42;
12841
12842
12843 fn main() {
12844 println!("hello");
12845
12846 println!("world");
12847 }"#
12848 .unindent(),
12849 );
12850
12851 cx.update_editor(|editor, cx| {
12852 editor.handle_input("\nnew_line\n", cx);
12853 });
12854 executor.run_until_parked();
12855
12856 cx.assert_diff_hunks(
12857 r#"
12858 use some::mod1;
12859 use some::mod2;
12860
12861 const A: u32 = 42;
12862 const B: u32 = 42;
12863 - const C: u32 = 42;
12864 + const C: u32 = 43
12865 + new_line
12866 +
12867 const D: u32 = 42;
12868
12869
12870 fn main() {
12871 println!("hello");
12872
12873 println!("world");
12874 }"#
12875 .unindent(),
12876 );
12877}
12878
12879async fn setup_indent_guides_editor(
12880 text: &str,
12881 cx: &mut gpui::TestAppContext,
12882) -> (BufferId, EditorTestContext) {
12883 init_test(cx, |_| {});
12884
12885 let mut cx = EditorTestContext::new(cx).await;
12886
12887 let buffer_id = cx.update_editor(|editor, cx| {
12888 editor.set_text(text, cx);
12889 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12890
12891 buffer_ids[0]
12892 });
12893
12894 (buffer_id, cx)
12895}
12896
12897fn assert_indent_guides(
12898 range: Range<u32>,
12899 expected: Vec<IndentGuide>,
12900 active_indices: Option<Vec<usize>>,
12901 cx: &mut EditorTestContext,
12902) {
12903 let indent_guides = cx.update_editor(|editor, cx| {
12904 let snapshot = editor.snapshot(cx).display_snapshot;
12905 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12906 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12907 true,
12908 &snapshot,
12909 cx,
12910 );
12911
12912 indent_guides.sort_by(|a, b| {
12913 a.depth.cmp(&b.depth).then(
12914 a.start_row
12915 .cmp(&b.start_row)
12916 .then(a.end_row.cmp(&b.end_row)),
12917 )
12918 });
12919 indent_guides
12920 });
12921
12922 if let Some(expected) = active_indices {
12923 let active_indices = cx.update_editor(|editor, cx| {
12924 let snapshot = editor.snapshot(cx).display_snapshot;
12925 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
12926 });
12927
12928 assert_eq!(
12929 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
12930 expected,
12931 "Active indent guide indices do not match"
12932 );
12933 }
12934
12935 let expected: Vec<_> = expected
12936 .into_iter()
12937 .map(|guide| MultiBufferIndentGuide {
12938 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
12939 buffer: guide,
12940 })
12941 .collect();
12942
12943 assert_eq!(indent_guides, expected, "Indent guides do not match");
12944}
12945
12946fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
12947 IndentGuide {
12948 buffer_id,
12949 start_row,
12950 end_row,
12951 depth,
12952 tab_size: 4,
12953 settings: IndentGuideSettings {
12954 enabled: true,
12955 line_width: 1,
12956 active_line_width: 1,
12957 ..Default::default()
12958 },
12959 }
12960}
12961
12962#[gpui::test]
12963async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12964 let (buffer_id, mut cx) = setup_indent_guides_editor(
12965 &"
12966 fn main() {
12967 let a = 1;
12968 }"
12969 .unindent(),
12970 cx,
12971 )
12972 .await;
12973
12974 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12975}
12976
12977#[gpui::test]
12978async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
12979 let (buffer_id, mut cx) = setup_indent_guides_editor(
12980 &"
12981 fn main() {
12982 let a = 1;
12983 let b = 2;
12984 }"
12985 .unindent(),
12986 cx,
12987 )
12988 .await;
12989
12990 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
12991}
12992
12993#[gpui::test]
12994async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
12995 let (buffer_id, mut cx) = setup_indent_guides_editor(
12996 &"
12997 fn main() {
12998 let a = 1;
12999 if a == 3 {
13000 let b = 2;
13001 } else {
13002 let c = 3;
13003 }
13004 }"
13005 .unindent(),
13006 cx,
13007 )
13008 .await;
13009
13010 assert_indent_guides(
13011 0..8,
13012 vec![
13013 indent_guide(buffer_id, 1, 6, 0),
13014 indent_guide(buffer_id, 3, 3, 1),
13015 indent_guide(buffer_id, 5, 5, 1),
13016 ],
13017 None,
13018 &mut cx,
13019 );
13020}
13021
13022#[gpui::test]
13023async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
13024 let (buffer_id, mut cx) = setup_indent_guides_editor(
13025 &"
13026 fn main() {
13027 let a = 1;
13028 let b = 2;
13029 let c = 3;
13030 }"
13031 .unindent(),
13032 cx,
13033 )
13034 .await;
13035
13036 assert_indent_guides(
13037 0..5,
13038 vec![
13039 indent_guide(buffer_id, 1, 3, 0),
13040 indent_guide(buffer_id, 2, 2, 1),
13041 ],
13042 None,
13043 &mut cx,
13044 );
13045}
13046
13047#[gpui::test]
13048async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
13049 let (buffer_id, mut cx) = setup_indent_guides_editor(
13050 &"
13051 fn main() {
13052 let a = 1;
13053
13054 let c = 3;
13055 }"
13056 .unindent(),
13057 cx,
13058 )
13059 .await;
13060
13061 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
13062}
13063
13064#[gpui::test]
13065async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
13066 let (buffer_id, mut cx) = setup_indent_guides_editor(
13067 &"
13068 fn main() {
13069 let a = 1;
13070
13071 let c = 3;
13072
13073 if a == 3 {
13074 let b = 2;
13075 } else {
13076 let c = 3;
13077 }
13078 }"
13079 .unindent(),
13080 cx,
13081 )
13082 .await;
13083
13084 assert_indent_guides(
13085 0..11,
13086 vec![
13087 indent_guide(buffer_id, 1, 9, 0),
13088 indent_guide(buffer_id, 6, 6, 1),
13089 indent_guide(buffer_id, 8, 8, 1),
13090 ],
13091 None,
13092 &mut cx,
13093 );
13094}
13095
13096#[gpui::test]
13097async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
13098 let (buffer_id, mut cx) = setup_indent_guides_editor(
13099 &"
13100 fn main() {
13101 let a = 1;
13102
13103 let c = 3;
13104
13105 if a == 3 {
13106 let b = 2;
13107 } else {
13108 let c = 3;
13109 }
13110 }"
13111 .unindent(),
13112 cx,
13113 )
13114 .await;
13115
13116 assert_indent_guides(
13117 1..11,
13118 vec![
13119 indent_guide(buffer_id, 1, 9, 0),
13120 indent_guide(buffer_id, 6, 6, 1),
13121 indent_guide(buffer_id, 8, 8, 1),
13122 ],
13123 None,
13124 &mut cx,
13125 );
13126}
13127
13128#[gpui::test]
13129async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
13130 let (buffer_id, mut cx) = setup_indent_guides_editor(
13131 &"
13132 fn main() {
13133 let a = 1;
13134
13135 let c = 3;
13136
13137 if a == 3 {
13138 let b = 2;
13139 } else {
13140 let c = 3;
13141 }
13142 }"
13143 .unindent(),
13144 cx,
13145 )
13146 .await;
13147
13148 assert_indent_guides(
13149 1..10,
13150 vec![
13151 indent_guide(buffer_id, 1, 9, 0),
13152 indent_guide(buffer_id, 6, 6, 1),
13153 indent_guide(buffer_id, 8, 8, 1),
13154 ],
13155 None,
13156 &mut cx,
13157 );
13158}
13159
13160#[gpui::test]
13161async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
13162 let (buffer_id, mut cx) = setup_indent_guides_editor(
13163 &"
13164 block1
13165 block2
13166 block3
13167 block4
13168 block2
13169 block1
13170 block1"
13171 .unindent(),
13172 cx,
13173 )
13174 .await;
13175
13176 assert_indent_guides(
13177 1..10,
13178 vec![
13179 indent_guide(buffer_id, 1, 4, 0),
13180 indent_guide(buffer_id, 2, 3, 1),
13181 indent_guide(buffer_id, 3, 3, 2),
13182 ],
13183 None,
13184 &mut cx,
13185 );
13186}
13187
13188#[gpui::test]
13189async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
13190 let (buffer_id, mut cx) = setup_indent_guides_editor(
13191 &"
13192 block1
13193 block2
13194 block3
13195
13196 block1
13197 block1"
13198 .unindent(),
13199 cx,
13200 )
13201 .await;
13202
13203 assert_indent_guides(
13204 0..6,
13205 vec![
13206 indent_guide(buffer_id, 1, 2, 0),
13207 indent_guide(buffer_id, 2, 2, 1),
13208 ],
13209 None,
13210 &mut cx,
13211 );
13212}
13213
13214#[gpui::test]
13215async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
13216 let (buffer_id, mut cx) = setup_indent_guides_editor(
13217 &"
13218 block1
13219
13220
13221
13222 block2
13223 "
13224 .unindent(),
13225 cx,
13226 )
13227 .await;
13228
13229 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13230}
13231
13232#[gpui::test]
13233async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
13234 let (buffer_id, mut cx) = setup_indent_guides_editor(
13235 &"
13236 def a:
13237 \tb = 3
13238 \tif True:
13239 \t\tc = 4
13240 \t\td = 5
13241 \tprint(b)
13242 "
13243 .unindent(),
13244 cx,
13245 )
13246 .await;
13247
13248 assert_indent_guides(
13249 0..6,
13250 vec![
13251 indent_guide(buffer_id, 1, 6, 0),
13252 indent_guide(buffer_id, 3, 4, 1),
13253 ],
13254 None,
13255 &mut cx,
13256 );
13257}
13258
13259#[gpui::test]
13260async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13261 let (buffer_id, mut cx) = setup_indent_guides_editor(
13262 &"
13263 fn main() {
13264 let a = 1;
13265 }"
13266 .unindent(),
13267 cx,
13268 )
13269 .await;
13270
13271 cx.update_editor(|editor, cx| {
13272 editor.change_selections(None, cx, |s| {
13273 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13274 });
13275 });
13276
13277 assert_indent_guides(
13278 0..3,
13279 vec![indent_guide(buffer_id, 1, 1, 0)],
13280 Some(vec![0]),
13281 &mut cx,
13282 );
13283}
13284
13285#[gpui::test]
13286async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13287 let (buffer_id, mut cx) = setup_indent_guides_editor(
13288 &"
13289 fn main() {
13290 if 1 == 2 {
13291 let a = 1;
13292 }
13293 }"
13294 .unindent(),
13295 cx,
13296 )
13297 .await;
13298
13299 cx.update_editor(|editor, cx| {
13300 editor.change_selections(None, cx, |s| {
13301 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13302 });
13303 });
13304
13305 assert_indent_guides(
13306 0..4,
13307 vec![
13308 indent_guide(buffer_id, 1, 3, 0),
13309 indent_guide(buffer_id, 2, 2, 1),
13310 ],
13311 Some(vec![1]),
13312 &mut cx,
13313 );
13314
13315 cx.update_editor(|editor, cx| {
13316 editor.change_selections(None, cx, |s| {
13317 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13318 });
13319 });
13320
13321 assert_indent_guides(
13322 0..4,
13323 vec![
13324 indent_guide(buffer_id, 1, 3, 0),
13325 indent_guide(buffer_id, 2, 2, 1),
13326 ],
13327 Some(vec![1]),
13328 &mut cx,
13329 );
13330
13331 cx.update_editor(|editor, cx| {
13332 editor.change_selections(None, cx, |s| {
13333 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13334 });
13335 });
13336
13337 assert_indent_guides(
13338 0..4,
13339 vec![
13340 indent_guide(buffer_id, 1, 3, 0),
13341 indent_guide(buffer_id, 2, 2, 1),
13342 ],
13343 Some(vec![0]),
13344 &mut cx,
13345 );
13346}
13347
13348#[gpui::test]
13349async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13350 let (buffer_id, mut cx) = setup_indent_guides_editor(
13351 &"
13352 fn main() {
13353 let a = 1;
13354
13355 let b = 2;
13356 }"
13357 .unindent(),
13358 cx,
13359 )
13360 .await;
13361
13362 cx.update_editor(|editor, cx| {
13363 editor.change_selections(None, cx, |s| {
13364 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13365 });
13366 });
13367
13368 assert_indent_guides(
13369 0..5,
13370 vec![indent_guide(buffer_id, 1, 3, 0)],
13371 Some(vec![0]),
13372 &mut cx,
13373 );
13374}
13375
13376#[gpui::test]
13377async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13378 let (buffer_id, mut cx) = setup_indent_guides_editor(
13379 &"
13380 def m:
13381 a = 1
13382 pass"
13383 .unindent(),
13384 cx,
13385 )
13386 .await;
13387
13388 cx.update_editor(|editor, cx| {
13389 editor.change_selections(None, cx, |s| {
13390 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13391 });
13392 });
13393
13394 assert_indent_guides(
13395 0..3,
13396 vec![indent_guide(buffer_id, 1, 2, 0)],
13397 Some(vec![0]),
13398 &mut cx,
13399 );
13400}
13401
13402#[gpui::test]
13403fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13404 init_test(cx, |_| {});
13405
13406 let editor = cx.add_window(|cx| {
13407 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13408 build_editor(buffer, cx)
13409 });
13410
13411 let render_args = Arc::new(Mutex::new(None));
13412 let snapshot = editor
13413 .update(cx, |editor, cx| {
13414 let snapshot = editor.buffer().read(cx).snapshot(cx);
13415 let range =
13416 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13417
13418 struct RenderArgs {
13419 row: MultiBufferRow,
13420 folded: bool,
13421 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13422 }
13423
13424 let crease = Crease::inline(
13425 range,
13426 FoldPlaceholder::test(),
13427 {
13428 let toggle_callback = render_args.clone();
13429 move |row, folded, callback, _cx| {
13430 *toggle_callback.lock() = Some(RenderArgs {
13431 row,
13432 folded,
13433 callback,
13434 });
13435 div()
13436 }
13437 },
13438 |_row, _folded, _cx| div(),
13439 );
13440
13441 editor.insert_creases(Some(crease), cx);
13442 let snapshot = editor.snapshot(cx);
13443 let _div =
13444 snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13445 snapshot
13446 })
13447 .unwrap();
13448
13449 let render_args = render_args.lock().take().unwrap();
13450 assert_eq!(render_args.row, MultiBufferRow(1));
13451 assert!(!render_args.folded);
13452 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13453
13454 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13455 .unwrap();
13456 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13457 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13458
13459 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13460 .unwrap();
13461 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13462 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13463}
13464
13465#[gpui::test]
13466async fn test_input_text(cx: &mut gpui::TestAppContext) {
13467 init_test(cx, |_| {});
13468 let mut cx = EditorTestContext::new(cx).await;
13469
13470 cx.set_state(
13471 &r#"ˇone
13472 two
13473
13474 three
13475 fourˇ
13476 five
13477
13478 siˇx"#
13479 .unindent(),
13480 );
13481
13482 cx.dispatch_action(HandleInput(String::new()));
13483 cx.assert_editor_state(
13484 &r#"ˇone
13485 two
13486
13487 three
13488 fourˇ
13489 five
13490
13491 siˇx"#
13492 .unindent(),
13493 );
13494
13495 cx.dispatch_action(HandleInput("AAAA".to_string()));
13496 cx.assert_editor_state(
13497 &r#"AAAAˇone
13498 two
13499
13500 three
13501 fourAAAAˇ
13502 five
13503
13504 siAAAAˇx"#
13505 .unindent(),
13506 );
13507}
13508
13509#[gpui::test]
13510async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13511 init_test(cx, |_| {});
13512
13513 let mut cx = EditorTestContext::new(cx).await;
13514 cx.set_state(
13515 r#"let foo = 1;
13516let foo = 2;
13517let foo = 3;
13518let fooˇ = 4;
13519let foo = 5;
13520let foo = 6;
13521let foo = 7;
13522let foo = 8;
13523let foo = 9;
13524let foo = 10;
13525let foo = 11;
13526let foo = 12;
13527let foo = 13;
13528let foo = 14;
13529let foo = 15;"#,
13530 );
13531
13532 cx.update_editor(|e, cx| {
13533 assert_eq!(
13534 e.next_scroll_position,
13535 NextScrollCursorCenterTopBottom::Center,
13536 "Default next scroll direction is center",
13537 );
13538
13539 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13540 assert_eq!(
13541 e.next_scroll_position,
13542 NextScrollCursorCenterTopBottom::Top,
13543 "After center, next scroll direction should be top",
13544 );
13545
13546 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13547 assert_eq!(
13548 e.next_scroll_position,
13549 NextScrollCursorCenterTopBottom::Bottom,
13550 "After top, next scroll direction should be bottom",
13551 );
13552
13553 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13554 assert_eq!(
13555 e.next_scroll_position,
13556 NextScrollCursorCenterTopBottom::Center,
13557 "After bottom, scrolling should start over",
13558 );
13559
13560 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13561 assert_eq!(
13562 e.next_scroll_position,
13563 NextScrollCursorCenterTopBottom::Top,
13564 "Scrolling continues if retriggered fast enough"
13565 );
13566 });
13567
13568 cx.executor()
13569 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13570 cx.executor().run_until_parked();
13571 cx.update_editor(|e, _| {
13572 assert_eq!(
13573 e.next_scroll_position,
13574 NextScrollCursorCenterTopBottom::Center,
13575 "If scrolling is not triggered fast enough, it should reset"
13576 );
13577 });
13578}
13579
13580#[gpui::test]
13581async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13582 init_test(cx, |_| {});
13583 let mut cx = EditorLspTestContext::new_rust(
13584 lsp::ServerCapabilities {
13585 definition_provider: Some(lsp::OneOf::Left(true)),
13586 references_provider: Some(lsp::OneOf::Left(true)),
13587 ..lsp::ServerCapabilities::default()
13588 },
13589 cx,
13590 )
13591 .await;
13592
13593 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13594 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13595 move |params, _| async move {
13596 if empty_go_to_definition {
13597 Ok(None)
13598 } else {
13599 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13600 uri: params.text_document_position_params.text_document.uri,
13601 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13602 })))
13603 }
13604 },
13605 );
13606 let references =
13607 cx.lsp
13608 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13609 Ok(Some(vec![lsp::Location {
13610 uri: params.text_document_position.text_document.uri,
13611 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13612 }]))
13613 });
13614 (go_to_definition, references)
13615 };
13616
13617 cx.set_state(
13618 &r#"fn one() {
13619 let mut a = ˇtwo();
13620 }
13621
13622 fn two() {}"#
13623 .unindent(),
13624 );
13625 set_up_lsp_handlers(false, &mut cx);
13626 let navigated = cx
13627 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13628 .await
13629 .expect("Failed to navigate to definition");
13630 assert_eq!(
13631 navigated,
13632 Navigated::Yes,
13633 "Should have navigated to definition from the GetDefinition response"
13634 );
13635 cx.assert_editor_state(
13636 &r#"fn one() {
13637 let mut a = two();
13638 }
13639
13640 fn «twoˇ»() {}"#
13641 .unindent(),
13642 );
13643
13644 let editors = cx.update_workspace(|workspace, cx| {
13645 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13646 });
13647 cx.update_editor(|_, test_editor_cx| {
13648 assert_eq!(
13649 editors.len(),
13650 1,
13651 "Initially, only one, test, editor should be open in the workspace"
13652 );
13653 assert_eq!(
13654 test_editor_cx.view(),
13655 editors.last().expect("Asserted len is 1")
13656 );
13657 });
13658
13659 set_up_lsp_handlers(true, &mut cx);
13660 let navigated = cx
13661 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13662 .await
13663 .expect("Failed to navigate to lookup references");
13664 assert_eq!(
13665 navigated,
13666 Navigated::Yes,
13667 "Should have navigated to references as a fallback after empty GoToDefinition response"
13668 );
13669 // We should not change the selections in the existing file,
13670 // if opening another milti buffer with the references
13671 cx.assert_editor_state(
13672 &r#"fn one() {
13673 let mut a = two();
13674 }
13675
13676 fn «twoˇ»() {}"#
13677 .unindent(),
13678 );
13679 let editors = cx.update_workspace(|workspace, cx| {
13680 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13681 });
13682 cx.update_editor(|_, test_editor_cx| {
13683 assert_eq!(
13684 editors.len(),
13685 2,
13686 "After falling back to references search, we open a new editor with the results"
13687 );
13688 let references_fallback_text = editors
13689 .into_iter()
13690 .find(|new_editor| new_editor != test_editor_cx.view())
13691 .expect("Should have one non-test editor now")
13692 .read(test_editor_cx)
13693 .text(test_editor_cx);
13694 assert_eq!(
13695 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13696 "Should use the range from the references response and not the GoToDefinition one"
13697 );
13698 });
13699}
13700
13701#[gpui::test]
13702async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
13703 init_test(cx, |_| {});
13704
13705 let language = Arc::new(Language::new(
13706 LanguageConfig::default(),
13707 Some(tree_sitter_rust::LANGUAGE.into()),
13708 ));
13709
13710 let text = r#"
13711 #[cfg(test)]
13712 mod tests() {
13713 #[test]
13714 fn runnable_1() {
13715 let a = 1;
13716 }
13717
13718 #[test]
13719 fn runnable_2() {
13720 let a = 1;
13721 let b = 2;
13722 }
13723 }
13724 "#
13725 .unindent();
13726
13727 let fs = FakeFs::new(cx.executor());
13728 fs.insert_file("/file.rs", Default::default()).await;
13729
13730 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13731 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
13732 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13733 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
13734 let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
13735
13736 let editor = cx.new_view(|cx| {
13737 Editor::new(
13738 EditorMode::Full,
13739 multi_buffer,
13740 Some(project.clone()),
13741 true,
13742 cx,
13743 )
13744 });
13745
13746 editor.update(cx, |editor, cx| {
13747 editor.tasks.insert(
13748 (buffer.read(cx).remote_id(), 3),
13749 RunnableTasks {
13750 templates: vec![],
13751 offset: MultiBufferOffset(43),
13752 column: 0,
13753 extra_variables: HashMap::default(),
13754 context_range: BufferOffset(43)..BufferOffset(85),
13755 },
13756 );
13757 editor.tasks.insert(
13758 (buffer.read(cx).remote_id(), 8),
13759 RunnableTasks {
13760 templates: vec![],
13761 offset: MultiBufferOffset(86),
13762 column: 0,
13763 extra_variables: HashMap::default(),
13764 context_range: BufferOffset(86)..BufferOffset(191),
13765 },
13766 );
13767
13768 // Test finding task when cursor is inside function body
13769 editor.change_selections(None, cx, |s| {
13770 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
13771 });
13772 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13773 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
13774
13775 // Test finding task when cursor is on function name
13776 editor.change_selections(None, cx, |s| {
13777 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
13778 });
13779 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13780 assert_eq!(row, 8, "Should find task when cursor is on function name");
13781 });
13782}
13783
13784fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13785 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13786 point..point
13787}
13788
13789fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13790 let (text, ranges) = marked_text_ranges(marked_text, true);
13791 assert_eq!(view.text(cx), text);
13792 assert_eq!(
13793 view.selections.ranges(cx),
13794 ranges,
13795 "Assert selections are {}",
13796 marked_text
13797 );
13798}
13799
13800pub fn handle_signature_help_request(
13801 cx: &mut EditorLspTestContext,
13802 mocked_response: lsp::SignatureHelp,
13803) -> impl Future<Output = ()> {
13804 let mut request =
13805 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13806 let mocked_response = mocked_response.clone();
13807 async move { Ok(Some(mocked_response)) }
13808 });
13809
13810 async move {
13811 request.next().await;
13812 }
13813}
13814
13815/// Handle completion request passing a marked string specifying where the completion
13816/// should be triggered from using '|' character, what range should be replaced, and what completions
13817/// should be returned using '<' and '>' to delimit the range
13818pub fn handle_completion_request(
13819 cx: &mut EditorLspTestContext,
13820 marked_string: &str,
13821 completions: Vec<&'static str>,
13822 counter: Arc<AtomicUsize>,
13823) -> impl Future<Output = ()> {
13824 let complete_from_marker: TextRangeMarker = '|'.into();
13825 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13826 let (_, mut marked_ranges) = marked_text_ranges_by(
13827 marked_string,
13828 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13829 );
13830
13831 let complete_from_position =
13832 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13833 let replace_range =
13834 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13835
13836 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13837 let completions = completions.clone();
13838 counter.fetch_add(1, atomic::Ordering::Release);
13839 async move {
13840 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13841 assert_eq!(
13842 params.text_document_position.position,
13843 complete_from_position
13844 );
13845 Ok(Some(lsp::CompletionResponse::Array(
13846 completions
13847 .iter()
13848 .map(|completion_text| lsp::CompletionItem {
13849 label: completion_text.to_string(),
13850 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13851 range: replace_range,
13852 new_text: completion_text.to_string(),
13853 })),
13854 ..Default::default()
13855 })
13856 .collect(),
13857 )))
13858 }
13859 });
13860
13861 async move {
13862 request.next().await;
13863 }
13864}
13865
13866fn handle_resolve_completion_request(
13867 cx: &mut EditorLspTestContext,
13868 edits: Option<Vec<(&'static str, &'static str)>>,
13869) -> impl Future<Output = ()> {
13870 let edits = edits.map(|edits| {
13871 edits
13872 .iter()
13873 .map(|(marked_string, new_text)| {
13874 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13875 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13876 lsp::TextEdit::new(replace_range, new_text.to_string())
13877 })
13878 .collect::<Vec<_>>()
13879 });
13880
13881 let mut request =
13882 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13883 let edits = edits.clone();
13884 async move {
13885 Ok(lsp::CompletionItem {
13886 additional_text_edits: edits,
13887 ..Default::default()
13888 })
13889 }
13890 });
13891
13892 async move {
13893 request.next().await;
13894 }
13895}
13896
13897pub(crate) fn update_test_language_settings(
13898 cx: &mut TestAppContext,
13899 f: impl Fn(&mut AllLanguageSettingsContent),
13900) {
13901 cx.update(|cx| {
13902 SettingsStore::update_global(cx, |store, cx| {
13903 store.update_user_settings::<AllLanguageSettings>(cx, f);
13904 });
13905 });
13906}
13907
13908pub(crate) fn update_test_project_settings(
13909 cx: &mut TestAppContext,
13910 f: impl Fn(&mut ProjectSettings),
13911) {
13912 cx.update(|cx| {
13913 SettingsStore::update_global(cx, |store, cx| {
13914 store.update_user_settings::<ProjectSettings>(cx, f);
13915 });
13916 });
13917}
13918
13919pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
13920 cx.update(|cx| {
13921 assets::Assets.load_test_fonts(cx);
13922 let store = SettingsStore::test(cx);
13923 cx.set_global(store);
13924 theme::init(theme::LoadThemes::JustBase, cx);
13925 release_channel::init(SemanticVersion::default(), cx);
13926 client::init_settings(cx);
13927 language::init(cx);
13928 Project::init_settings(cx);
13929 workspace::init_settings(cx);
13930 crate::init(cx);
13931 });
13932
13933 update_test_language_settings(cx, f);
13934}
13935
13936pub(crate) fn rust_lang() -> Arc<Language> {
13937 Arc::new(Language::new(
13938 LanguageConfig {
13939 name: "Rust".into(),
13940 matcher: LanguageMatcher {
13941 path_suffixes: vec!["rs".to_string()],
13942 ..Default::default()
13943 },
13944 ..Default::default()
13945 },
13946 Some(tree_sitter_rust::LANGUAGE.into()),
13947 ))
13948}
13949
13950#[track_caller]
13951fn assert_hunk_revert(
13952 not_reverted_text_with_selections: &str,
13953 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
13954 expected_reverted_text_with_selections: &str,
13955 base_text: &str,
13956 cx: &mut EditorLspTestContext,
13957) {
13958 cx.set_state(not_reverted_text_with_selections);
13959 cx.update_editor(|editor, cx| {
13960 editor
13961 .buffer()
13962 .read(cx)
13963 .as_singleton()
13964 .unwrap()
13965 .update(cx, |buffer, cx| {
13966 buffer.set_diff_base(Some(base_text.into()), cx);
13967 });
13968 });
13969 cx.executor().run_until_parked();
13970
13971 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
13972 let snapshot = editor.buffer().read(cx).snapshot(cx);
13973 let reverted_hunk_statuses = snapshot
13974 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
13975 .map(|hunk| hunk_status(&hunk))
13976 .collect::<Vec<_>>();
13977
13978 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
13979 reverted_hunk_statuses
13980 });
13981 cx.executor().run_until_parked();
13982 cx.assert_editor_state(expected_reverted_text_with_selections);
13983 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
13984}