1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
6 editor_test_context::EditorTestContext, select_ranges,
7 },
8 JoinLines,
9};
10use futures::StreamExt;
11use gpui::{
12 div, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds,
13 WindowOptions,
14};
15use indoc::indoc;
16use language::{
17 language_settings::{
18 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
19 },
20 BracketPairConfig,
21 Capability::ReadWrite,
22 FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
23 LanguageName, Override, ParsedMarkdown, Point,
24};
25use language_settings::{Formatter, FormatterList, IndentGuideSettings};
26use multi_buffer::MultiBufferIndentGuide;
27use parking_lot::Mutex;
28use project::FakeFs;
29use project::{
30 lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
31 project_settings::{LspSettings, ProjectSettings},
32};
33use serde_json::{self, json};
34use std::sync::atomic;
35use std::sync::atomic::AtomicUsize;
36use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
37use unindent::Unindent;
38use util::{
39 assert_set_eq,
40 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
41};
42use workspace::{
43 item::{FollowEvent, FollowableItem, Item, ItemHandle},
44 NavigationEntry, ViewId,
45};
46
47#[gpui::test]
48fn test_edit_events(cx: &mut TestAppContext) {
49 init_test(cx, |_| {});
50
51 let buffer = cx.new_model(|cx| {
52 let mut buffer = language::Buffer::local("123456", cx);
53 buffer.set_group_interval(Duration::from_secs(1));
54 buffer
55 });
56
57 let events = Rc::new(RefCell::new(Vec::new()));
58 let editor1 = cx.add_window({
59 let events = events.clone();
60 |cx| {
61 let view = cx.view().clone();
62 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
63 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
64 EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
65 _ => {}
66 })
67 .detach();
68 Editor::for_buffer(buffer.clone(), None, cx)
69 }
70 });
71
72 let editor2 = cx.add_window({
73 let events = events.clone();
74 |cx| {
75 cx.subscribe(
76 &cx.view().clone(),
77 move |_, _, event: &EditorEvent, _| match event {
78 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
79 EditorEvent::BufferEdited => {
80 events.borrow_mut().push(("editor2", "buffer edited"))
81 }
82 _ => {}
83 },
84 )
85 .detach();
86 Editor::for_buffer(buffer.clone(), None, cx)
87 }
88 });
89
90 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
91
92 // Mutating editor 1 will emit an `Edited` event only for that editor.
93 _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
94 assert_eq!(
95 mem::take(&mut *events.borrow_mut()),
96 [
97 ("editor1", "edited"),
98 ("editor1", "buffer edited"),
99 ("editor2", "buffer edited"),
100 ]
101 );
102
103 // Mutating editor 2 will emit an `Edited` event only for that editor.
104 _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
105 assert_eq!(
106 mem::take(&mut *events.borrow_mut()),
107 [
108 ("editor2", "edited"),
109 ("editor1", "buffer edited"),
110 ("editor2", "buffer edited"),
111 ]
112 );
113
114 // Undoing on editor 1 will emit an `Edited` event only for that editor.
115 _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
116 assert_eq!(
117 mem::take(&mut *events.borrow_mut()),
118 [
119 ("editor1", "edited"),
120 ("editor1", "buffer edited"),
121 ("editor2", "buffer edited"),
122 ]
123 );
124
125 // Redoing on editor 1 will emit an `Edited` event only for that editor.
126 _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
127 assert_eq!(
128 mem::take(&mut *events.borrow_mut()),
129 [
130 ("editor1", "edited"),
131 ("editor1", "buffer edited"),
132 ("editor2", "buffer edited"),
133 ]
134 );
135
136 // Undoing on editor 2 will emit an `Edited` event only for that editor.
137 _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
138 assert_eq!(
139 mem::take(&mut *events.borrow_mut()),
140 [
141 ("editor2", "edited"),
142 ("editor1", "buffer edited"),
143 ("editor2", "buffer edited"),
144 ]
145 );
146
147 // Redoing on editor 2 will emit an `Edited` event only for that editor.
148 _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
149 assert_eq!(
150 mem::take(&mut *events.borrow_mut()),
151 [
152 ("editor2", "edited"),
153 ("editor1", "buffer edited"),
154 ("editor2", "buffer edited"),
155 ]
156 );
157
158 // No event is emitted when the mutation is a no-op.
159 _ = editor2.update(cx, |editor, cx| {
160 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
161
162 editor.backspace(&Backspace, cx);
163 });
164 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
165}
166
167#[gpui::test]
168fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
169 init_test(cx, |_| {});
170
171 let mut now = Instant::now();
172 let group_interval = Duration::from_millis(1);
173 let buffer = cx.new_model(|cx| {
174 let mut buf = language::Buffer::local("123456", cx);
175 buf.set_group_interval(group_interval);
176 buf
177 });
178 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
179 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
180
181 _ = editor.update(cx, |editor, cx| {
182 editor.start_transaction_at(now, cx);
183 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
184
185 editor.insert("cd", cx);
186 editor.end_transaction_at(now, cx);
187 assert_eq!(editor.text(cx), "12cd56");
188 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
189
190 editor.start_transaction_at(now, cx);
191 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
192 editor.insert("e", cx);
193 editor.end_transaction_at(now, cx);
194 assert_eq!(editor.text(cx), "12cde6");
195 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
196
197 now += group_interval + Duration::from_millis(1);
198 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
199
200 // Simulate an edit in another editor
201 buffer.update(cx, |buffer, cx| {
202 buffer.start_transaction_at(now, cx);
203 buffer.edit([(0..1, "a")], None, cx);
204 buffer.edit([(1..1, "b")], None, cx);
205 buffer.end_transaction_at(now, cx);
206 });
207
208 assert_eq!(editor.text(cx), "ab2cde6");
209 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
210
211 // Last transaction happened past the group interval in a different editor.
212 // Undo it individually and don't restore selections.
213 editor.undo(&Undo, cx);
214 assert_eq!(editor.text(cx), "12cde6");
215 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
216
217 // First two transactions happened within the group interval in this editor.
218 // Undo them together and restore selections.
219 editor.undo(&Undo, cx);
220 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
221 assert_eq!(editor.text(cx), "123456");
222 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
223
224 // Redo the first two transactions together.
225 editor.redo(&Redo, cx);
226 assert_eq!(editor.text(cx), "12cde6");
227 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
228
229 // Redo the last transaction on its own.
230 editor.redo(&Redo, cx);
231 assert_eq!(editor.text(cx), "ab2cde6");
232 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
233
234 // Test empty transactions.
235 editor.start_transaction_at(now, cx);
236 editor.end_transaction_at(now, cx);
237 editor.undo(&Undo, cx);
238 assert_eq!(editor.text(cx), "12cde6");
239 });
240}
241
242#[gpui::test]
243fn test_ime_composition(cx: &mut TestAppContext) {
244 init_test(cx, |_| {});
245
246 let buffer = cx.new_model(|cx| {
247 let mut buffer = language::Buffer::local("abcde", cx);
248 // Ensure automatic grouping doesn't occur.
249 buffer.set_group_interval(Duration::ZERO);
250 buffer
251 });
252
253 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
254 cx.add_window(|cx| {
255 let mut editor = build_editor(buffer.clone(), cx);
256
257 // Start a new IME composition.
258 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
259 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
260 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
261 assert_eq!(editor.text(cx), "äbcde");
262 assert_eq!(
263 editor.marked_text_ranges(cx),
264 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
265 );
266
267 // Finalize IME composition.
268 editor.replace_text_in_range(None, "ā", cx);
269 assert_eq!(editor.text(cx), "ābcde");
270 assert_eq!(editor.marked_text_ranges(cx), None);
271
272 // IME composition edits are grouped and are undone/redone at once.
273 editor.undo(&Default::default(), cx);
274 assert_eq!(editor.text(cx), "abcde");
275 assert_eq!(editor.marked_text_ranges(cx), None);
276 editor.redo(&Default::default(), cx);
277 assert_eq!(editor.text(cx), "ābcde");
278 assert_eq!(editor.marked_text_ranges(cx), None);
279
280 // Start a new IME composition.
281 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
282 assert_eq!(
283 editor.marked_text_ranges(cx),
284 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
285 );
286
287 // Undoing during an IME composition cancels it.
288 editor.undo(&Default::default(), cx);
289 assert_eq!(editor.text(cx), "ābcde");
290 assert_eq!(editor.marked_text_ranges(cx), None);
291
292 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
293 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
294 assert_eq!(editor.text(cx), "ābcdè");
295 assert_eq!(
296 editor.marked_text_ranges(cx),
297 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
298 );
299
300 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
301 editor.replace_text_in_range(Some(4..999), "ę", cx);
302 assert_eq!(editor.text(cx), "ābcdę");
303 assert_eq!(editor.marked_text_ranges(cx), None);
304
305 // Start a new IME composition with multiple cursors.
306 editor.change_selections(None, cx, |s| {
307 s.select_ranges([
308 OffsetUtf16(1)..OffsetUtf16(1),
309 OffsetUtf16(3)..OffsetUtf16(3),
310 OffsetUtf16(5)..OffsetUtf16(5),
311 ])
312 });
313 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
314 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
315 assert_eq!(
316 editor.marked_text_ranges(cx),
317 Some(vec![
318 OffsetUtf16(0)..OffsetUtf16(3),
319 OffsetUtf16(4)..OffsetUtf16(7),
320 OffsetUtf16(8)..OffsetUtf16(11)
321 ])
322 );
323
324 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
325 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
326 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
327 assert_eq!(
328 editor.marked_text_ranges(cx),
329 Some(vec![
330 OffsetUtf16(1)..OffsetUtf16(2),
331 OffsetUtf16(5)..OffsetUtf16(6),
332 OffsetUtf16(9)..OffsetUtf16(10)
333 ])
334 );
335
336 // Finalize IME composition with multiple cursors.
337 editor.replace_text_in_range(Some(9..10), "2", cx);
338 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
339 assert_eq!(editor.marked_text_ranges(cx), None);
340
341 editor
342 });
343}
344
345#[gpui::test]
346fn test_selection_with_mouse(cx: &mut TestAppContext) {
347 init_test(cx, |_| {});
348
349 let editor = cx.add_window(|cx| {
350 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
351 build_editor(buffer, cx)
352 });
353
354 _ = editor.update(cx, |view, cx| {
355 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
356 });
357 assert_eq!(
358 editor
359 .update(cx, |view, cx| view.selections.display_ranges(cx))
360 .unwrap(),
361 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
362 );
363
364 _ = editor.update(cx, |view, cx| {
365 view.update_selection(
366 DisplayPoint::new(DisplayRow(3), 3),
367 0,
368 gpui::Point::<f32>::default(),
369 cx,
370 );
371 });
372
373 assert_eq!(
374 editor
375 .update(cx, |view, cx| view.selections.display_ranges(cx))
376 .unwrap(),
377 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
378 );
379
380 _ = editor.update(cx, |view, cx| {
381 view.update_selection(
382 DisplayPoint::new(DisplayRow(1), 1),
383 0,
384 gpui::Point::<f32>::default(),
385 cx,
386 );
387 });
388
389 assert_eq!(
390 editor
391 .update(cx, |view, cx| view.selections.display_ranges(cx))
392 .unwrap(),
393 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
394 );
395
396 _ = editor.update(cx, |view, cx| {
397 view.end_selection(cx);
398 view.update_selection(
399 DisplayPoint::new(DisplayRow(3), 3),
400 0,
401 gpui::Point::<f32>::default(),
402 cx,
403 );
404 });
405
406 assert_eq!(
407 editor
408 .update(cx, |view, cx| view.selections.display_ranges(cx))
409 .unwrap(),
410 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
411 );
412
413 _ = editor.update(cx, |view, cx| {
414 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
415 view.update_selection(
416 DisplayPoint::new(DisplayRow(0), 0),
417 0,
418 gpui::Point::<f32>::default(),
419 cx,
420 );
421 });
422
423 assert_eq!(
424 editor
425 .update(cx, |view, cx| view.selections.display_ranges(cx))
426 .unwrap(),
427 [
428 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
429 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
430 ]
431 );
432
433 _ = editor.update(cx, |view, cx| {
434 view.end_selection(cx);
435 });
436
437 assert_eq!(
438 editor
439 .update(cx, |view, cx| view.selections.display_ranges(cx))
440 .unwrap(),
441 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
442 );
443}
444
445#[gpui::test]
446fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
447 init_test(cx, |_| {});
448
449 let editor = cx.add_window(|cx| {
450 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
451 build_editor(buffer, cx)
452 });
453
454 _ = editor.update(cx, |view, cx| {
455 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
456 });
457
458 _ = editor.update(cx, |view, cx| {
459 view.end_selection(cx);
460 });
461
462 _ = editor.update(cx, |view, cx| {
463 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
464 });
465
466 _ = editor.update(cx, |view, cx| {
467 view.end_selection(cx);
468 });
469
470 assert_eq!(
471 editor
472 .update(cx, |view, cx| view.selections.display_ranges(cx))
473 .unwrap(),
474 [
475 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
476 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
477 ]
478 );
479
480 _ = editor.update(cx, |view, cx| {
481 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
482 });
483
484 _ = editor.update(cx, |view, cx| {
485 view.end_selection(cx);
486 });
487
488 assert_eq!(
489 editor
490 .update(cx, |view, cx| view.selections.display_ranges(cx))
491 .unwrap(),
492 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
493 );
494}
495
496#[gpui::test]
497fn test_canceling_pending_selection(cx: &mut TestAppContext) {
498 init_test(cx, |_| {});
499
500 let view = cx.add_window(|cx| {
501 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
502 build_editor(buffer, cx)
503 });
504
505 _ = view.update(cx, |view, cx| {
506 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
507 assert_eq!(
508 view.selections.display_ranges(cx),
509 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
510 );
511 });
512
513 _ = view.update(cx, |view, cx| {
514 view.update_selection(
515 DisplayPoint::new(DisplayRow(3), 3),
516 0,
517 gpui::Point::<f32>::default(),
518 cx,
519 );
520 assert_eq!(
521 view.selections.display_ranges(cx),
522 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
523 );
524 });
525
526 _ = view.update(cx, |view, cx| {
527 view.cancel(&Cancel, cx);
528 view.update_selection(
529 DisplayPoint::new(DisplayRow(1), 1),
530 0,
531 gpui::Point::<f32>::default(),
532 cx,
533 );
534 assert_eq!(
535 view.selections.display_ranges(cx),
536 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
537 );
538 });
539}
540
541#[gpui::test]
542fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
543 init_test(cx, |_| {});
544
545 let view = cx.add_window(|cx| {
546 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
547 build_editor(buffer, cx)
548 });
549
550 _ = view.update(cx, |view, cx| {
551 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
552 assert_eq!(
553 view.selections.display_ranges(cx),
554 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
555 );
556
557 view.move_down(&Default::default(), cx);
558 assert_eq!(
559 view.selections.display_ranges(cx),
560 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
561 );
562
563 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
564 assert_eq!(
565 view.selections.display_ranges(cx),
566 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
567 );
568
569 view.move_up(&Default::default(), cx);
570 assert_eq!(
571 view.selections.display_ranges(cx),
572 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
573 );
574 });
575}
576
577#[gpui::test]
578fn test_clone(cx: &mut TestAppContext) {
579 init_test(cx, |_| {});
580
581 let (text, selection_ranges) = marked_text_ranges(
582 indoc! {"
583 one
584 two
585 threeˇ
586 four
587 fiveˇ
588 "},
589 true,
590 );
591
592 let editor = cx.add_window(|cx| {
593 let buffer = MultiBuffer::build_simple(&text, cx);
594 build_editor(buffer, cx)
595 });
596
597 _ = editor.update(cx, |editor, cx| {
598 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
599 editor.fold_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 completion_data = default_data.clone();
10580 let completion_characters = default_commit_characters.clone();
10581 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10582 let default_data = completion_data.clone();
10583 let default_commit_characters = completion_characters.clone();
10584 async move {
10585 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
10586 items: vec![
10587 lsp::CompletionItem {
10588 label: "Some(2)".into(),
10589 insert_text: Some("Some(2)".into()),
10590 data: Some(json!({ "very": "special"})),
10591 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
10592 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
10593 lsp::InsertReplaceEdit {
10594 new_text: "Some(2)".to_string(),
10595 insert: lsp::Range::default(),
10596 replace: lsp::Range::default(),
10597 },
10598 )),
10599 ..lsp::CompletionItem::default()
10600 },
10601 lsp::CompletionItem {
10602 label: "vec![2]".into(),
10603 insert_text: Some("vec![2]".into()),
10604 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
10605 ..lsp::CompletionItem::default()
10606 },
10607 ],
10608 item_defaults: Some(lsp::CompletionListItemDefaults {
10609 data: Some(default_data.clone()),
10610 commit_characters: Some(default_commit_characters.clone()),
10611 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
10612 default_edit_range,
10613 )),
10614 insert_text_format: Some(default_insert_text_format),
10615 insert_text_mode: Some(default_insert_text_mode),
10616 }),
10617 ..lsp::CompletionList::default()
10618 })))
10619 }
10620 })
10621 .next()
10622 .await;
10623
10624 cx.condition(|editor, _| editor.context_menu_visible())
10625 .await;
10626
10627 cx.update_editor(|editor, _| {
10628 let menu = editor.context_menu.read();
10629 match menu.as_ref().expect("should have the completions menu") {
10630 ContextMenu::Completions(completions_menu) => {
10631 assert_eq!(
10632 completions_menu
10633 .matches
10634 .iter()
10635 .map(|c| c.string.as_str())
10636 .collect::<Vec<_>>(),
10637 vec!["Some(2)", "vec![2]"]
10638 );
10639 }
10640 ContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
10641 }
10642 });
10643
10644 cx.update_editor(|editor, cx| {
10645 editor.context_menu_first(&ContextMenuFirst, cx);
10646 });
10647 let first_item_resolve_characters = default_commit_characters.clone();
10648 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, item_to_resolve, _| {
10649 let default_commit_characters = first_item_resolve_characters.clone();
10650
10651 async move {
10652 assert_eq!(
10653 item_to_resolve.label, "Some(2)",
10654 "Should have selected the first item"
10655 );
10656 assert_eq!(
10657 item_to_resolve.data,
10658 Some(json!({ "very": "special"})),
10659 "First item should bring its own data for resolving"
10660 );
10661 assert_eq!(
10662 item_to_resolve.commit_characters,
10663 Some(default_commit_characters),
10664 "First item had no own commit characters and should inherit the default ones"
10665 );
10666 assert!(
10667 matches!(
10668 item_to_resolve.text_edit,
10669 Some(lsp::CompletionTextEdit::InsertAndReplace { .. })
10670 ),
10671 "First item should bring its own edit range for resolving"
10672 );
10673 assert_eq!(
10674 item_to_resolve.insert_text_format,
10675 Some(default_insert_text_format),
10676 "First item had no own insert text format and should inherit the default one"
10677 );
10678 assert_eq!(
10679 item_to_resolve.insert_text_mode,
10680 Some(lsp::InsertTextMode::ADJUST_INDENTATION),
10681 "First item should bring its own insert text mode for resolving"
10682 );
10683 Ok(item_to_resolve)
10684 }
10685 })
10686 .next()
10687 .await
10688 .unwrap();
10689
10690 cx.update_editor(|editor, cx| {
10691 editor.context_menu_last(&ContextMenuLast, cx);
10692 });
10693 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, item_to_resolve, _| {
10694 let default_data = default_data.clone();
10695 let default_commit_characters = default_commit_characters.clone();
10696 async move {
10697 assert_eq!(
10698 item_to_resolve.label, "vec![2]",
10699 "Should have selected the last item"
10700 );
10701 assert_eq!(
10702 item_to_resolve.data,
10703 Some(default_data),
10704 "Last item has no own resolve data and should inherit the default one"
10705 );
10706 assert_eq!(
10707 item_to_resolve.commit_characters,
10708 Some(default_commit_characters),
10709 "Last item had no own commit characters and should inherit the default ones"
10710 );
10711 assert_eq!(
10712 item_to_resolve.text_edit,
10713 Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10714 range: default_edit_range,
10715 new_text: "vec![2]".to_string()
10716 })),
10717 "Last item had no own edit range and should inherit the default one"
10718 );
10719 assert_eq!(
10720 item_to_resolve.insert_text_format,
10721 Some(lsp::InsertTextFormat::PLAIN_TEXT),
10722 "Last item should bring its own insert text format for resolving"
10723 );
10724 assert_eq!(
10725 item_to_resolve.insert_text_mode,
10726 Some(default_insert_text_mode),
10727 "Last item had no own insert text mode and should inherit the default one"
10728 );
10729
10730 Ok(item_to_resolve)
10731 }
10732 })
10733 .next()
10734 .await
10735 .unwrap();
10736}
10737
10738#[gpui::test]
10739async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
10740 init_test(cx, |_| {});
10741
10742 let mut cx = EditorLspTestContext::new(
10743 Language::new(
10744 LanguageConfig {
10745 matcher: LanguageMatcher {
10746 path_suffixes: vec!["jsx".into()],
10747 ..Default::default()
10748 },
10749 overrides: [(
10750 "element".into(),
10751 LanguageConfigOverride {
10752 word_characters: Override::Set(['-'].into_iter().collect()),
10753 ..Default::default()
10754 },
10755 )]
10756 .into_iter()
10757 .collect(),
10758 ..Default::default()
10759 },
10760 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10761 )
10762 .with_override_query("(jsx_self_closing_element) @element")
10763 .unwrap(),
10764 lsp::ServerCapabilities {
10765 completion_provider: Some(lsp::CompletionOptions {
10766 trigger_characters: Some(vec![":".to_string()]),
10767 ..Default::default()
10768 }),
10769 ..Default::default()
10770 },
10771 cx,
10772 )
10773 .await;
10774
10775 cx.lsp
10776 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10777 Ok(Some(lsp::CompletionResponse::Array(vec![
10778 lsp::CompletionItem {
10779 label: "bg-blue".into(),
10780 ..Default::default()
10781 },
10782 lsp::CompletionItem {
10783 label: "bg-red".into(),
10784 ..Default::default()
10785 },
10786 lsp::CompletionItem {
10787 label: "bg-yellow".into(),
10788 ..Default::default()
10789 },
10790 ])))
10791 });
10792
10793 cx.set_state(r#"<p class="bgˇ" />"#);
10794
10795 // Trigger completion when typing a dash, because the dash is an extra
10796 // word character in the 'element' scope, which contains the cursor.
10797 cx.simulate_keystroke("-");
10798 cx.executor().run_until_parked();
10799 cx.update_editor(|editor, _| {
10800 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10801 assert_eq!(
10802 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10803 &["bg-red", "bg-blue", "bg-yellow"]
10804 );
10805 } else {
10806 panic!("expected completion menu to be open");
10807 }
10808 });
10809
10810 cx.simulate_keystroke("l");
10811 cx.executor().run_until_parked();
10812 cx.update_editor(|editor, _| {
10813 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10814 assert_eq!(
10815 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10816 &["bg-blue", "bg-yellow"]
10817 );
10818 } else {
10819 panic!("expected completion menu to be open");
10820 }
10821 });
10822
10823 // When filtering completions, consider the character after the '-' to
10824 // be the start of a subword.
10825 cx.set_state(r#"<p class="yelˇ" />"#);
10826 cx.simulate_keystroke("l");
10827 cx.executor().run_until_parked();
10828 cx.update_editor(|editor, _| {
10829 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10830 assert_eq!(
10831 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10832 &["bg-yellow"]
10833 );
10834 } else {
10835 panic!("expected completion menu to be open");
10836 }
10837 });
10838}
10839
10840#[gpui::test]
10841async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
10842 init_test(cx, |settings| {
10843 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
10844 FormatterList(vec![Formatter::Prettier].into()),
10845 ))
10846 });
10847
10848 let fs = FakeFs::new(cx.executor());
10849 fs.insert_file("/file.ts", Default::default()).await;
10850
10851 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
10852 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10853
10854 language_registry.add(Arc::new(Language::new(
10855 LanguageConfig {
10856 name: "TypeScript".into(),
10857 matcher: LanguageMatcher {
10858 path_suffixes: vec!["ts".to_string()],
10859 ..Default::default()
10860 },
10861 ..Default::default()
10862 },
10863 Some(tree_sitter_rust::LANGUAGE.into()),
10864 )));
10865 update_test_language_settings(cx, |settings| {
10866 settings.defaults.prettier = Some(PrettierSettings {
10867 allowed: true,
10868 ..PrettierSettings::default()
10869 });
10870 });
10871
10872 let test_plugin = "test_plugin";
10873 let _ = language_registry.register_fake_lsp(
10874 "TypeScript",
10875 FakeLspAdapter {
10876 prettier_plugins: vec![test_plugin],
10877 ..Default::default()
10878 },
10879 );
10880
10881 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
10882 let buffer = project
10883 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
10884 .await
10885 .unwrap();
10886
10887 let buffer_text = "one\ntwo\nthree\n";
10888 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
10889 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
10890 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
10891
10892 editor
10893 .update(cx, |editor, cx| {
10894 editor.perform_format(
10895 project.clone(),
10896 FormatTrigger::Manual,
10897 FormatTarget::Buffer,
10898 cx,
10899 )
10900 })
10901 .unwrap()
10902 .await;
10903 assert_eq!(
10904 editor.update(cx, |editor, cx| editor.text(cx)),
10905 buffer_text.to_string() + prettier_format_suffix,
10906 "Test prettier formatting was not applied to the original buffer text",
10907 );
10908
10909 update_test_language_settings(cx, |settings| {
10910 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
10911 });
10912 let format = editor.update(cx, |editor, cx| {
10913 editor.perform_format(
10914 project.clone(),
10915 FormatTrigger::Manual,
10916 FormatTarget::Buffer,
10917 cx,
10918 )
10919 });
10920 format.await.unwrap();
10921 assert_eq!(
10922 editor.update(cx, |editor, cx| editor.text(cx)),
10923 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
10924 "Autoformatting (via test prettier) was not applied to the original buffer text",
10925 );
10926}
10927
10928#[gpui::test]
10929async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
10930 init_test(cx, |_| {});
10931 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
10932 let base_text = indoc! {r#"struct Row;
10933struct Row1;
10934struct Row2;
10935
10936struct Row4;
10937struct Row5;
10938struct Row6;
10939
10940struct Row8;
10941struct Row9;
10942struct Row10;"#};
10943
10944 // When addition hunks are not adjacent to carets, no hunk revert is performed
10945 assert_hunk_revert(
10946 indoc! {r#"struct Row;
10947 struct Row1;
10948 struct Row1.1;
10949 struct Row1.2;
10950 struct Row2;ˇ
10951
10952 struct Row4;
10953 struct Row5;
10954 struct Row6;
10955
10956 struct Row8;
10957 ˇstruct Row9;
10958 struct Row9.1;
10959 struct Row9.2;
10960 struct Row9.3;
10961 struct Row10;"#},
10962 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
10963 indoc! {r#"struct Row;
10964 struct Row1;
10965 struct Row1.1;
10966 struct Row1.2;
10967 struct Row2;ˇ
10968
10969 struct Row4;
10970 struct Row5;
10971 struct Row6;
10972
10973 struct Row8;
10974 ˇstruct Row9;
10975 struct Row9.1;
10976 struct Row9.2;
10977 struct Row9.3;
10978 struct Row10;"#},
10979 base_text,
10980 &mut cx,
10981 );
10982 // Same for selections
10983 assert_hunk_revert(
10984 indoc! {r#"struct Row;
10985 struct Row1;
10986 struct Row2;
10987 struct Row2.1;
10988 struct Row2.2;
10989 «ˇ
10990 struct Row4;
10991 struct» Row5;
10992 «struct Row6;
10993 ˇ»
10994 struct Row9.1;
10995 struct Row9.2;
10996 struct Row9.3;
10997 struct Row8;
10998 struct Row9;
10999 struct Row10;"#},
11000 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11001 indoc! {r#"struct Row;
11002 struct Row1;
11003 struct Row2;
11004 struct Row2.1;
11005 struct Row2.2;
11006 «ˇ
11007 struct Row4;
11008 struct» Row5;
11009 «struct Row6;
11010 ˇ»
11011 struct Row9.1;
11012 struct Row9.2;
11013 struct Row9.3;
11014 struct Row8;
11015 struct Row9;
11016 struct Row10;"#},
11017 base_text,
11018 &mut cx,
11019 );
11020
11021 // When carets and selections intersect the addition hunks, those are reverted.
11022 // Adjacent carets got merged.
11023 assert_hunk_revert(
11024 indoc! {r#"struct Row;
11025 ˇ// something on the top
11026 struct Row1;
11027 struct Row2;
11028 struct Roˇw3.1;
11029 struct Row2.2;
11030 struct Row2.3;ˇ
11031
11032 struct Row4;
11033 struct ˇRow5.1;
11034 struct Row5.2;
11035 struct «Rowˇ»5.3;
11036 struct Row5;
11037 struct Row6;
11038 ˇ
11039 struct Row9.1;
11040 struct «Rowˇ»9.2;
11041 struct «ˇRow»9.3;
11042 struct Row8;
11043 struct Row9;
11044 «ˇ// something on bottom»
11045 struct Row10;"#},
11046 vec![
11047 DiffHunkStatus::Added,
11048 DiffHunkStatus::Added,
11049 DiffHunkStatus::Added,
11050 DiffHunkStatus::Added,
11051 DiffHunkStatus::Added,
11052 ],
11053 indoc! {r#"struct Row;
11054 ˇstruct Row1;
11055 struct Row2;
11056 ˇ
11057 struct Row4;
11058 ˇstruct Row5;
11059 struct Row6;
11060 ˇ
11061 ˇstruct Row8;
11062 struct Row9;
11063 ˇstruct Row10;"#},
11064 base_text,
11065 &mut cx,
11066 );
11067}
11068
11069#[gpui::test]
11070async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
11071 init_test(cx, |_| {});
11072 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11073 let base_text = indoc! {r#"struct Row;
11074struct Row1;
11075struct Row2;
11076
11077struct Row4;
11078struct Row5;
11079struct Row6;
11080
11081struct Row8;
11082struct Row9;
11083struct Row10;"#};
11084
11085 // Modification hunks behave the same as the addition ones.
11086 assert_hunk_revert(
11087 indoc! {r#"struct Row;
11088 struct Row1;
11089 struct Row33;
11090 ˇ
11091 struct Row4;
11092 struct Row5;
11093 struct Row6;
11094 ˇ
11095 struct Row99;
11096 struct Row9;
11097 struct Row10;"#},
11098 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11099 indoc! {r#"struct Row;
11100 struct Row1;
11101 struct Row33;
11102 ˇ
11103 struct Row4;
11104 struct Row5;
11105 struct Row6;
11106 ˇ
11107 struct Row99;
11108 struct Row9;
11109 struct Row10;"#},
11110 base_text,
11111 &mut cx,
11112 );
11113 assert_hunk_revert(
11114 indoc! {r#"struct Row;
11115 struct Row1;
11116 struct Row33;
11117 «ˇ
11118 struct Row4;
11119 struct» Row5;
11120 «struct Row6;
11121 ˇ»
11122 struct Row99;
11123 struct Row9;
11124 struct Row10;"#},
11125 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11126 indoc! {r#"struct Row;
11127 struct Row1;
11128 struct Row33;
11129 «ˇ
11130 struct Row4;
11131 struct» Row5;
11132 «struct Row6;
11133 ˇ»
11134 struct Row99;
11135 struct Row9;
11136 struct Row10;"#},
11137 base_text,
11138 &mut cx,
11139 );
11140
11141 assert_hunk_revert(
11142 indoc! {r#"ˇstruct Row1.1;
11143 struct Row1;
11144 «ˇstr»uct Row22;
11145
11146 struct ˇRow44;
11147 struct Row5;
11148 struct «Rˇ»ow66;ˇ
11149
11150 «struˇ»ct Row88;
11151 struct Row9;
11152 struct Row1011;ˇ"#},
11153 vec![
11154 DiffHunkStatus::Modified,
11155 DiffHunkStatus::Modified,
11156 DiffHunkStatus::Modified,
11157 DiffHunkStatus::Modified,
11158 DiffHunkStatus::Modified,
11159 DiffHunkStatus::Modified,
11160 ],
11161 indoc! {r#"struct Row;
11162 ˇstruct Row1;
11163 struct Row2;
11164 ˇ
11165 struct Row4;
11166 ˇstruct Row5;
11167 struct Row6;
11168 ˇ
11169 struct Row8;
11170 ˇstruct Row9;
11171 struct Row10;ˇ"#},
11172 base_text,
11173 &mut cx,
11174 );
11175}
11176
11177#[gpui::test]
11178async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
11179 init_test(cx, |_| {});
11180 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11181 let base_text = indoc! {r#"struct Row;
11182struct Row1;
11183struct Row2;
11184
11185struct Row4;
11186struct Row5;
11187struct Row6;
11188
11189struct Row8;
11190struct Row9;
11191struct Row10;"#};
11192
11193 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
11194 assert_hunk_revert(
11195 indoc! {r#"struct Row;
11196 struct Row2;
11197
11198 ˇstruct Row4;
11199 struct Row5;
11200 struct Row6;
11201 ˇ
11202 struct Row8;
11203 struct Row10;"#},
11204 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11205 indoc! {r#"struct Row;
11206 struct Row2;
11207
11208 ˇstruct Row4;
11209 struct Row5;
11210 struct Row6;
11211 ˇ
11212 struct Row8;
11213 struct Row10;"#},
11214 base_text,
11215 &mut cx,
11216 );
11217 assert_hunk_revert(
11218 indoc! {r#"struct Row;
11219 struct Row2;
11220
11221 «ˇstruct Row4;
11222 struct» Row5;
11223 «struct Row6;
11224 ˇ»
11225 struct Row8;
11226 struct Row10;"#},
11227 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11228 indoc! {r#"struct Row;
11229 struct Row2;
11230
11231 «ˇstruct Row4;
11232 struct» Row5;
11233 «struct Row6;
11234 ˇ»
11235 struct Row8;
11236 struct Row10;"#},
11237 base_text,
11238 &mut cx,
11239 );
11240
11241 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
11242 assert_hunk_revert(
11243 indoc! {r#"struct Row;
11244 ˇstruct Row2;
11245
11246 struct Row4;
11247 struct Row5;
11248 struct Row6;
11249
11250 struct Row8;ˇ
11251 struct Row10;"#},
11252 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11253 indoc! {r#"struct Row;
11254 struct Row1;
11255 ˇstruct Row2;
11256
11257 struct Row4;
11258 struct Row5;
11259 struct Row6;
11260
11261 struct Row8;ˇ
11262 struct Row9;
11263 struct Row10;"#},
11264 base_text,
11265 &mut cx,
11266 );
11267 assert_hunk_revert(
11268 indoc! {r#"struct Row;
11269 struct Row2«ˇ;
11270 struct Row4;
11271 struct» Row5;
11272 «struct Row6;
11273
11274 struct Row8;ˇ»
11275 struct Row10;"#},
11276 vec![
11277 DiffHunkStatus::Removed,
11278 DiffHunkStatus::Removed,
11279 DiffHunkStatus::Removed,
11280 ],
11281 indoc! {r#"struct Row;
11282 struct Row1;
11283 struct Row2«ˇ;
11284
11285 struct Row4;
11286 struct» Row5;
11287 «struct Row6;
11288
11289 struct Row8;ˇ»
11290 struct Row9;
11291 struct Row10;"#},
11292 base_text,
11293 &mut cx,
11294 );
11295}
11296
11297#[gpui::test]
11298async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
11299 init_test(cx, |_| {});
11300
11301 let cols = 4;
11302 let rows = 10;
11303 let sample_text_1 = sample_text(rows, cols, 'a');
11304 assert_eq!(
11305 sample_text_1,
11306 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11307 );
11308 let sample_text_2 = sample_text(rows, cols, 'l');
11309 assert_eq!(
11310 sample_text_2,
11311 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11312 );
11313 let sample_text_3 = sample_text(rows, cols, 'v');
11314 assert_eq!(
11315 sample_text_3,
11316 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11317 );
11318
11319 fn diff_every_buffer_row(
11320 buffer: &Model<Buffer>,
11321 sample_text: String,
11322 cols: usize,
11323 cx: &mut gpui::TestAppContext,
11324 ) {
11325 // revert first character in each row, creating one large diff hunk per buffer
11326 let is_first_char = |offset: usize| offset % cols == 0;
11327 buffer.update(cx, |buffer, cx| {
11328 buffer.set_text(
11329 sample_text
11330 .chars()
11331 .enumerate()
11332 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
11333 .collect::<String>(),
11334 cx,
11335 );
11336 buffer.set_diff_base(Some(sample_text), cx);
11337 });
11338 cx.executor().run_until_parked();
11339 }
11340
11341 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11342 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
11343
11344 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11345 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
11346
11347 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11348 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
11349
11350 let multibuffer = cx.new_model(|cx| {
11351 let mut multibuffer = MultiBuffer::new(ReadWrite);
11352 multibuffer.push_excerpts(
11353 buffer_1.clone(),
11354 [
11355 ExcerptRange {
11356 context: Point::new(0, 0)..Point::new(3, 0),
11357 primary: None,
11358 },
11359 ExcerptRange {
11360 context: Point::new(5, 0)..Point::new(7, 0),
11361 primary: None,
11362 },
11363 ExcerptRange {
11364 context: Point::new(9, 0)..Point::new(10, 4),
11365 primary: None,
11366 },
11367 ],
11368 cx,
11369 );
11370 multibuffer.push_excerpts(
11371 buffer_2.clone(),
11372 [
11373 ExcerptRange {
11374 context: Point::new(0, 0)..Point::new(3, 0),
11375 primary: None,
11376 },
11377 ExcerptRange {
11378 context: Point::new(5, 0)..Point::new(7, 0),
11379 primary: None,
11380 },
11381 ExcerptRange {
11382 context: Point::new(9, 0)..Point::new(10, 4),
11383 primary: None,
11384 },
11385 ],
11386 cx,
11387 );
11388 multibuffer.push_excerpts(
11389 buffer_3.clone(),
11390 [
11391 ExcerptRange {
11392 context: Point::new(0, 0)..Point::new(3, 0),
11393 primary: None,
11394 },
11395 ExcerptRange {
11396 context: Point::new(5, 0)..Point::new(7, 0),
11397 primary: None,
11398 },
11399 ExcerptRange {
11400 context: Point::new(9, 0)..Point::new(10, 4),
11401 primary: None,
11402 },
11403 ],
11404 cx,
11405 );
11406 multibuffer
11407 });
11408
11409 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
11410 editor.update(cx, |editor, cx| {
11411 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");
11412 editor.select_all(&SelectAll, cx);
11413 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11414 });
11415 cx.executor().run_until_parked();
11416 // When all ranges are selected, all buffer hunks are reverted.
11417 editor.update(cx, |editor, cx| {
11418 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");
11419 });
11420 buffer_1.update(cx, |buffer, _| {
11421 assert_eq!(buffer.text(), sample_text_1);
11422 });
11423 buffer_2.update(cx, |buffer, _| {
11424 assert_eq!(buffer.text(), sample_text_2);
11425 });
11426 buffer_3.update(cx, |buffer, _| {
11427 assert_eq!(buffer.text(), sample_text_3);
11428 });
11429
11430 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
11431 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
11432 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
11433 editor.update(cx, |editor, cx| {
11434 editor.change_selections(None, cx, |s| {
11435 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
11436 });
11437 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11438 });
11439 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
11440 // but not affect buffer_2 and its related excerpts.
11441 editor.update(cx, |editor, cx| {
11442 assert_eq!(
11443 editor.text(cx),
11444 "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"
11445 );
11446 });
11447 buffer_1.update(cx, |buffer, _| {
11448 assert_eq!(buffer.text(), sample_text_1);
11449 });
11450 buffer_2.update(cx, |buffer, _| {
11451 assert_eq!(
11452 buffer.text(),
11453 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
11454 );
11455 });
11456 buffer_3.update(cx, |buffer, _| {
11457 assert_eq!(
11458 buffer.text(),
11459 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
11460 );
11461 });
11462}
11463
11464#[gpui::test]
11465async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
11466 init_test(cx, |_| {});
11467
11468 let cols = 4;
11469 let rows = 10;
11470 let sample_text_1 = sample_text(rows, cols, 'a');
11471 assert_eq!(
11472 sample_text_1,
11473 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11474 );
11475 let sample_text_2 = sample_text(rows, cols, 'l');
11476 assert_eq!(
11477 sample_text_2,
11478 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11479 );
11480 let sample_text_3 = sample_text(rows, cols, 'v');
11481 assert_eq!(
11482 sample_text_3,
11483 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11484 );
11485
11486 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11487 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11488 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11489
11490 let multi_buffer = cx.new_model(|cx| {
11491 let mut multibuffer = MultiBuffer::new(ReadWrite);
11492 multibuffer.push_excerpts(
11493 buffer_1.clone(),
11494 [
11495 ExcerptRange {
11496 context: Point::new(0, 0)..Point::new(3, 0),
11497 primary: None,
11498 },
11499 ExcerptRange {
11500 context: Point::new(5, 0)..Point::new(7, 0),
11501 primary: None,
11502 },
11503 ExcerptRange {
11504 context: Point::new(9, 0)..Point::new(10, 4),
11505 primary: None,
11506 },
11507 ],
11508 cx,
11509 );
11510 multibuffer.push_excerpts(
11511 buffer_2.clone(),
11512 [
11513 ExcerptRange {
11514 context: Point::new(0, 0)..Point::new(3, 0),
11515 primary: None,
11516 },
11517 ExcerptRange {
11518 context: Point::new(5, 0)..Point::new(7, 0),
11519 primary: None,
11520 },
11521 ExcerptRange {
11522 context: Point::new(9, 0)..Point::new(10, 4),
11523 primary: None,
11524 },
11525 ],
11526 cx,
11527 );
11528 multibuffer.push_excerpts(
11529 buffer_3.clone(),
11530 [
11531 ExcerptRange {
11532 context: Point::new(0, 0)..Point::new(3, 0),
11533 primary: None,
11534 },
11535 ExcerptRange {
11536 context: Point::new(5, 0)..Point::new(7, 0),
11537 primary: None,
11538 },
11539 ExcerptRange {
11540 context: Point::new(9, 0)..Point::new(10, 4),
11541 primary: None,
11542 },
11543 ],
11544 cx,
11545 );
11546 multibuffer
11547 });
11548
11549 let fs = FakeFs::new(cx.executor());
11550 fs.insert_tree(
11551 "/a",
11552 json!({
11553 "main.rs": sample_text_1,
11554 "other.rs": sample_text_2,
11555 "lib.rs": sample_text_3,
11556 }),
11557 )
11558 .await;
11559 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11560 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11561 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11562 let multi_buffer_editor = cx.new_view(|cx| {
11563 Editor::new(
11564 EditorMode::Full,
11565 multi_buffer,
11566 Some(project.clone()),
11567 true,
11568 cx,
11569 )
11570 });
11571 let multibuffer_item_id = workspace
11572 .update(cx, |workspace, cx| {
11573 assert!(
11574 workspace.active_item(cx).is_none(),
11575 "active item should be None before the first item is added"
11576 );
11577 workspace.add_item_to_active_pane(
11578 Box::new(multi_buffer_editor.clone()),
11579 None,
11580 true,
11581 cx,
11582 );
11583 let active_item = workspace
11584 .active_item(cx)
11585 .expect("should have an active item after adding the multi buffer");
11586 assert!(
11587 !active_item.is_singleton(cx),
11588 "A multi buffer was expected to active after adding"
11589 );
11590 active_item.item_id()
11591 })
11592 .unwrap();
11593 cx.executor().run_until_parked();
11594
11595 multi_buffer_editor.update(cx, |editor, cx| {
11596 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
11597 editor.open_excerpts(&OpenExcerpts, cx);
11598 });
11599 cx.executor().run_until_parked();
11600 let first_item_id = workspace
11601 .update(cx, |workspace, cx| {
11602 let active_item = workspace
11603 .active_item(cx)
11604 .expect("should have an active item after navigating into the 1st buffer");
11605 let first_item_id = active_item.item_id();
11606 assert_ne!(
11607 first_item_id, multibuffer_item_id,
11608 "Should navigate into the 1st buffer and activate it"
11609 );
11610 assert!(
11611 active_item.is_singleton(cx),
11612 "New active item should be a singleton buffer"
11613 );
11614 assert_eq!(
11615 active_item
11616 .act_as::<Editor>(cx)
11617 .expect("should have navigated into an editor for the 1st buffer")
11618 .read(cx)
11619 .text(cx),
11620 sample_text_1
11621 );
11622
11623 workspace
11624 .go_back(workspace.active_pane().downgrade(), cx)
11625 .detach_and_log_err(cx);
11626
11627 first_item_id
11628 })
11629 .unwrap();
11630 cx.executor().run_until_parked();
11631 workspace
11632 .update(cx, |workspace, cx| {
11633 let active_item = workspace
11634 .active_item(cx)
11635 .expect("should have an active item after navigating back");
11636 assert_eq!(
11637 active_item.item_id(),
11638 multibuffer_item_id,
11639 "Should navigate back to the multi buffer"
11640 );
11641 assert!(!active_item.is_singleton(cx));
11642 })
11643 .unwrap();
11644
11645 multi_buffer_editor.update(cx, |editor, cx| {
11646 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11647 s.select_ranges(Some(39..40))
11648 });
11649 editor.open_excerpts(&OpenExcerpts, cx);
11650 });
11651 cx.executor().run_until_parked();
11652 let second_item_id = workspace
11653 .update(cx, |workspace, cx| {
11654 let active_item = workspace
11655 .active_item(cx)
11656 .expect("should have an active item after navigating into the 2nd buffer");
11657 let second_item_id = active_item.item_id();
11658 assert_ne!(
11659 second_item_id, multibuffer_item_id,
11660 "Should navigate away from the multibuffer"
11661 );
11662 assert_ne!(
11663 second_item_id, first_item_id,
11664 "Should navigate into the 2nd buffer and activate it"
11665 );
11666 assert!(
11667 active_item.is_singleton(cx),
11668 "New active item should be a singleton buffer"
11669 );
11670 assert_eq!(
11671 active_item
11672 .act_as::<Editor>(cx)
11673 .expect("should have navigated into an editor")
11674 .read(cx)
11675 .text(cx),
11676 sample_text_2
11677 );
11678
11679 workspace
11680 .go_back(workspace.active_pane().downgrade(), cx)
11681 .detach_and_log_err(cx);
11682
11683 second_item_id
11684 })
11685 .unwrap();
11686 cx.executor().run_until_parked();
11687 workspace
11688 .update(cx, |workspace, cx| {
11689 let active_item = workspace
11690 .active_item(cx)
11691 .expect("should have an active item after navigating back from the 2nd buffer");
11692 assert_eq!(
11693 active_item.item_id(),
11694 multibuffer_item_id,
11695 "Should navigate back from the 2nd buffer to the multi buffer"
11696 );
11697 assert!(!active_item.is_singleton(cx));
11698 })
11699 .unwrap();
11700
11701 multi_buffer_editor.update(cx, |editor, cx| {
11702 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11703 s.select_ranges(Some(60..70))
11704 });
11705 editor.open_excerpts(&OpenExcerpts, cx);
11706 });
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 into the 3rd buffer");
11713 let third_item_id = active_item.item_id();
11714 assert_ne!(
11715 third_item_id, multibuffer_item_id,
11716 "Should navigate into the 3rd buffer and activate it"
11717 );
11718 assert_ne!(third_item_id, first_item_id);
11719 assert_ne!(third_item_id, second_item_id);
11720 assert!(
11721 active_item.is_singleton(cx),
11722 "New active item should be a singleton buffer"
11723 );
11724 assert_eq!(
11725 active_item
11726 .act_as::<Editor>(cx)
11727 .expect("should have navigated into an editor")
11728 .read(cx)
11729 .text(cx),
11730 sample_text_3
11731 );
11732
11733 workspace
11734 .go_back(workspace.active_pane().downgrade(), cx)
11735 .detach_and_log_err(cx);
11736 })
11737 .unwrap();
11738 cx.executor().run_until_parked();
11739 workspace
11740 .update(cx, |workspace, cx| {
11741 let active_item = workspace
11742 .active_item(cx)
11743 .expect("should have an active item after navigating back from the 3rd buffer");
11744 assert_eq!(
11745 active_item.item_id(),
11746 multibuffer_item_id,
11747 "Should navigate back from the 3rd buffer to the multi buffer"
11748 );
11749 assert!(!active_item.is_singleton(cx));
11750 })
11751 .unwrap();
11752}
11753
11754#[gpui::test]
11755async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11756 init_test(cx, |_| {});
11757
11758 let mut cx = EditorTestContext::new(cx).await;
11759
11760 let diff_base = r#"
11761 use some::mod;
11762
11763 const A: u32 = 42;
11764
11765 fn main() {
11766 println!("hello");
11767
11768 println!("world");
11769 }
11770 "#
11771 .unindent();
11772
11773 cx.set_state(
11774 &r#"
11775 use some::modified;
11776
11777 ˇ
11778 fn main() {
11779 println!("hello there");
11780
11781 println!("around the");
11782 println!("world");
11783 }
11784 "#
11785 .unindent(),
11786 );
11787
11788 cx.set_diff_base(Some(&diff_base));
11789 executor.run_until_parked();
11790
11791 cx.update_editor(|editor, cx| {
11792 editor.go_to_next_hunk(&GoToHunk, cx);
11793 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11794 });
11795 executor.run_until_parked();
11796 cx.assert_diff_hunks(
11797 r#"
11798 use some::modified;
11799
11800
11801 fn main() {
11802 - println!("hello");
11803 + println!("hello there");
11804
11805 println!("around the");
11806 println!("world");
11807 }
11808 "#
11809 .unindent(),
11810 );
11811
11812 cx.update_editor(|editor, cx| {
11813 for _ in 0..3 {
11814 editor.go_to_next_hunk(&GoToHunk, cx);
11815 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11816 }
11817 });
11818 executor.run_until_parked();
11819 cx.assert_editor_state(
11820 &r#"
11821 use some::modified;
11822
11823 ˇ
11824 fn main() {
11825 println!("hello there");
11826
11827 println!("around the");
11828 println!("world");
11829 }
11830 "#
11831 .unindent(),
11832 );
11833
11834 cx.assert_diff_hunks(
11835 r#"
11836 - use some::mod;
11837 + use some::modified;
11838
11839 - const A: u32 = 42;
11840
11841 fn main() {
11842 - println!("hello");
11843 + println!("hello there");
11844
11845 + println!("around the");
11846 println!("world");
11847 }
11848 "#
11849 .unindent(),
11850 );
11851
11852 cx.update_editor(|editor, cx| {
11853 editor.cancel(&Cancel, cx);
11854 });
11855
11856 cx.assert_diff_hunks(
11857 r#"
11858 use some::modified;
11859
11860
11861 fn main() {
11862 println!("hello there");
11863
11864 println!("around the");
11865 println!("world");
11866 }
11867 "#
11868 .unindent(),
11869 );
11870}
11871
11872#[gpui::test]
11873async fn test_diff_base_change_with_expanded_diff_hunks(
11874 executor: BackgroundExecutor,
11875 cx: &mut gpui::TestAppContext,
11876) {
11877 init_test(cx, |_| {});
11878
11879 let mut cx = EditorTestContext::new(cx).await;
11880
11881 let diff_base = r#"
11882 use some::mod1;
11883 use some::mod2;
11884
11885 const A: u32 = 42;
11886 const B: u32 = 42;
11887 const C: u32 = 42;
11888
11889 fn main() {
11890 println!("hello");
11891
11892 println!("world");
11893 }
11894 "#
11895 .unindent();
11896
11897 cx.set_state(
11898 &r#"
11899 use some::mod2;
11900
11901 const A: u32 = 42;
11902 const C: u32 = 42;
11903
11904 fn main(ˇ) {
11905 //println!("hello");
11906
11907 println!("world");
11908 //
11909 //
11910 }
11911 "#
11912 .unindent(),
11913 );
11914
11915 cx.set_diff_base(Some(&diff_base));
11916 executor.run_until_parked();
11917
11918 cx.update_editor(|editor, cx| {
11919 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11920 });
11921 executor.run_until_parked();
11922 cx.assert_diff_hunks(
11923 r#"
11924 - use some::mod1;
11925 use some::mod2;
11926
11927 const A: u32 = 42;
11928 - const B: u32 = 42;
11929 const C: u32 = 42;
11930
11931 fn main() {
11932 - println!("hello");
11933 + //println!("hello");
11934
11935 println!("world");
11936 + //
11937 + //
11938 }
11939 "#
11940 .unindent(),
11941 );
11942
11943 cx.set_diff_base(Some("new diff base!"));
11944 executor.run_until_parked();
11945 cx.assert_diff_hunks(
11946 r#"
11947 use some::mod2;
11948
11949 const A: u32 = 42;
11950 const C: u32 = 42;
11951
11952 fn main() {
11953 //println!("hello");
11954
11955 println!("world");
11956 //
11957 //
11958 }
11959 "#
11960 .unindent(),
11961 );
11962
11963 cx.update_editor(|editor, cx| {
11964 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
11965 });
11966 executor.run_until_parked();
11967 cx.assert_diff_hunks(
11968 r#"
11969 - new diff base!
11970 + use some::mod2;
11971 +
11972 + const A: u32 = 42;
11973 + const C: u32 = 42;
11974 +
11975 + fn main() {
11976 + //println!("hello");
11977 +
11978 + println!("world");
11979 + //
11980 + //
11981 + }
11982 "#
11983 .unindent(),
11984 );
11985}
11986
11987#[gpui::test]
11988async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11989 init_test(cx, |_| {});
11990
11991 let mut cx = EditorTestContext::new(cx).await;
11992
11993 let diff_base = r#"
11994 use some::mod1;
11995 use some::mod2;
11996
11997 const A: u32 = 42;
11998 const B: u32 = 42;
11999 const C: u32 = 42;
12000
12001 fn main() {
12002 println!("hello");
12003
12004 println!("world");
12005 }
12006
12007 fn another() {
12008 println!("another");
12009 }
12010
12011 fn another2() {
12012 println!("another2");
12013 }
12014 "#
12015 .unindent();
12016
12017 cx.set_state(
12018 &r#"
12019 «use some::mod2;
12020
12021 const A: u32 = 42;
12022 const C: u32 = 42;
12023
12024 fn main() {
12025 //println!("hello");
12026
12027 println!("world");
12028 //
12029 //ˇ»
12030 }
12031
12032 fn another() {
12033 println!("another");
12034 println!("another");
12035 }
12036
12037 println!("another2");
12038 }
12039 "#
12040 .unindent(),
12041 );
12042
12043 cx.set_diff_base(Some(&diff_base));
12044 executor.run_until_parked();
12045
12046 cx.update_editor(|editor, cx| {
12047 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12048 });
12049 executor.run_until_parked();
12050
12051 cx.assert_diff_hunks(
12052 r#"
12053 - use some::mod1;
12054 use some::mod2;
12055
12056 const A: u32 = 42;
12057 - const B: u32 = 42;
12058 const C: u32 = 42;
12059
12060 fn main() {
12061 - println!("hello");
12062 + //println!("hello");
12063
12064 println!("world");
12065 + //
12066 + //
12067 }
12068
12069 fn another() {
12070 println!("another");
12071 + println!("another");
12072 }
12073
12074 - fn another2() {
12075 println!("another2");
12076 }
12077 "#
12078 .unindent(),
12079 );
12080
12081 // Fold across some of the diff hunks. They should no longer appear expanded.
12082 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
12083 cx.executor().run_until_parked();
12084
12085 // Hunks are not shown if their position is within a fold
12086 cx.assert_diff_hunks(
12087 r#"
12088 use some::mod2;
12089
12090 const A: u32 = 42;
12091 const C: u32 = 42;
12092
12093 fn main() {
12094 //println!("hello");
12095
12096 println!("world");
12097 //
12098 //
12099 }
12100
12101 fn another() {
12102 println!("another");
12103 + println!("another");
12104 }
12105
12106 - fn another2() {
12107 println!("another2");
12108 }
12109 "#
12110 .unindent(),
12111 );
12112
12113 cx.update_editor(|editor, cx| {
12114 editor.select_all(&SelectAll, cx);
12115 editor.unfold_lines(&UnfoldLines, cx);
12116 });
12117 cx.executor().run_until_parked();
12118
12119 // The deletions reappear when unfolding.
12120 cx.assert_diff_hunks(
12121 r#"
12122 - use some::mod1;
12123 use some::mod2;
12124
12125 const A: u32 = 42;
12126 - const B: u32 = 42;
12127 const C: u32 = 42;
12128
12129 fn main() {
12130 - println!("hello");
12131 + //println!("hello");
12132
12133 println!("world");
12134 + //
12135 + //
12136 }
12137
12138 fn another() {
12139 println!("another");
12140 + println!("another");
12141 }
12142
12143 - fn another2() {
12144 println!("another2");
12145 }
12146 "#
12147 .unindent(),
12148 );
12149}
12150
12151#[gpui::test]
12152async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
12153 init_test(cx, |_| {});
12154
12155 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12156 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12157 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12158 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12159 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
12160 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
12161
12162 let buffer_1 = cx.new_model(|cx| {
12163 let mut buffer = Buffer::local(file_1_new.to_string(), cx);
12164 buffer.set_diff_base(Some(file_1_old.into()), cx);
12165 buffer
12166 });
12167 let buffer_2 = cx.new_model(|cx| {
12168 let mut buffer = Buffer::local(file_2_new.to_string(), cx);
12169 buffer.set_diff_base(Some(file_2_old.into()), cx);
12170 buffer
12171 });
12172 let buffer_3 = cx.new_model(|cx| {
12173 let mut buffer = Buffer::local(file_3_new.to_string(), cx);
12174 buffer.set_diff_base(Some(file_3_old.into()), cx);
12175 buffer
12176 });
12177
12178 let multi_buffer = cx.new_model(|cx| {
12179 let mut multibuffer = MultiBuffer::new(ReadWrite);
12180 multibuffer.push_excerpts(
12181 buffer_1.clone(),
12182 [
12183 ExcerptRange {
12184 context: Point::new(0, 0)..Point::new(3, 0),
12185 primary: None,
12186 },
12187 ExcerptRange {
12188 context: Point::new(5, 0)..Point::new(7, 0),
12189 primary: None,
12190 },
12191 ExcerptRange {
12192 context: Point::new(9, 0)..Point::new(10, 3),
12193 primary: None,
12194 },
12195 ],
12196 cx,
12197 );
12198 multibuffer.push_excerpts(
12199 buffer_2.clone(),
12200 [
12201 ExcerptRange {
12202 context: Point::new(0, 0)..Point::new(3, 0),
12203 primary: None,
12204 },
12205 ExcerptRange {
12206 context: Point::new(5, 0)..Point::new(7, 0),
12207 primary: None,
12208 },
12209 ExcerptRange {
12210 context: Point::new(9, 0)..Point::new(10, 3),
12211 primary: None,
12212 },
12213 ],
12214 cx,
12215 );
12216 multibuffer.push_excerpts(
12217 buffer_3.clone(),
12218 [
12219 ExcerptRange {
12220 context: Point::new(0, 0)..Point::new(3, 0),
12221 primary: None,
12222 },
12223 ExcerptRange {
12224 context: Point::new(5, 0)..Point::new(7, 0),
12225 primary: None,
12226 },
12227 ExcerptRange {
12228 context: Point::new(9, 0)..Point::new(10, 3),
12229 primary: None,
12230 },
12231 ],
12232 cx,
12233 );
12234 multibuffer
12235 });
12236
12237 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12238 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12239 cx.run_until_parked();
12240
12241 cx.assert_editor_state(
12242 &"
12243 ˇaaa
12244 ccc
12245 ddd
12246
12247 ggg
12248 hhh
12249
12250
12251 lll
12252 mmm
12253 NNN
12254
12255 qqq
12256 rrr
12257
12258 uuu
12259 111
12260 222
12261 333
12262
12263 666
12264 777
12265
12266 000
12267 !!!"
12268 .unindent(),
12269 );
12270
12271 cx.update_editor(|editor, cx| {
12272 editor.select_all(&SelectAll, cx);
12273 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12274 });
12275 cx.executor().run_until_parked();
12276
12277 cx.assert_diff_hunks(
12278 "
12279 aaa
12280 - bbb
12281 ccc
12282 ddd
12283
12284 ggg
12285 hhh
12286
12287
12288 lll
12289 mmm
12290 - nnn
12291 + NNN
12292
12293 qqq
12294 rrr
12295
12296 uuu
12297 111
12298 222
12299 333
12300
12301 + 666
12302 777
12303
12304 000
12305 !!!"
12306 .unindent(),
12307 );
12308}
12309
12310#[gpui::test]
12311async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
12312 init_test(cx, |_| {});
12313
12314 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
12315 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
12316
12317 let buffer = cx.new_model(|cx| {
12318 let mut buffer = Buffer::local(text.to_string(), cx);
12319 buffer.set_diff_base(Some(base.into()), cx);
12320 buffer
12321 });
12322
12323 let multi_buffer = cx.new_model(|cx| {
12324 let mut multibuffer = MultiBuffer::new(ReadWrite);
12325 multibuffer.push_excerpts(
12326 buffer.clone(),
12327 [
12328 ExcerptRange {
12329 context: Point::new(0, 0)..Point::new(2, 0),
12330 primary: None,
12331 },
12332 ExcerptRange {
12333 context: Point::new(5, 0)..Point::new(7, 0),
12334 primary: None,
12335 },
12336 ],
12337 cx,
12338 );
12339 multibuffer
12340 });
12341
12342 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12343 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12344 cx.run_until_parked();
12345
12346 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
12347 cx.executor().run_until_parked();
12348
12349 cx.assert_diff_hunks(
12350 "
12351 aaa
12352 - bbb
12353 + BBB
12354
12355 - ddd
12356 - eee
12357 + EEE
12358 fff
12359 "
12360 .unindent(),
12361 );
12362}
12363
12364#[gpui::test]
12365async fn test_edits_around_expanded_insertion_hunks(
12366 executor: BackgroundExecutor,
12367 cx: &mut gpui::TestAppContext,
12368) {
12369 init_test(cx, |_| {});
12370
12371 let mut cx = EditorTestContext::new(cx).await;
12372
12373 let diff_base = r#"
12374 use some::mod1;
12375 use some::mod2;
12376
12377 const A: u32 = 42;
12378
12379 fn main() {
12380 println!("hello");
12381
12382 println!("world");
12383 }
12384 "#
12385 .unindent();
12386 executor.run_until_parked();
12387 cx.set_state(
12388 &r#"
12389 use some::mod1;
12390 use some::mod2;
12391
12392 const A: u32 = 42;
12393 const B: u32 = 42;
12394 const C: u32 = 42;
12395 ˇ
12396
12397 fn main() {
12398 println!("hello");
12399
12400 println!("world");
12401 }
12402 "#
12403 .unindent(),
12404 );
12405
12406 cx.set_diff_base(Some(&diff_base));
12407 executor.run_until_parked();
12408
12409 cx.update_editor(|editor, cx| {
12410 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12411 });
12412 executor.run_until_parked();
12413
12414 cx.assert_diff_hunks(
12415 r#"
12416 use some::mod1;
12417 use some::mod2;
12418
12419 const A: u32 = 42;
12420 + const B: u32 = 42;
12421 + const C: u32 = 42;
12422 +
12423
12424 fn main() {
12425 println!("hello");
12426
12427 println!("world");
12428 }
12429 "#
12430 .unindent(),
12431 );
12432
12433 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
12434 executor.run_until_parked();
12435
12436 cx.assert_diff_hunks(
12437 r#"
12438 use some::mod1;
12439 use some::mod2;
12440
12441 const A: u32 = 42;
12442 + const B: u32 = 42;
12443 + const C: u32 = 42;
12444 + const D: u32 = 42;
12445 +
12446
12447 fn main() {
12448 println!("hello");
12449
12450 println!("world");
12451 }
12452 "#
12453 .unindent(),
12454 );
12455
12456 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
12457 executor.run_until_parked();
12458
12459 cx.assert_diff_hunks(
12460 r#"
12461 use some::mod1;
12462 use some::mod2;
12463
12464 const A: u32 = 42;
12465 + const B: u32 = 42;
12466 + const C: u32 = 42;
12467 + const D: u32 = 42;
12468 + const E: u32 = 42;
12469 +
12470
12471 fn main() {
12472 println!("hello");
12473
12474 println!("world");
12475 }
12476 "#
12477 .unindent(),
12478 );
12479
12480 cx.update_editor(|editor, cx| {
12481 editor.delete_line(&DeleteLine, cx);
12482 });
12483 executor.run_until_parked();
12484
12485 cx.assert_diff_hunks(
12486 r#"
12487 use some::mod1;
12488 use some::mod2;
12489
12490 const A: u32 = 42;
12491 + const B: u32 = 42;
12492 + const C: u32 = 42;
12493 + const D: u32 = 42;
12494 + const E: u32 = 42;
12495
12496 fn main() {
12497 println!("hello");
12498
12499 println!("world");
12500 }
12501 "#
12502 .unindent(),
12503 );
12504
12505 cx.update_editor(|editor, cx| {
12506 editor.move_up(&MoveUp, cx);
12507 editor.delete_line(&DeleteLine, cx);
12508 editor.move_up(&MoveUp, cx);
12509 editor.delete_line(&DeleteLine, cx);
12510 editor.move_up(&MoveUp, cx);
12511 editor.delete_line(&DeleteLine, cx);
12512 });
12513 executor.run_until_parked();
12514 cx.assert_editor_state(
12515 &r#"
12516 use some::mod1;
12517 use some::mod2;
12518
12519 const A: u32 = 42;
12520 const B: u32 = 42;
12521 ˇ
12522 fn main() {
12523 println!("hello");
12524
12525 println!("world");
12526 }
12527 "#
12528 .unindent(),
12529 );
12530
12531 cx.assert_diff_hunks(
12532 r#"
12533 use some::mod1;
12534 use some::mod2;
12535
12536 const A: u32 = 42;
12537 + const B: u32 = 42;
12538
12539 fn main() {
12540 println!("hello");
12541
12542 println!("world");
12543 }
12544 "#
12545 .unindent(),
12546 );
12547
12548 cx.update_editor(|editor, cx| {
12549 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
12550 editor.delete_line(&DeleteLine, cx);
12551 });
12552 executor.run_until_parked();
12553 cx.assert_diff_hunks(
12554 r#"
12555 use some::mod1;
12556 - use some::mod2;
12557 -
12558 - const A: u32 = 42;
12559
12560 fn main() {
12561 println!("hello");
12562
12563 println!("world");
12564 }
12565 "#
12566 .unindent(),
12567 );
12568}
12569
12570#[gpui::test]
12571async fn test_edits_around_expanded_deletion_hunks(
12572 executor: BackgroundExecutor,
12573 cx: &mut gpui::TestAppContext,
12574) {
12575 init_test(cx, |_| {});
12576
12577 let mut cx = EditorTestContext::new(cx).await;
12578
12579 let diff_base = r#"
12580 use some::mod1;
12581 use some::mod2;
12582
12583 const A: u32 = 42;
12584 const B: u32 = 42;
12585 const C: u32 = 42;
12586
12587
12588 fn main() {
12589 println!("hello");
12590
12591 println!("world");
12592 }
12593 "#
12594 .unindent();
12595 executor.run_until_parked();
12596 cx.set_state(
12597 &r#"
12598 use some::mod1;
12599 use some::mod2;
12600
12601 ˇconst B: u32 = 42;
12602 const C: u32 = 42;
12603
12604
12605 fn main() {
12606 println!("hello");
12607
12608 println!("world");
12609 }
12610 "#
12611 .unindent(),
12612 );
12613
12614 cx.set_diff_base(Some(&diff_base));
12615 executor.run_until_parked();
12616
12617 cx.update_editor(|editor, cx| {
12618 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12619 });
12620 executor.run_until_parked();
12621
12622 cx.assert_diff_hunks(
12623 r#"
12624 use some::mod1;
12625 use some::mod2;
12626
12627 - const A: u32 = 42;
12628 const B: u32 = 42;
12629 const C: u32 = 42;
12630
12631
12632 fn main() {
12633 println!("hello");
12634
12635 println!("world");
12636 }
12637 "#
12638 .unindent(),
12639 );
12640
12641 cx.update_editor(|editor, cx| {
12642 editor.delete_line(&DeleteLine, cx);
12643 });
12644 executor.run_until_parked();
12645 cx.assert_editor_state(
12646 &r#"
12647 use some::mod1;
12648 use some::mod2;
12649
12650 ˇconst C: u32 = 42;
12651
12652
12653 fn main() {
12654 println!("hello");
12655
12656 println!("world");
12657 }
12658 "#
12659 .unindent(),
12660 );
12661 cx.assert_diff_hunks(
12662 r#"
12663 use some::mod1;
12664 use some::mod2;
12665
12666 - const A: u32 = 42;
12667 - const B: u32 = 42;
12668 const C: u32 = 42;
12669
12670
12671 fn main() {
12672 println!("hello");
12673
12674 println!("world");
12675 }
12676 "#
12677 .unindent(),
12678 );
12679
12680 cx.update_editor(|editor, cx| {
12681 editor.delete_line(&DeleteLine, cx);
12682 });
12683 executor.run_until_parked();
12684 cx.assert_editor_state(
12685 &r#"
12686 use some::mod1;
12687 use some::mod2;
12688
12689 ˇ
12690
12691 fn main() {
12692 println!("hello");
12693
12694 println!("world");
12695 }
12696 "#
12697 .unindent(),
12698 );
12699 cx.assert_diff_hunks(
12700 r#"
12701 use some::mod1;
12702 use some::mod2;
12703
12704 - const A: u32 = 42;
12705 - const B: u32 = 42;
12706 - const C: u32 = 42;
12707
12708
12709 fn main() {
12710 println!("hello");
12711
12712 println!("world");
12713 }
12714 "#
12715 .unindent(),
12716 );
12717
12718 cx.update_editor(|editor, cx| {
12719 editor.handle_input("replacement", cx);
12720 });
12721 executor.run_until_parked();
12722 cx.assert_editor_state(
12723 &r#"
12724 use some::mod1;
12725 use some::mod2;
12726
12727 replacementˇ
12728
12729 fn main() {
12730 println!("hello");
12731
12732 println!("world");
12733 }
12734 "#
12735 .unindent(),
12736 );
12737 cx.assert_diff_hunks(
12738 r#"
12739 use some::mod1;
12740 use some::mod2;
12741
12742 - const A: u32 = 42;
12743 - const B: u32 = 42;
12744 - const C: u32 = 42;
12745 -
12746 + replacement
12747
12748 fn main() {
12749 println!("hello");
12750
12751 println!("world");
12752 }
12753 "#
12754 .unindent(),
12755 );
12756}
12757
12758#[gpui::test]
12759async fn test_edit_after_expanded_modification_hunk(
12760 executor: BackgroundExecutor,
12761 cx: &mut gpui::TestAppContext,
12762) {
12763 init_test(cx, |_| {});
12764
12765 let mut cx = EditorTestContext::new(cx).await;
12766
12767 let diff_base = r#"
12768 use some::mod1;
12769 use some::mod2;
12770
12771 const A: u32 = 42;
12772 const B: u32 = 42;
12773 const C: u32 = 42;
12774 const D: u32 = 42;
12775
12776
12777 fn main() {
12778 println!("hello");
12779
12780 println!("world");
12781 }"#
12782 .unindent();
12783
12784 cx.set_state(
12785 &r#"
12786 use some::mod1;
12787 use some::mod2;
12788
12789 const A: u32 = 42;
12790 const B: u32 = 42;
12791 const C: u32 = 43ˇ
12792 const D: u32 = 42;
12793
12794
12795 fn main() {
12796 println!("hello");
12797
12798 println!("world");
12799 }"#
12800 .unindent(),
12801 );
12802
12803 cx.set_diff_base(Some(&diff_base));
12804 executor.run_until_parked();
12805 cx.update_editor(|editor, cx| {
12806 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12807 });
12808 executor.run_until_parked();
12809
12810 cx.assert_diff_hunks(
12811 r#"
12812 use some::mod1;
12813 use some::mod2;
12814
12815 const A: u32 = 42;
12816 const B: u32 = 42;
12817 - const C: u32 = 42;
12818 + const C: u32 = 43
12819 const D: u32 = 42;
12820
12821
12822 fn main() {
12823 println!("hello");
12824
12825 println!("world");
12826 }"#
12827 .unindent(),
12828 );
12829
12830 cx.update_editor(|editor, cx| {
12831 editor.handle_input("\nnew_line\n", cx);
12832 });
12833 executor.run_until_parked();
12834
12835 cx.assert_diff_hunks(
12836 r#"
12837 use some::mod1;
12838 use some::mod2;
12839
12840 const A: u32 = 42;
12841 const B: u32 = 42;
12842 - const C: u32 = 42;
12843 + const C: u32 = 43
12844 + new_line
12845 +
12846 const D: u32 = 42;
12847
12848
12849 fn main() {
12850 println!("hello");
12851
12852 println!("world");
12853 }"#
12854 .unindent(),
12855 );
12856}
12857
12858async fn setup_indent_guides_editor(
12859 text: &str,
12860 cx: &mut gpui::TestAppContext,
12861) -> (BufferId, EditorTestContext) {
12862 init_test(cx, |_| {});
12863
12864 let mut cx = EditorTestContext::new(cx).await;
12865
12866 let buffer_id = cx.update_editor(|editor, cx| {
12867 editor.set_text(text, cx);
12868 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12869
12870 buffer_ids[0]
12871 });
12872
12873 (buffer_id, cx)
12874}
12875
12876fn assert_indent_guides(
12877 range: Range<u32>,
12878 expected: Vec<IndentGuide>,
12879 active_indices: Option<Vec<usize>>,
12880 cx: &mut EditorTestContext,
12881) {
12882 let indent_guides = cx.update_editor(|editor, cx| {
12883 let snapshot = editor.snapshot(cx).display_snapshot;
12884 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12885 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12886 true,
12887 &snapshot,
12888 cx,
12889 );
12890
12891 indent_guides.sort_by(|a, b| {
12892 a.depth.cmp(&b.depth).then(
12893 a.start_row
12894 .cmp(&b.start_row)
12895 .then(a.end_row.cmp(&b.end_row)),
12896 )
12897 });
12898 indent_guides
12899 });
12900
12901 if let Some(expected) = active_indices {
12902 let active_indices = cx.update_editor(|editor, cx| {
12903 let snapshot = editor.snapshot(cx).display_snapshot;
12904 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
12905 });
12906
12907 assert_eq!(
12908 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
12909 expected,
12910 "Active indent guide indices do not match"
12911 );
12912 }
12913
12914 let expected: Vec<_> = expected
12915 .into_iter()
12916 .map(|guide| MultiBufferIndentGuide {
12917 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
12918 buffer: guide,
12919 })
12920 .collect();
12921
12922 assert_eq!(indent_guides, expected, "Indent guides do not match");
12923}
12924
12925fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
12926 IndentGuide {
12927 buffer_id,
12928 start_row,
12929 end_row,
12930 depth,
12931 tab_size: 4,
12932 settings: IndentGuideSettings {
12933 enabled: true,
12934 line_width: 1,
12935 active_line_width: 1,
12936 ..Default::default()
12937 },
12938 }
12939}
12940
12941#[gpui::test]
12942async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
12943 let (buffer_id, mut cx) = setup_indent_guides_editor(
12944 &"
12945 fn main() {
12946 let a = 1;
12947 }"
12948 .unindent(),
12949 cx,
12950 )
12951 .await;
12952
12953 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
12954}
12955
12956#[gpui::test]
12957async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
12958 let (buffer_id, mut cx) = setup_indent_guides_editor(
12959 &"
12960 fn main() {
12961 let a = 1;
12962 let b = 2;
12963 }"
12964 .unindent(),
12965 cx,
12966 )
12967 .await;
12968
12969 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
12970}
12971
12972#[gpui::test]
12973async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
12974 let (buffer_id, mut cx) = setup_indent_guides_editor(
12975 &"
12976 fn main() {
12977 let a = 1;
12978 if a == 3 {
12979 let b = 2;
12980 } else {
12981 let c = 3;
12982 }
12983 }"
12984 .unindent(),
12985 cx,
12986 )
12987 .await;
12988
12989 assert_indent_guides(
12990 0..8,
12991 vec![
12992 indent_guide(buffer_id, 1, 6, 0),
12993 indent_guide(buffer_id, 3, 3, 1),
12994 indent_guide(buffer_id, 5, 5, 1),
12995 ],
12996 None,
12997 &mut cx,
12998 );
12999}
13000
13001#[gpui::test]
13002async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
13003 let (buffer_id, mut cx) = setup_indent_guides_editor(
13004 &"
13005 fn main() {
13006 let a = 1;
13007 let b = 2;
13008 let c = 3;
13009 }"
13010 .unindent(),
13011 cx,
13012 )
13013 .await;
13014
13015 assert_indent_guides(
13016 0..5,
13017 vec![
13018 indent_guide(buffer_id, 1, 3, 0),
13019 indent_guide(buffer_id, 2, 2, 1),
13020 ],
13021 None,
13022 &mut cx,
13023 );
13024}
13025
13026#[gpui::test]
13027async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
13028 let (buffer_id, mut cx) = setup_indent_guides_editor(
13029 &"
13030 fn main() {
13031 let a = 1;
13032
13033 let c = 3;
13034 }"
13035 .unindent(),
13036 cx,
13037 )
13038 .await;
13039
13040 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
13041}
13042
13043#[gpui::test]
13044async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
13045 let (buffer_id, mut cx) = setup_indent_guides_editor(
13046 &"
13047 fn main() {
13048 let a = 1;
13049
13050 let c = 3;
13051
13052 if a == 3 {
13053 let b = 2;
13054 } else {
13055 let c = 3;
13056 }
13057 }"
13058 .unindent(),
13059 cx,
13060 )
13061 .await;
13062
13063 assert_indent_guides(
13064 0..11,
13065 vec![
13066 indent_guide(buffer_id, 1, 9, 0),
13067 indent_guide(buffer_id, 6, 6, 1),
13068 indent_guide(buffer_id, 8, 8, 1),
13069 ],
13070 None,
13071 &mut cx,
13072 );
13073}
13074
13075#[gpui::test]
13076async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
13077 let (buffer_id, mut cx) = setup_indent_guides_editor(
13078 &"
13079 fn main() {
13080 let a = 1;
13081
13082 let c = 3;
13083
13084 if a == 3 {
13085 let b = 2;
13086 } else {
13087 let c = 3;
13088 }
13089 }"
13090 .unindent(),
13091 cx,
13092 )
13093 .await;
13094
13095 assert_indent_guides(
13096 1..11,
13097 vec![
13098 indent_guide(buffer_id, 1, 9, 0),
13099 indent_guide(buffer_id, 6, 6, 1),
13100 indent_guide(buffer_id, 8, 8, 1),
13101 ],
13102 None,
13103 &mut cx,
13104 );
13105}
13106
13107#[gpui::test]
13108async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
13109 let (buffer_id, mut cx) = setup_indent_guides_editor(
13110 &"
13111 fn main() {
13112 let a = 1;
13113
13114 let c = 3;
13115
13116 if a == 3 {
13117 let b = 2;
13118 } else {
13119 let c = 3;
13120 }
13121 }"
13122 .unindent(),
13123 cx,
13124 )
13125 .await;
13126
13127 assert_indent_guides(
13128 1..10,
13129 vec![
13130 indent_guide(buffer_id, 1, 9, 0),
13131 indent_guide(buffer_id, 6, 6, 1),
13132 indent_guide(buffer_id, 8, 8, 1),
13133 ],
13134 None,
13135 &mut cx,
13136 );
13137}
13138
13139#[gpui::test]
13140async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
13141 let (buffer_id, mut cx) = setup_indent_guides_editor(
13142 &"
13143 block1
13144 block2
13145 block3
13146 block4
13147 block2
13148 block1
13149 block1"
13150 .unindent(),
13151 cx,
13152 )
13153 .await;
13154
13155 assert_indent_guides(
13156 1..10,
13157 vec![
13158 indent_guide(buffer_id, 1, 4, 0),
13159 indent_guide(buffer_id, 2, 3, 1),
13160 indent_guide(buffer_id, 3, 3, 2),
13161 ],
13162 None,
13163 &mut cx,
13164 );
13165}
13166
13167#[gpui::test]
13168async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
13169 let (buffer_id, mut cx) = setup_indent_guides_editor(
13170 &"
13171 block1
13172 block2
13173 block3
13174
13175 block1
13176 block1"
13177 .unindent(),
13178 cx,
13179 )
13180 .await;
13181
13182 assert_indent_guides(
13183 0..6,
13184 vec![
13185 indent_guide(buffer_id, 1, 2, 0),
13186 indent_guide(buffer_id, 2, 2, 1),
13187 ],
13188 None,
13189 &mut cx,
13190 );
13191}
13192
13193#[gpui::test]
13194async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
13195 let (buffer_id, mut cx) = setup_indent_guides_editor(
13196 &"
13197 block1
13198
13199
13200
13201 block2
13202 "
13203 .unindent(),
13204 cx,
13205 )
13206 .await;
13207
13208 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13209}
13210
13211#[gpui::test]
13212async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
13213 let (buffer_id, mut cx) = setup_indent_guides_editor(
13214 &"
13215 def a:
13216 \tb = 3
13217 \tif True:
13218 \t\tc = 4
13219 \t\td = 5
13220 \tprint(b)
13221 "
13222 .unindent(),
13223 cx,
13224 )
13225 .await;
13226
13227 assert_indent_guides(
13228 0..6,
13229 vec![
13230 indent_guide(buffer_id, 1, 6, 0),
13231 indent_guide(buffer_id, 3, 4, 1),
13232 ],
13233 None,
13234 &mut cx,
13235 );
13236}
13237
13238#[gpui::test]
13239async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13240 let (buffer_id, mut cx) = setup_indent_guides_editor(
13241 &"
13242 fn main() {
13243 let a = 1;
13244 }"
13245 .unindent(),
13246 cx,
13247 )
13248 .await;
13249
13250 cx.update_editor(|editor, cx| {
13251 editor.change_selections(None, cx, |s| {
13252 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13253 });
13254 });
13255
13256 assert_indent_guides(
13257 0..3,
13258 vec![indent_guide(buffer_id, 1, 1, 0)],
13259 Some(vec![0]),
13260 &mut cx,
13261 );
13262}
13263
13264#[gpui::test]
13265async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13266 let (buffer_id, mut cx) = setup_indent_guides_editor(
13267 &"
13268 fn main() {
13269 if 1 == 2 {
13270 let a = 1;
13271 }
13272 }"
13273 .unindent(),
13274 cx,
13275 )
13276 .await;
13277
13278 cx.update_editor(|editor, cx| {
13279 editor.change_selections(None, cx, |s| {
13280 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13281 });
13282 });
13283
13284 assert_indent_guides(
13285 0..4,
13286 vec![
13287 indent_guide(buffer_id, 1, 3, 0),
13288 indent_guide(buffer_id, 2, 2, 1),
13289 ],
13290 Some(vec![1]),
13291 &mut cx,
13292 );
13293
13294 cx.update_editor(|editor, cx| {
13295 editor.change_selections(None, cx, |s| {
13296 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13297 });
13298 });
13299
13300 assert_indent_guides(
13301 0..4,
13302 vec![
13303 indent_guide(buffer_id, 1, 3, 0),
13304 indent_guide(buffer_id, 2, 2, 1),
13305 ],
13306 Some(vec![1]),
13307 &mut cx,
13308 );
13309
13310 cx.update_editor(|editor, cx| {
13311 editor.change_selections(None, cx, |s| {
13312 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13313 });
13314 });
13315
13316 assert_indent_guides(
13317 0..4,
13318 vec![
13319 indent_guide(buffer_id, 1, 3, 0),
13320 indent_guide(buffer_id, 2, 2, 1),
13321 ],
13322 Some(vec![0]),
13323 &mut cx,
13324 );
13325}
13326
13327#[gpui::test]
13328async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13329 let (buffer_id, mut cx) = setup_indent_guides_editor(
13330 &"
13331 fn main() {
13332 let a = 1;
13333
13334 let b = 2;
13335 }"
13336 .unindent(),
13337 cx,
13338 )
13339 .await;
13340
13341 cx.update_editor(|editor, cx| {
13342 editor.change_selections(None, cx, |s| {
13343 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13344 });
13345 });
13346
13347 assert_indent_guides(
13348 0..5,
13349 vec![indent_guide(buffer_id, 1, 3, 0)],
13350 Some(vec![0]),
13351 &mut cx,
13352 );
13353}
13354
13355#[gpui::test]
13356async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13357 let (buffer_id, mut cx) = setup_indent_guides_editor(
13358 &"
13359 def m:
13360 a = 1
13361 pass"
13362 .unindent(),
13363 cx,
13364 )
13365 .await;
13366
13367 cx.update_editor(|editor, cx| {
13368 editor.change_selections(None, cx, |s| {
13369 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13370 });
13371 });
13372
13373 assert_indent_guides(
13374 0..3,
13375 vec![indent_guide(buffer_id, 1, 2, 0)],
13376 Some(vec![0]),
13377 &mut cx,
13378 );
13379}
13380
13381#[gpui::test]
13382fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13383 init_test(cx, |_| {});
13384
13385 let editor = cx.add_window(|cx| {
13386 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13387 build_editor(buffer, cx)
13388 });
13389
13390 let render_args = Arc::new(Mutex::new(None));
13391 let snapshot = editor
13392 .update(cx, |editor, cx| {
13393 let snapshot = editor.buffer().read(cx).snapshot(cx);
13394 let range =
13395 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13396
13397 struct RenderArgs {
13398 row: MultiBufferRow,
13399 folded: bool,
13400 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13401 }
13402
13403 let crease = Crease::inline(
13404 range,
13405 FoldPlaceholder::test(),
13406 {
13407 let toggle_callback = render_args.clone();
13408 move |row, folded, callback, _cx| {
13409 *toggle_callback.lock() = Some(RenderArgs {
13410 row,
13411 folded,
13412 callback,
13413 });
13414 div()
13415 }
13416 },
13417 |_row, _folded, _cx| div(),
13418 );
13419
13420 editor.insert_creases(Some(crease), cx);
13421 let snapshot = editor.snapshot(cx);
13422 let _div =
13423 snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13424 snapshot
13425 })
13426 .unwrap();
13427
13428 let render_args = render_args.lock().take().unwrap();
13429 assert_eq!(render_args.row, MultiBufferRow(1));
13430 assert!(!render_args.folded);
13431 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13432
13433 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13434 .unwrap();
13435 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13436 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13437
13438 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13439 .unwrap();
13440 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13441 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13442}
13443
13444#[gpui::test]
13445async fn test_input_text(cx: &mut gpui::TestAppContext) {
13446 init_test(cx, |_| {});
13447 let mut cx = EditorTestContext::new(cx).await;
13448
13449 cx.set_state(
13450 &r#"ˇone
13451 two
13452
13453 three
13454 fourˇ
13455 five
13456
13457 siˇx"#
13458 .unindent(),
13459 );
13460
13461 cx.dispatch_action(HandleInput(String::new()));
13462 cx.assert_editor_state(
13463 &r#"ˇone
13464 two
13465
13466 three
13467 fourˇ
13468 five
13469
13470 siˇx"#
13471 .unindent(),
13472 );
13473
13474 cx.dispatch_action(HandleInput("AAAA".to_string()));
13475 cx.assert_editor_state(
13476 &r#"AAAAˇone
13477 two
13478
13479 three
13480 fourAAAAˇ
13481 five
13482
13483 siAAAAˇx"#
13484 .unindent(),
13485 );
13486}
13487
13488#[gpui::test]
13489async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13490 init_test(cx, |_| {});
13491
13492 let mut cx = EditorTestContext::new(cx).await;
13493 cx.set_state(
13494 r#"let foo = 1;
13495let foo = 2;
13496let foo = 3;
13497let fooˇ = 4;
13498let foo = 5;
13499let foo = 6;
13500let foo = 7;
13501let foo = 8;
13502let foo = 9;
13503let foo = 10;
13504let foo = 11;
13505let foo = 12;
13506let foo = 13;
13507let foo = 14;
13508let foo = 15;"#,
13509 );
13510
13511 cx.update_editor(|e, cx| {
13512 assert_eq!(
13513 e.next_scroll_position,
13514 NextScrollCursorCenterTopBottom::Center,
13515 "Default next scroll direction is center",
13516 );
13517
13518 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13519 assert_eq!(
13520 e.next_scroll_position,
13521 NextScrollCursorCenterTopBottom::Top,
13522 "After center, next scroll direction should be top",
13523 );
13524
13525 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13526 assert_eq!(
13527 e.next_scroll_position,
13528 NextScrollCursorCenterTopBottom::Bottom,
13529 "After top, next scroll direction should be bottom",
13530 );
13531
13532 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13533 assert_eq!(
13534 e.next_scroll_position,
13535 NextScrollCursorCenterTopBottom::Center,
13536 "After bottom, scrolling should start over",
13537 );
13538
13539 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13540 assert_eq!(
13541 e.next_scroll_position,
13542 NextScrollCursorCenterTopBottom::Top,
13543 "Scrolling continues if retriggered fast enough"
13544 );
13545 });
13546
13547 cx.executor()
13548 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13549 cx.executor().run_until_parked();
13550 cx.update_editor(|e, _| {
13551 assert_eq!(
13552 e.next_scroll_position,
13553 NextScrollCursorCenterTopBottom::Center,
13554 "If scrolling is not triggered fast enough, it should reset"
13555 );
13556 });
13557}
13558
13559#[gpui::test]
13560async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13561 init_test(cx, |_| {});
13562 let mut cx = EditorLspTestContext::new_rust(
13563 lsp::ServerCapabilities {
13564 definition_provider: Some(lsp::OneOf::Left(true)),
13565 references_provider: Some(lsp::OneOf::Left(true)),
13566 ..lsp::ServerCapabilities::default()
13567 },
13568 cx,
13569 )
13570 .await;
13571
13572 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13573 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13574 move |params, _| async move {
13575 if empty_go_to_definition {
13576 Ok(None)
13577 } else {
13578 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13579 uri: params.text_document_position_params.text_document.uri,
13580 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13581 })))
13582 }
13583 },
13584 );
13585 let references =
13586 cx.lsp
13587 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13588 Ok(Some(vec![lsp::Location {
13589 uri: params.text_document_position.text_document.uri,
13590 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13591 }]))
13592 });
13593 (go_to_definition, references)
13594 };
13595
13596 cx.set_state(
13597 &r#"fn one() {
13598 let mut a = ˇtwo();
13599 }
13600
13601 fn two() {}"#
13602 .unindent(),
13603 );
13604 set_up_lsp_handlers(false, &mut cx);
13605 let navigated = cx
13606 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13607 .await
13608 .expect("Failed to navigate to definition");
13609 assert_eq!(
13610 navigated,
13611 Navigated::Yes,
13612 "Should have navigated to definition from the GetDefinition response"
13613 );
13614 cx.assert_editor_state(
13615 &r#"fn one() {
13616 let mut a = two();
13617 }
13618
13619 fn «twoˇ»() {}"#
13620 .unindent(),
13621 );
13622
13623 let editors = cx.update_workspace(|workspace, cx| {
13624 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13625 });
13626 cx.update_editor(|_, test_editor_cx| {
13627 assert_eq!(
13628 editors.len(),
13629 1,
13630 "Initially, only one, test, editor should be open in the workspace"
13631 );
13632 assert_eq!(
13633 test_editor_cx.view(),
13634 editors.last().expect("Asserted len is 1")
13635 );
13636 });
13637
13638 set_up_lsp_handlers(true, &mut cx);
13639 let navigated = cx
13640 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13641 .await
13642 .expect("Failed to navigate to lookup references");
13643 assert_eq!(
13644 navigated,
13645 Navigated::Yes,
13646 "Should have navigated to references as a fallback after empty GoToDefinition response"
13647 );
13648 // We should not change the selections in the existing file,
13649 // if opening another milti buffer with the references
13650 cx.assert_editor_state(
13651 &r#"fn one() {
13652 let mut a = two();
13653 }
13654
13655 fn «twoˇ»() {}"#
13656 .unindent(),
13657 );
13658 let editors = cx.update_workspace(|workspace, cx| {
13659 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13660 });
13661 cx.update_editor(|_, test_editor_cx| {
13662 assert_eq!(
13663 editors.len(),
13664 2,
13665 "After falling back to references search, we open a new editor with the results"
13666 );
13667 let references_fallback_text = editors
13668 .into_iter()
13669 .find(|new_editor| new_editor != test_editor_cx.view())
13670 .expect("Should have one non-test editor now")
13671 .read(test_editor_cx)
13672 .text(test_editor_cx);
13673 assert_eq!(
13674 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13675 "Should use the range from the references response and not the GoToDefinition one"
13676 );
13677 });
13678}
13679
13680#[gpui::test]
13681async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
13682 init_test(cx, |_| {});
13683
13684 let language = Arc::new(Language::new(
13685 LanguageConfig::default(),
13686 Some(tree_sitter_rust::LANGUAGE.into()),
13687 ));
13688
13689 let text = r#"
13690 #[cfg(test)]
13691 mod tests() {
13692 #[test]
13693 fn runnable_1() {
13694 let a = 1;
13695 }
13696
13697 #[test]
13698 fn runnable_2() {
13699 let a = 1;
13700 let b = 2;
13701 }
13702 }
13703 "#
13704 .unindent();
13705
13706 let fs = FakeFs::new(cx.executor());
13707 fs.insert_file("/file.rs", Default::default()).await;
13708
13709 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13710 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
13711 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13712 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
13713 let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
13714
13715 let editor = cx.new_view(|cx| {
13716 Editor::new(
13717 EditorMode::Full,
13718 multi_buffer,
13719 Some(project.clone()),
13720 true,
13721 cx,
13722 )
13723 });
13724
13725 editor.update(cx, |editor, cx| {
13726 editor.tasks.insert(
13727 (buffer.read(cx).remote_id(), 3),
13728 RunnableTasks {
13729 templates: vec![],
13730 offset: MultiBufferOffset(43),
13731 column: 0,
13732 extra_variables: HashMap::default(),
13733 context_range: BufferOffset(43)..BufferOffset(85),
13734 },
13735 );
13736 editor.tasks.insert(
13737 (buffer.read(cx).remote_id(), 8),
13738 RunnableTasks {
13739 templates: vec![],
13740 offset: MultiBufferOffset(86),
13741 column: 0,
13742 extra_variables: HashMap::default(),
13743 context_range: BufferOffset(86)..BufferOffset(191),
13744 },
13745 );
13746
13747 // Test finding task when cursor is inside function body
13748 editor.change_selections(None, cx, |s| {
13749 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
13750 });
13751 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13752 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
13753
13754 // Test finding task when cursor is on function name
13755 editor.change_selections(None, cx, |s| {
13756 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
13757 });
13758 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13759 assert_eq!(row, 8, "Should find task when cursor is on function name");
13760 });
13761}
13762
13763fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13764 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13765 point..point
13766}
13767
13768fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13769 let (text, ranges) = marked_text_ranges(marked_text, true);
13770 assert_eq!(view.text(cx), text);
13771 assert_eq!(
13772 view.selections.ranges(cx),
13773 ranges,
13774 "Assert selections are {}",
13775 marked_text
13776 );
13777}
13778
13779pub fn handle_signature_help_request(
13780 cx: &mut EditorLspTestContext,
13781 mocked_response: lsp::SignatureHelp,
13782) -> impl Future<Output = ()> {
13783 let mut request =
13784 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13785 let mocked_response = mocked_response.clone();
13786 async move { Ok(Some(mocked_response)) }
13787 });
13788
13789 async move {
13790 request.next().await;
13791 }
13792}
13793
13794/// Handle completion request passing a marked string specifying where the completion
13795/// should be triggered from using '|' character, what range should be replaced, and what completions
13796/// should be returned using '<' and '>' to delimit the range
13797pub fn handle_completion_request(
13798 cx: &mut EditorLspTestContext,
13799 marked_string: &str,
13800 completions: Vec<&'static str>,
13801 counter: Arc<AtomicUsize>,
13802) -> impl Future<Output = ()> {
13803 let complete_from_marker: TextRangeMarker = '|'.into();
13804 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13805 let (_, mut marked_ranges) = marked_text_ranges_by(
13806 marked_string,
13807 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13808 );
13809
13810 let complete_from_position =
13811 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13812 let replace_range =
13813 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13814
13815 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13816 let completions = completions.clone();
13817 counter.fetch_add(1, atomic::Ordering::Release);
13818 async move {
13819 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13820 assert_eq!(
13821 params.text_document_position.position,
13822 complete_from_position
13823 );
13824 Ok(Some(lsp::CompletionResponse::Array(
13825 completions
13826 .iter()
13827 .map(|completion_text| lsp::CompletionItem {
13828 label: completion_text.to_string(),
13829 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13830 range: replace_range,
13831 new_text: completion_text.to_string(),
13832 })),
13833 ..Default::default()
13834 })
13835 .collect(),
13836 )))
13837 }
13838 });
13839
13840 async move {
13841 request.next().await;
13842 }
13843}
13844
13845fn handle_resolve_completion_request(
13846 cx: &mut EditorLspTestContext,
13847 edits: Option<Vec<(&'static str, &'static str)>>,
13848) -> impl Future<Output = ()> {
13849 let edits = edits.map(|edits| {
13850 edits
13851 .iter()
13852 .map(|(marked_string, new_text)| {
13853 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13854 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13855 lsp::TextEdit::new(replace_range, new_text.to_string())
13856 })
13857 .collect::<Vec<_>>()
13858 });
13859
13860 let mut request =
13861 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13862 let edits = edits.clone();
13863 async move {
13864 Ok(lsp::CompletionItem {
13865 additional_text_edits: edits,
13866 ..Default::default()
13867 })
13868 }
13869 });
13870
13871 async move {
13872 request.next().await;
13873 }
13874}
13875
13876pub(crate) fn update_test_language_settings(
13877 cx: &mut TestAppContext,
13878 f: impl Fn(&mut AllLanguageSettingsContent),
13879) {
13880 cx.update(|cx| {
13881 SettingsStore::update_global(cx, |store, cx| {
13882 store.update_user_settings::<AllLanguageSettings>(cx, f);
13883 });
13884 });
13885}
13886
13887pub(crate) fn update_test_project_settings(
13888 cx: &mut TestAppContext,
13889 f: impl Fn(&mut ProjectSettings),
13890) {
13891 cx.update(|cx| {
13892 SettingsStore::update_global(cx, |store, cx| {
13893 store.update_user_settings::<ProjectSettings>(cx, f);
13894 });
13895 });
13896}
13897
13898pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
13899 cx.update(|cx| {
13900 assets::Assets.load_test_fonts(cx);
13901 let store = SettingsStore::test(cx);
13902 cx.set_global(store);
13903 theme::init(theme::LoadThemes::JustBase, cx);
13904 release_channel::init(SemanticVersion::default(), cx);
13905 client::init_settings(cx);
13906 language::init(cx);
13907 Project::init_settings(cx);
13908 workspace::init_settings(cx);
13909 crate::init(cx);
13910 });
13911
13912 update_test_language_settings(cx, f);
13913}
13914
13915pub(crate) fn rust_lang() -> Arc<Language> {
13916 Arc::new(Language::new(
13917 LanguageConfig {
13918 name: "Rust".into(),
13919 matcher: LanguageMatcher {
13920 path_suffixes: vec!["rs".to_string()],
13921 ..Default::default()
13922 },
13923 ..Default::default()
13924 },
13925 Some(tree_sitter_rust::LANGUAGE.into()),
13926 ))
13927}
13928
13929#[track_caller]
13930fn assert_hunk_revert(
13931 not_reverted_text_with_selections: &str,
13932 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
13933 expected_reverted_text_with_selections: &str,
13934 base_text: &str,
13935 cx: &mut EditorLspTestContext,
13936) {
13937 cx.set_state(not_reverted_text_with_selections);
13938 cx.update_editor(|editor, cx| {
13939 editor
13940 .buffer()
13941 .read(cx)
13942 .as_singleton()
13943 .unwrap()
13944 .update(cx, |buffer, cx| {
13945 buffer.set_diff_base(Some(base_text.into()), cx);
13946 });
13947 });
13948 cx.executor().run_until_parked();
13949
13950 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
13951 let snapshot = editor.buffer().read(cx).snapshot(cx);
13952 let reverted_hunk_statuses = snapshot
13953 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
13954 .map(|hunk| hunk_status(&hunk))
13955 .collect::<Vec<_>>();
13956
13957 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
13958 reverted_hunk_statuses
13959 });
13960 cx.executor().run_until_parked();
13961 cx.assert_editor_state(expected_reverted_text_with_selections);
13962 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
13963}