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_mutlibuffer_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_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11945 init_test(cx, |_| {});
11946
11947 let mut cx = EditorTestContext::new(cx).await;
11948
11949 let diff_base = r#"
11950 use some::mod;
11951
11952 const A: u32 = 42;
11953
11954 fn main() {
11955 println!("hello");
11956
11957 println!("world");
11958 }
11959 "#
11960 .unindent();
11961
11962 cx.set_state(
11963 &r#"
11964 use some::modified;
11965
11966 ˇ
11967 fn main() {
11968 println!("hello there");
11969
11970 println!("around the");
11971 println!("world");
11972 }
11973 "#
11974 .unindent(),
11975 );
11976
11977 cx.set_diff_base(&diff_base);
11978 executor.run_until_parked();
11979
11980 cx.update_editor(|editor, cx| {
11981 editor.go_to_next_hunk(&GoToHunk, cx);
11982 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11983 });
11984 executor.run_until_parked();
11985 cx.assert_state_with_diff(
11986 r#"
11987 use some::modified;
11988
11989
11990 fn main() {
11991 - println!("hello");
11992 + ˇ println!("hello there");
11993
11994 println!("around the");
11995 println!("world");
11996 }
11997 "#
11998 .unindent(),
11999 );
12000
12001 cx.update_editor(|editor, cx| {
12002 for _ in 0..3 {
12003 editor.go_to_next_hunk(&GoToHunk, cx);
12004 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12005 }
12006 });
12007 executor.run_until_parked();
12008 cx.assert_state_with_diff(
12009 r#"
12010 - use some::mod;
12011 + use some::modified;
12012
12013 - const A: u32 = 42;
12014 ˇ
12015 fn main() {
12016 - println!("hello");
12017 + println!("hello there");
12018
12019 + println!("around the");
12020 println!("world");
12021 }
12022 "#
12023 .unindent(),
12024 );
12025
12026 cx.update_editor(|editor, cx| {
12027 editor.cancel(&Cancel, cx);
12028 });
12029
12030 cx.assert_state_with_diff(
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
12046#[gpui::test]
12047async fn test_diff_base_change_with_expanded_diff_hunks(
12048 executor: BackgroundExecutor,
12049 cx: &mut gpui::TestAppContext,
12050) {
12051 init_test(cx, |_| {});
12052
12053 let mut cx = EditorTestContext::new(cx).await;
12054
12055 let diff_base = r#"
12056 use some::mod1;
12057 use some::mod2;
12058
12059 const A: u32 = 42;
12060 const B: u32 = 42;
12061 const C: u32 = 42;
12062
12063 fn main() {
12064 println!("hello");
12065
12066 println!("world");
12067 }
12068 "#
12069 .unindent();
12070
12071 cx.set_state(
12072 &r#"
12073 use some::mod2;
12074
12075 const A: u32 = 42;
12076 const C: u32 = 42;
12077
12078 fn main(ˇ) {
12079 //println!("hello");
12080
12081 println!("world");
12082 //
12083 //
12084 }
12085 "#
12086 .unindent(),
12087 );
12088
12089 cx.set_diff_base(&diff_base);
12090 executor.run_until_parked();
12091
12092 cx.update_editor(|editor, cx| {
12093 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12094 });
12095 executor.run_until_parked();
12096 cx.assert_state_with_diff(
12097 r#"
12098 - use some::mod1;
12099 use some::mod2;
12100
12101 const A: u32 = 42;
12102 - const B: u32 = 42;
12103 const C: u32 = 42;
12104
12105 fn main(ˇ) {
12106 - println!("hello");
12107 + //println!("hello");
12108
12109 println!("world");
12110 + //
12111 + //
12112 }
12113 "#
12114 .unindent(),
12115 );
12116
12117 cx.set_diff_base("new diff base!");
12118 executor.run_until_parked();
12119 cx.assert_state_with_diff(
12120 r#"
12121 use some::mod2;
12122
12123 const A: u32 = 42;
12124 const C: u32 = 42;
12125
12126 fn main(ˇ) {
12127 //println!("hello");
12128
12129 println!("world");
12130 //
12131 //
12132 }
12133 "#
12134 .unindent(),
12135 );
12136
12137 cx.update_editor(|editor, cx| {
12138 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12139 });
12140 executor.run_until_parked();
12141 cx.assert_state_with_diff(
12142 r#"
12143 - new diff base!
12144 + use some::mod2;
12145 +
12146 + const A: u32 = 42;
12147 + const C: u32 = 42;
12148 +
12149 + fn main(ˇ) {
12150 + //println!("hello");
12151 +
12152 + println!("world");
12153 + //
12154 + //
12155 + }
12156 "#
12157 .unindent(),
12158 );
12159}
12160
12161#[gpui::test]
12162async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
12163 init_test(cx, |_| {});
12164
12165 let mut cx = EditorTestContext::new(cx).await;
12166
12167 let diff_base = r#"
12168 use some::mod1;
12169 use some::mod2;
12170
12171 const A: u32 = 42;
12172 const B: u32 = 42;
12173 const C: u32 = 42;
12174
12175 fn main() {
12176 println!("hello");
12177
12178 println!("world");
12179 }
12180
12181 fn another() {
12182 println!("another");
12183 }
12184
12185 fn another2() {
12186 println!("another2");
12187 }
12188 "#
12189 .unindent();
12190
12191 cx.set_state(
12192 &r#"
12193 «use some::mod2;
12194
12195 const A: u32 = 42;
12196 const C: u32 = 42;
12197
12198 fn main() {
12199 //println!("hello");
12200
12201 println!("world");
12202 //
12203 //ˇ»
12204 }
12205
12206 fn another() {
12207 println!("another");
12208 println!("another");
12209 }
12210
12211 println!("another2");
12212 }
12213 "#
12214 .unindent(),
12215 );
12216
12217 cx.set_diff_base(&diff_base);
12218 executor.run_until_parked();
12219
12220 cx.update_editor(|editor, cx| {
12221 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12222 });
12223 executor.run_until_parked();
12224
12225 cx.assert_state_with_diff(
12226 r#"
12227 - use some::mod1;
12228 «use some::mod2;
12229
12230 const A: u32 = 42;
12231 - const B: u32 = 42;
12232 const C: u32 = 42;
12233
12234 fn main() {
12235 - println!("hello");
12236 + //println!("hello");
12237
12238 println!("world");
12239 + //
12240 + //ˇ»
12241 }
12242
12243 fn another() {
12244 println!("another");
12245 + println!("another");
12246 }
12247
12248 - fn another2() {
12249 println!("another2");
12250 }
12251 "#
12252 .unindent(),
12253 );
12254
12255 // Fold across some of the diff hunks. They should no longer appear expanded.
12256 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
12257 cx.executor().run_until_parked();
12258
12259 // Hunks are not shown if their position is within a fold
12260 cx.assert_state_with_diff(
12261 r#"
12262 «use some::mod2;
12263
12264 const A: u32 = 42;
12265 const C: u32 = 42;
12266
12267 fn main() {
12268 //println!("hello");
12269
12270 println!("world");
12271 //
12272 //ˇ»
12273 }
12274
12275 fn another() {
12276 println!("another");
12277 + println!("another");
12278 }
12279
12280 - fn another2() {
12281 println!("another2");
12282 }
12283 "#
12284 .unindent(),
12285 );
12286
12287 cx.update_editor(|editor, cx| {
12288 editor.select_all(&SelectAll, cx);
12289 editor.unfold_lines(&UnfoldLines, cx);
12290 });
12291 cx.executor().run_until_parked();
12292
12293 // The deletions reappear when unfolding.
12294 cx.assert_state_with_diff(
12295 r#"
12296 - use some::mod1;
12297 «use some::mod2;
12298
12299 const A: u32 = 42;
12300 - const B: u32 = 42;
12301 const C: u32 = 42;
12302
12303 fn main() {
12304 - println!("hello");
12305 + //println!("hello");
12306
12307 println!("world");
12308 + //
12309 + //
12310 }
12311
12312 fn another() {
12313 println!("another");
12314 + println!("another");
12315 }
12316
12317 - fn another2() {
12318 println!("another2");
12319 }
12320 ˇ»"#
12321 .unindent(),
12322 );
12323}
12324
12325#[gpui::test]
12326async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
12327 init_test(cx, |_| {});
12328
12329 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12330 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12331 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12332 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12333 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
12334 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
12335
12336 let buffer_1 = cx.new_model(|cx| Buffer::local(file_1_new.to_string(), cx));
12337 let buffer_2 = cx.new_model(|cx| Buffer::local(file_2_new.to_string(), cx));
12338 let buffer_3 = cx.new_model(|cx| Buffer::local(file_3_new.to_string(), cx));
12339
12340 let multi_buffer = cx.new_model(|cx| {
12341 let mut multibuffer = MultiBuffer::new(ReadWrite);
12342 multibuffer.push_excerpts(
12343 buffer_1.clone(),
12344 [
12345 ExcerptRange {
12346 context: Point::new(0, 0)..Point::new(3, 0),
12347 primary: None,
12348 },
12349 ExcerptRange {
12350 context: Point::new(5, 0)..Point::new(7, 0),
12351 primary: None,
12352 },
12353 ExcerptRange {
12354 context: Point::new(9, 0)..Point::new(10, 3),
12355 primary: None,
12356 },
12357 ],
12358 cx,
12359 );
12360 multibuffer.push_excerpts(
12361 buffer_2.clone(),
12362 [
12363 ExcerptRange {
12364 context: Point::new(0, 0)..Point::new(3, 0),
12365 primary: None,
12366 },
12367 ExcerptRange {
12368 context: Point::new(5, 0)..Point::new(7, 0),
12369 primary: None,
12370 },
12371 ExcerptRange {
12372 context: Point::new(9, 0)..Point::new(10, 3),
12373 primary: None,
12374 },
12375 ],
12376 cx,
12377 );
12378 multibuffer.push_excerpts(
12379 buffer_3.clone(),
12380 [
12381 ExcerptRange {
12382 context: Point::new(0, 0)..Point::new(3, 0),
12383 primary: None,
12384 },
12385 ExcerptRange {
12386 context: Point::new(5, 0)..Point::new(7, 0),
12387 primary: None,
12388 },
12389 ExcerptRange {
12390 context: Point::new(9, 0)..Point::new(10, 3),
12391 primary: None,
12392 },
12393 ],
12394 cx,
12395 );
12396 multibuffer
12397 });
12398
12399 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12400 editor
12401 .update(cx, |editor, cx| {
12402 for (buffer, diff_base) in [
12403 (buffer_1.clone(), file_1_old),
12404 (buffer_2.clone(), file_2_old),
12405 (buffer_3.clone(), file_3_old),
12406 ] {
12407 let change_set = cx.new_model(|cx| {
12408 BufferChangeSet::new_with_base_text(
12409 diff_base.to_string(),
12410 buffer.read(cx).text_snapshot(),
12411 cx,
12412 )
12413 });
12414 editor.diff_map.add_change_set(change_set, cx)
12415 }
12416 })
12417 .unwrap();
12418
12419 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12420 cx.run_until_parked();
12421
12422 cx.assert_editor_state(
12423 &"
12424 ˇaaa
12425 ccc
12426 ddd
12427
12428 ggg
12429 hhh
12430
12431
12432 lll
12433 mmm
12434 NNN
12435
12436 qqq
12437 rrr
12438
12439 uuu
12440 111
12441 222
12442 333
12443
12444 666
12445 777
12446
12447 000
12448 !!!"
12449 .unindent(),
12450 );
12451
12452 cx.update_editor(|editor, cx| {
12453 editor.select_all(&SelectAll, cx);
12454 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12455 });
12456 cx.executor().run_until_parked();
12457
12458 cx.assert_state_with_diff(
12459 "
12460 «aaa
12461 - bbb
12462 ccc
12463 ddd
12464
12465 ggg
12466 hhh
12467
12468
12469 lll
12470 mmm
12471 - nnn
12472 + NNN
12473
12474 qqq
12475 rrr
12476
12477 uuu
12478 111
12479 222
12480 333
12481
12482 + 666
12483 777
12484
12485 000
12486 !!!ˇ»"
12487 .unindent(),
12488 );
12489}
12490
12491#[gpui::test]
12492async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
12493 init_test(cx, |_| {});
12494
12495 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
12496 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
12497
12498 let buffer = cx.new_model(|cx| Buffer::local(text.to_string(), cx));
12499 let multi_buffer = cx.new_model(|cx| {
12500 let mut multibuffer = MultiBuffer::new(ReadWrite);
12501 multibuffer.push_excerpts(
12502 buffer.clone(),
12503 [
12504 ExcerptRange {
12505 context: Point::new(0, 0)..Point::new(2, 0),
12506 primary: None,
12507 },
12508 ExcerptRange {
12509 context: Point::new(5, 0)..Point::new(7, 0),
12510 primary: None,
12511 },
12512 ],
12513 cx,
12514 );
12515 multibuffer
12516 });
12517
12518 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12519 editor
12520 .update(cx, |editor, cx| {
12521 let buffer = buffer.read(cx).text_snapshot();
12522 let change_set = cx
12523 .new_model(|cx| BufferChangeSet::new_with_base_text(base.to_string(), buffer, cx));
12524 editor.diff_map.add_change_set(change_set, cx)
12525 })
12526 .unwrap();
12527
12528 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12529 cx.run_until_parked();
12530
12531 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
12532 cx.executor().run_until_parked();
12533
12534 cx.assert_state_with_diff(
12535 "
12536 ˇaaa
12537 - bbb
12538 + BBB
12539
12540 - ddd
12541 - eee
12542 + EEE
12543 fff
12544 "
12545 .unindent(),
12546 );
12547}
12548
12549#[gpui::test]
12550async fn test_edits_around_expanded_insertion_hunks(
12551 executor: BackgroundExecutor,
12552 cx: &mut gpui::TestAppContext,
12553) {
12554 init_test(cx, |_| {});
12555
12556 let mut cx = EditorTestContext::new(cx).await;
12557
12558 let diff_base = r#"
12559 use some::mod1;
12560 use some::mod2;
12561
12562 const A: u32 = 42;
12563
12564 fn main() {
12565 println!("hello");
12566
12567 println!("world");
12568 }
12569 "#
12570 .unindent();
12571 executor.run_until_parked();
12572 cx.set_state(
12573 &r#"
12574 use some::mod1;
12575 use some::mod2;
12576
12577 const A: u32 = 42;
12578 const B: u32 = 42;
12579 const C: u32 = 42;
12580 ˇ
12581
12582 fn main() {
12583 println!("hello");
12584
12585 println!("world");
12586 }
12587 "#
12588 .unindent(),
12589 );
12590
12591 cx.set_diff_base(&diff_base);
12592 executor.run_until_parked();
12593
12594 cx.update_editor(|editor, cx| {
12595 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12596 });
12597 executor.run_until_parked();
12598
12599 cx.assert_state_with_diff(
12600 r#"
12601 use some::mod1;
12602 use some::mod2;
12603
12604 const A: u32 = 42;
12605 + const B: u32 = 42;
12606 + const C: u32 = 42;
12607 + ˇ
12608
12609 fn main() {
12610 println!("hello");
12611
12612 println!("world");
12613 }
12614 "#
12615 .unindent(),
12616 );
12617
12618 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
12619 executor.run_until_parked();
12620
12621 cx.assert_state_with_diff(
12622 r#"
12623 use some::mod1;
12624 use some::mod2;
12625
12626 const A: u32 = 42;
12627 + const B: u32 = 42;
12628 + const C: u32 = 42;
12629 + const D: u32 = 42;
12630 + ˇ
12631
12632 fn main() {
12633 println!("hello");
12634
12635 println!("world");
12636 }
12637 "#
12638 .unindent(),
12639 );
12640
12641 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
12642 executor.run_until_parked();
12643
12644 cx.assert_state_with_diff(
12645 r#"
12646 use some::mod1;
12647 use some::mod2;
12648
12649 const A: u32 = 42;
12650 + const B: u32 = 42;
12651 + const C: u32 = 42;
12652 + const D: u32 = 42;
12653 + const E: u32 = 42;
12654 + ˇ
12655
12656 fn main() {
12657 println!("hello");
12658
12659 println!("world");
12660 }
12661 "#
12662 .unindent(),
12663 );
12664
12665 cx.update_editor(|editor, cx| {
12666 editor.delete_line(&DeleteLine, cx);
12667 });
12668 executor.run_until_parked();
12669
12670 cx.assert_state_with_diff(
12671 r#"
12672 use some::mod1;
12673 use some::mod2;
12674
12675 const A: u32 = 42;
12676 + const B: u32 = 42;
12677 + const C: u32 = 42;
12678 + const D: u32 = 42;
12679 + const E: u32 = 42;
12680 ˇ
12681 fn main() {
12682 println!("hello");
12683
12684 println!("world");
12685 }
12686 "#
12687 .unindent(),
12688 );
12689
12690 cx.update_editor(|editor, cx| {
12691 editor.move_up(&MoveUp, cx);
12692 editor.delete_line(&DeleteLine, cx);
12693 editor.move_up(&MoveUp, cx);
12694 editor.delete_line(&DeleteLine, cx);
12695 editor.move_up(&MoveUp, cx);
12696 editor.delete_line(&DeleteLine, cx);
12697 });
12698 executor.run_until_parked();
12699 cx.assert_state_with_diff(
12700 r#"
12701 use some::mod1;
12702 use some::mod2;
12703
12704 const A: u32 = 42;
12705 + const B: u32 = 42;
12706 ˇ
12707 fn main() {
12708 println!("hello");
12709
12710 println!("world");
12711 }
12712 "#
12713 .unindent(),
12714 );
12715
12716 cx.update_editor(|editor, cx| {
12717 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
12718 editor.delete_line(&DeleteLine, cx);
12719 });
12720 executor.run_until_parked();
12721 cx.assert_state_with_diff(
12722 r#"
12723 use some::mod1;
12724 - use some::mod2;
12725 -
12726 - const A: u32 = 42;
12727 ˇ
12728 fn main() {
12729 println!("hello");
12730
12731 println!("world");
12732 }
12733 "#
12734 .unindent(),
12735 );
12736}
12737
12738#[gpui::test]
12739async fn test_edits_around_expanded_deletion_hunks(
12740 executor: BackgroundExecutor,
12741 cx: &mut gpui::TestAppContext,
12742) {
12743 init_test(cx, |_| {});
12744
12745 let mut cx = EditorTestContext::new(cx).await;
12746
12747 let diff_base = r#"
12748 use some::mod1;
12749 use some::mod2;
12750
12751 const A: u32 = 42;
12752 const B: u32 = 42;
12753 const C: u32 = 42;
12754
12755
12756 fn main() {
12757 println!("hello");
12758
12759 println!("world");
12760 }
12761 "#
12762 .unindent();
12763 executor.run_until_parked();
12764 cx.set_state(
12765 &r#"
12766 use some::mod1;
12767 use some::mod2;
12768
12769 ˇconst B: u32 = 42;
12770 const C: u32 = 42;
12771
12772
12773 fn main() {
12774 println!("hello");
12775
12776 println!("world");
12777 }
12778 "#
12779 .unindent(),
12780 );
12781
12782 cx.set_diff_base(&diff_base);
12783 executor.run_until_parked();
12784
12785 cx.update_editor(|editor, cx| {
12786 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12787 });
12788 executor.run_until_parked();
12789
12790 cx.assert_state_with_diff(
12791 r#"
12792 use some::mod1;
12793 use some::mod2;
12794
12795 - const A: u32 = 42;
12796 ˇconst B: u32 = 42;
12797 const C: u32 = 42;
12798
12799
12800 fn main() {
12801 println!("hello");
12802
12803 println!("world");
12804 }
12805 "#
12806 .unindent(),
12807 );
12808
12809 cx.update_editor(|editor, cx| {
12810 editor.delete_line(&DeleteLine, cx);
12811 });
12812 executor.run_until_parked();
12813 cx.assert_state_with_diff(
12814 r#"
12815 use some::mod1;
12816 use some::mod2;
12817
12818 - const A: u32 = 42;
12819 - const B: u32 = 42;
12820 ˇconst C: u32 = 42;
12821
12822
12823 fn main() {
12824 println!("hello");
12825
12826 println!("world");
12827 }
12828 "#
12829 .unindent(),
12830 );
12831
12832 cx.update_editor(|editor, cx| {
12833 editor.delete_line(&DeleteLine, cx);
12834 });
12835 executor.run_until_parked();
12836 cx.assert_state_with_diff(
12837 r#"
12838 use some::mod1;
12839 use some::mod2;
12840
12841 - const A: u32 = 42;
12842 - const B: u32 = 42;
12843 - const C: u32 = 42;
12844 ˇ
12845
12846 fn main() {
12847 println!("hello");
12848
12849 println!("world");
12850 }
12851 "#
12852 .unindent(),
12853 );
12854
12855 cx.update_editor(|editor, cx| {
12856 editor.handle_input("replacement", cx);
12857 });
12858 executor.run_until_parked();
12859 cx.assert_state_with_diff(
12860 r#"
12861 use some::mod1;
12862 use some::mod2;
12863
12864 - const A: u32 = 42;
12865 - const B: u32 = 42;
12866 - const C: u32 = 42;
12867 -
12868 + replacementˇ
12869
12870 fn main() {
12871 println!("hello");
12872
12873 println!("world");
12874 }
12875 "#
12876 .unindent(),
12877 );
12878}
12879
12880#[gpui::test]
12881async fn test_edit_after_expanded_modification_hunk(
12882 executor: BackgroundExecutor,
12883 cx: &mut gpui::TestAppContext,
12884) {
12885 init_test(cx, |_| {});
12886
12887 let mut cx = EditorTestContext::new(cx).await;
12888
12889 let diff_base = r#"
12890 use some::mod1;
12891 use some::mod2;
12892
12893 const A: u32 = 42;
12894 const B: u32 = 42;
12895 const C: u32 = 42;
12896 const D: u32 = 42;
12897
12898
12899 fn main() {
12900 println!("hello");
12901
12902 println!("world");
12903 }"#
12904 .unindent();
12905
12906 cx.set_state(
12907 &r#"
12908 use some::mod1;
12909 use some::mod2;
12910
12911 const A: u32 = 42;
12912 const B: u32 = 42;
12913 const C: u32 = 43ˇ
12914 const D: u32 = 42;
12915
12916
12917 fn main() {
12918 println!("hello");
12919
12920 println!("world");
12921 }"#
12922 .unindent(),
12923 );
12924
12925 cx.set_diff_base(&diff_base);
12926 executor.run_until_parked();
12927 cx.update_editor(|editor, cx| {
12928 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12929 });
12930 executor.run_until_parked();
12931
12932 cx.assert_state_with_diff(
12933 r#"
12934 use some::mod1;
12935 use some::mod2;
12936
12937 const A: u32 = 42;
12938 const B: u32 = 42;
12939 - const C: u32 = 42;
12940 + const C: u32 = 43ˇ
12941 const D: u32 = 42;
12942
12943
12944 fn main() {
12945 println!("hello");
12946
12947 println!("world");
12948 }"#
12949 .unindent(),
12950 );
12951
12952 cx.update_editor(|editor, cx| {
12953 editor.handle_input("\nnew_line\n", cx);
12954 });
12955 executor.run_until_parked();
12956
12957 cx.assert_state_with_diff(
12958 r#"
12959 use some::mod1;
12960 use some::mod2;
12961
12962 const A: u32 = 42;
12963 const B: u32 = 42;
12964 - const C: u32 = 42;
12965 + const C: u32 = 43
12966 + new_line
12967 + ˇ
12968 const D: u32 = 42;
12969
12970
12971 fn main() {
12972 println!("hello");
12973
12974 println!("world");
12975 }"#
12976 .unindent(),
12977 );
12978}
12979
12980async fn setup_indent_guides_editor(
12981 text: &str,
12982 cx: &mut gpui::TestAppContext,
12983) -> (BufferId, EditorTestContext) {
12984 init_test(cx, |_| {});
12985
12986 let mut cx = EditorTestContext::new(cx).await;
12987
12988 let buffer_id = cx.update_editor(|editor, cx| {
12989 editor.set_text(text, cx);
12990 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12991
12992 buffer_ids[0]
12993 });
12994
12995 (buffer_id, cx)
12996}
12997
12998fn assert_indent_guides(
12999 range: Range<u32>,
13000 expected: Vec<IndentGuide>,
13001 active_indices: Option<Vec<usize>>,
13002 cx: &mut EditorTestContext,
13003) {
13004 let indent_guides = cx.update_editor(|editor, cx| {
13005 let snapshot = editor.snapshot(cx).display_snapshot;
13006 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
13007 MultiBufferRow(range.start)..MultiBufferRow(range.end),
13008 true,
13009 &snapshot,
13010 cx,
13011 );
13012
13013 indent_guides.sort_by(|a, b| {
13014 a.depth.cmp(&b.depth).then(
13015 a.start_row
13016 .cmp(&b.start_row)
13017 .then(a.end_row.cmp(&b.end_row)),
13018 )
13019 });
13020 indent_guides
13021 });
13022
13023 if let Some(expected) = active_indices {
13024 let active_indices = cx.update_editor(|editor, cx| {
13025 let snapshot = editor.snapshot(cx).display_snapshot;
13026 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
13027 });
13028
13029 assert_eq!(
13030 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
13031 expected,
13032 "Active indent guide indices do not match"
13033 );
13034 }
13035
13036 let expected: Vec<_> = expected
13037 .into_iter()
13038 .map(|guide| MultiBufferIndentGuide {
13039 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
13040 buffer: guide,
13041 })
13042 .collect();
13043
13044 assert_eq!(indent_guides, expected, "Indent guides do not match");
13045}
13046
13047fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
13048 IndentGuide {
13049 buffer_id,
13050 start_row,
13051 end_row,
13052 depth,
13053 tab_size: 4,
13054 settings: IndentGuideSettings {
13055 enabled: true,
13056 line_width: 1,
13057 active_line_width: 1,
13058 ..Default::default()
13059 },
13060 }
13061}
13062
13063#[gpui::test]
13064async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13065 let (buffer_id, mut cx) = setup_indent_guides_editor(
13066 &"
13067 fn main() {
13068 let a = 1;
13069 }"
13070 .unindent(),
13071 cx,
13072 )
13073 .await;
13074
13075 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13076}
13077
13078#[gpui::test]
13079async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
13080 let (buffer_id, mut cx) = setup_indent_guides_editor(
13081 &"
13082 fn main() {
13083 let a = 1;
13084 let b = 2;
13085 }"
13086 .unindent(),
13087 cx,
13088 )
13089 .await;
13090
13091 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
13092}
13093
13094#[gpui::test]
13095async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
13096 let (buffer_id, mut cx) = setup_indent_guides_editor(
13097 &"
13098 fn main() {
13099 let a = 1;
13100 if a == 3 {
13101 let b = 2;
13102 } else {
13103 let c = 3;
13104 }
13105 }"
13106 .unindent(),
13107 cx,
13108 )
13109 .await;
13110
13111 assert_indent_guides(
13112 0..8,
13113 vec![
13114 indent_guide(buffer_id, 1, 6, 0),
13115 indent_guide(buffer_id, 3, 3, 1),
13116 indent_guide(buffer_id, 5, 5, 1),
13117 ],
13118 None,
13119 &mut cx,
13120 );
13121}
13122
13123#[gpui::test]
13124async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
13125 let (buffer_id, mut cx) = setup_indent_guides_editor(
13126 &"
13127 fn main() {
13128 let a = 1;
13129 let b = 2;
13130 let c = 3;
13131 }"
13132 .unindent(),
13133 cx,
13134 )
13135 .await;
13136
13137 assert_indent_guides(
13138 0..5,
13139 vec![
13140 indent_guide(buffer_id, 1, 3, 0),
13141 indent_guide(buffer_id, 2, 2, 1),
13142 ],
13143 None,
13144 &mut cx,
13145 );
13146}
13147
13148#[gpui::test]
13149async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
13150 let (buffer_id, mut cx) = setup_indent_guides_editor(
13151 &"
13152 fn main() {
13153 let a = 1;
13154
13155 let c = 3;
13156 }"
13157 .unindent(),
13158 cx,
13159 )
13160 .await;
13161
13162 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
13163}
13164
13165#[gpui::test]
13166async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
13167 let (buffer_id, mut cx) = setup_indent_guides_editor(
13168 &"
13169 fn main() {
13170 let a = 1;
13171
13172 let c = 3;
13173
13174 if a == 3 {
13175 let b = 2;
13176 } else {
13177 let c = 3;
13178 }
13179 }"
13180 .unindent(),
13181 cx,
13182 )
13183 .await;
13184
13185 assert_indent_guides(
13186 0..11,
13187 vec![
13188 indent_guide(buffer_id, 1, 9, 0),
13189 indent_guide(buffer_id, 6, 6, 1),
13190 indent_guide(buffer_id, 8, 8, 1),
13191 ],
13192 None,
13193 &mut cx,
13194 );
13195}
13196
13197#[gpui::test]
13198async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
13199 let (buffer_id, mut cx) = setup_indent_guides_editor(
13200 &"
13201 fn main() {
13202 let a = 1;
13203
13204 let c = 3;
13205
13206 if a == 3 {
13207 let b = 2;
13208 } else {
13209 let c = 3;
13210 }
13211 }"
13212 .unindent(),
13213 cx,
13214 )
13215 .await;
13216
13217 assert_indent_guides(
13218 1..11,
13219 vec![
13220 indent_guide(buffer_id, 1, 9, 0),
13221 indent_guide(buffer_id, 6, 6, 1),
13222 indent_guide(buffer_id, 8, 8, 1),
13223 ],
13224 None,
13225 &mut cx,
13226 );
13227}
13228
13229#[gpui::test]
13230async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
13231 let (buffer_id, mut cx) = setup_indent_guides_editor(
13232 &"
13233 fn main() {
13234 let a = 1;
13235
13236 let c = 3;
13237
13238 if a == 3 {
13239 let b = 2;
13240 } else {
13241 let c = 3;
13242 }
13243 }"
13244 .unindent(),
13245 cx,
13246 )
13247 .await;
13248
13249 assert_indent_guides(
13250 1..10,
13251 vec![
13252 indent_guide(buffer_id, 1, 9, 0),
13253 indent_guide(buffer_id, 6, 6, 1),
13254 indent_guide(buffer_id, 8, 8, 1),
13255 ],
13256 None,
13257 &mut cx,
13258 );
13259}
13260
13261#[gpui::test]
13262async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
13263 let (buffer_id, mut cx) = setup_indent_guides_editor(
13264 &"
13265 block1
13266 block2
13267 block3
13268 block4
13269 block2
13270 block1
13271 block1"
13272 .unindent(),
13273 cx,
13274 )
13275 .await;
13276
13277 assert_indent_guides(
13278 1..10,
13279 vec![
13280 indent_guide(buffer_id, 1, 4, 0),
13281 indent_guide(buffer_id, 2, 3, 1),
13282 indent_guide(buffer_id, 3, 3, 2),
13283 ],
13284 None,
13285 &mut cx,
13286 );
13287}
13288
13289#[gpui::test]
13290async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
13291 let (buffer_id, mut cx) = setup_indent_guides_editor(
13292 &"
13293 block1
13294 block2
13295 block3
13296
13297 block1
13298 block1"
13299 .unindent(),
13300 cx,
13301 )
13302 .await;
13303
13304 assert_indent_guides(
13305 0..6,
13306 vec![
13307 indent_guide(buffer_id, 1, 2, 0),
13308 indent_guide(buffer_id, 2, 2, 1),
13309 ],
13310 None,
13311 &mut cx,
13312 );
13313}
13314
13315#[gpui::test]
13316async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
13317 let (buffer_id, mut cx) = setup_indent_guides_editor(
13318 &"
13319 block1
13320
13321
13322
13323 block2
13324 "
13325 .unindent(),
13326 cx,
13327 )
13328 .await;
13329
13330 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13331}
13332
13333#[gpui::test]
13334async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
13335 let (buffer_id, mut cx) = setup_indent_guides_editor(
13336 &"
13337 def a:
13338 \tb = 3
13339 \tif True:
13340 \t\tc = 4
13341 \t\td = 5
13342 \tprint(b)
13343 "
13344 .unindent(),
13345 cx,
13346 )
13347 .await;
13348
13349 assert_indent_guides(
13350 0..6,
13351 vec![
13352 indent_guide(buffer_id, 1, 6, 0),
13353 indent_guide(buffer_id, 3, 4, 1),
13354 ],
13355 None,
13356 &mut cx,
13357 );
13358}
13359
13360#[gpui::test]
13361async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13362 let (buffer_id, mut cx) = setup_indent_guides_editor(
13363 &"
13364 fn main() {
13365 let a = 1;
13366 }"
13367 .unindent(),
13368 cx,
13369 )
13370 .await;
13371
13372 cx.update_editor(|editor, cx| {
13373 editor.change_selections(None, cx, |s| {
13374 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13375 });
13376 });
13377
13378 assert_indent_guides(
13379 0..3,
13380 vec![indent_guide(buffer_id, 1, 1, 0)],
13381 Some(vec![0]),
13382 &mut cx,
13383 );
13384}
13385
13386#[gpui::test]
13387async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13388 let (buffer_id, mut cx) = setup_indent_guides_editor(
13389 &"
13390 fn main() {
13391 if 1 == 2 {
13392 let a = 1;
13393 }
13394 }"
13395 .unindent(),
13396 cx,
13397 )
13398 .await;
13399
13400 cx.update_editor(|editor, cx| {
13401 editor.change_selections(None, cx, |s| {
13402 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13403 });
13404 });
13405
13406 assert_indent_guides(
13407 0..4,
13408 vec![
13409 indent_guide(buffer_id, 1, 3, 0),
13410 indent_guide(buffer_id, 2, 2, 1),
13411 ],
13412 Some(vec![1]),
13413 &mut cx,
13414 );
13415
13416 cx.update_editor(|editor, cx| {
13417 editor.change_selections(None, cx, |s| {
13418 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13419 });
13420 });
13421
13422 assert_indent_guides(
13423 0..4,
13424 vec![
13425 indent_guide(buffer_id, 1, 3, 0),
13426 indent_guide(buffer_id, 2, 2, 1),
13427 ],
13428 Some(vec![1]),
13429 &mut cx,
13430 );
13431
13432 cx.update_editor(|editor, cx| {
13433 editor.change_selections(None, cx, |s| {
13434 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13435 });
13436 });
13437
13438 assert_indent_guides(
13439 0..4,
13440 vec![
13441 indent_guide(buffer_id, 1, 3, 0),
13442 indent_guide(buffer_id, 2, 2, 1),
13443 ],
13444 Some(vec![0]),
13445 &mut cx,
13446 );
13447}
13448
13449#[gpui::test]
13450async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13451 let (buffer_id, mut cx) = setup_indent_guides_editor(
13452 &"
13453 fn main() {
13454 let a = 1;
13455
13456 let b = 2;
13457 }"
13458 .unindent(),
13459 cx,
13460 )
13461 .await;
13462
13463 cx.update_editor(|editor, cx| {
13464 editor.change_selections(None, cx, |s| {
13465 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13466 });
13467 });
13468
13469 assert_indent_guides(
13470 0..5,
13471 vec![indent_guide(buffer_id, 1, 3, 0)],
13472 Some(vec![0]),
13473 &mut cx,
13474 );
13475}
13476
13477#[gpui::test]
13478async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13479 let (buffer_id, mut cx) = setup_indent_guides_editor(
13480 &"
13481 def m:
13482 a = 1
13483 pass"
13484 .unindent(),
13485 cx,
13486 )
13487 .await;
13488
13489 cx.update_editor(|editor, cx| {
13490 editor.change_selections(None, cx, |s| {
13491 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13492 });
13493 });
13494
13495 assert_indent_guides(
13496 0..3,
13497 vec![indent_guide(buffer_id, 1, 2, 0)],
13498 Some(vec![0]),
13499 &mut cx,
13500 );
13501}
13502
13503#[gpui::test]
13504fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13505 init_test(cx, |_| {});
13506
13507 let editor = cx.add_window(|cx| {
13508 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13509 build_editor(buffer, cx)
13510 });
13511
13512 let render_args = Arc::new(Mutex::new(None));
13513 let snapshot = editor
13514 .update(cx, |editor, cx| {
13515 let snapshot = editor.buffer().read(cx).snapshot(cx);
13516 let range =
13517 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13518
13519 struct RenderArgs {
13520 row: MultiBufferRow,
13521 folded: bool,
13522 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13523 }
13524
13525 let crease = Crease::inline(
13526 range,
13527 FoldPlaceholder::test(),
13528 {
13529 let toggle_callback = render_args.clone();
13530 move |row, folded, callback, _cx| {
13531 *toggle_callback.lock() = Some(RenderArgs {
13532 row,
13533 folded,
13534 callback,
13535 });
13536 div()
13537 }
13538 },
13539 |_row, _folded, _cx| div(),
13540 );
13541
13542 editor.insert_creases(Some(crease), cx);
13543 let snapshot = editor.snapshot(cx);
13544 let _div =
13545 snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13546 snapshot
13547 })
13548 .unwrap();
13549
13550 let render_args = render_args.lock().take().unwrap();
13551 assert_eq!(render_args.row, MultiBufferRow(1));
13552 assert!(!render_args.folded);
13553 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13554
13555 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13556 .unwrap();
13557 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13558 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13559
13560 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13561 .unwrap();
13562 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13563 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13564}
13565
13566#[gpui::test]
13567async fn test_input_text(cx: &mut gpui::TestAppContext) {
13568 init_test(cx, |_| {});
13569 let mut cx = EditorTestContext::new(cx).await;
13570
13571 cx.set_state(
13572 &r#"ˇone
13573 two
13574
13575 three
13576 fourˇ
13577 five
13578
13579 siˇx"#
13580 .unindent(),
13581 );
13582
13583 cx.dispatch_action(HandleInput(String::new()));
13584 cx.assert_editor_state(
13585 &r#"ˇone
13586 two
13587
13588 three
13589 fourˇ
13590 five
13591
13592 siˇx"#
13593 .unindent(),
13594 );
13595
13596 cx.dispatch_action(HandleInput("AAAA".to_string()));
13597 cx.assert_editor_state(
13598 &r#"AAAAˇone
13599 two
13600
13601 three
13602 fourAAAAˇ
13603 five
13604
13605 siAAAAˇx"#
13606 .unindent(),
13607 );
13608}
13609
13610#[gpui::test]
13611async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13612 init_test(cx, |_| {});
13613
13614 let mut cx = EditorTestContext::new(cx).await;
13615 cx.set_state(
13616 r#"let foo = 1;
13617let foo = 2;
13618let foo = 3;
13619let fooˇ = 4;
13620let foo = 5;
13621let foo = 6;
13622let foo = 7;
13623let foo = 8;
13624let foo = 9;
13625let foo = 10;
13626let foo = 11;
13627let foo = 12;
13628let foo = 13;
13629let foo = 14;
13630let foo = 15;"#,
13631 );
13632
13633 cx.update_editor(|e, cx| {
13634 assert_eq!(
13635 e.next_scroll_position,
13636 NextScrollCursorCenterTopBottom::Center,
13637 "Default next scroll direction is center",
13638 );
13639
13640 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13641 assert_eq!(
13642 e.next_scroll_position,
13643 NextScrollCursorCenterTopBottom::Top,
13644 "After center, next scroll direction should be top",
13645 );
13646
13647 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13648 assert_eq!(
13649 e.next_scroll_position,
13650 NextScrollCursorCenterTopBottom::Bottom,
13651 "After top, next scroll direction should be bottom",
13652 );
13653
13654 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13655 assert_eq!(
13656 e.next_scroll_position,
13657 NextScrollCursorCenterTopBottom::Center,
13658 "After bottom, scrolling should start over",
13659 );
13660
13661 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13662 assert_eq!(
13663 e.next_scroll_position,
13664 NextScrollCursorCenterTopBottom::Top,
13665 "Scrolling continues if retriggered fast enough"
13666 );
13667 });
13668
13669 cx.executor()
13670 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13671 cx.executor().run_until_parked();
13672 cx.update_editor(|e, _| {
13673 assert_eq!(
13674 e.next_scroll_position,
13675 NextScrollCursorCenterTopBottom::Center,
13676 "If scrolling is not triggered fast enough, it should reset"
13677 );
13678 });
13679}
13680
13681#[gpui::test]
13682async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13683 init_test(cx, |_| {});
13684 let mut cx = EditorLspTestContext::new_rust(
13685 lsp::ServerCapabilities {
13686 definition_provider: Some(lsp::OneOf::Left(true)),
13687 references_provider: Some(lsp::OneOf::Left(true)),
13688 ..lsp::ServerCapabilities::default()
13689 },
13690 cx,
13691 )
13692 .await;
13693
13694 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13695 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13696 move |params, _| async move {
13697 if empty_go_to_definition {
13698 Ok(None)
13699 } else {
13700 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13701 uri: params.text_document_position_params.text_document.uri,
13702 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13703 })))
13704 }
13705 },
13706 );
13707 let references =
13708 cx.lsp
13709 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13710 Ok(Some(vec![lsp::Location {
13711 uri: params.text_document_position.text_document.uri,
13712 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13713 }]))
13714 });
13715 (go_to_definition, references)
13716 };
13717
13718 cx.set_state(
13719 &r#"fn one() {
13720 let mut a = ˇtwo();
13721 }
13722
13723 fn two() {}"#
13724 .unindent(),
13725 );
13726 set_up_lsp_handlers(false, &mut cx);
13727 let navigated = cx
13728 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13729 .await
13730 .expect("Failed to navigate to definition");
13731 assert_eq!(
13732 navigated,
13733 Navigated::Yes,
13734 "Should have navigated to definition from the GetDefinition response"
13735 );
13736 cx.assert_editor_state(
13737 &r#"fn one() {
13738 let mut a = two();
13739 }
13740
13741 fn «twoˇ»() {}"#
13742 .unindent(),
13743 );
13744
13745 let editors = cx.update_workspace(|workspace, cx| {
13746 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13747 });
13748 cx.update_editor(|_, test_editor_cx| {
13749 assert_eq!(
13750 editors.len(),
13751 1,
13752 "Initially, only one, test, editor should be open in the workspace"
13753 );
13754 assert_eq!(
13755 test_editor_cx.view(),
13756 editors.last().expect("Asserted len is 1")
13757 );
13758 });
13759
13760 set_up_lsp_handlers(true, &mut cx);
13761 let navigated = cx
13762 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13763 .await
13764 .expect("Failed to navigate to lookup references");
13765 assert_eq!(
13766 navigated,
13767 Navigated::Yes,
13768 "Should have navigated to references as a fallback after empty GoToDefinition response"
13769 );
13770 // We should not change the selections in the existing file,
13771 // if opening another milti buffer with the references
13772 cx.assert_editor_state(
13773 &r#"fn one() {
13774 let mut a = two();
13775 }
13776
13777 fn «twoˇ»() {}"#
13778 .unindent(),
13779 );
13780 let editors = cx.update_workspace(|workspace, cx| {
13781 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13782 });
13783 cx.update_editor(|_, test_editor_cx| {
13784 assert_eq!(
13785 editors.len(),
13786 2,
13787 "After falling back to references search, we open a new editor with the results"
13788 );
13789 let references_fallback_text = editors
13790 .into_iter()
13791 .find(|new_editor| new_editor != test_editor_cx.view())
13792 .expect("Should have one non-test editor now")
13793 .read(test_editor_cx)
13794 .text(test_editor_cx);
13795 assert_eq!(
13796 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13797 "Should use the range from the references response and not the GoToDefinition one"
13798 );
13799 });
13800}
13801
13802#[gpui::test]
13803async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
13804 init_test(cx, |_| {});
13805
13806 let language = Arc::new(Language::new(
13807 LanguageConfig::default(),
13808 Some(tree_sitter_rust::LANGUAGE.into()),
13809 ));
13810
13811 let text = r#"
13812 #[cfg(test)]
13813 mod tests() {
13814 #[test]
13815 fn runnable_1() {
13816 let a = 1;
13817 }
13818
13819 #[test]
13820 fn runnable_2() {
13821 let a = 1;
13822 let b = 2;
13823 }
13824 }
13825 "#
13826 .unindent();
13827
13828 let fs = FakeFs::new(cx.executor());
13829 fs.insert_file("/file.rs", Default::default()).await;
13830
13831 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13832 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
13833 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13834 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
13835 let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
13836
13837 let editor = cx.new_view(|cx| {
13838 Editor::new(
13839 EditorMode::Full,
13840 multi_buffer,
13841 Some(project.clone()),
13842 true,
13843 cx,
13844 )
13845 });
13846
13847 editor.update(cx, |editor, cx| {
13848 editor.tasks.insert(
13849 (buffer.read(cx).remote_id(), 3),
13850 RunnableTasks {
13851 templates: vec![],
13852 offset: MultiBufferOffset(43),
13853 column: 0,
13854 extra_variables: HashMap::default(),
13855 context_range: BufferOffset(43)..BufferOffset(85),
13856 },
13857 );
13858 editor.tasks.insert(
13859 (buffer.read(cx).remote_id(), 8),
13860 RunnableTasks {
13861 templates: vec![],
13862 offset: MultiBufferOffset(86),
13863 column: 0,
13864 extra_variables: HashMap::default(),
13865 context_range: BufferOffset(86)..BufferOffset(191),
13866 },
13867 );
13868
13869 // Test finding task when cursor is inside function body
13870 editor.change_selections(None, cx, |s| {
13871 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
13872 });
13873 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13874 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
13875
13876 // Test finding task when cursor is on function name
13877 editor.change_selections(None, cx, |s| {
13878 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
13879 });
13880 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13881 assert_eq!(row, 8, "Should find task when cursor is on function name");
13882 });
13883}
13884
13885fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13886 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13887 point..point
13888}
13889
13890fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13891 let (text, ranges) = marked_text_ranges(marked_text, true);
13892 assert_eq!(view.text(cx), text);
13893 assert_eq!(
13894 view.selections.ranges(cx),
13895 ranges,
13896 "Assert selections are {}",
13897 marked_text
13898 );
13899}
13900
13901pub fn handle_signature_help_request(
13902 cx: &mut EditorLspTestContext,
13903 mocked_response: lsp::SignatureHelp,
13904) -> impl Future<Output = ()> {
13905 let mut request =
13906 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13907 let mocked_response = mocked_response.clone();
13908 async move { Ok(Some(mocked_response)) }
13909 });
13910
13911 async move {
13912 request.next().await;
13913 }
13914}
13915
13916/// Handle completion request passing a marked string specifying where the completion
13917/// should be triggered from using '|' character, what range should be replaced, and what completions
13918/// should be returned using '<' and '>' to delimit the range
13919pub fn handle_completion_request(
13920 cx: &mut EditorLspTestContext,
13921 marked_string: &str,
13922 completions: Vec<&'static str>,
13923 counter: Arc<AtomicUsize>,
13924) -> impl Future<Output = ()> {
13925 let complete_from_marker: TextRangeMarker = '|'.into();
13926 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13927 let (_, mut marked_ranges) = marked_text_ranges_by(
13928 marked_string,
13929 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13930 );
13931
13932 let complete_from_position =
13933 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13934 let replace_range =
13935 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13936
13937 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13938 let completions = completions.clone();
13939 counter.fetch_add(1, atomic::Ordering::Release);
13940 async move {
13941 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13942 assert_eq!(
13943 params.text_document_position.position,
13944 complete_from_position
13945 );
13946 Ok(Some(lsp::CompletionResponse::Array(
13947 completions
13948 .iter()
13949 .map(|completion_text| lsp::CompletionItem {
13950 label: completion_text.to_string(),
13951 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13952 range: replace_range,
13953 new_text: completion_text.to_string(),
13954 })),
13955 ..Default::default()
13956 })
13957 .collect(),
13958 )))
13959 }
13960 });
13961
13962 async move {
13963 request.next().await;
13964 }
13965}
13966
13967fn handle_resolve_completion_request(
13968 cx: &mut EditorLspTestContext,
13969 edits: Option<Vec<(&'static str, &'static str)>>,
13970) -> impl Future<Output = ()> {
13971 let edits = edits.map(|edits| {
13972 edits
13973 .iter()
13974 .map(|(marked_string, new_text)| {
13975 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13976 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13977 lsp::TextEdit::new(replace_range, new_text.to_string())
13978 })
13979 .collect::<Vec<_>>()
13980 });
13981
13982 let mut request =
13983 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13984 let edits = edits.clone();
13985 async move {
13986 Ok(lsp::CompletionItem {
13987 additional_text_edits: edits,
13988 ..Default::default()
13989 })
13990 }
13991 });
13992
13993 async move {
13994 request.next().await;
13995 }
13996}
13997
13998pub(crate) fn update_test_language_settings(
13999 cx: &mut TestAppContext,
14000 f: impl Fn(&mut AllLanguageSettingsContent),
14001) {
14002 cx.update(|cx| {
14003 SettingsStore::update_global(cx, |store, cx| {
14004 store.update_user_settings::<AllLanguageSettings>(cx, f);
14005 });
14006 });
14007}
14008
14009pub(crate) fn update_test_project_settings(
14010 cx: &mut TestAppContext,
14011 f: impl Fn(&mut ProjectSettings),
14012) {
14013 cx.update(|cx| {
14014 SettingsStore::update_global(cx, |store, cx| {
14015 store.update_user_settings::<ProjectSettings>(cx, f);
14016 });
14017 });
14018}
14019
14020pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
14021 cx.update(|cx| {
14022 assets::Assets.load_test_fonts(cx);
14023 let store = SettingsStore::test(cx);
14024 cx.set_global(store);
14025 theme::init(theme::LoadThemes::JustBase, cx);
14026 release_channel::init(SemanticVersion::default(), cx);
14027 client::init_settings(cx);
14028 language::init(cx);
14029 Project::init_settings(cx);
14030 workspace::init_settings(cx);
14031 crate::init(cx);
14032 });
14033
14034 update_test_language_settings(cx, f);
14035}
14036
14037#[track_caller]
14038fn assert_hunk_revert(
14039 not_reverted_text_with_selections: &str,
14040 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
14041 expected_reverted_text_with_selections: &str,
14042 base_text: &str,
14043 cx: &mut EditorLspTestContext,
14044) {
14045 cx.set_state(not_reverted_text_with_selections);
14046 cx.set_diff_base(base_text);
14047 cx.executor().run_until_parked();
14048
14049 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
14050 let snapshot = editor.snapshot(cx);
14051 let reverted_hunk_statuses = snapshot
14052 .diff_map
14053 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
14054 .map(|hunk| hunk_status(&hunk))
14055 .collect::<Vec<_>>();
14056
14057 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
14058 reverted_hunk_statuses
14059 });
14060 cx.executor().run_until_parked();
14061 cx.assert_editor_state(expected_reverted_text_with_selections);
14062 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
14063}