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::{buffer_store::BufferChangeSet, FakeFs};
29use project::{
30 lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
31 project_settings::{LspSettings, ProjectSettings},
32};
33use serde_json::{self, json};
34use std::sync::atomic::AtomicUsize;
35use std::sync::atomic::{self, AtomicBool};
36use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
37use test::editor_lsp_test_context::rust_lang;
38use unindent::Unindent;
39use util::{
40 assert_set_eq,
41 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
42};
43use workspace::{
44 item::{FollowEvent, FollowableItem, Item, ItemHandle},
45 NavigationEntry, ViewId,
46};
47
48#[gpui::test]
49fn test_edit_events(cx: &mut TestAppContext) {
50 init_test(cx, |_| {});
51
52 let buffer = cx.new_model(|cx| {
53 let mut buffer = language::Buffer::local("123456", cx);
54 buffer.set_group_interval(Duration::from_secs(1));
55 buffer
56 });
57
58 let events = Rc::new(RefCell::new(Vec::new()));
59 let editor1 = cx.add_window({
60 let events = events.clone();
61 |cx| {
62 let view = cx.view().clone();
63 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
64 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
65 EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
66 _ => {}
67 })
68 .detach();
69 Editor::for_buffer(buffer.clone(), None, cx)
70 }
71 });
72
73 let editor2 = cx.add_window({
74 let events = events.clone();
75 |cx| {
76 cx.subscribe(
77 &cx.view().clone(),
78 move |_, _, event: &EditorEvent, _| match event {
79 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
80 EditorEvent::BufferEdited => {
81 events.borrow_mut().push(("editor2", "buffer edited"))
82 }
83 _ => {}
84 },
85 )
86 .detach();
87 Editor::for_buffer(buffer.clone(), None, cx)
88 }
89 });
90
91 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
92
93 // Mutating editor 1 will emit an `Edited` event only for that editor.
94 _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
95 assert_eq!(
96 mem::take(&mut *events.borrow_mut()),
97 [
98 ("editor1", "edited"),
99 ("editor1", "buffer edited"),
100 ("editor2", "buffer edited"),
101 ]
102 );
103
104 // Mutating editor 2 will emit an `Edited` event only for that editor.
105 _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
106 assert_eq!(
107 mem::take(&mut *events.borrow_mut()),
108 [
109 ("editor2", "edited"),
110 ("editor1", "buffer edited"),
111 ("editor2", "buffer edited"),
112 ]
113 );
114
115 // Undoing on editor 1 will emit an `Edited` event only for that editor.
116 _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
117 assert_eq!(
118 mem::take(&mut *events.borrow_mut()),
119 [
120 ("editor1", "edited"),
121 ("editor1", "buffer edited"),
122 ("editor2", "buffer edited"),
123 ]
124 );
125
126 // Redoing on editor 1 will emit an `Edited` event only for that editor.
127 _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
128 assert_eq!(
129 mem::take(&mut *events.borrow_mut()),
130 [
131 ("editor1", "edited"),
132 ("editor1", "buffer edited"),
133 ("editor2", "buffer edited"),
134 ]
135 );
136
137 // Undoing on editor 2 will emit an `Edited` event only for that editor.
138 _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
139 assert_eq!(
140 mem::take(&mut *events.borrow_mut()),
141 [
142 ("editor2", "edited"),
143 ("editor1", "buffer edited"),
144 ("editor2", "buffer edited"),
145 ]
146 );
147
148 // Redoing on editor 2 will emit an `Edited` event only for that editor.
149 _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
150 assert_eq!(
151 mem::take(&mut *events.borrow_mut()),
152 [
153 ("editor2", "edited"),
154 ("editor1", "buffer edited"),
155 ("editor2", "buffer edited"),
156 ]
157 );
158
159 // No event is emitted when the mutation is a no-op.
160 _ = editor2.update(cx, |editor, cx| {
161 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
162
163 editor.backspace(&Backspace, cx);
164 });
165 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
166}
167
168#[gpui::test]
169fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
170 init_test(cx, |_| {});
171
172 let mut now = Instant::now();
173 let group_interval = Duration::from_millis(1);
174 let buffer = cx.new_model(|cx| {
175 let mut buf = language::Buffer::local("123456", cx);
176 buf.set_group_interval(group_interval);
177 buf
178 });
179 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
180 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
181
182 _ = editor.update(cx, |editor, cx| {
183 editor.start_transaction_at(now, cx);
184 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
185
186 editor.insert("cd", cx);
187 editor.end_transaction_at(now, cx);
188 assert_eq!(editor.text(cx), "12cd56");
189 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
190
191 editor.start_transaction_at(now, cx);
192 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
193 editor.insert("e", cx);
194 editor.end_transaction_at(now, cx);
195 assert_eq!(editor.text(cx), "12cde6");
196 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
197
198 now += group_interval + Duration::from_millis(1);
199 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
200
201 // Simulate an edit in another editor
202 buffer.update(cx, |buffer, cx| {
203 buffer.start_transaction_at(now, cx);
204 buffer.edit([(0..1, "a")], None, cx);
205 buffer.edit([(1..1, "b")], None, cx);
206 buffer.end_transaction_at(now, cx);
207 });
208
209 assert_eq!(editor.text(cx), "ab2cde6");
210 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
211
212 // Last transaction happened past the group interval in a different editor.
213 // Undo it individually and don't restore selections.
214 editor.undo(&Undo, cx);
215 assert_eq!(editor.text(cx), "12cde6");
216 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
217
218 // First two transactions happened within the group interval in this editor.
219 // Undo them together and restore selections.
220 editor.undo(&Undo, cx);
221 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
222 assert_eq!(editor.text(cx), "123456");
223 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
224
225 // Redo the first two transactions together.
226 editor.redo(&Redo, cx);
227 assert_eq!(editor.text(cx), "12cde6");
228 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
229
230 // Redo the last transaction on its own.
231 editor.redo(&Redo, cx);
232 assert_eq!(editor.text(cx), "ab2cde6");
233 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
234
235 // Test empty transactions.
236 editor.start_transaction_at(now, cx);
237 editor.end_transaction_at(now, cx);
238 editor.undo(&Undo, cx);
239 assert_eq!(editor.text(cx), "12cde6");
240 });
241}
242
243#[gpui::test]
244fn test_ime_composition(cx: &mut TestAppContext) {
245 init_test(cx, |_| {});
246
247 let buffer = cx.new_model(|cx| {
248 let mut buffer = language::Buffer::local("abcde", cx);
249 // Ensure automatic grouping doesn't occur.
250 buffer.set_group_interval(Duration::ZERO);
251 buffer
252 });
253
254 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
255 cx.add_window(|cx| {
256 let mut editor = build_editor(buffer.clone(), cx);
257
258 // Start a new IME composition.
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 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
262 assert_eq!(editor.text(cx), "äbcde");
263 assert_eq!(
264 editor.marked_text_ranges(cx),
265 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
266 );
267
268 // Finalize IME composition.
269 editor.replace_text_in_range(None, "ā", cx);
270 assert_eq!(editor.text(cx), "ābcde");
271 assert_eq!(editor.marked_text_ranges(cx), None);
272
273 // IME composition edits are grouped and are undone/redone at once.
274 editor.undo(&Default::default(), cx);
275 assert_eq!(editor.text(cx), "abcde");
276 assert_eq!(editor.marked_text_ranges(cx), None);
277 editor.redo(&Default::default(), cx);
278 assert_eq!(editor.text(cx), "ābcde");
279 assert_eq!(editor.marked_text_ranges(cx), None);
280
281 // Start a new IME composition.
282 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
283 assert_eq!(
284 editor.marked_text_ranges(cx),
285 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
286 );
287
288 // Undoing during an IME composition cancels it.
289 editor.undo(&Default::default(), cx);
290 assert_eq!(editor.text(cx), "ābcde");
291 assert_eq!(editor.marked_text_ranges(cx), None);
292
293 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
294 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
295 assert_eq!(editor.text(cx), "ābcdè");
296 assert_eq!(
297 editor.marked_text_ranges(cx),
298 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
299 );
300
301 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
302 editor.replace_text_in_range(Some(4..999), "ę", cx);
303 assert_eq!(editor.text(cx), "ābcdę");
304 assert_eq!(editor.marked_text_ranges(cx), None);
305
306 // Start a new IME composition with multiple cursors.
307 editor.change_selections(None, cx, |s| {
308 s.select_ranges([
309 OffsetUtf16(1)..OffsetUtf16(1),
310 OffsetUtf16(3)..OffsetUtf16(3),
311 OffsetUtf16(5)..OffsetUtf16(5),
312 ])
313 });
314 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
315 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
316 assert_eq!(
317 editor.marked_text_ranges(cx),
318 Some(vec![
319 OffsetUtf16(0)..OffsetUtf16(3),
320 OffsetUtf16(4)..OffsetUtf16(7),
321 OffsetUtf16(8)..OffsetUtf16(11)
322 ])
323 );
324
325 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
326 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
327 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
328 assert_eq!(
329 editor.marked_text_ranges(cx),
330 Some(vec![
331 OffsetUtf16(1)..OffsetUtf16(2),
332 OffsetUtf16(5)..OffsetUtf16(6),
333 OffsetUtf16(9)..OffsetUtf16(10)
334 ])
335 );
336
337 // Finalize IME composition with multiple cursors.
338 editor.replace_text_in_range(Some(9..10), "2", cx);
339 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
340 assert_eq!(editor.marked_text_ranges(cx), None);
341
342 editor
343 });
344}
345
346#[gpui::test]
347fn test_selection_with_mouse(cx: &mut TestAppContext) {
348 init_test(cx, |_| {});
349
350 let editor = cx.add_window(|cx| {
351 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
352 build_editor(buffer, cx)
353 });
354
355 _ = editor.update(cx, |view, cx| {
356 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
357 });
358 assert_eq!(
359 editor
360 .update(cx, |view, cx| view.selections.display_ranges(cx))
361 .unwrap(),
362 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
363 );
364
365 _ = editor.update(cx, |view, cx| {
366 view.update_selection(
367 DisplayPoint::new(DisplayRow(3), 3),
368 0,
369 gpui::Point::<f32>::default(),
370 cx,
371 );
372 });
373
374 assert_eq!(
375 editor
376 .update(cx, |view, cx| view.selections.display_ranges(cx))
377 .unwrap(),
378 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
379 );
380
381 _ = editor.update(cx, |view, cx| {
382 view.update_selection(
383 DisplayPoint::new(DisplayRow(1), 1),
384 0,
385 gpui::Point::<f32>::default(),
386 cx,
387 );
388 });
389
390 assert_eq!(
391 editor
392 .update(cx, |view, cx| view.selections.display_ranges(cx))
393 .unwrap(),
394 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
395 );
396
397 _ = editor.update(cx, |view, cx| {
398 view.end_selection(cx);
399 view.update_selection(
400 DisplayPoint::new(DisplayRow(3), 3),
401 0,
402 gpui::Point::<f32>::default(),
403 cx,
404 );
405 });
406
407 assert_eq!(
408 editor
409 .update(cx, |view, cx| view.selections.display_ranges(cx))
410 .unwrap(),
411 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
412 );
413
414 _ = editor.update(cx, |view, cx| {
415 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
416 view.update_selection(
417 DisplayPoint::new(DisplayRow(0), 0),
418 0,
419 gpui::Point::<f32>::default(),
420 cx,
421 );
422 });
423
424 assert_eq!(
425 editor
426 .update(cx, |view, cx| view.selections.display_ranges(cx))
427 .unwrap(),
428 [
429 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
430 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
431 ]
432 );
433
434 _ = editor.update(cx, |view, cx| {
435 view.end_selection(cx);
436 });
437
438 assert_eq!(
439 editor
440 .update(cx, |view, cx| view.selections.display_ranges(cx))
441 .unwrap(),
442 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
443 );
444}
445
446#[gpui::test]
447fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
448 init_test(cx, |_| {});
449
450 let editor = cx.add_window(|cx| {
451 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
452 build_editor(buffer, cx)
453 });
454
455 _ = editor.update(cx, |view, cx| {
456 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
457 });
458
459 _ = editor.update(cx, |view, cx| {
460 view.end_selection(cx);
461 });
462
463 _ = editor.update(cx, |view, cx| {
464 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
465 });
466
467 _ = editor.update(cx, |view, cx| {
468 view.end_selection(cx);
469 });
470
471 assert_eq!(
472 editor
473 .update(cx, |view, cx| view.selections.display_ranges(cx))
474 .unwrap(),
475 [
476 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
477 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
478 ]
479 );
480
481 _ = editor.update(cx, |view, cx| {
482 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
483 });
484
485 _ = editor.update(cx, |view, cx| {
486 view.end_selection(cx);
487 });
488
489 assert_eq!(
490 editor
491 .update(cx, |view, cx| view.selections.display_ranges(cx))
492 .unwrap(),
493 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
494 );
495}
496
497#[gpui::test]
498fn test_canceling_pending_selection(cx: &mut TestAppContext) {
499 init_test(cx, |_| {});
500
501 let view = cx.add_window(|cx| {
502 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
503 build_editor(buffer, cx)
504 });
505
506 _ = view.update(cx, |view, cx| {
507 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
508 assert_eq!(
509 view.selections.display_ranges(cx),
510 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
511 );
512 });
513
514 _ = view.update(cx, |view, cx| {
515 view.update_selection(
516 DisplayPoint::new(DisplayRow(3), 3),
517 0,
518 gpui::Point::<f32>::default(),
519 cx,
520 );
521 assert_eq!(
522 view.selections.display_ranges(cx),
523 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
524 );
525 });
526
527 _ = view.update(cx, |view, cx| {
528 view.cancel(&Cancel, cx);
529 view.update_selection(
530 DisplayPoint::new(DisplayRow(1), 1),
531 0,
532 gpui::Point::<f32>::default(),
533 cx,
534 );
535 assert_eq!(
536 view.selections.display_ranges(cx),
537 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
538 );
539 });
540}
541
542#[gpui::test]
543fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
544 init_test(cx, |_| {});
545
546 let view = cx.add_window(|cx| {
547 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
548 build_editor(buffer, cx)
549 });
550
551 _ = view.update(cx, |view, cx| {
552 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
553 assert_eq!(
554 view.selections.display_ranges(cx),
555 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
556 );
557
558 view.move_down(&Default::default(), cx);
559 assert_eq!(
560 view.selections.display_ranges(cx),
561 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
562 );
563
564 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
565 assert_eq!(
566 view.selections.display_ranges(cx),
567 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
568 );
569
570 view.move_up(&Default::default(), cx);
571 assert_eq!(
572 view.selections.display_ranges(cx),
573 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
574 );
575 });
576}
577
578#[gpui::test]
579fn test_clone(cx: &mut TestAppContext) {
580 init_test(cx, |_| {});
581
582 let (text, selection_ranges) = marked_text_ranges(
583 indoc! {"
584 one
585 two
586 threeˇ
587 four
588 fiveˇ
589 "},
590 true,
591 );
592
593 let editor = cx.add_window(|cx| {
594 let buffer = MultiBuffer::build_simple(&text, cx);
595 build_editor(buffer, cx)
596 });
597
598 _ = editor.update(cx, |editor, cx| {
599 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
600 editor.fold_creases(
601 vec![
602 Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
603 Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
604 ],
605 true,
606 cx,
607 );
608 });
609
610 let cloned_editor = editor
611 .update(cx, |editor, cx| {
612 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
613 })
614 .unwrap()
615 .unwrap();
616
617 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
618 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
619
620 assert_eq!(
621 cloned_editor
622 .update(cx, |e, cx| e.display_text(cx))
623 .unwrap(),
624 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
625 );
626 assert_eq!(
627 cloned_snapshot
628 .folds_in_range(0..text.len())
629 .collect::<Vec<_>>(),
630 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
631 );
632 assert_set_eq!(
633 cloned_editor
634 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
635 .unwrap(),
636 editor
637 .update(cx, |editor, cx| editor.selections.ranges(cx))
638 .unwrap()
639 );
640 assert_set_eq!(
641 cloned_editor
642 .update(cx, |e, cx| e.selections.display_ranges(cx))
643 .unwrap(),
644 editor
645 .update(cx, |e, cx| e.selections.display_ranges(cx))
646 .unwrap()
647 );
648}
649
650#[gpui::test]
651async fn test_navigation_history(cx: &mut TestAppContext) {
652 init_test(cx, |_| {});
653
654 use workspace::item::Item;
655
656 let fs = FakeFs::new(cx.executor());
657 let project = Project::test(fs, [], cx).await;
658 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
659 let pane = workspace
660 .update(cx, |workspace, _| workspace.active_pane().clone())
661 .unwrap();
662
663 _ = workspace.update(cx, |_v, cx| {
664 cx.new_view(|cx| {
665 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
666 let mut editor = build_editor(buffer.clone(), cx);
667 let handle = cx.view();
668 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(handle)));
669
670 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
671 editor.nav_history.as_mut().unwrap().pop_backward(cx)
672 }
673
674 // Move the cursor a small distance.
675 // Nothing is added to the navigation history.
676 editor.change_selections(None, cx, |s| {
677 s.select_display_ranges([
678 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
679 ])
680 });
681 editor.change_selections(None, cx, |s| {
682 s.select_display_ranges([
683 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
684 ])
685 });
686 assert!(pop_history(&mut editor, cx).is_none());
687
688 // Move the cursor a large distance.
689 // The history can jump back to the previous position.
690 editor.change_selections(None, cx, |s| {
691 s.select_display_ranges([
692 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
693 ])
694 });
695 let nav_entry = pop_history(&mut editor, cx).unwrap();
696 editor.navigate(nav_entry.data.unwrap(), cx);
697 assert_eq!(nav_entry.item.id(), cx.entity_id());
698 assert_eq!(
699 editor.selections.display_ranges(cx),
700 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
701 );
702 assert!(pop_history(&mut editor, cx).is_none());
703
704 // Move the cursor a small distance via the mouse.
705 // Nothing is added to the navigation history.
706 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
707 editor.end_selection(cx);
708 assert_eq!(
709 editor.selections.display_ranges(cx),
710 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
711 );
712 assert!(pop_history(&mut editor, cx).is_none());
713
714 // Move the cursor a large distance via the mouse.
715 // The history can jump back to the previous position.
716 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
717 editor.end_selection(cx);
718 assert_eq!(
719 editor.selections.display_ranges(cx),
720 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
721 );
722 let nav_entry = pop_history(&mut editor, cx).unwrap();
723 editor.navigate(nav_entry.data.unwrap(), cx);
724 assert_eq!(nav_entry.item.id(), cx.entity_id());
725 assert_eq!(
726 editor.selections.display_ranges(cx),
727 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
728 );
729 assert!(pop_history(&mut editor, cx).is_none());
730
731 // Set scroll position to check later
732 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
733 let original_scroll_position = editor.scroll_manager.anchor();
734
735 // Jump to the end of the document and adjust scroll
736 editor.move_to_end(&MoveToEnd, cx);
737 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
738 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
739
740 let nav_entry = pop_history(&mut editor, cx).unwrap();
741 editor.navigate(nav_entry.data.unwrap(), cx);
742 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
743
744 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
745 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
746 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
747 let invalid_point = Point::new(9999, 0);
748 editor.navigate(
749 Box::new(NavigationData {
750 cursor_anchor: invalid_anchor,
751 cursor_position: invalid_point,
752 scroll_anchor: ScrollAnchor {
753 anchor: invalid_anchor,
754 offset: Default::default(),
755 },
756 scroll_top_row: invalid_point.row,
757 }),
758 cx,
759 );
760 assert_eq!(
761 editor.selections.display_ranges(cx),
762 &[editor.max_point(cx)..editor.max_point(cx)]
763 );
764 assert_eq!(
765 editor.scroll_position(cx),
766 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
767 );
768
769 editor
770 })
771 });
772}
773
774#[gpui::test]
775fn test_cancel(cx: &mut TestAppContext) {
776 init_test(cx, |_| {});
777
778 let view = cx.add_window(|cx| {
779 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
780 build_editor(buffer, cx)
781 });
782
783 _ = view.update(cx, |view, cx| {
784 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
785 view.update_selection(
786 DisplayPoint::new(DisplayRow(1), 1),
787 0,
788 gpui::Point::<f32>::default(),
789 cx,
790 );
791 view.end_selection(cx);
792
793 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
794 view.update_selection(
795 DisplayPoint::new(DisplayRow(0), 3),
796 0,
797 gpui::Point::<f32>::default(),
798 cx,
799 );
800 view.end_selection(cx);
801 assert_eq!(
802 view.selections.display_ranges(cx),
803 [
804 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
805 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
806 ]
807 );
808 });
809
810 _ = view.update(cx, |view, cx| {
811 view.cancel(&Cancel, cx);
812 assert_eq!(
813 view.selections.display_ranges(cx),
814 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
815 );
816 });
817
818 _ = view.update(cx, |view, cx| {
819 view.cancel(&Cancel, cx);
820 assert_eq!(
821 view.selections.display_ranges(cx),
822 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
823 );
824 });
825}
826
827#[gpui::test]
828fn test_fold_action(cx: &mut TestAppContext) {
829 init_test(cx, |_| {});
830
831 let view = cx.add_window(|cx| {
832 let buffer = MultiBuffer::build_simple(
833 &"
834 impl Foo {
835 // Hello!
836
837 fn a() {
838 1
839 }
840
841 fn b() {
842 2
843 }
844
845 fn c() {
846 3
847 }
848 }
849 "
850 .unindent(),
851 cx,
852 );
853 build_editor(buffer.clone(), cx)
854 });
855
856 _ = view.update(cx, |view, cx| {
857 view.change_selections(None, cx, |s| {
858 s.select_display_ranges([
859 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
860 ]);
861 });
862 view.fold(&Fold, cx);
863 assert_eq!(
864 view.display_text(cx),
865 "
866 impl Foo {
867 // Hello!
868
869 fn a() {
870 1
871 }
872
873 fn b() {⋯
874 }
875
876 fn c() {⋯
877 }
878 }
879 "
880 .unindent(),
881 );
882
883 view.fold(&Fold, cx);
884 assert_eq!(
885 view.display_text(cx),
886 "
887 impl Foo {⋯
888 }
889 "
890 .unindent(),
891 );
892
893 view.unfold_lines(&UnfoldLines, cx);
894 assert_eq!(
895 view.display_text(cx),
896 "
897 impl Foo {
898 // Hello!
899
900 fn a() {
901 1
902 }
903
904 fn b() {⋯
905 }
906
907 fn c() {⋯
908 }
909 }
910 "
911 .unindent(),
912 );
913
914 view.unfold_lines(&UnfoldLines, cx);
915 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
916 });
917}
918
919#[gpui::test]
920fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
921 init_test(cx, |_| {});
922
923 let view = cx.add_window(|cx| {
924 let buffer = MultiBuffer::build_simple(
925 &"
926 class Foo:
927 # Hello!
928
929 def a():
930 print(1)
931
932 def b():
933 print(2)
934
935 def c():
936 print(3)
937 "
938 .unindent(),
939 cx,
940 );
941 build_editor(buffer.clone(), cx)
942 });
943
944 _ = view.update(cx, |view, cx| {
945 view.change_selections(None, cx, |s| {
946 s.select_display_ranges([
947 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
948 ]);
949 });
950 view.fold(&Fold, cx);
951 assert_eq!(
952 view.display_text(cx),
953 "
954 class Foo:
955 # Hello!
956
957 def a():
958 print(1)
959
960 def b():⋯
961
962 def c():⋯
963 "
964 .unindent(),
965 );
966
967 view.fold(&Fold, cx);
968 assert_eq!(
969 view.display_text(cx),
970 "
971 class Foo:⋯
972 "
973 .unindent(),
974 );
975
976 view.unfold_lines(&UnfoldLines, cx);
977 assert_eq!(
978 view.display_text(cx),
979 "
980 class Foo:
981 # Hello!
982
983 def a():
984 print(1)
985
986 def b():⋯
987
988 def c():⋯
989 "
990 .unindent(),
991 );
992
993 view.unfold_lines(&UnfoldLines, cx);
994 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
995 });
996}
997
998#[gpui::test]
999fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
1000 init_test(cx, |_| {});
1001
1002 let view = cx.add_window(|cx| {
1003 let buffer = MultiBuffer::build_simple(
1004 &"
1005 class Foo:
1006 # Hello!
1007
1008 def a():
1009 print(1)
1010
1011 def b():
1012 print(2)
1013
1014
1015 def c():
1016 print(3)
1017
1018
1019 "
1020 .unindent(),
1021 cx,
1022 );
1023 build_editor(buffer.clone(), cx)
1024 });
1025
1026 _ = view.update(cx, |view, cx| {
1027 view.change_selections(None, cx, |s| {
1028 s.select_display_ranges([
1029 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1030 ]);
1031 });
1032 view.fold(&Fold, cx);
1033 assert_eq!(
1034 view.display_text(cx),
1035 "
1036 class Foo:
1037 # Hello!
1038
1039 def a():
1040 print(1)
1041
1042 def b():⋯
1043
1044
1045 def c():⋯
1046
1047
1048 "
1049 .unindent(),
1050 );
1051
1052 view.fold(&Fold, cx);
1053 assert_eq!(
1054 view.display_text(cx),
1055 "
1056 class Foo:⋯
1057
1058
1059 "
1060 .unindent(),
1061 );
1062
1063 view.unfold_lines(&UnfoldLines, cx);
1064 assert_eq!(
1065 view.display_text(cx),
1066 "
1067 class Foo:
1068 # Hello!
1069
1070 def a():
1071 print(1)
1072
1073 def b():⋯
1074
1075
1076 def c():⋯
1077
1078
1079 "
1080 .unindent(),
1081 );
1082
1083 view.unfold_lines(&UnfoldLines, cx);
1084 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1085 });
1086}
1087
1088#[gpui::test]
1089fn test_fold_at_level(cx: &mut TestAppContext) {
1090 init_test(cx, |_| {});
1091
1092 let view = cx.add_window(|cx| {
1093 let buffer = MultiBuffer::build_simple(
1094 &"
1095 class Foo:
1096 # Hello!
1097
1098 def a():
1099 print(1)
1100
1101 def b():
1102 print(2)
1103
1104
1105 class Bar:
1106 # World!
1107
1108 def a():
1109 print(1)
1110
1111 def b():
1112 print(2)
1113
1114
1115 "
1116 .unindent(),
1117 cx,
1118 );
1119 build_editor(buffer.clone(), cx)
1120 });
1121
1122 _ = view.update(cx, |view, cx| {
1123 view.fold_at_level(&FoldAtLevel { level: 2 }, cx);
1124 assert_eq!(
1125 view.display_text(cx),
1126 "
1127 class Foo:
1128 # Hello!
1129
1130 def a():⋯
1131
1132 def b():⋯
1133
1134
1135 class Bar:
1136 # World!
1137
1138 def a():⋯
1139
1140 def b():⋯
1141
1142
1143 "
1144 .unindent(),
1145 );
1146
1147 view.fold_at_level(&FoldAtLevel { level: 1 }, cx);
1148 assert_eq!(
1149 view.display_text(cx),
1150 "
1151 class Foo:⋯
1152
1153
1154 class Bar:⋯
1155
1156
1157 "
1158 .unindent(),
1159 );
1160
1161 view.unfold_all(&UnfoldAll, cx);
1162 view.fold_at_level(&FoldAtLevel { level: 0 }, cx);
1163 assert_eq!(
1164 view.display_text(cx),
1165 "
1166 class Foo:
1167 # Hello!
1168
1169 def a():
1170 print(1)
1171
1172 def b():
1173 print(2)
1174
1175
1176 class Bar:
1177 # World!
1178
1179 def a():
1180 print(1)
1181
1182 def b():
1183 print(2)
1184
1185
1186 "
1187 .unindent(),
1188 );
1189
1190 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1191 });
1192}
1193
1194#[gpui::test]
1195fn test_move_cursor(cx: &mut TestAppContext) {
1196 init_test(cx, |_| {});
1197
1198 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1199 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1200
1201 buffer.update(cx, |buffer, cx| {
1202 buffer.edit(
1203 vec![
1204 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1205 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1206 ],
1207 None,
1208 cx,
1209 );
1210 });
1211 _ = view.update(cx, |view, cx| {
1212 assert_eq!(
1213 view.selections.display_ranges(cx),
1214 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1215 );
1216
1217 view.move_down(&MoveDown, cx);
1218 assert_eq!(
1219 view.selections.display_ranges(cx),
1220 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1221 );
1222
1223 view.move_right(&MoveRight, cx);
1224 assert_eq!(
1225 view.selections.display_ranges(cx),
1226 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1227 );
1228
1229 view.move_left(&MoveLeft, cx);
1230 assert_eq!(
1231 view.selections.display_ranges(cx),
1232 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1233 );
1234
1235 view.move_up(&MoveUp, cx);
1236 assert_eq!(
1237 view.selections.display_ranges(cx),
1238 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1239 );
1240
1241 view.move_to_end(&MoveToEnd, cx);
1242 assert_eq!(
1243 view.selections.display_ranges(cx),
1244 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1245 );
1246
1247 view.move_to_beginning(&MoveToBeginning, cx);
1248 assert_eq!(
1249 view.selections.display_ranges(cx),
1250 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1251 );
1252
1253 view.change_selections(None, cx, |s| {
1254 s.select_display_ranges([
1255 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1256 ]);
1257 });
1258 view.select_to_beginning(&SelectToBeginning, cx);
1259 assert_eq!(
1260 view.selections.display_ranges(cx),
1261 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1262 );
1263
1264 view.select_to_end(&SelectToEnd, cx);
1265 assert_eq!(
1266 view.selections.display_ranges(cx),
1267 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1268 );
1269 });
1270}
1271
1272// TODO: Re-enable this test
1273#[cfg(target_os = "macos")]
1274#[gpui::test]
1275fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1276 init_test(cx, |_| {});
1277
1278 let view = cx.add_window(|cx| {
1279 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1280 build_editor(buffer.clone(), cx)
1281 });
1282
1283 assert_eq!('ⓐ'.len_utf8(), 3);
1284 assert_eq!('α'.len_utf8(), 2);
1285
1286 _ = view.update(cx, |view, cx| {
1287 view.fold_creases(
1288 vec![
1289 Crease::simple(Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1290 Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1291 Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1292 ],
1293 true,
1294 cx,
1295 );
1296 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1297
1298 view.move_right(&MoveRight, cx);
1299 assert_eq!(
1300 view.selections.display_ranges(cx),
1301 &[empty_range(0, "ⓐ".len())]
1302 );
1303 view.move_right(&MoveRight, cx);
1304 assert_eq!(
1305 view.selections.display_ranges(cx),
1306 &[empty_range(0, "ⓐⓑ".len())]
1307 );
1308 view.move_right(&MoveRight, cx);
1309 assert_eq!(
1310 view.selections.display_ranges(cx),
1311 &[empty_range(0, "ⓐⓑ⋯".len())]
1312 );
1313
1314 view.move_down(&MoveDown, cx);
1315 assert_eq!(
1316 view.selections.display_ranges(cx),
1317 &[empty_range(1, "ab⋯e".len())]
1318 );
1319 view.move_left(&MoveLeft, cx);
1320 assert_eq!(
1321 view.selections.display_ranges(cx),
1322 &[empty_range(1, "ab⋯".len())]
1323 );
1324 view.move_left(&MoveLeft, cx);
1325 assert_eq!(
1326 view.selections.display_ranges(cx),
1327 &[empty_range(1, "ab".len())]
1328 );
1329 view.move_left(&MoveLeft, cx);
1330 assert_eq!(
1331 view.selections.display_ranges(cx),
1332 &[empty_range(1, "a".len())]
1333 );
1334
1335 view.move_down(&MoveDown, cx);
1336 assert_eq!(
1337 view.selections.display_ranges(cx),
1338 &[empty_range(2, "α".len())]
1339 );
1340 view.move_right(&MoveRight, cx);
1341 assert_eq!(
1342 view.selections.display_ranges(cx),
1343 &[empty_range(2, "αβ".len())]
1344 );
1345 view.move_right(&MoveRight, cx);
1346 assert_eq!(
1347 view.selections.display_ranges(cx),
1348 &[empty_range(2, "αβ⋯".len())]
1349 );
1350 view.move_right(&MoveRight, cx);
1351 assert_eq!(
1352 view.selections.display_ranges(cx),
1353 &[empty_range(2, "αβ⋯ε".len())]
1354 );
1355
1356 view.move_up(&MoveUp, cx);
1357 assert_eq!(
1358 view.selections.display_ranges(cx),
1359 &[empty_range(1, "ab⋯e".len())]
1360 );
1361 view.move_down(&MoveDown, cx);
1362 assert_eq!(
1363 view.selections.display_ranges(cx),
1364 &[empty_range(2, "αβ⋯ε".len())]
1365 );
1366 view.move_up(&MoveUp, cx);
1367 assert_eq!(
1368 view.selections.display_ranges(cx),
1369 &[empty_range(1, "ab⋯e".len())]
1370 );
1371
1372 view.move_up(&MoveUp, cx);
1373 assert_eq!(
1374 view.selections.display_ranges(cx),
1375 &[empty_range(0, "ⓐⓑ".len())]
1376 );
1377 view.move_left(&MoveLeft, cx);
1378 assert_eq!(
1379 view.selections.display_ranges(cx),
1380 &[empty_range(0, "ⓐ".len())]
1381 );
1382 view.move_left(&MoveLeft, cx);
1383 assert_eq!(
1384 view.selections.display_ranges(cx),
1385 &[empty_range(0, "".len())]
1386 );
1387 });
1388}
1389
1390#[gpui::test]
1391fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1392 init_test(cx, |_| {});
1393
1394 let view = cx.add_window(|cx| {
1395 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1396 build_editor(buffer.clone(), cx)
1397 });
1398 _ = view.update(cx, |view, cx| {
1399 view.change_selections(None, cx, |s| {
1400 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1401 });
1402
1403 // moving above start of document should move selection to start of document,
1404 // but the next move down should still be at the original goal_x
1405 view.move_up(&MoveUp, cx);
1406 assert_eq!(
1407 view.selections.display_ranges(cx),
1408 &[empty_range(0, "".len())]
1409 );
1410
1411 view.move_down(&MoveDown, cx);
1412 assert_eq!(
1413 view.selections.display_ranges(cx),
1414 &[empty_range(1, "abcd".len())]
1415 );
1416
1417 view.move_down(&MoveDown, cx);
1418 assert_eq!(
1419 view.selections.display_ranges(cx),
1420 &[empty_range(2, "αβγ".len())]
1421 );
1422
1423 view.move_down(&MoveDown, cx);
1424 assert_eq!(
1425 view.selections.display_ranges(cx),
1426 &[empty_range(3, "abcd".len())]
1427 );
1428
1429 view.move_down(&MoveDown, cx);
1430 assert_eq!(
1431 view.selections.display_ranges(cx),
1432 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1433 );
1434
1435 // moving past end of document should not change goal_x
1436 view.move_down(&MoveDown, cx);
1437 assert_eq!(
1438 view.selections.display_ranges(cx),
1439 &[empty_range(5, "".len())]
1440 );
1441
1442 view.move_down(&MoveDown, cx);
1443 assert_eq!(
1444 view.selections.display_ranges(cx),
1445 &[empty_range(5, "".len())]
1446 );
1447
1448 view.move_up(&MoveUp, cx);
1449 assert_eq!(
1450 view.selections.display_ranges(cx),
1451 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1452 );
1453
1454 view.move_up(&MoveUp, cx);
1455 assert_eq!(
1456 view.selections.display_ranges(cx),
1457 &[empty_range(3, "abcd".len())]
1458 );
1459
1460 view.move_up(&MoveUp, cx);
1461 assert_eq!(
1462 view.selections.display_ranges(cx),
1463 &[empty_range(2, "αβγ".len())]
1464 );
1465 });
1466}
1467
1468#[gpui::test]
1469fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1470 init_test(cx, |_| {});
1471 let move_to_beg = MoveToBeginningOfLine {
1472 stop_at_soft_wraps: true,
1473 };
1474
1475 let move_to_end = MoveToEndOfLine {
1476 stop_at_soft_wraps: true,
1477 };
1478
1479 let view = cx.add_window(|cx| {
1480 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1481 build_editor(buffer, cx)
1482 });
1483 _ = view.update(cx, |view, cx| {
1484 view.change_selections(None, cx, |s| {
1485 s.select_display_ranges([
1486 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1487 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1488 ]);
1489 });
1490 });
1491
1492 _ = view.update(cx, |view, cx| {
1493 view.move_to_beginning_of_line(&move_to_beg, cx);
1494 assert_eq!(
1495 view.selections.display_ranges(cx),
1496 &[
1497 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1498 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1499 ]
1500 );
1501 });
1502
1503 _ = view.update(cx, |view, cx| {
1504 view.move_to_beginning_of_line(&move_to_beg, cx);
1505 assert_eq!(
1506 view.selections.display_ranges(cx),
1507 &[
1508 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1509 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1510 ]
1511 );
1512 });
1513
1514 _ = view.update(cx, |view, cx| {
1515 view.move_to_beginning_of_line(&move_to_beg, cx);
1516 assert_eq!(
1517 view.selections.display_ranges(cx),
1518 &[
1519 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1520 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1521 ]
1522 );
1523 });
1524
1525 _ = view.update(cx, |view, cx| {
1526 view.move_to_end_of_line(&move_to_end, cx);
1527 assert_eq!(
1528 view.selections.display_ranges(cx),
1529 &[
1530 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1531 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1532 ]
1533 );
1534 });
1535
1536 // Moving to the end of line again is a no-op.
1537 _ = view.update(cx, |view, cx| {
1538 view.move_to_end_of_line(&move_to_end, cx);
1539 assert_eq!(
1540 view.selections.display_ranges(cx),
1541 &[
1542 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1543 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1544 ]
1545 );
1546 });
1547
1548 _ = view.update(cx, |view, cx| {
1549 view.move_left(&MoveLeft, cx);
1550 view.select_to_beginning_of_line(
1551 &SelectToBeginningOfLine {
1552 stop_at_soft_wraps: true,
1553 },
1554 cx,
1555 );
1556 assert_eq!(
1557 view.selections.display_ranges(cx),
1558 &[
1559 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1560 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1561 ]
1562 );
1563 });
1564
1565 _ = view.update(cx, |view, cx| {
1566 view.select_to_beginning_of_line(
1567 &SelectToBeginningOfLine {
1568 stop_at_soft_wraps: true,
1569 },
1570 cx,
1571 );
1572 assert_eq!(
1573 view.selections.display_ranges(cx),
1574 &[
1575 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1576 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1577 ]
1578 );
1579 });
1580
1581 _ = view.update(cx, |view, cx| {
1582 view.select_to_beginning_of_line(
1583 &SelectToBeginningOfLine {
1584 stop_at_soft_wraps: true,
1585 },
1586 cx,
1587 );
1588 assert_eq!(
1589 view.selections.display_ranges(cx),
1590 &[
1591 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1592 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1593 ]
1594 );
1595 });
1596
1597 _ = view.update(cx, |view, cx| {
1598 view.select_to_end_of_line(
1599 &SelectToEndOfLine {
1600 stop_at_soft_wraps: true,
1601 },
1602 cx,
1603 );
1604 assert_eq!(
1605 view.selections.display_ranges(cx),
1606 &[
1607 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1608 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1609 ]
1610 );
1611 });
1612
1613 _ = view.update(cx, |view, cx| {
1614 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1615 assert_eq!(view.display_text(cx), "ab\n de");
1616 assert_eq!(
1617 view.selections.display_ranges(cx),
1618 &[
1619 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1620 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1621 ]
1622 );
1623 });
1624
1625 _ = view.update(cx, |view, cx| {
1626 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1627 assert_eq!(view.display_text(cx), "\n");
1628 assert_eq!(
1629 view.selections.display_ranges(cx),
1630 &[
1631 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1632 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1633 ]
1634 );
1635 });
1636}
1637
1638#[gpui::test]
1639fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1640 init_test(cx, |_| {});
1641 let move_to_beg = MoveToBeginningOfLine {
1642 stop_at_soft_wraps: false,
1643 };
1644
1645 let move_to_end = MoveToEndOfLine {
1646 stop_at_soft_wraps: false,
1647 };
1648
1649 let view = cx.add_window(|cx| {
1650 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1651 build_editor(buffer, cx)
1652 });
1653
1654 _ = view.update(cx, |view, cx| {
1655 view.set_wrap_width(Some(140.0.into()), cx);
1656
1657 // We expect the following lines after wrapping
1658 // ```
1659 // thequickbrownfox
1660 // jumpedoverthelazydo
1661 // gs
1662 // ```
1663 // The final `gs` was soft-wrapped onto a new line.
1664 assert_eq!(
1665 "thequickbrownfox\njumpedoverthelaz\nydogs",
1666 view.display_text(cx),
1667 );
1668
1669 // First, let's assert behavior on the first line, that was not soft-wrapped.
1670 // Start the cursor at the `k` on the first line
1671 view.change_selections(None, cx, |s| {
1672 s.select_display_ranges([
1673 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1674 ]);
1675 });
1676
1677 // Moving to the beginning of the line should put us at the beginning of the line.
1678 view.move_to_beginning_of_line(&move_to_beg, cx);
1679 assert_eq!(
1680 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1681 view.selections.display_ranges(cx)
1682 );
1683
1684 // Moving to the end of the line should put us at the end of the line.
1685 view.move_to_end_of_line(&move_to_end, cx);
1686 assert_eq!(
1687 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1688 view.selections.display_ranges(cx)
1689 );
1690
1691 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1692 // Start the cursor at the last line (`y` that was wrapped to a new line)
1693 view.change_selections(None, cx, |s| {
1694 s.select_display_ranges([
1695 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1696 ]);
1697 });
1698
1699 // Moving to the beginning of the line should put us at the start of the second line of
1700 // display text, i.e., the `j`.
1701 view.move_to_beginning_of_line(&move_to_beg, cx);
1702 assert_eq!(
1703 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1704 view.selections.display_ranges(cx)
1705 );
1706
1707 // Moving to the beginning of the line again should be a no-op.
1708 view.move_to_beginning_of_line(&move_to_beg, cx);
1709 assert_eq!(
1710 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1711 view.selections.display_ranges(cx)
1712 );
1713
1714 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1715 // next display line.
1716 view.move_to_end_of_line(&move_to_end, cx);
1717 assert_eq!(
1718 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1719 view.selections.display_ranges(cx)
1720 );
1721
1722 // Moving to the end of the line again should be a no-op.
1723 view.move_to_end_of_line(&move_to_end, cx);
1724 assert_eq!(
1725 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1726 view.selections.display_ranges(cx)
1727 );
1728 });
1729}
1730
1731#[gpui::test]
1732fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1733 init_test(cx, |_| {});
1734
1735 let view = cx.add_window(|cx| {
1736 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1737 build_editor(buffer, cx)
1738 });
1739 _ = view.update(cx, |view, cx| {
1740 view.change_selections(None, cx, |s| {
1741 s.select_display_ranges([
1742 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1743 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1744 ])
1745 });
1746
1747 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1748 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1749
1750 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1751 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1752
1753 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1754 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1755
1756 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1757 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1758
1759 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1760 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1761
1762 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1763 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1764
1765 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1766 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1767
1768 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1769 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1770
1771 view.move_right(&MoveRight, cx);
1772 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1773 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1774
1775 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1776 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1777
1778 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1779 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1780 });
1781}
1782
1783#[gpui::test]
1784fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1785 init_test(cx, |_| {});
1786
1787 let view = cx.add_window(|cx| {
1788 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1789 build_editor(buffer, cx)
1790 });
1791
1792 _ = view.update(cx, |view, cx| {
1793 view.set_wrap_width(Some(140.0.into()), cx);
1794 assert_eq!(
1795 view.display_text(cx),
1796 "use one::{\n two::three::\n four::five\n};"
1797 );
1798
1799 view.change_selections(None, cx, |s| {
1800 s.select_display_ranges([
1801 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1802 ]);
1803 });
1804
1805 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1806 assert_eq!(
1807 view.selections.display_ranges(cx),
1808 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1809 );
1810
1811 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1812 assert_eq!(
1813 view.selections.display_ranges(cx),
1814 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1815 );
1816
1817 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1818 assert_eq!(
1819 view.selections.display_ranges(cx),
1820 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1821 );
1822
1823 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1824 assert_eq!(
1825 view.selections.display_ranges(cx),
1826 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1827 );
1828
1829 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1830 assert_eq!(
1831 view.selections.display_ranges(cx),
1832 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1833 );
1834
1835 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1836 assert_eq!(
1837 view.selections.display_ranges(cx),
1838 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1839 );
1840 });
1841}
1842
1843#[gpui::test]
1844async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1845 init_test(cx, |_| {});
1846 let mut cx = EditorTestContext::new(cx).await;
1847
1848 let line_height = cx.editor(|editor, cx| {
1849 editor
1850 .style()
1851 .unwrap()
1852 .text
1853 .line_height_in_pixels(cx.rem_size())
1854 });
1855 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1856
1857 cx.set_state(
1858 &r#"ˇone
1859 two
1860
1861 three
1862 fourˇ
1863 five
1864
1865 six"#
1866 .unindent(),
1867 );
1868
1869 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1870 cx.assert_editor_state(
1871 &r#"one
1872 two
1873 ˇ
1874 three
1875 four
1876 five
1877 ˇ
1878 six"#
1879 .unindent(),
1880 );
1881
1882 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1883 cx.assert_editor_state(
1884 &r#"one
1885 two
1886
1887 three
1888 four
1889 five
1890 ˇ
1891 sixˇ"#
1892 .unindent(),
1893 );
1894
1895 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1896 cx.assert_editor_state(
1897 &r#"one
1898 two
1899
1900 three
1901 four
1902 five
1903
1904 sixˇ"#
1905 .unindent(),
1906 );
1907
1908 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1909 cx.assert_editor_state(
1910 &r#"one
1911 two
1912
1913 three
1914 four
1915 five
1916 ˇ
1917 six"#
1918 .unindent(),
1919 );
1920
1921 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1922 cx.assert_editor_state(
1923 &r#"one
1924 two
1925 ˇ
1926 three
1927 four
1928 five
1929
1930 six"#
1931 .unindent(),
1932 );
1933
1934 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1935 cx.assert_editor_state(
1936 &r#"ˇone
1937 two
1938
1939 three
1940 four
1941 five
1942
1943 six"#
1944 .unindent(),
1945 );
1946}
1947
1948#[gpui::test]
1949async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1950 init_test(cx, |_| {});
1951 let mut cx = EditorTestContext::new(cx).await;
1952 let line_height = cx.editor(|editor, cx| {
1953 editor
1954 .style()
1955 .unwrap()
1956 .text
1957 .line_height_in_pixels(cx.rem_size())
1958 });
1959 let window = cx.window;
1960 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1961
1962 cx.set_state(
1963 r#"ˇone
1964 two
1965 three
1966 four
1967 five
1968 six
1969 seven
1970 eight
1971 nine
1972 ten
1973 "#,
1974 );
1975
1976 cx.update_editor(|editor, cx| {
1977 assert_eq!(
1978 editor.snapshot(cx).scroll_position(),
1979 gpui::Point::new(0., 0.)
1980 );
1981 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1982 assert_eq!(
1983 editor.snapshot(cx).scroll_position(),
1984 gpui::Point::new(0., 3.)
1985 );
1986 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1987 assert_eq!(
1988 editor.snapshot(cx).scroll_position(),
1989 gpui::Point::new(0., 6.)
1990 );
1991 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1992 assert_eq!(
1993 editor.snapshot(cx).scroll_position(),
1994 gpui::Point::new(0., 3.)
1995 );
1996
1997 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1998 assert_eq!(
1999 editor.snapshot(cx).scroll_position(),
2000 gpui::Point::new(0., 1.)
2001 );
2002 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
2003 assert_eq!(
2004 editor.snapshot(cx).scroll_position(),
2005 gpui::Point::new(0., 3.)
2006 );
2007 });
2008}
2009
2010#[gpui::test]
2011async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
2012 init_test(cx, |_| {});
2013 let mut cx = EditorTestContext::new(cx).await;
2014
2015 let line_height = cx.update_editor(|editor, cx| {
2016 editor.set_vertical_scroll_margin(2, cx);
2017 editor
2018 .style()
2019 .unwrap()
2020 .text
2021 .line_height_in_pixels(cx.rem_size())
2022 });
2023 let window = cx.window;
2024 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2025
2026 cx.set_state(
2027 r#"ˇone
2028 two
2029 three
2030 four
2031 five
2032 six
2033 seven
2034 eight
2035 nine
2036 ten
2037 "#,
2038 );
2039 cx.update_editor(|editor, cx| {
2040 assert_eq!(
2041 editor.snapshot(cx).scroll_position(),
2042 gpui::Point::new(0., 0.0)
2043 );
2044 });
2045
2046 // Add a cursor below the visible area. Since both cursors cannot fit
2047 // on screen, the editor autoscrolls to reveal the newest cursor, and
2048 // allows the vertical scroll margin below that cursor.
2049 cx.update_editor(|editor, cx| {
2050 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2051 selections.select_ranges([
2052 Point::new(0, 0)..Point::new(0, 0),
2053 Point::new(6, 0)..Point::new(6, 0),
2054 ]);
2055 })
2056 });
2057 cx.update_editor(|editor, cx| {
2058 assert_eq!(
2059 editor.snapshot(cx).scroll_position(),
2060 gpui::Point::new(0., 3.0)
2061 );
2062 });
2063
2064 // Move down. The editor cursor scrolls down to track the newest cursor.
2065 cx.update_editor(|editor, cx| {
2066 editor.move_down(&Default::default(), cx);
2067 });
2068 cx.update_editor(|editor, cx| {
2069 assert_eq!(
2070 editor.snapshot(cx).scroll_position(),
2071 gpui::Point::new(0., 4.0)
2072 );
2073 });
2074
2075 // Add a cursor above the visible area. Since both cursors fit on screen,
2076 // the editor scrolls to show both.
2077 cx.update_editor(|editor, cx| {
2078 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2079 selections.select_ranges([
2080 Point::new(1, 0)..Point::new(1, 0),
2081 Point::new(6, 0)..Point::new(6, 0),
2082 ]);
2083 })
2084 });
2085 cx.update_editor(|editor, cx| {
2086 assert_eq!(
2087 editor.snapshot(cx).scroll_position(),
2088 gpui::Point::new(0., 1.0)
2089 );
2090 });
2091}
2092
2093#[gpui::test]
2094async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
2095 init_test(cx, |_| {});
2096 let mut cx = EditorTestContext::new(cx).await;
2097
2098 let line_height = cx.editor(|editor, cx| {
2099 editor
2100 .style()
2101 .unwrap()
2102 .text
2103 .line_height_in_pixels(cx.rem_size())
2104 });
2105 let window = cx.window;
2106 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2107 cx.set_state(
2108 &r#"
2109 ˇone
2110 two
2111 threeˇ
2112 four
2113 five
2114 six
2115 seven
2116 eight
2117 nine
2118 ten
2119 "#
2120 .unindent(),
2121 );
2122
2123 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2124 cx.assert_editor_state(
2125 &r#"
2126 one
2127 two
2128 three
2129 ˇfour
2130 five
2131 sixˇ
2132 seven
2133 eight
2134 nine
2135 ten
2136 "#
2137 .unindent(),
2138 );
2139
2140 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2141 cx.assert_editor_state(
2142 &r#"
2143 one
2144 two
2145 three
2146 four
2147 five
2148 six
2149 ˇseven
2150 eight
2151 nineˇ
2152 ten
2153 "#
2154 .unindent(),
2155 );
2156
2157 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2158 cx.assert_editor_state(
2159 &r#"
2160 one
2161 two
2162 three
2163 ˇfour
2164 five
2165 sixˇ
2166 seven
2167 eight
2168 nine
2169 ten
2170 "#
2171 .unindent(),
2172 );
2173
2174 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2175 cx.assert_editor_state(
2176 &r#"
2177 ˇone
2178 two
2179 threeˇ
2180 four
2181 five
2182 six
2183 seven
2184 eight
2185 nine
2186 ten
2187 "#
2188 .unindent(),
2189 );
2190
2191 // Test select collapsing
2192 cx.update_editor(|editor, cx| {
2193 editor.move_page_down(&MovePageDown::default(), cx);
2194 editor.move_page_down(&MovePageDown::default(), cx);
2195 editor.move_page_down(&MovePageDown::default(), cx);
2196 });
2197 cx.assert_editor_state(
2198 &r#"
2199 one
2200 two
2201 three
2202 four
2203 five
2204 six
2205 seven
2206 eight
2207 nine
2208 ˇten
2209 ˇ"#
2210 .unindent(),
2211 );
2212}
2213
2214#[gpui::test]
2215async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2216 init_test(cx, |_| {});
2217 let mut cx = EditorTestContext::new(cx).await;
2218 cx.set_state("one «two threeˇ» four");
2219 cx.update_editor(|editor, cx| {
2220 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2221 assert_eq!(editor.text(cx), " four");
2222 });
2223}
2224
2225#[gpui::test]
2226fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2227 init_test(cx, |_| {});
2228
2229 let view = cx.add_window(|cx| {
2230 let buffer = MultiBuffer::build_simple("one two three four", cx);
2231 build_editor(buffer.clone(), cx)
2232 });
2233
2234 _ = view.update(cx, |view, cx| {
2235 view.change_selections(None, cx, |s| {
2236 s.select_display_ranges([
2237 // an empty selection - the preceding word fragment is deleted
2238 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2239 // characters selected - they are deleted
2240 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2241 ])
2242 });
2243 view.delete_to_previous_word_start(
2244 &DeleteToPreviousWordStart {
2245 ignore_newlines: false,
2246 },
2247 cx,
2248 );
2249 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2250 });
2251
2252 _ = view.update(cx, |view, cx| {
2253 view.change_selections(None, cx, |s| {
2254 s.select_display_ranges([
2255 // an empty selection - the following word fragment is deleted
2256 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2257 // characters selected - they are deleted
2258 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2259 ])
2260 });
2261 view.delete_to_next_word_end(
2262 &DeleteToNextWordEnd {
2263 ignore_newlines: false,
2264 },
2265 cx,
2266 );
2267 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2268 });
2269}
2270
2271#[gpui::test]
2272fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2273 init_test(cx, |_| {});
2274
2275 let view = cx.add_window(|cx| {
2276 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2277 build_editor(buffer.clone(), cx)
2278 });
2279 let del_to_prev_word_start = DeleteToPreviousWordStart {
2280 ignore_newlines: false,
2281 };
2282 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2283 ignore_newlines: true,
2284 };
2285
2286 _ = view.update(cx, |view, cx| {
2287 view.change_selections(None, cx, |s| {
2288 s.select_display_ranges([
2289 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2290 ])
2291 });
2292 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2293 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2294 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2295 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2296 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2297 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\n");
2298 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2299 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2");
2300 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2301 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n");
2302 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2303 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2304 });
2305}
2306
2307#[gpui::test]
2308fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2309 init_test(cx, |_| {});
2310
2311 let view = cx.add_window(|cx| {
2312 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2313 build_editor(buffer.clone(), cx)
2314 });
2315 let del_to_next_word_end = DeleteToNextWordEnd {
2316 ignore_newlines: false,
2317 };
2318 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2319 ignore_newlines: true,
2320 };
2321
2322 _ = view.update(cx, |view, cx| {
2323 view.change_selections(None, cx, |s| {
2324 s.select_display_ranges([
2325 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2326 ])
2327 });
2328 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2329 assert_eq!(
2330 view.buffer.read(cx).read(cx).text(),
2331 "one\n two\nthree\n four"
2332 );
2333 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2334 assert_eq!(
2335 view.buffer.read(cx).read(cx).text(),
2336 "\n two\nthree\n four"
2337 );
2338 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2339 assert_eq!(view.buffer.read(cx).read(cx).text(), "two\nthree\n four");
2340 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2341 assert_eq!(view.buffer.read(cx).read(cx).text(), "\nthree\n four");
2342 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2343 assert_eq!(view.buffer.read(cx).read(cx).text(), "\n four");
2344 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2345 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2346 });
2347}
2348
2349#[gpui::test]
2350fn test_newline(cx: &mut TestAppContext) {
2351 init_test(cx, |_| {});
2352
2353 let view = cx.add_window(|cx| {
2354 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2355 build_editor(buffer.clone(), cx)
2356 });
2357
2358 _ = view.update(cx, |view, cx| {
2359 view.change_selections(None, cx, |s| {
2360 s.select_display_ranges([
2361 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2362 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2363 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2364 ])
2365 });
2366
2367 view.newline(&Newline, cx);
2368 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2369 });
2370}
2371
2372#[gpui::test]
2373fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2374 init_test(cx, |_| {});
2375
2376 let editor = cx.add_window(|cx| {
2377 let buffer = MultiBuffer::build_simple(
2378 "
2379 a
2380 b(
2381 X
2382 )
2383 c(
2384 X
2385 )
2386 "
2387 .unindent()
2388 .as_str(),
2389 cx,
2390 );
2391 let mut editor = build_editor(buffer.clone(), cx);
2392 editor.change_selections(None, cx, |s| {
2393 s.select_ranges([
2394 Point::new(2, 4)..Point::new(2, 5),
2395 Point::new(5, 4)..Point::new(5, 5),
2396 ])
2397 });
2398 editor
2399 });
2400
2401 _ = editor.update(cx, |editor, cx| {
2402 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2403 editor.buffer.update(cx, |buffer, cx| {
2404 buffer.edit(
2405 [
2406 (Point::new(1, 2)..Point::new(3, 0), ""),
2407 (Point::new(4, 2)..Point::new(6, 0), ""),
2408 ],
2409 None,
2410 cx,
2411 );
2412 assert_eq!(
2413 buffer.read(cx).text(),
2414 "
2415 a
2416 b()
2417 c()
2418 "
2419 .unindent()
2420 );
2421 });
2422 assert_eq!(
2423 editor.selections.ranges(cx),
2424 &[
2425 Point::new(1, 2)..Point::new(1, 2),
2426 Point::new(2, 2)..Point::new(2, 2),
2427 ],
2428 );
2429
2430 editor.newline(&Newline, cx);
2431 assert_eq!(
2432 editor.text(cx),
2433 "
2434 a
2435 b(
2436 )
2437 c(
2438 )
2439 "
2440 .unindent()
2441 );
2442
2443 // The selections are moved after the inserted newlines
2444 assert_eq!(
2445 editor.selections.ranges(cx),
2446 &[
2447 Point::new(2, 0)..Point::new(2, 0),
2448 Point::new(4, 0)..Point::new(4, 0),
2449 ],
2450 );
2451 });
2452}
2453
2454#[gpui::test]
2455async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2456 init_test(cx, |settings| {
2457 settings.defaults.tab_size = NonZeroU32::new(4)
2458 });
2459
2460 let language = Arc::new(
2461 Language::new(
2462 LanguageConfig::default(),
2463 Some(tree_sitter_rust::LANGUAGE.into()),
2464 )
2465 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2466 .unwrap(),
2467 );
2468
2469 let mut cx = EditorTestContext::new(cx).await;
2470 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2471 cx.set_state(indoc! {"
2472 const a: ˇA = (
2473 (ˇ
2474 «const_functionˇ»(ˇ),
2475 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2476 )ˇ
2477 ˇ);ˇ
2478 "});
2479
2480 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2481 cx.assert_editor_state(indoc! {"
2482 ˇ
2483 const a: A = (
2484 ˇ
2485 (
2486 ˇ
2487 ˇ
2488 const_function(),
2489 ˇ
2490 ˇ
2491 ˇ
2492 ˇ
2493 something_else,
2494 ˇ
2495 )
2496 ˇ
2497 ˇ
2498 );
2499 "});
2500}
2501
2502#[gpui::test]
2503async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2504 init_test(cx, |settings| {
2505 settings.defaults.tab_size = NonZeroU32::new(4)
2506 });
2507
2508 let language = Arc::new(
2509 Language::new(
2510 LanguageConfig::default(),
2511 Some(tree_sitter_rust::LANGUAGE.into()),
2512 )
2513 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2514 .unwrap(),
2515 );
2516
2517 let mut cx = EditorTestContext::new(cx).await;
2518 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2519 cx.set_state(indoc! {"
2520 const a: ˇA = (
2521 (ˇ
2522 «const_functionˇ»(ˇ),
2523 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2524 )ˇ
2525 ˇ);ˇ
2526 "});
2527
2528 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2529 cx.assert_editor_state(indoc! {"
2530 const a: A = (
2531 ˇ
2532 (
2533 ˇ
2534 const_function(),
2535 ˇ
2536 ˇ
2537 something_else,
2538 ˇ
2539 ˇ
2540 ˇ
2541 ˇ
2542 )
2543 ˇ
2544 );
2545 ˇ
2546 ˇ
2547 "});
2548}
2549
2550#[gpui::test]
2551async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2552 init_test(cx, |settings| {
2553 settings.defaults.tab_size = NonZeroU32::new(4)
2554 });
2555
2556 let language = Arc::new(Language::new(
2557 LanguageConfig {
2558 line_comments: vec!["//".into()],
2559 ..LanguageConfig::default()
2560 },
2561 None,
2562 ));
2563 {
2564 let mut cx = EditorTestContext::new(cx).await;
2565 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2566 cx.set_state(indoc! {"
2567 // Fooˇ
2568 "});
2569
2570 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2571 cx.assert_editor_state(indoc! {"
2572 // Foo
2573 //ˇ
2574 "});
2575 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2576 cx.set_state(indoc! {"
2577 ˇ// Foo
2578 "});
2579 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2580 cx.assert_editor_state(indoc! {"
2581
2582 ˇ// Foo
2583 "});
2584 }
2585 // Ensure that comment continuations can be disabled.
2586 update_test_language_settings(cx, |settings| {
2587 settings.defaults.extend_comment_on_newline = Some(false);
2588 });
2589 let mut cx = EditorTestContext::new(cx).await;
2590 cx.set_state(indoc! {"
2591 // Fooˇ
2592 "});
2593 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2594 cx.assert_editor_state(indoc! {"
2595 // Foo
2596 ˇ
2597 "});
2598}
2599
2600#[gpui::test]
2601fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2602 init_test(cx, |_| {});
2603
2604 let editor = cx.add_window(|cx| {
2605 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2606 let mut editor = build_editor(buffer.clone(), cx);
2607 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2608 editor
2609 });
2610
2611 _ = editor.update(cx, |editor, cx| {
2612 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2613 editor.buffer.update(cx, |buffer, cx| {
2614 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2615 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2616 });
2617 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2618
2619 editor.insert("Z", cx);
2620 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2621
2622 // The selections are moved after the inserted characters
2623 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2624 });
2625}
2626
2627#[gpui::test]
2628async fn test_tab(cx: &mut gpui::TestAppContext) {
2629 init_test(cx, |settings| {
2630 settings.defaults.tab_size = NonZeroU32::new(3)
2631 });
2632
2633 let mut cx = EditorTestContext::new(cx).await;
2634 cx.set_state(indoc! {"
2635 ˇabˇc
2636 ˇ🏀ˇ🏀ˇefg
2637 dˇ
2638 "});
2639 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2640 cx.assert_editor_state(indoc! {"
2641 ˇab ˇc
2642 ˇ🏀 ˇ🏀 ˇefg
2643 d ˇ
2644 "});
2645
2646 cx.set_state(indoc! {"
2647 a
2648 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2649 "});
2650 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2651 cx.assert_editor_state(indoc! {"
2652 a
2653 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2654 "});
2655}
2656
2657#[gpui::test]
2658async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2659 init_test(cx, |_| {});
2660
2661 let mut cx = EditorTestContext::new(cx).await;
2662 let language = Arc::new(
2663 Language::new(
2664 LanguageConfig::default(),
2665 Some(tree_sitter_rust::LANGUAGE.into()),
2666 )
2667 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2668 .unwrap(),
2669 );
2670 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2671
2672 // cursors that are already at the suggested indent level insert
2673 // a soft tab. cursors that are to the left of the suggested indent
2674 // auto-indent their line.
2675 cx.set_state(indoc! {"
2676 ˇ
2677 const a: B = (
2678 c(
2679 d(
2680 ˇ
2681 )
2682 ˇ
2683 ˇ )
2684 );
2685 "});
2686 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2687 cx.assert_editor_state(indoc! {"
2688 ˇ
2689 const a: B = (
2690 c(
2691 d(
2692 ˇ
2693 )
2694 ˇ
2695 ˇ)
2696 );
2697 "});
2698
2699 // handle auto-indent when there are multiple cursors on the same line
2700 cx.set_state(indoc! {"
2701 const a: B = (
2702 c(
2703 ˇ ˇ
2704 ˇ )
2705 );
2706 "});
2707 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2708 cx.assert_editor_state(indoc! {"
2709 const a: B = (
2710 c(
2711 ˇ
2712 ˇ)
2713 );
2714 "});
2715}
2716
2717#[gpui::test]
2718async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2719 init_test(cx, |settings| {
2720 settings.defaults.tab_size = NonZeroU32::new(4)
2721 });
2722
2723 let language = Arc::new(
2724 Language::new(
2725 LanguageConfig::default(),
2726 Some(tree_sitter_rust::LANGUAGE.into()),
2727 )
2728 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2729 .unwrap(),
2730 );
2731
2732 let mut cx = EditorTestContext::new(cx).await;
2733 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2734 cx.set_state(indoc! {"
2735 fn a() {
2736 if b {
2737 \t ˇc
2738 }
2739 }
2740 "});
2741
2742 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2743 cx.assert_editor_state(indoc! {"
2744 fn a() {
2745 if b {
2746 ˇc
2747 }
2748 }
2749 "});
2750}
2751
2752#[gpui::test]
2753async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2754 init_test(cx, |settings| {
2755 settings.defaults.tab_size = NonZeroU32::new(4);
2756 });
2757
2758 let mut cx = EditorTestContext::new(cx).await;
2759
2760 cx.set_state(indoc! {"
2761 «oneˇ» «twoˇ»
2762 three
2763 four
2764 "});
2765 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2766 cx.assert_editor_state(indoc! {"
2767 «oneˇ» «twoˇ»
2768 three
2769 four
2770 "});
2771
2772 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2773 cx.assert_editor_state(indoc! {"
2774 «oneˇ» «twoˇ»
2775 three
2776 four
2777 "});
2778
2779 // select across line ending
2780 cx.set_state(indoc! {"
2781 one two
2782 t«hree
2783 ˇ» four
2784 "});
2785 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2786 cx.assert_editor_state(indoc! {"
2787 one two
2788 t«hree
2789 ˇ» four
2790 "});
2791
2792 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2793 cx.assert_editor_state(indoc! {"
2794 one two
2795 t«hree
2796 ˇ» four
2797 "});
2798
2799 // Ensure that indenting/outdenting works when the cursor is at column 0.
2800 cx.set_state(indoc! {"
2801 one two
2802 ˇthree
2803 four
2804 "});
2805 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2806 cx.assert_editor_state(indoc! {"
2807 one two
2808 ˇthree
2809 four
2810 "});
2811
2812 cx.set_state(indoc! {"
2813 one two
2814 ˇ three
2815 four
2816 "});
2817 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2818 cx.assert_editor_state(indoc! {"
2819 one two
2820 ˇthree
2821 four
2822 "});
2823}
2824
2825#[gpui::test]
2826async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2827 init_test(cx, |settings| {
2828 settings.defaults.hard_tabs = Some(true);
2829 });
2830
2831 let mut cx = EditorTestContext::new(cx).await;
2832
2833 // select two ranges on one line
2834 cx.set_state(indoc! {"
2835 «oneˇ» «twoˇ»
2836 three
2837 four
2838 "});
2839 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2840 cx.assert_editor_state(indoc! {"
2841 \t«oneˇ» «twoˇ»
2842 three
2843 four
2844 "});
2845 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2846 cx.assert_editor_state(indoc! {"
2847 \t\t«oneˇ» «twoˇ»
2848 three
2849 four
2850 "});
2851 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2852 cx.assert_editor_state(indoc! {"
2853 \t«oneˇ» «twoˇ»
2854 three
2855 four
2856 "});
2857 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2858 cx.assert_editor_state(indoc! {"
2859 «oneˇ» «twoˇ»
2860 three
2861 four
2862 "});
2863
2864 // select across a line ending
2865 cx.set_state(indoc! {"
2866 one two
2867 t«hree
2868 ˇ»four
2869 "});
2870 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2871 cx.assert_editor_state(indoc! {"
2872 one two
2873 \tt«hree
2874 ˇ»four
2875 "});
2876 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2877 cx.assert_editor_state(indoc! {"
2878 one two
2879 \t\tt«hree
2880 ˇ»four
2881 "});
2882 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2883 cx.assert_editor_state(indoc! {"
2884 one two
2885 \tt«hree
2886 ˇ»four
2887 "});
2888 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2889 cx.assert_editor_state(indoc! {"
2890 one two
2891 t«hree
2892 ˇ»four
2893 "});
2894
2895 // Ensure that indenting/outdenting works when the cursor is at column 0.
2896 cx.set_state(indoc! {"
2897 one two
2898 ˇthree
2899 four
2900 "});
2901 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2902 cx.assert_editor_state(indoc! {"
2903 one two
2904 ˇthree
2905 four
2906 "});
2907 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2908 cx.assert_editor_state(indoc! {"
2909 one two
2910 \tˇthree
2911 four
2912 "});
2913 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2914 cx.assert_editor_state(indoc! {"
2915 one two
2916 ˇthree
2917 four
2918 "});
2919}
2920
2921#[gpui::test]
2922fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2923 init_test(cx, |settings| {
2924 settings.languages.extend([
2925 (
2926 "TOML".into(),
2927 LanguageSettingsContent {
2928 tab_size: NonZeroU32::new(2),
2929 ..Default::default()
2930 },
2931 ),
2932 (
2933 "Rust".into(),
2934 LanguageSettingsContent {
2935 tab_size: NonZeroU32::new(4),
2936 ..Default::default()
2937 },
2938 ),
2939 ]);
2940 });
2941
2942 let toml_language = Arc::new(Language::new(
2943 LanguageConfig {
2944 name: "TOML".into(),
2945 ..Default::default()
2946 },
2947 None,
2948 ));
2949 let rust_language = Arc::new(Language::new(
2950 LanguageConfig {
2951 name: "Rust".into(),
2952 ..Default::default()
2953 },
2954 None,
2955 ));
2956
2957 let toml_buffer =
2958 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2959 let rust_buffer = cx.new_model(|cx| {
2960 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2961 });
2962 let multibuffer = cx.new_model(|cx| {
2963 let mut multibuffer = MultiBuffer::new(ReadWrite);
2964 multibuffer.push_excerpts(
2965 toml_buffer.clone(),
2966 [ExcerptRange {
2967 context: Point::new(0, 0)..Point::new(2, 0),
2968 primary: None,
2969 }],
2970 cx,
2971 );
2972 multibuffer.push_excerpts(
2973 rust_buffer.clone(),
2974 [ExcerptRange {
2975 context: Point::new(0, 0)..Point::new(1, 0),
2976 primary: None,
2977 }],
2978 cx,
2979 );
2980 multibuffer
2981 });
2982
2983 cx.add_window(|cx| {
2984 let mut editor = build_editor(multibuffer, cx);
2985
2986 assert_eq!(
2987 editor.text(cx),
2988 indoc! {"
2989 a = 1
2990 b = 2
2991
2992 const c: usize = 3;
2993 "}
2994 );
2995
2996 select_ranges(
2997 &mut editor,
2998 indoc! {"
2999 «aˇ» = 1
3000 b = 2
3001
3002 «const c:ˇ» usize = 3;
3003 "},
3004 cx,
3005 );
3006
3007 editor.tab(&Tab, cx);
3008 assert_text_with_selections(
3009 &mut editor,
3010 indoc! {"
3011 «aˇ» = 1
3012 b = 2
3013
3014 «const c:ˇ» usize = 3;
3015 "},
3016 cx,
3017 );
3018 editor.tab_prev(&TabPrev, cx);
3019 assert_text_with_selections(
3020 &mut editor,
3021 indoc! {"
3022 «aˇ» = 1
3023 b = 2
3024
3025 «const c:ˇ» usize = 3;
3026 "},
3027 cx,
3028 );
3029
3030 editor
3031 });
3032}
3033
3034#[gpui::test]
3035async fn test_backspace(cx: &mut gpui::TestAppContext) {
3036 init_test(cx, |_| {});
3037
3038 let mut cx = EditorTestContext::new(cx).await;
3039
3040 // Basic backspace
3041 cx.set_state(indoc! {"
3042 onˇe two three
3043 fou«rˇ» five six
3044 seven «ˇeight nine
3045 »ten
3046 "});
3047 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3048 cx.assert_editor_state(indoc! {"
3049 oˇe two three
3050 fouˇ five six
3051 seven ˇten
3052 "});
3053
3054 // Test backspace inside and around indents
3055 cx.set_state(indoc! {"
3056 zero
3057 ˇone
3058 ˇtwo
3059 ˇ ˇ ˇ three
3060 ˇ ˇ four
3061 "});
3062 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3063 cx.assert_editor_state(indoc! {"
3064 zero
3065 ˇone
3066 ˇtwo
3067 ˇ threeˇ four
3068 "});
3069
3070 // Test backspace with line_mode set to true
3071 cx.update_editor(|e, _| e.selections.line_mode = true);
3072 cx.set_state(indoc! {"
3073 The ˇquick ˇbrown
3074 fox jumps over
3075 the lazy dog
3076 ˇThe qu«ick bˇ»rown"});
3077 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3078 cx.assert_editor_state(indoc! {"
3079 ˇfox jumps over
3080 the lazy dogˇ"});
3081}
3082
3083#[gpui::test]
3084async fn test_delete(cx: &mut gpui::TestAppContext) {
3085 init_test(cx, |_| {});
3086
3087 let mut cx = EditorTestContext::new(cx).await;
3088 cx.set_state(indoc! {"
3089 onˇe two three
3090 fou«rˇ» five six
3091 seven «ˇeight nine
3092 »ten
3093 "});
3094 cx.update_editor(|e, cx| e.delete(&Delete, cx));
3095 cx.assert_editor_state(indoc! {"
3096 onˇ two three
3097 fouˇ five six
3098 seven ˇten
3099 "});
3100
3101 // Test backspace with line_mode set to true
3102 cx.update_editor(|e, _| e.selections.line_mode = true);
3103 cx.set_state(indoc! {"
3104 The ˇquick ˇbrown
3105 fox «ˇjum»ps over
3106 the lazy dog
3107 ˇThe qu«ick bˇ»rown"});
3108 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3109 cx.assert_editor_state("ˇthe lazy dogˇ");
3110}
3111
3112#[gpui::test]
3113fn test_delete_line(cx: &mut TestAppContext) {
3114 init_test(cx, |_| {});
3115
3116 let view = cx.add_window(|cx| {
3117 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3118 build_editor(buffer, cx)
3119 });
3120 _ = view.update(cx, |view, cx| {
3121 view.change_selections(None, cx, |s| {
3122 s.select_display_ranges([
3123 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3124 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3125 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3126 ])
3127 });
3128 view.delete_line(&DeleteLine, cx);
3129 assert_eq!(view.display_text(cx), "ghi");
3130 assert_eq!(
3131 view.selections.display_ranges(cx),
3132 vec![
3133 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3134 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3135 ]
3136 );
3137 });
3138
3139 let view = cx.add_window(|cx| {
3140 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3141 build_editor(buffer, cx)
3142 });
3143 _ = view.update(cx, |view, cx| {
3144 view.change_selections(None, cx, |s| {
3145 s.select_display_ranges([
3146 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3147 ])
3148 });
3149 view.delete_line(&DeleteLine, cx);
3150 assert_eq!(view.display_text(cx), "ghi\n");
3151 assert_eq!(
3152 view.selections.display_ranges(cx),
3153 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3154 );
3155 });
3156}
3157
3158#[gpui::test]
3159fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3160 init_test(cx, |_| {});
3161
3162 cx.add_window(|cx| {
3163 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3164 let mut editor = build_editor(buffer.clone(), cx);
3165 let buffer = buffer.read(cx).as_singleton().unwrap();
3166
3167 assert_eq!(
3168 editor.selections.ranges::<Point>(cx),
3169 &[Point::new(0, 0)..Point::new(0, 0)]
3170 );
3171
3172 // When on single line, replace newline at end by space
3173 editor.join_lines(&JoinLines, cx);
3174 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3175 assert_eq!(
3176 editor.selections.ranges::<Point>(cx),
3177 &[Point::new(0, 3)..Point::new(0, 3)]
3178 );
3179
3180 // When multiple lines are selected, remove newlines that are spanned by the selection
3181 editor.change_selections(None, cx, |s| {
3182 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3183 });
3184 editor.join_lines(&JoinLines, cx);
3185 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3186 assert_eq!(
3187 editor.selections.ranges::<Point>(cx),
3188 &[Point::new(0, 11)..Point::new(0, 11)]
3189 );
3190
3191 // Undo should be transactional
3192 editor.undo(&Undo, cx);
3193 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3194 assert_eq!(
3195 editor.selections.ranges::<Point>(cx),
3196 &[Point::new(0, 5)..Point::new(2, 2)]
3197 );
3198
3199 // When joining an empty line don't insert a space
3200 editor.change_selections(None, cx, |s| {
3201 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3202 });
3203 editor.join_lines(&JoinLines, cx);
3204 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3205 assert_eq!(
3206 editor.selections.ranges::<Point>(cx),
3207 [Point::new(2, 3)..Point::new(2, 3)]
3208 );
3209
3210 // We can remove trailing newlines
3211 editor.join_lines(&JoinLines, cx);
3212 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3213 assert_eq!(
3214 editor.selections.ranges::<Point>(cx),
3215 [Point::new(2, 3)..Point::new(2, 3)]
3216 );
3217
3218 // We don't blow up on the last line
3219 editor.join_lines(&JoinLines, cx);
3220 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3221 assert_eq!(
3222 editor.selections.ranges::<Point>(cx),
3223 [Point::new(2, 3)..Point::new(2, 3)]
3224 );
3225
3226 // reset to test indentation
3227 editor.buffer.update(cx, |buffer, cx| {
3228 buffer.edit(
3229 [
3230 (Point::new(1, 0)..Point::new(1, 2), " "),
3231 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3232 ],
3233 None,
3234 cx,
3235 )
3236 });
3237
3238 // We remove any leading spaces
3239 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3240 editor.change_selections(None, cx, |s| {
3241 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3242 });
3243 editor.join_lines(&JoinLines, cx);
3244 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3245
3246 // We don't insert a space for a line containing only spaces
3247 editor.join_lines(&JoinLines, cx);
3248 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3249
3250 // We ignore any leading tabs
3251 editor.join_lines(&JoinLines, cx);
3252 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3253
3254 editor
3255 });
3256}
3257
3258#[gpui::test]
3259fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3260 init_test(cx, |_| {});
3261
3262 cx.add_window(|cx| {
3263 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3264 let mut editor = build_editor(buffer.clone(), cx);
3265 let buffer = buffer.read(cx).as_singleton().unwrap();
3266
3267 editor.change_selections(None, cx, |s| {
3268 s.select_ranges([
3269 Point::new(0, 2)..Point::new(1, 1),
3270 Point::new(1, 2)..Point::new(1, 2),
3271 Point::new(3, 1)..Point::new(3, 2),
3272 ])
3273 });
3274
3275 editor.join_lines(&JoinLines, cx);
3276 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3277
3278 assert_eq!(
3279 editor.selections.ranges::<Point>(cx),
3280 [
3281 Point::new(0, 7)..Point::new(0, 7),
3282 Point::new(1, 3)..Point::new(1, 3)
3283 ]
3284 );
3285 editor
3286 });
3287}
3288
3289#[gpui::test]
3290async fn test_join_lines_with_git_diff_base(
3291 executor: BackgroundExecutor,
3292 cx: &mut gpui::TestAppContext,
3293) {
3294 init_test(cx, |_| {});
3295
3296 let mut cx = EditorTestContext::new(cx).await;
3297
3298 let diff_base = r#"
3299 Line 0
3300 Line 1
3301 Line 2
3302 Line 3
3303 "#
3304 .unindent();
3305
3306 cx.set_state(
3307 &r#"
3308 ˇLine 0
3309 Line 1
3310 Line 2
3311 Line 3
3312 "#
3313 .unindent(),
3314 );
3315
3316 cx.set_diff_base(&diff_base);
3317 executor.run_until_parked();
3318
3319 // Join lines
3320 cx.update_editor(|editor, cx| {
3321 editor.join_lines(&JoinLines, cx);
3322 });
3323 executor.run_until_parked();
3324
3325 cx.assert_editor_state(
3326 &r#"
3327 Line 0ˇ Line 1
3328 Line 2
3329 Line 3
3330 "#
3331 .unindent(),
3332 );
3333 // Join again
3334 cx.update_editor(|editor, cx| {
3335 editor.join_lines(&JoinLines, cx);
3336 });
3337 executor.run_until_parked();
3338
3339 cx.assert_editor_state(
3340 &r#"
3341 Line 0 Line 1ˇ Line 2
3342 Line 3
3343 "#
3344 .unindent(),
3345 );
3346}
3347
3348#[gpui::test]
3349async fn test_custom_newlines_cause_no_false_positive_diffs(
3350 executor: BackgroundExecutor,
3351 cx: &mut gpui::TestAppContext,
3352) {
3353 init_test(cx, |_| {});
3354 let mut cx = EditorTestContext::new(cx).await;
3355 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3356 cx.set_diff_base("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3357 executor.run_until_parked();
3358
3359 cx.update_editor(|editor, cx| {
3360 let snapshot = editor.snapshot(cx);
3361 assert_eq!(
3362 snapshot
3363 .diff_map
3364 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
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(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_autoindent_selections(cx: &mut gpui::TestAppContext) {
5525 init_test(cx, |_| {});
5526
5527 {
5528 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5529 cx.set_state(indoc! {"
5530 impl A {
5531
5532 fn b() {}
5533
5534 «fn c() {
5535
5536 }ˇ»
5537 }
5538 "});
5539
5540 cx.update_editor(|editor, cx| {
5541 editor.autoindent(&Default::default(), cx);
5542 });
5543
5544 cx.assert_editor_state(indoc! {"
5545 impl A {
5546
5547 fn b() {}
5548
5549 «fn c() {
5550
5551 }ˇ»
5552 }
5553 "});
5554 }
5555
5556 {
5557 let mut cx = EditorTestContext::new_multibuffer(
5558 cx,
5559 [indoc! { "
5560 impl A {
5561 «
5562 // a
5563 fn b(){}
5564 »
5565 «
5566 }
5567 fn c(){}
5568 »
5569 "}],
5570 );
5571
5572 let buffer = cx.update_editor(|editor, cx| {
5573 let buffer = editor.buffer().update(cx, |buffer, _| {
5574 buffer.all_buffers().iter().next().unwrap().clone()
5575 });
5576 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5577 buffer
5578 });
5579
5580 cx.run_until_parked();
5581 cx.update_editor(|editor, cx| {
5582 editor.select_all(&Default::default(), cx);
5583 editor.autoindent(&Default::default(), cx)
5584 });
5585 cx.run_until_parked();
5586
5587 cx.update(|cx| {
5588 pretty_assertions::assert_eq!(
5589 buffer.read(cx).text(),
5590 indoc! { "
5591 impl A {
5592
5593 // a
5594 fn b(){}
5595
5596
5597 }
5598 fn c(){}
5599
5600 " }
5601 )
5602 });
5603 }
5604}
5605
5606#[gpui::test]
5607async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5608 init_test(cx, |_| {});
5609
5610 let mut cx = EditorTestContext::new(cx).await;
5611
5612 let language = Arc::new(Language::new(
5613 LanguageConfig {
5614 brackets: BracketPairConfig {
5615 pairs: vec![
5616 BracketPair {
5617 start: "{".to_string(),
5618 end: "}".to_string(),
5619 close: true,
5620 surround: true,
5621 newline: true,
5622 },
5623 BracketPair {
5624 start: "(".to_string(),
5625 end: ")".to_string(),
5626 close: true,
5627 surround: true,
5628 newline: true,
5629 },
5630 BracketPair {
5631 start: "/*".to_string(),
5632 end: " */".to_string(),
5633 close: true,
5634 surround: true,
5635 newline: true,
5636 },
5637 BracketPair {
5638 start: "[".to_string(),
5639 end: "]".to_string(),
5640 close: false,
5641 surround: false,
5642 newline: true,
5643 },
5644 BracketPair {
5645 start: "\"".to_string(),
5646 end: "\"".to_string(),
5647 close: true,
5648 surround: true,
5649 newline: false,
5650 },
5651 BracketPair {
5652 start: "<".to_string(),
5653 end: ">".to_string(),
5654 close: false,
5655 surround: true,
5656 newline: true,
5657 },
5658 ],
5659 ..Default::default()
5660 },
5661 autoclose_before: "})]".to_string(),
5662 ..Default::default()
5663 },
5664 Some(tree_sitter_rust::LANGUAGE.into()),
5665 ));
5666
5667 cx.language_registry().add(language.clone());
5668 cx.update_buffer(|buffer, cx| {
5669 buffer.set_language(Some(language), cx);
5670 });
5671
5672 cx.set_state(
5673 &r#"
5674 🏀ˇ
5675 εˇ
5676 ❤️ˇ
5677 "#
5678 .unindent(),
5679 );
5680
5681 // autoclose multiple nested brackets at multiple cursors
5682 cx.update_editor(|view, cx| {
5683 view.handle_input("{", cx);
5684 view.handle_input("{", cx);
5685 view.handle_input("{", cx);
5686 });
5687 cx.assert_editor_state(
5688 &"
5689 🏀{{{ˇ}}}
5690 ε{{{ˇ}}}
5691 ❤️{{{ˇ}}}
5692 "
5693 .unindent(),
5694 );
5695
5696 // insert a different closing bracket
5697 cx.update_editor(|view, cx| {
5698 view.handle_input(")", cx);
5699 });
5700 cx.assert_editor_state(
5701 &"
5702 🏀{{{)ˇ}}}
5703 ε{{{)ˇ}}}
5704 ❤️{{{)ˇ}}}
5705 "
5706 .unindent(),
5707 );
5708
5709 // skip over the auto-closed brackets when typing a closing bracket
5710 cx.update_editor(|view, cx| {
5711 view.move_right(&MoveRight, cx);
5712 view.handle_input("}", cx);
5713 view.handle_input("}", cx);
5714 view.handle_input("}", cx);
5715 });
5716 cx.assert_editor_state(
5717 &"
5718 🏀{{{)}}}}ˇ
5719 ε{{{)}}}}ˇ
5720 ❤️{{{)}}}}ˇ
5721 "
5722 .unindent(),
5723 );
5724
5725 // autoclose multi-character pairs
5726 cx.set_state(
5727 &"
5728 ˇ
5729 ˇ
5730 "
5731 .unindent(),
5732 );
5733 cx.update_editor(|view, cx| {
5734 view.handle_input("/", cx);
5735 view.handle_input("*", cx);
5736 });
5737 cx.assert_editor_state(
5738 &"
5739 /*ˇ */
5740 /*ˇ */
5741 "
5742 .unindent(),
5743 );
5744
5745 // one cursor autocloses a multi-character pair, one cursor
5746 // does not autoclose.
5747 cx.set_state(
5748 &"
5749 /ˇ
5750 ˇ
5751 "
5752 .unindent(),
5753 );
5754 cx.update_editor(|view, cx| view.handle_input("*", cx));
5755 cx.assert_editor_state(
5756 &"
5757 /*ˇ */
5758 *ˇ
5759 "
5760 .unindent(),
5761 );
5762
5763 // Don't autoclose if the next character isn't whitespace and isn't
5764 // listed in the language's "autoclose_before" section.
5765 cx.set_state("ˇa b");
5766 cx.update_editor(|view, cx| view.handle_input("{", cx));
5767 cx.assert_editor_state("{ˇa b");
5768
5769 // Don't autoclose if `close` is false for the bracket pair
5770 cx.set_state("ˇ");
5771 cx.update_editor(|view, cx| view.handle_input("[", cx));
5772 cx.assert_editor_state("[ˇ");
5773
5774 // Surround with brackets if text is selected
5775 cx.set_state("«aˇ» b");
5776 cx.update_editor(|view, cx| view.handle_input("{", cx));
5777 cx.assert_editor_state("{«aˇ»} b");
5778
5779 // Autclose pair where the start and end characters are the same
5780 cx.set_state("aˇ");
5781 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5782 cx.assert_editor_state("a\"ˇ\"");
5783 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5784 cx.assert_editor_state("a\"\"ˇ");
5785
5786 // Don't autoclose pair if autoclose is disabled
5787 cx.set_state("ˇ");
5788 cx.update_editor(|view, cx| view.handle_input("<", cx));
5789 cx.assert_editor_state("<ˇ");
5790
5791 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5792 cx.set_state("«aˇ» b");
5793 cx.update_editor(|view, cx| view.handle_input("<", cx));
5794 cx.assert_editor_state("<«aˇ»> b");
5795}
5796
5797#[gpui::test]
5798async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5799 init_test(cx, |settings| {
5800 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5801 });
5802
5803 let mut cx = EditorTestContext::new(cx).await;
5804
5805 let language = Arc::new(Language::new(
5806 LanguageConfig {
5807 brackets: BracketPairConfig {
5808 pairs: vec![
5809 BracketPair {
5810 start: "{".to_string(),
5811 end: "}".to_string(),
5812 close: true,
5813 surround: true,
5814 newline: true,
5815 },
5816 BracketPair {
5817 start: "(".to_string(),
5818 end: ")".to_string(),
5819 close: true,
5820 surround: true,
5821 newline: true,
5822 },
5823 BracketPair {
5824 start: "[".to_string(),
5825 end: "]".to_string(),
5826 close: false,
5827 surround: false,
5828 newline: true,
5829 },
5830 ],
5831 ..Default::default()
5832 },
5833 autoclose_before: "})]".to_string(),
5834 ..Default::default()
5835 },
5836 Some(tree_sitter_rust::LANGUAGE.into()),
5837 ));
5838
5839 cx.language_registry().add(language.clone());
5840 cx.update_buffer(|buffer, cx| {
5841 buffer.set_language(Some(language), cx);
5842 });
5843
5844 cx.set_state(
5845 &"
5846 ˇ
5847 ˇ
5848 ˇ
5849 "
5850 .unindent(),
5851 );
5852
5853 // ensure only matching closing brackets are skipped over
5854 cx.update_editor(|view, cx| {
5855 view.handle_input("}", cx);
5856 view.move_left(&MoveLeft, cx);
5857 view.handle_input(")", cx);
5858 view.move_left(&MoveLeft, cx);
5859 });
5860 cx.assert_editor_state(
5861 &"
5862 ˇ)}
5863 ˇ)}
5864 ˇ)}
5865 "
5866 .unindent(),
5867 );
5868
5869 // skip-over closing brackets at multiple cursors
5870 cx.update_editor(|view, cx| {
5871 view.handle_input(")", cx);
5872 view.handle_input("}", cx);
5873 });
5874 cx.assert_editor_state(
5875 &"
5876 )}ˇ
5877 )}ˇ
5878 )}ˇ
5879 "
5880 .unindent(),
5881 );
5882
5883 // ignore non-close brackets
5884 cx.update_editor(|view, cx| {
5885 view.handle_input("]", cx);
5886 view.move_left(&MoveLeft, cx);
5887 view.handle_input("]", cx);
5888 });
5889 cx.assert_editor_state(
5890 &"
5891 )}]ˇ]
5892 )}]ˇ]
5893 )}]ˇ]
5894 "
5895 .unindent(),
5896 );
5897}
5898
5899#[gpui::test]
5900async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5901 init_test(cx, |_| {});
5902
5903 let mut cx = EditorTestContext::new(cx).await;
5904
5905 let html_language = Arc::new(
5906 Language::new(
5907 LanguageConfig {
5908 name: "HTML".into(),
5909 brackets: BracketPairConfig {
5910 pairs: vec![
5911 BracketPair {
5912 start: "<".into(),
5913 end: ">".into(),
5914 close: true,
5915 ..Default::default()
5916 },
5917 BracketPair {
5918 start: "{".into(),
5919 end: "}".into(),
5920 close: true,
5921 ..Default::default()
5922 },
5923 BracketPair {
5924 start: "(".into(),
5925 end: ")".into(),
5926 close: true,
5927 ..Default::default()
5928 },
5929 ],
5930 ..Default::default()
5931 },
5932 autoclose_before: "})]>".into(),
5933 ..Default::default()
5934 },
5935 Some(tree_sitter_html::language()),
5936 )
5937 .with_injection_query(
5938 r#"
5939 (script_element
5940 (raw_text) @content
5941 (#set! "language" "javascript"))
5942 "#,
5943 )
5944 .unwrap(),
5945 );
5946
5947 let javascript_language = Arc::new(Language::new(
5948 LanguageConfig {
5949 name: "JavaScript".into(),
5950 brackets: BracketPairConfig {
5951 pairs: vec![
5952 BracketPair {
5953 start: "/*".into(),
5954 end: " */".into(),
5955 close: true,
5956 ..Default::default()
5957 },
5958 BracketPair {
5959 start: "{".into(),
5960 end: "}".into(),
5961 close: true,
5962 ..Default::default()
5963 },
5964 BracketPair {
5965 start: "(".into(),
5966 end: ")".into(),
5967 close: true,
5968 ..Default::default()
5969 },
5970 ],
5971 ..Default::default()
5972 },
5973 autoclose_before: "})]>".into(),
5974 ..Default::default()
5975 },
5976 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
5977 ));
5978
5979 cx.language_registry().add(html_language.clone());
5980 cx.language_registry().add(javascript_language.clone());
5981
5982 cx.update_buffer(|buffer, cx| {
5983 buffer.set_language(Some(html_language), cx);
5984 });
5985
5986 cx.set_state(
5987 &r#"
5988 <body>ˇ
5989 <script>
5990 var x = 1;ˇ
5991 </script>
5992 </body>ˇ
5993 "#
5994 .unindent(),
5995 );
5996
5997 // Precondition: different languages are active at different locations.
5998 cx.update_editor(|editor, cx| {
5999 let snapshot = editor.snapshot(cx);
6000 let cursors = editor.selections.ranges::<usize>(cx);
6001 let languages = cursors
6002 .iter()
6003 .map(|c| snapshot.language_at(c.start).unwrap().name())
6004 .collect::<Vec<_>>();
6005 assert_eq!(
6006 languages,
6007 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6008 );
6009 });
6010
6011 // Angle brackets autoclose in HTML, but not JavaScript.
6012 cx.update_editor(|editor, cx| {
6013 editor.handle_input("<", cx);
6014 editor.handle_input("a", cx);
6015 });
6016 cx.assert_editor_state(
6017 &r#"
6018 <body><aˇ>
6019 <script>
6020 var x = 1;<aˇ
6021 </script>
6022 </body><aˇ>
6023 "#
6024 .unindent(),
6025 );
6026
6027 // Curly braces and parens autoclose in both HTML and JavaScript.
6028 cx.update_editor(|editor, cx| {
6029 editor.handle_input(" b=", cx);
6030 editor.handle_input("{", cx);
6031 editor.handle_input("c", cx);
6032 editor.handle_input("(", cx);
6033 });
6034 cx.assert_editor_state(
6035 &r#"
6036 <body><a b={c(ˇ)}>
6037 <script>
6038 var x = 1;<a b={c(ˇ)}
6039 </script>
6040 </body><a b={c(ˇ)}>
6041 "#
6042 .unindent(),
6043 );
6044
6045 // Brackets that were already autoclosed are skipped.
6046 cx.update_editor(|editor, cx| {
6047 editor.handle_input(")", cx);
6048 editor.handle_input("d", cx);
6049 editor.handle_input("}", cx);
6050 });
6051 cx.assert_editor_state(
6052 &r#"
6053 <body><a b={c()d}ˇ>
6054 <script>
6055 var x = 1;<a b={c()d}ˇ
6056 </script>
6057 </body><a b={c()d}ˇ>
6058 "#
6059 .unindent(),
6060 );
6061 cx.update_editor(|editor, cx| {
6062 editor.handle_input(">", cx);
6063 });
6064 cx.assert_editor_state(
6065 &r#"
6066 <body><a b={c()d}>ˇ
6067 <script>
6068 var x = 1;<a b={c()d}>ˇ
6069 </script>
6070 </body><a b={c()d}>ˇ
6071 "#
6072 .unindent(),
6073 );
6074
6075 // Reset
6076 cx.set_state(
6077 &r#"
6078 <body>ˇ
6079 <script>
6080 var x = 1;ˇ
6081 </script>
6082 </body>ˇ
6083 "#
6084 .unindent(),
6085 );
6086
6087 cx.update_editor(|editor, cx| {
6088 editor.handle_input("<", cx);
6089 });
6090 cx.assert_editor_state(
6091 &r#"
6092 <body><ˇ>
6093 <script>
6094 var x = 1;<ˇ
6095 </script>
6096 </body><ˇ>
6097 "#
6098 .unindent(),
6099 );
6100
6101 // When backspacing, the closing angle brackets are removed.
6102 cx.update_editor(|editor, cx| {
6103 editor.backspace(&Backspace, cx);
6104 });
6105 cx.assert_editor_state(
6106 &r#"
6107 <body>ˇ
6108 <script>
6109 var x = 1;ˇ
6110 </script>
6111 </body>ˇ
6112 "#
6113 .unindent(),
6114 );
6115
6116 // Block comments autoclose in JavaScript, but not HTML.
6117 cx.update_editor(|editor, cx| {
6118 editor.handle_input("/", cx);
6119 editor.handle_input("*", cx);
6120 });
6121 cx.assert_editor_state(
6122 &r#"
6123 <body>/*ˇ
6124 <script>
6125 var x = 1;/*ˇ */
6126 </script>
6127 </body>/*ˇ
6128 "#
6129 .unindent(),
6130 );
6131}
6132
6133#[gpui::test]
6134async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
6135 init_test(cx, |_| {});
6136
6137 let mut cx = EditorTestContext::new(cx).await;
6138
6139 let rust_language = Arc::new(
6140 Language::new(
6141 LanguageConfig {
6142 name: "Rust".into(),
6143 brackets: serde_json::from_value(json!([
6144 { "start": "{", "end": "}", "close": true, "newline": true },
6145 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6146 ]))
6147 .unwrap(),
6148 autoclose_before: "})]>".into(),
6149 ..Default::default()
6150 },
6151 Some(tree_sitter_rust::LANGUAGE.into()),
6152 )
6153 .with_override_query("(string_literal) @string")
6154 .unwrap(),
6155 );
6156
6157 cx.language_registry().add(rust_language.clone());
6158 cx.update_buffer(|buffer, cx| {
6159 buffer.set_language(Some(rust_language), cx);
6160 });
6161
6162 cx.set_state(
6163 &r#"
6164 let x = ˇ
6165 "#
6166 .unindent(),
6167 );
6168
6169 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6170 cx.update_editor(|editor, cx| {
6171 editor.handle_input("\"", cx);
6172 });
6173 cx.assert_editor_state(
6174 &r#"
6175 let x = "ˇ"
6176 "#
6177 .unindent(),
6178 );
6179
6180 // Inserting another quotation mark. The cursor moves across the existing
6181 // automatically-inserted quotation mark.
6182 cx.update_editor(|editor, cx| {
6183 editor.handle_input("\"", cx);
6184 });
6185 cx.assert_editor_state(
6186 &r#"
6187 let x = ""ˇ
6188 "#
6189 .unindent(),
6190 );
6191
6192 // Reset
6193 cx.set_state(
6194 &r#"
6195 let x = ˇ
6196 "#
6197 .unindent(),
6198 );
6199
6200 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6201 cx.update_editor(|editor, cx| {
6202 editor.handle_input("\"", cx);
6203 editor.handle_input(" ", cx);
6204 editor.move_left(&Default::default(), cx);
6205 editor.handle_input("\\", cx);
6206 editor.handle_input("\"", cx);
6207 });
6208 cx.assert_editor_state(
6209 &r#"
6210 let x = "\"ˇ "
6211 "#
6212 .unindent(),
6213 );
6214
6215 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6216 // mark. Nothing is inserted.
6217 cx.update_editor(|editor, cx| {
6218 editor.move_right(&Default::default(), cx);
6219 editor.handle_input("\"", cx);
6220 });
6221 cx.assert_editor_state(
6222 &r#"
6223 let x = "\" "ˇ
6224 "#
6225 .unindent(),
6226 );
6227}
6228
6229#[gpui::test]
6230async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
6231 init_test(cx, |_| {});
6232
6233 let language = Arc::new(Language::new(
6234 LanguageConfig {
6235 brackets: BracketPairConfig {
6236 pairs: vec![
6237 BracketPair {
6238 start: "{".to_string(),
6239 end: "}".to_string(),
6240 close: true,
6241 surround: true,
6242 newline: true,
6243 },
6244 BracketPair {
6245 start: "/* ".to_string(),
6246 end: "*/".to_string(),
6247 close: true,
6248 surround: true,
6249 ..Default::default()
6250 },
6251 ],
6252 ..Default::default()
6253 },
6254 ..Default::default()
6255 },
6256 Some(tree_sitter_rust::LANGUAGE.into()),
6257 ));
6258
6259 let text = r#"
6260 a
6261 b
6262 c
6263 "#
6264 .unindent();
6265
6266 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6267 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6268 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6269 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6270 .await;
6271
6272 view.update(cx, |view, cx| {
6273 view.change_selections(None, cx, |s| {
6274 s.select_display_ranges([
6275 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6276 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6277 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6278 ])
6279 });
6280
6281 view.handle_input("{", cx);
6282 view.handle_input("{", cx);
6283 view.handle_input("{", cx);
6284 assert_eq!(
6285 view.text(cx),
6286 "
6287 {{{a}}}
6288 {{{b}}}
6289 {{{c}}}
6290 "
6291 .unindent()
6292 );
6293 assert_eq!(
6294 view.selections.display_ranges(cx),
6295 [
6296 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6297 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6298 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6299 ]
6300 );
6301
6302 view.undo(&Undo, cx);
6303 view.undo(&Undo, cx);
6304 view.undo(&Undo, cx);
6305 assert_eq!(
6306 view.text(cx),
6307 "
6308 a
6309 b
6310 c
6311 "
6312 .unindent()
6313 );
6314 assert_eq!(
6315 view.selections.display_ranges(cx),
6316 [
6317 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6318 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6319 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6320 ]
6321 );
6322
6323 // Ensure inserting the first character of a multi-byte bracket pair
6324 // doesn't surround the selections with the bracket.
6325 view.handle_input("/", cx);
6326 assert_eq!(
6327 view.text(cx),
6328 "
6329 /
6330 /
6331 /
6332 "
6333 .unindent()
6334 );
6335 assert_eq!(
6336 view.selections.display_ranges(cx),
6337 [
6338 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6339 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6340 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6341 ]
6342 );
6343
6344 view.undo(&Undo, cx);
6345 assert_eq!(
6346 view.text(cx),
6347 "
6348 a
6349 b
6350 c
6351 "
6352 .unindent()
6353 );
6354 assert_eq!(
6355 view.selections.display_ranges(cx),
6356 [
6357 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6358 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6359 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6360 ]
6361 );
6362
6363 // Ensure inserting the last character of a multi-byte bracket pair
6364 // doesn't surround the selections with the bracket.
6365 view.handle_input("*", cx);
6366 assert_eq!(
6367 view.text(cx),
6368 "
6369 *
6370 *
6371 *
6372 "
6373 .unindent()
6374 );
6375 assert_eq!(
6376 view.selections.display_ranges(cx),
6377 [
6378 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6379 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6380 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6381 ]
6382 );
6383 });
6384}
6385
6386#[gpui::test]
6387async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6388 init_test(cx, |_| {});
6389
6390 let language = Arc::new(Language::new(
6391 LanguageConfig {
6392 brackets: BracketPairConfig {
6393 pairs: vec![BracketPair {
6394 start: "{".to_string(),
6395 end: "}".to_string(),
6396 close: true,
6397 surround: true,
6398 newline: true,
6399 }],
6400 ..Default::default()
6401 },
6402 autoclose_before: "}".to_string(),
6403 ..Default::default()
6404 },
6405 Some(tree_sitter_rust::LANGUAGE.into()),
6406 ));
6407
6408 let text = r#"
6409 a
6410 b
6411 c
6412 "#
6413 .unindent();
6414
6415 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6416 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6417 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6418 editor
6419 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6420 .await;
6421
6422 editor.update(cx, |editor, cx| {
6423 editor.change_selections(None, cx, |s| {
6424 s.select_ranges([
6425 Point::new(0, 1)..Point::new(0, 1),
6426 Point::new(1, 1)..Point::new(1, 1),
6427 Point::new(2, 1)..Point::new(2, 1),
6428 ])
6429 });
6430
6431 editor.handle_input("{", cx);
6432 editor.handle_input("{", cx);
6433 editor.handle_input("_", cx);
6434 assert_eq!(
6435 editor.text(cx),
6436 "
6437 a{{_}}
6438 b{{_}}
6439 c{{_}}
6440 "
6441 .unindent()
6442 );
6443 assert_eq!(
6444 editor.selections.ranges::<Point>(cx),
6445 [
6446 Point::new(0, 4)..Point::new(0, 4),
6447 Point::new(1, 4)..Point::new(1, 4),
6448 Point::new(2, 4)..Point::new(2, 4)
6449 ]
6450 );
6451
6452 editor.backspace(&Default::default(), cx);
6453 editor.backspace(&Default::default(), cx);
6454 assert_eq!(
6455 editor.text(cx),
6456 "
6457 a{}
6458 b{}
6459 c{}
6460 "
6461 .unindent()
6462 );
6463 assert_eq!(
6464 editor.selections.ranges::<Point>(cx),
6465 [
6466 Point::new(0, 2)..Point::new(0, 2),
6467 Point::new(1, 2)..Point::new(1, 2),
6468 Point::new(2, 2)..Point::new(2, 2)
6469 ]
6470 );
6471
6472 editor.delete_to_previous_word_start(&Default::default(), cx);
6473 assert_eq!(
6474 editor.text(cx),
6475 "
6476 a
6477 b
6478 c
6479 "
6480 .unindent()
6481 );
6482 assert_eq!(
6483 editor.selections.ranges::<Point>(cx),
6484 [
6485 Point::new(0, 1)..Point::new(0, 1),
6486 Point::new(1, 1)..Point::new(1, 1),
6487 Point::new(2, 1)..Point::new(2, 1)
6488 ]
6489 );
6490 });
6491}
6492
6493#[gpui::test]
6494async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6495 init_test(cx, |settings| {
6496 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6497 });
6498
6499 let mut cx = EditorTestContext::new(cx).await;
6500
6501 let language = Arc::new(Language::new(
6502 LanguageConfig {
6503 brackets: BracketPairConfig {
6504 pairs: vec![
6505 BracketPair {
6506 start: "{".to_string(),
6507 end: "}".to_string(),
6508 close: true,
6509 surround: true,
6510 newline: true,
6511 },
6512 BracketPair {
6513 start: "(".to_string(),
6514 end: ")".to_string(),
6515 close: true,
6516 surround: true,
6517 newline: true,
6518 },
6519 BracketPair {
6520 start: "[".to_string(),
6521 end: "]".to_string(),
6522 close: false,
6523 surround: true,
6524 newline: true,
6525 },
6526 ],
6527 ..Default::default()
6528 },
6529 autoclose_before: "})]".to_string(),
6530 ..Default::default()
6531 },
6532 Some(tree_sitter_rust::LANGUAGE.into()),
6533 ));
6534
6535 cx.language_registry().add(language.clone());
6536 cx.update_buffer(|buffer, cx| {
6537 buffer.set_language(Some(language), cx);
6538 });
6539
6540 cx.set_state(
6541 &"
6542 {(ˇ)}
6543 [[ˇ]]
6544 {(ˇ)}
6545 "
6546 .unindent(),
6547 );
6548
6549 cx.update_editor(|view, cx| {
6550 view.backspace(&Default::default(), cx);
6551 view.backspace(&Default::default(), cx);
6552 });
6553
6554 cx.assert_editor_state(
6555 &"
6556 ˇ
6557 ˇ]]
6558 ˇ
6559 "
6560 .unindent(),
6561 );
6562
6563 cx.update_editor(|view, cx| {
6564 view.handle_input("{", cx);
6565 view.handle_input("{", cx);
6566 view.move_right(&MoveRight, cx);
6567 view.move_right(&MoveRight, cx);
6568 view.move_left(&MoveLeft, cx);
6569 view.move_left(&MoveLeft, cx);
6570 view.backspace(&Default::default(), cx);
6571 });
6572
6573 cx.assert_editor_state(
6574 &"
6575 {ˇ}
6576 {ˇ}]]
6577 {ˇ}
6578 "
6579 .unindent(),
6580 );
6581
6582 cx.update_editor(|view, cx| {
6583 view.backspace(&Default::default(), cx);
6584 });
6585
6586 cx.assert_editor_state(
6587 &"
6588 ˇ
6589 ˇ]]
6590 ˇ
6591 "
6592 .unindent(),
6593 );
6594}
6595
6596#[gpui::test]
6597async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6598 init_test(cx, |_| {});
6599
6600 let language = Arc::new(Language::new(
6601 LanguageConfig::default(),
6602 Some(tree_sitter_rust::LANGUAGE.into()),
6603 ));
6604
6605 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6606 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6607 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6608 editor
6609 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6610 .await;
6611
6612 editor.update(cx, |editor, cx| {
6613 editor.set_auto_replace_emoji_shortcode(true);
6614
6615 editor.handle_input("Hello ", cx);
6616 editor.handle_input(":wave", cx);
6617 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6618
6619 editor.handle_input(":", cx);
6620 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6621
6622 editor.handle_input(" :smile", cx);
6623 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6624
6625 editor.handle_input(":", cx);
6626 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6627
6628 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6629 editor.handle_input(":wave", cx);
6630 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6631
6632 editor.handle_input(":", cx);
6633 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6634
6635 editor.handle_input(":1", cx);
6636 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6637
6638 editor.handle_input(":", cx);
6639 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6640
6641 // Ensure shortcode does not get replaced when it is part of a word
6642 editor.handle_input(" Test:wave", cx);
6643 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6644
6645 editor.handle_input(":", cx);
6646 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6647
6648 editor.set_auto_replace_emoji_shortcode(false);
6649
6650 // Ensure shortcode does not get replaced when auto replace is off
6651 editor.handle_input(" :wave", cx);
6652 assert_eq!(
6653 editor.text(cx),
6654 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6655 );
6656
6657 editor.handle_input(":", cx);
6658 assert_eq!(
6659 editor.text(cx),
6660 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6661 );
6662 });
6663}
6664
6665#[gpui::test]
6666async fn test_snippet_placeholder_choices(cx: &mut gpui::TestAppContext) {
6667 init_test(cx, |_| {});
6668
6669 let (text, insertion_ranges) = marked_text_ranges(
6670 indoc! {"
6671 ˇ
6672 "},
6673 false,
6674 );
6675
6676 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6677 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6678
6679 _ = editor.update(cx, |editor, cx| {
6680 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
6681
6682 editor
6683 .insert_snippet(&insertion_ranges, snippet, cx)
6684 .unwrap();
6685
6686 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6687 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6688 assert_eq!(editor.text(cx), expected_text);
6689 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6690 }
6691
6692 assert(
6693 editor,
6694 cx,
6695 indoc! {"
6696 type «» =•
6697 "},
6698 );
6699
6700 assert!(editor.context_menu_visible(), "There should be a matches");
6701 });
6702}
6703
6704#[gpui::test]
6705async fn test_snippets(cx: &mut gpui::TestAppContext) {
6706 init_test(cx, |_| {});
6707
6708 let (text, insertion_ranges) = marked_text_ranges(
6709 indoc! {"
6710 a.ˇ b
6711 a.ˇ b
6712 a.ˇ b
6713 "},
6714 false,
6715 );
6716
6717 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6718 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6719
6720 editor.update(cx, |editor, cx| {
6721 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6722
6723 editor
6724 .insert_snippet(&insertion_ranges, snippet, cx)
6725 .unwrap();
6726
6727 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6728 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6729 assert_eq!(editor.text(cx), expected_text);
6730 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6731 }
6732
6733 assert(
6734 editor,
6735 cx,
6736 indoc! {"
6737 a.f(«one», two, «three») b
6738 a.f(«one», two, «three») b
6739 a.f(«one», two, «three») b
6740 "},
6741 );
6742
6743 // Can't move earlier than the first tab stop
6744 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6745 assert(
6746 editor,
6747 cx,
6748 indoc! {"
6749 a.f(«one», two, «three») b
6750 a.f(«one», two, «three») b
6751 a.f(«one», two, «three») b
6752 "},
6753 );
6754
6755 assert!(editor.move_to_next_snippet_tabstop(cx));
6756 assert(
6757 editor,
6758 cx,
6759 indoc! {"
6760 a.f(one, «two», three) b
6761 a.f(one, «two», three) b
6762 a.f(one, «two», three) b
6763 "},
6764 );
6765
6766 editor.move_to_prev_snippet_tabstop(cx);
6767 assert(
6768 editor,
6769 cx,
6770 indoc! {"
6771 a.f(«one», two, «three») b
6772 a.f(«one», two, «three») b
6773 a.f(«one», two, «three») b
6774 "},
6775 );
6776
6777 assert!(editor.move_to_next_snippet_tabstop(cx));
6778 assert(
6779 editor,
6780 cx,
6781 indoc! {"
6782 a.f(one, «two», three) b
6783 a.f(one, «two», three) b
6784 a.f(one, «two», three) b
6785 "},
6786 );
6787 assert!(editor.move_to_next_snippet_tabstop(cx));
6788 assert(
6789 editor,
6790 cx,
6791 indoc! {"
6792 a.f(one, two, three)ˇ b
6793 a.f(one, two, three)ˇ b
6794 a.f(one, two, three)ˇ b
6795 "},
6796 );
6797
6798 // As soon as the last tab stop is reached, snippet state is gone
6799 editor.move_to_prev_snippet_tabstop(cx);
6800 assert(
6801 editor,
6802 cx,
6803 indoc! {"
6804 a.f(one, two, three)ˇ b
6805 a.f(one, two, three)ˇ b
6806 a.f(one, two, three)ˇ b
6807 "},
6808 );
6809 });
6810}
6811
6812#[gpui::test]
6813async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6814 init_test(cx, |_| {});
6815
6816 let fs = FakeFs::new(cx.executor());
6817 fs.insert_file("/file.rs", Default::default()).await;
6818
6819 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6820
6821 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6822 language_registry.add(rust_lang());
6823 let mut fake_servers = language_registry.register_fake_lsp(
6824 "Rust",
6825 FakeLspAdapter {
6826 capabilities: lsp::ServerCapabilities {
6827 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6828 ..Default::default()
6829 },
6830 ..Default::default()
6831 },
6832 );
6833
6834 let buffer = project
6835 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6836 .await
6837 .unwrap();
6838
6839 cx.executor().start_waiting();
6840 let fake_server = fake_servers.next().await.unwrap();
6841
6842 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6843 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6844 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6845 assert!(cx.read(|cx| editor.is_dirty(cx)));
6846
6847 let save = editor
6848 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6849 .unwrap();
6850 fake_server
6851 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6852 assert_eq!(
6853 params.text_document.uri,
6854 lsp::Url::from_file_path("/file.rs").unwrap()
6855 );
6856 assert_eq!(params.options.tab_size, 4);
6857 Ok(Some(vec![lsp::TextEdit::new(
6858 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6859 ", ".to_string(),
6860 )]))
6861 })
6862 .next()
6863 .await;
6864 cx.executor().start_waiting();
6865 save.await;
6866
6867 assert_eq!(
6868 editor.update(cx, |editor, cx| editor.text(cx)),
6869 "one, two\nthree\n"
6870 );
6871 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6872
6873 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6874 assert!(cx.read(|cx| editor.is_dirty(cx)));
6875
6876 // Ensure we can still save even if formatting hangs.
6877 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6878 assert_eq!(
6879 params.text_document.uri,
6880 lsp::Url::from_file_path("/file.rs").unwrap()
6881 );
6882 futures::future::pending::<()>().await;
6883 unreachable!()
6884 });
6885 let save = editor
6886 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6887 .unwrap();
6888 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6889 cx.executor().start_waiting();
6890 save.await;
6891 assert_eq!(
6892 editor.update(cx, |editor, cx| editor.text(cx)),
6893 "one\ntwo\nthree\n"
6894 );
6895 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6896
6897 // For non-dirty buffer, no formatting request should be sent
6898 let save = editor
6899 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6900 .unwrap();
6901 let _pending_format_request = fake_server
6902 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6903 panic!("Should not be invoked on non-dirty buffer");
6904 })
6905 .next();
6906 cx.executor().start_waiting();
6907 save.await;
6908
6909 // Set rust language override and assert overridden tabsize is sent to language server
6910 update_test_language_settings(cx, |settings| {
6911 settings.languages.insert(
6912 "Rust".into(),
6913 LanguageSettingsContent {
6914 tab_size: NonZeroU32::new(8),
6915 ..Default::default()
6916 },
6917 );
6918 });
6919
6920 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6921 assert!(cx.read(|cx| editor.is_dirty(cx)));
6922 let save = editor
6923 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6924 .unwrap();
6925 fake_server
6926 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6927 assert_eq!(
6928 params.text_document.uri,
6929 lsp::Url::from_file_path("/file.rs").unwrap()
6930 );
6931 assert_eq!(params.options.tab_size, 8);
6932 Ok(Some(vec![]))
6933 })
6934 .next()
6935 .await;
6936 cx.executor().start_waiting();
6937 save.await;
6938}
6939
6940#[gpui::test]
6941async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6942 init_test(cx, |_| {});
6943
6944 let cols = 4;
6945 let rows = 10;
6946 let sample_text_1 = sample_text(rows, cols, 'a');
6947 assert_eq!(
6948 sample_text_1,
6949 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6950 );
6951 let sample_text_2 = sample_text(rows, cols, 'l');
6952 assert_eq!(
6953 sample_text_2,
6954 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6955 );
6956 let sample_text_3 = sample_text(rows, cols, 'v');
6957 assert_eq!(
6958 sample_text_3,
6959 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6960 );
6961
6962 let fs = FakeFs::new(cx.executor());
6963 fs.insert_tree(
6964 "/a",
6965 json!({
6966 "main.rs": sample_text_1,
6967 "other.rs": sample_text_2,
6968 "lib.rs": sample_text_3,
6969 }),
6970 )
6971 .await;
6972
6973 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6974 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6975 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6976
6977 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6978 language_registry.add(rust_lang());
6979 let mut fake_servers = language_registry.register_fake_lsp(
6980 "Rust",
6981 FakeLspAdapter {
6982 capabilities: lsp::ServerCapabilities {
6983 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6984 ..Default::default()
6985 },
6986 ..Default::default()
6987 },
6988 );
6989
6990 let worktree = project.update(cx, |project, cx| {
6991 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
6992 assert_eq!(worktrees.len(), 1);
6993 worktrees.pop().unwrap()
6994 });
6995 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6996
6997 let buffer_1 = project
6998 .update(cx, |project, cx| {
6999 project.open_buffer((worktree_id, "main.rs"), cx)
7000 })
7001 .await
7002 .unwrap();
7003 let buffer_2 = project
7004 .update(cx, |project, cx| {
7005 project.open_buffer((worktree_id, "other.rs"), cx)
7006 })
7007 .await
7008 .unwrap();
7009 let buffer_3 = project
7010 .update(cx, |project, cx| {
7011 project.open_buffer((worktree_id, "lib.rs"), cx)
7012 })
7013 .await
7014 .unwrap();
7015
7016 let multi_buffer = cx.new_model(|cx| {
7017 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7018 multi_buffer.push_excerpts(
7019 buffer_1.clone(),
7020 [
7021 ExcerptRange {
7022 context: Point::new(0, 0)..Point::new(3, 0),
7023 primary: None,
7024 },
7025 ExcerptRange {
7026 context: Point::new(5, 0)..Point::new(7, 0),
7027 primary: None,
7028 },
7029 ExcerptRange {
7030 context: Point::new(9, 0)..Point::new(10, 4),
7031 primary: None,
7032 },
7033 ],
7034 cx,
7035 );
7036 multi_buffer.push_excerpts(
7037 buffer_2.clone(),
7038 [
7039 ExcerptRange {
7040 context: Point::new(0, 0)..Point::new(3, 0),
7041 primary: None,
7042 },
7043 ExcerptRange {
7044 context: Point::new(5, 0)..Point::new(7, 0),
7045 primary: None,
7046 },
7047 ExcerptRange {
7048 context: Point::new(9, 0)..Point::new(10, 4),
7049 primary: None,
7050 },
7051 ],
7052 cx,
7053 );
7054 multi_buffer.push_excerpts(
7055 buffer_3.clone(),
7056 [
7057 ExcerptRange {
7058 context: Point::new(0, 0)..Point::new(3, 0),
7059 primary: None,
7060 },
7061 ExcerptRange {
7062 context: Point::new(5, 0)..Point::new(7, 0),
7063 primary: None,
7064 },
7065 ExcerptRange {
7066 context: Point::new(9, 0)..Point::new(10, 4),
7067 primary: None,
7068 },
7069 ],
7070 cx,
7071 );
7072 multi_buffer
7073 });
7074 let multi_buffer_editor = cx.new_view(|cx| {
7075 Editor::new(
7076 EditorMode::Full,
7077 multi_buffer,
7078 Some(project.clone()),
7079 true,
7080 cx,
7081 )
7082 });
7083
7084 multi_buffer_editor.update(cx, |editor, cx| {
7085 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
7086 editor.insert("|one|two|three|", cx);
7087 });
7088 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7089 multi_buffer_editor.update(cx, |editor, cx| {
7090 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
7091 s.select_ranges(Some(60..70))
7092 });
7093 editor.insert("|four|five|six|", cx);
7094 });
7095 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7096
7097 // First two buffers should be edited, but not the third one.
7098 assert_eq!(
7099 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7100 "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}",
7101 );
7102 buffer_1.update(cx, |buffer, _| {
7103 assert!(buffer.is_dirty());
7104 assert_eq!(
7105 buffer.text(),
7106 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7107 )
7108 });
7109 buffer_2.update(cx, |buffer, _| {
7110 assert!(buffer.is_dirty());
7111 assert_eq!(
7112 buffer.text(),
7113 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7114 )
7115 });
7116 buffer_3.update(cx, |buffer, _| {
7117 assert!(!buffer.is_dirty());
7118 assert_eq!(buffer.text(), sample_text_3,)
7119 });
7120
7121 cx.executor().start_waiting();
7122 let save = multi_buffer_editor
7123 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7124 .unwrap();
7125
7126 let fake_server = fake_servers.next().await.unwrap();
7127 fake_server
7128 .server
7129 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7130 Ok(Some(vec![lsp::TextEdit::new(
7131 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7132 format!("[{} formatted]", params.text_document.uri),
7133 )]))
7134 })
7135 .detach();
7136 save.await;
7137
7138 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7139 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7140 assert_eq!(
7141 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7142 "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}",
7143 );
7144 buffer_1.update(cx, |buffer, _| {
7145 assert!(!buffer.is_dirty());
7146 assert_eq!(
7147 buffer.text(),
7148 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
7149 )
7150 });
7151 buffer_2.update(cx, |buffer, _| {
7152 assert!(!buffer.is_dirty());
7153 assert_eq!(
7154 buffer.text(),
7155 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
7156 )
7157 });
7158 buffer_3.update(cx, |buffer, _| {
7159 assert!(!buffer.is_dirty());
7160 assert_eq!(buffer.text(), sample_text_3,)
7161 });
7162}
7163
7164#[gpui::test]
7165async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
7166 init_test(cx, |_| {});
7167
7168 let fs = FakeFs::new(cx.executor());
7169 fs.insert_file("/file.rs", Default::default()).await;
7170
7171 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7172
7173 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7174 language_registry.add(rust_lang());
7175 let mut fake_servers = language_registry.register_fake_lsp(
7176 "Rust",
7177 FakeLspAdapter {
7178 capabilities: lsp::ServerCapabilities {
7179 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7180 ..Default::default()
7181 },
7182 ..Default::default()
7183 },
7184 );
7185
7186 let buffer = project
7187 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7188 .await
7189 .unwrap();
7190
7191 cx.executor().start_waiting();
7192 let fake_server = fake_servers.next().await.unwrap();
7193
7194 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7195 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7196 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7197 assert!(cx.read(|cx| editor.is_dirty(cx)));
7198
7199 let save = editor
7200 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7201 .unwrap();
7202 fake_server
7203 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7204 assert_eq!(
7205 params.text_document.uri,
7206 lsp::Url::from_file_path("/file.rs").unwrap()
7207 );
7208 assert_eq!(params.options.tab_size, 4);
7209 Ok(Some(vec![lsp::TextEdit::new(
7210 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7211 ", ".to_string(),
7212 )]))
7213 })
7214 .next()
7215 .await;
7216 cx.executor().start_waiting();
7217 save.await;
7218 assert_eq!(
7219 editor.update(cx, |editor, cx| editor.text(cx)),
7220 "one, two\nthree\n"
7221 );
7222 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7223
7224 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7225 assert!(cx.read(|cx| editor.is_dirty(cx)));
7226
7227 // Ensure we can still save even if formatting hangs.
7228 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7229 move |params, _| async move {
7230 assert_eq!(
7231 params.text_document.uri,
7232 lsp::Url::from_file_path("/file.rs").unwrap()
7233 );
7234 futures::future::pending::<()>().await;
7235 unreachable!()
7236 },
7237 );
7238 let save = editor
7239 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7240 .unwrap();
7241 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7242 cx.executor().start_waiting();
7243 save.await;
7244 assert_eq!(
7245 editor.update(cx, |editor, cx| editor.text(cx)),
7246 "one\ntwo\nthree\n"
7247 );
7248 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7249
7250 // For non-dirty buffer, no formatting request should be sent
7251 let save = editor
7252 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7253 .unwrap();
7254 let _pending_format_request = fake_server
7255 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7256 panic!("Should not be invoked on non-dirty buffer");
7257 })
7258 .next();
7259 cx.executor().start_waiting();
7260 save.await;
7261
7262 // Set Rust language override and assert overridden tabsize is sent to language server
7263 update_test_language_settings(cx, |settings| {
7264 settings.languages.insert(
7265 "Rust".into(),
7266 LanguageSettingsContent {
7267 tab_size: NonZeroU32::new(8),
7268 ..Default::default()
7269 },
7270 );
7271 });
7272
7273 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
7274 assert!(cx.read(|cx| editor.is_dirty(cx)));
7275 let save = editor
7276 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7277 .unwrap();
7278 fake_server
7279 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7280 assert_eq!(
7281 params.text_document.uri,
7282 lsp::Url::from_file_path("/file.rs").unwrap()
7283 );
7284 assert_eq!(params.options.tab_size, 8);
7285 Ok(Some(vec![]))
7286 })
7287 .next()
7288 .await;
7289 cx.executor().start_waiting();
7290 save.await;
7291}
7292
7293#[gpui::test]
7294async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7295 init_test(cx, |settings| {
7296 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7297 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7298 ))
7299 });
7300
7301 let fs = FakeFs::new(cx.executor());
7302 fs.insert_file("/file.rs", Default::default()).await;
7303
7304 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7305
7306 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7307 language_registry.add(Arc::new(Language::new(
7308 LanguageConfig {
7309 name: "Rust".into(),
7310 matcher: LanguageMatcher {
7311 path_suffixes: vec!["rs".to_string()],
7312 ..Default::default()
7313 },
7314 ..LanguageConfig::default()
7315 },
7316 Some(tree_sitter_rust::LANGUAGE.into()),
7317 )));
7318 update_test_language_settings(cx, |settings| {
7319 // Enable Prettier formatting for the same buffer, and ensure
7320 // LSP is called instead of Prettier.
7321 settings.defaults.prettier = Some(PrettierSettings {
7322 allowed: true,
7323 ..PrettierSettings::default()
7324 });
7325 });
7326 let mut fake_servers = language_registry.register_fake_lsp(
7327 "Rust",
7328 FakeLspAdapter {
7329 capabilities: lsp::ServerCapabilities {
7330 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7331 ..Default::default()
7332 },
7333 ..Default::default()
7334 },
7335 );
7336
7337 let buffer = project
7338 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7339 .await
7340 .unwrap();
7341
7342 cx.executor().start_waiting();
7343 let fake_server = fake_servers.next().await.unwrap();
7344
7345 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7346 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7347 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7348
7349 let format = editor
7350 .update(cx, |editor, cx| {
7351 editor.perform_format(
7352 project.clone(),
7353 FormatTrigger::Manual,
7354 FormatTarget::Buffer,
7355 cx,
7356 )
7357 })
7358 .unwrap();
7359 fake_server
7360 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7361 assert_eq!(
7362 params.text_document.uri,
7363 lsp::Url::from_file_path("/file.rs").unwrap()
7364 );
7365 assert_eq!(params.options.tab_size, 4);
7366 Ok(Some(vec![lsp::TextEdit::new(
7367 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7368 ", ".to_string(),
7369 )]))
7370 })
7371 .next()
7372 .await;
7373 cx.executor().start_waiting();
7374 format.await;
7375 assert_eq!(
7376 editor.update(cx, |editor, cx| editor.text(cx)),
7377 "one, two\nthree\n"
7378 );
7379
7380 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7381 // Ensure we don't lock if formatting hangs.
7382 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7383 assert_eq!(
7384 params.text_document.uri,
7385 lsp::Url::from_file_path("/file.rs").unwrap()
7386 );
7387 futures::future::pending::<()>().await;
7388 unreachable!()
7389 });
7390 let format = editor
7391 .update(cx, |editor, cx| {
7392 editor.perform_format(project, FormatTrigger::Manual, FormatTarget::Buffer, cx)
7393 })
7394 .unwrap();
7395 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7396 cx.executor().start_waiting();
7397 format.await;
7398 assert_eq!(
7399 editor.update(cx, |editor, cx| editor.text(cx)),
7400 "one\ntwo\nthree\n"
7401 );
7402}
7403
7404#[gpui::test]
7405async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7406 init_test(cx, |_| {});
7407
7408 let mut cx = EditorLspTestContext::new_rust(
7409 lsp::ServerCapabilities {
7410 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7411 ..Default::default()
7412 },
7413 cx,
7414 )
7415 .await;
7416
7417 cx.set_state(indoc! {"
7418 one.twoˇ
7419 "});
7420
7421 // The format request takes a long time. When it completes, it inserts
7422 // a newline and an indent before the `.`
7423 cx.lsp
7424 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7425 let executor = cx.background_executor().clone();
7426 async move {
7427 executor.timer(Duration::from_millis(100)).await;
7428 Ok(Some(vec![lsp::TextEdit {
7429 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7430 new_text: "\n ".into(),
7431 }]))
7432 }
7433 });
7434
7435 // Submit a format request.
7436 let format_1 = cx
7437 .update_editor(|editor, cx| editor.format(&Format, cx))
7438 .unwrap();
7439 cx.executor().run_until_parked();
7440
7441 // Submit a second format request.
7442 let format_2 = cx
7443 .update_editor(|editor, cx| editor.format(&Format, cx))
7444 .unwrap();
7445 cx.executor().run_until_parked();
7446
7447 // Wait for both format requests to complete
7448 cx.executor().advance_clock(Duration::from_millis(200));
7449 cx.executor().start_waiting();
7450 format_1.await.unwrap();
7451 cx.executor().start_waiting();
7452 format_2.await.unwrap();
7453
7454 // The formatting edits only happens once.
7455 cx.assert_editor_state(indoc! {"
7456 one
7457 .twoˇ
7458 "});
7459}
7460
7461#[gpui::test]
7462async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7463 init_test(cx, |settings| {
7464 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7465 });
7466
7467 let mut cx = EditorLspTestContext::new_rust(
7468 lsp::ServerCapabilities {
7469 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7470 ..Default::default()
7471 },
7472 cx,
7473 )
7474 .await;
7475
7476 // Set up a buffer white some trailing whitespace and no trailing newline.
7477 cx.set_state(
7478 &[
7479 "one ", //
7480 "twoˇ", //
7481 "three ", //
7482 "four", //
7483 ]
7484 .join("\n"),
7485 );
7486
7487 // Submit a format request.
7488 let format = cx
7489 .update_editor(|editor, cx| editor.format(&Format, cx))
7490 .unwrap();
7491
7492 // Record which buffer changes have been sent to the language server
7493 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7494 cx.lsp
7495 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7496 let buffer_changes = buffer_changes.clone();
7497 move |params, _| {
7498 buffer_changes.lock().extend(
7499 params
7500 .content_changes
7501 .into_iter()
7502 .map(|e| (e.range.unwrap(), e.text)),
7503 );
7504 }
7505 });
7506
7507 // Handle formatting requests to the language server.
7508 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7509 let buffer_changes = buffer_changes.clone();
7510 move |_, _| {
7511 // When formatting is requested, trailing whitespace has already been stripped,
7512 // and the trailing newline has already been added.
7513 assert_eq!(
7514 &buffer_changes.lock()[1..],
7515 &[
7516 (
7517 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7518 "".into()
7519 ),
7520 (
7521 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7522 "".into()
7523 ),
7524 (
7525 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7526 "\n".into()
7527 ),
7528 ]
7529 );
7530
7531 // Insert blank lines between each line of the buffer.
7532 async move {
7533 Ok(Some(vec![
7534 lsp::TextEdit {
7535 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7536 new_text: "\n".into(),
7537 },
7538 lsp::TextEdit {
7539 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7540 new_text: "\n".into(),
7541 },
7542 ]))
7543 }
7544 }
7545 });
7546
7547 // After formatting the buffer, the trailing whitespace is stripped,
7548 // a newline is appended, and the edits provided by the language server
7549 // have been applied.
7550 format.await.unwrap();
7551 cx.assert_editor_state(
7552 &[
7553 "one", //
7554 "", //
7555 "twoˇ", //
7556 "", //
7557 "three", //
7558 "four", //
7559 "", //
7560 ]
7561 .join("\n"),
7562 );
7563
7564 // Undoing the formatting undoes the trailing whitespace removal, the
7565 // trailing newline, and the LSP edits.
7566 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7567 cx.assert_editor_state(
7568 &[
7569 "one ", //
7570 "twoˇ", //
7571 "three ", //
7572 "four", //
7573 ]
7574 .join("\n"),
7575 );
7576}
7577
7578#[gpui::test]
7579async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7580 cx: &mut gpui::TestAppContext,
7581) {
7582 init_test(cx, |_| {});
7583
7584 cx.update(|cx| {
7585 cx.update_global::<SettingsStore, _>(|settings, cx| {
7586 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7587 settings.auto_signature_help = Some(true);
7588 });
7589 });
7590 });
7591
7592 let mut cx = EditorLspTestContext::new_rust(
7593 lsp::ServerCapabilities {
7594 signature_help_provider: Some(lsp::SignatureHelpOptions {
7595 ..Default::default()
7596 }),
7597 ..Default::default()
7598 },
7599 cx,
7600 )
7601 .await;
7602
7603 let language = Language::new(
7604 LanguageConfig {
7605 name: "Rust".into(),
7606 brackets: BracketPairConfig {
7607 pairs: vec![
7608 BracketPair {
7609 start: "{".to_string(),
7610 end: "}".to_string(),
7611 close: true,
7612 surround: true,
7613 newline: true,
7614 },
7615 BracketPair {
7616 start: "(".to_string(),
7617 end: ")".to_string(),
7618 close: true,
7619 surround: true,
7620 newline: true,
7621 },
7622 BracketPair {
7623 start: "/*".to_string(),
7624 end: " */".to_string(),
7625 close: true,
7626 surround: true,
7627 newline: true,
7628 },
7629 BracketPair {
7630 start: "[".to_string(),
7631 end: "]".to_string(),
7632 close: false,
7633 surround: false,
7634 newline: true,
7635 },
7636 BracketPair {
7637 start: "\"".to_string(),
7638 end: "\"".to_string(),
7639 close: true,
7640 surround: true,
7641 newline: false,
7642 },
7643 BracketPair {
7644 start: "<".to_string(),
7645 end: ">".to_string(),
7646 close: false,
7647 surround: true,
7648 newline: true,
7649 },
7650 ],
7651 ..Default::default()
7652 },
7653 autoclose_before: "})]".to_string(),
7654 ..Default::default()
7655 },
7656 Some(tree_sitter_rust::LANGUAGE.into()),
7657 );
7658 let language = Arc::new(language);
7659
7660 cx.language_registry().add(language.clone());
7661 cx.update_buffer(|buffer, cx| {
7662 buffer.set_language(Some(language), cx);
7663 });
7664
7665 cx.set_state(
7666 &r#"
7667 fn main() {
7668 sampleˇ
7669 }
7670 "#
7671 .unindent(),
7672 );
7673
7674 cx.update_editor(|view, cx| {
7675 view.handle_input("(", cx);
7676 });
7677 cx.assert_editor_state(
7678 &"
7679 fn main() {
7680 sample(ˇ)
7681 }
7682 "
7683 .unindent(),
7684 );
7685
7686 let mocked_response = lsp::SignatureHelp {
7687 signatures: vec![lsp::SignatureInformation {
7688 label: "fn sample(param1: u8, param2: u8)".to_string(),
7689 documentation: None,
7690 parameters: Some(vec![
7691 lsp::ParameterInformation {
7692 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7693 documentation: None,
7694 },
7695 lsp::ParameterInformation {
7696 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7697 documentation: None,
7698 },
7699 ]),
7700 active_parameter: None,
7701 }],
7702 active_signature: Some(0),
7703 active_parameter: Some(0),
7704 };
7705 handle_signature_help_request(&mut cx, mocked_response).await;
7706
7707 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7708 .await;
7709
7710 cx.editor(|editor, _| {
7711 let signature_help_state = editor.signature_help_state.popover().cloned();
7712 assert!(signature_help_state.is_some());
7713 let ParsedMarkdown {
7714 text, highlights, ..
7715 } = signature_help_state.unwrap().parsed_content;
7716 assert_eq!(text, "param1: u8, param2: u8");
7717 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7718 });
7719}
7720
7721#[gpui::test]
7722async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7723 init_test(cx, |_| {});
7724
7725 cx.update(|cx| {
7726 cx.update_global::<SettingsStore, _>(|settings, cx| {
7727 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7728 settings.auto_signature_help = Some(false);
7729 settings.show_signature_help_after_edits = Some(false);
7730 });
7731 });
7732 });
7733
7734 let mut cx = EditorLspTestContext::new_rust(
7735 lsp::ServerCapabilities {
7736 signature_help_provider: Some(lsp::SignatureHelpOptions {
7737 ..Default::default()
7738 }),
7739 ..Default::default()
7740 },
7741 cx,
7742 )
7743 .await;
7744
7745 let language = Language::new(
7746 LanguageConfig {
7747 name: "Rust".into(),
7748 brackets: BracketPairConfig {
7749 pairs: vec![
7750 BracketPair {
7751 start: "{".to_string(),
7752 end: "}".to_string(),
7753 close: true,
7754 surround: true,
7755 newline: true,
7756 },
7757 BracketPair {
7758 start: "(".to_string(),
7759 end: ")".to_string(),
7760 close: true,
7761 surround: true,
7762 newline: true,
7763 },
7764 BracketPair {
7765 start: "/*".to_string(),
7766 end: " */".to_string(),
7767 close: true,
7768 surround: true,
7769 newline: true,
7770 },
7771 BracketPair {
7772 start: "[".to_string(),
7773 end: "]".to_string(),
7774 close: false,
7775 surround: false,
7776 newline: true,
7777 },
7778 BracketPair {
7779 start: "\"".to_string(),
7780 end: "\"".to_string(),
7781 close: true,
7782 surround: true,
7783 newline: false,
7784 },
7785 BracketPair {
7786 start: "<".to_string(),
7787 end: ">".to_string(),
7788 close: false,
7789 surround: true,
7790 newline: true,
7791 },
7792 ],
7793 ..Default::default()
7794 },
7795 autoclose_before: "})]".to_string(),
7796 ..Default::default()
7797 },
7798 Some(tree_sitter_rust::LANGUAGE.into()),
7799 );
7800 let language = Arc::new(language);
7801
7802 cx.language_registry().add(language.clone());
7803 cx.update_buffer(|buffer, cx| {
7804 buffer.set_language(Some(language), cx);
7805 });
7806
7807 // Ensure that signature_help is not called when no signature help is enabled.
7808 cx.set_state(
7809 &r#"
7810 fn main() {
7811 sampleˇ
7812 }
7813 "#
7814 .unindent(),
7815 );
7816 cx.update_editor(|view, cx| {
7817 view.handle_input("(", cx);
7818 });
7819 cx.assert_editor_state(
7820 &"
7821 fn main() {
7822 sample(ˇ)
7823 }
7824 "
7825 .unindent(),
7826 );
7827 cx.editor(|editor, _| {
7828 assert!(editor.signature_help_state.task().is_none());
7829 });
7830
7831 let mocked_response = lsp::SignatureHelp {
7832 signatures: vec![lsp::SignatureInformation {
7833 label: "fn sample(param1: u8, param2: u8)".to_string(),
7834 documentation: None,
7835 parameters: Some(vec![
7836 lsp::ParameterInformation {
7837 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7838 documentation: None,
7839 },
7840 lsp::ParameterInformation {
7841 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7842 documentation: None,
7843 },
7844 ]),
7845 active_parameter: None,
7846 }],
7847 active_signature: Some(0),
7848 active_parameter: Some(0),
7849 };
7850
7851 // Ensure that signature_help is called when enabled afte edits
7852 cx.update(|cx| {
7853 cx.update_global::<SettingsStore, _>(|settings, cx| {
7854 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7855 settings.auto_signature_help = Some(false);
7856 settings.show_signature_help_after_edits = Some(true);
7857 });
7858 });
7859 });
7860 cx.set_state(
7861 &r#"
7862 fn main() {
7863 sampleˇ
7864 }
7865 "#
7866 .unindent(),
7867 );
7868 cx.update_editor(|view, cx| {
7869 view.handle_input("(", cx);
7870 });
7871 cx.assert_editor_state(
7872 &"
7873 fn main() {
7874 sample(ˇ)
7875 }
7876 "
7877 .unindent(),
7878 );
7879 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7880 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7881 .await;
7882 cx.update_editor(|editor, _| {
7883 let signature_help_state = editor.signature_help_state.popover().cloned();
7884 assert!(signature_help_state.is_some());
7885 let ParsedMarkdown {
7886 text, highlights, ..
7887 } = signature_help_state.unwrap().parsed_content;
7888 assert_eq!(text, "param1: u8, param2: u8");
7889 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7890 editor.signature_help_state = SignatureHelpState::default();
7891 });
7892
7893 // Ensure that signature_help is called when auto signature help override is enabled
7894 cx.update(|cx| {
7895 cx.update_global::<SettingsStore, _>(|settings, cx| {
7896 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7897 settings.auto_signature_help = Some(true);
7898 settings.show_signature_help_after_edits = Some(false);
7899 });
7900 });
7901 });
7902 cx.set_state(
7903 &r#"
7904 fn main() {
7905 sampleˇ
7906 }
7907 "#
7908 .unindent(),
7909 );
7910 cx.update_editor(|view, cx| {
7911 view.handle_input("(", cx);
7912 });
7913 cx.assert_editor_state(
7914 &"
7915 fn main() {
7916 sample(ˇ)
7917 }
7918 "
7919 .unindent(),
7920 );
7921 handle_signature_help_request(&mut cx, mocked_response).await;
7922 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7923 .await;
7924 cx.editor(|editor, _| {
7925 let signature_help_state = editor.signature_help_state.popover().cloned();
7926 assert!(signature_help_state.is_some());
7927 let ParsedMarkdown {
7928 text, highlights, ..
7929 } = signature_help_state.unwrap().parsed_content;
7930 assert_eq!(text, "param1: u8, param2: u8");
7931 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7932 });
7933}
7934
7935#[gpui::test]
7936async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7937 init_test(cx, |_| {});
7938 cx.update(|cx| {
7939 cx.update_global::<SettingsStore, _>(|settings, cx| {
7940 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7941 settings.auto_signature_help = Some(true);
7942 });
7943 });
7944 });
7945
7946 let mut cx = EditorLspTestContext::new_rust(
7947 lsp::ServerCapabilities {
7948 signature_help_provider: Some(lsp::SignatureHelpOptions {
7949 ..Default::default()
7950 }),
7951 ..Default::default()
7952 },
7953 cx,
7954 )
7955 .await;
7956
7957 // A test that directly calls `show_signature_help`
7958 cx.update_editor(|editor, cx| {
7959 editor.show_signature_help(&ShowSignatureHelp, cx);
7960 });
7961
7962 let mocked_response = lsp::SignatureHelp {
7963 signatures: vec![lsp::SignatureInformation {
7964 label: "fn sample(param1: u8, param2: u8)".to_string(),
7965 documentation: None,
7966 parameters: Some(vec![
7967 lsp::ParameterInformation {
7968 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7969 documentation: None,
7970 },
7971 lsp::ParameterInformation {
7972 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7973 documentation: None,
7974 },
7975 ]),
7976 active_parameter: None,
7977 }],
7978 active_signature: Some(0),
7979 active_parameter: Some(0),
7980 };
7981 handle_signature_help_request(&mut cx, mocked_response).await;
7982
7983 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7984 .await;
7985
7986 cx.editor(|editor, _| {
7987 let signature_help_state = editor.signature_help_state.popover().cloned();
7988 assert!(signature_help_state.is_some());
7989 let ParsedMarkdown {
7990 text, highlights, ..
7991 } = signature_help_state.unwrap().parsed_content;
7992 assert_eq!(text, "param1: u8, param2: u8");
7993 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7994 });
7995
7996 // When exiting outside from inside the brackets, `signature_help` is closed.
7997 cx.set_state(indoc! {"
7998 fn main() {
7999 sample(ˇ);
8000 }
8001
8002 fn sample(param1: u8, param2: u8) {}
8003 "});
8004
8005 cx.update_editor(|editor, cx| {
8006 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
8007 });
8008
8009 let mocked_response = lsp::SignatureHelp {
8010 signatures: Vec::new(),
8011 active_signature: None,
8012 active_parameter: None,
8013 };
8014 handle_signature_help_request(&mut cx, mocked_response).await;
8015
8016 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8017 .await;
8018
8019 cx.editor(|editor, _| {
8020 assert!(!editor.signature_help_state.is_shown());
8021 });
8022
8023 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8024 cx.set_state(indoc! {"
8025 fn main() {
8026 sample(ˇ);
8027 }
8028
8029 fn sample(param1: u8, param2: u8) {}
8030 "});
8031
8032 let mocked_response = lsp::SignatureHelp {
8033 signatures: vec![lsp::SignatureInformation {
8034 label: "fn sample(param1: u8, param2: u8)".to_string(),
8035 documentation: None,
8036 parameters: Some(vec![
8037 lsp::ParameterInformation {
8038 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8039 documentation: None,
8040 },
8041 lsp::ParameterInformation {
8042 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8043 documentation: None,
8044 },
8045 ]),
8046 active_parameter: None,
8047 }],
8048 active_signature: Some(0),
8049 active_parameter: Some(0),
8050 };
8051 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8052 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8053 .await;
8054 cx.editor(|editor, _| {
8055 assert!(editor.signature_help_state.is_shown());
8056 });
8057
8058 // Restore the popover with more parameter input
8059 cx.set_state(indoc! {"
8060 fn main() {
8061 sample(param1, param2ˇ);
8062 }
8063
8064 fn sample(param1: u8, param2: u8) {}
8065 "});
8066
8067 let mocked_response = lsp::SignatureHelp {
8068 signatures: vec![lsp::SignatureInformation {
8069 label: "fn sample(param1: u8, param2: u8)".to_string(),
8070 documentation: None,
8071 parameters: Some(vec![
8072 lsp::ParameterInformation {
8073 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8074 documentation: None,
8075 },
8076 lsp::ParameterInformation {
8077 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8078 documentation: None,
8079 },
8080 ]),
8081 active_parameter: None,
8082 }],
8083 active_signature: Some(0),
8084 active_parameter: Some(1),
8085 };
8086 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8087 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8088 .await;
8089
8090 // When selecting a range, the popover is gone.
8091 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8092 cx.update_editor(|editor, cx| {
8093 editor.change_selections(None, cx, |s| {
8094 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8095 })
8096 });
8097 cx.assert_editor_state(indoc! {"
8098 fn main() {
8099 sample(param1, «ˇparam2»);
8100 }
8101
8102 fn sample(param1: u8, param2: u8) {}
8103 "});
8104 cx.editor(|editor, _| {
8105 assert!(!editor.signature_help_state.is_shown());
8106 });
8107
8108 // When unselecting again, the popover is back if within the brackets.
8109 cx.update_editor(|editor, cx| {
8110 editor.change_selections(None, cx, |s| {
8111 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8112 })
8113 });
8114 cx.assert_editor_state(indoc! {"
8115 fn main() {
8116 sample(param1, ˇparam2);
8117 }
8118
8119 fn sample(param1: u8, param2: u8) {}
8120 "});
8121 handle_signature_help_request(&mut cx, mocked_response).await;
8122 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8123 .await;
8124 cx.editor(|editor, _| {
8125 assert!(editor.signature_help_state.is_shown());
8126 });
8127
8128 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8129 cx.update_editor(|editor, cx| {
8130 editor.change_selections(None, cx, |s| {
8131 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8132 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8133 })
8134 });
8135 cx.assert_editor_state(indoc! {"
8136 fn main() {
8137 sample(param1, ˇparam2);
8138 }
8139
8140 fn sample(param1: u8, param2: u8) {}
8141 "});
8142
8143 let mocked_response = lsp::SignatureHelp {
8144 signatures: vec![lsp::SignatureInformation {
8145 label: "fn sample(param1: u8, param2: u8)".to_string(),
8146 documentation: None,
8147 parameters: Some(vec![
8148 lsp::ParameterInformation {
8149 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8150 documentation: None,
8151 },
8152 lsp::ParameterInformation {
8153 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8154 documentation: None,
8155 },
8156 ]),
8157 active_parameter: None,
8158 }],
8159 active_signature: Some(0),
8160 active_parameter: Some(1),
8161 };
8162 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8163 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8164 .await;
8165 cx.update_editor(|editor, cx| {
8166 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8167 });
8168 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8169 .await;
8170 cx.update_editor(|editor, cx| {
8171 editor.change_selections(None, cx, |s| {
8172 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8173 })
8174 });
8175 cx.assert_editor_state(indoc! {"
8176 fn main() {
8177 sample(param1, «ˇparam2»);
8178 }
8179
8180 fn sample(param1: u8, param2: u8) {}
8181 "});
8182 cx.update_editor(|editor, cx| {
8183 editor.change_selections(None, cx, |s| {
8184 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8185 })
8186 });
8187 cx.assert_editor_state(indoc! {"
8188 fn main() {
8189 sample(param1, ˇparam2);
8190 }
8191
8192 fn sample(param1: u8, param2: u8) {}
8193 "});
8194 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8195 .await;
8196}
8197
8198#[gpui::test]
8199async fn test_completion(cx: &mut gpui::TestAppContext) {
8200 init_test(cx, |_| {});
8201
8202 let mut cx = EditorLspTestContext::new_rust(
8203 lsp::ServerCapabilities {
8204 completion_provider: Some(lsp::CompletionOptions {
8205 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8206 resolve_provider: Some(true),
8207 ..Default::default()
8208 }),
8209 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8210 ..Default::default()
8211 },
8212 cx,
8213 )
8214 .await;
8215 let counter = Arc::new(AtomicUsize::new(0));
8216
8217 cx.set_state(indoc! {"
8218 oneˇ
8219 two
8220 three
8221 "});
8222 cx.simulate_keystroke(".");
8223 handle_completion_request(
8224 &mut cx,
8225 indoc! {"
8226 one.|<>
8227 two
8228 three
8229 "},
8230 vec!["first_completion", "second_completion"],
8231 counter.clone(),
8232 )
8233 .await;
8234 cx.condition(|editor, _| editor.context_menu_visible())
8235 .await;
8236 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8237
8238 let _handler = handle_signature_help_request(
8239 &mut cx,
8240 lsp::SignatureHelp {
8241 signatures: vec![lsp::SignatureInformation {
8242 label: "test signature".to_string(),
8243 documentation: None,
8244 parameters: Some(vec![lsp::ParameterInformation {
8245 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8246 documentation: None,
8247 }]),
8248 active_parameter: None,
8249 }],
8250 active_signature: None,
8251 active_parameter: None,
8252 },
8253 );
8254 cx.update_editor(|editor, cx| {
8255 assert!(
8256 !editor.signature_help_state.is_shown(),
8257 "No signature help was called for"
8258 );
8259 editor.show_signature_help(&ShowSignatureHelp, cx);
8260 });
8261 cx.run_until_parked();
8262 cx.update_editor(|editor, _| {
8263 assert!(
8264 !editor.signature_help_state.is_shown(),
8265 "No signature help should be shown when completions menu is open"
8266 );
8267 });
8268
8269 let apply_additional_edits = cx.update_editor(|editor, cx| {
8270 editor.context_menu_next(&Default::default(), cx);
8271 editor
8272 .confirm_completion(&ConfirmCompletion::default(), cx)
8273 .unwrap()
8274 });
8275 cx.assert_editor_state(indoc! {"
8276 one.second_completionˇ
8277 two
8278 three
8279 "});
8280
8281 handle_resolve_completion_request(
8282 &mut cx,
8283 Some(vec![
8284 (
8285 //This overlaps with the primary completion edit which is
8286 //misbehavior from the LSP spec, test that we filter it out
8287 indoc! {"
8288 one.second_ˇcompletion
8289 two
8290 threeˇ
8291 "},
8292 "overlapping additional edit",
8293 ),
8294 (
8295 indoc! {"
8296 one.second_completion
8297 two
8298 threeˇ
8299 "},
8300 "\nadditional edit",
8301 ),
8302 ]),
8303 )
8304 .await;
8305 apply_additional_edits.await.unwrap();
8306 cx.assert_editor_state(indoc! {"
8307 one.second_completionˇ
8308 two
8309 three
8310 additional edit
8311 "});
8312
8313 cx.set_state(indoc! {"
8314 one.second_completion
8315 twoˇ
8316 threeˇ
8317 additional edit
8318 "});
8319 cx.simulate_keystroke(" ");
8320 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8321 cx.simulate_keystroke("s");
8322 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8323
8324 cx.assert_editor_state(indoc! {"
8325 one.second_completion
8326 two sˇ
8327 three sˇ
8328 additional edit
8329 "});
8330 handle_completion_request(
8331 &mut cx,
8332 indoc! {"
8333 one.second_completion
8334 two s
8335 three <s|>
8336 additional edit
8337 "},
8338 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8339 counter.clone(),
8340 )
8341 .await;
8342 cx.condition(|editor, _| editor.context_menu_visible())
8343 .await;
8344 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8345
8346 cx.simulate_keystroke("i");
8347
8348 handle_completion_request(
8349 &mut cx,
8350 indoc! {"
8351 one.second_completion
8352 two si
8353 three <si|>
8354 additional edit
8355 "},
8356 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8357 counter.clone(),
8358 )
8359 .await;
8360 cx.condition(|editor, _| editor.context_menu_visible())
8361 .await;
8362 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8363
8364 let apply_additional_edits = cx.update_editor(|editor, cx| {
8365 editor
8366 .confirm_completion(&ConfirmCompletion::default(), cx)
8367 .unwrap()
8368 });
8369 cx.assert_editor_state(indoc! {"
8370 one.second_completion
8371 two sixth_completionˇ
8372 three sixth_completionˇ
8373 additional edit
8374 "});
8375
8376 handle_resolve_completion_request(&mut cx, None).await;
8377 apply_additional_edits.await.unwrap();
8378
8379 cx.update(|cx| {
8380 cx.update_global::<SettingsStore, _>(|settings, cx| {
8381 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8382 settings.show_completions_on_input = Some(false);
8383 });
8384 })
8385 });
8386 cx.set_state("editorˇ");
8387 cx.simulate_keystroke(".");
8388 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8389 cx.simulate_keystroke("c");
8390 cx.simulate_keystroke("l");
8391 cx.simulate_keystroke("o");
8392 cx.assert_editor_state("editor.cloˇ");
8393 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8394 cx.update_editor(|editor, cx| {
8395 editor.show_completions(&ShowCompletions { trigger: None }, cx);
8396 });
8397 handle_completion_request(
8398 &mut cx,
8399 "editor.<clo|>",
8400 vec!["close", "clobber"],
8401 counter.clone(),
8402 )
8403 .await;
8404 cx.condition(|editor, _| editor.context_menu_visible())
8405 .await;
8406 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8407
8408 let apply_additional_edits = cx.update_editor(|editor, cx| {
8409 editor
8410 .confirm_completion(&ConfirmCompletion::default(), cx)
8411 .unwrap()
8412 });
8413 cx.assert_editor_state("editor.closeˇ");
8414 handle_resolve_completion_request(&mut cx, None).await;
8415 apply_additional_edits.await.unwrap();
8416}
8417
8418#[gpui::test]
8419async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8420 init_test(cx, |_| {});
8421 let mut cx = EditorLspTestContext::new_rust(
8422 lsp::ServerCapabilities {
8423 completion_provider: Some(lsp::CompletionOptions {
8424 trigger_characters: Some(vec![".".to_string()]),
8425 ..Default::default()
8426 }),
8427 ..Default::default()
8428 },
8429 cx,
8430 )
8431 .await;
8432 cx.lsp
8433 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8434 Ok(Some(lsp::CompletionResponse::Array(vec![
8435 lsp::CompletionItem {
8436 label: "first".into(),
8437 ..Default::default()
8438 },
8439 lsp::CompletionItem {
8440 label: "last".into(),
8441 ..Default::default()
8442 },
8443 ])))
8444 });
8445 cx.set_state("variableˇ");
8446 cx.simulate_keystroke(".");
8447 cx.executor().run_until_parked();
8448
8449 cx.update_editor(|editor, _| {
8450 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8451 assert_eq!(
8452 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8453 &["first", "last"]
8454 );
8455 } else {
8456 panic!("expected completion menu to be open");
8457 }
8458 });
8459
8460 cx.update_editor(|editor, cx| {
8461 editor.move_page_down(&MovePageDown::default(), cx);
8462 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8463 assert!(
8464 menu.selected_item == 1,
8465 "expected PageDown to select the last item from the context menu"
8466 );
8467 } else {
8468 panic!("expected completion menu to stay open after PageDown");
8469 }
8470 });
8471
8472 cx.update_editor(|editor, cx| {
8473 editor.move_page_up(&MovePageUp::default(), cx);
8474 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8475 assert!(
8476 menu.selected_item == 0,
8477 "expected PageUp to select the first item from the context menu"
8478 );
8479 } else {
8480 panic!("expected completion menu to stay open after PageUp");
8481 }
8482 });
8483}
8484
8485#[gpui::test]
8486async fn test_completion_sort(cx: &mut gpui::TestAppContext) {
8487 init_test(cx, |_| {});
8488 let mut cx = EditorLspTestContext::new_rust(
8489 lsp::ServerCapabilities {
8490 completion_provider: Some(lsp::CompletionOptions {
8491 trigger_characters: Some(vec![".".to_string()]),
8492 ..Default::default()
8493 }),
8494 ..Default::default()
8495 },
8496 cx,
8497 )
8498 .await;
8499 cx.lsp
8500 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8501 Ok(Some(lsp::CompletionResponse::Array(vec![
8502 lsp::CompletionItem {
8503 label: "Range".into(),
8504 sort_text: Some("a".into()),
8505 ..Default::default()
8506 },
8507 lsp::CompletionItem {
8508 label: "r".into(),
8509 sort_text: Some("b".into()),
8510 ..Default::default()
8511 },
8512 lsp::CompletionItem {
8513 label: "ret".into(),
8514 sort_text: Some("c".into()),
8515 ..Default::default()
8516 },
8517 lsp::CompletionItem {
8518 label: "return".into(),
8519 sort_text: Some("d".into()),
8520 ..Default::default()
8521 },
8522 lsp::CompletionItem {
8523 label: "slice".into(),
8524 sort_text: Some("d".into()),
8525 ..Default::default()
8526 },
8527 ])))
8528 });
8529 cx.set_state("rˇ");
8530 cx.executor().run_until_parked();
8531 cx.update_editor(|editor, cx| {
8532 editor.show_completions(
8533 &ShowCompletions {
8534 trigger: Some("r".into()),
8535 },
8536 cx,
8537 );
8538 });
8539 cx.executor().run_until_parked();
8540
8541 cx.update_editor(|editor, _| {
8542 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8543 assert_eq!(
8544 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8545 &["r", "ret", "Range", "return"]
8546 );
8547 } else {
8548 panic!("expected completion menu to be open");
8549 }
8550 });
8551}
8552
8553#[gpui::test]
8554async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8555 init_test(cx, |_| {});
8556
8557 let mut cx = EditorLspTestContext::new_rust(
8558 lsp::ServerCapabilities {
8559 completion_provider: Some(lsp::CompletionOptions {
8560 trigger_characters: Some(vec![".".to_string()]),
8561 resolve_provider: Some(true),
8562 ..Default::default()
8563 }),
8564 ..Default::default()
8565 },
8566 cx,
8567 )
8568 .await;
8569
8570 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8571 cx.simulate_keystroke(".");
8572 let completion_item = lsp::CompletionItem {
8573 label: "Some".into(),
8574 kind: Some(lsp::CompletionItemKind::SNIPPET),
8575 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8576 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8577 kind: lsp::MarkupKind::Markdown,
8578 value: "```rust\nSome(2)\n```".to_string(),
8579 })),
8580 deprecated: Some(false),
8581 sort_text: Some("Some".to_string()),
8582 filter_text: Some("Some".to_string()),
8583 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8584 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8585 range: lsp::Range {
8586 start: lsp::Position {
8587 line: 0,
8588 character: 22,
8589 },
8590 end: lsp::Position {
8591 line: 0,
8592 character: 22,
8593 },
8594 },
8595 new_text: "Some(2)".to_string(),
8596 })),
8597 additional_text_edits: Some(vec![lsp::TextEdit {
8598 range: lsp::Range {
8599 start: lsp::Position {
8600 line: 0,
8601 character: 20,
8602 },
8603 end: lsp::Position {
8604 line: 0,
8605 character: 22,
8606 },
8607 },
8608 new_text: "".to_string(),
8609 }]),
8610 ..Default::default()
8611 };
8612
8613 let closure_completion_item = completion_item.clone();
8614 let counter = Arc::new(AtomicUsize::new(0));
8615 let counter_clone = counter.clone();
8616 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8617 let task_completion_item = closure_completion_item.clone();
8618 counter_clone.fetch_add(1, atomic::Ordering::Release);
8619 async move {
8620 Ok(Some(lsp::CompletionResponse::Array(vec![
8621 task_completion_item,
8622 ])))
8623 }
8624 });
8625
8626 cx.condition(|editor, _| editor.context_menu_visible())
8627 .await;
8628 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8629 assert!(request.next().await.is_some());
8630 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8631
8632 cx.simulate_keystroke("S");
8633 cx.simulate_keystroke("o");
8634 cx.simulate_keystroke("m");
8635 cx.condition(|editor, _| editor.context_menu_visible())
8636 .await;
8637 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
8638 assert!(request.next().await.is_some());
8639 assert!(request.next().await.is_some());
8640 assert!(request.next().await.is_some());
8641 request.close();
8642 assert!(request.next().await.is_none());
8643 assert_eq!(
8644 counter.load(atomic::Ordering::Acquire),
8645 4,
8646 "With the completions menu open, only one LSP request should happen per input"
8647 );
8648}
8649
8650#[gpui::test]
8651async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
8652 init_test(cx, |_| {});
8653 let mut cx = EditorTestContext::new(cx).await;
8654 let language = Arc::new(Language::new(
8655 LanguageConfig {
8656 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8657 ..Default::default()
8658 },
8659 Some(tree_sitter_rust::LANGUAGE.into()),
8660 ));
8661 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8662
8663 // If multiple selections intersect a line, the line is only toggled once.
8664 cx.set_state(indoc! {"
8665 fn a() {
8666 «//b();
8667 ˇ»// «c();
8668 //ˇ» d();
8669 }
8670 "});
8671
8672 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8673
8674 cx.assert_editor_state(indoc! {"
8675 fn a() {
8676 «b();
8677 c();
8678 ˇ» d();
8679 }
8680 "});
8681
8682 // The comment prefix is inserted at the same column for every line in a
8683 // selection.
8684 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8685
8686 cx.assert_editor_state(indoc! {"
8687 fn a() {
8688 // «b();
8689 // c();
8690 ˇ»// d();
8691 }
8692 "});
8693
8694 // If a selection ends at the beginning of a line, that line is not toggled.
8695 cx.set_selections_state(indoc! {"
8696 fn a() {
8697 // b();
8698 «// c();
8699 ˇ» // d();
8700 }
8701 "});
8702
8703 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8704
8705 cx.assert_editor_state(indoc! {"
8706 fn a() {
8707 // b();
8708 «c();
8709 ˇ» // d();
8710 }
8711 "});
8712
8713 // If a selection span a single line and is empty, the line is toggled.
8714 cx.set_state(indoc! {"
8715 fn a() {
8716 a();
8717 b();
8718 ˇ
8719 }
8720 "});
8721
8722 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8723
8724 cx.assert_editor_state(indoc! {"
8725 fn a() {
8726 a();
8727 b();
8728 //•ˇ
8729 }
8730 "});
8731
8732 // If a selection span multiple lines, empty lines are not toggled.
8733 cx.set_state(indoc! {"
8734 fn a() {
8735 «a();
8736
8737 c();ˇ»
8738 }
8739 "});
8740
8741 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8742
8743 cx.assert_editor_state(indoc! {"
8744 fn a() {
8745 // «a();
8746
8747 // c();ˇ»
8748 }
8749 "});
8750
8751 // If a selection includes multiple comment prefixes, all lines are uncommented.
8752 cx.set_state(indoc! {"
8753 fn a() {
8754 «// a();
8755 /// b();
8756 //! c();ˇ»
8757 }
8758 "});
8759
8760 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8761
8762 cx.assert_editor_state(indoc! {"
8763 fn a() {
8764 «a();
8765 b();
8766 c();ˇ»
8767 }
8768 "});
8769}
8770
8771#[gpui::test]
8772async fn test_toggle_comment_ignore_indent(cx: &mut gpui::TestAppContext) {
8773 init_test(cx, |_| {});
8774 let mut cx = EditorTestContext::new(cx).await;
8775 let language = Arc::new(Language::new(
8776 LanguageConfig {
8777 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8778 ..Default::default()
8779 },
8780 Some(tree_sitter_rust::LANGUAGE.into()),
8781 ));
8782 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8783
8784 let toggle_comments = &ToggleComments {
8785 advance_downwards: false,
8786 ignore_indent: true,
8787 };
8788
8789 // If multiple selections intersect a line, the line is only toggled once.
8790 cx.set_state(indoc! {"
8791 fn a() {
8792 // «b();
8793 // c();
8794 // ˇ» d();
8795 }
8796 "});
8797
8798 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8799
8800 cx.assert_editor_state(indoc! {"
8801 fn a() {
8802 «b();
8803 c();
8804 ˇ» d();
8805 }
8806 "});
8807
8808 // The comment prefix is inserted at the beginning of each line
8809 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8810
8811 cx.assert_editor_state(indoc! {"
8812 fn a() {
8813 // «b();
8814 // c();
8815 // ˇ» d();
8816 }
8817 "});
8818
8819 // If a selection ends at the beginning of a line, that line is not toggled.
8820 cx.set_selections_state(indoc! {"
8821 fn a() {
8822 // b();
8823 // «c();
8824 ˇ»// d();
8825 }
8826 "});
8827
8828 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8829
8830 cx.assert_editor_state(indoc! {"
8831 fn a() {
8832 // b();
8833 «c();
8834 ˇ»// d();
8835 }
8836 "});
8837
8838 // If a selection span a single line and is empty, the line is toggled.
8839 cx.set_state(indoc! {"
8840 fn a() {
8841 a();
8842 b();
8843 ˇ
8844 }
8845 "});
8846
8847 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8848
8849 cx.assert_editor_state(indoc! {"
8850 fn a() {
8851 a();
8852 b();
8853 //ˇ
8854 }
8855 "});
8856
8857 // If a selection span multiple lines, empty lines are not toggled.
8858 cx.set_state(indoc! {"
8859 fn a() {
8860 «a();
8861
8862 c();ˇ»
8863 }
8864 "});
8865
8866 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8867
8868 cx.assert_editor_state(indoc! {"
8869 fn a() {
8870 // «a();
8871
8872 // c();ˇ»
8873 }
8874 "});
8875
8876 // If a selection includes multiple comment prefixes, all lines are uncommented.
8877 cx.set_state(indoc! {"
8878 fn a() {
8879 // «a();
8880 /// b();
8881 //! c();ˇ»
8882 }
8883 "});
8884
8885 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8886
8887 cx.assert_editor_state(indoc! {"
8888 fn a() {
8889 «a();
8890 b();
8891 c();ˇ»
8892 }
8893 "});
8894}
8895
8896#[gpui::test]
8897async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8898 init_test(cx, |_| {});
8899
8900 let language = Arc::new(Language::new(
8901 LanguageConfig {
8902 line_comments: vec!["// ".into()],
8903 ..Default::default()
8904 },
8905 Some(tree_sitter_rust::LANGUAGE.into()),
8906 ));
8907
8908 let mut cx = EditorTestContext::new(cx).await;
8909
8910 cx.language_registry().add(language.clone());
8911 cx.update_buffer(|buffer, cx| {
8912 buffer.set_language(Some(language), cx);
8913 });
8914
8915 let toggle_comments = &ToggleComments {
8916 advance_downwards: true,
8917 ignore_indent: false,
8918 };
8919
8920 // Single cursor on one line -> advance
8921 // Cursor moves horizontally 3 characters as well on non-blank line
8922 cx.set_state(indoc!(
8923 "fn a() {
8924 ˇdog();
8925 cat();
8926 }"
8927 ));
8928 cx.update_editor(|editor, cx| {
8929 editor.toggle_comments(toggle_comments, cx);
8930 });
8931 cx.assert_editor_state(indoc!(
8932 "fn a() {
8933 // dog();
8934 catˇ();
8935 }"
8936 ));
8937
8938 // Single selection on one line -> don't advance
8939 cx.set_state(indoc!(
8940 "fn a() {
8941 «dog()ˇ»;
8942 cat();
8943 }"
8944 ));
8945 cx.update_editor(|editor, cx| {
8946 editor.toggle_comments(toggle_comments, cx);
8947 });
8948 cx.assert_editor_state(indoc!(
8949 "fn a() {
8950 // «dog()ˇ»;
8951 cat();
8952 }"
8953 ));
8954
8955 // Multiple cursors on one line -> advance
8956 cx.set_state(indoc!(
8957 "fn a() {
8958 ˇdˇog();
8959 cat();
8960 }"
8961 ));
8962 cx.update_editor(|editor, cx| {
8963 editor.toggle_comments(toggle_comments, cx);
8964 });
8965 cx.assert_editor_state(indoc!(
8966 "fn a() {
8967 // dog();
8968 catˇ(ˇ);
8969 }"
8970 ));
8971
8972 // Multiple cursors on one line, with selection -> don't advance
8973 cx.set_state(indoc!(
8974 "fn a() {
8975 ˇdˇog«()ˇ»;
8976 cat();
8977 }"
8978 ));
8979 cx.update_editor(|editor, cx| {
8980 editor.toggle_comments(toggle_comments, cx);
8981 });
8982 cx.assert_editor_state(indoc!(
8983 "fn a() {
8984 // ˇdˇog«()ˇ»;
8985 cat();
8986 }"
8987 ));
8988
8989 // Single cursor on one line -> advance
8990 // Cursor moves to column 0 on blank line
8991 cx.set_state(indoc!(
8992 "fn a() {
8993 ˇdog();
8994
8995 cat();
8996 }"
8997 ));
8998 cx.update_editor(|editor, cx| {
8999 editor.toggle_comments(toggle_comments, cx);
9000 });
9001 cx.assert_editor_state(indoc!(
9002 "fn a() {
9003 // dog();
9004 ˇ
9005 cat();
9006 }"
9007 ));
9008
9009 // Single cursor on one line -> advance
9010 // Cursor starts and ends at column 0
9011 cx.set_state(indoc!(
9012 "fn a() {
9013 ˇ dog();
9014 cat();
9015 }"
9016 ));
9017 cx.update_editor(|editor, cx| {
9018 editor.toggle_comments(toggle_comments, cx);
9019 });
9020 cx.assert_editor_state(indoc!(
9021 "fn a() {
9022 // dog();
9023 ˇ cat();
9024 }"
9025 ));
9026}
9027
9028#[gpui::test]
9029async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
9030 init_test(cx, |_| {});
9031
9032 let mut cx = EditorTestContext::new(cx).await;
9033
9034 let html_language = Arc::new(
9035 Language::new(
9036 LanguageConfig {
9037 name: "HTML".into(),
9038 block_comment: Some(("<!-- ".into(), " -->".into())),
9039 ..Default::default()
9040 },
9041 Some(tree_sitter_html::language()),
9042 )
9043 .with_injection_query(
9044 r#"
9045 (script_element
9046 (raw_text) @content
9047 (#set! "language" "javascript"))
9048 "#,
9049 )
9050 .unwrap(),
9051 );
9052
9053 let javascript_language = Arc::new(Language::new(
9054 LanguageConfig {
9055 name: "JavaScript".into(),
9056 line_comments: vec!["// ".into()],
9057 ..Default::default()
9058 },
9059 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
9060 ));
9061
9062 cx.language_registry().add(html_language.clone());
9063 cx.language_registry().add(javascript_language.clone());
9064 cx.update_buffer(|buffer, cx| {
9065 buffer.set_language(Some(html_language), cx);
9066 });
9067
9068 // Toggle comments for empty selections
9069 cx.set_state(
9070 &r#"
9071 <p>A</p>ˇ
9072 <p>B</p>ˇ
9073 <p>C</p>ˇ
9074 "#
9075 .unindent(),
9076 );
9077 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9078 cx.assert_editor_state(
9079 &r#"
9080 <!-- <p>A</p>ˇ -->
9081 <!-- <p>B</p>ˇ -->
9082 <!-- <p>C</p>ˇ -->
9083 "#
9084 .unindent(),
9085 );
9086 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9087 cx.assert_editor_state(
9088 &r#"
9089 <p>A</p>ˇ
9090 <p>B</p>ˇ
9091 <p>C</p>ˇ
9092 "#
9093 .unindent(),
9094 );
9095
9096 // Toggle comments for mixture of empty and non-empty selections, where
9097 // multiple selections occupy a given line.
9098 cx.set_state(
9099 &r#"
9100 <p>A«</p>
9101 <p>ˇ»B</p>ˇ
9102 <p>C«</p>
9103 <p>ˇ»D</p>ˇ
9104 "#
9105 .unindent(),
9106 );
9107
9108 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9109 cx.assert_editor_state(
9110 &r#"
9111 <!-- <p>A«</p>
9112 <p>ˇ»B</p>ˇ -->
9113 <!-- <p>C«</p>
9114 <p>ˇ»D</p>ˇ -->
9115 "#
9116 .unindent(),
9117 );
9118 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9119 cx.assert_editor_state(
9120 &r#"
9121 <p>A«</p>
9122 <p>ˇ»B</p>ˇ
9123 <p>C«</p>
9124 <p>ˇ»D</p>ˇ
9125 "#
9126 .unindent(),
9127 );
9128
9129 // Toggle comments when different languages are active for different
9130 // selections.
9131 cx.set_state(
9132 &r#"
9133 ˇ<script>
9134 ˇvar x = new Y();
9135 ˇ</script>
9136 "#
9137 .unindent(),
9138 );
9139 cx.executor().run_until_parked();
9140 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9141 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
9142 // Uncommenting and commenting from this position brings in even more wrong artifacts.
9143 cx.assert_editor_state(
9144 &r#"
9145 <!-- ˇ<script> -->
9146 // ˇvar x = new Y();
9147 // ˇ</script>
9148 "#
9149 .unindent(),
9150 );
9151}
9152
9153#[gpui::test]
9154fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
9155 init_test(cx, |_| {});
9156
9157 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9158 let multibuffer = cx.new_model(|cx| {
9159 let mut multibuffer = MultiBuffer::new(ReadWrite);
9160 multibuffer.push_excerpts(
9161 buffer.clone(),
9162 [
9163 ExcerptRange {
9164 context: Point::new(0, 0)..Point::new(0, 4),
9165 primary: None,
9166 },
9167 ExcerptRange {
9168 context: Point::new(1, 0)..Point::new(1, 4),
9169 primary: None,
9170 },
9171 ],
9172 cx,
9173 );
9174 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
9175 multibuffer
9176 });
9177
9178 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9179 view.update(cx, |view, cx| {
9180 assert_eq!(view.text(cx), "aaaa\nbbbb");
9181 view.change_selections(None, cx, |s| {
9182 s.select_ranges([
9183 Point::new(0, 0)..Point::new(0, 0),
9184 Point::new(1, 0)..Point::new(1, 0),
9185 ])
9186 });
9187
9188 view.handle_input("X", cx);
9189 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
9190 assert_eq!(
9191 view.selections.ranges(cx),
9192 [
9193 Point::new(0, 1)..Point::new(0, 1),
9194 Point::new(1, 1)..Point::new(1, 1),
9195 ]
9196 );
9197
9198 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
9199 view.change_selections(None, cx, |s| {
9200 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
9201 });
9202 view.backspace(&Default::default(), cx);
9203 assert_eq!(view.text(cx), "Xa\nbbb");
9204 assert_eq!(
9205 view.selections.ranges(cx),
9206 [Point::new(1, 0)..Point::new(1, 0)]
9207 );
9208
9209 view.change_selections(None, cx, |s| {
9210 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
9211 });
9212 view.backspace(&Default::default(), cx);
9213 assert_eq!(view.text(cx), "X\nbb");
9214 assert_eq!(
9215 view.selections.ranges(cx),
9216 [Point::new(0, 1)..Point::new(0, 1)]
9217 );
9218 });
9219}
9220
9221#[gpui::test]
9222fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
9223 init_test(cx, |_| {});
9224
9225 let markers = vec![('[', ']').into(), ('(', ')').into()];
9226 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
9227 indoc! {"
9228 [aaaa
9229 (bbbb]
9230 cccc)",
9231 },
9232 markers.clone(),
9233 );
9234 let excerpt_ranges = markers.into_iter().map(|marker| {
9235 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9236 ExcerptRange {
9237 context,
9238 primary: None,
9239 }
9240 });
9241 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
9242 let multibuffer = cx.new_model(|cx| {
9243 let mut multibuffer = MultiBuffer::new(ReadWrite);
9244 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9245 multibuffer
9246 });
9247
9248 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9249 view.update(cx, |view, cx| {
9250 let (expected_text, selection_ranges) = marked_text_ranges(
9251 indoc! {"
9252 aaaa
9253 bˇbbb
9254 bˇbbˇb
9255 cccc"
9256 },
9257 true,
9258 );
9259 assert_eq!(view.text(cx), expected_text);
9260 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
9261
9262 view.handle_input("X", cx);
9263
9264 let (expected_text, expected_selections) = marked_text_ranges(
9265 indoc! {"
9266 aaaa
9267 bXˇbbXb
9268 bXˇbbXˇb
9269 cccc"
9270 },
9271 false,
9272 );
9273 assert_eq!(view.text(cx), expected_text);
9274 assert_eq!(view.selections.ranges(cx), expected_selections);
9275
9276 view.newline(&Newline, cx);
9277 let (expected_text, expected_selections) = marked_text_ranges(
9278 indoc! {"
9279 aaaa
9280 bX
9281 ˇbbX
9282 b
9283 bX
9284 ˇbbX
9285 ˇb
9286 cccc"
9287 },
9288 false,
9289 );
9290 assert_eq!(view.text(cx), expected_text);
9291 assert_eq!(view.selections.ranges(cx), expected_selections);
9292 });
9293}
9294
9295#[gpui::test]
9296fn test_refresh_selections(cx: &mut TestAppContext) {
9297 init_test(cx, |_| {});
9298
9299 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9300 let mut excerpt1_id = None;
9301 let multibuffer = cx.new_model(|cx| {
9302 let mut multibuffer = MultiBuffer::new(ReadWrite);
9303 excerpt1_id = multibuffer
9304 .push_excerpts(
9305 buffer.clone(),
9306 [
9307 ExcerptRange {
9308 context: Point::new(0, 0)..Point::new(1, 4),
9309 primary: None,
9310 },
9311 ExcerptRange {
9312 context: Point::new(1, 0)..Point::new(2, 4),
9313 primary: None,
9314 },
9315 ],
9316 cx,
9317 )
9318 .into_iter()
9319 .next();
9320 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9321 multibuffer
9322 });
9323
9324 let editor = cx.add_window(|cx| {
9325 let mut editor = build_editor(multibuffer.clone(), cx);
9326 let snapshot = editor.snapshot(cx);
9327 editor.change_selections(None, cx, |s| {
9328 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
9329 });
9330 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
9331 assert_eq!(
9332 editor.selections.ranges(cx),
9333 [
9334 Point::new(1, 3)..Point::new(1, 3),
9335 Point::new(2, 1)..Point::new(2, 1),
9336 ]
9337 );
9338 editor
9339 });
9340
9341 // Refreshing selections is a no-op when excerpts haven't changed.
9342 _ = editor.update(cx, |editor, cx| {
9343 editor.change_selections(None, cx, |s| s.refresh());
9344 assert_eq!(
9345 editor.selections.ranges(cx),
9346 [
9347 Point::new(1, 3)..Point::new(1, 3),
9348 Point::new(2, 1)..Point::new(2, 1),
9349 ]
9350 );
9351 });
9352
9353 multibuffer.update(cx, |multibuffer, cx| {
9354 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9355 });
9356 _ = editor.update(cx, |editor, cx| {
9357 // Removing an excerpt causes the first selection to become degenerate.
9358 assert_eq!(
9359 editor.selections.ranges(cx),
9360 [
9361 Point::new(0, 0)..Point::new(0, 0),
9362 Point::new(0, 1)..Point::new(0, 1)
9363 ]
9364 );
9365
9366 // Refreshing selections will relocate the first selection to the original buffer
9367 // location.
9368 editor.change_selections(None, cx, |s| s.refresh());
9369 assert_eq!(
9370 editor.selections.ranges(cx),
9371 [
9372 Point::new(0, 1)..Point::new(0, 1),
9373 Point::new(0, 3)..Point::new(0, 3)
9374 ]
9375 );
9376 assert!(editor.selections.pending_anchor().is_some());
9377 });
9378}
9379
9380#[gpui::test]
9381fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
9382 init_test(cx, |_| {});
9383
9384 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9385 let mut excerpt1_id = None;
9386 let multibuffer = cx.new_model(|cx| {
9387 let mut multibuffer = MultiBuffer::new(ReadWrite);
9388 excerpt1_id = multibuffer
9389 .push_excerpts(
9390 buffer.clone(),
9391 [
9392 ExcerptRange {
9393 context: Point::new(0, 0)..Point::new(1, 4),
9394 primary: None,
9395 },
9396 ExcerptRange {
9397 context: Point::new(1, 0)..Point::new(2, 4),
9398 primary: None,
9399 },
9400 ],
9401 cx,
9402 )
9403 .into_iter()
9404 .next();
9405 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9406 multibuffer
9407 });
9408
9409 let editor = cx.add_window(|cx| {
9410 let mut editor = build_editor(multibuffer.clone(), cx);
9411 let snapshot = editor.snapshot(cx);
9412 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
9413 assert_eq!(
9414 editor.selections.ranges(cx),
9415 [Point::new(1, 3)..Point::new(1, 3)]
9416 );
9417 editor
9418 });
9419
9420 multibuffer.update(cx, |multibuffer, cx| {
9421 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9422 });
9423 _ = editor.update(cx, |editor, cx| {
9424 assert_eq!(
9425 editor.selections.ranges(cx),
9426 [Point::new(0, 0)..Point::new(0, 0)]
9427 );
9428
9429 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
9430 editor.change_selections(None, cx, |s| s.refresh());
9431 assert_eq!(
9432 editor.selections.ranges(cx),
9433 [Point::new(0, 3)..Point::new(0, 3)]
9434 );
9435 assert!(editor.selections.pending_anchor().is_some());
9436 });
9437}
9438
9439#[gpui::test]
9440async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
9441 init_test(cx, |_| {});
9442
9443 let language = Arc::new(
9444 Language::new(
9445 LanguageConfig {
9446 brackets: BracketPairConfig {
9447 pairs: vec![
9448 BracketPair {
9449 start: "{".to_string(),
9450 end: "}".to_string(),
9451 close: true,
9452 surround: true,
9453 newline: true,
9454 },
9455 BracketPair {
9456 start: "/* ".to_string(),
9457 end: " */".to_string(),
9458 close: true,
9459 surround: true,
9460 newline: true,
9461 },
9462 ],
9463 ..Default::default()
9464 },
9465 ..Default::default()
9466 },
9467 Some(tree_sitter_rust::LANGUAGE.into()),
9468 )
9469 .with_indents_query("")
9470 .unwrap(),
9471 );
9472
9473 let text = concat!(
9474 "{ }\n", //
9475 " x\n", //
9476 " /* */\n", //
9477 "x\n", //
9478 "{{} }\n", //
9479 );
9480
9481 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
9482 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9483 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9484 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
9485 .await;
9486
9487 view.update(cx, |view, cx| {
9488 view.change_selections(None, cx, |s| {
9489 s.select_display_ranges([
9490 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
9491 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
9492 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
9493 ])
9494 });
9495 view.newline(&Newline, cx);
9496
9497 assert_eq!(
9498 view.buffer().read(cx).read(cx).text(),
9499 concat!(
9500 "{ \n", // Suppress rustfmt
9501 "\n", //
9502 "}\n", //
9503 " x\n", //
9504 " /* \n", //
9505 " \n", //
9506 " */\n", //
9507 "x\n", //
9508 "{{} \n", //
9509 "}\n", //
9510 )
9511 );
9512 });
9513}
9514
9515#[gpui::test]
9516fn test_highlighted_ranges(cx: &mut TestAppContext) {
9517 init_test(cx, |_| {});
9518
9519 let editor = cx.add_window(|cx| {
9520 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
9521 build_editor(buffer.clone(), cx)
9522 });
9523
9524 _ = editor.update(cx, |editor, cx| {
9525 struct Type1;
9526 struct Type2;
9527
9528 let buffer = editor.buffer.read(cx).snapshot(cx);
9529
9530 let anchor_range =
9531 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
9532
9533 editor.highlight_background::<Type1>(
9534 &[
9535 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
9536 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
9537 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
9538 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
9539 ],
9540 |_| Hsla::red(),
9541 cx,
9542 );
9543 editor.highlight_background::<Type2>(
9544 &[
9545 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
9546 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
9547 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
9548 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
9549 ],
9550 |_| Hsla::green(),
9551 cx,
9552 );
9553
9554 let snapshot = editor.snapshot(cx);
9555 let mut highlighted_ranges = editor.background_highlights_in_range(
9556 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
9557 &snapshot,
9558 cx.theme().colors(),
9559 );
9560 // Enforce a consistent ordering based on color without relying on the ordering of the
9561 // highlight's `TypeId` which is non-executor.
9562 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
9563 assert_eq!(
9564 highlighted_ranges,
9565 &[
9566 (
9567 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
9568 Hsla::red(),
9569 ),
9570 (
9571 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9572 Hsla::red(),
9573 ),
9574 (
9575 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9576 Hsla::green(),
9577 ),
9578 (
9579 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9580 Hsla::green(),
9581 ),
9582 ]
9583 );
9584 assert_eq!(
9585 editor.background_highlights_in_range(
9586 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9587 &snapshot,
9588 cx.theme().colors(),
9589 ),
9590 &[(
9591 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9592 Hsla::red(),
9593 )]
9594 );
9595 });
9596}
9597
9598#[gpui::test]
9599async fn test_following(cx: &mut gpui::TestAppContext) {
9600 init_test(cx, |_| {});
9601
9602 let fs = FakeFs::new(cx.executor());
9603 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9604
9605 let buffer = project.update(cx, |project, cx| {
9606 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9607 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9608 });
9609 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9610 let follower = cx.update(|cx| {
9611 cx.open_window(
9612 WindowOptions {
9613 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9614 gpui::Point::new(px(0.), px(0.)),
9615 gpui::Point::new(px(10.), px(80.)),
9616 ))),
9617 ..Default::default()
9618 },
9619 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9620 )
9621 .unwrap()
9622 });
9623
9624 let is_still_following = Rc::new(RefCell::new(true));
9625 let follower_edit_event_count = Rc::new(RefCell::new(0));
9626 let pending_update = Rc::new(RefCell::new(None));
9627 _ = follower.update(cx, {
9628 let update = pending_update.clone();
9629 let is_still_following = is_still_following.clone();
9630 let follower_edit_event_count = follower_edit_event_count.clone();
9631 |_, cx| {
9632 cx.subscribe(
9633 &leader.root_view(cx).unwrap(),
9634 move |_, leader, event, cx| {
9635 leader
9636 .read(cx)
9637 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9638 },
9639 )
9640 .detach();
9641
9642 cx.subscribe(
9643 &follower.root_view(cx).unwrap(),
9644 move |_, _, event: &EditorEvent, _cx| {
9645 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
9646 *is_still_following.borrow_mut() = false;
9647 }
9648
9649 if let EditorEvent::BufferEdited = event {
9650 *follower_edit_event_count.borrow_mut() += 1;
9651 }
9652 },
9653 )
9654 .detach();
9655 }
9656 });
9657
9658 // Update the selections only
9659 _ = leader.update(cx, |leader, cx| {
9660 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9661 });
9662 follower
9663 .update(cx, |follower, cx| {
9664 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9665 })
9666 .unwrap()
9667 .await
9668 .unwrap();
9669 _ = follower.update(cx, |follower, cx| {
9670 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
9671 });
9672 assert!(*is_still_following.borrow());
9673 assert_eq!(*follower_edit_event_count.borrow(), 0);
9674
9675 // Update the scroll position only
9676 _ = leader.update(cx, |leader, cx| {
9677 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9678 });
9679 follower
9680 .update(cx, |follower, cx| {
9681 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9682 })
9683 .unwrap()
9684 .await
9685 .unwrap();
9686 assert_eq!(
9687 follower
9688 .update(cx, |follower, cx| follower.scroll_position(cx))
9689 .unwrap(),
9690 gpui::Point::new(1.5, 3.5)
9691 );
9692 assert!(*is_still_following.borrow());
9693 assert_eq!(*follower_edit_event_count.borrow(), 0);
9694
9695 // Update the selections and scroll position. The follower's scroll position is updated
9696 // via autoscroll, not via the leader's exact scroll position.
9697 _ = leader.update(cx, |leader, cx| {
9698 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
9699 leader.request_autoscroll(Autoscroll::newest(), cx);
9700 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9701 });
9702 follower
9703 .update(cx, |follower, cx| {
9704 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9705 })
9706 .unwrap()
9707 .await
9708 .unwrap();
9709 _ = follower.update(cx, |follower, cx| {
9710 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
9711 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
9712 });
9713 assert!(*is_still_following.borrow());
9714
9715 // Creating a pending selection that precedes another selection
9716 _ = leader.update(cx, |leader, cx| {
9717 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9718 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
9719 });
9720 follower
9721 .update(cx, |follower, cx| {
9722 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9723 })
9724 .unwrap()
9725 .await
9726 .unwrap();
9727 _ = follower.update(cx, |follower, cx| {
9728 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
9729 });
9730 assert!(*is_still_following.borrow());
9731
9732 // Extend the pending selection so that it surrounds another selection
9733 _ = leader.update(cx, |leader, cx| {
9734 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
9735 });
9736 follower
9737 .update(cx, |follower, cx| {
9738 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9739 })
9740 .unwrap()
9741 .await
9742 .unwrap();
9743 _ = follower.update(cx, |follower, cx| {
9744 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
9745 });
9746
9747 // Scrolling locally breaks the follow
9748 _ = follower.update(cx, |follower, cx| {
9749 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
9750 follower.set_scroll_anchor(
9751 ScrollAnchor {
9752 anchor: top_anchor,
9753 offset: gpui::Point::new(0.0, 0.5),
9754 },
9755 cx,
9756 );
9757 });
9758 assert!(!(*is_still_following.borrow()));
9759}
9760
9761#[gpui::test]
9762async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
9763 init_test(cx, |_| {});
9764
9765 let fs = FakeFs::new(cx.executor());
9766 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9767 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9768 let pane = workspace
9769 .update(cx, |workspace, _| workspace.active_pane().clone())
9770 .unwrap();
9771
9772 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9773
9774 let leader = pane.update(cx, |_, cx| {
9775 let multibuffer = cx.new_model(|_| MultiBuffer::new(ReadWrite));
9776 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
9777 });
9778
9779 // Start following the editor when it has no excerpts.
9780 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9781 let follower_1 = cx
9782 .update_window(*workspace.deref(), |_, cx| {
9783 Editor::from_state_proto(
9784 workspace.root_view(cx).unwrap(),
9785 ViewId {
9786 creator: Default::default(),
9787 id: 0,
9788 },
9789 &mut state_message,
9790 cx,
9791 )
9792 })
9793 .unwrap()
9794 .unwrap()
9795 .await
9796 .unwrap();
9797
9798 let update_message = Rc::new(RefCell::new(None));
9799 follower_1.update(cx, {
9800 let update = update_message.clone();
9801 |_, cx| {
9802 cx.subscribe(&leader, move |_, leader, event, cx| {
9803 leader
9804 .read(cx)
9805 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9806 })
9807 .detach();
9808 }
9809 });
9810
9811 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
9812 (
9813 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9814 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9815 )
9816 });
9817
9818 // Insert some excerpts.
9819 leader.update(cx, |leader, cx| {
9820 leader.buffer.update(cx, |multibuffer, cx| {
9821 let excerpt_ids = multibuffer.push_excerpts(
9822 buffer_1.clone(),
9823 [
9824 ExcerptRange {
9825 context: 1..6,
9826 primary: None,
9827 },
9828 ExcerptRange {
9829 context: 12..15,
9830 primary: None,
9831 },
9832 ExcerptRange {
9833 context: 0..3,
9834 primary: None,
9835 },
9836 ],
9837 cx,
9838 );
9839 multibuffer.insert_excerpts_after(
9840 excerpt_ids[0],
9841 buffer_2.clone(),
9842 [
9843 ExcerptRange {
9844 context: 8..12,
9845 primary: None,
9846 },
9847 ExcerptRange {
9848 context: 0..6,
9849 primary: None,
9850 },
9851 ],
9852 cx,
9853 );
9854 });
9855 });
9856
9857 // Apply the update of adding the excerpts.
9858 follower_1
9859 .update(cx, |follower, cx| {
9860 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9861 })
9862 .await
9863 .unwrap();
9864 assert_eq!(
9865 follower_1.update(cx, |editor, cx| editor.text(cx)),
9866 leader.update(cx, |editor, cx| editor.text(cx))
9867 );
9868 update_message.borrow_mut().take();
9869
9870 // Start following separately after it already has excerpts.
9871 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9872 let follower_2 = cx
9873 .update_window(*workspace.deref(), |_, cx| {
9874 Editor::from_state_proto(
9875 workspace.root_view(cx).unwrap().clone(),
9876 ViewId {
9877 creator: Default::default(),
9878 id: 0,
9879 },
9880 &mut state_message,
9881 cx,
9882 )
9883 })
9884 .unwrap()
9885 .unwrap()
9886 .await
9887 .unwrap();
9888 assert_eq!(
9889 follower_2.update(cx, |editor, cx| editor.text(cx)),
9890 leader.update(cx, |editor, cx| editor.text(cx))
9891 );
9892
9893 // Remove some excerpts.
9894 leader.update(cx, |leader, cx| {
9895 leader.buffer.update(cx, |multibuffer, cx| {
9896 let excerpt_ids = multibuffer.excerpt_ids();
9897 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9898 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9899 });
9900 });
9901
9902 // Apply the update of removing the excerpts.
9903 follower_1
9904 .update(cx, |follower, cx| {
9905 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9906 })
9907 .await
9908 .unwrap();
9909 follower_2
9910 .update(cx, |follower, cx| {
9911 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9912 })
9913 .await
9914 .unwrap();
9915 update_message.borrow_mut().take();
9916 assert_eq!(
9917 follower_1.update(cx, |editor, cx| editor.text(cx)),
9918 leader.update(cx, |editor, cx| editor.text(cx))
9919 );
9920}
9921
9922#[gpui::test]
9923async fn go_to_prev_overlapping_diagnostic(
9924 executor: BackgroundExecutor,
9925 cx: &mut gpui::TestAppContext,
9926) {
9927 init_test(cx, |_| {});
9928
9929 let mut cx = EditorTestContext::new(cx).await;
9930 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9931
9932 cx.set_state(indoc! {"
9933 ˇfn func(abc def: i32) -> u32 {
9934 }
9935 "});
9936
9937 cx.update(|cx| {
9938 project.update(cx, |project, cx| {
9939 project
9940 .update_diagnostics(
9941 LanguageServerId(0),
9942 lsp::PublishDiagnosticsParams {
9943 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9944 version: None,
9945 diagnostics: vec![
9946 lsp::Diagnostic {
9947 range: lsp::Range::new(
9948 lsp::Position::new(0, 11),
9949 lsp::Position::new(0, 12),
9950 ),
9951 severity: Some(lsp::DiagnosticSeverity::ERROR),
9952 ..Default::default()
9953 },
9954 lsp::Diagnostic {
9955 range: lsp::Range::new(
9956 lsp::Position::new(0, 12),
9957 lsp::Position::new(0, 15),
9958 ),
9959 severity: Some(lsp::DiagnosticSeverity::ERROR),
9960 ..Default::default()
9961 },
9962 lsp::Diagnostic {
9963 range: lsp::Range::new(
9964 lsp::Position::new(0, 25),
9965 lsp::Position::new(0, 28),
9966 ),
9967 severity: Some(lsp::DiagnosticSeverity::ERROR),
9968 ..Default::default()
9969 },
9970 ],
9971 },
9972 &[],
9973 cx,
9974 )
9975 .unwrap()
9976 });
9977 });
9978
9979 executor.run_until_parked();
9980
9981 cx.update_editor(|editor, cx| {
9982 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9983 });
9984
9985 cx.assert_editor_state(indoc! {"
9986 fn func(abc def: i32) -> ˇu32 {
9987 }
9988 "});
9989
9990 cx.update_editor(|editor, cx| {
9991 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9992 });
9993
9994 cx.assert_editor_state(indoc! {"
9995 fn func(abc ˇdef: i32) -> u32 {
9996 }
9997 "});
9998
9999 cx.update_editor(|editor, cx| {
10000 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10001 });
10002
10003 cx.assert_editor_state(indoc! {"
10004 fn func(abcˇ def: i32) -> u32 {
10005 }
10006 "});
10007
10008 cx.update_editor(|editor, cx| {
10009 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10010 });
10011
10012 cx.assert_editor_state(indoc! {"
10013 fn func(abc def: i32) -> ˇu32 {
10014 }
10015 "});
10016}
10017
10018#[gpui::test]
10019async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
10020 init_test(cx, |_| {});
10021
10022 let mut cx = EditorTestContext::new(cx).await;
10023
10024 cx.set_state(indoc! {"
10025 fn func(abˇc def: i32) -> u32 {
10026 }
10027 "});
10028 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
10029
10030 cx.update(|cx| {
10031 project.update(cx, |project, cx| {
10032 project.update_diagnostics(
10033 LanguageServerId(0),
10034 lsp::PublishDiagnosticsParams {
10035 uri: lsp::Url::from_file_path("/root/file").unwrap(),
10036 version: None,
10037 diagnostics: vec![lsp::Diagnostic {
10038 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
10039 severity: Some(lsp::DiagnosticSeverity::ERROR),
10040 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
10041 ..Default::default()
10042 }],
10043 },
10044 &[],
10045 cx,
10046 )
10047 })
10048 }).unwrap();
10049 cx.run_until_parked();
10050 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
10051 cx.run_until_parked();
10052 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
10053}
10054
10055#[gpui::test]
10056async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10057 init_test(cx, |_| {});
10058
10059 let mut cx = EditorTestContext::new(cx).await;
10060
10061 let diff_base = r#"
10062 use some::mod;
10063
10064 const A: u32 = 42;
10065
10066 fn main() {
10067 println!("hello");
10068
10069 println!("world");
10070 }
10071 "#
10072 .unindent();
10073
10074 // Edits are modified, removed, modified, added
10075 cx.set_state(
10076 &r#"
10077 use some::modified;
10078
10079 ˇ
10080 fn main() {
10081 println!("hello there");
10082
10083 println!("around the");
10084 println!("world");
10085 }
10086 "#
10087 .unindent(),
10088 );
10089
10090 cx.set_diff_base(&diff_base);
10091 executor.run_until_parked();
10092
10093 cx.update_editor(|editor, cx| {
10094 //Wrap around the bottom of the buffer
10095 for _ in 0..3 {
10096 editor.go_to_next_hunk(&GoToHunk, cx);
10097 }
10098 });
10099
10100 cx.assert_editor_state(
10101 &r#"
10102 ˇuse some::modified;
10103
10104
10105 fn main() {
10106 println!("hello there");
10107
10108 println!("around the");
10109 println!("world");
10110 }
10111 "#
10112 .unindent(),
10113 );
10114
10115 cx.update_editor(|editor, cx| {
10116 //Wrap around the top of the buffer
10117 for _ in 0..2 {
10118 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10119 }
10120 });
10121
10122 cx.assert_editor_state(
10123 &r#"
10124 use some::modified;
10125
10126
10127 fn main() {
10128 ˇ println!("hello there");
10129
10130 println!("around the");
10131 println!("world");
10132 }
10133 "#
10134 .unindent(),
10135 );
10136
10137 cx.update_editor(|editor, cx| {
10138 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10139 });
10140
10141 cx.assert_editor_state(
10142 &r#"
10143 use some::modified;
10144
10145 ˇ
10146 fn main() {
10147 println!("hello there");
10148
10149 println!("around the");
10150 println!("world");
10151 }
10152 "#
10153 .unindent(),
10154 );
10155
10156 cx.update_editor(|editor, cx| {
10157 for _ in 0..3 {
10158 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10159 }
10160 });
10161
10162 cx.assert_editor_state(
10163 &r#"
10164 use some::modified;
10165
10166
10167 fn main() {
10168 ˇ println!("hello there");
10169
10170 println!("around the");
10171 println!("world");
10172 }
10173 "#
10174 .unindent(),
10175 );
10176
10177 cx.update_editor(|editor, cx| {
10178 editor.fold(&Fold, cx);
10179
10180 //Make sure that the fold only gets one hunk
10181 for _ in 0..4 {
10182 editor.go_to_next_hunk(&GoToHunk, cx);
10183 }
10184 });
10185
10186 cx.assert_editor_state(
10187 &r#"
10188 ˇuse some::modified;
10189
10190
10191 fn main() {
10192 println!("hello there");
10193
10194 println!("around the");
10195 println!("world");
10196 }
10197 "#
10198 .unindent(),
10199 );
10200}
10201
10202#[test]
10203fn test_split_words() {
10204 fn split(text: &str) -> Vec<&str> {
10205 split_words(text).collect()
10206 }
10207
10208 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
10209 assert_eq!(split("hello_world"), &["hello_", "world"]);
10210 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
10211 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
10212 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
10213 assert_eq!(split("helloworld"), &["helloworld"]);
10214
10215 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
10216}
10217
10218#[gpui::test]
10219async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
10220 init_test(cx, |_| {});
10221
10222 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
10223 let mut assert = |before, after| {
10224 let _state_context = cx.set_state(before);
10225 cx.update_editor(|editor, cx| {
10226 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
10227 });
10228 cx.assert_editor_state(after);
10229 };
10230
10231 // Outside bracket jumps to outside of matching bracket
10232 assert("console.logˇ(var);", "console.log(var)ˇ;");
10233 assert("console.log(var)ˇ;", "console.logˇ(var);");
10234
10235 // Inside bracket jumps to inside of matching bracket
10236 assert("console.log(ˇvar);", "console.log(varˇ);");
10237 assert("console.log(varˇ);", "console.log(ˇvar);");
10238
10239 // When outside a bracket and inside, favor jumping to the inside bracket
10240 assert(
10241 "console.log('foo', [1, 2, 3]ˇ);",
10242 "console.log(ˇ'foo', [1, 2, 3]);",
10243 );
10244 assert(
10245 "console.log(ˇ'foo', [1, 2, 3]);",
10246 "console.log('foo', [1, 2, 3]ˇ);",
10247 );
10248
10249 // Bias forward if two options are equally likely
10250 assert(
10251 "let result = curried_fun()ˇ();",
10252 "let result = curried_fun()()ˇ;",
10253 );
10254
10255 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
10256 assert(
10257 indoc! {"
10258 function test() {
10259 console.log('test')ˇ
10260 }"},
10261 indoc! {"
10262 function test() {
10263 console.logˇ('test')
10264 }"},
10265 );
10266}
10267
10268#[gpui::test]
10269async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
10270 init_test(cx, |_| {});
10271
10272 let fs = FakeFs::new(cx.executor());
10273 fs.insert_tree(
10274 "/a",
10275 json!({
10276 "main.rs": "fn main() { let a = 5; }",
10277 "other.rs": "// Test file",
10278 }),
10279 )
10280 .await;
10281 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10282
10283 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10284 language_registry.add(Arc::new(Language::new(
10285 LanguageConfig {
10286 name: "Rust".into(),
10287 matcher: LanguageMatcher {
10288 path_suffixes: vec!["rs".to_string()],
10289 ..Default::default()
10290 },
10291 brackets: BracketPairConfig {
10292 pairs: vec![BracketPair {
10293 start: "{".to_string(),
10294 end: "}".to_string(),
10295 close: true,
10296 surround: true,
10297 newline: true,
10298 }],
10299 disabled_scopes_by_bracket_ix: Vec::new(),
10300 },
10301 ..Default::default()
10302 },
10303 Some(tree_sitter_rust::LANGUAGE.into()),
10304 )));
10305 let mut fake_servers = language_registry.register_fake_lsp(
10306 "Rust",
10307 FakeLspAdapter {
10308 capabilities: lsp::ServerCapabilities {
10309 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
10310 first_trigger_character: "{".to_string(),
10311 more_trigger_character: None,
10312 }),
10313 ..Default::default()
10314 },
10315 ..Default::default()
10316 },
10317 );
10318
10319 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10320
10321 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10322
10323 let worktree_id = workspace
10324 .update(cx, |workspace, cx| {
10325 workspace.project().update(cx, |project, cx| {
10326 project.worktrees(cx).next().unwrap().read(cx).id()
10327 })
10328 })
10329 .unwrap();
10330
10331 let buffer = project
10332 .update(cx, |project, cx| {
10333 project.open_local_buffer("/a/main.rs", cx)
10334 })
10335 .await
10336 .unwrap();
10337 cx.executor().run_until_parked();
10338 cx.executor().start_waiting();
10339 let fake_server = fake_servers.next().await.unwrap();
10340 let editor_handle = workspace
10341 .update(cx, |workspace, cx| {
10342 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
10343 })
10344 .unwrap()
10345 .await
10346 .unwrap()
10347 .downcast::<Editor>()
10348 .unwrap();
10349
10350 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
10351 assert_eq!(
10352 params.text_document_position.text_document.uri,
10353 lsp::Url::from_file_path("/a/main.rs").unwrap(),
10354 );
10355 assert_eq!(
10356 params.text_document_position.position,
10357 lsp::Position::new(0, 21),
10358 );
10359
10360 Ok(Some(vec![lsp::TextEdit {
10361 new_text: "]".to_string(),
10362 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10363 }]))
10364 });
10365
10366 editor_handle.update(cx, |editor, cx| {
10367 editor.focus(cx);
10368 editor.change_selections(None, cx, |s| {
10369 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
10370 });
10371 editor.handle_input("{", cx);
10372 });
10373
10374 cx.executor().run_until_parked();
10375
10376 buffer.update(cx, |buffer, _| {
10377 assert_eq!(
10378 buffer.text(),
10379 "fn main() { let a = {5}; }",
10380 "No extra braces from on type formatting should appear in the buffer"
10381 )
10382 });
10383}
10384
10385#[gpui::test]
10386async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
10387 init_test(cx, |_| {});
10388
10389 let fs = FakeFs::new(cx.executor());
10390 fs.insert_tree(
10391 "/a",
10392 json!({
10393 "main.rs": "fn main() { let a = 5; }",
10394 "other.rs": "// Test file",
10395 }),
10396 )
10397 .await;
10398
10399 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10400
10401 let server_restarts = Arc::new(AtomicUsize::new(0));
10402 let closure_restarts = Arc::clone(&server_restarts);
10403 let language_server_name = "test language server";
10404 let language_name: LanguageName = "Rust".into();
10405
10406 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10407 language_registry.add(Arc::new(Language::new(
10408 LanguageConfig {
10409 name: language_name.clone(),
10410 matcher: LanguageMatcher {
10411 path_suffixes: vec!["rs".to_string()],
10412 ..Default::default()
10413 },
10414 ..Default::default()
10415 },
10416 Some(tree_sitter_rust::LANGUAGE.into()),
10417 )));
10418 let mut fake_servers = language_registry.register_fake_lsp(
10419 "Rust",
10420 FakeLspAdapter {
10421 name: language_server_name,
10422 initialization_options: Some(json!({
10423 "testOptionValue": true
10424 })),
10425 initializer: Some(Box::new(move |fake_server| {
10426 let task_restarts = Arc::clone(&closure_restarts);
10427 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
10428 task_restarts.fetch_add(1, atomic::Ordering::Release);
10429 futures::future::ready(Ok(()))
10430 });
10431 })),
10432 ..Default::default()
10433 },
10434 );
10435
10436 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10437 let _buffer = project
10438 .update(cx, |project, cx| {
10439 project.open_local_buffer("/a/main.rs", cx)
10440 })
10441 .await
10442 .unwrap();
10443 let _fake_server = fake_servers.next().await.unwrap();
10444 update_test_language_settings(cx, |language_settings| {
10445 language_settings.languages.insert(
10446 language_name.clone(),
10447 LanguageSettingsContent {
10448 tab_size: NonZeroU32::new(8),
10449 ..Default::default()
10450 },
10451 );
10452 });
10453 cx.executor().run_until_parked();
10454 assert_eq!(
10455 server_restarts.load(atomic::Ordering::Acquire),
10456 0,
10457 "Should not restart LSP server on an unrelated change"
10458 );
10459
10460 update_test_project_settings(cx, |project_settings| {
10461 project_settings.lsp.insert(
10462 "Some other server name".into(),
10463 LspSettings {
10464 binary: None,
10465 settings: None,
10466 initialization_options: Some(json!({
10467 "some other init value": false
10468 })),
10469 },
10470 );
10471 });
10472 cx.executor().run_until_parked();
10473 assert_eq!(
10474 server_restarts.load(atomic::Ordering::Acquire),
10475 0,
10476 "Should not restart LSP server on an unrelated LSP settings change"
10477 );
10478
10479 update_test_project_settings(cx, |project_settings| {
10480 project_settings.lsp.insert(
10481 language_server_name.into(),
10482 LspSettings {
10483 binary: None,
10484 settings: None,
10485 initialization_options: Some(json!({
10486 "anotherInitValue": false
10487 })),
10488 },
10489 );
10490 });
10491 cx.executor().run_until_parked();
10492 assert_eq!(
10493 server_restarts.load(atomic::Ordering::Acquire),
10494 1,
10495 "Should restart LSP server on a related LSP settings change"
10496 );
10497
10498 update_test_project_settings(cx, |project_settings| {
10499 project_settings.lsp.insert(
10500 language_server_name.into(),
10501 LspSettings {
10502 binary: None,
10503 settings: None,
10504 initialization_options: Some(json!({
10505 "anotherInitValue": false
10506 })),
10507 },
10508 );
10509 });
10510 cx.executor().run_until_parked();
10511 assert_eq!(
10512 server_restarts.load(atomic::Ordering::Acquire),
10513 1,
10514 "Should not restart LSP server on a related LSP settings change that is the same"
10515 );
10516
10517 update_test_project_settings(cx, |project_settings| {
10518 project_settings.lsp.insert(
10519 language_server_name.into(),
10520 LspSettings {
10521 binary: None,
10522 settings: None,
10523 initialization_options: None,
10524 },
10525 );
10526 });
10527 cx.executor().run_until_parked();
10528 assert_eq!(
10529 server_restarts.load(atomic::Ordering::Acquire),
10530 2,
10531 "Should restart LSP server on another related LSP settings change"
10532 );
10533}
10534
10535#[gpui::test]
10536async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10537 init_test(cx, |_| {});
10538
10539 let mut cx = EditorLspTestContext::new_rust(
10540 lsp::ServerCapabilities {
10541 completion_provider: Some(lsp::CompletionOptions {
10542 trigger_characters: Some(vec![".".to_string()]),
10543 resolve_provider: Some(true),
10544 ..Default::default()
10545 }),
10546 ..Default::default()
10547 },
10548 cx,
10549 )
10550 .await;
10551
10552 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10553 cx.simulate_keystroke(".");
10554 let completion_item = lsp::CompletionItem {
10555 label: "some".into(),
10556 kind: Some(lsp::CompletionItemKind::SNIPPET),
10557 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10558 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10559 kind: lsp::MarkupKind::Markdown,
10560 value: "```rust\nSome(2)\n```".to_string(),
10561 })),
10562 deprecated: Some(false),
10563 sort_text: Some("fffffff2".to_string()),
10564 filter_text: Some("some".to_string()),
10565 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10566 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10567 range: lsp::Range {
10568 start: lsp::Position {
10569 line: 0,
10570 character: 22,
10571 },
10572 end: lsp::Position {
10573 line: 0,
10574 character: 22,
10575 },
10576 },
10577 new_text: "Some(2)".to_string(),
10578 })),
10579 additional_text_edits: Some(vec![lsp::TextEdit {
10580 range: lsp::Range {
10581 start: lsp::Position {
10582 line: 0,
10583 character: 20,
10584 },
10585 end: lsp::Position {
10586 line: 0,
10587 character: 22,
10588 },
10589 },
10590 new_text: "".to_string(),
10591 }]),
10592 ..Default::default()
10593 };
10594
10595 let closure_completion_item = completion_item.clone();
10596 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10597 let task_completion_item = closure_completion_item.clone();
10598 async move {
10599 Ok(Some(lsp::CompletionResponse::Array(vec![
10600 task_completion_item,
10601 ])))
10602 }
10603 });
10604
10605 request.next().await;
10606
10607 cx.condition(|editor, _| editor.context_menu_visible())
10608 .await;
10609 let apply_additional_edits = cx.update_editor(|editor, cx| {
10610 editor
10611 .confirm_completion(&ConfirmCompletion::default(), cx)
10612 .unwrap()
10613 });
10614 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
10615
10616 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10617 let task_completion_item = completion_item.clone();
10618 async move { Ok(task_completion_item) }
10619 })
10620 .next()
10621 .await
10622 .unwrap();
10623 apply_additional_edits.await.unwrap();
10624 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10625}
10626
10627#[gpui::test]
10628async fn test_completions_resolve_updates_labels(cx: &mut gpui::TestAppContext) {
10629 init_test(cx, |_| {});
10630
10631 let mut cx = EditorLspTestContext::new_rust(
10632 lsp::ServerCapabilities {
10633 completion_provider: Some(lsp::CompletionOptions {
10634 trigger_characters: Some(vec![".".to_string()]),
10635 resolve_provider: Some(true),
10636 ..Default::default()
10637 }),
10638 ..Default::default()
10639 },
10640 cx,
10641 )
10642 .await;
10643
10644 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10645 cx.simulate_keystroke(".");
10646
10647 let completion_item = lsp::CompletionItem {
10648 label: "unresolved".to_string(),
10649 detail: None,
10650 documentation: None,
10651 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10652 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10653 new_text: ".unresolved".to_string(),
10654 })),
10655 ..lsp::CompletionItem::default()
10656 };
10657
10658 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10659 let item = completion_item.clone();
10660 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item]))) }
10661 })
10662 .next()
10663 .await;
10664
10665 cx.condition(|editor, _| editor.context_menu_visible())
10666 .await;
10667 cx.update_editor(|editor, _| {
10668 let context_menu = editor.context_menu.read();
10669 let context_menu = context_menu
10670 .as_ref()
10671 .expect("Should have the context menu deployed");
10672 match context_menu {
10673 ContextMenu::Completions(completions_menu) => {
10674 let completions = completions_menu.completions.read();
10675 assert_eq!(completions.len(), 1, "Should have one completion");
10676 assert_eq!(completions.get(0).unwrap().label.text, "unresolved");
10677 }
10678 ContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10679 }
10680 });
10681
10682 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| async move {
10683 Ok(lsp::CompletionItem {
10684 label: "resolved".to_string(),
10685 detail: Some("Now resolved!".to_string()),
10686 documentation: Some(lsp::Documentation::String("Docs".to_string())),
10687 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10688 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10689 new_text: ".resolved".to_string(),
10690 })),
10691 ..lsp::CompletionItem::default()
10692 })
10693 })
10694 .next()
10695 .await;
10696 cx.run_until_parked();
10697
10698 cx.update_editor(|editor, _| {
10699 let context_menu = editor.context_menu.read();
10700 let context_menu = context_menu
10701 .as_ref()
10702 .expect("Should have the context menu deployed");
10703 match context_menu {
10704 ContextMenu::Completions(completions_menu) => {
10705 let completions = completions_menu.completions.read();
10706 assert_eq!(completions.len(), 1, "Should have one completion");
10707 assert_eq!(
10708 completions.get(0).unwrap().label.text,
10709 "resolved",
10710 "Should update the completion label after resolving"
10711 );
10712 }
10713 ContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10714 }
10715 });
10716}
10717
10718#[gpui::test]
10719async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
10720 init_test(cx, |_| {});
10721
10722 let mut cx = EditorLspTestContext::new_rust(
10723 lsp::ServerCapabilities {
10724 completion_provider: Some(lsp::CompletionOptions {
10725 trigger_characters: Some(vec![".".to_string()]),
10726 resolve_provider: Some(true),
10727 ..Default::default()
10728 }),
10729 ..Default::default()
10730 },
10731 cx,
10732 )
10733 .await;
10734
10735 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10736 cx.simulate_keystroke(".");
10737
10738 let default_commit_characters = vec!["?".to_string()];
10739 let default_data = json!({ "very": "special"});
10740 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
10741 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
10742 let default_edit_range = lsp::Range {
10743 start: lsp::Position {
10744 line: 0,
10745 character: 5,
10746 },
10747 end: lsp::Position {
10748 line: 0,
10749 character: 5,
10750 },
10751 };
10752
10753 let resolve_requests_number = Arc::new(AtomicUsize::new(0));
10754 let expect_first_item = Arc::new(AtomicBool::new(true));
10755 cx.lsp
10756 .server
10757 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
10758 let closure_default_data = default_data.clone();
10759 let closure_resolve_requests_number = resolve_requests_number.clone();
10760 let closure_expect_first_item = expect_first_item.clone();
10761 let closure_default_commit_characters = default_commit_characters.clone();
10762 move |item_to_resolve, _| {
10763 closure_resolve_requests_number.fetch_add(1, atomic::Ordering::Release);
10764 let default_data = closure_default_data.clone();
10765 let default_commit_characters = closure_default_commit_characters.clone();
10766 let expect_first_item = closure_expect_first_item.clone();
10767 async move {
10768 if expect_first_item.load(atomic::Ordering::Acquire) {
10769 assert_eq!(
10770 item_to_resolve.label, "Some(2)",
10771 "Should have selected the first item"
10772 );
10773 assert_eq!(
10774 item_to_resolve.data,
10775 Some(json!({ "very": "special"})),
10776 "First item should bring its own data for resolving"
10777 );
10778 assert_eq!(
10779 item_to_resolve.commit_characters,
10780 Some(default_commit_characters),
10781 "First item had no own commit characters and should inherit the default ones"
10782 );
10783 assert!(
10784 matches!(
10785 item_to_resolve.text_edit,
10786 Some(lsp::CompletionTextEdit::InsertAndReplace { .. })
10787 ),
10788 "First item should bring its own edit range for resolving"
10789 );
10790 assert_eq!(
10791 item_to_resolve.insert_text_format,
10792 Some(default_insert_text_format),
10793 "First item had no own insert text format and should inherit the default one"
10794 );
10795 assert_eq!(
10796 item_to_resolve.insert_text_mode,
10797 Some(lsp::InsertTextMode::ADJUST_INDENTATION),
10798 "First item should bring its own insert text mode for resolving"
10799 );
10800 Ok(item_to_resolve)
10801 } else {
10802 assert_eq!(
10803 item_to_resolve.label, "vec![2]",
10804 "Should have selected the last item"
10805 );
10806 assert_eq!(
10807 item_to_resolve.data,
10808 Some(default_data),
10809 "Last item has no own resolve data and should inherit the default one"
10810 );
10811 assert_eq!(
10812 item_to_resolve.commit_characters,
10813 Some(default_commit_characters),
10814 "Last item had no own commit characters and should inherit the default ones"
10815 );
10816 assert_eq!(
10817 item_to_resolve.text_edit,
10818 Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10819 range: default_edit_range,
10820 new_text: "vec![2]".to_string()
10821 })),
10822 "Last item had no own edit range and should inherit the default one"
10823 );
10824 assert_eq!(
10825 item_to_resolve.insert_text_format,
10826 Some(lsp::InsertTextFormat::PLAIN_TEXT),
10827 "Last item should bring its own insert text format for resolving"
10828 );
10829 assert_eq!(
10830 item_to_resolve.insert_text_mode,
10831 Some(default_insert_text_mode),
10832 "Last item had no own insert text mode and should inherit the default one"
10833 );
10834
10835 Ok(item_to_resolve)
10836 }
10837 }
10838 }
10839 }).detach();
10840
10841 let completion_data = default_data.clone();
10842 let completion_characters = default_commit_characters.clone();
10843 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10844 let default_data = completion_data.clone();
10845 let default_commit_characters = completion_characters.clone();
10846 async move {
10847 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
10848 items: vec![
10849 lsp::CompletionItem {
10850 label: "Some(2)".into(),
10851 insert_text: Some("Some(2)".into()),
10852 data: Some(json!({ "very": "special"})),
10853 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
10854 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
10855 lsp::InsertReplaceEdit {
10856 new_text: "Some(2)".to_string(),
10857 insert: lsp::Range::default(),
10858 replace: lsp::Range::default(),
10859 },
10860 )),
10861 ..lsp::CompletionItem::default()
10862 },
10863 lsp::CompletionItem {
10864 label: "vec![2]".into(),
10865 insert_text: Some("vec![2]".into()),
10866 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
10867 ..lsp::CompletionItem::default()
10868 },
10869 ],
10870 item_defaults: Some(lsp::CompletionListItemDefaults {
10871 data: Some(default_data.clone()),
10872 commit_characters: Some(default_commit_characters.clone()),
10873 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
10874 default_edit_range,
10875 )),
10876 insert_text_format: Some(default_insert_text_format),
10877 insert_text_mode: Some(default_insert_text_mode),
10878 }),
10879 ..lsp::CompletionList::default()
10880 })))
10881 }
10882 })
10883 .next()
10884 .await;
10885
10886 cx.condition(|editor, _| editor.context_menu_visible())
10887 .await;
10888 cx.run_until_parked();
10889 cx.update_editor(|editor, _| {
10890 let menu = editor.context_menu.read();
10891 match menu.as_ref().expect("should have the completions menu") {
10892 ContextMenu::Completions(completions_menu) => {
10893 assert_eq!(
10894 completions_menu
10895 .matches
10896 .iter()
10897 .map(|c| c.string.as_str())
10898 .collect::<Vec<_>>(),
10899 vec!["Some(2)", "vec![2]"]
10900 );
10901 }
10902 ContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
10903 }
10904 });
10905 assert_eq!(
10906 resolve_requests_number.load(atomic::Ordering::Acquire),
10907 1,
10908 "While there are 2 items in the completion list, only 1 resolve request should have been sent, for the selected item"
10909 );
10910
10911 cx.update_editor(|editor, cx| {
10912 editor.context_menu_first(&ContextMenuFirst, cx);
10913 });
10914 cx.run_until_parked();
10915 assert_eq!(
10916 resolve_requests_number.load(atomic::Ordering::Acquire),
10917 2,
10918 "After re-selecting the first item, another resolve request should have been sent"
10919 );
10920
10921 expect_first_item.store(false, atomic::Ordering::Release);
10922 cx.update_editor(|editor, cx| {
10923 editor.context_menu_last(&ContextMenuLast, cx);
10924 });
10925 cx.run_until_parked();
10926 assert_eq!(
10927 resolve_requests_number.load(atomic::Ordering::Acquire),
10928 3,
10929 "After selecting the other item, another resolve request should have been sent"
10930 );
10931}
10932
10933#[gpui::test]
10934async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
10935 init_test(cx, |_| {});
10936
10937 let mut cx = EditorLspTestContext::new(
10938 Language::new(
10939 LanguageConfig {
10940 matcher: LanguageMatcher {
10941 path_suffixes: vec!["jsx".into()],
10942 ..Default::default()
10943 },
10944 overrides: [(
10945 "element".into(),
10946 LanguageConfigOverride {
10947 word_characters: Override::Set(['-'].into_iter().collect()),
10948 ..Default::default()
10949 },
10950 )]
10951 .into_iter()
10952 .collect(),
10953 ..Default::default()
10954 },
10955 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10956 )
10957 .with_override_query("(jsx_self_closing_element) @element")
10958 .unwrap(),
10959 lsp::ServerCapabilities {
10960 completion_provider: Some(lsp::CompletionOptions {
10961 trigger_characters: Some(vec![":".to_string()]),
10962 ..Default::default()
10963 }),
10964 ..Default::default()
10965 },
10966 cx,
10967 )
10968 .await;
10969
10970 cx.lsp
10971 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10972 Ok(Some(lsp::CompletionResponse::Array(vec![
10973 lsp::CompletionItem {
10974 label: "bg-blue".into(),
10975 ..Default::default()
10976 },
10977 lsp::CompletionItem {
10978 label: "bg-red".into(),
10979 ..Default::default()
10980 },
10981 lsp::CompletionItem {
10982 label: "bg-yellow".into(),
10983 ..Default::default()
10984 },
10985 ])))
10986 });
10987
10988 cx.set_state(r#"<p class="bgˇ" />"#);
10989
10990 // Trigger completion when typing a dash, because the dash is an extra
10991 // word character in the 'element' scope, which contains the cursor.
10992 cx.simulate_keystroke("-");
10993 cx.executor().run_until_parked();
10994 cx.update_editor(|editor, _| {
10995 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10996 assert_eq!(
10997 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10998 &["bg-red", "bg-blue", "bg-yellow"]
10999 );
11000 } else {
11001 panic!("expected completion menu to be open");
11002 }
11003 });
11004
11005 cx.simulate_keystroke("l");
11006 cx.executor().run_until_parked();
11007 cx.update_editor(|editor, _| {
11008 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
11009 assert_eq!(
11010 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
11011 &["bg-blue", "bg-yellow"]
11012 );
11013 } else {
11014 panic!("expected completion menu to be open");
11015 }
11016 });
11017
11018 // When filtering completions, consider the character after the '-' to
11019 // be the start of a subword.
11020 cx.set_state(r#"<p class="yelˇ" />"#);
11021 cx.simulate_keystroke("l");
11022 cx.executor().run_until_parked();
11023 cx.update_editor(|editor, _| {
11024 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
11025 assert_eq!(
11026 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
11027 &["bg-yellow"]
11028 );
11029 } else {
11030 panic!("expected completion menu to be open");
11031 }
11032 });
11033}
11034
11035#[gpui::test]
11036async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
11037 init_test(cx, |settings| {
11038 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
11039 FormatterList(vec![Formatter::Prettier].into()),
11040 ))
11041 });
11042
11043 let fs = FakeFs::new(cx.executor());
11044 fs.insert_file("/file.ts", Default::default()).await;
11045
11046 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
11047 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11048
11049 language_registry.add(Arc::new(Language::new(
11050 LanguageConfig {
11051 name: "TypeScript".into(),
11052 matcher: LanguageMatcher {
11053 path_suffixes: vec!["ts".to_string()],
11054 ..Default::default()
11055 },
11056 ..Default::default()
11057 },
11058 Some(tree_sitter_rust::LANGUAGE.into()),
11059 )));
11060 update_test_language_settings(cx, |settings| {
11061 settings.defaults.prettier = Some(PrettierSettings {
11062 allowed: true,
11063 ..PrettierSettings::default()
11064 });
11065 });
11066
11067 let test_plugin = "test_plugin";
11068 let _ = language_registry.register_fake_lsp(
11069 "TypeScript",
11070 FakeLspAdapter {
11071 prettier_plugins: vec![test_plugin],
11072 ..Default::default()
11073 },
11074 );
11075
11076 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
11077 let buffer = project
11078 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
11079 .await
11080 .unwrap();
11081
11082 let buffer_text = "one\ntwo\nthree\n";
11083 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
11084 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
11085 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
11086
11087 editor
11088 .update(cx, |editor, cx| {
11089 editor.perform_format(
11090 project.clone(),
11091 FormatTrigger::Manual,
11092 FormatTarget::Buffer,
11093 cx,
11094 )
11095 })
11096 .unwrap()
11097 .await;
11098 assert_eq!(
11099 editor.update(cx, |editor, cx| editor.text(cx)),
11100 buffer_text.to_string() + prettier_format_suffix,
11101 "Test prettier formatting was not applied to the original buffer text",
11102 );
11103
11104 update_test_language_settings(cx, |settings| {
11105 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
11106 });
11107 let format = editor.update(cx, |editor, cx| {
11108 editor.perform_format(
11109 project.clone(),
11110 FormatTrigger::Manual,
11111 FormatTarget::Buffer,
11112 cx,
11113 )
11114 });
11115 format.await.unwrap();
11116 assert_eq!(
11117 editor.update(cx, |editor, cx| editor.text(cx)),
11118 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
11119 "Autoformatting (via test prettier) was not applied to the original buffer text",
11120 );
11121}
11122
11123#[gpui::test]
11124async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
11125 init_test(cx, |_| {});
11126 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11127 let base_text = indoc! {r#"
11128 struct Row;
11129 struct Row1;
11130 struct Row2;
11131
11132 struct Row4;
11133 struct Row5;
11134 struct Row6;
11135
11136 struct Row8;
11137 struct Row9;
11138 struct Row10;"#};
11139
11140 // When addition hunks are not adjacent to carets, no hunk revert is performed
11141 assert_hunk_revert(
11142 indoc! {r#"struct Row;
11143 struct Row1;
11144 struct Row1.1;
11145 struct Row1.2;
11146 struct Row2;ˇ
11147
11148 struct Row4;
11149 struct Row5;
11150 struct Row6;
11151
11152 struct Row8;
11153 ˇstruct Row9;
11154 struct Row9.1;
11155 struct Row9.2;
11156 struct Row9.3;
11157 struct Row10;"#},
11158 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11159 indoc! {r#"struct Row;
11160 struct Row1;
11161 struct Row1.1;
11162 struct Row1.2;
11163 struct Row2;ˇ
11164
11165 struct Row4;
11166 struct Row5;
11167 struct Row6;
11168
11169 struct Row8;
11170 ˇstruct Row9;
11171 struct Row9.1;
11172 struct Row9.2;
11173 struct Row9.3;
11174 struct Row10;"#},
11175 base_text,
11176 &mut cx,
11177 );
11178 // Same for selections
11179 assert_hunk_revert(
11180 indoc! {r#"struct Row;
11181 struct Row1;
11182 struct Row2;
11183 struct Row2.1;
11184 struct Row2.2;
11185 «ˇ
11186 struct Row4;
11187 struct» Row5;
11188 «struct Row6;
11189 ˇ»
11190 struct Row9.1;
11191 struct Row9.2;
11192 struct Row9.3;
11193 struct Row8;
11194 struct Row9;
11195 struct Row10;"#},
11196 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11197 indoc! {r#"struct Row;
11198 struct Row1;
11199 struct Row2;
11200 struct Row2.1;
11201 struct Row2.2;
11202 «ˇ
11203 struct Row4;
11204 struct» Row5;
11205 «struct Row6;
11206 ˇ»
11207 struct Row9.1;
11208 struct Row9.2;
11209 struct Row9.3;
11210 struct Row8;
11211 struct Row9;
11212 struct Row10;"#},
11213 base_text,
11214 &mut cx,
11215 );
11216
11217 // When carets and selections intersect the addition hunks, those are reverted.
11218 // Adjacent carets got merged.
11219 assert_hunk_revert(
11220 indoc! {r#"struct Row;
11221 ˇ// something on the top
11222 struct Row1;
11223 struct Row2;
11224 struct Roˇw3.1;
11225 struct Row2.2;
11226 struct Row2.3;ˇ
11227
11228 struct Row4;
11229 struct ˇRow5.1;
11230 struct Row5.2;
11231 struct «Rowˇ»5.3;
11232 struct Row5;
11233 struct Row6;
11234 ˇ
11235 struct Row9.1;
11236 struct «Rowˇ»9.2;
11237 struct «ˇRow»9.3;
11238 struct Row8;
11239 struct Row9;
11240 «ˇ// something on bottom»
11241 struct Row10;"#},
11242 vec![
11243 DiffHunkStatus::Added,
11244 DiffHunkStatus::Added,
11245 DiffHunkStatus::Added,
11246 DiffHunkStatus::Added,
11247 DiffHunkStatus::Added,
11248 ],
11249 indoc! {r#"struct Row;
11250 ˇstruct Row1;
11251 struct Row2;
11252 ˇ
11253 struct Row4;
11254 ˇstruct Row5;
11255 struct Row6;
11256 ˇ
11257 ˇstruct Row8;
11258 struct Row9;
11259 ˇstruct Row10;"#},
11260 base_text,
11261 &mut cx,
11262 );
11263}
11264
11265#[gpui::test]
11266async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
11267 init_test(cx, |_| {});
11268 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11269 let base_text = indoc! {r#"
11270 struct Row;
11271 struct Row1;
11272 struct Row2;
11273
11274 struct Row4;
11275 struct Row5;
11276 struct Row6;
11277
11278 struct Row8;
11279 struct Row9;
11280 struct Row10;"#};
11281
11282 // Modification hunks behave the same as the addition ones.
11283 assert_hunk_revert(
11284 indoc! {r#"struct Row;
11285 struct Row1;
11286 struct Row33;
11287 ˇ
11288 struct Row4;
11289 struct Row5;
11290 struct Row6;
11291 ˇ
11292 struct Row99;
11293 struct Row9;
11294 struct Row10;"#},
11295 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11296 indoc! {r#"struct Row;
11297 struct Row1;
11298 struct Row33;
11299 ˇ
11300 struct Row4;
11301 struct Row5;
11302 struct Row6;
11303 ˇ
11304 struct Row99;
11305 struct Row9;
11306 struct Row10;"#},
11307 base_text,
11308 &mut cx,
11309 );
11310 assert_hunk_revert(
11311 indoc! {r#"struct Row;
11312 struct Row1;
11313 struct Row33;
11314 «ˇ
11315 struct Row4;
11316 struct» Row5;
11317 «struct Row6;
11318 ˇ»
11319 struct Row99;
11320 struct Row9;
11321 struct Row10;"#},
11322 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11323 indoc! {r#"struct Row;
11324 struct Row1;
11325 struct Row33;
11326 «ˇ
11327 struct Row4;
11328 struct» Row5;
11329 «struct Row6;
11330 ˇ»
11331 struct Row99;
11332 struct Row9;
11333 struct Row10;"#},
11334 base_text,
11335 &mut cx,
11336 );
11337
11338 assert_hunk_revert(
11339 indoc! {r#"ˇstruct Row1.1;
11340 struct Row1;
11341 «ˇstr»uct Row22;
11342
11343 struct ˇRow44;
11344 struct Row5;
11345 struct «Rˇ»ow66;ˇ
11346
11347 «struˇ»ct Row88;
11348 struct Row9;
11349 struct Row1011;ˇ"#},
11350 vec![
11351 DiffHunkStatus::Modified,
11352 DiffHunkStatus::Modified,
11353 DiffHunkStatus::Modified,
11354 DiffHunkStatus::Modified,
11355 DiffHunkStatus::Modified,
11356 DiffHunkStatus::Modified,
11357 ],
11358 indoc! {r#"struct Row;
11359 ˇstruct Row1;
11360 struct Row2;
11361 ˇ
11362 struct Row4;
11363 ˇstruct Row5;
11364 struct Row6;
11365 ˇ
11366 struct Row8;
11367 ˇstruct Row9;
11368 struct Row10;ˇ"#},
11369 base_text,
11370 &mut cx,
11371 );
11372}
11373
11374#[gpui::test]
11375async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
11376 init_test(cx, |_| {});
11377 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11378 let base_text = indoc! {r#"struct Row;
11379struct Row1;
11380struct Row2;
11381
11382struct Row4;
11383struct Row5;
11384struct Row6;
11385
11386struct Row8;
11387struct Row9;
11388struct Row10;"#};
11389
11390 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
11391 assert_hunk_revert(
11392 indoc! {r#"struct Row;
11393 struct Row2;
11394
11395 ˇstruct Row4;
11396 struct Row5;
11397 struct Row6;
11398 ˇ
11399 struct Row8;
11400 struct Row10;"#},
11401 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11402 indoc! {r#"struct Row;
11403 struct Row2;
11404
11405 ˇstruct Row4;
11406 struct Row5;
11407 struct Row6;
11408 ˇ
11409 struct Row8;
11410 struct Row10;"#},
11411 base_text,
11412 &mut cx,
11413 );
11414 assert_hunk_revert(
11415 indoc! {r#"struct Row;
11416 struct Row2;
11417
11418 «ˇstruct Row4;
11419 struct» Row5;
11420 «struct Row6;
11421 ˇ»
11422 struct Row8;
11423 struct Row10;"#},
11424 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11425 indoc! {r#"struct Row;
11426 struct Row2;
11427
11428 «ˇstruct Row4;
11429 struct» Row5;
11430 «struct Row6;
11431 ˇ»
11432 struct Row8;
11433 struct Row10;"#},
11434 base_text,
11435 &mut cx,
11436 );
11437
11438 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
11439 assert_hunk_revert(
11440 indoc! {r#"struct Row;
11441 ˇstruct Row2;
11442
11443 struct Row4;
11444 struct Row5;
11445 struct Row6;
11446
11447 struct Row8;ˇ
11448 struct Row10;"#},
11449 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11450 indoc! {r#"struct Row;
11451 struct Row1;
11452 ˇstruct Row2;
11453
11454 struct Row4;
11455 struct Row5;
11456 struct Row6;
11457
11458 struct Row8;ˇ
11459 struct Row9;
11460 struct Row10;"#},
11461 base_text,
11462 &mut cx,
11463 );
11464 assert_hunk_revert(
11465 indoc! {r#"struct Row;
11466 struct Row2«ˇ;
11467 struct Row4;
11468 struct» Row5;
11469 «struct Row6;
11470
11471 struct Row8;ˇ»
11472 struct Row10;"#},
11473 vec![
11474 DiffHunkStatus::Removed,
11475 DiffHunkStatus::Removed,
11476 DiffHunkStatus::Removed,
11477 ],
11478 indoc! {r#"struct Row;
11479 struct Row1;
11480 struct Row2«ˇ;
11481
11482 struct Row4;
11483 struct» Row5;
11484 «struct Row6;
11485
11486 struct Row8;ˇ»
11487 struct Row9;
11488 struct Row10;"#},
11489 base_text,
11490 &mut cx,
11491 );
11492}
11493
11494#[gpui::test]
11495async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
11496 init_test(cx, |_| {});
11497
11498 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
11499 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
11500 let base_text_3 =
11501 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
11502
11503 let text_1 = edit_first_char_of_every_line(base_text_1);
11504 let text_2 = edit_first_char_of_every_line(base_text_2);
11505 let text_3 = edit_first_char_of_every_line(base_text_3);
11506
11507 let buffer_1 = cx.new_model(|cx| Buffer::local(text_1.clone(), cx));
11508 let buffer_2 = cx.new_model(|cx| Buffer::local(text_2.clone(), cx));
11509 let buffer_3 = cx.new_model(|cx| Buffer::local(text_3.clone(), cx));
11510
11511 let multibuffer = cx.new_model(|cx| {
11512 let mut multibuffer = MultiBuffer::new(ReadWrite);
11513 multibuffer.push_excerpts(
11514 buffer_1.clone(),
11515 [
11516 ExcerptRange {
11517 context: Point::new(0, 0)..Point::new(3, 0),
11518 primary: None,
11519 },
11520 ExcerptRange {
11521 context: Point::new(5, 0)..Point::new(7, 0),
11522 primary: None,
11523 },
11524 ExcerptRange {
11525 context: Point::new(9, 0)..Point::new(10, 4),
11526 primary: None,
11527 },
11528 ],
11529 cx,
11530 );
11531 multibuffer.push_excerpts(
11532 buffer_2.clone(),
11533 [
11534 ExcerptRange {
11535 context: Point::new(0, 0)..Point::new(3, 0),
11536 primary: None,
11537 },
11538 ExcerptRange {
11539 context: Point::new(5, 0)..Point::new(7, 0),
11540 primary: None,
11541 },
11542 ExcerptRange {
11543 context: Point::new(9, 0)..Point::new(10, 4),
11544 primary: None,
11545 },
11546 ],
11547 cx,
11548 );
11549 multibuffer.push_excerpts(
11550 buffer_3.clone(),
11551 [
11552 ExcerptRange {
11553 context: Point::new(0, 0)..Point::new(3, 0),
11554 primary: None,
11555 },
11556 ExcerptRange {
11557 context: Point::new(5, 0)..Point::new(7, 0),
11558 primary: None,
11559 },
11560 ExcerptRange {
11561 context: Point::new(9, 0)..Point::new(10, 4),
11562 primary: None,
11563 },
11564 ],
11565 cx,
11566 );
11567 multibuffer
11568 });
11569
11570 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
11571 editor.update(cx, |editor, cx| {
11572 for (buffer, diff_base) in [
11573 (buffer_1.clone(), base_text_1),
11574 (buffer_2.clone(), base_text_2),
11575 (buffer_3.clone(), base_text_3),
11576 ] {
11577 let change_set = cx.new_model(|cx| {
11578 BufferChangeSet::new_with_base_text(
11579 diff_base.to_string(),
11580 buffer.read(cx).text_snapshot(),
11581 cx,
11582 )
11583 });
11584 editor.diff_map.add_change_set(change_set, cx)
11585 }
11586 });
11587 cx.executor().run_until_parked();
11588
11589 editor.update(cx, |editor, cx| {
11590 assert_eq!(editor.text(cx), "Xaaa\nXbbb\nXccc\n\nXfff\nXggg\n\nXjjj\nXlll\nXmmm\nXnnn\n\nXqqq\nXrrr\n\nXuuu\nXvvv\nXwww\nXxxx\n\nX{{{\nX|||\n\nX\u{7f}\u{7f}\u{7f}");
11591 editor.select_all(&SelectAll, cx);
11592 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11593 });
11594 cx.executor().run_until_parked();
11595
11596 // When all ranges are selected, all buffer hunks are reverted.
11597 editor.update(cx, |editor, cx| {
11598 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");
11599 });
11600 buffer_1.update(cx, |buffer, _| {
11601 assert_eq!(buffer.text(), base_text_1);
11602 });
11603 buffer_2.update(cx, |buffer, _| {
11604 assert_eq!(buffer.text(), base_text_2);
11605 });
11606 buffer_3.update(cx, |buffer, _| {
11607 assert_eq!(buffer.text(), base_text_3);
11608 });
11609
11610 editor.update(cx, |editor, cx| {
11611 editor.undo(&Default::default(), cx);
11612 });
11613
11614 editor.update(cx, |editor, cx| {
11615 editor.change_selections(None, cx, |s| {
11616 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
11617 });
11618 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11619 });
11620
11621 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
11622 // but not affect buffer_2 and its related excerpts.
11623 editor.update(cx, |editor, cx| {
11624 assert_eq!(
11625 editor.text(cx),
11626 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nXlll\nXmmm\nXnnn\n\nXqqq\nXrrr\n\nXuuu\nXvvv\nXwww\nXxxx\n\nX{{{\nX|||\n\nX\u{7f}\u{7f}\u{7f}"
11627 );
11628 });
11629 buffer_1.update(cx, |buffer, _| {
11630 assert_eq!(buffer.text(), base_text_1);
11631 });
11632 buffer_2.update(cx, |buffer, _| {
11633 assert_eq!(
11634 buffer.text(),
11635 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
11636 );
11637 });
11638 buffer_3.update(cx, |buffer, _| {
11639 assert_eq!(
11640 buffer.text(),
11641 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
11642 );
11643 });
11644
11645 fn edit_first_char_of_every_line(text: &str) -> String {
11646 text.split('\n')
11647 .map(|line| format!("X{}", &line[1..]))
11648 .collect::<Vec<_>>()
11649 .join("\n")
11650 }
11651}
11652
11653#[gpui::test]
11654async fn test_multibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
11655 init_test(cx, |_| {});
11656
11657 let cols = 4;
11658 let rows = 10;
11659 let sample_text_1 = sample_text(rows, cols, 'a');
11660 assert_eq!(
11661 sample_text_1,
11662 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11663 );
11664 let sample_text_2 = sample_text(rows, cols, 'l');
11665 assert_eq!(
11666 sample_text_2,
11667 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11668 );
11669 let sample_text_3 = sample_text(rows, cols, 'v');
11670 assert_eq!(
11671 sample_text_3,
11672 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11673 );
11674
11675 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11676 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11677 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11678
11679 let multi_buffer = cx.new_model(|cx| {
11680 let mut multibuffer = MultiBuffer::new(ReadWrite);
11681 multibuffer.push_excerpts(
11682 buffer_1.clone(),
11683 [
11684 ExcerptRange {
11685 context: Point::new(0, 0)..Point::new(3, 0),
11686 primary: None,
11687 },
11688 ExcerptRange {
11689 context: Point::new(5, 0)..Point::new(7, 0),
11690 primary: None,
11691 },
11692 ExcerptRange {
11693 context: Point::new(9, 0)..Point::new(10, 4),
11694 primary: None,
11695 },
11696 ],
11697 cx,
11698 );
11699 multibuffer.push_excerpts(
11700 buffer_2.clone(),
11701 [
11702 ExcerptRange {
11703 context: Point::new(0, 0)..Point::new(3, 0),
11704 primary: None,
11705 },
11706 ExcerptRange {
11707 context: Point::new(5, 0)..Point::new(7, 0),
11708 primary: None,
11709 },
11710 ExcerptRange {
11711 context: Point::new(9, 0)..Point::new(10, 4),
11712 primary: None,
11713 },
11714 ],
11715 cx,
11716 );
11717 multibuffer.push_excerpts(
11718 buffer_3.clone(),
11719 [
11720 ExcerptRange {
11721 context: Point::new(0, 0)..Point::new(3, 0),
11722 primary: None,
11723 },
11724 ExcerptRange {
11725 context: Point::new(5, 0)..Point::new(7, 0),
11726 primary: None,
11727 },
11728 ExcerptRange {
11729 context: Point::new(9, 0)..Point::new(10, 4),
11730 primary: None,
11731 },
11732 ],
11733 cx,
11734 );
11735 multibuffer
11736 });
11737
11738 let fs = FakeFs::new(cx.executor());
11739 fs.insert_tree(
11740 "/a",
11741 json!({
11742 "main.rs": sample_text_1,
11743 "other.rs": sample_text_2,
11744 "lib.rs": sample_text_3,
11745 }),
11746 )
11747 .await;
11748 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11749 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11750 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11751 let multi_buffer_editor = cx.new_view(|cx| {
11752 Editor::new(
11753 EditorMode::Full,
11754 multi_buffer,
11755 Some(project.clone()),
11756 true,
11757 cx,
11758 )
11759 });
11760 let multibuffer_item_id = workspace
11761 .update(cx, |workspace, cx| {
11762 assert!(
11763 workspace.active_item(cx).is_none(),
11764 "active item should be None before the first item is added"
11765 );
11766 workspace.add_item_to_active_pane(
11767 Box::new(multi_buffer_editor.clone()),
11768 None,
11769 true,
11770 cx,
11771 );
11772 let active_item = workspace
11773 .active_item(cx)
11774 .expect("should have an active item after adding the multi buffer");
11775 assert!(
11776 !active_item.is_singleton(cx),
11777 "A multi buffer was expected to active after adding"
11778 );
11779 active_item.item_id()
11780 })
11781 .unwrap();
11782 cx.executor().run_until_parked();
11783
11784 multi_buffer_editor.update(cx, |editor, cx| {
11785 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
11786 editor.open_excerpts(&OpenExcerpts, cx);
11787 });
11788 cx.executor().run_until_parked();
11789 let first_item_id = workspace
11790 .update(cx, |workspace, cx| {
11791 let active_item = workspace
11792 .active_item(cx)
11793 .expect("should have an active item after navigating into the 1st buffer");
11794 let first_item_id = active_item.item_id();
11795 assert_ne!(
11796 first_item_id, multibuffer_item_id,
11797 "Should navigate into the 1st buffer and activate it"
11798 );
11799 assert!(
11800 active_item.is_singleton(cx),
11801 "New active item should be a singleton buffer"
11802 );
11803 assert_eq!(
11804 active_item
11805 .act_as::<Editor>(cx)
11806 .expect("should have navigated into an editor for the 1st buffer")
11807 .read(cx)
11808 .text(cx),
11809 sample_text_1
11810 );
11811
11812 workspace
11813 .go_back(workspace.active_pane().downgrade(), cx)
11814 .detach_and_log_err(cx);
11815
11816 first_item_id
11817 })
11818 .unwrap();
11819 cx.executor().run_until_parked();
11820 workspace
11821 .update(cx, |workspace, cx| {
11822 let active_item = workspace
11823 .active_item(cx)
11824 .expect("should have an active item after navigating back");
11825 assert_eq!(
11826 active_item.item_id(),
11827 multibuffer_item_id,
11828 "Should navigate back to the multi buffer"
11829 );
11830 assert!(!active_item.is_singleton(cx));
11831 })
11832 .unwrap();
11833
11834 multi_buffer_editor.update(cx, |editor, cx| {
11835 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11836 s.select_ranges(Some(39..40))
11837 });
11838 editor.open_excerpts(&OpenExcerpts, cx);
11839 });
11840 cx.executor().run_until_parked();
11841 let second_item_id = workspace
11842 .update(cx, |workspace, cx| {
11843 let active_item = workspace
11844 .active_item(cx)
11845 .expect("should have an active item after navigating into the 2nd buffer");
11846 let second_item_id = active_item.item_id();
11847 assert_ne!(
11848 second_item_id, multibuffer_item_id,
11849 "Should navigate away from the multibuffer"
11850 );
11851 assert_ne!(
11852 second_item_id, first_item_id,
11853 "Should navigate into the 2nd buffer and activate it"
11854 );
11855 assert!(
11856 active_item.is_singleton(cx),
11857 "New active item should be a singleton buffer"
11858 );
11859 assert_eq!(
11860 active_item
11861 .act_as::<Editor>(cx)
11862 .expect("should have navigated into an editor")
11863 .read(cx)
11864 .text(cx),
11865 sample_text_2
11866 );
11867
11868 workspace
11869 .go_back(workspace.active_pane().downgrade(), cx)
11870 .detach_and_log_err(cx);
11871
11872 second_item_id
11873 })
11874 .unwrap();
11875 cx.executor().run_until_parked();
11876 workspace
11877 .update(cx, |workspace, cx| {
11878 let active_item = workspace
11879 .active_item(cx)
11880 .expect("should have an active item after navigating back from the 2nd buffer");
11881 assert_eq!(
11882 active_item.item_id(),
11883 multibuffer_item_id,
11884 "Should navigate back from the 2nd buffer to the multi buffer"
11885 );
11886 assert!(!active_item.is_singleton(cx));
11887 })
11888 .unwrap();
11889
11890 multi_buffer_editor.update(cx, |editor, cx| {
11891 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11892 s.select_ranges(Some(70..70))
11893 });
11894 editor.open_excerpts(&OpenExcerpts, cx);
11895 });
11896 cx.executor().run_until_parked();
11897 workspace
11898 .update(cx, |workspace, cx| {
11899 let active_item = workspace
11900 .active_item(cx)
11901 .expect("should have an active item after navigating into the 3rd buffer");
11902 let third_item_id = active_item.item_id();
11903 assert_ne!(
11904 third_item_id, multibuffer_item_id,
11905 "Should navigate into the 3rd buffer and activate it"
11906 );
11907 assert_ne!(third_item_id, first_item_id);
11908 assert_ne!(third_item_id, second_item_id);
11909 assert!(
11910 active_item.is_singleton(cx),
11911 "New active item should be a singleton buffer"
11912 );
11913 assert_eq!(
11914 active_item
11915 .act_as::<Editor>(cx)
11916 .expect("should have navigated into an editor")
11917 .read(cx)
11918 .text(cx),
11919 sample_text_3
11920 );
11921
11922 workspace
11923 .go_back(workspace.active_pane().downgrade(), cx)
11924 .detach_and_log_err(cx);
11925 })
11926 .unwrap();
11927 cx.executor().run_until_parked();
11928 workspace
11929 .update(cx, |workspace, cx| {
11930 let active_item = workspace
11931 .active_item(cx)
11932 .expect("should have an active item after navigating back from the 3rd buffer");
11933 assert_eq!(
11934 active_item.item_id(),
11935 multibuffer_item_id,
11936 "Should navigate back from the 3rd buffer to the multi buffer"
11937 );
11938 assert!(!active_item.is_singleton(cx));
11939 })
11940 .unwrap();
11941}
11942
11943#[gpui::test]
11944async fn test_multibuffer_unfold_on_jump(cx: &mut gpui::TestAppContext) {
11945 init_test(cx, |_| {});
11946
11947 let texts = ["{\n\tx\n}".to_owned(), "y".to_owned()];
11948 let buffers = texts
11949 .clone()
11950 .map(|txt| cx.new_model(|cx| Buffer::local(txt, cx)));
11951 let multi_buffer = cx.new_model(|cx| {
11952 let mut multi_buffer = MultiBuffer::new(ReadWrite);
11953 for i in 0..2 {
11954 multi_buffer.push_excerpts(
11955 buffers[i].clone(),
11956 [ExcerptRange {
11957 context: 0..texts[i].len(),
11958 primary: None,
11959 }],
11960 cx,
11961 );
11962 }
11963 multi_buffer
11964 });
11965
11966 let fs = FakeFs::new(cx.executor());
11967 fs.insert_tree(
11968 "/project",
11969 json!({
11970 "x": &texts[0],
11971 "y": &texts[1],
11972 }),
11973 )
11974 .await;
11975 let project = Project::test(fs, ["/project".as_ref()], cx).await;
11976 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11977 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11978
11979 let multi_buffer_editor = cx.new_view(|cx| {
11980 Editor::for_multibuffer(multi_buffer.clone(), Some(project.clone()), true, cx)
11981 });
11982 let buffer_editor =
11983 cx.new_view(|cx| Editor::for_buffer(buffers[0].clone(), Some(project.clone()), cx));
11984 workspace
11985 .update(cx, |workspace, cx| {
11986 workspace.add_item_to_active_pane(
11987 Box::new(multi_buffer_editor.clone()),
11988 None,
11989 true,
11990 cx,
11991 );
11992 workspace.add_item_to_active_pane(Box::new(buffer_editor.clone()), None, false, cx);
11993 })
11994 .unwrap();
11995 cx.executor().run_until_parked();
11996 buffer_editor.update(cx, |buffer_editor, cx| {
11997 buffer_editor.fold_at_level(&FoldAtLevel { level: 1 }, cx);
11998 assert!(buffer_editor.snapshot(cx).fold_count() == 1);
11999 });
12000 cx.executor().run_until_parked();
12001 multi_buffer_editor.update(cx, |multi_buffer_editor, cx| {
12002 multi_buffer_editor.change_selections(None, cx, |s| s.select_ranges([3..4]));
12003 multi_buffer_editor.open_excerpts(&OpenExcerpts, cx);
12004 });
12005 cx.executor().run_until_parked();
12006 buffer_editor.update(cx, |buffer_editor, cx| {
12007 assert!(buffer_editor.snapshot(cx).fold_count() == 0);
12008 });
12009}
12010
12011#[gpui::test]
12012async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
12013 init_test(cx, |_| {});
12014
12015 let mut cx = EditorTestContext::new(cx).await;
12016
12017 let diff_base = r#"
12018 use some::mod;
12019
12020 const A: u32 = 42;
12021
12022 fn main() {
12023 println!("hello");
12024
12025 println!("world");
12026 }
12027 "#
12028 .unindent();
12029
12030 cx.set_state(
12031 &r#"
12032 use some::modified;
12033
12034 ˇ
12035 fn main() {
12036 println!("hello there");
12037
12038 println!("around the");
12039 println!("world");
12040 }
12041 "#
12042 .unindent(),
12043 );
12044
12045 cx.set_diff_base(&diff_base);
12046 executor.run_until_parked();
12047
12048 cx.update_editor(|editor, cx| {
12049 editor.go_to_next_hunk(&GoToHunk, cx);
12050 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12051 });
12052 executor.run_until_parked();
12053 cx.assert_state_with_diff(
12054 r#"
12055 use some::modified;
12056
12057
12058 fn main() {
12059 - println!("hello");
12060 + ˇ println!("hello there");
12061
12062 println!("around the");
12063 println!("world");
12064 }
12065 "#
12066 .unindent(),
12067 );
12068
12069 cx.update_editor(|editor, cx| {
12070 for _ in 0..3 {
12071 editor.go_to_next_hunk(&GoToHunk, cx);
12072 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12073 }
12074 });
12075 executor.run_until_parked();
12076 cx.assert_state_with_diff(
12077 r#"
12078 - use some::mod;
12079 + use some::modified;
12080
12081 - const A: u32 = 42;
12082 ˇ
12083 fn main() {
12084 - println!("hello");
12085 + println!("hello there");
12086
12087 + println!("around the");
12088 println!("world");
12089 }
12090 "#
12091 .unindent(),
12092 );
12093
12094 cx.update_editor(|editor, cx| {
12095 editor.cancel(&Cancel, cx);
12096 });
12097
12098 cx.assert_state_with_diff(
12099 r#"
12100 use some::modified;
12101
12102 ˇ
12103 fn main() {
12104 println!("hello there");
12105
12106 println!("around the");
12107 println!("world");
12108 }
12109 "#
12110 .unindent(),
12111 );
12112}
12113
12114#[gpui::test]
12115async fn test_diff_base_change_with_expanded_diff_hunks(
12116 executor: BackgroundExecutor,
12117 cx: &mut gpui::TestAppContext,
12118) {
12119 init_test(cx, |_| {});
12120
12121 let mut cx = EditorTestContext::new(cx).await;
12122
12123 let diff_base = r#"
12124 use some::mod1;
12125 use some::mod2;
12126
12127 const A: u32 = 42;
12128 const B: u32 = 42;
12129 const C: u32 = 42;
12130
12131 fn main() {
12132 println!("hello");
12133
12134 println!("world");
12135 }
12136 "#
12137 .unindent();
12138
12139 cx.set_state(
12140 &r#"
12141 use some::mod2;
12142
12143 const A: u32 = 42;
12144 const C: u32 = 42;
12145
12146 fn main(ˇ) {
12147 //println!("hello");
12148
12149 println!("world");
12150 //
12151 //
12152 }
12153 "#
12154 .unindent(),
12155 );
12156
12157 cx.set_diff_base(&diff_base);
12158 executor.run_until_parked();
12159
12160 cx.update_editor(|editor, cx| {
12161 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12162 });
12163 executor.run_until_parked();
12164 cx.assert_state_with_diff(
12165 r#"
12166 - use some::mod1;
12167 use some::mod2;
12168
12169 const A: u32 = 42;
12170 - const B: u32 = 42;
12171 const C: u32 = 42;
12172
12173 fn main(ˇ) {
12174 - println!("hello");
12175 + //println!("hello");
12176
12177 println!("world");
12178 + //
12179 + //
12180 }
12181 "#
12182 .unindent(),
12183 );
12184
12185 cx.set_diff_base("new diff base!");
12186 executor.run_until_parked();
12187 cx.assert_state_with_diff(
12188 r#"
12189 use some::mod2;
12190
12191 const A: u32 = 42;
12192 const C: u32 = 42;
12193
12194 fn main(ˇ) {
12195 //println!("hello");
12196
12197 println!("world");
12198 //
12199 //
12200 }
12201 "#
12202 .unindent(),
12203 );
12204
12205 cx.update_editor(|editor, cx| {
12206 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12207 });
12208 executor.run_until_parked();
12209 cx.assert_state_with_diff(
12210 r#"
12211 - new diff base!
12212 + use some::mod2;
12213 +
12214 + const A: u32 = 42;
12215 + const C: u32 = 42;
12216 +
12217 + fn main(ˇ) {
12218 + //println!("hello");
12219 +
12220 + println!("world");
12221 + //
12222 + //
12223 + }
12224 "#
12225 .unindent(),
12226 );
12227}
12228
12229#[gpui::test]
12230async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
12231 init_test(cx, |_| {});
12232
12233 let mut cx = EditorTestContext::new(cx).await;
12234
12235 let diff_base = r#"
12236 use some::mod1;
12237 use some::mod2;
12238
12239 const A: u32 = 42;
12240 const B: u32 = 42;
12241 const C: u32 = 42;
12242
12243 fn main() {
12244 println!("hello");
12245
12246 println!("world");
12247 }
12248
12249 fn another() {
12250 println!("another");
12251 }
12252
12253 fn another2() {
12254 println!("another2");
12255 }
12256 "#
12257 .unindent();
12258
12259 cx.set_state(
12260 &r#"
12261 «use some::mod2;
12262
12263 const A: u32 = 42;
12264 const C: u32 = 42;
12265
12266 fn main() {
12267 //println!("hello");
12268
12269 println!("world");
12270 //
12271 //ˇ»
12272 }
12273
12274 fn another() {
12275 println!("another");
12276 println!("another");
12277 }
12278
12279 println!("another2");
12280 }
12281 "#
12282 .unindent(),
12283 );
12284
12285 cx.set_diff_base(&diff_base);
12286 executor.run_until_parked();
12287
12288 cx.update_editor(|editor, cx| {
12289 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12290 });
12291 executor.run_until_parked();
12292
12293 cx.assert_state_with_diff(
12294 r#"
12295 - use some::mod1;
12296 «use some::mod2;
12297
12298 const A: u32 = 42;
12299 - const B: u32 = 42;
12300 const C: u32 = 42;
12301
12302 fn main() {
12303 - println!("hello");
12304 + //println!("hello");
12305
12306 println!("world");
12307 + //
12308 + //ˇ»
12309 }
12310
12311 fn another() {
12312 println!("another");
12313 + println!("another");
12314 }
12315
12316 - fn another2() {
12317 println!("another2");
12318 }
12319 "#
12320 .unindent(),
12321 );
12322
12323 // Fold across some of the diff hunks. They should no longer appear expanded.
12324 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
12325 cx.executor().run_until_parked();
12326
12327 // Hunks are not shown if their position is within a fold
12328 cx.assert_state_with_diff(
12329 r#"
12330 «use some::mod2;
12331
12332 const A: u32 = 42;
12333 const C: u32 = 42;
12334
12335 fn main() {
12336 //println!("hello");
12337
12338 println!("world");
12339 //
12340 //ˇ»
12341 }
12342
12343 fn another() {
12344 println!("another");
12345 + println!("another");
12346 }
12347
12348 - fn another2() {
12349 println!("another2");
12350 }
12351 "#
12352 .unindent(),
12353 );
12354
12355 cx.update_editor(|editor, cx| {
12356 editor.select_all(&SelectAll, cx);
12357 editor.unfold_lines(&UnfoldLines, cx);
12358 });
12359 cx.executor().run_until_parked();
12360
12361 // The deletions reappear when unfolding.
12362 cx.assert_state_with_diff(
12363 r#"
12364 - use some::mod1;
12365 «use some::mod2;
12366
12367 const A: u32 = 42;
12368 - const B: u32 = 42;
12369 const C: u32 = 42;
12370
12371 fn main() {
12372 - println!("hello");
12373 + //println!("hello");
12374
12375 println!("world");
12376 + //
12377 + //
12378 }
12379
12380 fn another() {
12381 println!("another");
12382 + println!("another");
12383 }
12384
12385 - fn another2() {
12386 println!("another2");
12387 }
12388 ˇ»"#
12389 .unindent(),
12390 );
12391}
12392
12393#[gpui::test]
12394async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
12395 init_test(cx, |_| {});
12396
12397 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12398 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12399 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12400 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12401 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
12402 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
12403
12404 let buffer_1 = cx.new_model(|cx| Buffer::local(file_1_new.to_string(), cx));
12405 let buffer_2 = cx.new_model(|cx| Buffer::local(file_2_new.to_string(), cx));
12406 let buffer_3 = cx.new_model(|cx| Buffer::local(file_3_new.to_string(), cx));
12407
12408 let multi_buffer = cx.new_model(|cx| {
12409 let mut multibuffer = MultiBuffer::new(ReadWrite);
12410 multibuffer.push_excerpts(
12411 buffer_1.clone(),
12412 [
12413 ExcerptRange {
12414 context: Point::new(0, 0)..Point::new(3, 0),
12415 primary: None,
12416 },
12417 ExcerptRange {
12418 context: Point::new(5, 0)..Point::new(7, 0),
12419 primary: None,
12420 },
12421 ExcerptRange {
12422 context: Point::new(9, 0)..Point::new(10, 3),
12423 primary: None,
12424 },
12425 ],
12426 cx,
12427 );
12428 multibuffer.push_excerpts(
12429 buffer_2.clone(),
12430 [
12431 ExcerptRange {
12432 context: Point::new(0, 0)..Point::new(3, 0),
12433 primary: None,
12434 },
12435 ExcerptRange {
12436 context: Point::new(5, 0)..Point::new(7, 0),
12437 primary: None,
12438 },
12439 ExcerptRange {
12440 context: Point::new(9, 0)..Point::new(10, 3),
12441 primary: None,
12442 },
12443 ],
12444 cx,
12445 );
12446 multibuffer.push_excerpts(
12447 buffer_3.clone(),
12448 [
12449 ExcerptRange {
12450 context: Point::new(0, 0)..Point::new(3, 0),
12451 primary: None,
12452 },
12453 ExcerptRange {
12454 context: Point::new(5, 0)..Point::new(7, 0),
12455 primary: None,
12456 },
12457 ExcerptRange {
12458 context: Point::new(9, 0)..Point::new(10, 3),
12459 primary: None,
12460 },
12461 ],
12462 cx,
12463 );
12464 multibuffer
12465 });
12466
12467 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12468 editor
12469 .update(cx, |editor, cx| {
12470 for (buffer, diff_base) in [
12471 (buffer_1.clone(), file_1_old),
12472 (buffer_2.clone(), file_2_old),
12473 (buffer_3.clone(), file_3_old),
12474 ] {
12475 let change_set = cx.new_model(|cx| {
12476 BufferChangeSet::new_with_base_text(
12477 diff_base.to_string(),
12478 buffer.read(cx).text_snapshot(),
12479 cx,
12480 )
12481 });
12482 editor.diff_map.add_change_set(change_set, cx)
12483 }
12484 })
12485 .unwrap();
12486
12487 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12488 cx.run_until_parked();
12489
12490 cx.assert_editor_state(
12491 &"
12492 ˇaaa
12493 ccc
12494 ddd
12495
12496 ggg
12497 hhh
12498
12499
12500 lll
12501 mmm
12502 NNN
12503
12504 qqq
12505 rrr
12506
12507 uuu
12508 111
12509 222
12510 333
12511
12512 666
12513 777
12514
12515 000
12516 !!!"
12517 .unindent(),
12518 );
12519
12520 cx.update_editor(|editor, cx| {
12521 editor.select_all(&SelectAll, cx);
12522 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12523 });
12524 cx.executor().run_until_parked();
12525
12526 cx.assert_state_with_diff(
12527 "
12528 «aaa
12529 - bbb
12530 ccc
12531 ddd
12532
12533 ggg
12534 hhh
12535
12536
12537 lll
12538 mmm
12539 - nnn
12540 + NNN
12541
12542 qqq
12543 rrr
12544
12545 uuu
12546 111
12547 222
12548 333
12549
12550 + 666
12551 777
12552
12553 000
12554 !!!ˇ»"
12555 .unindent(),
12556 );
12557}
12558
12559#[gpui::test]
12560async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
12561 init_test(cx, |_| {});
12562
12563 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
12564 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
12565
12566 let buffer = cx.new_model(|cx| Buffer::local(text.to_string(), cx));
12567 let multi_buffer = cx.new_model(|cx| {
12568 let mut multibuffer = MultiBuffer::new(ReadWrite);
12569 multibuffer.push_excerpts(
12570 buffer.clone(),
12571 [
12572 ExcerptRange {
12573 context: Point::new(0, 0)..Point::new(2, 0),
12574 primary: None,
12575 },
12576 ExcerptRange {
12577 context: Point::new(5, 0)..Point::new(7, 0),
12578 primary: None,
12579 },
12580 ],
12581 cx,
12582 );
12583 multibuffer
12584 });
12585
12586 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12587 editor
12588 .update(cx, |editor, cx| {
12589 let buffer = buffer.read(cx).text_snapshot();
12590 let change_set = cx
12591 .new_model(|cx| BufferChangeSet::new_with_base_text(base.to_string(), buffer, cx));
12592 editor.diff_map.add_change_set(change_set, cx)
12593 })
12594 .unwrap();
12595
12596 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12597 cx.run_until_parked();
12598
12599 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
12600 cx.executor().run_until_parked();
12601
12602 cx.assert_state_with_diff(
12603 "
12604 ˇaaa
12605 - bbb
12606 + BBB
12607
12608 - ddd
12609 - eee
12610 + EEE
12611 fff
12612 "
12613 .unindent(),
12614 );
12615}
12616
12617#[gpui::test]
12618async fn test_edits_around_expanded_insertion_hunks(
12619 executor: BackgroundExecutor,
12620 cx: &mut gpui::TestAppContext,
12621) {
12622 init_test(cx, |_| {});
12623
12624 let mut cx = EditorTestContext::new(cx).await;
12625
12626 let diff_base = r#"
12627 use some::mod1;
12628 use some::mod2;
12629
12630 const A: u32 = 42;
12631
12632 fn main() {
12633 println!("hello");
12634
12635 println!("world");
12636 }
12637 "#
12638 .unindent();
12639 executor.run_until_parked();
12640 cx.set_state(
12641 &r#"
12642 use some::mod1;
12643 use some::mod2;
12644
12645 const A: u32 = 42;
12646 const B: u32 = 42;
12647 const C: u32 = 42;
12648 ˇ
12649
12650 fn main() {
12651 println!("hello");
12652
12653 println!("world");
12654 }
12655 "#
12656 .unindent(),
12657 );
12658
12659 cx.set_diff_base(&diff_base);
12660 executor.run_until_parked();
12661
12662 cx.update_editor(|editor, cx| {
12663 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12664 });
12665 executor.run_until_parked();
12666
12667 cx.assert_state_with_diff(
12668 r#"
12669 use some::mod1;
12670 use some::mod2;
12671
12672 const A: u32 = 42;
12673 + const B: u32 = 42;
12674 + const C: u32 = 42;
12675 + ˇ
12676
12677 fn main() {
12678 println!("hello");
12679
12680 println!("world");
12681 }
12682 "#
12683 .unindent(),
12684 );
12685
12686 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
12687 executor.run_until_parked();
12688
12689 cx.assert_state_with_diff(
12690 r#"
12691 use some::mod1;
12692 use some::mod2;
12693
12694 const A: u32 = 42;
12695 + const B: u32 = 42;
12696 + const C: u32 = 42;
12697 + const D: u32 = 42;
12698 + ˇ
12699
12700 fn main() {
12701 println!("hello");
12702
12703 println!("world");
12704 }
12705 "#
12706 .unindent(),
12707 );
12708
12709 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
12710 executor.run_until_parked();
12711
12712 cx.assert_state_with_diff(
12713 r#"
12714 use some::mod1;
12715 use some::mod2;
12716
12717 const A: u32 = 42;
12718 + const B: u32 = 42;
12719 + const C: u32 = 42;
12720 + const D: u32 = 42;
12721 + const E: u32 = 42;
12722 + ˇ
12723
12724 fn main() {
12725 println!("hello");
12726
12727 println!("world");
12728 }
12729 "#
12730 .unindent(),
12731 );
12732
12733 cx.update_editor(|editor, cx| {
12734 editor.delete_line(&DeleteLine, cx);
12735 });
12736 executor.run_until_parked();
12737
12738 cx.assert_state_with_diff(
12739 r#"
12740 use some::mod1;
12741 use some::mod2;
12742
12743 const A: u32 = 42;
12744 + const B: u32 = 42;
12745 + const C: u32 = 42;
12746 + const D: u32 = 42;
12747 + const E: u32 = 42;
12748 ˇ
12749 fn main() {
12750 println!("hello");
12751
12752 println!("world");
12753 }
12754 "#
12755 .unindent(),
12756 );
12757
12758 cx.update_editor(|editor, cx| {
12759 editor.move_up(&MoveUp, cx);
12760 editor.delete_line(&DeleteLine, cx);
12761 editor.move_up(&MoveUp, cx);
12762 editor.delete_line(&DeleteLine, cx);
12763 editor.move_up(&MoveUp, cx);
12764 editor.delete_line(&DeleteLine, cx);
12765 });
12766 executor.run_until_parked();
12767 cx.assert_state_with_diff(
12768 r#"
12769 use some::mod1;
12770 use some::mod2;
12771
12772 const A: u32 = 42;
12773 + const B: u32 = 42;
12774 ˇ
12775 fn main() {
12776 println!("hello");
12777
12778 println!("world");
12779 }
12780 "#
12781 .unindent(),
12782 );
12783
12784 cx.update_editor(|editor, cx| {
12785 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
12786 editor.delete_line(&DeleteLine, cx);
12787 });
12788 executor.run_until_parked();
12789 cx.assert_state_with_diff(
12790 r#"
12791 use some::mod1;
12792 - use some::mod2;
12793 -
12794 - const A: u32 = 42;
12795 ˇ
12796 fn main() {
12797 println!("hello");
12798
12799 println!("world");
12800 }
12801 "#
12802 .unindent(),
12803 );
12804}
12805
12806#[gpui::test]
12807async fn test_edits_around_expanded_deletion_hunks(
12808 executor: BackgroundExecutor,
12809 cx: &mut gpui::TestAppContext,
12810) {
12811 init_test(cx, |_| {});
12812
12813 let mut cx = EditorTestContext::new(cx).await;
12814
12815 let diff_base = r#"
12816 use some::mod1;
12817 use some::mod2;
12818
12819 const A: u32 = 42;
12820 const B: u32 = 42;
12821 const C: u32 = 42;
12822
12823
12824 fn main() {
12825 println!("hello");
12826
12827 println!("world");
12828 }
12829 "#
12830 .unindent();
12831 executor.run_until_parked();
12832 cx.set_state(
12833 &r#"
12834 use some::mod1;
12835 use some::mod2;
12836
12837 ˇconst B: u32 = 42;
12838 const C: u32 = 42;
12839
12840
12841 fn main() {
12842 println!("hello");
12843
12844 println!("world");
12845 }
12846 "#
12847 .unindent(),
12848 );
12849
12850 cx.set_diff_base(&diff_base);
12851 executor.run_until_parked();
12852
12853 cx.update_editor(|editor, cx| {
12854 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12855 });
12856 executor.run_until_parked();
12857
12858 cx.assert_state_with_diff(
12859 r#"
12860 use some::mod1;
12861 use some::mod2;
12862
12863 - const A: u32 = 42;
12864 ˇconst B: u32 = 42;
12865 const C: u32 = 42;
12866
12867
12868 fn main() {
12869 println!("hello");
12870
12871 println!("world");
12872 }
12873 "#
12874 .unindent(),
12875 );
12876
12877 cx.update_editor(|editor, cx| {
12878 editor.delete_line(&DeleteLine, cx);
12879 });
12880 executor.run_until_parked();
12881 cx.assert_state_with_diff(
12882 r#"
12883 use some::mod1;
12884 use some::mod2;
12885
12886 - const A: u32 = 42;
12887 - const B: u32 = 42;
12888 ˇconst C: u32 = 42;
12889
12890
12891 fn main() {
12892 println!("hello");
12893
12894 println!("world");
12895 }
12896 "#
12897 .unindent(),
12898 );
12899
12900 cx.update_editor(|editor, cx| {
12901 editor.delete_line(&DeleteLine, cx);
12902 });
12903 executor.run_until_parked();
12904 cx.assert_state_with_diff(
12905 r#"
12906 use some::mod1;
12907 use some::mod2;
12908
12909 - const A: u32 = 42;
12910 - const B: u32 = 42;
12911 - const C: u32 = 42;
12912 ˇ
12913
12914 fn main() {
12915 println!("hello");
12916
12917 println!("world");
12918 }
12919 "#
12920 .unindent(),
12921 );
12922
12923 cx.update_editor(|editor, cx| {
12924 editor.handle_input("replacement", cx);
12925 });
12926 executor.run_until_parked();
12927 cx.assert_state_with_diff(
12928 r#"
12929 use some::mod1;
12930 use some::mod2;
12931
12932 - const A: u32 = 42;
12933 - const B: u32 = 42;
12934 - const C: u32 = 42;
12935 -
12936 + replacementˇ
12937
12938 fn main() {
12939 println!("hello");
12940
12941 println!("world");
12942 }
12943 "#
12944 .unindent(),
12945 );
12946}
12947
12948#[gpui::test]
12949async fn test_edit_after_expanded_modification_hunk(
12950 executor: BackgroundExecutor,
12951 cx: &mut gpui::TestAppContext,
12952) {
12953 init_test(cx, |_| {});
12954
12955 let mut cx = EditorTestContext::new(cx).await;
12956
12957 let diff_base = r#"
12958 use some::mod1;
12959 use some::mod2;
12960
12961 const A: u32 = 42;
12962 const B: u32 = 42;
12963 const C: u32 = 42;
12964 const D: u32 = 42;
12965
12966
12967 fn main() {
12968 println!("hello");
12969
12970 println!("world");
12971 }"#
12972 .unindent();
12973
12974 cx.set_state(
12975 &r#"
12976 use some::mod1;
12977 use some::mod2;
12978
12979 const A: u32 = 42;
12980 const B: u32 = 42;
12981 const C: u32 = 43ˇ
12982 const D: u32 = 42;
12983
12984
12985 fn main() {
12986 println!("hello");
12987
12988 println!("world");
12989 }"#
12990 .unindent(),
12991 );
12992
12993 cx.set_diff_base(&diff_base);
12994 executor.run_until_parked();
12995 cx.update_editor(|editor, cx| {
12996 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12997 });
12998 executor.run_until_parked();
12999
13000 cx.assert_state_with_diff(
13001 r#"
13002 use some::mod1;
13003 use some::mod2;
13004
13005 const A: u32 = 42;
13006 const B: u32 = 42;
13007 - const C: u32 = 42;
13008 + const C: u32 = 43ˇ
13009 const D: u32 = 42;
13010
13011
13012 fn main() {
13013 println!("hello");
13014
13015 println!("world");
13016 }"#
13017 .unindent(),
13018 );
13019
13020 cx.update_editor(|editor, cx| {
13021 editor.handle_input("\nnew_line\n", cx);
13022 });
13023 executor.run_until_parked();
13024
13025 cx.assert_state_with_diff(
13026 r#"
13027 use some::mod1;
13028 use some::mod2;
13029
13030 const A: u32 = 42;
13031 const B: u32 = 42;
13032 - const C: u32 = 42;
13033 + const C: u32 = 43
13034 + new_line
13035 + ˇ
13036 const D: u32 = 42;
13037
13038
13039 fn main() {
13040 println!("hello");
13041
13042 println!("world");
13043 }"#
13044 .unindent(),
13045 );
13046}
13047
13048async fn setup_indent_guides_editor(
13049 text: &str,
13050 cx: &mut gpui::TestAppContext,
13051) -> (BufferId, EditorTestContext) {
13052 init_test(cx, |_| {});
13053
13054 let mut cx = EditorTestContext::new(cx).await;
13055
13056 let buffer_id = cx.update_editor(|editor, cx| {
13057 editor.set_text(text, cx);
13058 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
13059
13060 buffer_ids[0]
13061 });
13062
13063 (buffer_id, cx)
13064}
13065
13066fn assert_indent_guides(
13067 range: Range<u32>,
13068 expected: Vec<IndentGuide>,
13069 active_indices: Option<Vec<usize>>,
13070 cx: &mut EditorTestContext,
13071) {
13072 let indent_guides = cx.update_editor(|editor, cx| {
13073 let snapshot = editor.snapshot(cx).display_snapshot;
13074 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
13075 MultiBufferRow(range.start)..MultiBufferRow(range.end),
13076 true,
13077 &snapshot,
13078 cx,
13079 );
13080
13081 indent_guides.sort_by(|a, b| {
13082 a.depth.cmp(&b.depth).then(
13083 a.start_row
13084 .cmp(&b.start_row)
13085 .then(a.end_row.cmp(&b.end_row)),
13086 )
13087 });
13088 indent_guides
13089 });
13090
13091 if let Some(expected) = active_indices {
13092 let active_indices = cx.update_editor(|editor, cx| {
13093 let snapshot = editor.snapshot(cx).display_snapshot;
13094 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
13095 });
13096
13097 assert_eq!(
13098 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
13099 expected,
13100 "Active indent guide indices do not match"
13101 );
13102 }
13103
13104 let expected: Vec<_> = expected
13105 .into_iter()
13106 .map(|guide| MultiBufferIndentGuide {
13107 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
13108 buffer: guide,
13109 })
13110 .collect();
13111
13112 assert_eq!(indent_guides, expected, "Indent guides do not match");
13113}
13114
13115fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
13116 IndentGuide {
13117 buffer_id,
13118 start_row,
13119 end_row,
13120 depth,
13121 tab_size: 4,
13122 settings: IndentGuideSettings {
13123 enabled: true,
13124 line_width: 1,
13125 active_line_width: 1,
13126 ..Default::default()
13127 },
13128 }
13129}
13130
13131#[gpui::test]
13132async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13133 let (buffer_id, mut cx) = setup_indent_guides_editor(
13134 &"
13135 fn main() {
13136 let a = 1;
13137 }"
13138 .unindent(),
13139 cx,
13140 )
13141 .await;
13142
13143 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13144}
13145
13146#[gpui::test]
13147async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
13148 let (buffer_id, mut cx) = setup_indent_guides_editor(
13149 &"
13150 fn main() {
13151 let a = 1;
13152 let b = 2;
13153 }"
13154 .unindent(),
13155 cx,
13156 )
13157 .await;
13158
13159 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
13160}
13161
13162#[gpui::test]
13163async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
13164 let (buffer_id, mut cx) = setup_indent_guides_editor(
13165 &"
13166 fn main() {
13167 let a = 1;
13168 if a == 3 {
13169 let b = 2;
13170 } else {
13171 let c = 3;
13172 }
13173 }"
13174 .unindent(),
13175 cx,
13176 )
13177 .await;
13178
13179 assert_indent_guides(
13180 0..8,
13181 vec![
13182 indent_guide(buffer_id, 1, 6, 0),
13183 indent_guide(buffer_id, 3, 3, 1),
13184 indent_guide(buffer_id, 5, 5, 1),
13185 ],
13186 None,
13187 &mut cx,
13188 );
13189}
13190
13191#[gpui::test]
13192async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
13193 let (buffer_id, mut cx) = setup_indent_guides_editor(
13194 &"
13195 fn main() {
13196 let a = 1;
13197 let b = 2;
13198 let c = 3;
13199 }"
13200 .unindent(),
13201 cx,
13202 )
13203 .await;
13204
13205 assert_indent_guides(
13206 0..5,
13207 vec![
13208 indent_guide(buffer_id, 1, 3, 0),
13209 indent_guide(buffer_id, 2, 2, 1),
13210 ],
13211 None,
13212 &mut cx,
13213 );
13214}
13215
13216#[gpui::test]
13217async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
13218 let (buffer_id, mut cx) = setup_indent_guides_editor(
13219 &"
13220 fn main() {
13221 let a = 1;
13222
13223 let c = 3;
13224 }"
13225 .unindent(),
13226 cx,
13227 )
13228 .await;
13229
13230 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
13231}
13232
13233#[gpui::test]
13234async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
13235 let (buffer_id, mut cx) = setup_indent_guides_editor(
13236 &"
13237 fn main() {
13238 let a = 1;
13239
13240 let c = 3;
13241
13242 if a == 3 {
13243 let b = 2;
13244 } else {
13245 let c = 3;
13246 }
13247 }"
13248 .unindent(),
13249 cx,
13250 )
13251 .await;
13252
13253 assert_indent_guides(
13254 0..11,
13255 vec![
13256 indent_guide(buffer_id, 1, 9, 0),
13257 indent_guide(buffer_id, 6, 6, 1),
13258 indent_guide(buffer_id, 8, 8, 1),
13259 ],
13260 None,
13261 &mut cx,
13262 );
13263}
13264
13265#[gpui::test]
13266async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
13267 let (buffer_id, mut cx) = setup_indent_guides_editor(
13268 &"
13269 fn main() {
13270 let a = 1;
13271
13272 let c = 3;
13273
13274 if a == 3 {
13275 let b = 2;
13276 } else {
13277 let c = 3;
13278 }
13279 }"
13280 .unindent(),
13281 cx,
13282 )
13283 .await;
13284
13285 assert_indent_guides(
13286 1..11,
13287 vec![
13288 indent_guide(buffer_id, 1, 9, 0),
13289 indent_guide(buffer_id, 6, 6, 1),
13290 indent_guide(buffer_id, 8, 8, 1),
13291 ],
13292 None,
13293 &mut cx,
13294 );
13295}
13296
13297#[gpui::test]
13298async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
13299 let (buffer_id, mut cx) = setup_indent_guides_editor(
13300 &"
13301 fn main() {
13302 let a = 1;
13303
13304 let c = 3;
13305
13306 if a == 3 {
13307 let b = 2;
13308 } else {
13309 let c = 3;
13310 }
13311 }"
13312 .unindent(),
13313 cx,
13314 )
13315 .await;
13316
13317 assert_indent_guides(
13318 1..10,
13319 vec![
13320 indent_guide(buffer_id, 1, 9, 0),
13321 indent_guide(buffer_id, 6, 6, 1),
13322 indent_guide(buffer_id, 8, 8, 1),
13323 ],
13324 None,
13325 &mut cx,
13326 );
13327}
13328
13329#[gpui::test]
13330async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
13331 let (buffer_id, mut cx) = setup_indent_guides_editor(
13332 &"
13333 block1
13334 block2
13335 block3
13336 block4
13337 block2
13338 block1
13339 block1"
13340 .unindent(),
13341 cx,
13342 )
13343 .await;
13344
13345 assert_indent_guides(
13346 1..10,
13347 vec![
13348 indent_guide(buffer_id, 1, 4, 0),
13349 indent_guide(buffer_id, 2, 3, 1),
13350 indent_guide(buffer_id, 3, 3, 2),
13351 ],
13352 None,
13353 &mut cx,
13354 );
13355}
13356
13357#[gpui::test]
13358async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
13359 let (buffer_id, mut cx) = setup_indent_guides_editor(
13360 &"
13361 block1
13362 block2
13363 block3
13364
13365 block1
13366 block1"
13367 .unindent(),
13368 cx,
13369 )
13370 .await;
13371
13372 assert_indent_guides(
13373 0..6,
13374 vec![
13375 indent_guide(buffer_id, 1, 2, 0),
13376 indent_guide(buffer_id, 2, 2, 1),
13377 ],
13378 None,
13379 &mut cx,
13380 );
13381}
13382
13383#[gpui::test]
13384async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
13385 let (buffer_id, mut cx) = setup_indent_guides_editor(
13386 &"
13387 block1
13388
13389
13390
13391 block2
13392 "
13393 .unindent(),
13394 cx,
13395 )
13396 .await;
13397
13398 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13399}
13400
13401#[gpui::test]
13402async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
13403 let (buffer_id, mut cx) = setup_indent_guides_editor(
13404 &"
13405 def a:
13406 \tb = 3
13407 \tif True:
13408 \t\tc = 4
13409 \t\td = 5
13410 \tprint(b)
13411 "
13412 .unindent(),
13413 cx,
13414 )
13415 .await;
13416
13417 assert_indent_guides(
13418 0..6,
13419 vec![
13420 indent_guide(buffer_id, 1, 6, 0),
13421 indent_guide(buffer_id, 3, 4, 1),
13422 ],
13423 None,
13424 &mut cx,
13425 );
13426}
13427
13428#[gpui::test]
13429async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13430 let (buffer_id, mut cx) = setup_indent_guides_editor(
13431 &"
13432 fn main() {
13433 let a = 1;
13434 }"
13435 .unindent(),
13436 cx,
13437 )
13438 .await;
13439
13440 cx.update_editor(|editor, cx| {
13441 editor.change_selections(None, cx, |s| {
13442 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13443 });
13444 });
13445
13446 assert_indent_guides(
13447 0..3,
13448 vec![indent_guide(buffer_id, 1, 1, 0)],
13449 Some(vec![0]),
13450 &mut cx,
13451 );
13452}
13453
13454#[gpui::test]
13455async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13456 let (buffer_id, mut cx) = setup_indent_guides_editor(
13457 &"
13458 fn main() {
13459 if 1 == 2 {
13460 let a = 1;
13461 }
13462 }"
13463 .unindent(),
13464 cx,
13465 )
13466 .await;
13467
13468 cx.update_editor(|editor, cx| {
13469 editor.change_selections(None, cx, |s| {
13470 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13471 });
13472 });
13473
13474 assert_indent_guides(
13475 0..4,
13476 vec![
13477 indent_guide(buffer_id, 1, 3, 0),
13478 indent_guide(buffer_id, 2, 2, 1),
13479 ],
13480 Some(vec![1]),
13481 &mut cx,
13482 );
13483
13484 cx.update_editor(|editor, cx| {
13485 editor.change_selections(None, cx, |s| {
13486 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13487 });
13488 });
13489
13490 assert_indent_guides(
13491 0..4,
13492 vec![
13493 indent_guide(buffer_id, 1, 3, 0),
13494 indent_guide(buffer_id, 2, 2, 1),
13495 ],
13496 Some(vec![1]),
13497 &mut cx,
13498 );
13499
13500 cx.update_editor(|editor, cx| {
13501 editor.change_selections(None, cx, |s| {
13502 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13503 });
13504 });
13505
13506 assert_indent_guides(
13507 0..4,
13508 vec![
13509 indent_guide(buffer_id, 1, 3, 0),
13510 indent_guide(buffer_id, 2, 2, 1),
13511 ],
13512 Some(vec![0]),
13513 &mut cx,
13514 );
13515}
13516
13517#[gpui::test]
13518async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13519 let (buffer_id, mut cx) = setup_indent_guides_editor(
13520 &"
13521 fn main() {
13522 let a = 1;
13523
13524 let b = 2;
13525 }"
13526 .unindent(),
13527 cx,
13528 )
13529 .await;
13530
13531 cx.update_editor(|editor, cx| {
13532 editor.change_selections(None, cx, |s| {
13533 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13534 });
13535 });
13536
13537 assert_indent_guides(
13538 0..5,
13539 vec![indent_guide(buffer_id, 1, 3, 0)],
13540 Some(vec![0]),
13541 &mut cx,
13542 );
13543}
13544
13545#[gpui::test]
13546async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13547 let (buffer_id, mut cx) = setup_indent_guides_editor(
13548 &"
13549 def m:
13550 a = 1
13551 pass"
13552 .unindent(),
13553 cx,
13554 )
13555 .await;
13556
13557 cx.update_editor(|editor, cx| {
13558 editor.change_selections(None, cx, |s| {
13559 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13560 });
13561 });
13562
13563 assert_indent_guides(
13564 0..3,
13565 vec![indent_guide(buffer_id, 1, 2, 0)],
13566 Some(vec![0]),
13567 &mut cx,
13568 );
13569}
13570
13571#[gpui::test]
13572fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13573 init_test(cx, |_| {});
13574
13575 let editor = cx.add_window(|cx| {
13576 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13577 build_editor(buffer, cx)
13578 });
13579
13580 let render_args = Arc::new(Mutex::new(None));
13581 let snapshot = editor
13582 .update(cx, |editor, cx| {
13583 let snapshot = editor.buffer().read(cx).snapshot(cx);
13584 let range =
13585 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13586
13587 struct RenderArgs {
13588 row: MultiBufferRow,
13589 folded: bool,
13590 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13591 }
13592
13593 let crease = Crease::inline(
13594 range,
13595 FoldPlaceholder::test(),
13596 {
13597 let toggle_callback = render_args.clone();
13598 move |row, folded, callback, _cx| {
13599 *toggle_callback.lock() = Some(RenderArgs {
13600 row,
13601 folded,
13602 callback,
13603 });
13604 div()
13605 }
13606 },
13607 |_row, _folded, _cx| div(),
13608 );
13609
13610 editor.insert_creases(Some(crease), cx);
13611 let snapshot = editor.snapshot(cx);
13612 let _div =
13613 snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13614 snapshot
13615 })
13616 .unwrap();
13617
13618 let render_args = render_args.lock().take().unwrap();
13619 assert_eq!(render_args.row, MultiBufferRow(1));
13620 assert!(!render_args.folded);
13621 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13622
13623 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13624 .unwrap();
13625 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13626 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13627
13628 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13629 .unwrap();
13630 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13631 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13632}
13633
13634#[gpui::test]
13635async fn test_input_text(cx: &mut gpui::TestAppContext) {
13636 init_test(cx, |_| {});
13637 let mut cx = EditorTestContext::new(cx).await;
13638
13639 cx.set_state(
13640 &r#"ˇone
13641 two
13642
13643 three
13644 fourˇ
13645 five
13646
13647 siˇx"#
13648 .unindent(),
13649 );
13650
13651 cx.dispatch_action(HandleInput(String::new()));
13652 cx.assert_editor_state(
13653 &r#"ˇone
13654 two
13655
13656 three
13657 fourˇ
13658 five
13659
13660 siˇx"#
13661 .unindent(),
13662 );
13663
13664 cx.dispatch_action(HandleInput("AAAA".to_string()));
13665 cx.assert_editor_state(
13666 &r#"AAAAˇone
13667 two
13668
13669 three
13670 fourAAAAˇ
13671 five
13672
13673 siAAAAˇx"#
13674 .unindent(),
13675 );
13676}
13677
13678#[gpui::test]
13679async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13680 init_test(cx, |_| {});
13681
13682 let mut cx = EditorTestContext::new(cx).await;
13683 cx.set_state(
13684 r#"let foo = 1;
13685let foo = 2;
13686let foo = 3;
13687let fooˇ = 4;
13688let foo = 5;
13689let foo = 6;
13690let foo = 7;
13691let foo = 8;
13692let foo = 9;
13693let foo = 10;
13694let foo = 11;
13695let foo = 12;
13696let foo = 13;
13697let foo = 14;
13698let foo = 15;"#,
13699 );
13700
13701 cx.update_editor(|e, cx| {
13702 assert_eq!(
13703 e.next_scroll_position,
13704 NextScrollCursorCenterTopBottom::Center,
13705 "Default next scroll direction is center",
13706 );
13707
13708 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13709 assert_eq!(
13710 e.next_scroll_position,
13711 NextScrollCursorCenterTopBottom::Top,
13712 "After center, next scroll direction should be top",
13713 );
13714
13715 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13716 assert_eq!(
13717 e.next_scroll_position,
13718 NextScrollCursorCenterTopBottom::Bottom,
13719 "After top, next scroll direction should be bottom",
13720 );
13721
13722 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13723 assert_eq!(
13724 e.next_scroll_position,
13725 NextScrollCursorCenterTopBottom::Center,
13726 "After bottom, scrolling should start over",
13727 );
13728
13729 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13730 assert_eq!(
13731 e.next_scroll_position,
13732 NextScrollCursorCenterTopBottom::Top,
13733 "Scrolling continues if retriggered fast enough"
13734 );
13735 });
13736
13737 cx.executor()
13738 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13739 cx.executor().run_until_parked();
13740 cx.update_editor(|e, _| {
13741 assert_eq!(
13742 e.next_scroll_position,
13743 NextScrollCursorCenterTopBottom::Center,
13744 "If scrolling is not triggered fast enough, it should reset"
13745 );
13746 });
13747}
13748
13749#[gpui::test]
13750async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13751 init_test(cx, |_| {});
13752 let mut cx = EditorLspTestContext::new_rust(
13753 lsp::ServerCapabilities {
13754 definition_provider: Some(lsp::OneOf::Left(true)),
13755 references_provider: Some(lsp::OneOf::Left(true)),
13756 ..lsp::ServerCapabilities::default()
13757 },
13758 cx,
13759 )
13760 .await;
13761
13762 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13763 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13764 move |params, _| async move {
13765 if empty_go_to_definition {
13766 Ok(None)
13767 } else {
13768 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13769 uri: params.text_document_position_params.text_document.uri,
13770 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13771 })))
13772 }
13773 },
13774 );
13775 let references =
13776 cx.lsp
13777 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13778 Ok(Some(vec![lsp::Location {
13779 uri: params.text_document_position.text_document.uri,
13780 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13781 }]))
13782 });
13783 (go_to_definition, references)
13784 };
13785
13786 cx.set_state(
13787 &r#"fn one() {
13788 let mut a = ˇtwo();
13789 }
13790
13791 fn two() {}"#
13792 .unindent(),
13793 );
13794 set_up_lsp_handlers(false, &mut cx);
13795 let navigated = cx
13796 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13797 .await
13798 .expect("Failed to navigate to definition");
13799 assert_eq!(
13800 navigated,
13801 Navigated::Yes,
13802 "Should have navigated to definition from the GetDefinition response"
13803 );
13804 cx.assert_editor_state(
13805 &r#"fn one() {
13806 let mut a = two();
13807 }
13808
13809 fn «twoˇ»() {}"#
13810 .unindent(),
13811 );
13812
13813 let editors = cx.update_workspace(|workspace, cx| {
13814 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13815 });
13816 cx.update_editor(|_, test_editor_cx| {
13817 assert_eq!(
13818 editors.len(),
13819 1,
13820 "Initially, only one, test, editor should be open in the workspace"
13821 );
13822 assert_eq!(
13823 test_editor_cx.view(),
13824 editors.last().expect("Asserted len is 1")
13825 );
13826 });
13827
13828 set_up_lsp_handlers(true, &mut cx);
13829 let navigated = cx
13830 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13831 .await
13832 .expect("Failed to navigate to lookup references");
13833 assert_eq!(
13834 navigated,
13835 Navigated::Yes,
13836 "Should have navigated to references as a fallback after empty GoToDefinition response"
13837 );
13838 // We should not change the selections in the existing file,
13839 // if opening another milti buffer with the references
13840 cx.assert_editor_state(
13841 &r#"fn one() {
13842 let mut a = two();
13843 }
13844
13845 fn «twoˇ»() {}"#
13846 .unindent(),
13847 );
13848 let editors = cx.update_workspace(|workspace, cx| {
13849 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13850 });
13851 cx.update_editor(|_, test_editor_cx| {
13852 assert_eq!(
13853 editors.len(),
13854 2,
13855 "After falling back to references search, we open a new editor with the results"
13856 );
13857 let references_fallback_text = editors
13858 .into_iter()
13859 .find(|new_editor| new_editor != test_editor_cx.view())
13860 .expect("Should have one non-test editor now")
13861 .read(test_editor_cx)
13862 .text(test_editor_cx);
13863 assert_eq!(
13864 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13865 "Should use the range from the references response and not the GoToDefinition one"
13866 );
13867 });
13868}
13869
13870#[gpui::test]
13871async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
13872 init_test(cx, |_| {});
13873
13874 let language = Arc::new(Language::new(
13875 LanguageConfig::default(),
13876 Some(tree_sitter_rust::LANGUAGE.into()),
13877 ));
13878
13879 let text = r#"
13880 #[cfg(test)]
13881 mod tests() {
13882 #[test]
13883 fn runnable_1() {
13884 let a = 1;
13885 }
13886
13887 #[test]
13888 fn runnable_2() {
13889 let a = 1;
13890 let b = 2;
13891 }
13892 }
13893 "#
13894 .unindent();
13895
13896 let fs = FakeFs::new(cx.executor());
13897 fs.insert_file("/file.rs", Default::default()).await;
13898
13899 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13900 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
13901 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13902 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
13903 let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
13904
13905 let editor = cx.new_view(|cx| {
13906 Editor::new(
13907 EditorMode::Full,
13908 multi_buffer,
13909 Some(project.clone()),
13910 true,
13911 cx,
13912 )
13913 });
13914
13915 editor.update(cx, |editor, cx| {
13916 editor.tasks.insert(
13917 (buffer.read(cx).remote_id(), 3),
13918 RunnableTasks {
13919 templates: vec![],
13920 offset: MultiBufferOffset(43),
13921 column: 0,
13922 extra_variables: HashMap::default(),
13923 context_range: BufferOffset(43)..BufferOffset(85),
13924 },
13925 );
13926 editor.tasks.insert(
13927 (buffer.read(cx).remote_id(), 8),
13928 RunnableTasks {
13929 templates: vec![],
13930 offset: MultiBufferOffset(86),
13931 column: 0,
13932 extra_variables: HashMap::default(),
13933 context_range: BufferOffset(86)..BufferOffset(191),
13934 },
13935 );
13936
13937 // Test finding task when cursor is inside function body
13938 editor.change_selections(None, cx, |s| {
13939 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
13940 });
13941 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13942 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
13943
13944 // Test finding task when cursor is on function name
13945 editor.change_selections(None, cx, |s| {
13946 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
13947 });
13948 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13949 assert_eq!(row, 8, "Should find task when cursor is on function name");
13950 });
13951}
13952
13953fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13954 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13955 point..point
13956}
13957
13958fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13959 let (text, ranges) = marked_text_ranges(marked_text, true);
13960 assert_eq!(view.text(cx), text);
13961 assert_eq!(
13962 view.selections.ranges(cx),
13963 ranges,
13964 "Assert selections are {}",
13965 marked_text
13966 );
13967}
13968
13969pub fn handle_signature_help_request(
13970 cx: &mut EditorLspTestContext,
13971 mocked_response: lsp::SignatureHelp,
13972) -> impl Future<Output = ()> {
13973 let mut request =
13974 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13975 let mocked_response = mocked_response.clone();
13976 async move { Ok(Some(mocked_response)) }
13977 });
13978
13979 async move {
13980 request.next().await;
13981 }
13982}
13983
13984/// Handle completion request passing a marked string specifying where the completion
13985/// should be triggered from using '|' character, what range should be replaced, and what completions
13986/// should be returned using '<' and '>' to delimit the range
13987pub fn handle_completion_request(
13988 cx: &mut EditorLspTestContext,
13989 marked_string: &str,
13990 completions: Vec<&'static str>,
13991 counter: Arc<AtomicUsize>,
13992) -> impl Future<Output = ()> {
13993 let complete_from_marker: TextRangeMarker = '|'.into();
13994 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13995 let (_, mut marked_ranges) = marked_text_ranges_by(
13996 marked_string,
13997 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13998 );
13999
14000 let complete_from_position =
14001 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
14002 let replace_range =
14003 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
14004
14005 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
14006 let completions = completions.clone();
14007 counter.fetch_add(1, atomic::Ordering::Release);
14008 async move {
14009 assert_eq!(params.text_document_position.text_document.uri, url.clone());
14010 assert_eq!(
14011 params.text_document_position.position,
14012 complete_from_position
14013 );
14014 Ok(Some(lsp::CompletionResponse::Array(
14015 completions
14016 .iter()
14017 .map(|completion_text| lsp::CompletionItem {
14018 label: completion_text.to_string(),
14019 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14020 range: replace_range,
14021 new_text: completion_text.to_string(),
14022 })),
14023 ..Default::default()
14024 })
14025 .collect(),
14026 )))
14027 }
14028 });
14029
14030 async move {
14031 request.next().await;
14032 }
14033}
14034
14035fn handle_resolve_completion_request(
14036 cx: &mut EditorLspTestContext,
14037 edits: Option<Vec<(&'static str, &'static str)>>,
14038) -> impl Future<Output = ()> {
14039 let edits = edits.map(|edits| {
14040 edits
14041 .iter()
14042 .map(|(marked_string, new_text)| {
14043 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
14044 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
14045 lsp::TextEdit::new(replace_range, new_text.to_string())
14046 })
14047 .collect::<Vec<_>>()
14048 });
14049
14050 let mut request =
14051 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
14052 let edits = edits.clone();
14053 async move {
14054 Ok(lsp::CompletionItem {
14055 additional_text_edits: edits,
14056 ..Default::default()
14057 })
14058 }
14059 });
14060
14061 async move {
14062 request.next().await;
14063 }
14064}
14065
14066pub(crate) fn update_test_language_settings(
14067 cx: &mut TestAppContext,
14068 f: impl Fn(&mut AllLanguageSettingsContent),
14069) {
14070 cx.update(|cx| {
14071 SettingsStore::update_global(cx, |store, cx| {
14072 store.update_user_settings::<AllLanguageSettings>(cx, f);
14073 });
14074 });
14075}
14076
14077pub(crate) fn update_test_project_settings(
14078 cx: &mut TestAppContext,
14079 f: impl Fn(&mut ProjectSettings),
14080) {
14081 cx.update(|cx| {
14082 SettingsStore::update_global(cx, |store, cx| {
14083 store.update_user_settings::<ProjectSettings>(cx, f);
14084 });
14085 });
14086}
14087
14088pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
14089 cx.update(|cx| {
14090 assets::Assets.load_test_fonts(cx);
14091 let store = SettingsStore::test(cx);
14092 cx.set_global(store);
14093 theme::init(theme::LoadThemes::JustBase, cx);
14094 release_channel::init(SemanticVersion::default(), cx);
14095 client::init_settings(cx);
14096 language::init(cx);
14097 Project::init_settings(cx);
14098 workspace::init_settings(cx);
14099 crate::init(cx);
14100 });
14101
14102 update_test_language_settings(cx, f);
14103}
14104
14105#[track_caller]
14106fn assert_hunk_revert(
14107 not_reverted_text_with_selections: &str,
14108 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
14109 expected_reverted_text_with_selections: &str,
14110 base_text: &str,
14111 cx: &mut EditorLspTestContext,
14112) {
14113 cx.set_state(not_reverted_text_with_selections);
14114 cx.set_diff_base(base_text);
14115 cx.executor().run_until_parked();
14116
14117 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
14118 let snapshot = editor.snapshot(cx);
14119 let reverted_hunk_statuses = snapshot
14120 .diff_map
14121 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
14122 .map(|hunk| hunk_status(&hunk))
14123 .collect::<Vec<_>>();
14124
14125 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
14126 reverted_hunk_statuses
14127 });
14128 cx.executor().run_until_parked();
14129 cx.assert_editor_state(expected_reverted_text_with_selections);
14130 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
14131}