1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
6 editor_test_context::EditorTestContext, select_ranges,
7 },
8 JoinLines,
9};
10use futures::StreamExt;
11use gpui::{
12 div, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds,
13 WindowOptions,
14};
15use indoc::indoc;
16use language::{
17 language_settings::{
18 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
19 },
20 BracketPairConfig,
21 Capability::ReadWrite,
22 FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
23 LanguageName, Override, ParsedMarkdown, Point,
24};
25use language_settings::{Formatter, FormatterList, IndentGuideSettings};
26use multi_buffer::MultiBufferIndentGuide;
27use parking_lot::Mutex;
28use project::FakeFs;
29use project::{
30 lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
31 project_settings::{LspSettings, ProjectSettings},
32};
33use serde_json::{self, json};
34use std::sync::atomic::AtomicUsize;
35use std::sync::atomic::{self, AtomicBool};
36use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
37use 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(Some(&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(Some("Line 0\r\nLine 1\r\nLine 2\r\nLine 3"));
3357 executor.run_until_parked();
3358
3359 cx.update_editor(|editor, cx| {
3360 assert_eq!(
3361 editor
3362 .buffer()
3363 .read(cx)
3364 .snapshot(cx)
3365 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
3366 .collect::<Vec<_>>(),
3367 Vec::new(),
3368 "Should not have any diffs for files with custom newlines"
3369 );
3370 });
3371}
3372
3373#[gpui::test]
3374async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3375 init_test(cx, |_| {});
3376
3377 let mut cx = EditorTestContext::new(cx).await;
3378
3379 // Test sort_lines_case_insensitive()
3380 cx.set_state(indoc! {"
3381 «z
3382 y
3383 x
3384 Z
3385 Y
3386 Xˇ»
3387 "});
3388 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3389 cx.assert_editor_state(indoc! {"
3390 «x
3391 X
3392 y
3393 Y
3394 z
3395 Zˇ»
3396 "});
3397
3398 // Test reverse_lines()
3399 cx.set_state(indoc! {"
3400 «5
3401 4
3402 3
3403 2
3404 1ˇ»
3405 "});
3406 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3407 cx.assert_editor_state(indoc! {"
3408 «1
3409 2
3410 3
3411 4
3412 5ˇ»
3413 "});
3414
3415 // Skip testing shuffle_line()
3416
3417 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3418 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3419
3420 // Don't manipulate when cursor is on single line, but expand the selection
3421 cx.set_state(indoc! {"
3422 ddˇdd
3423 ccc
3424 bb
3425 a
3426 "});
3427 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3428 cx.assert_editor_state(indoc! {"
3429 «ddddˇ»
3430 ccc
3431 bb
3432 a
3433 "});
3434
3435 // Basic manipulate case
3436 // Start selection moves to column 0
3437 // End of selection shrinks to fit shorter line
3438 cx.set_state(indoc! {"
3439 dd«d
3440 ccc
3441 bb
3442 aaaaaˇ»
3443 "});
3444 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3445 cx.assert_editor_state(indoc! {"
3446 «aaaaa
3447 bb
3448 ccc
3449 dddˇ»
3450 "});
3451
3452 // Manipulate case with newlines
3453 cx.set_state(indoc! {"
3454 dd«d
3455 ccc
3456
3457 bb
3458 aaaaa
3459
3460 ˇ»
3461 "});
3462 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3463 cx.assert_editor_state(indoc! {"
3464 «
3465
3466 aaaaa
3467 bb
3468 ccc
3469 dddˇ»
3470
3471 "});
3472
3473 // Adding new line
3474 cx.set_state(indoc! {"
3475 aa«a
3476 bbˇ»b
3477 "});
3478 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3479 cx.assert_editor_state(indoc! {"
3480 «aaa
3481 bbb
3482 added_lineˇ»
3483 "});
3484
3485 // Removing line
3486 cx.set_state(indoc! {"
3487 aa«a
3488 bbbˇ»
3489 "});
3490 cx.update_editor(|e, cx| {
3491 e.manipulate_lines(cx, |lines| {
3492 lines.pop();
3493 })
3494 });
3495 cx.assert_editor_state(indoc! {"
3496 «aaaˇ»
3497 "});
3498
3499 // Removing all lines
3500 cx.set_state(indoc! {"
3501 aa«a
3502 bbbˇ»
3503 "});
3504 cx.update_editor(|e, cx| {
3505 e.manipulate_lines(cx, |lines| {
3506 lines.drain(..);
3507 })
3508 });
3509 cx.assert_editor_state(indoc! {"
3510 ˇ
3511 "});
3512}
3513
3514#[gpui::test]
3515async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3516 init_test(cx, |_| {});
3517
3518 let mut cx = EditorTestContext::new(cx).await;
3519
3520 // Consider continuous selection as single selection
3521 cx.set_state(indoc! {"
3522 Aaa«aa
3523 cˇ»c«c
3524 bb
3525 aaaˇ»aa
3526 "});
3527 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3528 cx.assert_editor_state(indoc! {"
3529 «Aaaaa
3530 ccc
3531 bb
3532 aaaaaˇ»
3533 "});
3534
3535 cx.set_state(indoc! {"
3536 Aaa«aa
3537 cˇ»c«c
3538 bb
3539 aaaˇ»aa
3540 "});
3541 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3542 cx.assert_editor_state(indoc! {"
3543 «Aaaaa
3544 ccc
3545 bbˇ»
3546 "});
3547
3548 // Consider non continuous selection as distinct dedup operations
3549 cx.set_state(indoc! {"
3550 «aaaaa
3551 bb
3552 aaaaa
3553 aaaaaˇ»
3554
3555 aaa«aaˇ»
3556 "});
3557 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3558 cx.assert_editor_state(indoc! {"
3559 «aaaaa
3560 bbˇ»
3561
3562 «aaaaaˇ»
3563 "});
3564}
3565
3566#[gpui::test]
3567async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3568 init_test(cx, |_| {});
3569
3570 let mut cx = EditorTestContext::new(cx).await;
3571
3572 cx.set_state(indoc! {"
3573 «Aaa
3574 aAa
3575 Aaaˇ»
3576 "});
3577 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3578 cx.assert_editor_state(indoc! {"
3579 «Aaa
3580 aAaˇ»
3581 "});
3582
3583 cx.set_state(indoc! {"
3584 «Aaa
3585 aAa
3586 aaAˇ»
3587 "});
3588 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3589 cx.assert_editor_state(indoc! {"
3590 «Aaaˇ»
3591 "});
3592}
3593
3594#[gpui::test]
3595async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3596 init_test(cx, |_| {});
3597
3598 let mut cx = EditorTestContext::new(cx).await;
3599
3600 // Manipulate with multiple selections on a single line
3601 cx.set_state(indoc! {"
3602 dd«dd
3603 cˇ»c«c
3604 bb
3605 aaaˇ»aa
3606 "});
3607 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3608 cx.assert_editor_state(indoc! {"
3609 «aaaaa
3610 bb
3611 ccc
3612 ddddˇ»
3613 "});
3614
3615 // Manipulate with multiple disjoin selections
3616 cx.set_state(indoc! {"
3617 5«
3618 4
3619 3
3620 2
3621 1ˇ»
3622
3623 dd«dd
3624 ccc
3625 bb
3626 aaaˇ»aa
3627 "});
3628 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3629 cx.assert_editor_state(indoc! {"
3630 «1
3631 2
3632 3
3633 4
3634 5ˇ»
3635
3636 «aaaaa
3637 bb
3638 ccc
3639 ddddˇ»
3640 "});
3641
3642 // Adding lines on each selection
3643 cx.set_state(indoc! {"
3644 2«
3645 1ˇ»
3646
3647 bb«bb
3648 aaaˇ»aa
3649 "});
3650 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3651 cx.assert_editor_state(indoc! {"
3652 «2
3653 1
3654 added lineˇ»
3655
3656 «bbbb
3657 aaaaa
3658 added lineˇ»
3659 "});
3660
3661 // Removing lines on each selection
3662 cx.set_state(indoc! {"
3663 2«
3664 1ˇ»
3665
3666 bb«bb
3667 aaaˇ»aa
3668 "});
3669 cx.update_editor(|e, cx| {
3670 e.manipulate_lines(cx, |lines| {
3671 lines.pop();
3672 })
3673 });
3674 cx.assert_editor_state(indoc! {"
3675 «2ˇ»
3676
3677 «bbbbˇ»
3678 "});
3679}
3680
3681#[gpui::test]
3682async fn test_manipulate_text(cx: &mut TestAppContext) {
3683 init_test(cx, |_| {});
3684
3685 let mut cx = EditorTestContext::new(cx).await;
3686
3687 // Test convert_to_upper_case()
3688 cx.set_state(indoc! {"
3689 «hello worldˇ»
3690 "});
3691 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3692 cx.assert_editor_state(indoc! {"
3693 «HELLO WORLDˇ»
3694 "});
3695
3696 // Test convert_to_lower_case()
3697 cx.set_state(indoc! {"
3698 «HELLO WORLDˇ»
3699 "});
3700 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3701 cx.assert_editor_state(indoc! {"
3702 «hello worldˇ»
3703 "});
3704
3705 // Test multiple line, single selection case
3706 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3707 cx.set_state(indoc! {"
3708 «The quick brown
3709 fox jumps over
3710 the lazy dogˇ»
3711 "});
3712 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3713 cx.assert_editor_state(indoc! {"
3714 «The Quick Brown
3715 Fox Jumps Over
3716 The Lazy Dogˇ»
3717 "});
3718
3719 // Test multiple line, single selection case
3720 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3721 cx.set_state(indoc! {"
3722 «The quick brown
3723 fox jumps over
3724 the lazy dogˇ»
3725 "});
3726 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3727 cx.assert_editor_state(indoc! {"
3728 «TheQuickBrown
3729 FoxJumpsOver
3730 TheLazyDogˇ»
3731 "});
3732
3733 // From here on out, test more complex cases of manipulate_text()
3734
3735 // Test no selection case - should affect words cursors are in
3736 // Cursor at beginning, middle, and end of word
3737 cx.set_state(indoc! {"
3738 ˇhello big beauˇtiful worldˇ
3739 "});
3740 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3741 cx.assert_editor_state(indoc! {"
3742 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3743 "});
3744
3745 // Test multiple selections on a single line and across multiple lines
3746 cx.set_state(indoc! {"
3747 «Theˇ» quick «brown
3748 foxˇ» jumps «overˇ»
3749 the «lazyˇ» dog
3750 "});
3751 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3752 cx.assert_editor_state(indoc! {"
3753 «THEˇ» quick «BROWN
3754 FOXˇ» jumps «OVERˇ»
3755 the «LAZYˇ» dog
3756 "});
3757
3758 // Test case where text length grows
3759 cx.set_state(indoc! {"
3760 «tschüߡ»
3761 "});
3762 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3763 cx.assert_editor_state(indoc! {"
3764 «TSCHÜSSˇ»
3765 "});
3766
3767 // Test to make sure we don't crash when text shrinks
3768 cx.set_state(indoc! {"
3769 aaa_bbbˇ
3770 "});
3771 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3772 cx.assert_editor_state(indoc! {"
3773 «aaaBbbˇ»
3774 "});
3775
3776 // Test to make sure we all aware of the fact that each word can grow and shrink
3777 // Final selections should be aware of this fact
3778 cx.set_state(indoc! {"
3779 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3780 "});
3781 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3782 cx.assert_editor_state(indoc! {"
3783 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3784 "});
3785
3786 cx.set_state(indoc! {"
3787 «hElLo, WoRld!ˇ»
3788 "});
3789 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3790 cx.assert_editor_state(indoc! {"
3791 «HeLlO, wOrLD!ˇ»
3792 "});
3793}
3794
3795#[gpui::test]
3796fn test_duplicate_line(cx: &mut TestAppContext) {
3797 init_test(cx, |_| {});
3798
3799 let view = cx.add_window(|cx| {
3800 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3801 build_editor(buffer, cx)
3802 });
3803 _ = view.update(cx, |view, cx| {
3804 view.change_selections(None, cx, |s| {
3805 s.select_display_ranges([
3806 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3807 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3808 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3809 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3810 ])
3811 });
3812 view.duplicate_line_down(&DuplicateLineDown, cx);
3813 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3814 assert_eq!(
3815 view.selections.display_ranges(cx),
3816 vec![
3817 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3818 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3819 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3820 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3821 ]
3822 );
3823 });
3824
3825 let view = cx.add_window(|cx| {
3826 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3827 build_editor(buffer, cx)
3828 });
3829 _ = view.update(cx, |view, cx| {
3830 view.change_selections(None, cx, |s| {
3831 s.select_display_ranges([
3832 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3833 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3834 ])
3835 });
3836 view.duplicate_line_down(&DuplicateLineDown, cx);
3837 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3838 assert_eq!(
3839 view.selections.display_ranges(cx),
3840 vec![
3841 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3842 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3843 ]
3844 );
3845 });
3846
3847 // With `move_upwards` the selections stay in place, except for
3848 // the lines inserted above them
3849 let view = cx.add_window(|cx| {
3850 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3851 build_editor(buffer, cx)
3852 });
3853 _ = view.update(cx, |view, cx| {
3854 view.change_selections(None, cx, |s| {
3855 s.select_display_ranges([
3856 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3857 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3858 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3859 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3860 ])
3861 });
3862 view.duplicate_line_up(&DuplicateLineUp, cx);
3863 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3864 assert_eq!(
3865 view.selections.display_ranges(cx),
3866 vec![
3867 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3868 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3869 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3870 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3871 ]
3872 );
3873 });
3874
3875 let view = cx.add_window(|cx| {
3876 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3877 build_editor(buffer, cx)
3878 });
3879 _ = view.update(cx, |view, cx| {
3880 view.change_selections(None, cx, |s| {
3881 s.select_display_ranges([
3882 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3883 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3884 ])
3885 });
3886 view.duplicate_line_up(&DuplicateLineUp, cx);
3887 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3888 assert_eq!(
3889 view.selections.display_ranges(cx),
3890 vec![
3891 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3892 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3893 ]
3894 );
3895 });
3896}
3897
3898#[gpui::test]
3899fn test_move_line_up_down(cx: &mut TestAppContext) {
3900 init_test(cx, |_| {});
3901
3902 let view = cx.add_window(|cx| {
3903 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3904 build_editor(buffer, cx)
3905 });
3906 _ = view.update(cx, |view, cx| {
3907 view.fold_creases(
3908 vec![
3909 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3910 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3911 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3912 ],
3913 true,
3914 cx,
3915 );
3916 view.change_selections(None, cx, |s| {
3917 s.select_display_ranges([
3918 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3919 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3920 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3921 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3922 ])
3923 });
3924 assert_eq!(
3925 view.display_text(cx),
3926 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3927 );
3928
3929 view.move_line_up(&MoveLineUp, cx);
3930 assert_eq!(
3931 view.display_text(cx),
3932 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3933 );
3934 assert_eq!(
3935 view.selections.display_ranges(cx),
3936 vec![
3937 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3938 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3939 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3940 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3941 ]
3942 );
3943 });
3944
3945 _ = view.update(cx, |view, cx| {
3946 view.move_line_down(&MoveLineDown, cx);
3947 assert_eq!(
3948 view.display_text(cx),
3949 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3950 );
3951 assert_eq!(
3952 view.selections.display_ranges(cx),
3953 vec![
3954 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3955 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3956 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3957 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3958 ]
3959 );
3960 });
3961
3962 _ = view.update(cx, |view, cx| {
3963 view.move_line_down(&MoveLineDown, cx);
3964 assert_eq!(
3965 view.display_text(cx),
3966 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3967 );
3968 assert_eq!(
3969 view.selections.display_ranges(cx),
3970 vec![
3971 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3972 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3973 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3974 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3975 ]
3976 );
3977 });
3978
3979 _ = view.update(cx, |view, cx| {
3980 view.move_line_up(&MoveLineUp, cx);
3981 assert_eq!(
3982 view.display_text(cx),
3983 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3984 );
3985 assert_eq!(
3986 view.selections.display_ranges(cx),
3987 vec![
3988 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3989 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3990 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3991 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3992 ]
3993 );
3994 });
3995}
3996
3997#[gpui::test]
3998fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3999 init_test(cx, |_| {});
4000
4001 let editor = cx.add_window(|cx| {
4002 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4003 build_editor(buffer, cx)
4004 });
4005 _ = editor.update(cx, |editor, cx| {
4006 let snapshot = editor.buffer.read(cx).snapshot(cx);
4007 editor.insert_blocks(
4008 [BlockProperties {
4009 style: BlockStyle::Fixed,
4010 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4011 height: 1,
4012 render: Arc::new(|_| div().into_any()),
4013 priority: 0,
4014 }],
4015 Some(Autoscroll::fit()),
4016 cx,
4017 );
4018 editor.change_selections(None, cx, |s| {
4019 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4020 });
4021 editor.move_line_down(&MoveLineDown, cx);
4022 });
4023}
4024
4025#[gpui::test]
4026async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4027 init_test(cx, |_| {});
4028
4029 let mut cx = EditorTestContext::new(cx).await;
4030 cx.set_state(
4031 &"
4032 ˇzero
4033 one
4034 two
4035 three
4036 four
4037 five
4038 "
4039 .unindent(),
4040 );
4041
4042 // Create a four-line block that replaces three lines of text.
4043 cx.update_editor(|editor, cx| {
4044 let snapshot = editor.snapshot(cx);
4045 let snapshot = &snapshot.buffer_snapshot;
4046 let placement = BlockPlacement::Replace(
4047 snapshot.anchor_after(Point::new(1, 0))..snapshot.anchor_after(Point::new(3, 0)),
4048 );
4049 editor.insert_blocks(
4050 [BlockProperties {
4051 placement,
4052 height: 4,
4053 style: BlockStyle::Sticky,
4054 render: Arc::new(|_| gpui::div().into_any_element()),
4055 priority: 0,
4056 }],
4057 None,
4058 cx,
4059 );
4060 });
4061
4062 // Move down so that the cursor touches the block.
4063 cx.update_editor(|editor, cx| {
4064 editor.move_down(&Default::default(), cx);
4065 });
4066 cx.assert_editor_state(
4067 &"
4068 zero
4069 «one
4070 two
4071 threeˇ»
4072 four
4073 five
4074 "
4075 .unindent(),
4076 );
4077
4078 // Move down past the block.
4079 cx.update_editor(|editor, cx| {
4080 editor.move_down(&Default::default(), cx);
4081 });
4082 cx.assert_editor_state(
4083 &"
4084 zero
4085 one
4086 two
4087 three
4088 ˇfour
4089 five
4090 "
4091 .unindent(),
4092 );
4093}
4094
4095#[gpui::test]
4096fn test_transpose(cx: &mut TestAppContext) {
4097 init_test(cx, |_| {});
4098
4099 _ = cx.add_window(|cx| {
4100 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
4101 editor.set_style(EditorStyle::default(), cx);
4102 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
4103 editor.transpose(&Default::default(), cx);
4104 assert_eq!(editor.text(cx), "bac");
4105 assert_eq!(editor.selections.ranges(cx), [2..2]);
4106
4107 editor.transpose(&Default::default(), cx);
4108 assert_eq!(editor.text(cx), "bca");
4109 assert_eq!(editor.selections.ranges(cx), [3..3]);
4110
4111 editor.transpose(&Default::default(), cx);
4112 assert_eq!(editor.text(cx), "bac");
4113 assert_eq!(editor.selections.ranges(cx), [3..3]);
4114
4115 editor
4116 });
4117
4118 _ = cx.add_window(|cx| {
4119 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4120 editor.set_style(EditorStyle::default(), cx);
4121 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
4122 editor.transpose(&Default::default(), cx);
4123 assert_eq!(editor.text(cx), "acb\nde");
4124 assert_eq!(editor.selections.ranges(cx), [3..3]);
4125
4126 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4127 editor.transpose(&Default::default(), cx);
4128 assert_eq!(editor.text(cx), "acbd\ne");
4129 assert_eq!(editor.selections.ranges(cx), [5..5]);
4130
4131 editor.transpose(&Default::default(), cx);
4132 assert_eq!(editor.text(cx), "acbde\n");
4133 assert_eq!(editor.selections.ranges(cx), [6..6]);
4134
4135 editor.transpose(&Default::default(), cx);
4136 assert_eq!(editor.text(cx), "acbd\ne");
4137 assert_eq!(editor.selections.ranges(cx), [6..6]);
4138
4139 editor
4140 });
4141
4142 _ = cx.add_window(|cx| {
4143 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4144 editor.set_style(EditorStyle::default(), cx);
4145 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4146 editor.transpose(&Default::default(), cx);
4147 assert_eq!(editor.text(cx), "bacd\ne");
4148 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4149
4150 editor.transpose(&Default::default(), cx);
4151 assert_eq!(editor.text(cx), "bcade\n");
4152 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4153
4154 editor.transpose(&Default::default(), cx);
4155 assert_eq!(editor.text(cx), "bcda\ne");
4156 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4157
4158 editor.transpose(&Default::default(), cx);
4159 assert_eq!(editor.text(cx), "bcade\n");
4160 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4161
4162 editor.transpose(&Default::default(), cx);
4163 assert_eq!(editor.text(cx), "bcaed\n");
4164 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4165
4166 editor
4167 });
4168
4169 _ = cx.add_window(|cx| {
4170 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
4171 editor.set_style(EditorStyle::default(), cx);
4172 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4173 editor.transpose(&Default::default(), cx);
4174 assert_eq!(editor.text(cx), "🏀🍐✋");
4175 assert_eq!(editor.selections.ranges(cx), [8..8]);
4176
4177 editor.transpose(&Default::default(), cx);
4178 assert_eq!(editor.text(cx), "🏀✋🍐");
4179 assert_eq!(editor.selections.ranges(cx), [11..11]);
4180
4181 editor.transpose(&Default::default(), cx);
4182 assert_eq!(editor.text(cx), "🏀🍐✋");
4183 assert_eq!(editor.selections.ranges(cx), [11..11]);
4184
4185 editor
4186 });
4187}
4188
4189#[gpui::test]
4190async fn test_rewrap(cx: &mut TestAppContext) {
4191 init_test(cx, |_| {});
4192
4193 let mut cx = EditorTestContext::new(cx).await;
4194
4195 let language_with_c_comments = Arc::new(Language::new(
4196 LanguageConfig {
4197 line_comments: vec!["// ".into()],
4198 ..LanguageConfig::default()
4199 },
4200 None,
4201 ));
4202 let language_with_pound_comments = Arc::new(Language::new(
4203 LanguageConfig {
4204 line_comments: vec!["# ".into()],
4205 ..LanguageConfig::default()
4206 },
4207 None,
4208 ));
4209 let markdown_language = Arc::new(Language::new(
4210 LanguageConfig {
4211 name: "Markdown".into(),
4212 ..LanguageConfig::default()
4213 },
4214 None,
4215 ));
4216 let language_with_doc_comments = Arc::new(Language::new(
4217 LanguageConfig {
4218 line_comments: vec!["// ".into(), "/// ".into()],
4219 ..LanguageConfig::default()
4220 },
4221 Some(tree_sitter_rust::LANGUAGE.into()),
4222 ));
4223
4224 let plaintext_language = Arc::new(Language::new(
4225 LanguageConfig {
4226 name: "Plain Text".into(),
4227 ..LanguageConfig::default()
4228 },
4229 None,
4230 ));
4231
4232 assert_rewrap(
4233 indoc! {"
4234 // ˇ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.
4235 "},
4236 indoc! {"
4237 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4238 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4239 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4240 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4241 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4242 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4243 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4244 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4245 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4246 // porttitor id. Aliquam id accumsan eros.
4247 "},
4248 language_with_c_comments.clone(),
4249 &mut cx,
4250 );
4251
4252 // Test that rewrapping works inside of a selection
4253 assert_rewrap(
4254 indoc! {"
4255 «// 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.ˇ»
4256 "},
4257 indoc! {"
4258 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4259 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4260 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4261 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4262 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4263 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4264 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4265 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4266 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4267 // porttitor id. Aliquam id accumsan eros.ˇ»
4268 "},
4269 language_with_c_comments.clone(),
4270 &mut cx,
4271 );
4272
4273 // Test that cursors that expand to the same region are collapsed.
4274 assert_rewrap(
4275 indoc! {"
4276 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4277 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4278 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4279 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4280 "},
4281 indoc! {"
4282 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4283 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4284 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4285 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4286 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4287 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4288 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4289 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4290 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4291 // porttitor id. Aliquam id accumsan eros.
4292 "},
4293 language_with_c_comments.clone(),
4294 &mut cx,
4295 );
4296
4297 // Test that non-contiguous selections are treated separately.
4298 assert_rewrap(
4299 indoc! {"
4300 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4301 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4302 //
4303 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4304 // ˇ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.
4305 "},
4306 indoc! {"
4307 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4308 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4309 // auctor, eu lacinia sapien scelerisque.
4310 //
4311 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4312 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4313 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4314 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4315 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4316 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4317 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4318 "},
4319 language_with_c_comments.clone(),
4320 &mut cx,
4321 );
4322
4323 // Test that different comment prefixes are supported.
4324 assert_rewrap(
4325 indoc! {"
4326 # ˇ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.
4327 "},
4328 indoc! {"
4329 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4330 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4331 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4332 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4333 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4334 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4335 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4336 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4337 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4338 # accumsan eros.
4339 "},
4340 language_with_pound_comments.clone(),
4341 &mut cx,
4342 );
4343
4344 // Test that rewrapping is ignored outside of comments in most languages.
4345 assert_rewrap(
4346 indoc! {"
4347 /// Adds two numbers.
4348 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4349 fn add(a: u32, b: u32) -> u32 {
4350 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ˇ
4351 }
4352 "},
4353 indoc! {"
4354 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4355 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4356 fn add(a: u32, b: u32) -> u32 {
4357 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4358 }
4359 "},
4360 language_with_doc_comments.clone(),
4361 &mut cx,
4362 );
4363
4364 // Test that rewrapping works in Markdown and Plain Text languages.
4365 assert_rewrap(
4366 indoc! {"
4367 # Hello
4368
4369 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.
4370 "},
4371 indoc! {"
4372 # Hello
4373
4374 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4375 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4376 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4377 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4378 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4379 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4380 Integer sit amet scelerisque nisi.
4381 "},
4382 markdown_language,
4383 &mut cx,
4384 );
4385
4386 assert_rewrap(
4387 indoc! {"
4388 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.
4389 "},
4390 indoc! {"
4391 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4392 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4393 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4394 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4395 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4396 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4397 Integer sit amet scelerisque nisi.
4398 "},
4399 plaintext_language,
4400 &mut cx,
4401 );
4402
4403 // Test rewrapping unaligned comments in a selection.
4404 assert_rewrap(
4405 indoc! {"
4406 fn foo() {
4407 if true {
4408 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4409 // Praesent semper egestas tellus id dignissim.ˇ»
4410 do_something();
4411 } else {
4412 //
4413 }
4414 }
4415 "},
4416 indoc! {"
4417 fn foo() {
4418 if true {
4419 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4420 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4421 // egestas tellus id dignissim.ˇ»
4422 do_something();
4423 } else {
4424 //
4425 }
4426 }
4427 "},
4428 language_with_doc_comments.clone(),
4429 &mut cx,
4430 );
4431
4432 assert_rewrap(
4433 indoc! {"
4434 fn foo() {
4435 if true {
4436 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4437 // Praesent semper egestas tellus id dignissim.»
4438 do_something();
4439 } else {
4440 //
4441 }
4442
4443 }
4444 "},
4445 indoc! {"
4446 fn foo() {
4447 if true {
4448 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4449 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4450 // egestas tellus id dignissim.»
4451 do_something();
4452 } else {
4453 //
4454 }
4455
4456 }
4457 "},
4458 language_with_doc_comments.clone(),
4459 &mut cx,
4460 );
4461
4462 #[track_caller]
4463 fn assert_rewrap(
4464 unwrapped_text: &str,
4465 wrapped_text: &str,
4466 language: Arc<Language>,
4467 cx: &mut EditorTestContext,
4468 ) {
4469 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4470 cx.set_state(unwrapped_text);
4471 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4472 cx.assert_editor_state(wrapped_text);
4473 }
4474}
4475
4476#[gpui::test]
4477async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4478 init_test(cx, |_| {});
4479
4480 let mut cx = EditorTestContext::new(cx).await;
4481
4482 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4483 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4484 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4485
4486 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4487 cx.set_state("two ˇfour ˇsix ˇ");
4488 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4489 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4490
4491 // Paste again but with only two cursors. Since the number of cursors doesn't
4492 // match the number of slices in the clipboard, the entire clipboard text
4493 // is pasted at each cursor.
4494 cx.set_state("ˇtwo one✅ four three six five ˇ");
4495 cx.update_editor(|e, cx| {
4496 e.handle_input("( ", cx);
4497 e.paste(&Paste, cx);
4498 e.handle_input(") ", cx);
4499 });
4500 cx.assert_editor_state(
4501 &([
4502 "( one✅ ",
4503 "three ",
4504 "five ) ˇtwo one✅ four three six five ( one✅ ",
4505 "three ",
4506 "five ) ˇ",
4507 ]
4508 .join("\n")),
4509 );
4510
4511 // Cut with three selections, one of which is full-line.
4512 cx.set_state(indoc! {"
4513 1«2ˇ»3
4514 4ˇ567
4515 «8ˇ»9"});
4516 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4517 cx.assert_editor_state(indoc! {"
4518 1ˇ3
4519 ˇ9"});
4520
4521 // Paste with three selections, noticing how the copied selection that was full-line
4522 // gets inserted before the second cursor.
4523 cx.set_state(indoc! {"
4524 1ˇ3
4525 9ˇ
4526 «oˇ»ne"});
4527 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4528 cx.assert_editor_state(indoc! {"
4529 12ˇ3
4530 4567
4531 9ˇ
4532 8ˇne"});
4533
4534 // Copy with a single cursor only, which writes the whole line into the clipboard.
4535 cx.set_state(indoc! {"
4536 The quick brown
4537 fox juˇmps over
4538 the lazy dog"});
4539 cx.update_editor(|e, cx| e.copy(&Copy, cx));
4540 assert_eq!(
4541 cx.read_from_clipboard()
4542 .and_then(|item| item.text().as_deref().map(str::to_string)),
4543 Some("fox jumps over\n".to_string())
4544 );
4545
4546 // Paste with three selections, noticing how the copied full-line selection is inserted
4547 // before the empty selections but replaces the selection that is non-empty.
4548 cx.set_state(indoc! {"
4549 Tˇhe quick brown
4550 «foˇ»x jumps over
4551 tˇhe lazy dog"});
4552 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4553 cx.assert_editor_state(indoc! {"
4554 fox jumps over
4555 Tˇhe quick brown
4556 fox jumps over
4557 ˇx jumps over
4558 fox jumps over
4559 tˇhe lazy dog"});
4560}
4561
4562#[gpui::test]
4563async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4564 init_test(cx, |_| {});
4565
4566 let mut cx = EditorTestContext::new(cx).await;
4567 let language = Arc::new(Language::new(
4568 LanguageConfig::default(),
4569 Some(tree_sitter_rust::LANGUAGE.into()),
4570 ));
4571 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4572
4573 // Cut an indented block, without the leading whitespace.
4574 cx.set_state(indoc! {"
4575 const a: B = (
4576 c(),
4577 «d(
4578 e,
4579 f
4580 )ˇ»
4581 );
4582 "});
4583 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4584 cx.assert_editor_state(indoc! {"
4585 const a: B = (
4586 c(),
4587 ˇ
4588 );
4589 "});
4590
4591 // Paste it at the same position.
4592 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4593 cx.assert_editor_state(indoc! {"
4594 const a: B = (
4595 c(),
4596 d(
4597 e,
4598 f
4599 )ˇ
4600 );
4601 "});
4602
4603 // Paste it at a line with a lower indent level.
4604 cx.set_state(indoc! {"
4605 ˇ
4606 const a: B = (
4607 c(),
4608 );
4609 "});
4610 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4611 cx.assert_editor_state(indoc! {"
4612 d(
4613 e,
4614 f
4615 )ˇ
4616 const a: B = (
4617 c(),
4618 );
4619 "});
4620
4621 // Cut an indented block, with the leading whitespace.
4622 cx.set_state(indoc! {"
4623 const a: B = (
4624 c(),
4625 « d(
4626 e,
4627 f
4628 )
4629 ˇ»);
4630 "});
4631 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4632 cx.assert_editor_state(indoc! {"
4633 const a: B = (
4634 c(),
4635 ˇ);
4636 "});
4637
4638 // Paste it at the same position.
4639 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4640 cx.assert_editor_state(indoc! {"
4641 const a: B = (
4642 c(),
4643 d(
4644 e,
4645 f
4646 )
4647 ˇ);
4648 "});
4649
4650 // Paste it at a line with a higher indent level.
4651 cx.set_state(indoc! {"
4652 const a: B = (
4653 c(),
4654 d(
4655 e,
4656 fˇ
4657 )
4658 );
4659 "});
4660 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4661 cx.assert_editor_state(indoc! {"
4662 const a: B = (
4663 c(),
4664 d(
4665 e,
4666 f d(
4667 e,
4668 f
4669 )
4670 ˇ
4671 )
4672 );
4673 "});
4674}
4675
4676#[gpui::test]
4677fn test_select_all(cx: &mut TestAppContext) {
4678 init_test(cx, |_| {});
4679
4680 let view = cx.add_window(|cx| {
4681 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4682 build_editor(buffer, cx)
4683 });
4684 _ = view.update(cx, |view, cx| {
4685 view.select_all(&SelectAll, cx);
4686 assert_eq!(
4687 view.selections.display_ranges(cx),
4688 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4689 );
4690 });
4691}
4692
4693#[gpui::test]
4694fn test_select_line(cx: &mut TestAppContext) {
4695 init_test(cx, |_| {});
4696
4697 let view = cx.add_window(|cx| {
4698 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4699 build_editor(buffer, cx)
4700 });
4701 _ = view.update(cx, |view, cx| {
4702 view.change_selections(None, cx, |s| {
4703 s.select_display_ranges([
4704 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4705 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4706 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4707 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4708 ])
4709 });
4710 view.select_line(&SelectLine, cx);
4711 assert_eq!(
4712 view.selections.display_ranges(cx),
4713 vec![
4714 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4715 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4716 ]
4717 );
4718 });
4719
4720 _ = view.update(cx, |view, cx| {
4721 view.select_line(&SelectLine, cx);
4722 assert_eq!(
4723 view.selections.display_ranges(cx),
4724 vec![
4725 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4726 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4727 ]
4728 );
4729 });
4730
4731 _ = view.update(cx, |view, cx| {
4732 view.select_line(&SelectLine, cx);
4733 assert_eq!(
4734 view.selections.display_ranges(cx),
4735 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4736 );
4737 });
4738}
4739
4740#[gpui::test]
4741fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4742 init_test(cx, |_| {});
4743
4744 let view = cx.add_window(|cx| {
4745 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4746 build_editor(buffer, cx)
4747 });
4748 _ = view.update(cx, |view, cx| {
4749 view.fold_creases(
4750 vec![
4751 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4752 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4753 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4754 ],
4755 true,
4756 cx,
4757 );
4758 view.change_selections(None, cx, |s| {
4759 s.select_display_ranges([
4760 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4761 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4762 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4763 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4764 ])
4765 });
4766 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4767 });
4768
4769 _ = view.update(cx, |view, cx| {
4770 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4771 assert_eq!(
4772 view.display_text(cx),
4773 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4774 );
4775 assert_eq!(
4776 view.selections.display_ranges(cx),
4777 [
4778 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4779 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4780 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4781 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4782 ]
4783 );
4784 });
4785
4786 _ = view.update(cx, |view, cx| {
4787 view.change_selections(None, cx, |s| {
4788 s.select_display_ranges([
4789 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4790 ])
4791 });
4792 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4793 assert_eq!(
4794 view.display_text(cx),
4795 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4796 );
4797 assert_eq!(
4798 view.selections.display_ranges(cx),
4799 [
4800 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4801 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4802 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4803 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4804 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4805 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4806 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4807 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4808 ]
4809 );
4810 });
4811}
4812
4813#[gpui::test]
4814async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4815 init_test(cx, |_| {});
4816
4817 let mut cx = EditorTestContext::new(cx).await;
4818
4819 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4820 cx.set_state(indoc!(
4821 r#"abc
4822 defˇghi
4823
4824 jk
4825 nlmo
4826 "#
4827 ));
4828
4829 cx.update_editor(|editor, cx| {
4830 editor.add_selection_above(&Default::default(), cx);
4831 });
4832
4833 cx.assert_editor_state(indoc!(
4834 r#"abcˇ
4835 defˇghi
4836
4837 jk
4838 nlmo
4839 "#
4840 ));
4841
4842 cx.update_editor(|editor, cx| {
4843 editor.add_selection_above(&Default::default(), cx);
4844 });
4845
4846 cx.assert_editor_state(indoc!(
4847 r#"abcˇ
4848 defˇghi
4849
4850 jk
4851 nlmo
4852 "#
4853 ));
4854
4855 cx.update_editor(|view, cx| {
4856 view.add_selection_below(&Default::default(), cx);
4857 });
4858
4859 cx.assert_editor_state(indoc!(
4860 r#"abc
4861 defˇghi
4862
4863 jk
4864 nlmo
4865 "#
4866 ));
4867
4868 cx.update_editor(|view, cx| {
4869 view.undo_selection(&Default::default(), cx);
4870 });
4871
4872 cx.assert_editor_state(indoc!(
4873 r#"abcˇ
4874 defˇghi
4875
4876 jk
4877 nlmo
4878 "#
4879 ));
4880
4881 cx.update_editor(|view, cx| {
4882 view.redo_selection(&Default::default(), cx);
4883 });
4884
4885 cx.assert_editor_state(indoc!(
4886 r#"abc
4887 defˇghi
4888
4889 jk
4890 nlmo
4891 "#
4892 ));
4893
4894 cx.update_editor(|view, cx| {
4895 view.add_selection_below(&Default::default(), cx);
4896 });
4897
4898 cx.assert_editor_state(indoc!(
4899 r#"abc
4900 defˇghi
4901
4902 jk
4903 nlmˇo
4904 "#
4905 ));
4906
4907 cx.update_editor(|view, cx| {
4908 view.add_selection_below(&Default::default(), cx);
4909 });
4910
4911 cx.assert_editor_state(indoc!(
4912 r#"abc
4913 defˇghi
4914
4915 jk
4916 nlmˇo
4917 "#
4918 ));
4919
4920 // change selections
4921 cx.set_state(indoc!(
4922 r#"abc
4923 def«ˇg»hi
4924
4925 jk
4926 nlmo
4927 "#
4928 ));
4929
4930 cx.update_editor(|view, cx| {
4931 view.add_selection_below(&Default::default(), cx);
4932 });
4933
4934 cx.assert_editor_state(indoc!(
4935 r#"abc
4936 def«ˇg»hi
4937
4938 jk
4939 nlm«ˇo»
4940 "#
4941 ));
4942
4943 cx.update_editor(|view, cx| {
4944 view.add_selection_below(&Default::default(), cx);
4945 });
4946
4947 cx.assert_editor_state(indoc!(
4948 r#"abc
4949 def«ˇg»hi
4950
4951 jk
4952 nlm«ˇo»
4953 "#
4954 ));
4955
4956 cx.update_editor(|view, cx| {
4957 view.add_selection_above(&Default::default(), cx);
4958 });
4959
4960 cx.assert_editor_state(indoc!(
4961 r#"abc
4962 def«ˇg»hi
4963
4964 jk
4965 nlmo
4966 "#
4967 ));
4968
4969 cx.update_editor(|view, cx| {
4970 view.add_selection_above(&Default::default(), cx);
4971 });
4972
4973 cx.assert_editor_state(indoc!(
4974 r#"abc
4975 def«ˇg»hi
4976
4977 jk
4978 nlmo
4979 "#
4980 ));
4981
4982 // Change selections again
4983 cx.set_state(indoc!(
4984 r#"a«bc
4985 defgˇ»hi
4986
4987 jk
4988 nlmo
4989 "#
4990 ));
4991
4992 cx.update_editor(|view, cx| {
4993 view.add_selection_below(&Default::default(), cx);
4994 });
4995
4996 cx.assert_editor_state(indoc!(
4997 r#"a«bcˇ»
4998 d«efgˇ»hi
4999
5000 j«kˇ»
5001 nlmo
5002 "#
5003 ));
5004
5005 cx.update_editor(|view, cx| {
5006 view.add_selection_below(&Default::default(), cx);
5007 });
5008 cx.assert_editor_state(indoc!(
5009 r#"a«bcˇ»
5010 d«efgˇ»hi
5011
5012 j«kˇ»
5013 n«lmoˇ»
5014 "#
5015 ));
5016 cx.update_editor(|view, cx| {
5017 view.add_selection_above(&Default::default(), cx);
5018 });
5019
5020 cx.assert_editor_state(indoc!(
5021 r#"a«bcˇ»
5022 d«efgˇ»hi
5023
5024 j«kˇ»
5025 nlmo
5026 "#
5027 ));
5028
5029 // Change selections again
5030 cx.set_state(indoc!(
5031 r#"abc
5032 d«ˇefghi
5033
5034 jk
5035 nlm»o
5036 "#
5037 ));
5038
5039 cx.update_editor(|view, cx| {
5040 view.add_selection_above(&Default::default(), cx);
5041 });
5042
5043 cx.assert_editor_state(indoc!(
5044 r#"a«ˇbc»
5045 d«ˇef»ghi
5046
5047 j«ˇk»
5048 n«ˇlm»o
5049 "#
5050 ));
5051
5052 cx.update_editor(|view, cx| {
5053 view.add_selection_below(&Default::default(), cx);
5054 });
5055
5056 cx.assert_editor_state(indoc!(
5057 r#"abc
5058 d«ˇef»ghi
5059
5060 j«ˇk»
5061 n«ˇlm»o
5062 "#
5063 ));
5064}
5065
5066#[gpui::test]
5067async fn test_select_next(cx: &mut gpui::TestAppContext) {
5068 init_test(cx, |_| {});
5069
5070 let mut cx = EditorTestContext::new(cx).await;
5071 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5072
5073 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5074 .unwrap();
5075 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5076
5077 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5078 .unwrap();
5079 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5080
5081 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5082 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5083
5084 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5085 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5086
5087 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5088 .unwrap();
5089 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5090
5091 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5092 .unwrap();
5093 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5094}
5095
5096#[gpui::test]
5097async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
5098 init_test(cx, |_| {});
5099
5100 let mut cx = EditorTestContext::new(cx).await;
5101 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5102
5103 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
5104 .unwrap();
5105 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5106}
5107
5108#[gpui::test]
5109async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5110 init_test(cx, |_| {});
5111
5112 let mut cx = EditorTestContext::new(cx).await;
5113 cx.set_state(
5114 r#"let foo = 2;
5115lˇet foo = 2;
5116let fooˇ = 2;
5117let foo = 2;
5118let foo = ˇ2;"#,
5119 );
5120
5121 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5122 .unwrap();
5123 cx.assert_editor_state(
5124 r#"let foo = 2;
5125«letˇ» foo = 2;
5126let «fooˇ» = 2;
5127let foo = 2;
5128let foo = «2ˇ»;"#,
5129 );
5130
5131 // noop for multiple selections with different contents
5132 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5133 .unwrap();
5134 cx.assert_editor_state(
5135 r#"let foo = 2;
5136«letˇ» foo = 2;
5137let «fooˇ» = 2;
5138let foo = 2;
5139let foo = «2ˇ»;"#,
5140 );
5141}
5142
5143#[gpui::test]
5144async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
5145 init_test(cx, |_| {});
5146
5147 let mut cx =
5148 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5149
5150 cx.assert_editor_state(indoc! {"
5151 ˇbbb
5152 ccc
5153
5154 bbb
5155 ccc
5156 "});
5157 cx.dispatch_action(SelectPrevious::default());
5158 cx.assert_editor_state(indoc! {"
5159 «bbbˇ»
5160 ccc
5161
5162 bbb
5163 ccc
5164 "});
5165 cx.dispatch_action(SelectPrevious::default());
5166 cx.assert_editor_state(indoc! {"
5167 «bbbˇ»
5168 ccc
5169
5170 «bbbˇ»
5171 ccc
5172 "});
5173}
5174
5175#[gpui::test]
5176async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
5177 init_test(cx, |_| {});
5178
5179 let mut cx = EditorTestContext::new(cx).await;
5180 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5181
5182 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5183 .unwrap();
5184 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5185
5186 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5187 .unwrap();
5188 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5189
5190 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5191 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5192
5193 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5194 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5195
5196 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5197 .unwrap();
5198 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5199
5200 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5201 .unwrap();
5202 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5203
5204 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5205 .unwrap();
5206 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5207}
5208
5209#[gpui::test]
5210async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5211 init_test(cx, |_| {});
5212
5213 let mut cx = EditorTestContext::new(cx).await;
5214 cx.set_state(
5215 r#"let foo = 2;
5216lˇet foo = 2;
5217let fooˇ = 2;
5218let foo = 2;
5219let foo = ˇ2;"#,
5220 );
5221
5222 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5223 .unwrap();
5224 cx.assert_editor_state(
5225 r#"let foo = 2;
5226«letˇ» foo = 2;
5227let «fooˇ» = 2;
5228let foo = 2;
5229let foo = «2ˇ»;"#,
5230 );
5231
5232 // noop for multiple selections with different contents
5233 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5234 .unwrap();
5235 cx.assert_editor_state(
5236 r#"let foo = 2;
5237«letˇ» foo = 2;
5238let «fooˇ» = 2;
5239let foo = 2;
5240let foo = «2ˇ»;"#,
5241 );
5242}
5243
5244#[gpui::test]
5245async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
5246 init_test(cx, |_| {});
5247
5248 let mut cx = EditorTestContext::new(cx).await;
5249 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5250
5251 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5252 .unwrap();
5253 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5254
5255 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5256 .unwrap();
5257 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5258
5259 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5260 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5261
5262 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5263 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5264
5265 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5266 .unwrap();
5267 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5268
5269 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5270 .unwrap();
5271 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5272}
5273
5274#[gpui::test]
5275async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5276 init_test(cx, |_| {});
5277
5278 let language = Arc::new(Language::new(
5279 LanguageConfig::default(),
5280 Some(tree_sitter_rust::LANGUAGE.into()),
5281 ));
5282
5283 let text = r#"
5284 use mod1::mod2::{mod3, mod4};
5285
5286 fn fn_1(param1: bool, param2: &str) {
5287 let var1 = "text";
5288 }
5289 "#
5290 .unindent();
5291
5292 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5293 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5294 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5295
5296 editor
5297 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5298 .await;
5299
5300 editor.update(cx, |view, cx| {
5301 view.change_selections(None, cx, |s| {
5302 s.select_display_ranges([
5303 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5304 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5305 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5306 ]);
5307 });
5308 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5309 });
5310 editor.update(cx, |editor, cx| {
5311 assert_text_with_selections(
5312 editor,
5313 indoc! {r#"
5314 use mod1::mod2::{mod3, «mod4ˇ»};
5315
5316 fn fn_1«ˇ(param1: bool, param2: &str)» {
5317 let var1 = "«textˇ»";
5318 }
5319 "#},
5320 cx,
5321 );
5322 });
5323
5324 editor.update(cx, |view, cx| {
5325 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5326 });
5327 editor.update(cx, |editor, cx| {
5328 assert_text_with_selections(
5329 editor,
5330 indoc! {r#"
5331 use mod1::mod2::«{mod3, mod4}ˇ»;
5332
5333 «ˇfn fn_1(param1: bool, param2: &str) {
5334 let var1 = "text";
5335 }»
5336 "#},
5337 cx,
5338 );
5339 });
5340
5341 editor.update(cx, |view, cx| {
5342 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5343 });
5344 assert_eq!(
5345 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5346 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5347 );
5348
5349 // Trying to expand the selected syntax node one more time has no effect.
5350 editor.update(cx, |view, cx| {
5351 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5352 });
5353 assert_eq!(
5354 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5355 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5356 );
5357
5358 editor.update(cx, |view, cx| {
5359 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5360 });
5361 editor.update(cx, |editor, cx| {
5362 assert_text_with_selections(
5363 editor,
5364 indoc! {r#"
5365 use mod1::mod2::«{mod3, mod4}ˇ»;
5366
5367 «ˇfn fn_1(param1: bool, param2: &str) {
5368 let var1 = "text";
5369 }»
5370 "#},
5371 cx,
5372 );
5373 });
5374
5375 editor.update(cx, |view, cx| {
5376 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5377 });
5378 editor.update(cx, |editor, cx| {
5379 assert_text_with_selections(
5380 editor,
5381 indoc! {r#"
5382 use mod1::mod2::{mod3, «mod4ˇ»};
5383
5384 fn fn_1«ˇ(param1: bool, param2: &str)» {
5385 let var1 = "«textˇ»";
5386 }
5387 "#},
5388 cx,
5389 );
5390 });
5391
5392 editor.update(cx, |view, cx| {
5393 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5394 });
5395 editor.update(cx, |editor, cx| {
5396 assert_text_with_selections(
5397 editor,
5398 indoc! {r#"
5399 use mod1::mod2::{mod3, mo«ˇ»d4};
5400
5401 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5402 let var1 = "te«ˇ»xt";
5403 }
5404 "#},
5405 cx,
5406 );
5407 });
5408
5409 // Trying to shrink the selected syntax node one more time has no effect.
5410 editor.update(cx, |view, cx| {
5411 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5412 });
5413 editor.update(cx, |editor, cx| {
5414 assert_text_with_selections(
5415 editor,
5416 indoc! {r#"
5417 use mod1::mod2::{mod3, mo«ˇ»d4};
5418
5419 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5420 let var1 = "te«ˇ»xt";
5421 }
5422 "#},
5423 cx,
5424 );
5425 });
5426
5427 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5428 // a fold.
5429 editor.update(cx, |view, cx| {
5430 view.fold_creases(
5431 vec![
5432 Crease::simple(
5433 Point::new(0, 21)..Point::new(0, 24),
5434 FoldPlaceholder::test(),
5435 ),
5436 Crease::simple(
5437 Point::new(3, 20)..Point::new(3, 22),
5438 FoldPlaceholder::test(),
5439 ),
5440 ],
5441 true,
5442 cx,
5443 );
5444 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5445 });
5446 editor.update(cx, |editor, cx| {
5447 assert_text_with_selections(
5448 editor,
5449 indoc! {r#"
5450 use mod1::mod2::«{mod3, mod4}ˇ»;
5451
5452 fn fn_1«ˇ(param1: bool, param2: &str)» {
5453 «let var1 = "text";ˇ»
5454 }
5455 "#},
5456 cx,
5457 );
5458 });
5459}
5460
5461#[gpui::test]
5462async fn test_autoindent(cx: &mut gpui::TestAppContext) {
5463 init_test(cx, |_| {});
5464
5465 let language = Arc::new(
5466 Language::new(
5467 LanguageConfig {
5468 brackets: BracketPairConfig {
5469 pairs: vec![
5470 BracketPair {
5471 start: "{".to_string(),
5472 end: "}".to_string(),
5473 close: false,
5474 surround: false,
5475 newline: true,
5476 },
5477 BracketPair {
5478 start: "(".to_string(),
5479 end: ")".to_string(),
5480 close: false,
5481 surround: false,
5482 newline: true,
5483 },
5484 ],
5485 ..Default::default()
5486 },
5487 ..Default::default()
5488 },
5489 Some(tree_sitter_rust::LANGUAGE.into()),
5490 )
5491 .with_indents_query(
5492 r#"
5493 (_ "(" ")" @end) @indent
5494 (_ "{" "}" @end) @indent
5495 "#,
5496 )
5497 .unwrap(),
5498 );
5499
5500 let text = "fn a() {}";
5501
5502 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5503 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5504 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5505 editor
5506 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5507 .await;
5508
5509 editor.update(cx, |editor, cx| {
5510 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5511 editor.newline(&Newline, cx);
5512 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5513 assert_eq!(
5514 editor.selections.ranges(cx),
5515 &[
5516 Point::new(1, 4)..Point::new(1, 4),
5517 Point::new(3, 4)..Point::new(3, 4),
5518 Point::new(5, 0)..Point::new(5, 0)
5519 ]
5520 );
5521 });
5522}
5523
5524#[gpui::test]
5525async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5526 init_test(cx, |_| {});
5527
5528 {
5529 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5530 cx.set_state(indoc! {"
5531 impl A {
5532
5533 fn b() {}
5534
5535 «fn c() {
5536
5537 }ˇ»
5538 }
5539 "});
5540
5541 cx.update_editor(|editor, cx| {
5542 editor.autoindent(&Default::default(), cx);
5543 });
5544
5545 cx.assert_editor_state(indoc! {"
5546 impl A {
5547
5548 fn b() {}
5549
5550 «fn c() {
5551
5552 }ˇ»
5553 }
5554 "});
5555 }
5556
5557 {
5558 let mut cx = EditorTestContext::new_multibuffer(
5559 cx,
5560 [indoc! { "
5561 impl A {
5562 «
5563 // a
5564 fn b(){}
5565 »
5566 «
5567 }
5568 fn c(){}
5569 »
5570 "}],
5571 );
5572
5573 let buffer = cx.update_editor(|editor, cx| {
5574 let buffer = editor.buffer().update(cx, |buffer, _| {
5575 buffer.all_buffers().iter().next().unwrap().clone()
5576 });
5577 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5578 buffer
5579 });
5580
5581 cx.run_until_parked();
5582 cx.update_editor(|editor, cx| {
5583 editor.select_all(&Default::default(), cx);
5584 editor.autoindent(&Default::default(), cx)
5585 });
5586 cx.run_until_parked();
5587
5588 cx.update(|cx| {
5589 pretty_assertions::assert_eq!(
5590 buffer.read(cx).text(),
5591 indoc! { "
5592 impl A {
5593
5594 // a
5595 fn b(){}
5596
5597
5598 }
5599 fn c(){}
5600
5601 " }
5602 )
5603 });
5604 }
5605}
5606
5607#[gpui::test]
5608async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5609 init_test(cx, |_| {});
5610
5611 let mut cx = EditorTestContext::new(cx).await;
5612
5613 let language = Arc::new(Language::new(
5614 LanguageConfig {
5615 brackets: BracketPairConfig {
5616 pairs: vec![
5617 BracketPair {
5618 start: "{".to_string(),
5619 end: "}".to_string(),
5620 close: true,
5621 surround: true,
5622 newline: true,
5623 },
5624 BracketPair {
5625 start: "(".to_string(),
5626 end: ")".to_string(),
5627 close: true,
5628 surround: true,
5629 newline: true,
5630 },
5631 BracketPair {
5632 start: "/*".to_string(),
5633 end: " */".to_string(),
5634 close: true,
5635 surround: true,
5636 newline: true,
5637 },
5638 BracketPair {
5639 start: "[".to_string(),
5640 end: "]".to_string(),
5641 close: false,
5642 surround: false,
5643 newline: true,
5644 },
5645 BracketPair {
5646 start: "\"".to_string(),
5647 end: "\"".to_string(),
5648 close: true,
5649 surround: true,
5650 newline: false,
5651 },
5652 BracketPair {
5653 start: "<".to_string(),
5654 end: ">".to_string(),
5655 close: false,
5656 surround: true,
5657 newline: true,
5658 },
5659 ],
5660 ..Default::default()
5661 },
5662 autoclose_before: "})]".to_string(),
5663 ..Default::default()
5664 },
5665 Some(tree_sitter_rust::LANGUAGE.into()),
5666 ));
5667
5668 cx.language_registry().add(language.clone());
5669 cx.update_buffer(|buffer, cx| {
5670 buffer.set_language(Some(language), cx);
5671 });
5672
5673 cx.set_state(
5674 &r#"
5675 🏀ˇ
5676 εˇ
5677 ❤️ˇ
5678 "#
5679 .unindent(),
5680 );
5681
5682 // autoclose multiple nested brackets at multiple cursors
5683 cx.update_editor(|view, cx| {
5684 view.handle_input("{", cx);
5685 view.handle_input("{", cx);
5686 view.handle_input("{", cx);
5687 });
5688 cx.assert_editor_state(
5689 &"
5690 🏀{{{ˇ}}}
5691 ε{{{ˇ}}}
5692 ❤️{{{ˇ}}}
5693 "
5694 .unindent(),
5695 );
5696
5697 // insert a different closing bracket
5698 cx.update_editor(|view, cx| {
5699 view.handle_input(")", cx);
5700 });
5701 cx.assert_editor_state(
5702 &"
5703 🏀{{{)ˇ}}}
5704 ε{{{)ˇ}}}
5705 ❤️{{{)ˇ}}}
5706 "
5707 .unindent(),
5708 );
5709
5710 // skip over the auto-closed brackets when typing a closing bracket
5711 cx.update_editor(|view, cx| {
5712 view.move_right(&MoveRight, cx);
5713 view.handle_input("}", cx);
5714 view.handle_input("}", cx);
5715 view.handle_input("}", cx);
5716 });
5717 cx.assert_editor_state(
5718 &"
5719 🏀{{{)}}}}ˇ
5720 ε{{{)}}}}ˇ
5721 ❤️{{{)}}}}ˇ
5722 "
5723 .unindent(),
5724 );
5725
5726 // autoclose multi-character pairs
5727 cx.set_state(
5728 &"
5729 ˇ
5730 ˇ
5731 "
5732 .unindent(),
5733 );
5734 cx.update_editor(|view, cx| {
5735 view.handle_input("/", cx);
5736 view.handle_input("*", cx);
5737 });
5738 cx.assert_editor_state(
5739 &"
5740 /*ˇ */
5741 /*ˇ */
5742 "
5743 .unindent(),
5744 );
5745
5746 // one cursor autocloses a multi-character pair, one cursor
5747 // does not autoclose.
5748 cx.set_state(
5749 &"
5750 /ˇ
5751 ˇ
5752 "
5753 .unindent(),
5754 );
5755 cx.update_editor(|view, cx| view.handle_input("*", cx));
5756 cx.assert_editor_state(
5757 &"
5758 /*ˇ */
5759 *ˇ
5760 "
5761 .unindent(),
5762 );
5763
5764 // Don't autoclose if the next character isn't whitespace and isn't
5765 // listed in the language's "autoclose_before" section.
5766 cx.set_state("ˇa b");
5767 cx.update_editor(|view, cx| view.handle_input("{", cx));
5768 cx.assert_editor_state("{ˇa b");
5769
5770 // Don't autoclose if `close` is false for the bracket pair
5771 cx.set_state("ˇ");
5772 cx.update_editor(|view, cx| view.handle_input("[", cx));
5773 cx.assert_editor_state("[ˇ");
5774
5775 // Surround with brackets if text is selected
5776 cx.set_state("«aˇ» b");
5777 cx.update_editor(|view, cx| view.handle_input("{", cx));
5778 cx.assert_editor_state("{«aˇ»} b");
5779
5780 // Autclose pair where the start and end characters are the same
5781 cx.set_state("aˇ");
5782 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5783 cx.assert_editor_state("a\"ˇ\"");
5784 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5785 cx.assert_editor_state("a\"\"ˇ");
5786
5787 // Don't autoclose pair if autoclose is disabled
5788 cx.set_state("ˇ");
5789 cx.update_editor(|view, cx| view.handle_input("<", cx));
5790 cx.assert_editor_state("<ˇ");
5791
5792 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5793 cx.set_state("«aˇ» b");
5794 cx.update_editor(|view, cx| view.handle_input("<", cx));
5795 cx.assert_editor_state("<«aˇ»> b");
5796}
5797
5798#[gpui::test]
5799async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5800 init_test(cx, |settings| {
5801 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5802 });
5803
5804 let mut cx = EditorTestContext::new(cx).await;
5805
5806 let language = Arc::new(Language::new(
5807 LanguageConfig {
5808 brackets: BracketPairConfig {
5809 pairs: vec![
5810 BracketPair {
5811 start: "{".to_string(),
5812 end: "}".to_string(),
5813 close: true,
5814 surround: true,
5815 newline: true,
5816 },
5817 BracketPair {
5818 start: "(".to_string(),
5819 end: ")".to_string(),
5820 close: true,
5821 surround: true,
5822 newline: true,
5823 },
5824 BracketPair {
5825 start: "[".to_string(),
5826 end: "]".to_string(),
5827 close: false,
5828 surround: false,
5829 newline: true,
5830 },
5831 ],
5832 ..Default::default()
5833 },
5834 autoclose_before: "})]".to_string(),
5835 ..Default::default()
5836 },
5837 Some(tree_sitter_rust::LANGUAGE.into()),
5838 ));
5839
5840 cx.language_registry().add(language.clone());
5841 cx.update_buffer(|buffer, cx| {
5842 buffer.set_language(Some(language), cx);
5843 });
5844
5845 cx.set_state(
5846 &"
5847 ˇ
5848 ˇ
5849 ˇ
5850 "
5851 .unindent(),
5852 );
5853
5854 // ensure only matching closing brackets are skipped over
5855 cx.update_editor(|view, cx| {
5856 view.handle_input("}", cx);
5857 view.move_left(&MoveLeft, cx);
5858 view.handle_input(")", cx);
5859 view.move_left(&MoveLeft, cx);
5860 });
5861 cx.assert_editor_state(
5862 &"
5863 ˇ)}
5864 ˇ)}
5865 ˇ)}
5866 "
5867 .unindent(),
5868 );
5869
5870 // skip-over closing brackets at multiple cursors
5871 cx.update_editor(|view, cx| {
5872 view.handle_input(")", cx);
5873 view.handle_input("}", cx);
5874 });
5875 cx.assert_editor_state(
5876 &"
5877 )}ˇ
5878 )}ˇ
5879 )}ˇ
5880 "
5881 .unindent(),
5882 );
5883
5884 // ignore non-close brackets
5885 cx.update_editor(|view, cx| {
5886 view.handle_input("]", cx);
5887 view.move_left(&MoveLeft, cx);
5888 view.handle_input("]", cx);
5889 });
5890 cx.assert_editor_state(
5891 &"
5892 )}]ˇ]
5893 )}]ˇ]
5894 )}]ˇ]
5895 "
5896 .unindent(),
5897 );
5898}
5899
5900#[gpui::test]
5901async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5902 init_test(cx, |_| {});
5903
5904 let mut cx = EditorTestContext::new(cx).await;
5905
5906 let html_language = Arc::new(
5907 Language::new(
5908 LanguageConfig {
5909 name: "HTML".into(),
5910 brackets: BracketPairConfig {
5911 pairs: vec![
5912 BracketPair {
5913 start: "<".into(),
5914 end: ">".into(),
5915 close: true,
5916 ..Default::default()
5917 },
5918 BracketPair {
5919 start: "{".into(),
5920 end: "}".into(),
5921 close: true,
5922 ..Default::default()
5923 },
5924 BracketPair {
5925 start: "(".into(),
5926 end: ")".into(),
5927 close: true,
5928 ..Default::default()
5929 },
5930 ],
5931 ..Default::default()
5932 },
5933 autoclose_before: "})]>".into(),
5934 ..Default::default()
5935 },
5936 Some(tree_sitter_html::language()),
5937 )
5938 .with_injection_query(
5939 r#"
5940 (script_element
5941 (raw_text) @content
5942 (#set! "language" "javascript"))
5943 "#,
5944 )
5945 .unwrap(),
5946 );
5947
5948 let javascript_language = Arc::new(Language::new(
5949 LanguageConfig {
5950 name: "JavaScript".into(),
5951 brackets: BracketPairConfig {
5952 pairs: vec![
5953 BracketPair {
5954 start: "/*".into(),
5955 end: " */".into(),
5956 close: true,
5957 ..Default::default()
5958 },
5959 BracketPair {
5960 start: "{".into(),
5961 end: "}".into(),
5962 close: true,
5963 ..Default::default()
5964 },
5965 BracketPair {
5966 start: "(".into(),
5967 end: ")".into(),
5968 close: true,
5969 ..Default::default()
5970 },
5971 ],
5972 ..Default::default()
5973 },
5974 autoclose_before: "})]>".into(),
5975 ..Default::default()
5976 },
5977 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
5978 ));
5979
5980 cx.language_registry().add(html_language.clone());
5981 cx.language_registry().add(javascript_language.clone());
5982
5983 cx.update_buffer(|buffer, cx| {
5984 buffer.set_language(Some(html_language), cx);
5985 });
5986
5987 cx.set_state(
5988 &r#"
5989 <body>ˇ
5990 <script>
5991 var x = 1;ˇ
5992 </script>
5993 </body>ˇ
5994 "#
5995 .unindent(),
5996 );
5997
5998 // Precondition: different languages are active at different locations.
5999 cx.update_editor(|editor, cx| {
6000 let snapshot = editor.snapshot(cx);
6001 let cursors = editor.selections.ranges::<usize>(cx);
6002 let languages = cursors
6003 .iter()
6004 .map(|c| snapshot.language_at(c.start).unwrap().name())
6005 .collect::<Vec<_>>();
6006 assert_eq!(
6007 languages,
6008 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6009 );
6010 });
6011
6012 // Angle brackets autoclose in HTML, but not JavaScript.
6013 cx.update_editor(|editor, cx| {
6014 editor.handle_input("<", cx);
6015 editor.handle_input("a", cx);
6016 });
6017 cx.assert_editor_state(
6018 &r#"
6019 <body><aˇ>
6020 <script>
6021 var x = 1;<aˇ
6022 </script>
6023 </body><aˇ>
6024 "#
6025 .unindent(),
6026 );
6027
6028 // Curly braces and parens autoclose in both HTML and JavaScript.
6029 cx.update_editor(|editor, cx| {
6030 editor.handle_input(" b=", cx);
6031 editor.handle_input("{", cx);
6032 editor.handle_input("c", cx);
6033 editor.handle_input("(", cx);
6034 });
6035 cx.assert_editor_state(
6036 &r#"
6037 <body><a b={c(ˇ)}>
6038 <script>
6039 var x = 1;<a b={c(ˇ)}
6040 </script>
6041 </body><a b={c(ˇ)}>
6042 "#
6043 .unindent(),
6044 );
6045
6046 // Brackets that were already autoclosed are skipped.
6047 cx.update_editor(|editor, cx| {
6048 editor.handle_input(")", cx);
6049 editor.handle_input("d", cx);
6050 editor.handle_input("}", cx);
6051 });
6052 cx.assert_editor_state(
6053 &r#"
6054 <body><a b={c()d}ˇ>
6055 <script>
6056 var x = 1;<a b={c()d}ˇ
6057 </script>
6058 </body><a b={c()d}ˇ>
6059 "#
6060 .unindent(),
6061 );
6062 cx.update_editor(|editor, cx| {
6063 editor.handle_input(">", cx);
6064 });
6065 cx.assert_editor_state(
6066 &r#"
6067 <body><a b={c()d}>ˇ
6068 <script>
6069 var x = 1;<a b={c()d}>ˇ
6070 </script>
6071 </body><a b={c()d}>ˇ
6072 "#
6073 .unindent(),
6074 );
6075
6076 // Reset
6077 cx.set_state(
6078 &r#"
6079 <body>ˇ
6080 <script>
6081 var x = 1;ˇ
6082 </script>
6083 </body>ˇ
6084 "#
6085 .unindent(),
6086 );
6087
6088 cx.update_editor(|editor, cx| {
6089 editor.handle_input("<", cx);
6090 });
6091 cx.assert_editor_state(
6092 &r#"
6093 <body><ˇ>
6094 <script>
6095 var x = 1;<ˇ
6096 </script>
6097 </body><ˇ>
6098 "#
6099 .unindent(),
6100 );
6101
6102 // When backspacing, the closing angle brackets are removed.
6103 cx.update_editor(|editor, cx| {
6104 editor.backspace(&Backspace, cx);
6105 });
6106 cx.assert_editor_state(
6107 &r#"
6108 <body>ˇ
6109 <script>
6110 var x = 1;ˇ
6111 </script>
6112 </body>ˇ
6113 "#
6114 .unindent(),
6115 );
6116
6117 // Block comments autoclose in JavaScript, but not HTML.
6118 cx.update_editor(|editor, cx| {
6119 editor.handle_input("/", cx);
6120 editor.handle_input("*", cx);
6121 });
6122 cx.assert_editor_state(
6123 &r#"
6124 <body>/*ˇ
6125 <script>
6126 var x = 1;/*ˇ */
6127 </script>
6128 </body>/*ˇ
6129 "#
6130 .unindent(),
6131 );
6132}
6133
6134#[gpui::test]
6135async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
6136 init_test(cx, |_| {});
6137
6138 let mut cx = EditorTestContext::new(cx).await;
6139
6140 let rust_language = Arc::new(
6141 Language::new(
6142 LanguageConfig {
6143 name: "Rust".into(),
6144 brackets: serde_json::from_value(json!([
6145 { "start": "{", "end": "}", "close": true, "newline": true },
6146 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6147 ]))
6148 .unwrap(),
6149 autoclose_before: "})]>".into(),
6150 ..Default::default()
6151 },
6152 Some(tree_sitter_rust::LANGUAGE.into()),
6153 )
6154 .with_override_query("(string_literal) @string")
6155 .unwrap(),
6156 );
6157
6158 cx.language_registry().add(rust_language.clone());
6159 cx.update_buffer(|buffer, cx| {
6160 buffer.set_language(Some(rust_language), cx);
6161 });
6162
6163 cx.set_state(
6164 &r#"
6165 let x = ˇ
6166 "#
6167 .unindent(),
6168 );
6169
6170 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6171 cx.update_editor(|editor, cx| {
6172 editor.handle_input("\"", cx);
6173 });
6174 cx.assert_editor_state(
6175 &r#"
6176 let x = "ˇ"
6177 "#
6178 .unindent(),
6179 );
6180
6181 // Inserting another quotation mark. The cursor moves across the existing
6182 // automatically-inserted quotation mark.
6183 cx.update_editor(|editor, cx| {
6184 editor.handle_input("\"", cx);
6185 });
6186 cx.assert_editor_state(
6187 &r#"
6188 let x = ""ˇ
6189 "#
6190 .unindent(),
6191 );
6192
6193 // Reset
6194 cx.set_state(
6195 &r#"
6196 let x = ˇ
6197 "#
6198 .unindent(),
6199 );
6200
6201 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6202 cx.update_editor(|editor, cx| {
6203 editor.handle_input("\"", cx);
6204 editor.handle_input(" ", cx);
6205 editor.move_left(&Default::default(), cx);
6206 editor.handle_input("\\", cx);
6207 editor.handle_input("\"", cx);
6208 });
6209 cx.assert_editor_state(
6210 &r#"
6211 let x = "\"ˇ "
6212 "#
6213 .unindent(),
6214 );
6215
6216 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6217 // mark. Nothing is inserted.
6218 cx.update_editor(|editor, cx| {
6219 editor.move_right(&Default::default(), cx);
6220 editor.handle_input("\"", cx);
6221 });
6222 cx.assert_editor_state(
6223 &r#"
6224 let x = "\" "ˇ
6225 "#
6226 .unindent(),
6227 );
6228}
6229
6230#[gpui::test]
6231async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
6232 init_test(cx, |_| {});
6233
6234 let language = Arc::new(Language::new(
6235 LanguageConfig {
6236 brackets: BracketPairConfig {
6237 pairs: vec![
6238 BracketPair {
6239 start: "{".to_string(),
6240 end: "}".to_string(),
6241 close: true,
6242 surround: true,
6243 newline: true,
6244 },
6245 BracketPair {
6246 start: "/* ".to_string(),
6247 end: "*/".to_string(),
6248 close: true,
6249 surround: true,
6250 ..Default::default()
6251 },
6252 ],
6253 ..Default::default()
6254 },
6255 ..Default::default()
6256 },
6257 Some(tree_sitter_rust::LANGUAGE.into()),
6258 ));
6259
6260 let text = r#"
6261 a
6262 b
6263 c
6264 "#
6265 .unindent();
6266
6267 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6268 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6269 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6270 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6271 .await;
6272
6273 view.update(cx, |view, cx| {
6274 view.change_selections(None, cx, |s| {
6275 s.select_display_ranges([
6276 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6277 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6278 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6279 ])
6280 });
6281
6282 view.handle_input("{", cx);
6283 view.handle_input("{", cx);
6284 view.handle_input("{", cx);
6285 assert_eq!(
6286 view.text(cx),
6287 "
6288 {{{a}}}
6289 {{{b}}}
6290 {{{c}}}
6291 "
6292 .unindent()
6293 );
6294 assert_eq!(
6295 view.selections.display_ranges(cx),
6296 [
6297 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6298 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6299 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6300 ]
6301 );
6302
6303 view.undo(&Undo, cx);
6304 view.undo(&Undo, cx);
6305 view.undo(&Undo, cx);
6306 assert_eq!(
6307 view.text(cx),
6308 "
6309 a
6310 b
6311 c
6312 "
6313 .unindent()
6314 );
6315 assert_eq!(
6316 view.selections.display_ranges(cx),
6317 [
6318 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6319 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6320 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6321 ]
6322 );
6323
6324 // Ensure inserting the first character of a multi-byte bracket pair
6325 // doesn't surround the selections with the bracket.
6326 view.handle_input("/", cx);
6327 assert_eq!(
6328 view.text(cx),
6329 "
6330 /
6331 /
6332 /
6333 "
6334 .unindent()
6335 );
6336 assert_eq!(
6337 view.selections.display_ranges(cx),
6338 [
6339 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6340 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6341 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6342 ]
6343 );
6344
6345 view.undo(&Undo, cx);
6346 assert_eq!(
6347 view.text(cx),
6348 "
6349 a
6350 b
6351 c
6352 "
6353 .unindent()
6354 );
6355 assert_eq!(
6356 view.selections.display_ranges(cx),
6357 [
6358 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6359 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6360 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6361 ]
6362 );
6363
6364 // Ensure inserting the last character of a multi-byte bracket pair
6365 // doesn't surround the selections with the bracket.
6366 view.handle_input("*", cx);
6367 assert_eq!(
6368 view.text(cx),
6369 "
6370 *
6371 *
6372 *
6373 "
6374 .unindent()
6375 );
6376 assert_eq!(
6377 view.selections.display_ranges(cx),
6378 [
6379 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6380 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6381 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6382 ]
6383 );
6384 });
6385}
6386
6387#[gpui::test]
6388async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6389 init_test(cx, |_| {});
6390
6391 let language = Arc::new(Language::new(
6392 LanguageConfig {
6393 brackets: BracketPairConfig {
6394 pairs: vec![BracketPair {
6395 start: "{".to_string(),
6396 end: "}".to_string(),
6397 close: true,
6398 surround: true,
6399 newline: true,
6400 }],
6401 ..Default::default()
6402 },
6403 autoclose_before: "}".to_string(),
6404 ..Default::default()
6405 },
6406 Some(tree_sitter_rust::LANGUAGE.into()),
6407 ));
6408
6409 let text = r#"
6410 a
6411 b
6412 c
6413 "#
6414 .unindent();
6415
6416 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6417 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6418 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6419 editor
6420 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6421 .await;
6422
6423 editor.update(cx, |editor, cx| {
6424 editor.change_selections(None, cx, |s| {
6425 s.select_ranges([
6426 Point::new(0, 1)..Point::new(0, 1),
6427 Point::new(1, 1)..Point::new(1, 1),
6428 Point::new(2, 1)..Point::new(2, 1),
6429 ])
6430 });
6431
6432 editor.handle_input("{", cx);
6433 editor.handle_input("{", cx);
6434 editor.handle_input("_", cx);
6435 assert_eq!(
6436 editor.text(cx),
6437 "
6438 a{{_}}
6439 b{{_}}
6440 c{{_}}
6441 "
6442 .unindent()
6443 );
6444 assert_eq!(
6445 editor.selections.ranges::<Point>(cx),
6446 [
6447 Point::new(0, 4)..Point::new(0, 4),
6448 Point::new(1, 4)..Point::new(1, 4),
6449 Point::new(2, 4)..Point::new(2, 4)
6450 ]
6451 );
6452
6453 editor.backspace(&Default::default(), cx);
6454 editor.backspace(&Default::default(), cx);
6455 assert_eq!(
6456 editor.text(cx),
6457 "
6458 a{}
6459 b{}
6460 c{}
6461 "
6462 .unindent()
6463 );
6464 assert_eq!(
6465 editor.selections.ranges::<Point>(cx),
6466 [
6467 Point::new(0, 2)..Point::new(0, 2),
6468 Point::new(1, 2)..Point::new(1, 2),
6469 Point::new(2, 2)..Point::new(2, 2)
6470 ]
6471 );
6472
6473 editor.delete_to_previous_word_start(&Default::default(), cx);
6474 assert_eq!(
6475 editor.text(cx),
6476 "
6477 a
6478 b
6479 c
6480 "
6481 .unindent()
6482 );
6483 assert_eq!(
6484 editor.selections.ranges::<Point>(cx),
6485 [
6486 Point::new(0, 1)..Point::new(0, 1),
6487 Point::new(1, 1)..Point::new(1, 1),
6488 Point::new(2, 1)..Point::new(2, 1)
6489 ]
6490 );
6491 });
6492}
6493
6494#[gpui::test]
6495async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6496 init_test(cx, |settings| {
6497 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6498 });
6499
6500 let mut cx = EditorTestContext::new(cx).await;
6501
6502 let language = Arc::new(Language::new(
6503 LanguageConfig {
6504 brackets: BracketPairConfig {
6505 pairs: vec![
6506 BracketPair {
6507 start: "{".to_string(),
6508 end: "}".to_string(),
6509 close: true,
6510 surround: true,
6511 newline: true,
6512 },
6513 BracketPair {
6514 start: "(".to_string(),
6515 end: ")".to_string(),
6516 close: true,
6517 surround: true,
6518 newline: true,
6519 },
6520 BracketPair {
6521 start: "[".to_string(),
6522 end: "]".to_string(),
6523 close: false,
6524 surround: true,
6525 newline: true,
6526 },
6527 ],
6528 ..Default::default()
6529 },
6530 autoclose_before: "})]".to_string(),
6531 ..Default::default()
6532 },
6533 Some(tree_sitter_rust::LANGUAGE.into()),
6534 ));
6535
6536 cx.language_registry().add(language.clone());
6537 cx.update_buffer(|buffer, cx| {
6538 buffer.set_language(Some(language), cx);
6539 });
6540
6541 cx.set_state(
6542 &"
6543 {(ˇ)}
6544 [[ˇ]]
6545 {(ˇ)}
6546 "
6547 .unindent(),
6548 );
6549
6550 cx.update_editor(|view, cx| {
6551 view.backspace(&Default::default(), cx);
6552 view.backspace(&Default::default(), cx);
6553 });
6554
6555 cx.assert_editor_state(
6556 &"
6557 ˇ
6558 ˇ]]
6559 ˇ
6560 "
6561 .unindent(),
6562 );
6563
6564 cx.update_editor(|view, cx| {
6565 view.handle_input("{", cx);
6566 view.handle_input("{", cx);
6567 view.move_right(&MoveRight, cx);
6568 view.move_right(&MoveRight, cx);
6569 view.move_left(&MoveLeft, cx);
6570 view.move_left(&MoveLeft, cx);
6571 view.backspace(&Default::default(), cx);
6572 });
6573
6574 cx.assert_editor_state(
6575 &"
6576 {ˇ}
6577 {ˇ}]]
6578 {ˇ}
6579 "
6580 .unindent(),
6581 );
6582
6583 cx.update_editor(|view, cx| {
6584 view.backspace(&Default::default(), cx);
6585 });
6586
6587 cx.assert_editor_state(
6588 &"
6589 ˇ
6590 ˇ]]
6591 ˇ
6592 "
6593 .unindent(),
6594 );
6595}
6596
6597#[gpui::test]
6598async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6599 init_test(cx, |_| {});
6600
6601 let language = Arc::new(Language::new(
6602 LanguageConfig::default(),
6603 Some(tree_sitter_rust::LANGUAGE.into()),
6604 ));
6605
6606 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6607 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6608 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6609 editor
6610 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6611 .await;
6612
6613 editor.update(cx, |editor, cx| {
6614 editor.set_auto_replace_emoji_shortcode(true);
6615
6616 editor.handle_input("Hello ", cx);
6617 editor.handle_input(":wave", cx);
6618 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6619
6620 editor.handle_input(":", cx);
6621 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6622
6623 editor.handle_input(" :smile", cx);
6624 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6625
6626 editor.handle_input(":", cx);
6627 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6628
6629 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6630 editor.handle_input(":wave", cx);
6631 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6632
6633 editor.handle_input(":", cx);
6634 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6635
6636 editor.handle_input(":1", cx);
6637 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6638
6639 editor.handle_input(":", cx);
6640 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6641
6642 // Ensure shortcode does not get replaced when it is part of a word
6643 editor.handle_input(" Test:wave", cx);
6644 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6645
6646 editor.handle_input(":", cx);
6647 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6648
6649 editor.set_auto_replace_emoji_shortcode(false);
6650
6651 // Ensure shortcode does not get replaced when auto replace is off
6652 editor.handle_input(" :wave", cx);
6653 assert_eq!(
6654 editor.text(cx),
6655 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6656 );
6657
6658 editor.handle_input(":", cx);
6659 assert_eq!(
6660 editor.text(cx),
6661 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6662 );
6663 });
6664}
6665
6666#[gpui::test]
6667async fn test_snippet_placeholder_choices(cx: &mut gpui::TestAppContext) {
6668 init_test(cx, |_| {});
6669
6670 let (text, insertion_ranges) = marked_text_ranges(
6671 indoc! {"
6672 ˇ
6673 "},
6674 false,
6675 );
6676
6677 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6678 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6679
6680 _ = editor.update(cx, |editor, cx| {
6681 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
6682
6683 editor
6684 .insert_snippet(&insertion_ranges, snippet, cx)
6685 .unwrap();
6686
6687 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6688 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6689 assert_eq!(editor.text(cx), expected_text);
6690 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6691 }
6692
6693 assert(
6694 editor,
6695 cx,
6696 indoc! {"
6697 type «» =•
6698 "},
6699 );
6700
6701 assert!(editor.context_menu_visible(), "There should be a matches");
6702 });
6703}
6704
6705#[gpui::test]
6706async fn test_snippets(cx: &mut gpui::TestAppContext) {
6707 init_test(cx, |_| {});
6708
6709 let (text, insertion_ranges) = marked_text_ranges(
6710 indoc! {"
6711 a.ˇ b
6712 a.ˇ b
6713 a.ˇ b
6714 "},
6715 false,
6716 );
6717
6718 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6719 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6720
6721 editor.update(cx, |editor, cx| {
6722 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6723
6724 editor
6725 .insert_snippet(&insertion_ranges, snippet, cx)
6726 .unwrap();
6727
6728 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6729 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6730 assert_eq!(editor.text(cx), expected_text);
6731 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6732 }
6733
6734 assert(
6735 editor,
6736 cx,
6737 indoc! {"
6738 a.f(«one», two, «three») b
6739 a.f(«one», two, «three») b
6740 a.f(«one», two, «three») b
6741 "},
6742 );
6743
6744 // Can't move earlier than the first tab stop
6745 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6746 assert(
6747 editor,
6748 cx,
6749 indoc! {"
6750 a.f(«one», two, «three») b
6751 a.f(«one», two, «three») b
6752 a.f(«one», two, «three») b
6753 "},
6754 );
6755
6756 assert!(editor.move_to_next_snippet_tabstop(cx));
6757 assert(
6758 editor,
6759 cx,
6760 indoc! {"
6761 a.f(one, «two», three) b
6762 a.f(one, «two», three) b
6763 a.f(one, «two», three) b
6764 "},
6765 );
6766
6767 editor.move_to_prev_snippet_tabstop(cx);
6768 assert(
6769 editor,
6770 cx,
6771 indoc! {"
6772 a.f(«one», two, «three») b
6773 a.f(«one», two, «three») b
6774 a.f(«one», two, «three») b
6775 "},
6776 );
6777
6778 assert!(editor.move_to_next_snippet_tabstop(cx));
6779 assert(
6780 editor,
6781 cx,
6782 indoc! {"
6783 a.f(one, «two», three) b
6784 a.f(one, «two», three) b
6785 a.f(one, «two», three) b
6786 "},
6787 );
6788 assert!(editor.move_to_next_snippet_tabstop(cx));
6789 assert(
6790 editor,
6791 cx,
6792 indoc! {"
6793 a.f(one, two, three)ˇ b
6794 a.f(one, two, three)ˇ b
6795 a.f(one, two, three)ˇ b
6796 "},
6797 );
6798
6799 // As soon as the last tab stop is reached, snippet state is gone
6800 editor.move_to_prev_snippet_tabstop(cx);
6801 assert(
6802 editor,
6803 cx,
6804 indoc! {"
6805 a.f(one, two, three)ˇ b
6806 a.f(one, two, three)ˇ b
6807 a.f(one, two, three)ˇ b
6808 "},
6809 );
6810 });
6811}
6812
6813#[gpui::test]
6814async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6815 init_test(cx, |_| {});
6816
6817 let fs = FakeFs::new(cx.executor());
6818 fs.insert_file("/file.rs", Default::default()).await;
6819
6820 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6821
6822 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6823 language_registry.add(rust_lang());
6824 let mut fake_servers = language_registry.register_fake_lsp(
6825 "Rust",
6826 FakeLspAdapter {
6827 capabilities: lsp::ServerCapabilities {
6828 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6829 ..Default::default()
6830 },
6831 ..Default::default()
6832 },
6833 );
6834
6835 let buffer = project
6836 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6837 .await
6838 .unwrap();
6839
6840 cx.executor().start_waiting();
6841 let fake_server = fake_servers.next().await.unwrap();
6842
6843 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6844 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6845 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6846 assert!(cx.read(|cx| editor.is_dirty(cx)));
6847
6848 let save = editor
6849 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6850 .unwrap();
6851 fake_server
6852 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6853 assert_eq!(
6854 params.text_document.uri,
6855 lsp::Url::from_file_path("/file.rs").unwrap()
6856 );
6857 assert_eq!(params.options.tab_size, 4);
6858 Ok(Some(vec![lsp::TextEdit::new(
6859 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6860 ", ".to_string(),
6861 )]))
6862 })
6863 .next()
6864 .await;
6865 cx.executor().start_waiting();
6866 save.await;
6867
6868 assert_eq!(
6869 editor.update(cx, |editor, cx| editor.text(cx)),
6870 "one, two\nthree\n"
6871 );
6872 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6873
6874 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6875 assert!(cx.read(|cx| editor.is_dirty(cx)));
6876
6877 // Ensure we can still save even if formatting hangs.
6878 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6879 assert_eq!(
6880 params.text_document.uri,
6881 lsp::Url::from_file_path("/file.rs").unwrap()
6882 );
6883 futures::future::pending::<()>().await;
6884 unreachable!()
6885 });
6886 let save = editor
6887 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6888 .unwrap();
6889 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6890 cx.executor().start_waiting();
6891 save.await;
6892 assert_eq!(
6893 editor.update(cx, |editor, cx| editor.text(cx)),
6894 "one\ntwo\nthree\n"
6895 );
6896 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6897
6898 // For non-dirty buffer, no formatting request should be sent
6899 let save = editor
6900 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6901 .unwrap();
6902 let _pending_format_request = fake_server
6903 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6904 panic!("Should not be invoked on non-dirty buffer");
6905 })
6906 .next();
6907 cx.executor().start_waiting();
6908 save.await;
6909
6910 // Set rust language override and assert overridden tabsize is sent to language server
6911 update_test_language_settings(cx, |settings| {
6912 settings.languages.insert(
6913 "Rust".into(),
6914 LanguageSettingsContent {
6915 tab_size: NonZeroU32::new(8),
6916 ..Default::default()
6917 },
6918 );
6919 });
6920
6921 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6922 assert!(cx.read(|cx| editor.is_dirty(cx)));
6923 let save = editor
6924 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6925 .unwrap();
6926 fake_server
6927 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6928 assert_eq!(
6929 params.text_document.uri,
6930 lsp::Url::from_file_path("/file.rs").unwrap()
6931 );
6932 assert_eq!(params.options.tab_size, 8);
6933 Ok(Some(vec![]))
6934 })
6935 .next()
6936 .await;
6937 cx.executor().start_waiting();
6938 save.await;
6939}
6940
6941#[gpui::test]
6942async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6943 init_test(cx, |_| {});
6944
6945 let cols = 4;
6946 let rows = 10;
6947 let sample_text_1 = sample_text(rows, cols, 'a');
6948 assert_eq!(
6949 sample_text_1,
6950 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6951 );
6952 let sample_text_2 = sample_text(rows, cols, 'l');
6953 assert_eq!(
6954 sample_text_2,
6955 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6956 );
6957 let sample_text_3 = sample_text(rows, cols, 'v');
6958 assert_eq!(
6959 sample_text_3,
6960 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6961 );
6962
6963 let fs = FakeFs::new(cx.executor());
6964 fs.insert_tree(
6965 "/a",
6966 json!({
6967 "main.rs": sample_text_1,
6968 "other.rs": sample_text_2,
6969 "lib.rs": sample_text_3,
6970 }),
6971 )
6972 .await;
6973
6974 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6975 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6976 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6977
6978 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6979 language_registry.add(rust_lang());
6980 let mut fake_servers = language_registry.register_fake_lsp(
6981 "Rust",
6982 FakeLspAdapter {
6983 capabilities: lsp::ServerCapabilities {
6984 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6985 ..Default::default()
6986 },
6987 ..Default::default()
6988 },
6989 );
6990
6991 let worktree = project.update(cx, |project, cx| {
6992 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
6993 assert_eq!(worktrees.len(), 1);
6994 worktrees.pop().unwrap()
6995 });
6996 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6997
6998 let buffer_1 = project
6999 .update(cx, |project, cx| {
7000 project.open_buffer((worktree_id, "main.rs"), cx)
7001 })
7002 .await
7003 .unwrap();
7004 let buffer_2 = project
7005 .update(cx, |project, cx| {
7006 project.open_buffer((worktree_id, "other.rs"), cx)
7007 })
7008 .await
7009 .unwrap();
7010 let buffer_3 = project
7011 .update(cx, |project, cx| {
7012 project.open_buffer((worktree_id, "lib.rs"), cx)
7013 })
7014 .await
7015 .unwrap();
7016
7017 let multi_buffer = cx.new_model(|cx| {
7018 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7019 multi_buffer.push_excerpts(
7020 buffer_1.clone(),
7021 [
7022 ExcerptRange {
7023 context: Point::new(0, 0)..Point::new(3, 0),
7024 primary: None,
7025 },
7026 ExcerptRange {
7027 context: Point::new(5, 0)..Point::new(7, 0),
7028 primary: None,
7029 },
7030 ExcerptRange {
7031 context: Point::new(9, 0)..Point::new(10, 4),
7032 primary: None,
7033 },
7034 ],
7035 cx,
7036 );
7037 multi_buffer.push_excerpts(
7038 buffer_2.clone(),
7039 [
7040 ExcerptRange {
7041 context: Point::new(0, 0)..Point::new(3, 0),
7042 primary: None,
7043 },
7044 ExcerptRange {
7045 context: Point::new(5, 0)..Point::new(7, 0),
7046 primary: None,
7047 },
7048 ExcerptRange {
7049 context: Point::new(9, 0)..Point::new(10, 4),
7050 primary: None,
7051 },
7052 ],
7053 cx,
7054 );
7055 multi_buffer.push_excerpts(
7056 buffer_3.clone(),
7057 [
7058 ExcerptRange {
7059 context: Point::new(0, 0)..Point::new(3, 0),
7060 primary: None,
7061 },
7062 ExcerptRange {
7063 context: Point::new(5, 0)..Point::new(7, 0),
7064 primary: None,
7065 },
7066 ExcerptRange {
7067 context: Point::new(9, 0)..Point::new(10, 4),
7068 primary: None,
7069 },
7070 ],
7071 cx,
7072 );
7073 multi_buffer
7074 });
7075 let multi_buffer_editor = cx.new_view(|cx| {
7076 Editor::new(
7077 EditorMode::Full,
7078 multi_buffer,
7079 Some(project.clone()),
7080 true,
7081 cx,
7082 )
7083 });
7084
7085 multi_buffer_editor.update(cx, |editor, cx| {
7086 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
7087 editor.insert("|one|two|three|", cx);
7088 });
7089 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7090 multi_buffer_editor.update(cx, |editor, cx| {
7091 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
7092 s.select_ranges(Some(60..70))
7093 });
7094 editor.insert("|four|five|six|", cx);
7095 });
7096 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7097
7098 // First two buffers should be edited, but not the third one.
7099 assert_eq!(
7100 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7101 "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}",
7102 );
7103 buffer_1.update(cx, |buffer, _| {
7104 assert!(buffer.is_dirty());
7105 assert_eq!(
7106 buffer.text(),
7107 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7108 )
7109 });
7110 buffer_2.update(cx, |buffer, _| {
7111 assert!(buffer.is_dirty());
7112 assert_eq!(
7113 buffer.text(),
7114 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7115 )
7116 });
7117 buffer_3.update(cx, |buffer, _| {
7118 assert!(!buffer.is_dirty());
7119 assert_eq!(buffer.text(), sample_text_3,)
7120 });
7121
7122 cx.executor().start_waiting();
7123 let save = multi_buffer_editor
7124 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7125 .unwrap();
7126
7127 let fake_server = fake_servers.next().await.unwrap();
7128 fake_server
7129 .server
7130 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7131 Ok(Some(vec![lsp::TextEdit::new(
7132 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7133 format!("[{} formatted]", params.text_document.uri),
7134 )]))
7135 })
7136 .detach();
7137 save.await;
7138
7139 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7140 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7141 assert_eq!(
7142 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7143 "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}",
7144 );
7145 buffer_1.update(cx, |buffer, _| {
7146 assert!(!buffer.is_dirty());
7147 assert_eq!(
7148 buffer.text(),
7149 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
7150 )
7151 });
7152 buffer_2.update(cx, |buffer, _| {
7153 assert!(!buffer.is_dirty());
7154 assert_eq!(
7155 buffer.text(),
7156 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
7157 )
7158 });
7159 buffer_3.update(cx, |buffer, _| {
7160 assert!(!buffer.is_dirty());
7161 assert_eq!(buffer.text(), sample_text_3,)
7162 });
7163}
7164
7165#[gpui::test]
7166async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
7167 init_test(cx, |_| {});
7168
7169 let fs = FakeFs::new(cx.executor());
7170 fs.insert_file("/file.rs", Default::default()).await;
7171
7172 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7173
7174 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7175 language_registry.add(rust_lang());
7176 let mut fake_servers = language_registry.register_fake_lsp(
7177 "Rust",
7178 FakeLspAdapter {
7179 capabilities: lsp::ServerCapabilities {
7180 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7181 ..Default::default()
7182 },
7183 ..Default::default()
7184 },
7185 );
7186
7187 let buffer = project
7188 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7189 .await
7190 .unwrap();
7191
7192 cx.executor().start_waiting();
7193 let fake_server = fake_servers.next().await.unwrap();
7194
7195 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7196 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7197 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7198 assert!(cx.read(|cx| editor.is_dirty(cx)));
7199
7200 let save = editor
7201 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7202 .unwrap();
7203 fake_server
7204 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7205 assert_eq!(
7206 params.text_document.uri,
7207 lsp::Url::from_file_path("/file.rs").unwrap()
7208 );
7209 assert_eq!(params.options.tab_size, 4);
7210 Ok(Some(vec![lsp::TextEdit::new(
7211 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7212 ", ".to_string(),
7213 )]))
7214 })
7215 .next()
7216 .await;
7217 cx.executor().start_waiting();
7218 save.await;
7219 assert_eq!(
7220 editor.update(cx, |editor, cx| editor.text(cx)),
7221 "one, two\nthree\n"
7222 );
7223 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7224
7225 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7226 assert!(cx.read(|cx| editor.is_dirty(cx)));
7227
7228 // Ensure we can still save even if formatting hangs.
7229 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7230 move |params, _| async move {
7231 assert_eq!(
7232 params.text_document.uri,
7233 lsp::Url::from_file_path("/file.rs").unwrap()
7234 );
7235 futures::future::pending::<()>().await;
7236 unreachable!()
7237 },
7238 );
7239 let save = editor
7240 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7241 .unwrap();
7242 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7243 cx.executor().start_waiting();
7244 save.await;
7245 assert_eq!(
7246 editor.update(cx, |editor, cx| editor.text(cx)),
7247 "one\ntwo\nthree\n"
7248 );
7249 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7250
7251 // For non-dirty buffer, no formatting request should be sent
7252 let save = editor
7253 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7254 .unwrap();
7255 let _pending_format_request = fake_server
7256 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7257 panic!("Should not be invoked on non-dirty buffer");
7258 })
7259 .next();
7260 cx.executor().start_waiting();
7261 save.await;
7262
7263 // Set Rust language override and assert overridden tabsize is sent to language server
7264 update_test_language_settings(cx, |settings| {
7265 settings.languages.insert(
7266 "Rust".into(),
7267 LanguageSettingsContent {
7268 tab_size: NonZeroU32::new(8),
7269 ..Default::default()
7270 },
7271 );
7272 });
7273
7274 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
7275 assert!(cx.read(|cx| editor.is_dirty(cx)));
7276 let save = editor
7277 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7278 .unwrap();
7279 fake_server
7280 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7281 assert_eq!(
7282 params.text_document.uri,
7283 lsp::Url::from_file_path("/file.rs").unwrap()
7284 );
7285 assert_eq!(params.options.tab_size, 8);
7286 Ok(Some(vec![]))
7287 })
7288 .next()
7289 .await;
7290 cx.executor().start_waiting();
7291 save.await;
7292}
7293
7294#[gpui::test]
7295async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7296 init_test(cx, |settings| {
7297 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7298 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7299 ))
7300 });
7301
7302 let fs = FakeFs::new(cx.executor());
7303 fs.insert_file("/file.rs", Default::default()).await;
7304
7305 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7306
7307 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7308 language_registry.add(Arc::new(Language::new(
7309 LanguageConfig {
7310 name: "Rust".into(),
7311 matcher: LanguageMatcher {
7312 path_suffixes: vec!["rs".to_string()],
7313 ..Default::default()
7314 },
7315 ..LanguageConfig::default()
7316 },
7317 Some(tree_sitter_rust::LANGUAGE.into()),
7318 )));
7319 update_test_language_settings(cx, |settings| {
7320 // Enable Prettier formatting for the same buffer, and ensure
7321 // LSP is called instead of Prettier.
7322 settings.defaults.prettier = Some(PrettierSettings {
7323 allowed: true,
7324 ..PrettierSettings::default()
7325 });
7326 });
7327 let mut fake_servers = language_registry.register_fake_lsp(
7328 "Rust",
7329 FakeLspAdapter {
7330 capabilities: lsp::ServerCapabilities {
7331 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7332 ..Default::default()
7333 },
7334 ..Default::default()
7335 },
7336 );
7337
7338 let buffer = project
7339 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7340 .await
7341 .unwrap();
7342
7343 cx.executor().start_waiting();
7344 let fake_server = fake_servers.next().await.unwrap();
7345
7346 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7347 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7348 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7349
7350 let format = editor
7351 .update(cx, |editor, cx| {
7352 editor.perform_format(
7353 project.clone(),
7354 FormatTrigger::Manual,
7355 FormatTarget::Buffer,
7356 cx,
7357 )
7358 })
7359 .unwrap();
7360 fake_server
7361 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7362 assert_eq!(
7363 params.text_document.uri,
7364 lsp::Url::from_file_path("/file.rs").unwrap()
7365 );
7366 assert_eq!(params.options.tab_size, 4);
7367 Ok(Some(vec![lsp::TextEdit::new(
7368 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7369 ", ".to_string(),
7370 )]))
7371 })
7372 .next()
7373 .await;
7374 cx.executor().start_waiting();
7375 format.await;
7376 assert_eq!(
7377 editor.update(cx, |editor, cx| editor.text(cx)),
7378 "one, two\nthree\n"
7379 );
7380
7381 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7382 // Ensure we don't lock if formatting hangs.
7383 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7384 assert_eq!(
7385 params.text_document.uri,
7386 lsp::Url::from_file_path("/file.rs").unwrap()
7387 );
7388 futures::future::pending::<()>().await;
7389 unreachable!()
7390 });
7391 let format = editor
7392 .update(cx, |editor, cx| {
7393 editor.perform_format(project, FormatTrigger::Manual, FormatTarget::Buffer, cx)
7394 })
7395 .unwrap();
7396 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7397 cx.executor().start_waiting();
7398 format.await;
7399 assert_eq!(
7400 editor.update(cx, |editor, cx| editor.text(cx)),
7401 "one\ntwo\nthree\n"
7402 );
7403}
7404
7405#[gpui::test]
7406async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7407 init_test(cx, |_| {});
7408
7409 let mut cx = EditorLspTestContext::new_rust(
7410 lsp::ServerCapabilities {
7411 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7412 ..Default::default()
7413 },
7414 cx,
7415 )
7416 .await;
7417
7418 cx.set_state(indoc! {"
7419 one.twoˇ
7420 "});
7421
7422 // The format request takes a long time. When it completes, it inserts
7423 // a newline and an indent before the `.`
7424 cx.lsp
7425 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7426 let executor = cx.background_executor().clone();
7427 async move {
7428 executor.timer(Duration::from_millis(100)).await;
7429 Ok(Some(vec![lsp::TextEdit {
7430 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7431 new_text: "\n ".into(),
7432 }]))
7433 }
7434 });
7435
7436 // Submit a format request.
7437 let format_1 = cx
7438 .update_editor(|editor, cx| editor.format(&Format, cx))
7439 .unwrap();
7440 cx.executor().run_until_parked();
7441
7442 // Submit a second format request.
7443 let format_2 = cx
7444 .update_editor(|editor, cx| editor.format(&Format, cx))
7445 .unwrap();
7446 cx.executor().run_until_parked();
7447
7448 // Wait for both format requests to complete
7449 cx.executor().advance_clock(Duration::from_millis(200));
7450 cx.executor().start_waiting();
7451 format_1.await.unwrap();
7452 cx.executor().start_waiting();
7453 format_2.await.unwrap();
7454
7455 // The formatting edits only happens once.
7456 cx.assert_editor_state(indoc! {"
7457 one
7458 .twoˇ
7459 "});
7460}
7461
7462#[gpui::test]
7463async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7464 init_test(cx, |settings| {
7465 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7466 });
7467
7468 let mut cx = EditorLspTestContext::new_rust(
7469 lsp::ServerCapabilities {
7470 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7471 ..Default::default()
7472 },
7473 cx,
7474 )
7475 .await;
7476
7477 // Set up a buffer white some trailing whitespace and no trailing newline.
7478 cx.set_state(
7479 &[
7480 "one ", //
7481 "twoˇ", //
7482 "three ", //
7483 "four", //
7484 ]
7485 .join("\n"),
7486 );
7487
7488 // Submit a format request.
7489 let format = cx
7490 .update_editor(|editor, cx| editor.format(&Format, cx))
7491 .unwrap();
7492
7493 // Record which buffer changes have been sent to the language server
7494 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7495 cx.lsp
7496 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7497 let buffer_changes = buffer_changes.clone();
7498 move |params, _| {
7499 buffer_changes.lock().extend(
7500 params
7501 .content_changes
7502 .into_iter()
7503 .map(|e| (e.range.unwrap(), e.text)),
7504 );
7505 }
7506 });
7507
7508 // Handle formatting requests to the language server.
7509 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7510 let buffer_changes = buffer_changes.clone();
7511 move |_, _| {
7512 // When formatting is requested, trailing whitespace has already been stripped,
7513 // and the trailing newline has already been added.
7514 assert_eq!(
7515 &buffer_changes.lock()[1..],
7516 &[
7517 (
7518 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7519 "".into()
7520 ),
7521 (
7522 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7523 "".into()
7524 ),
7525 (
7526 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7527 "\n".into()
7528 ),
7529 ]
7530 );
7531
7532 // Insert blank lines between each line of the buffer.
7533 async move {
7534 Ok(Some(vec![
7535 lsp::TextEdit {
7536 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7537 new_text: "\n".into(),
7538 },
7539 lsp::TextEdit {
7540 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7541 new_text: "\n".into(),
7542 },
7543 ]))
7544 }
7545 }
7546 });
7547
7548 // After formatting the buffer, the trailing whitespace is stripped,
7549 // a newline is appended, and the edits provided by the language server
7550 // have been applied.
7551 format.await.unwrap();
7552 cx.assert_editor_state(
7553 &[
7554 "one", //
7555 "", //
7556 "twoˇ", //
7557 "", //
7558 "three", //
7559 "four", //
7560 "", //
7561 ]
7562 .join("\n"),
7563 );
7564
7565 // Undoing the formatting undoes the trailing whitespace removal, the
7566 // trailing newline, and the LSP edits.
7567 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7568 cx.assert_editor_state(
7569 &[
7570 "one ", //
7571 "twoˇ", //
7572 "three ", //
7573 "four", //
7574 ]
7575 .join("\n"),
7576 );
7577}
7578
7579#[gpui::test]
7580async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7581 cx: &mut gpui::TestAppContext,
7582) {
7583 init_test(cx, |_| {});
7584
7585 cx.update(|cx| {
7586 cx.update_global::<SettingsStore, _>(|settings, cx| {
7587 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7588 settings.auto_signature_help = Some(true);
7589 });
7590 });
7591 });
7592
7593 let mut cx = EditorLspTestContext::new_rust(
7594 lsp::ServerCapabilities {
7595 signature_help_provider: Some(lsp::SignatureHelpOptions {
7596 ..Default::default()
7597 }),
7598 ..Default::default()
7599 },
7600 cx,
7601 )
7602 .await;
7603
7604 let language = Language::new(
7605 LanguageConfig {
7606 name: "Rust".into(),
7607 brackets: BracketPairConfig {
7608 pairs: vec![
7609 BracketPair {
7610 start: "{".to_string(),
7611 end: "}".to_string(),
7612 close: true,
7613 surround: true,
7614 newline: true,
7615 },
7616 BracketPair {
7617 start: "(".to_string(),
7618 end: ")".to_string(),
7619 close: true,
7620 surround: true,
7621 newline: true,
7622 },
7623 BracketPair {
7624 start: "/*".to_string(),
7625 end: " */".to_string(),
7626 close: true,
7627 surround: true,
7628 newline: true,
7629 },
7630 BracketPair {
7631 start: "[".to_string(),
7632 end: "]".to_string(),
7633 close: false,
7634 surround: false,
7635 newline: true,
7636 },
7637 BracketPair {
7638 start: "\"".to_string(),
7639 end: "\"".to_string(),
7640 close: true,
7641 surround: true,
7642 newline: false,
7643 },
7644 BracketPair {
7645 start: "<".to_string(),
7646 end: ">".to_string(),
7647 close: false,
7648 surround: true,
7649 newline: true,
7650 },
7651 ],
7652 ..Default::default()
7653 },
7654 autoclose_before: "})]".to_string(),
7655 ..Default::default()
7656 },
7657 Some(tree_sitter_rust::LANGUAGE.into()),
7658 );
7659 let language = Arc::new(language);
7660
7661 cx.language_registry().add(language.clone());
7662 cx.update_buffer(|buffer, cx| {
7663 buffer.set_language(Some(language), cx);
7664 });
7665
7666 cx.set_state(
7667 &r#"
7668 fn main() {
7669 sampleˇ
7670 }
7671 "#
7672 .unindent(),
7673 );
7674
7675 cx.update_editor(|view, cx| {
7676 view.handle_input("(", cx);
7677 });
7678 cx.assert_editor_state(
7679 &"
7680 fn main() {
7681 sample(ˇ)
7682 }
7683 "
7684 .unindent(),
7685 );
7686
7687 let mocked_response = lsp::SignatureHelp {
7688 signatures: vec![lsp::SignatureInformation {
7689 label: "fn sample(param1: u8, param2: u8)".to_string(),
7690 documentation: None,
7691 parameters: Some(vec![
7692 lsp::ParameterInformation {
7693 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7694 documentation: None,
7695 },
7696 lsp::ParameterInformation {
7697 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7698 documentation: None,
7699 },
7700 ]),
7701 active_parameter: None,
7702 }],
7703 active_signature: Some(0),
7704 active_parameter: Some(0),
7705 };
7706 handle_signature_help_request(&mut cx, mocked_response).await;
7707
7708 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7709 .await;
7710
7711 cx.editor(|editor, _| {
7712 let signature_help_state = editor.signature_help_state.popover().cloned();
7713 assert!(signature_help_state.is_some());
7714 let ParsedMarkdown {
7715 text, highlights, ..
7716 } = signature_help_state.unwrap().parsed_content;
7717 assert_eq!(text, "param1: u8, param2: u8");
7718 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7719 });
7720}
7721
7722#[gpui::test]
7723async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7724 init_test(cx, |_| {});
7725
7726 cx.update(|cx| {
7727 cx.update_global::<SettingsStore, _>(|settings, cx| {
7728 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7729 settings.auto_signature_help = Some(false);
7730 settings.show_signature_help_after_edits = Some(false);
7731 });
7732 });
7733 });
7734
7735 let mut cx = EditorLspTestContext::new_rust(
7736 lsp::ServerCapabilities {
7737 signature_help_provider: Some(lsp::SignatureHelpOptions {
7738 ..Default::default()
7739 }),
7740 ..Default::default()
7741 },
7742 cx,
7743 )
7744 .await;
7745
7746 let language = Language::new(
7747 LanguageConfig {
7748 name: "Rust".into(),
7749 brackets: BracketPairConfig {
7750 pairs: vec![
7751 BracketPair {
7752 start: "{".to_string(),
7753 end: "}".to_string(),
7754 close: true,
7755 surround: true,
7756 newline: true,
7757 },
7758 BracketPair {
7759 start: "(".to_string(),
7760 end: ")".to_string(),
7761 close: true,
7762 surround: true,
7763 newline: true,
7764 },
7765 BracketPair {
7766 start: "/*".to_string(),
7767 end: " */".to_string(),
7768 close: true,
7769 surround: true,
7770 newline: true,
7771 },
7772 BracketPair {
7773 start: "[".to_string(),
7774 end: "]".to_string(),
7775 close: false,
7776 surround: false,
7777 newline: true,
7778 },
7779 BracketPair {
7780 start: "\"".to_string(),
7781 end: "\"".to_string(),
7782 close: true,
7783 surround: true,
7784 newline: false,
7785 },
7786 BracketPair {
7787 start: "<".to_string(),
7788 end: ">".to_string(),
7789 close: false,
7790 surround: true,
7791 newline: true,
7792 },
7793 ],
7794 ..Default::default()
7795 },
7796 autoclose_before: "})]".to_string(),
7797 ..Default::default()
7798 },
7799 Some(tree_sitter_rust::LANGUAGE.into()),
7800 );
7801 let language = Arc::new(language);
7802
7803 cx.language_registry().add(language.clone());
7804 cx.update_buffer(|buffer, cx| {
7805 buffer.set_language(Some(language), cx);
7806 });
7807
7808 // Ensure that signature_help is not called when no signature help is enabled.
7809 cx.set_state(
7810 &r#"
7811 fn main() {
7812 sampleˇ
7813 }
7814 "#
7815 .unindent(),
7816 );
7817 cx.update_editor(|view, cx| {
7818 view.handle_input("(", cx);
7819 });
7820 cx.assert_editor_state(
7821 &"
7822 fn main() {
7823 sample(ˇ)
7824 }
7825 "
7826 .unindent(),
7827 );
7828 cx.editor(|editor, _| {
7829 assert!(editor.signature_help_state.task().is_none());
7830 });
7831
7832 let mocked_response = lsp::SignatureHelp {
7833 signatures: vec![lsp::SignatureInformation {
7834 label: "fn sample(param1: u8, param2: u8)".to_string(),
7835 documentation: None,
7836 parameters: Some(vec![
7837 lsp::ParameterInformation {
7838 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7839 documentation: None,
7840 },
7841 lsp::ParameterInformation {
7842 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7843 documentation: None,
7844 },
7845 ]),
7846 active_parameter: None,
7847 }],
7848 active_signature: Some(0),
7849 active_parameter: Some(0),
7850 };
7851
7852 // Ensure that signature_help is called when enabled afte edits
7853 cx.update(|cx| {
7854 cx.update_global::<SettingsStore, _>(|settings, cx| {
7855 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7856 settings.auto_signature_help = Some(false);
7857 settings.show_signature_help_after_edits = Some(true);
7858 });
7859 });
7860 });
7861 cx.set_state(
7862 &r#"
7863 fn main() {
7864 sampleˇ
7865 }
7866 "#
7867 .unindent(),
7868 );
7869 cx.update_editor(|view, cx| {
7870 view.handle_input("(", cx);
7871 });
7872 cx.assert_editor_state(
7873 &"
7874 fn main() {
7875 sample(ˇ)
7876 }
7877 "
7878 .unindent(),
7879 );
7880 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7881 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7882 .await;
7883 cx.update_editor(|editor, _| {
7884 let signature_help_state = editor.signature_help_state.popover().cloned();
7885 assert!(signature_help_state.is_some());
7886 let ParsedMarkdown {
7887 text, highlights, ..
7888 } = signature_help_state.unwrap().parsed_content;
7889 assert_eq!(text, "param1: u8, param2: u8");
7890 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7891 editor.signature_help_state = SignatureHelpState::default();
7892 });
7893
7894 // Ensure that signature_help is called when auto signature help override is enabled
7895 cx.update(|cx| {
7896 cx.update_global::<SettingsStore, _>(|settings, cx| {
7897 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7898 settings.auto_signature_help = Some(true);
7899 settings.show_signature_help_after_edits = Some(false);
7900 });
7901 });
7902 });
7903 cx.set_state(
7904 &r#"
7905 fn main() {
7906 sampleˇ
7907 }
7908 "#
7909 .unindent(),
7910 );
7911 cx.update_editor(|view, cx| {
7912 view.handle_input("(", cx);
7913 });
7914 cx.assert_editor_state(
7915 &"
7916 fn main() {
7917 sample(ˇ)
7918 }
7919 "
7920 .unindent(),
7921 );
7922 handle_signature_help_request(&mut cx, mocked_response).await;
7923 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7924 .await;
7925 cx.editor(|editor, _| {
7926 let signature_help_state = editor.signature_help_state.popover().cloned();
7927 assert!(signature_help_state.is_some());
7928 let ParsedMarkdown {
7929 text, highlights, ..
7930 } = signature_help_state.unwrap().parsed_content;
7931 assert_eq!(text, "param1: u8, param2: u8");
7932 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7933 });
7934}
7935
7936#[gpui::test]
7937async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7938 init_test(cx, |_| {});
7939 cx.update(|cx| {
7940 cx.update_global::<SettingsStore, _>(|settings, cx| {
7941 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7942 settings.auto_signature_help = Some(true);
7943 });
7944 });
7945 });
7946
7947 let mut cx = EditorLspTestContext::new_rust(
7948 lsp::ServerCapabilities {
7949 signature_help_provider: Some(lsp::SignatureHelpOptions {
7950 ..Default::default()
7951 }),
7952 ..Default::default()
7953 },
7954 cx,
7955 )
7956 .await;
7957
7958 // A test that directly calls `show_signature_help`
7959 cx.update_editor(|editor, cx| {
7960 editor.show_signature_help(&ShowSignatureHelp, cx);
7961 });
7962
7963 let mocked_response = lsp::SignatureHelp {
7964 signatures: vec![lsp::SignatureInformation {
7965 label: "fn sample(param1: u8, param2: u8)".to_string(),
7966 documentation: None,
7967 parameters: Some(vec![
7968 lsp::ParameterInformation {
7969 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7970 documentation: None,
7971 },
7972 lsp::ParameterInformation {
7973 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7974 documentation: None,
7975 },
7976 ]),
7977 active_parameter: None,
7978 }],
7979 active_signature: Some(0),
7980 active_parameter: Some(0),
7981 };
7982 handle_signature_help_request(&mut cx, mocked_response).await;
7983
7984 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7985 .await;
7986
7987 cx.editor(|editor, _| {
7988 let signature_help_state = editor.signature_help_state.popover().cloned();
7989 assert!(signature_help_state.is_some());
7990 let ParsedMarkdown {
7991 text, highlights, ..
7992 } = signature_help_state.unwrap().parsed_content;
7993 assert_eq!(text, "param1: u8, param2: u8");
7994 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7995 });
7996
7997 // When exiting outside from inside the brackets, `signature_help` is closed.
7998 cx.set_state(indoc! {"
7999 fn main() {
8000 sample(ˇ);
8001 }
8002
8003 fn sample(param1: u8, param2: u8) {}
8004 "});
8005
8006 cx.update_editor(|editor, cx| {
8007 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
8008 });
8009
8010 let mocked_response = lsp::SignatureHelp {
8011 signatures: Vec::new(),
8012 active_signature: None,
8013 active_parameter: None,
8014 };
8015 handle_signature_help_request(&mut cx, mocked_response).await;
8016
8017 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8018 .await;
8019
8020 cx.editor(|editor, _| {
8021 assert!(!editor.signature_help_state.is_shown());
8022 });
8023
8024 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8025 cx.set_state(indoc! {"
8026 fn main() {
8027 sample(ˇ);
8028 }
8029
8030 fn sample(param1: u8, param2: u8) {}
8031 "});
8032
8033 let mocked_response = lsp::SignatureHelp {
8034 signatures: vec![lsp::SignatureInformation {
8035 label: "fn sample(param1: u8, param2: u8)".to_string(),
8036 documentation: None,
8037 parameters: Some(vec![
8038 lsp::ParameterInformation {
8039 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8040 documentation: None,
8041 },
8042 lsp::ParameterInformation {
8043 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8044 documentation: None,
8045 },
8046 ]),
8047 active_parameter: None,
8048 }],
8049 active_signature: Some(0),
8050 active_parameter: Some(0),
8051 };
8052 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8053 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8054 .await;
8055 cx.editor(|editor, _| {
8056 assert!(editor.signature_help_state.is_shown());
8057 });
8058
8059 // Restore the popover with more parameter input
8060 cx.set_state(indoc! {"
8061 fn main() {
8062 sample(param1, param2ˇ);
8063 }
8064
8065 fn sample(param1: u8, param2: u8) {}
8066 "});
8067
8068 let mocked_response = lsp::SignatureHelp {
8069 signatures: vec![lsp::SignatureInformation {
8070 label: "fn sample(param1: u8, param2: u8)".to_string(),
8071 documentation: None,
8072 parameters: Some(vec![
8073 lsp::ParameterInformation {
8074 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8075 documentation: None,
8076 },
8077 lsp::ParameterInformation {
8078 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8079 documentation: None,
8080 },
8081 ]),
8082 active_parameter: None,
8083 }],
8084 active_signature: Some(0),
8085 active_parameter: Some(1),
8086 };
8087 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8088 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8089 .await;
8090
8091 // When selecting a range, the popover is gone.
8092 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8093 cx.update_editor(|editor, cx| {
8094 editor.change_selections(None, cx, |s| {
8095 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8096 })
8097 });
8098 cx.assert_editor_state(indoc! {"
8099 fn main() {
8100 sample(param1, «ˇparam2»);
8101 }
8102
8103 fn sample(param1: u8, param2: u8) {}
8104 "});
8105 cx.editor(|editor, _| {
8106 assert!(!editor.signature_help_state.is_shown());
8107 });
8108
8109 // When unselecting again, the popover is back if within the brackets.
8110 cx.update_editor(|editor, cx| {
8111 editor.change_selections(None, cx, |s| {
8112 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8113 })
8114 });
8115 cx.assert_editor_state(indoc! {"
8116 fn main() {
8117 sample(param1, ˇparam2);
8118 }
8119
8120 fn sample(param1: u8, param2: u8) {}
8121 "});
8122 handle_signature_help_request(&mut cx, mocked_response).await;
8123 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8124 .await;
8125 cx.editor(|editor, _| {
8126 assert!(editor.signature_help_state.is_shown());
8127 });
8128
8129 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8130 cx.update_editor(|editor, cx| {
8131 editor.change_selections(None, cx, |s| {
8132 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8133 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8134 })
8135 });
8136 cx.assert_editor_state(indoc! {"
8137 fn main() {
8138 sample(param1, ˇparam2);
8139 }
8140
8141 fn sample(param1: u8, param2: u8) {}
8142 "});
8143
8144 let mocked_response = lsp::SignatureHelp {
8145 signatures: vec![lsp::SignatureInformation {
8146 label: "fn sample(param1: u8, param2: u8)".to_string(),
8147 documentation: None,
8148 parameters: Some(vec![
8149 lsp::ParameterInformation {
8150 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8151 documentation: None,
8152 },
8153 lsp::ParameterInformation {
8154 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8155 documentation: None,
8156 },
8157 ]),
8158 active_parameter: None,
8159 }],
8160 active_signature: Some(0),
8161 active_parameter: Some(1),
8162 };
8163 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8164 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8165 .await;
8166 cx.update_editor(|editor, cx| {
8167 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8168 });
8169 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8170 .await;
8171 cx.update_editor(|editor, cx| {
8172 editor.change_selections(None, cx, |s| {
8173 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8174 })
8175 });
8176 cx.assert_editor_state(indoc! {"
8177 fn main() {
8178 sample(param1, «ˇparam2»);
8179 }
8180
8181 fn sample(param1: u8, param2: u8) {}
8182 "});
8183 cx.update_editor(|editor, cx| {
8184 editor.change_selections(None, cx, |s| {
8185 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8186 })
8187 });
8188 cx.assert_editor_state(indoc! {"
8189 fn main() {
8190 sample(param1, ˇparam2);
8191 }
8192
8193 fn sample(param1: u8, param2: u8) {}
8194 "});
8195 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8196 .await;
8197}
8198
8199#[gpui::test]
8200async fn test_completion(cx: &mut gpui::TestAppContext) {
8201 init_test(cx, |_| {});
8202
8203 let mut cx = EditorLspTestContext::new_rust(
8204 lsp::ServerCapabilities {
8205 completion_provider: Some(lsp::CompletionOptions {
8206 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8207 resolve_provider: Some(true),
8208 ..Default::default()
8209 }),
8210 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8211 ..Default::default()
8212 },
8213 cx,
8214 )
8215 .await;
8216 let counter = Arc::new(AtomicUsize::new(0));
8217
8218 cx.set_state(indoc! {"
8219 oneˇ
8220 two
8221 three
8222 "});
8223 cx.simulate_keystroke(".");
8224 handle_completion_request(
8225 &mut cx,
8226 indoc! {"
8227 one.|<>
8228 two
8229 three
8230 "},
8231 vec!["first_completion", "second_completion"],
8232 counter.clone(),
8233 )
8234 .await;
8235 cx.condition(|editor, _| editor.context_menu_visible())
8236 .await;
8237 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8238
8239 let _handler = handle_signature_help_request(
8240 &mut cx,
8241 lsp::SignatureHelp {
8242 signatures: vec![lsp::SignatureInformation {
8243 label: "test signature".to_string(),
8244 documentation: None,
8245 parameters: Some(vec![lsp::ParameterInformation {
8246 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8247 documentation: None,
8248 }]),
8249 active_parameter: None,
8250 }],
8251 active_signature: None,
8252 active_parameter: None,
8253 },
8254 );
8255 cx.update_editor(|editor, cx| {
8256 assert!(
8257 !editor.signature_help_state.is_shown(),
8258 "No signature help was called for"
8259 );
8260 editor.show_signature_help(&ShowSignatureHelp, cx);
8261 });
8262 cx.run_until_parked();
8263 cx.update_editor(|editor, _| {
8264 assert!(
8265 !editor.signature_help_state.is_shown(),
8266 "No signature help should be shown when completions menu is open"
8267 );
8268 });
8269
8270 let apply_additional_edits = cx.update_editor(|editor, cx| {
8271 editor.context_menu_next(&Default::default(), cx);
8272 editor
8273 .confirm_completion(&ConfirmCompletion::default(), cx)
8274 .unwrap()
8275 });
8276 cx.assert_editor_state(indoc! {"
8277 one.second_completionˇ
8278 two
8279 three
8280 "});
8281
8282 handle_resolve_completion_request(
8283 &mut cx,
8284 Some(vec![
8285 (
8286 //This overlaps with the primary completion edit which is
8287 //misbehavior from the LSP spec, test that we filter it out
8288 indoc! {"
8289 one.second_ˇcompletion
8290 two
8291 threeˇ
8292 "},
8293 "overlapping additional edit",
8294 ),
8295 (
8296 indoc! {"
8297 one.second_completion
8298 two
8299 threeˇ
8300 "},
8301 "\nadditional edit",
8302 ),
8303 ]),
8304 )
8305 .await;
8306 apply_additional_edits.await.unwrap();
8307 cx.assert_editor_state(indoc! {"
8308 one.second_completionˇ
8309 two
8310 three
8311 additional edit
8312 "});
8313
8314 cx.set_state(indoc! {"
8315 one.second_completion
8316 twoˇ
8317 threeˇ
8318 additional edit
8319 "});
8320 cx.simulate_keystroke(" ");
8321 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8322 cx.simulate_keystroke("s");
8323 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8324
8325 cx.assert_editor_state(indoc! {"
8326 one.second_completion
8327 two sˇ
8328 three sˇ
8329 additional edit
8330 "});
8331 handle_completion_request(
8332 &mut cx,
8333 indoc! {"
8334 one.second_completion
8335 two s
8336 three <s|>
8337 additional edit
8338 "},
8339 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8340 counter.clone(),
8341 )
8342 .await;
8343 cx.condition(|editor, _| editor.context_menu_visible())
8344 .await;
8345 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8346
8347 cx.simulate_keystroke("i");
8348
8349 handle_completion_request(
8350 &mut cx,
8351 indoc! {"
8352 one.second_completion
8353 two si
8354 three <si|>
8355 additional edit
8356 "},
8357 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8358 counter.clone(),
8359 )
8360 .await;
8361 cx.condition(|editor, _| editor.context_menu_visible())
8362 .await;
8363 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8364
8365 let apply_additional_edits = cx.update_editor(|editor, cx| {
8366 editor
8367 .confirm_completion(&ConfirmCompletion::default(), cx)
8368 .unwrap()
8369 });
8370 cx.assert_editor_state(indoc! {"
8371 one.second_completion
8372 two sixth_completionˇ
8373 three sixth_completionˇ
8374 additional edit
8375 "});
8376
8377 handle_resolve_completion_request(&mut cx, None).await;
8378 apply_additional_edits.await.unwrap();
8379
8380 cx.update(|cx| {
8381 cx.update_global::<SettingsStore, _>(|settings, cx| {
8382 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8383 settings.show_completions_on_input = Some(false);
8384 });
8385 })
8386 });
8387 cx.set_state("editorˇ");
8388 cx.simulate_keystroke(".");
8389 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8390 cx.simulate_keystroke("c");
8391 cx.simulate_keystroke("l");
8392 cx.simulate_keystroke("o");
8393 cx.assert_editor_state("editor.cloˇ");
8394 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8395 cx.update_editor(|editor, cx| {
8396 editor.show_completions(&ShowCompletions { trigger: None }, cx);
8397 });
8398 handle_completion_request(
8399 &mut cx,
8400 "editor.<clo|>",
8401 vec!["close", "clobber"],
8402 counter.clone(),
8403 )
8404 .await;
8405 cx.condition(|editor, _| editor.context_menu_visible())
8406 .await;
8407 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8408
8409 let apply_additional_edits = cx.update_editor(|editor, cx| {
8410 editor
8411 .confirm_completion(&ConfirmCompletion::default(), cx)
8412 .unwrap()
8413 });
8414 cx.assert_editor_state("editor.closeˇ");
8415 handle_resolve_completion_request(&mut cx, None).await;
8416 apply_additional_edits.await.unwrap();
8417}
8418
8419#[gpui::test]
8420async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8421 init_test(cx, |_| {});
8422 let mut cx = EditorLspTestContext::new_rust(
8423 lsp::ServerCapabilities {
8424 completion_provider: Some(lsp::CompletionOptions {
8425 trigger_characters: Some(vec![".".to_string()]),
8426 ..Default::default()
8427 }),
8428 ..Default::default()
8429 },
8430 cx,
8431 )
8432 .await;
8433 cx.lsp
8434 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8435 Ok(Some(lsp::CompletionResponse::Array(vec![
8436 lsp::CompletionItem {
8437 label: "first".into(),
8438 ..Default::default()
8439 },
8440 lsp::CompletionItem {
8441 label: "last".into(),
8442 ..Default::default()
8443 },
8444 ])))
8445 });
8446 cx.set_state("variableˇ");
8447 cx.simulate_keystroke(".");
8448 cx.executor().run_until_parked();
8449
8450 cx.update_editor(|editor, _| {
8451 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8452 assert_eq!(
8453 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8454 &["first", "last"]
8455 );
8456 } else {
8457 panic!("expected completion menu to be open");
8458 }
8459 });
8460
8461 cx.update_editor(|editor, cx| {
8462 editor.move_page_down(&MovePageDown::default(), cx);
8463 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8464 assert!(
8465 menu.selected_item == 1,
8466 "expected PageDown to select the last item from the context menu"
8467 );
8468 } else {
8469 panic!("expected completion menu to stay open after PageDown");
8470 }
8471 });
8472
8473 cx.update_editor(|editor, cx| {
8474 editor.move_page_up(&MovePageUp::default(), cx);
8475 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8476 assert!(
8477 menu.selected_item == 0,
8478 "expected PageUp to select the first item from the context menu"
8479 );
8480 } else {
8481 panic!("expected completion menu to stay open after PageUp");
8482 }
8483 });
8484}
8485
8486#[gpui::test]
8487async fn test_completion_sort(cx: &mut gpui::TestAppContext) {
8488 init_test(cx, |_| {});
8489 let mut cx = EditorLspTestContext::new_rust(
8490 lsp::ServerCapabilities {
8491 completion_provider: Some(lsp::CompletionOptions {
8492 trigger_characters: Some(vec![".".to_string()]),
8493 ..Default::default()
8494 }),
8495 ..Default::default()
8496 },
8497 cx,
8498 )
8499 .await;
8500 cx.lsp
8501 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8502 Ok(Some(lsp::CompletionResponse::Array(vec![
8503 lsp::CompletionItem {
8504 label: "Range".into(),
8505 sort_text: Some("a".into()),
8506 ..Default::default()
8507 },
8508 lsp::CompletionItem {
8509 label: "r".into(),
8510 sort_text: Some("b".into()),
8511 ..Default::default()
8512 },
8513 lsp::CompletionItem {
8514 label: "ret".into(),
8515 sort_text: Some("c".into()),
8516 ..Default::default()
8517 },
8518 lsp::CompletionItem {
8519 label: "return".into(),
8520 sort_text: Some("d".into()),
8521 ..Default::default()
8522 },
8523 lsp::CompletionItem {
8524 label: "slice".into(),
8525 sort_text: Some("d".into()),
8526 ..Default::default()
8527 },
8528 ])))
8529 });
8530 cx.set_state("rˇ");
8531 cx.executor().run_until_parked();
8532 cx.update_editor(|editor, cx| {
8533 editor.show_completions(
8534 &ShowCompletions {
8535 trigger: Some("r".into()),
8536 },
8537 cx,
8538 );
8539 });
8540 cx.executor().run_until_parked();
8541
8542 cx.update_editor(|editor, _| {
8543 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8544 assert_eq!(
8545 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8546 &["r", "ret", "Range", "return"]
8547 );
8548 } else {
8549 panic!("expected completion menu to be open");
8550 }
8551 });
8552}
8553
8554#[gpui::test]
8555async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8556 init_test(cx, |_| {});
8557
8558 let mut cx = EditorLspTestContext::new_rust(
8559 lsp::ServerCapabilities {
8560 completion_provider: Some(lsp::CompletionOptions {
8561 trigger_characters: Some(vec![".".to_string()]),
8562 resolve_provider: Some(true),
8563 ..Default::default()
8564 }),
8565 ..Default::default()
8566 },
8567 cx,
8568 )
8569 .await;
8570
8571 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8572 cx.simulate_keystroke(".");
8573 let completion_item = lsp::CompletionItem {
8574 label: "Some".into(),
8575 kind: Some(lsp::CompletionItemKind::SNIPPET),
8576 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8577 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8578 kind: lsp::MarkupKind::Markdown,
8579 value: "```rust\nSome(2)\n```".to_string(),
8580 })),
8581 deprecated: Some(false),
8582 sort_text: Some("Some".to_string()),
8583 filter_text: Some("Some".to_string()),
8584 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8585 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8586 range: lsp::Range {
8587 start: lsp::Position {
8588 line: 0,
8589 character: 22,
8590 },
8591 end: lsp::Position {
8592 line: 0,
8593 character: 22,
8594 },
8595 },
8596 new_text: "Some(2)".to_string(),
8597 })),
8598 additional_text_edits: Some(vec![lsp::TextEdit {
8599 range: lsp::Range {
8600 start: lsp::Position {
8601 line: 0,
8602 character: 20,
8603 },
8604 end: lsp::Position {
8605 line: 0,
8606 character: 22,
8607 },
8608 },
8609 new_text: "".to_string(),
8610 }]),
8611 ..Default::default()
8612 };
8613
8614 let closure_completion_item = completion_item.clone();
8615 let counter = Arc::new(AtomicUsize::new(0));
8616 let counter_clone = counter.clone();
8617 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8618 let task_completion_item = closure_completion_item.clone();
8619 counter_clone.fetch_add(1, atomic::Ordering::Release);
8620 async move {
8621 Ok(Some(lsp::CompletionResponse::Array(vec![
8622 task_completion_item,
8623 ])))
8624 }
8625 });
8626
8627 cx.condition(|editor, _| editor.context_menu_visible())
8628 .await;
8629 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8630 assert!(request.next().await.is_some());
8631 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8632
8633 cx.simulate_keystroke("S");
8634 cx.simulate_keystroke("o");
8635 cx.simulate_keystroke("m");
8636 cx.condition(|editor, _| editor.context_menu_visible())
8637 .await;
8638 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
8639 assert!(request.next().await.is_some());
8640 assert!(request.next().await.is_some());
8641 assert!(request.next().await.is_some());
8642 request.close();
8643 assert!(request.next().await.is_none());
8644 assert_eq!(
8645 counter.load(atomic::Ordering::Acquire),
8646 4,
8647 "With the completions menu open, only one LSP request should happen per input"
8648 );
8649}
8650
8651#[gpui::test]
8652async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
8653 init_test(cx, |_| {});
8654 let mut cx = EditorTestContext::new(cx).await;
8655 let language = Arc::new(Language::new(
8656 LanguageConfig {
8657 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8658 ..Default::default()
8659 },
8660 Some(tree_sitter_rust::LANGUAGE.into()),
8661 ));
8662 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8663
8664 // If multiple selections intersect a line, the line is only toggled once.
8665 cx.set_state(indoc! {"
8666 fn a() {
8667 «//b();
8668 ˇ»// «c();
8669 //ˇ» d();
8670 }
8671 "});
8672
8673 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8674
8675 cx.assert_editor_state(indoc! {"
8676 fn a() {
8677 «b();
8678 c();
8679 ˇ» d();
8680 }
8681 "});
8682
8683 // The comment prefix is inserted at the same column for every line in a
8684 // selection.
8685 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8686
8687 cx.assert_editor_state(indoc! {"
8688 fn a() {
8689 // «b();
8690 // c();
8691 ˇ»// d();
8692 }
8693 "});
8694
8695 // If a selection ends at the beginning of a line, that line is not toggled.
8696 cx.set_selections_state(indoc! {"
8697 fn a() {
8698 // b();
8699 «// c();
8700 ˇ» // d();
8701 }
8702 "});
8703
8704 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8705
8706 cx.assert_editor_state(indoc! {"
8707 fn a() {
8708 // b();
8709 «c();
8710 ˇ» // d();
8711 }
8712 "});
8713
8714 // If a selection span a single line and is empty, the line is toggled.
8715 cx.set_state(indoc! {"
8716 fn a() {
8717 a();
8718 b();
8719 ˇ
8720 }
8721 "});
8722
8723 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8724
8725 cx.assert_editor_state(indoc! {"
8726 fn a() {
8727 a();
8728 b();
8729 //•ˇ
8730 }
8731 "});
8732
8733 // If a selection span multiple lines, empty lines are not toggled.
8734 cx.set_state(indoc! {"
8735 fn a() {
8736 «a();
8737
8738 c();ˇ»
8739 }
8740 "});
8741
8742 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8743
8744 cx.assert_editor_state(indoc! {"
8745 fn a() {
8746 // «a();
8747
8748 // c();ˇ»
8749 }
8750 "});
8751
8752 // If a selection includes multiple comment prefixes, all lines are uncommented.
8753 cx.set_state(indoc! {"
8754 fn a() {
8755 «// a();
8756 /// b();
8757 //! c();ˇ»
8758 }
8759 "});
8760
8761 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8762
8763 cx.assert_editor_state(indoc! {"
8764 fn a() {
8765 «a();
8766 b();
8767 c();ˇ»
8768 }
8769 "});
8770}
8771
8772#[gpui::test]
8773async fn test_toggle_comment_ignore_indent(cx: &mut gpui::TestAppContext) {
8774 init_test(cx, |_| {});
8775 let mut cx = EditorTestContext::new(cx).await;
8776 let language = Arc::new(Language::new(
8777 LanguageConfig {
8778 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8779 ..Default::default()
8780 },
8781 Some(tree_sitter_rust::LANGUAGE.into()),
8782 ));
8783 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8784
8785 let toggle_comments = &ToggleComments {
8786 advance_downwards: false,
8787 ignore_indent: true,
8788 };
8789
8790 // If multiple selections intersect a line, the line is only toggled once.
8791 cx.set_state(indoc! {"
8792 fn a() {
8793 // «b();
8794 // c();
8795 // ˇ» d();
8796 }
8797 "});
8798
8799 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8800
8801 cx.assert_editor_state(indoc! {"
8802 fn a() {
8803 «b();
8804 c();
8805 ˇ» d();
8806 }
8807 "});
8808
8809 // The comment prefix is inserted at the beginning of each line
8810 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8811
8812 cx.assert_editor_state(indoc! {"
8813 fn a() {
8814 // «b();
8815 // c();
8816 // ˇ» d();
8817 }
8818 "});
8819
8820 // If a selection ends at the beginning of a line, that line is not toggled.
8821 cx.set_selections_state(indoc! {"
8822 fn a() {
8823 // b();
8824 // «c();
8825 ˇ»// d();
8826 }
8827 "});
8828
8829 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8830
8831 cx.assert_editor_state(indoc! {"
8832 fn a() {
8833 // b();
8834 «c();
8835 ˇ»// d();
8836 }
8837 "});
8838
8839 // If a selection span a single line and is empty, the line is toggled.
8840 cx.set_state(indoc! {"
8841 fn a() {
8842 a();
8843 b();
8844 ˇ
8845 }
8846 "});
8847
8848 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8849
8850 cx.assert_editor_state(indoc! {"
8851 fn a() {
8852 a();
8853 b();
8854 //ˇ
8855 }
8856 "});
8857
8858 // If a selection span multiple lines, empty lines are not toggled.
8859 cx.set_state(indoc! {"
8860 fn a() {
8861 «a();
8862
8863 c();ˇ»
8864 }
8865 "});
8866
8867 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8868
8869 cx.assert_editor_state(indoc! {"
8870 fn a() {
8871 // «a();
8872
8873 // c();ˇ»
8874 }
8875 "});
8876
8877 // If a selection includes multiple comment prefixes, all lines are uncommented.
8878 cx.set_state(indoc! {"
8879 fn a() {
8880 // «a();
8881 /// b();
8882 //! c();ˇ»
8883 }
8884 "});
8885
8886 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8887
8888 cx.assert_editor_state(indoc! {"
8889 fn a() {
8890 «a();
8891 b();
8892 c();ˇ»
8893 }
8894 "});
8895}
8896
8897#[gpui::test]
8898async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8899 init_test(cx, |_| {});
8900
8901 let language = Arc::new(Language::new(
8902 LanguageConfig {
8903 line_comments: vec!["// ".into()],
8904 ..Default::default()
8905 },
8906 Some(tree_sitter_rust::LANGUAGE.into()),
8907 ));
8908
8909 let mut cx = EditorTestContext::new(cx).await;
8910
8911 cx.language_registry().add(language.clone());
8912 cx.update_buffer(|buffer, cx| {
8913 buffer.set_language(Some(language), cx);
8914 });
8915
8916 let toggle_comments = &ToggleComments {
8917 advance_downwards: true,
8918 ignore_indent: false,
8919 };
8920
8921 // Single cursor on one line -> advance
8922 // Cursor moves horizontally 3 characters as well on non-blank line
8923 cx.set_state(indoc!(
8924 "fn a() {
8925 ˇdog();
8926 cat();
8927 }"
8928 ));
8929 cx.update_editor(|editor, cx| {
8930 editor.toggle_comments(toggle_comments, cx);
8931 });
8932 cx.assert_editor_state(indoc!(
8933 "fn a() {
8934 // dog();
8935 catˇ();
8936 }"
8937 ));
8938
8939 // Single selection on one line -> don't advance
8940 cx.set_state(indoc!(
8941 "fn a() {
8942 «dog()ˇ»;
8943 cat();
8944 }"
8945 ));
8946 cx.update_editor(|editor, cx| {
8947 editor.toggle_comments(toggle_comments, cx);
8948 });
8949 cx.assert_editor_state(indoc!(
8950 "fn a() {
8951 // «dog()ˇ»;
8952 cat();
8953 }"
8954 ));
8955
8956 // Multiple cursors on one line -> advance
8957 cx.set_state(indoc!(
8958 "fn a() {
8959 ˇdˇog();
8960 cat();
8961 }"
8962 ));
8963 cx.update_editor(|editor, cx| {
8964 editor.toggle_comments(toggle_comments, cx);
8965 });
8966 cx.assert_editor_state(indoc!(
8967 "fn a() {
8968 // dog();
8969 catˇ(ˇ);
8970 }"
8971 ));
8972
8973 // Multiple cursors on one line, with selection -> don't advance
8974 cx.set_state(indoc!(
8975 "fn a() {
8976 ˇdˇog«()ˇ»;
8977 cat();
8978 }"
8979 ));
8980 cx.update_editor(|editor, cx| {
8981 editor.toggle_comments(toggle_comments, cx);
8982 });
8983 cx.assert_editor_state(indoc!(
8984 "fn a() {
8985 // ˇdˇog«()ˇ»;
8986 cat();
8987 }"
8988 ));
8989
8990 // Single cursor on one line -> advance
8991 // Cursor moves to column 0 on blank line
8992 cx.set_state(indoc!(
8993 "fn a() {
8994 ˇdog();
8995
8996 cat();
8997 }"
8998 ));
8999 cx.update_editor(|editor, cx| {
9000 editor.toggle_comments(toggle_comments, cx);
9001 });
9002 cx.assert_editor_state(indoc!(
9003 "fn a() {
9004 // dog();
9005 ˇ
9006 cat();
9007 }"
9008 ));
9009
9010 // Single cursor on one line -> advance
9011 // Cursor starts and ends at column 0
9012 cx.set_state(indoc!(
9013 "fn a() {
9014 ˇ dog();
9015 cat();
9016 }"
9017 ));
9018 cx.update_editor(|editor, cx| {
9019 editor.toggle_comments(toggle_comments, cx);
9020 });
9021 cx.assert_editor_state(indoc!(
9022 "fn a() {
9023 // dog();
9024 ˇ cat();
9025 }"
9026 ));
9027}
9028
9029#[gpui::test]
9030async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
9031 init_test(cx, |_| {});
9032
9033 let mut cx = EditorTestContext::new(cx).await;
9034
9035 let html_language = Arc::new(
9036 Language::new(
9037 LanguageConfig {
9038 name: "HTML".into(),
9039 block_comment: Some(("<!-- ".into(), " -->".into())),
9040 ..Default::default()
9041 },
9042 Some(tree_sitter_html::language()),
9043 )
9044 .with_injection_query(
9045 r#"
9046 (script_element
9047 (raw_text) @content
9048 (#set! "language" "javascript"))
9049 "#,
9050 )
9051 .unwrap(),
9052 );
9053
9054 let javascript_language = Arc::new(Language::new(
9055 LanguageConfig {
9056 name: "JavaScript".into(),
9057 line_comments: vec!["// ".into()],
9058 ..Default::default()
9059 },
9060 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
9061 ));
9062
9063 cx.language_registry().add(html_language.clone());
9064 cx.language_registry().add(javascript_language.clone());
9065 cx.update_buffer(|buffer, cx| {
9066 buffer.set_language(Some(html_language), cx);
9067 });
9068
9069 // Toggle comments for empty selections
9070 cx.set_state(
9071 &r#"
9072 <p>A</p>ˇ
9073 <p>B</p>ˇ
9074 <p>C</p>ˇ
9075 "#
9076 .unindent(),
9077 );
9078 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9079 cx.assert_editor_state(
9080 &r#"
9081 <!-- <p>A</p>ˇ -->
9082 <!-- <p>B</p>ˇ -->
9083 <!-- <p>C</p>ˇ -->
9084 "#
9085 .unindent(),
9086 );
9087 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9088 cx.assert_editor_state(
9089 &r#"
9090 <p>A</p>ˇ
9091 <p>B</p>ˇ
9092 <p>C</p>ˇ
9093 "#
9094 .unindent(),
9095 );
9096
9097 // Toggle comments for mixture of empty and non-empty selections, where
9098 // multiple selections occupy a given line.
9099 cx.set_state(
9100 &r#"
9101 <p>A«</p>
9102 <p>ˇ»B</p>ˇ
9103 <p>C«</p>
9104 <p>ˇ»D</p>ˇ
9105 "#
9106 .unindent(),
9107 );
9108
9109 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9110 cx.assert_editor_state(
9111 &r#"
9112 <!-- <p>A«</p>
9113 <p>ˇ»B</p>ˇ -->
9114 <!-- <p>C«</p>
9115 <p>ˇ»D</p>ˇ -->
9116 "#
9117 .unindent(),
9118 );
9119 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9120 cx.assert_editor_state(
9121 &r#"
9122 <p>A«</p>
9123 <p>ˇ»B</p>ˇ
9124 <p>C«</p>
9125 <p>ˇ»D</p>ˇ
9126 "#
9127 .unindent(),
9128 );
9129
9130 // Toggle comments when different languages are active for different
9131 // selections.
9132 cx.set_state(
9133 &r#"
9134 ˇ<script>
9135 ˇvar x = new Y();
9136 ˇ</script>
9137 "#
9138 .unindent(),
9139 );
9140 cx.executor().run_until_parked();
9141 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9142 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
9143 // Uncommenting and commenting from this position brings in even more wrong artifacts.
9144 cx.assert_editor_state(
9145 &r#"
9146 <!-- ˇ<script> -->
9147 // ˇvar x = new Y();
9148 // ˇ</script>
9149 "#
9150 .unindent(),
9151 );
9152}
9153
9154#[gpui::test]
9155fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
9156 init_test(cx, |_| {});
9157
9158 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9159 let multibuffer = cx.new_model(|cx| {
9160 let mut multibuffer = MultiBuffer::new(ReadWrite);
9161 multibuffer.push_excerpts(
9162 buffer.clone(),
9163 [
9164 ExcerptRange {
9165 context: Point::new(0, 0)..Point::new(0, 4),
9166 primary: None,
9167 },
9168 ExcerptRange {
9169 context: Point::new(1, 0)..Point::new(1, 4),
9170 primary: None,
9171 },
9172 ],
9173 cx,
9174 );
9175 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
9176 multibuffer
9177 });
9178
9179 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9180 view.update(cx, |view, cx| {
9181 assert_eq!(view.text(cx), "aaaa\nbbbb");
9182 view.change_selections(None, cx, |s| {
9183 s.select_ranges([
9184 Point::new(0, 0)..Point::new(0, 0),
9185 Point::new(1, 0)..Point::new(1, 0),
9186 ])
9187 });
9188
9189 view.handle_input("X", cx);
9190 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
9191 assert_eq!(
9192 view.selections.ranges(cx),
9193 [
9194 Point::new(0, 1)..Point::new(0, 1),
9195 Point::new(1, 1)..Point::new(1, 1),
9196 ]
9197 );
9198
9199 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
9200 view.change_selections(None, cx, |s| {
9201 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
9202 });
9203 view.backspace(&Default::default(), cx);
9204 assert_eq!(view.text(cx), "Xa\nbbb");
9205 assert_eq!(
9206 view.selections.ranges(cx),
9207 [Point::new(1, 0)..Point::new(1, 0)]
9208 );
9209
9210 view.change_selections(None, cx, |s| {
9211 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
9212 });
9213 view.backspace(&Default::default(), cx);
9214 assert_eq!(view.text(cx), "X\nbb");
9215 assert_eq!(
9216 view.selections.ranges(cx),
9217 [Point::new(0, 1)..Point::new(0, 1)]
9218 );
9219 });
9220}
9221
9222#[gpui::test]
9223fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
9224 init_test(cx, |_| {});
9225
9226 let markers = vec![('[', ']').into(), ('(', ')').into()];
9227 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
9228 indoc! {"
9229 [aaaa
9230 (bbbb]
9231 cccc)",
9232 },
9233 markers.clone(),
9234 );
9235 let excerpt_ranges = markers.into_iter().map(|marker| {
9236 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9237 ExcerptRange {
9238 context,
9239 primary: None,
9240 }
9241 });
9242 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
9243 let multibuffer = cx.new_model(|cx| {
9244 let mut multibuffer = MultiBuffer::new(ReadWrite);
9245 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9246 multibuffer
9247 });
9248
9249 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9250 view.update(cx, |view, cx| {
9251 let (expected_text, selection_ranges) = marked_text_ranges(
9252 indoc! {"
9253 aaaa
9254 bˇbbb
9255 bˇbbˇb
9256 cccc"
9257 },
9258 true,
9259 );
9260 assert_eq!(view.text(cx), expected_text);
9261 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
9262
9263 view.handle_input("X", cx);
9264
9265 let (expected_text, expected_selections) = marked_text_ranges(
9266 indoc! {"
9267 aaaa
9268 bXˇbbXb
9269 bXˇbbXˇb
9270 cccc"
9271 },
9272 false,
9273 );
9274 assert_eq!(view.text(cx), expected_text);
9275 assert_eq!(view.selections.ranges(cx), expected_selections);
9276
9277 view.newline(&Newline, cx);
9278 let (expected_text, expected_selections) = marked_text_ranges(
9279 indoc! {"
9280 aaaa
9281 bX
9282 ˇbbX
9283 b
9284 bX
9285 ˇbbX
9286 ˇb
9287 cccc"
9288 },
9289 false,
9290 );
9291 assert_eq!(view.text(cx), expected_text);
9292 assert_eq!(view.selections.ranges(cx), expected_selections);
9293 });
9294}
9295
9296#[gpui::test]
9297fn test_refresh_selections(cx: &mut TestAppContext) {
9298 init_test(cx, |_| {});
9299
9300 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9301 let mut excerpt1_id = None;
9302 let multibuffer = cx.new_model(|cx| {
9303 let mut multibuffer = MultiBuffer::new(ReadWrite);
9304 excerpt1_id = multibuffer
9305 .push_excerpts(
9306 buffer.clone(),
9307 [
9308 ExcerptRange {
9309 context: Point::new(0, 0)..Point::new(1, 4),
9310 primary: None,
9311 },
9312 ExcerptRange {
9313 context: Point::new(1, 0)..Point::new(2, 4),
9314 primary: None,
9315 },
9316 ],
9317 cx,
9318 )
9319 .into_iter()
9320 .next();
9321 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9322 multibuffer
9323 });
9324
9325 let editor = cx.add_window(|cx| {
9326 let mut editor = build_editor(multibuffer.clone(), cx);
9327 let snapshot = editor.snapshot(cx);
9328 editor.change_selections(None, cx, |s| {
9329 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
9330 });
9331 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
9332 assert_eq!(
9333 editor.selections.ranges(cx),
9334 [
9335 Point::new(1, 3)..Point::new(1, 3),
9336 Point::new(2, 1)..Point::new(2, 1),
9337 ]
9338 );
9339 editor
9340 });
9341
9342 // Refreshing selections is a no-op when excerpts haven't changed.
9343 _ = editor.update(cx, |editor, cx| {
9344 editor.change_selections(None, cx, |s| s.refresh());
9345 assert_eq!(
9346 editor.selections.ranges(cx),
9347 [
9348 Point::new(1, 3)..Point::new(1, 3),
9349 Point::new(2, 1)..Point::new(2, 1),
9350 ]
9351 );
9352 });
9353
9354 multibuffer.update(cx, |multibuffer, cx| {
9355 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9356 });
9357 _ = editor.update(cx, |editor, cx| {
9358 // Removing an excerpt causes the first selection to become degenerate.
9359 assert_eq!(
9360 editor.selections.ranges(cx),
9361 [
9362 Point::new(0, 0)..Point::new(0, 0),
9363 Point::new(0, 1)..Point::new(0, 1)
9364 ]
9365 );
9366
9367 // Refreshing selections will relocate the first selection to the original buffer
9368 // location.
9369 editor.change_selections(None, cx, |s| s.refresh());
9370 assert_eq!(
9371 editor.selections.ranges(cx),
9372 [
9373 Point::new(0, 1)..Point::new(0, 1),
9374 Point::new(0, 3)..Point::new(0, 3)
9375 ]
9376 );
9377 assert!(editor.selections.pending_anchor().is_some());
9378 });
9379}
9380
9381#[gpui::test]
9382fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
9383 init_test(cx, |_| {});
9384
9385 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9386 let mut excerpt1_id = None;
9387 let multibuffer = cx.new_model(|cx| {
9388 let mut multibuffer = MultiBuffer::new(ReadWrite);
9389 excerpt1_id = multibuffer
9390 .push_excerpts(
9391 buffer.clone(),
9392 [
9393 ExcerptRange {
9394 context: Point::new(0, 0)..Point::new(1, 4),
9395 primary: None,
9396 },
9397 ExcerptRange {
9398 context: Point::new(1, 0)..Point::new(2, 4),
9399 primary: None,
9400 },
9401 ],
9402 cx,
9403 )
9404 .into_iter()
9405 .next();
9406 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9407 multibuffer
9408 });
9409
9410 let editor = cx.add_window(|cx| {
9411 let mut editor = build_editor(multibuffer.clone(), cx);
9412 let snapshot = editor.snapshot(cx);
9413 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
9414 assert_eq!(
9415 editor.selections.ranges(cx),
9416 [Point::new(1, 3)..Point::new(1, 3)]
9417 );
9418 editor
9419 });
9420
9421 multibuffer.update(cx, |multibuffer, cx| {
9422 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9423 });
9424 _ = editor.update(cx, |editor, cx| {
9425 assert_eq!(
9426 editor.selections.ranges(cx),
9427 [Point::new(0, 0)..Point::new(0, 0)]
9428 );
9429
9430 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
9431 editor.change_selections(None, cx, |s| s.refresh());
9432 assert_eq!(
9433 editor.selections.ranges(cx),
9434 [Point::new(0, 3)..Point::new(0, 3)]
9435 );
9436 assert!(editor.selections.pending_anchor().is_some());
9437 });
9438}
9439
9440#[gpui::test]
9441async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
9442 init_test(cx, |_| {});
9443
9444 let language = Arc::new(
9445 Language::new(
9446 LanguageConfig {
9447 brackets: BracketPairConfig {
9448 pairs: vec![
9449 BracketPair {
9450 start: "{".to_string(),
9451 end: "}".to_string(),
9452 close: true,
9453 surround: true,
9454 newline: true,
9455 },
9456 BracketPair {
9457 start: "/* ".to_string(),
9458 end: " */".to_string(),
9459 close: true,
9460 surround: true,
9461 newline: true,
9462 },
9463 ],
9464 ..Default::default()
9465 },
9466 ..Default::default()
9467 },
9468 Some(tree_sitter_rust::LANGUAGE.into()),
9469 )
9470 .with_indents_query("")
9471 .unwrap(),
9472 );
9473
9474 let text = concat!(
9475 "{ }\n", //
9476 " x\n", //
9477 " /* */\n", //
9478 "x\n", //
9479 "{{} }\n", //
9480 );
9481
9482 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
9483 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9484 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9485 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
9486 .await;
9487
9488 view.update(cx, |view, cx| {
9489 view.change_selections(None, cx, |s| {
9490 s.select_display_ranges([
9491 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
9492 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
9493 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
9494 ])
9495 });
9496 view.newline(&Newline, cx);
9497
9498 assert_eq!(
9499 view.buffer().read(cx).read(cx).text(),
9500 concat!(
9501 "{ \n", // Suppress rustfmt
9502 "\n", //
9503 "}\n", //
9504 " x\n", //
9505 " /* \n", //
9506 " \n", //
9507 " */\n", //
9508 "x\n", //
9509 "{{} \n", //
9510 "}\n", //
9511 )
9512 );
9513 });
9514}
9515
9516#[gpui::test]
9517fn test_highlighted_ranges(cx: &mut TestAppContext) {
9518 init_test(cx, |_| {});
9519
9520 let editor = cx.add_window(|cx| {
9521 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
9522 build_editor(buffer.clone(), cx)
9523 });
9524
9525 _ = editor.update(cx, |editor, cx| {
9526 struct Type1;
9527 struct Type2;
9528
9529 let buffer = editor.buffer.read(cx).snapshot(cx);
9530
9531 let anchor_range =
9532 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
9533
9534 editor.highlight_background::<Type1>(
9535 &[
9536 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
9537 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
9538 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
9539 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
9540 ],
9541 |_| Hsla::red(),
9542 cx,
9543 );
9544 editor.highlight_background::<Type2>(
9545 &[
9546 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
9547 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
9548 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
9549 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
9550 ],
9551 |_| Hsla::green(),
9552 cx,
9553 );
9554
9555 let snapshot = editor.snapshot(cx);
9556 let mut highlighted_ranges = editor.background_highlights_in_range(
9557 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
9558 &snapshot,
9559 cx.theme().colors(),
9560 );
9561 // Enforce a consistent ordering based on color without relying on the ordering of the
9562 // highlight's `TypeId` which is non-executor.
9563 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
9564 assert_eq!(
9565 highlighted_ranges,
9566 &[
9567 (
9568 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
9569 Hsla::red(),
9570 ),
9571 (
9572 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9573 Hsla::red(),
9574 ),
9575 (
9576 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9577 Hsla::green(),
9578 ),
9579 (
9580 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9581 Hsla::green(),
9582 ),
9583 ]
9584 );
9585 assert_eq!(
9586 editor.background_highlights_in_range(
9587 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9588 &snapshot,
9589 cx.theme().colors(),
9590 ),
9591 &[(
9592 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9593 Hsla::red(),
9594 )]
9595 );
9596 });
9597}
9598
9599#[gpui::test]
9600async fn test_following(cx: &mut gpui::TestAppContext) {
9601 init_test(cx, |_| {});
9602
9603 let fs = FakeFs::new(cx.executor());
9604 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9605
9606 let buffer = project.update(cx, |project, cx| {
9607 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9608 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9609 });
9610 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9611 let follower = cx.update(|cx| {
9612 cx.open_window(
9613 WindowOptions {
9614 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9615 gpui::Point::new(px(0.), px(0.)),
9616 gpui::Point::new(px(10.), px(80.)),
9617 ))),
9618 ..Default::default()
9619 },
9620 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9621 )
9622 .unwrap()
9623 });
9624
9625 let is_still_following = Rc::new(RefCell::new(true));
9626 let follower_edit_event_count = Rc::new(RefCell::new(0));
9627 let pending_update = Rc::new(RefCell::new(None));
9628 _ = follower.update(cx, {
9629 let update = pending_update.clone();
9630 let is_still_following = is_still_following.clone();
9631 let follower_edit_event_count = follower_edit_event_count.clone();
9632 |_, cx| {
9633 cx.subscribe(
9634 &leader.root_view(cx).unwrap(),
9635 move |_, leader, event, cx| {
9636 leader
9637 .read(cx)
9638 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9639 },
9640 )
9641 .detach();
9642
9643 cx.subscribe(
9644 &follower.root_view(cx).unwrap(),
9645 move |_, _, event: &EditorEvent, _cx| {
9646 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
9647 *is_still_following.borrow_mut() = false;
9648 }
9649
9650 if let EditorEvent::BufferEdited = event {
9651 *follower_edit_event_count.borrow_mut() += 1;
9652 }
9653 },
9654 )
9655 .detach();
9656 }
9657 });
9658
9659 // Update the selections only
9660 _ = leader.update(cx, |leader, cx| {
9661 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9662 });
9663 follower
9664 .update(cx, |follower, cx| {
9665 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9666 })
9667 .unwrap()
9668 .await
9669 .unwrap();
9670 _ = follower.update(cx, |follower, cx| {
9671 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
9672 });
9673 assert!(*is_still_following.borrow());
9674 assert_eq!(*follower_edit_event_count.borrow(), 0);
9675
9676 // Update the scroll position only
9677 _ = leader.update(cx, |leader, cx| {
9678 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9679 });
9680 follower
9681 .update(cx, |follower, cx| {
9682 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9683 })
9684 .unwrap()
9685 .await
9686 .unwrap();
9687 assert_eq!(
9688 follower
9689 .update(cx, |follower, cx| follower.scroll_position(cx))
9690 .unwrap(),
9691 gpui::Point::new(1.5, 3.5)
9692 );
9693 assert!(*is_still_following.borrow());
9694 assert_eq!(*follower_edit_event_count.borrow(), 0);
9695
9696 // Update the selections and scroll position. The follower's scroll position is updated
9697 // via autoscroll, not via the leader's exact scroll position.
9698 _ = leader.update(cx, |leader, cx| {
9699 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
9700 leader.request_autoscroll(Autoscroll::newest(), cx);
9701 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9702 });
9703 follower
9704 .update(cx, |follower, cx| {
9705 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9706 })
9707 .unwrap()
9708 .await
9709 .unwrap();
9710 _ = follower.update(cx, |follower, cx| {
9711 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
9712 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
9713 });
9714 assert!(*is_still_following.borrow());
9715
9716 // Creating a pending selection that precedes another selection
9717 _ = leader.update(cx, |leader, cx| {
9718 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9719 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
9720 });
9721 follower
9722 .update(cx, |follower, cx| {
9723 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9724 })
9725 .unwrap()
9726 .await
9727 .unwrap();
9728 _ = follower.update(cx, |follower, cx| {
9729 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
9730 });
9731 assert!(*is_still_following.borrow());
9732
9733 // Extend the pending selection so that it surrounds another selection
9734 _ = leader.update(cx, |leader, cx| {
9735 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
9736 });
9737 follower
9738 .update(cx, |follower, cx| {
9739 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9740 })
9741 .unwrap()
9742 .await
9743 .unwrap();
9744 _ = follower.update(cx, |follower, cx| {
9745 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
9746 });
9747
9748 // Scrolling locally breaks the follow
9749 _ = follower.update(cx, |follower, cx| {
9750 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
9751 follower.set_scroll_anchor(
9752 ScrollAnchor {
9753 anchor: top_anchor,
9754 offset: gpui::Point::new(0.0, 0.5),
9755 },
9756 cx,
9757 );
9758 });
9759 assert!(!(*is_still_following.borrow()));
9760}
9761
9762#[gpui::test]
9763async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
9764 init_test(cx, |_| {});
9765
9766 let fs = FakeFs::new(cx.executor());
9767 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9768 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9769 let pane = workspace
9770 .update(cx, |workspace, _| workspace.active_pane().clone())
9771 .unwrap();
9772
9773 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9774
9775 let leader = pane.update(cx, |_, cx| {
9776 let multibuffer = cx.new_model(|_| MultiBuffer::new(ReadWrite));
9777 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
9778 });
9779
9780 // Start following the editor when it has no excerpts.
9781 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9782 let follower_1 = cx
9783 .update_window(*workspace.deref(), |_, cx| {
9784 Editor::from_state_proto(
9785 workspace.root_view(cx).unwrap(),
9786 ViewId {
9787 creator: Default::default(),
9788 id: 0,
9789 },
9790 &mut state_message,
9791 cx,
9792 )
9793 })
9794 .unwrap()
9795 .unwrap()
9796 .await
9797 .unwrap();
9798
9799 let update_message = Rc::new(RefCell::new(None));
9800 follower_1.update(cx, {
9801 let update = update_message.clone();
9802 |_, cx| {
9803 cx.subscribe(&leader, move |_, leader, event, cx| {
9804 leader
9805 .read(cx)
9806 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9807 })
9808 .detach();
9809 }
9810 });
9811
9812 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
9813 (
9814 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9815 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9816 )
9817 });
9818
9819 // Insert some excerpts.
9820 leader.update(cx, |leader, cx| {
9821 leader.buffer.update(cx, |multibuffer, cx| {
9822 let excerpt_ids = multibuffer.push_excerpts(
9823 buffer_1.clone(),
9824 [
9825 ExcerptRange {
9826 context: 1..6,
9827 primary: None,
9828 },
9829 ExcerptRange {
9830 context: 12..15,
9831 primary: None,
9832 },
9833 ExcerptRange {
9834 context: 0..3,
9835 primary: None,
9836 },
9837 ],
9838 cx,
9839 );
9840 multibuffer.insert_excerpts_after(
9841 excerpt_ids[0],
9842 buffer_2.clone(),
9843 [
9844 ExcerptRange {
9845 context: 8..12,
9846 primary: None,
9847 },
9848 ExcerptRange {
9849 context: 0..6,
9850 primary: None,
9851 },
9852 ],
9853 cx,
9854 );
9855 });
9856 });
9857
9858 // Apply the update of adding the excerpts.
9859 follower_1
9860 .update(cx, |follower, cx| {
9861 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9862 })
9863 .await
9864 .unwrap();
9865 assert_eq!(
9866 follower_1.update(cx, |editor, cx| editor.text(cx)),
9867 leader.update(cx, |editor, cx| editor.text(cx))
9868 );
9869 update_message.borrow_mut().take();
9870
9871 // Start following separately after it already has excerpts.
9872 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9873 let follower_2 = cx
9874 .update_window(*workspace.deref(), |_, cx| {
9875 Editor::from_state_proto(
9876 workspace.root_view(cx).unwrap().clone(),
9877 ViewId {
9878 creator: Default::default(),
9879 id: 0,
9880 },
9881 &mut state_message,
9882 cx,
9883 )
9884 })
9885 .unwrap()
9886 .unwrap()
9887 .await
9888 .unwrap();
9889 assert_eq!(
9890 follower_2.update(cx, |editor, cx| editor.text(cx)),
9891 leader.update(cx, |editor, cx| editor.text(cx))
9892 );
9893
9894 // Remove some excerpts.
9895 leader.update(cx, |leader, cx| {
9896 leader.buffer.update(cx, |multibuffer, cx| {
9897 let excerpt_ids = multibuffer.excerpt_ids();
9898 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9899 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9900 });
9901 });
9902
9903 // Apply the update of removing the excerpts.
9904 follower_1
9905 .update(cx, |follower, cx| {
9906 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9907 })
9908 .await
9909 .unwrap();
9910 follower_2
9911 .update(cx, |follower, cx| {
9912 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9913 })
9914 .await
9915 .unwrap();
9916 update_message.borrow_mut().take();
9917 assert_eq!(
9918 follower_1.update(cx, |editor, cx| editor.text(cx)),
9919 leader.update(cx, |editor, cx| editor.text(cx))
9920 );
9921}
9922
9923#[gpui::test]
9924async fn go_to_prev_overlapping_diagnostic(
9925 executor: BackgroundExecutor,
9926 cx: &mut gpui::TestAppContext,
9927) {
9928 init_test(cx, |_| {});
9929
9930 let mut cx = EditorTestContext::new(cx).await;
9931 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9932
9933 cx.set_state(indoc! {"
9934 ˇfn func(abc def: i32) -> u32 {
9935 }
9936 "});
9937
9938 cx.update(|cx| {
9939 project.update(cx, |project, cx| {
9940 project
9941 .update_diagnostics(
9942 LanguageServerId(0),
9943 lsp::PublishDiagnosticsParams {
9944 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9945 version: None,
9946 diagnostics: vec![
9947 lsp::Diagnostic {
9948 range: lsp::Range::new(
9949 lsp::Position::new(0, 11),
9950 lsp::Position::new(0, 12),
9951 ),
9952 severity: Some(lsp::DiagnosticSeverity::ERROR),
9953 ..Default::default()
9954 },
9955 lsp::Diagnostic {
9956 range: lsp::Range::new(
9957 lsp::Position::new(0, 12),
9958 lsp::Position::new(0, 15),
9959 ),
9960 severity: Some(lsp::DiagnosticSeverity::ERROR),
9961 ..Default::default()
9962 },
9963 lsp::Diagnostic {
9964 range: lsp::Range::new(
9965 lsp::Position::new(0, 25),
9966 lsp::Position::new(0, 28),
9967 ),
9968 severity: Some(lsp::DiagnosticSeverity::ERROR),
9969 ..Default::default()
9970 },
9971 ],
9972 },
9973 &[],
9974 cx,
9975 )
9976 .unwrap()
9977 });
9978 });
9979
9980 executor.run_until_parked();
9981
9982 cx.update_editor(|editor, cx| {
9983 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9984 });
9985
9986 cx.assert_editor_state(indoc! {"
9987 fn func(abc def: i32) -> ˇu32 {
9988 }
9989 "});
9990
9991 cx.update_editor(|editor, cx| {
9992 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9993 });
9994
9995 cx.assert_editor_state(indoc! {"
9996 fn func(abc ˇdef: i32) -> u32 {
9997 }
9998 "});
9999
10000 cx.update_editor(|editor, cx| {
10001 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10002 });
10003
10004 cx.assert_editor_state(indoc! {"
10005 fn func(abcˇ def: i32) -> u32 {
10006 }
10007 "});
10008
10009 cx.update_editor(|editor, cx| {
10010 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10011 });
10012
10013 cx.assert_editor_state(indoc! {"
10014 fn func(abc def: i32) -> ˇu32 {
10015 }
10016 "});
10017}
10018
10019#[gpui::test]
10020async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
10021 init_test(cx, |_| {});
10022
10023 let mut cx = EditorTestContext::new(cx).await;
10024
10025 cx.set_state(indoc! {"
10026 fn func(abˇc def: i32) -> u32 {
10027 }
10028 "});
10029 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
10030
10031 cx.update(|cx| {
10032 project.update(cx, |project, cx| {
10033 project.update_diagnostics(
10034 LanguageServerId(0),
10035 lsp::PublishDiagnosticsParams {
10036 uri: lsp::Url::from_file_path("/root/file").unwrap(),
10037 version: None,
10038 diagnostics: vec![lsp::Diagnostic {
10039 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
10040 severity: Some(lsp::DiagnosticSeverity::ERROR),
10041 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
10042 ..Default::default()
10043 }],
10044 },
10045 &[],
10046 cx,
10047 )
10048 })
10049 }).unwrap();
10050 cx.run_until_parked();
10051 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
10052 cx.run_until_parked();
10053 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
10054}
10055
10056#[gpui::test]
10057async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10058 init_test(cx, |_| {});
10059
10060 let mut cx = EditorTestContext::new(cx).await;
10061
10062 let diff_base = r#"
10063 use some::mod;
10064
10065 const A: u32 = 42;
10066
10067 fn main() {
10068 println!("hello");
10069
10070 println!("world");
10071 }
10072 "#
10073 .unindent();
10074
10075 // Edits are modified, removed, modified, added
10076 cx.set_state(
10077 &r#"
10078 use some::modified;
10079
10080 ˇ
10081 fn main() {
10082 println!("hello there");
10083
10084 println!("around the");
10085 println!("world");
10086 }
10087 "#
10088 .unindent(),
10089 );
10090
10091 cx.set_diff_base(Some(&diff_base));
10092 executor.run_until_parked();
10093
10094 cx.update_editor(|editor, cx| {
10095 //Wrap around the bottom of the buffer
10096 for _ in 0..3 {
10097 editor.go_to_next_hunk(&GoToHunk, cx);
10098 }
10099 });
10100
10101 cx.assert_editor_state(
10102 &r#"
10103 ˇuse some::modified;
10104
10105
10106 fn main() {
10107 println!("hello there");
10108
10109 println!("around the");
10110 println!("world");
10111 }
10112 "#
10113 .unindent(),
10114 );
10115
10116 cx.update_editor(|editor, cx| {
10117 //Wrap around the top of the buffer
10118 for _ in 0..2 {
10119 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10120 }
10121 });
10122
10123 cx.assert_editor_state(
10124 &r#"
10125 use some::modified;
10126
10127
10128 fn main() {
10129 ˇ println!("hello there");
10130
10131 println!("around the");
10132 println!("world");
10133 }
10134 "#
10135 .unindent(),
10136 );
10137
10138 cx.update_editor(|editor, cx| {
10139 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10140 });
10141
10142 cx.assert_editor_state(
10143 &r#"
10144 use some::modified;
10145
10146 ˇ
10147 fn main() {
10148 println!("hello there");
10149
10150 println!("around the");
10151 println!("world");
10152 }
10153 "#
10154 .unindent(),
10155 );
10156
10157 cx.update_editor(|editor, cx| {
10158 for _ in 0..3 {
10159 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10160 }
10161 });
10162
10163 cx.assert_editor_state(
10164 &r#"
10165 use some::modified;
10166
10167
10168 fn main() {
10169 ˇ println!("hello there");
10170
10171 println!("around the");
10172 println!("world");
10173 }
10174 "#
10175 .unindent(),
10176 );
10177
10178 cx.update_editor(|editor, cx| {
10179 editor.fold(&Fold, cx);
10180
10181 //Make sure that the fold only gets one hunk
10182 for _ in 0..4 {
10183 editor.go_to_next_hunk(&GoToHunk, cx);
10184 }
10185 });
10186
10187 cx.assert_editor_state(
10188 &r#"
10189 ˇuse some::modified;
10190
10191
10192 fn main() {
10193 println!("hello there");
10194
10195 println!("around the");
10196 println!("world");
10197 }
10198 "#
10199 .unindent(),
10200 );
10201}
10202
10203#[test]
10204fn test_split_words() {
10205 fn split(text: &str) -> Vec<&str> {
10206 split_words(text).collect()
10207 }
10208
10209 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
10210 assert_eq!(split("hello_world"), &["hello_", "world"]);
10211 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
10212 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
10213 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
10214 assert_eq!(split("helloworld"), &["helloworld"]);
10215
10216 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
10217}
10218
10219#[gpui::test]
10220async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
10221 init_test(cx, |_| {});
10222
10223 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
10224 let mut assert = |before, after| {
10225 let _state_context = cx.set_state(before);
10226 cx.update_editor(|editor, cx| {
10227 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
10228 });
10229 cx.assert_editor_state(after);
10230 };
10231
10232 // Outside bracket jumps to outside of matching bracket
10233 assert("console.logˇ(var);", "console.log(var)ˇ;");
10234 assert("console.log(var)ˇ;", "console.logˇ(var);");
10235
10236 // Inside bracket jumps to inside of matching bracket
10237 assert("console.log(ˇvar);", "console.log(varˇ);");
10238 assert("console.log(varˇ);", "console.log(ˇvar);");
10239
10240 // When outside a bracket and inside, favor jumping to the inside bracket
10241 assert(
10242 "console.log('foo', [1, 2, 3]ˇ);",
10243 "console.log(ˇ'foo', [1, 2, 3]);",
10244 );
10245 assert(
10246 "console.log(ˇ'foo', [1, 2, 3]);",
10247 "console.log('foo', [1, 2, 3]ˇ);",
10248 );
10249
10250 // Bias forward if two options are equally likely
10251 assert(
10252 "let result = curried_fun()ˇ();",
10253 "let result = curried_fun()()ˇ;",
10254 );
10255
10256 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
10257 assert(
10258 indoc! {"
10259 function test() {
10260 console.log('test')ˇ
10261 }"},
10262 indoc! {"
10263 function test() {
10264 console.logˇ('test')
10265 }"},
10266 );
10267}
10268
10269#[gpui::test]
10270async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
10271 init_test(cx, |_| {});
10272
10273 let fs = FakeFs::new(cx.executor());
10274 fs.insert_tree(
10275 "/a",
10276 json!({
10277 "main.rs": "fn main() { let a = 5; }",
10278 "other.rs": "// Test file",
10279 }),
10280 )
10281 .await;
10282 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10283
10284 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10285 language_registry.add(Arc::new(Language::new(
10286 LanguageConfig {
10287 name: "Rust".into(),
10288 matcher: LanguageMatcher {
10289 path_suffixes: vec!["rs".to_string()],
10290 ..Default::default()
10291 },
10292 brackets: BracketPairConfig {
10293 pairs: vec![BracketPair {
10294 start: "{".to_string(),
10295 end: "}".to_string(),
10296 close: true,
10297 surround: true,
10298 newline: true,
10299 }],
10300 disabled_scopes_by_bracket_ix: Vec::new(),
10301 },
10302 ..Default::default()
10303 },
10304 Some(tree_sitter_rust::LANGUAGE.into()),
10305 )));
10306 let mut fake_servers = language_registry.register_fake_lsp(
10307 "Rust",
10308 FakeLspAdapter {
10309 capabilities: lsp::ServerCapabilities {
10310 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
10311 first_trigger_character: "{".to_string(),
10312 more_trigger_character: None,
10313 }),
10314 ..Default::default()
10315 },
10316 ..Default::default()
10317 },
10318 );
10319
10320 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10321
10322 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10323
10324 let worktree_id = workspace
10325 .update(cx, |workspace, cx| {
10326 workspace.project().update(cx, |project, cx| {
10327 project.worktrees(cx).next().unwrap().read(cx).id()
10328 })
10329 })
10330 .unwrap();
10331
10332 let buffer = project
10333 .update(cx, |project, cx| {
10334 project.open_local_buffer("/a/main.rs", cx)
10335 })
10336 .await
10337 .unwrap();
10338 cx.executor().run_until_parked();
10339 cx.executor().start_waiting();
10340 let fake_server = fake_servers.next().await.unwrap();
10341 let editor_handle = workspace
10342 .update(cx, |workspace, cx| {
10343 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
10344 })
10345 .unwrap()
10346 .await
10347 .unwrap()
10348 .downcast::<Editor>()
10349 .unwrap();
10350
10351 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
10352 assert_eq!(
10353 params.text_document_position.text_document.uri,
10354 lsp::Url::from_file_path("/a/main.rs").unwrap(),
10355 );
10356 assert_eq!(
10357 params.text_document_position.position,
10358 lsp::Position::new(0, 21),
10359 );
10360
10361 Ok(Some(vec![lsp::TextEdit {
10362 new_text: "]".to_string(),
10363 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10364 }]))
10365 });
10366
10367 editor_handle.update(cx, |editor, cx| {
10368 editor.focus(cx);
10369 editor.change_selections(None, cx, |s| {
10370 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
10371 });
10372 editor.handle_input("{", cx);
10373 });
10374
10375 cx.executor().run_until_parked();
10376
10377 buffer.update(cx, |buffer, _| {
10378 assert_eq!(
10379 buffer.text(),
10380 "fn main() { let a = {5}; }",
10381 "No extra braces from on type formatting should appear in the buffer"
10382 )
10383 });
10384}
10385
10386#[gpui::test]
10387async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
10388 init_test(cx, |_| {});
10389
10390 let fs = FakeFs::new(cx.executor());
10391 fs.insert_tree(
10392 "/a",
10393 json!({
10394 "main.rs": "fn main() { let a = 5; }",
10395 "other.rs": "// Test file",
10396 }),
10397 )
10398 .await;
10399
10400 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10401
10402 let server_restarts = Arc::new(AtomicUsize::new(0));
10403 let closure_restarts = Arc::clone(&server_restarts);
10404 let language_server_name = "test language server";
10405 let language_name: LanguageName = "Rust".into();
10406
10407 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10408 language_registry.add(Arc::new(Language::new(
10409 LanguageConfig {
10410 name: language_name.clone(),
10411 matcher: LanguageMatcher {
10412 path_suffixes: vec!["rs".to_string()],
10413 ..Default::default()
10414 },
10415 ..Default::default()
10416 },
10417 Some(tree_sitter_rust::LANGUAGE.into()),
10418 )));
10419 let mut fake_servers = language_registry.register_fake_lsp(
10420 "Rust",
10421 FakeLspAdapter {
10422 name: language_server_name,
10423 initialization_options: Some(json!({
10424 "testOptionValue": true
10425 })),
10426 initializer: Some(Box::new(move |fake_server| {
10427 let task_restarts = Arc::clone(&closure_restarts);
10428 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
10429 task_restarts.fetch_add(1, atomic::Ordering::Release);
10430 futures::future::ready(Ok(()))
10431 });
10432 })),
10433 ..Default::default()
10434 },
10435 );
10436
10437 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10438 let _buffer = project
10439 .update(cx, |project, cx| {
10440 project.open_local_buffer("/a/main.rs", cx)
10441 })
10442 .await
10443 .unwrap();
10444 let _fake_server = fake_servers.next().await.unwrap();
10445 update_test_language_settings(cx, |language_settings| {
10446 language_settings.languages.insert(
10447 language_name.clone(),
10448 LanguageSettingsContent {
10449 tab_size: NonZeroU32::new(8),
10450 ..Default::default()
10451 },
10452 );
10453 });
10454 cx.executor().run_until_parked();
10455 assert_eq!(
10456 server_restarts.load(atomic::Ordering::Acquire),
10457 0,
10458 "Should not restart LSP server on an unrelated change"
10459 );
10460
10461 update_test_project_settings(cx, |project_settings| {
10462 project_settings.lsp.insert(
10463 "Some other server name".into(),
10464 LspSettings {
10465 binary: None,
10466 settings: None,
10467 initialization_options: Some(json!({
10468 "some other init value": false
10469 })),
10470 },
10471 );
10472 });
10473 cx.executor().run_until_parked();
10474 assert_eq!(
10475 server_restarts.load(atomic::Ordering::Acquire),
10476 0,
10477 "Should not restart LSP server on an unrelated LSP settings change"
10478 );
10479
10480 update_test_project_settings(cx, |project_settings| {
10481 project_settings.lsp.insert(
10482 language_server_name.into(),
10483 LspSettings {
10484 binary: None,
10485 settings: None,
10486 initialization_options: Some(json!({
10487 "anotherInitValue": false
10488 })),
10489 },
10490 );
10491 });
10492 cx.executor().run_until_parked();
10493 assert_eq!(
10494 server_restarts.load(atomic::Ordering::Acquire),
10495 1,
10496 "Should restart LSP server on a related LSP settings change"
10497 );
10498
10499 update_test_project_settings(cx, |project_settings| {
10500 project_settings.lsp.insert(
10501 language_server_name.into(),
10502 LspSettings {
10503 binary: None,
10504 settings: None,
10505 initialization_options: Some(json!({
10506 "anotherInitValue": false
10507 })),
10508 },
10509 );
10510 });
10511 cx.executor().run_until_parked();
10512 assert_eq!(
10513 server_restarts.load(atomic::Ordering::Acquire),
10514 1,
10515 "Should not restart LSP server on a related LSP settings change that is the same"
10516 );
10517
10518 update_test_project_settings(cx, |project_settings| {
10519 project_settings.lsp.insert(
10520 language_server_name.into(),
10521 LspSettings {
10522 binary: None,
10523 settings: None,
10524 initialization_options: None,
10525 },
10526 );
10527 });
10528 cx.executor().run_until_parked();
10529 assert_eq!(
10530 server_restarts.load(atomic::Ordering::Acquire),
10531 2,
10532 "Should restart LSP server on another related LSP settings change"
10533 );
10534}
10535
10536#[gpui::test]
10537async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10538 init_test(cx, |_| {});
10539
10540 let mut cx = EditorLspTestContext::new_rust(
10541 lsp::ServerCapabilities {
10542 completion_provider: Some(lsp::CompletionOptions {
10543 trigger_characters: Some(vec![".".to_string()]),
10544 resolve_provider: Some(true),
10545 ..Default::default()
10546 }),
10547 ..Default::default()
10548 },
10549 cx,
10550 )
10551 .await;
10552
10553 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10554 cx.simulate_keystroke(".");
10555 let completion_item = lsp::CompletionItem {
10556 label: "some".into(),
10557 kind: Some(lsp::CompletionItemKind::SNIPPET),
10558 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10559 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10560 kind: lsp::MarkupKind::Markdown,
10561 value: "```rust\nSome(2)\n```".to_string(),
10562 })),
10563 deprecated: Some(false),
10564 sort_text: Some("fffffff2".to_string()),
10565 filter_text: Some("some".to_string()),
10566 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10567 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10568 range: lsp::Range {
10569 start: lsp::Position {
10570 line: 0,
10571 character: 22,
10572 },
10573 end: lsp::Position {
10574 line: 0,
10575 character: 22,
10576 },
10577 },
10578 new_text: "Some(2)".to_string(),
10579 })),
10580 additional_text_edits: Some(vec![lsp::TextEdit {
10581 range: lsp::Range {
10582 start: lsp::Position {
10583 line: 0,
10584 character: 20,
10585 },
10586 end: lsp::Position {
10587 line: 0,
10588 character: 22,
10589 },
10590 },
10591 new_text: "".to_string(),
10592 }]),
10593 ..Default::default()
10594 };
10595
10596 let closure_completion_item = completion_item.clone();
10597 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10598 let task_completion_item = closure_completion_item.clone();
10599 async move {
10600 Ok(Some(lsp::CompletionResponse::Array(vec![
10601 task_completion_item,
10602 ])))
10603 }
10604 });
10605
10606 request.next().await;
10607
10608 cx.condition(|editor, _| editor.context_menu_visible())
10609 .await;
10610 let apply_additional_edits = cx.update_editor(|editor, cx| {
10611 editor
10612 .confirm_completion(&ConfirmCompletion::default(), cx)
10613 .unwrap()
10614 });
10615 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
10616
10617 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10618 let task_completion_item = completion_item.clone();
10619 async move { Ok(task_completion_item) }
10620 })
10621 .next()
10622 .await
10623 .unwrap();
10624 apply_additional_edits.await.unwrap();
10625 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10626}
10627
10628#[gpui::test]
10629async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
10630 init_test(cx, |_| {});
10631
10632 let mut cx = EditorLspTestContext::new_rust(
10633 lsp::ServerCapabilities {
10634 completion_provider: Some(lsp::CompletionOptions {
10635 trigger_characters: Some(vec![".".to_string()]),
10636 resolve_provider: Some(true),
10637 ..Default::default()
10638 }),
10639 ..Default::default()
10640 },
10641 cx,
10642 )
10643 .await;
10644
10645 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10646 cx.simulate_keystroke(".");
10647
10648 let default_commit_characters = vec!["?".to_string()];
10649 let default_data = json!({ "very": "special"});
10650 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
10651 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
10652 let default_edit_range = lsp::Range {
10653 start: lsp::Position {
10654 line: 0,
10655 character: 5,
10656 },
10657 end: lsp::Position {
10658 line: 0,
10659 character: 5,
10660 },
10661 };
10662
10663 let resolve_requests_number = Arc::new(AtomicUsize::new(0));
10664 let expect_first_item = Arc::new(AtomicBool::new(true));
10665 cx.lsp
10666 .server
10667 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
10668 let closure_default_data = default_data.clone();
10669 let closure_resolve_requests_number = resolve_requests_number.clone();
10670 let closure_expect_first_item = expect_first_item.clone();
10671 let closure_default_commit_characters = default_commit_characters.clone();
10672 move |item_to_resolve, _| {
10673 closure_resolve_requests_number.fetch_add(1, atomic::Ordering::Release);
10674 let default_data = closure_default_data.clone();
10675 let default_commit_characters = closure_default_commit_characters.clone();
10676 let expect_first_item = closure_expect_first_item.clone();
10677 async move {
10678 if expect_first_item.load(atomic::Ordering::Acquire) {
10679 assert_eq!(
10680 item_to_resolve.label, "Some(2)",
10681 "Should have selected the first item"
10682 );
10683 assert_eq!(
10684 item_to_resolve.data,
10685 Some(json!({ "very": "special"})),
10686 "First item should bring its own data for resolving"
10687 );
10688 assert_eq!(
10689 item_to_resolve.commit_characters,
10690 Some(default_commit_characters),
10691 "First item had no own commit characters and should inherit the default ones"
10692 );
10693 assert!(
10694 matches!(
10695 item_to_resolve.text_edit,
10696 Some(lsp::CompletionTextEdit::InsertAndReplace { .. })
10697 ),
10698 "First item should bring its own edit range for resolving"
10699 );
10700 assert_eq!(
10701 item_to_resolve.insert_text_format,
10702 Some(default_insert_text_format),
10703 "First item had no own insert text format and should inherit the default one"
10704 );
10705 assert_eq!(
10706 item_to_resolve.insert_text_mode,
10707 Some(lsp::InsertTextMode::ADJUST_INDENTATION),
10708 "First item should bring its own insert text mode for resolving"
10709 );
10710 Ok(item_to_resolve)
10711 } else {
10712 assert_eq!(
10713 item_to_resolve.label, "vec![2]",
10714 "Should have selected the last item"
10715 );
10716 assert_eq!(
10717 item_to_resolve.data,
10718 Some(default_data),
10719 "Last item has no own resolve data and should inherit the default one"
10720 );
10721 assert_eq!(
10722 item_to_resolve.commit_characters,
10723 Some(default_commit_characters),
10724 "Last item had no own commit characters and should inherit the default ones"
10725 );
10726 assert_eq!(
10727 item_to_resolve.text_edit,
10728 Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10729 range: default_edit_range,
10730 new_text: "vec![2]".to_string()
10731 })),
10732 "Last item had no own edit range and should inherit the default one"
10733 );
10734 assert_eq!(
10735 item_to_resolve.insert_text_format,
10736 Some(lsp::InsertTextFormat::PLAIN_TEXT),
10737 "Last item should bring its own insert text format for resolving"
10738 );
10739 assert_eq!(
10740 item_to_resolve.insert_text_mode,
10741 Some(default_insert_text_mode),
10742 "Last item had no own insert text mode and should inherit the default one"
10743 );
10744
10745 Ok(item_to_resolve)
10746 }
10747 }
10748 }
10749 }).detach();
10750
10751 let completion_data = default_data.clone();
10752 let completion_characters = default_commit_characters.clone();
10753 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10754 let default_data = completion_data.clone();
10755 let default_commit_characters = completion_characters.clone();
10756 async move {
10757 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
10758 items: vec![
10759 lsp::CompletionItem {
10760 label: "Some(2)".into(),
10761 insert_text: Some("Some(2)".into()),
10762 data: Some(json!({ "very": "special"})),
10763 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
10764 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
10765 lsp::InsertReplaceEdit {
10766 new_text: "Some(2)".to_string(),
10767 insert: lsp::Range::default(),
10768 replace: lsp::Range::default(),
10769 },
10770 )),
10771 ..lsp::CompletionItem::default()
10772 },
10773 lsp::CompletionItem {
10774 label: "vec![2]".into(),
10775 insert_text: Some("vec![2]".into()),
10776 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
10777 ..lsp::CompletionItem::default()
10778 },
10779 ],
10780 item_defaults: Some(lsp::CompletionListItemDefaults {
10781 data: Some(default_data.clone()),
10782 commit_characters: Some(default_commit_characters.clone()),
10783 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
10784 default_edit_range,
10785 )),
10786 insert_text_format: Some(default_insert_text_format),
10787 insert_text_mode: Some(default_insert_text_mode),
10788 }),
10789 ..lsp::CompletionList::default()
10790 })))
10791 }
10792 })
10793 .next()
10794 .await;
10795
10796 cx.condition(|editor, _| editor.context_menu_visible())
10797 .await;
10798 cx.run_until_parked();
10799 cx.update_editor(|editor, _| {
10800 let menu = editor.context_menu.read();
10801 match menu.as_ref().expect("should have the completions menu") {
10802 ContextMenu::Completions(completions_menu) => {
10803 assert_eq!(
10804 completions_menu
10805 .matches
10806 .iter()
10807 .map(|c| c.string.as_str())
10808 .collect::<Vec<_>>(),
10809 vec!["Some(2)", "vec![2]"]
10810 );
10811 }
10812 ContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
10813 }
10814 });
10815 assert_eq!(
10816 resolve_requests_number.load(atomic::Ordering::Acquire),
10817 1,
10818 "While there are 2 items in the completion list, only 1 resolve request should have been sent, for the selected item"
10819 );
10820
10821 cx.update_editor(|editor, cx| {
10822 editor.context_menu_first(&ContextMenuFirst, cx);
10823 });
10824 cx.run_until_parked();
10825 assert_eq!(
10826 resolve_requests_number.load(atomic::Ordering::Acquire),
10827 2,
10828 "After re-selecting the first item, another resolve request should have been sent"
10829 );
10830
10831 expect_first_item.store(false, atomic::Ordering::Release);
10832 cx.update_editor(|editor, cx| {
10833 editor.context_menu_last(&ContextMenuLast, cx);
10834 });
10835 cx.run_until_parked();
10836 assert_eq!(
10837 resolve_requests_number.load(atomic::Ordering::Acquire),
10838 3,
10839 "After selecting the other item, another resolve request should have been sent"
10840 );
10841}
10842
10843#[gpui::test]
10844async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
10845 init_test(cx, |_| {});
10846
10847 let mut cx = EditorLspTestContext::new(
10848 Language::new(
10849 LanguageConfig {
10850 matcher: LanguageMatcher {
10851 path_suffixes: vec!["jsx".into()],
10852 ..Default::default()
10853 },
10854 overrides: [(
10855 "element".into(),
10856 LanguageConfigOverride {
10857 word_characters: Override::Set(['-'].into_iter().collect()),
10858 ..Default::default()
10859 },
10860 )]
10861 .into_iter()
10862 .collect(),
10863 ..Default::default()
10864 },
10865 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10866 )
10867 .with_override_query("(jsx_self_closing_element) @element")
10868 .unwrap(),
10869 lsp::ServerCapabilities {
10870 completion_provider: Some(lsp::CompletionOptions {
10871 trigger_characters: Some(vec![":".to_string()]),
10872 ..Default::default()
10873 }),
10874 ..Default::default()
10875 },
10876 cx,
10877 )
10878 .await;
10879
10880 cx.lsp
10881 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10882 Ok(Some(lsp::CompletionResponse::Array(vec![
10883 lsp::CompletionItem {
10884 label: "bg-blue".into(),
10885 ..Default::default()
10886 },
10887 lsp::CompletionItem {
10888 label: "bg-red".into(),
10889 ..Default::default()
10890 },
10891 lsp::CompletionItem {
10892 label: "bg-yellow".into(),
10893 ..Default::default()
10894 },
10895 ])))
10896 });
10897
10898 cx.set_state(r#"<p class="bgˇ" />"#);
10899
10900 // Trigger completion when typing a dash, because the dash is an extra
10901 // word character in the 'element' scope, which contains the cursor.
10902 cx.simulate_keystroke("-");
10903 cx.executor().run_until_parked();
10904 cx.update_editor(|editor, _| {
10905 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10906 assert_eq!(
10907 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10908 &["bg-red", "bg-blue", "bg-yellow"]
10909 );
10910 } else {
10911 panic!("expected completion menu to be open");
10912 }
10913 });
10914
10915 cx.simulate_keystroke("l");
10916 cx.executor().run_until_parked();
10917 cx.update_editor(|editor, _| {
10918 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10919 assert_eq!(
10920 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10921 &["bg-blue", "bg-yellow"]
10922 );
10923 } else {
10924 panic!("expected completion menu to be open");
10925 }
10926 });
10927
10928 // When filtering completions, consider the character after the '-' to
10929 // be the start of a subword.
10930 cx.set_state(r#"<p class="yelˇ" />"#);
10931 cx.simulate_keystroke("l");
10932 cx.executor().run_until_parked();
10933 cx.update_editor(|editor, _| {
10934 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10935 assert_eq!(
10936 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10937 &["bg-yellow"]
10938 );
10939 } else {
10940 panic!("expected completion menu to be open");
10941 }
10942 });
10943}
10944
10945#[gpui::test]
10946async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
10947 init_test(cx, |settings| {
10948 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
10949 FormatterList(vec![Formatter::Prettier].into()),
10950 ))
10951 });
10952
10953 let fs = FakeFs::new(cx.executor());
10954 fs.insert_file("/file.ts", Default::default()).await;
10955
10956 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
10957 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10958
10959 language_registry.add(Arc::new(Language::new(
10960 LanguageConfig {
10961 name: "TypeScript".into(),
10962 matcher: LanguageMatcher {
10963 path_suffixes: vec!["ts".to_string()],
10964 ..Default::default()
10965 },
10966 ..Default::default()
10967 },
10968 Some(tree_sitter_rust::LANGUAGE.into()),
10969 )));
10970 update_test_language_settings(cx, |settings| {
10971 settings.defaults.prettier = Some(PrettierSettings {
10972 allowed: true,
10973 ..PrettierSettings::default()
10974 });
10975 });
10976
10977 let test_plugin = "test_plugin";
10978 let _ = language_registry.register_fake_lsp(
10979 "TypeScript",
10980 FakeLspAdapter {
10981 prettier_plugins: vec![test_plugin],
10982 ..Default::default()
10983 },
10984 );
10985
10986 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
10987 let buffer = project
10988 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
10989 .await
10990 .unwrap();
10991
10992 let buffer_text = "one\ntwo\nthree\n";
10993 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
10994 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
10995 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
10996
10997 editor
10998 .update(cx, |editor, cx| {
10999 editor.perform_format(
11000 project.clone(),
11001 FormatTrigger::Manual,
11002 FormatTarget::Buffer,
11003 cx,
11004 )
11005 })
11006 .unwrap()
11007 .await;
11008 assert_eq!(
11009 editor.update(cx, |editor, cx| editor.text(cx)),
11010 buffer_text.to_string() + prettier_format_suffix,
11011 "Test prettier formatting was not applied to the original buffer text",
11012 );
11013
11014 update_test_language_settings(cx, |settings| {
11015 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
11016 });
11017 let format = editor.update(cx, |editor, cx| {
11018 editor.perform_format(
11019 project.clone(),
11020 FormatTrigger::Manual,
11021 FormatTarget::Buffer,
11022 cx,
11023 )
11024 });
11025 format.await.unwrap();
11026 assert_eq!(
11027 editor.update(cx, |editor, cx| editor.text(cx)),
11028 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
11029 "Autoformatting (via test prettier) was not applied to the original buffer text",
11030 );
11031}
11032
11033#[gpui::test]
11034async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
11035 init_test(cx, |_| {});
11036 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11037 let base_text = indoc! {r#"struct Row;
11038struct Row1;
11039struct Row2;
11040
11041struct Row4;
11042struct Row5;
11043struct Row6;
11044
11045struct Row8;
11046struct Row9;
11047struct Row10;"#};
11048
11049 // When addition hunks are not adjacent to carets, no hunk revert is performed
11050 assert_hunk_revert(
11051 indoc! {r#"struct Row;
11052 struct Row1;
11053 struct Row1.1;
11054 struct Row1.2;
11055 struct Row2;ˇ
11056
11057 struct Row4;
11058 struct Row5;
11059 struct Row6;
11060
11061 struct Row8;
11062 ˇstruct Row9;
11063 struct Row9.1;
11064 struct Row9.2;
11065 struct Row9.3;
11066 struct Row10;"#},
11067 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11068 indoc! {r#"struct Row;
11069 struct Row1;
11070 struct Row1.1;
11071 struct Row1.2;
11072 struct Row2;ˇ
11073
11074 struct Row4;
11075 struct Row5;
11076 struct Row6;
11077
11078 struct Row8;
11079 ˇstruct Row9;
11080 struct Row9.1;
11081 struct Row9.2;
11082 struct Row9.3;
11083 struct Row10;"#},
11084 base_text,
11085 &mut cx,
11086 );
11087 // Same for selections
11088 assert_hunk_revert(
11089 indoc! {r#"struct Row;
11090 struct Row1;
11091 struct Row2;
11092 struct Row2.1;
11093 struct Row2.2;
11094 «ˇ
11095 struct Row4;
11096 struct» Row5;
11097 «struct Row6;
11098 ˇ»
11099 struct Row9.1;
11100 struct Row9.2;
11101 struct Row9.3;
11102 struct Row8;
11103 struct Row9;
11104 struct Row10;"#},
11105 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11106 indoc! {r#"struct Row;
11107 struct Row1;
11108 struct Row2;
11109 struct Row2.1;
11110 struct Row2.2;
11111 «ˇ
11112 struct Row4;
11113 struct» Row5;
11114 «struct Row6;
11115 ˇ»
11116 struct Row9.1;
11117 struct Row9.2;
11118 struct Row9.3;
11119 struct Row8;
11120 struct Row9;
11121 struct Row10;"#},
11122 base_text,
11123 &mut cx,
11124 );
11125
11126 // When carets and selections intersect the addition hunks, those are reverted.
11127 // Adjacent carets got merged.
11128 assert_hunk_revert(
11129 indoc! {r#"struct Row;
11130 ˇ// something on the top
11131 struct Row1;
11132 struct Row2;
11133 struct Roˇw3.1;
11134 struct Row2.2;
11135 struct Row2.3;ˇ
11136
11137 struct Row4;
11138 struct ˇRow5.1;
11139 struct Row5.2;
11140 struct «Rowˇ»5.3;
11141 struct Row5;
11142 struct Row6;
11143 ˇ
11144 struct Row9.1;
11145 struct «Rowˇ»9.2;
11146 struct «ˇRow»9.3;
11147 struct Row8;
11148 struct Row9;
11149 «ˇ// something on bottom»
11150 struct Row10;"#},
11151 vec![
11152 DiffHunkStatus::Added,
11153 DiffHunkStatus::Added,
11154 DiffHunkStatus::Added,
11155 DiffHunkStatus::Added,
11156 DiffHunkStatus::Added,
11157 ],
11158 indoc! {r#"struct Row;
11159 ˇstruct Row1;
11160 struct Row2;
11161 ˇ
11162 struct Row4;
11163 ˇstruct Row5;
11164 struct Row6;
11165 ˇ
11166 ˇstruct Row8;
11167 struct Row9;
11168 ˇstruct Row10;"#},
11169 base_text,
11170 &mut cx,
11171 );
11172}
11173
11174#[gpui::test]
11175async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
11176 init_test(cx, |_| {});
11177 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11178 let base_text = indoc! {r#"struct Row;
11179struct Row1;
11180struct Row2;
11181
11182struct Row4;
11183struct Row5;
11184struct Row6;
11185
11186struct Row8;
11187struct Row9;
11188struct Row10;"#};
11189
11190 // Modification hunks behave the same as the addition ones.
11191 assert_hunk_revert(
11192 indoc! {r#"struct Row;
11193 struct Row1;
11194 struct Row33;
11195 ˇ
11196 struct Row4;
11197 struct Row5;
11198 struct Row6;
11199 ˇ
11200 struct Row99;
11201 struct Row9;
11202 struct Row10;"#},
11203 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11204 indoc! {r#"struct Row;
11205 struct Row1;
11206 struct Row33;
11207 ˇ
11208 struct Row4;
11209 struct Row5;
11210 struct Row6;
11211 ˇ
11212 struct Row99;
11213 struct Row9;
11214 struct Row10;"#},
11215 base_text,
11216 &mut cx,
11217 );
11218 assert_hunk_revert(
11219 indoc! {r#"struct Row;
11220 struct Row1;
11221 struct Row33;
11222 «ˇ
11223 struct Row4;
11224 struct» Row5;
11225 «struct Row6;
11226 ˇ»
11227 struct Row99;
11228 struct Row9;
11229 struct Row10;"#},
11230 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11231 indoc! {r#"struct Row;
11232 struct Row1;
11233 struct Row33;
11234 «ˇ
11235 struct Row4;
11236 struct» Row5;
11237 «struct Row6;
11238 ˇ»
11239 struct Row99;
11240 struct Row9;
11241 struct Row10;"#},
11242 base_text,
11243 &mut cx,
11244 );
11245
11246 assert_hunk_revert(
11247 indoc! {r#"ˇstruct Row1.1;
11248 struct Row1;
11249 «ˇstr»uct Row22;
11250
11251 struct ˇRow44;
11252 struct Row5;
11253 struct «Rˇ»ow66;ˇ
11254
11255 «struˇ»ct Row88;
11256 struct Row9;
11257 struct Row1011;ˇ"#},
11258 vec![
11259 DiffHunkStatus::Modified,
11260 DiffHunkStatus::Modified,
11261 DiffHunkStatus::Modified,
11262 DiffHunkStatus::Modified,
11263 DiffHunkStatus::Modified,
11264 DiffHunkStatus::Modified,
11265 ],
11266 indoc! {r#"struct Row;
11267 ˇstruct Row1;
11268 struct Row2;
11269 ˇ
11270 struct Row4;
11271 ˇstruct Row5;
11272 struct Row6;
11273 ˇ
11274 struct Row8;
11275 ˇstruct Row9;
11276 struct Row10;ˇ"#},
11277 base_text,
11278 &mut cx,
11279 );
11280}
11281
11282#[gpui::test]
11283async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
11284 init_test(cx, |_| {});
11285 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11286 let base_text = indoc! {r#"struct Row;
11287struct Row1;
11288struct Row2;
11289
11290struct Row4;
11291struct Row5;
11292struct Row6;
11293
11294struct Row8;
11295struct Row9;
11296struct Row10;"#};
11297
11298 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
11299 assert_hunk_revert(
11300 indoc! {r#"struct Row;
11301 struct Row2;
11302
11303 ˇstruct Row4;
11304 struct Row5;
11305 struct Row6;
11306 ˇ
11307 struct Row8;
11308 struct Row10;"#},
11309 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11310 indoc! {r#"struct Row;
11311 struct Row2;
11312
11313 ˇstruct Row4;
11314 struct Row5;
11315 struct Row6;
11316 ˇ
11317 struct Row8;
11318 struct Row10;"#},
11319 base_text,
11320 &mut cx,
11321 );
11322 assert_hunk_revert(
11323 indoc! {r#"struct Row;
11324 struct Row2;
11325
11326 «ˇstruct Row4;
11327 struct» Row5;
11328 «struct Row6;
11329 ˇ»
11330 struct Row8;
11331 struct Row10;"#},
11332 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11333 indoc! {r#"struct Row;
11334 struct Row2;
11335
11336 «ˇstruct Row4;
11337 struct» Row5;
11338 «struct Row6;
11339 ˇ»
11340 struct Row8;
11341 struct Row10;"#},
11342 base_text,
11343 &mut cx,
11344 );
11345
11346 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
11347 assert_hunk_revert(
11348 indoc! {r#"struct Row;
11349 ˇstruct Row2;
11350
11351 struct Row4;
11352 struct Row5;
11353 struct Row6;
11354
11355 struct Row8;ˇ
11356 struct Row10;"#},
11357 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
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 assert_hunk_revert(
11373 indoc! {r#"struct Row;
11374 struct Row2«ˇ;
11375 struct Row4;
11376 struct» Row5;
11377 «struct Row6;
11378
11379 struct Row8;ˇ»
11380 struct Row10;"#},
11381 vec![
11382 DiffHunkStatus::Removed,
11383 DiffHunkStatus::Removed,
11384 DiffHunkStatus::Removed,
11385 ],
11386 indoc! {r#"struct Row;
11387 struct Row1;
11388 struct Row2«ˇ;
11389
11390 struct Row4;
11391 struct» Row5;
11392 «struct Row6;
11393
11394 struct Row8;ˇ»
11395 struct Row9;
11396 struct Row10;"#},
11397 base_text,
11398 &mut cx,
11399 );
11400}
11401
11402#[gpui::test]
11403async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
11404 init_test(cx, |_| {});
11405
11406 let cols = 4;
11407 let rows = 10;
11408 let sample_text_1 = sample_text(rows, cols, 'a');
11409 assert_eq!(
11410 sample_text_1,
11411 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11412 );
11413 let sample_text_2 = sample_text(rows, cols, 'l');
11414 assert_eq!(
11415 sample_text_2,
11416 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11417 );
11418 let sample_text_3 = sample_text(rows, cols, 'v');
11419 assert_eq!(
11420 sample_text_3,
11421 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11422 );
11423
11424 fn diff_every_buffer_row(
11425 buffer: &Model<Buffer>,
11426 sample_text: String,
11427 cols: usize,
11428 cx: &mut gpui::TestAppContext,
11429 ) {
11430 // revert first character in each row, creating one large diff hunk per buffer
11431 let is_first_char = |offset: usize| offset % cols == 0;
11432 buffer.update(cx, |buffer, cx| {
11433 buffer.set_text(
11434 sample_text
11435 .chars()
11436 .enumerate()
11437 .map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
11438 .collect::<String>(),
11439 cx,
11440 );
11441 buffer.set_diff_base(Some(sample_text), cx);
11442 });
11443 cx.executor().run_until_parked();
11444 }
11445
11446 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11447 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
11448
11449 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11450 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
11451
11452 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11453 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
11454
11455 let multibuffer = cx.new_model(|cx| {
11456 let mut multibuffer = MultiBuffer::new(ReadWrite);
11457 multibuffer.push_excerpts(
11458 buffer_1.clone(),
11459 [
11460 ExcerptRange {
11461 context: Point::new(0, 0)..Point::new(3, 0),
11462 primary: None,
11463 },
11464 ExcerptRange {
11465 context: Point::new(5, 0)..Point::new(7, 0),
11466 primary: None,
11467 },
11468 ExcerptRange {
11469 context: Point::new(9, 0)..Point::new(10, 4),
11470 primary: None,
11471 },
11472 ],
11473 cx,
11474 );
11475 multibuffer.push_excerpts(
11476 buffer_2.clone(),
11477 [
11478 ExcerptRange {
11479 context: Point::new(0, 0)..Point::new(3, 0),
11480 primary: None,
11481 },
11482 ExcerptRange {
11483 context: Point::new(5, 0)..Point::new(7, 0),
11484 primary: None,
11485 },
11486 ExcerptRange {
11487 context: Point::new(9, 0)..Point::new(10, 4),
11488 primary: None,
11489 },
11490 ],
11491 cx,
11492 );
11493 multibuffer.push_excerpts(
11494 buffer_3.clone(),
11495 [
11496 ExcerptRange {
11497 context: Point::new(0, 0)..Point::new(3, 0),
11498 primary: None,
11499 },
11500 ExcerptRange {
11501 context: Point::new(5, 0)..Point::new(7, 0),
11502 primary: None,
11503 },
11504 ExcerptRange {
11505 context: Point::new(9, 0)..Point::new(10, 4),
11506 primary: None,
11507 },
11508 ],
11509 cx,
11510 );
11511 multibuffer
11512 });
11513
11514 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
11515 editor.update(cx, |editor, cx| {
11516 assert_eq!(editor.text(cx), "XaaaXbbbX\nccXc\ndXdd\n\nhXhh\nXiiiXjjjX\n\nXlllXmmmX\nnnXn\noXoo\n\nsXss\nXtttXuuuX\n\nXvvvXwwwX\nxxXx\nyXyy\n\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X\n");
11517 editor.select_all(&SelectAll, cx);
11518 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11519 });
11520 cx.executor().run_until_parked();
11521 // When all ranges are selected, all buffer hunks are reverted.
11522 editor.update(cx, |editor, cx| {
11523 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");
11524 });
11525 buffer_1.update(cx, |buffer, _| {
11526 assert_eq!(buffer.text(), sample_text_1);
11527 });
11528 buffer_2.update(cx, |buffer, _| {
11529 assert_eq!(buffer.text(), sample_text_2);
11530 });
11531 buffer_3.update(cx, |buffer, _| {
11532 assert_eq!(buffer.text(), sample_text_3);
11533 });
11534
11535 diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
11536 diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
11537 diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
11538 editor.update(cx, |editor, cx| {
11539 editor.change_selections(None, cx, |s| {
11540 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
11541 });
11542 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11543 });
11544 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
11545 // but not affect buffer_2 and its related excerpts.
11546 editor.update(cx, |editor, cx| {
11547 assert_eq!(
11548 editor.text(cx),
11549 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nXlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX\n\n\nXvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X\n\n"
11550 );
11551 });
11552 buffer_1.update(cx, |buffer, _| {
11553 assert_eq!(buffer.text(), sample_text_1);
11554 });
11555 buffer_2.update(cx, |buffer, _| {
11556 assert_eq!(
11557 buffer.text(),
11558 "XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
11559 );
11560 });
11561 buffer_3.update(cx, |buffer, _| {
11562 assert_eq!(
11563 buffer.text(),
11564 "XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
11565 );
11566 });
11567}
11568
11569#[gpui::test]
11570async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
11571 init_test(cx, |_| {});
11572
11573 let cols = 4;
11574 let rows = 10;
11575 let sample_text_1 = sample_text(rows, cols, 'a');
11576 assert_eq!(
11577 sample_text_1,
11578 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11579 );
11580 let sample_text_2 = sample_text(rows, cols, 'l');
11581 assert_eq!(
11582 sample_text_2,
11583 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11584 );
11585 let sample_text_3 = sample_text(rows, cols, 'v');
11586 assert_eq!(
11587 sample_text_3,
11588 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11589 );
11590
11591 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11592 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11593 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11594
11595 let multi_buffer = cx.new_model(|cx| {
11596 let mut multibuffer = MultiBuffer::new(ReadWrite);
11597 multibuffer.push_excerpts(
11598 buffer_1.clone(),
11599 [
11600 ExcerptRange {
11601 context: Point::new(0, 0)..Point::new(3, 0),
11602 primary: None,
11603 },
11604 ExcerptRange {
11605 context: Point::new(5, 0)..Point::new(7, 0),
11606 primary: None,
11607 },
11608 ExcerptRange {
11609 context: Point::new(9, 0)..Point::new(10, 4),
11610 primary: None,
11611 },
11612 ],
11613 cx,
11614 );
11615 multibuffer.push_excerpts(
11616 buffer_2.clone(),
11617 [
11618 ExcerptRange {
11619 context: Point::new(0, 0)..Point::new(3, 0),
11620 primary: None,
11621 },
11622 ExcerptRange {
11623 context: Point::new(5, 0)..Point::new(7, 0),
11624 primary: None,
11625 },
11626 ExcerptRange {
11627 context: Point::new(9, 0)..Point::new(10, 4),
11628 primary: None,
11629 },
11630 ],
11631 cx,
11632 );
11633 multibuffer.push_excerpts(
11634 buffer_3.clone(),
11635 [
11636 ExcerptRange {
11637 context: Point::new(0, 0)..Point::new(3, 0),
11638 primary: None,
11639 },
11640 ExcerptRange {
11641 context: Point::new(5, 0)..Point::new(7, 0),
11642 primary: None,
11643 },
11644 ExcerptRange {
11645 context: Point::new(9, 0)..Point::new(10, 4),
11646 primary: None,
11647 },
11648 ],
11649 cx,
11650 );
11651 multibuffer
11652 });
11653
11654 let fs = FakeFs::new(cx.executor());
11655 fs.insert_tree(
11656 "/a",
11657 json!({
11658 "main.rs": sample_text_1,
11659 "other.rs": sample_text_2,
11660 "lib.rs": sample_text_3,
11661 }),
11662 )
11663 .await;
11664 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11665 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11666 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11667 let multi_buffer_editor = cx.new_view(|cx| {
11668 Editor::new(
11669 EditorMode::Full,
11670 multi_buffer,
11671 Some(project.clone()),
11672 true,
11673 cx,
11674 )
11675 });
11676 let multibuffer_item_id = workspace
11677 .update(cx, |workspace, cx| {
11678 assert!(
11679 workspace.active_item(cx).is_none(),
11680 "active item should be None before the first item is added"
11681 );
11682 workspace.add_item_to_active_pane(
11683 Box::new(multi_buffer_editor.clone()),
11684 None,
11685 true,
11686 cx,
11687 );
11688 let active_item = workspace
11689 .active_item(cx)
11690 .expect("should have an active item after adding the multi buffer");
11691 assert!(
11692 !active_item.is_singleton(cx),
11693 "A multi buffer was expected to active after adding"
11694 );
11695 active_item.item_id()
11696 })
11697 .unwrap();
11698 cx.executor().run_until_parked();
11699
11700 multi_buffer_editor.update(cx, |editor, cx| {
11701 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
11702 editor.open_excerpts(&OpenExcerpts, cx);
11703 });
11704 cx.executor().run_until_parked();
11705 let first_item_id = workspace
11706 .update(cx, |workspace, cx| {
11707 let active_item = workspace
11708 .active_item(cx)
11709 .expect("should have an active item after navigating into the 1st buffer");
11710 let first_item_id = active_item.item_id();
11711 assert_ne!(
11712 first_item_id, multibuffer_item_id,
11713 "Should navigate into the 1st buffer and activate it"
11714 );
11715 assert!(
11716 active_item.is_singleton(cx),
11717 "New active item should be a singleton buffer"
11718 );
11719 assert_eq!(
11720 active_item
11721 .act_as::<Editor>(cx)
11722 .expect("should have navigated into an editor for the 1st buffer")
11723 .read(cx)
11724 .text(cx),
11725 sample_text_1
11726 );
11727
11728 workspace
11729 .go_back(workspace.active_pane().downgrade(), cx)
11730 .detach_and_log_err(cx);
11731
11732 first_item_id
11733 })
11734 .unwrap();
11735 cx.executor().run_until_parked();
11736 workspace
11737 .update(cx, |workspace, cx| {
11738 let active_item = workspace
11739 .active_item(cx)
11740 .expect("should have an active item after navigating back");
11741 assert_eq!(
11742 active_item.item_id(),
11743 multibuffer_item_id,
11744 "Should navigate back to the multi buffer"
11745 );
11746 assert!(!active_item.is_singleton(cx));
11747 })
11748 .unwrap();
11749
11750 multi_buffer_editor.update(cx, |editor, cx| {
11751 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11752 s.select_ranges(Some(39..40))
11753 });
11754 editor.open_excerpts(&OpenExcerpts, cx);
11755 });
11756 cx.executor().run_until_parked();
11757 let second_item_id = workspace
11758 .update(cx, |workspace, cx| {
11759 let active_item = workspace
11760 .active_item(cx)
11761 .expect("should have an active item after navigating into the 2nd buffer");
11762 let second_item_id = active_item.item_id();
11763 assert_ne!(
11764 second_item_id, multibuffer_item_id,
11765 "Should navigate away from the multibuffer"
11766 );
11767 assert_ne!(
11768 second_item_id, first_item_id,
11769 "Should navigate into the 2nd buffer and activate it"
11770 );
11771 assert!(
11772 active_item.is_singleton(cx),
11773 "New active item should be a singleton buffer"
11774 );
11775 assert_eq!(
11776 active_item
11777 .act_as::<Editor>(cx)
11778 .expect("should have navigated into an editor")
11779 .read(cx)
11780 .text(cx),
11781 sample_text_2
11782 );
11783
11784 workspace
11785 .go_back(workspace.active_pane().downgrade(), cx)
11786 .detach_and_log_err(cx);
11787
11788 second_item_id
11789 })
11790 .unwrap();
11791 cx.executor().run_until_parked();
11792 workspace
11793 .update(cx, |workspace, cx| {
11794 let active_item = workspace
11795 .active_item(cx)
11796 .expect("should have an active item after navigating back from the 2nd buffer");
11797 assert_eq!(
11798 active_item.item_id(),
11799 multibuffer_item_id,
11800 "Should navigate back from the 2nd buffer to the multi buffer"
11801 );
11802 assert!(!active_item.is_singleton(cx));
11803 })
11804 .unwrap();
11805
11806 multi_buffer_editor.update(cx, |editor, cx| {
11807 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11808 s.select_ranges(Some(70..70))
11809 });
11810 editor.open_excerpts(&OpenExcerpts, cx);
11811 });
11812 cx.executor().run_until_parked();
11813 workspace
11814 .update(cx, |workspace, cx| {
11815 let active_item = workspace
11816 .active_item(cx)
11817 .expect("should have an active item after navigating into the 3rd buffer");
11818 let third_item_id = active_item.item_id();
11819 assert_ne!(
11820 third_item_id, multibuffer_item_id,
11821 "Should navigate into the 3rd buffer and activate it"
11822 );
11823 assert_ne!(third_item_id, first_item_id);
11824 assert_ne!(third_item_id, second_item_id);
11825 assert!(
11826 active_item.is_singleton(cx),
11827 "New active item should be a singleton buffer"
11828 );
11829 assert_eq!(
11830 active_item
11831 .act_as::<Editor>(cx)
11832 .expect("should have navigated into an editor")
11833 .read(cx)
11834 .text(cx),
11835 sample_text_3
11836 );
11837
11838 workspace
11839 .go_back(workspace.active_pane().downgrade(), cx)
11840 .detach_and_log_err(cx);
11841 })
11842 .unwrap();
11843 cx.executor().run_until_parked();
11844 workspace
11845 .update(cx, |workspace, cx| {
11846 let active_item = workspace
11847 .active_item(cx)
11848 .expect("should have an active item after navigating back from the 3rd buffer");
11849 assert_eq!(
11850 active_item.item_id(),
11851 multibuffer_item_id,
11852 "Should navigate back from the 3rd buffer to the multi buffer"
11853 );
11854 assert!(!active_item.is_singleton(cx));
11855 })
11856 .unwrap();
11857}
11858
11859#[gpui::test]
11860async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11861 init_test(cx, |_| {});
11862
11863 let mut cx = EditorTestContext::new(cx).await;
11864
11865 let diff_base = r#"
11866 use some::mod;
11867
11868 const A: u32 = 42;
11869
11870 fn main() {
11871 println!("hello");
11872
11873 println!("world");
11874 }
11875 "#
11876 .unindent();
11877
11878 cx.set_state(
11879 &r#"
11880 use some::modified;
11881
11882 ˇ
11883 fn main() {
11884 println!("hello there");
11885
11886 println!("around the");
11887 println!("world");
11888 }
11889 "#
11890 .unindent(),
11891 );
11892
11893 cx.set_diff_base(Some(&diff_base));
11894 executor.run_until_parked();
11895
11896 cx.update_editor(|editor, cx| {
11897 editor.go_to_next_hunk(&GoToHunk, cx);
11898 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11899 });
11900 executor.run_until_parked();
11901 cx.assert_diff_hunks(
11902 r#"
11903 use some::modified;
11904
11905
11906 fn main() {
11907 - println!("hello");
11908 + println!("hello there");
11909
11910 println!("around the");
11911 println!("world");
11912 }
11913 "#
11914 .unindent(),
11915 );
11916
11917 cx.update_editor(|editor, cx| {
11918 for _ in 0..3 {
11919 editor.go_to_next_hunk(&GoToHunk, cx);
11920 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11921 }
11922 });
11923 executor.run_until_parked();
11924 cx.assert_editor_state(
11925 &r#"
11926 use some::modified;
11927
11928 ˇ
11929 fn main() {
11930 println!("hello there");
11931
11932 println!("around the");
11933 println!("world");
11934 }
11935 "#
11936 .unindent(),
11937 );
11938
11939 cx.assert_diff_hunks(
11940 r#"
11941 - use some::mod;
11942 + use some::modified;
11943
11944 - const A: u32 = 42;
11945
11946 fn main() {
11947 - println!("hello");
11948 + println!("hello there");
11949
11950 + println!("around the");
11951 println!("world");
11952 }
11953 "#
11954 .unindent(),
11955 );
11956
11957 cx.update_editor(|editor, cx| {
11958 editor.cancel(&Cancel, cx);
11959 });
11960
11961 cx.assert_diff_hunks(
11962 r#"
11963 use some::modified;
11964
11965
11966 fn main() {
11967 println!("hello there");
11968
11969 println!("around the");
11970 println!("world");
11971 }
11972 "#
11973 .unindent(),
11974 );
11975}
11976
11977#[gpui::test]
11978async fn test_diff_base_change_with_expanded_diff_hunks(
11979 executor: BackgroundExecutor,
11980 cx: &mut gpui::TestAppContext,
11981) {
11982 init_test(cx, |_| {});
11983
11984 let mut cx = EditorTestContext::new(cx).await;
11985
11986 let diff_base = r#"
11987 use some::mod1;
11988 use some::mod2;
11989
11990 const A: u32 = 42;
11991 const B: u32 = 42;
11992 const C: u32 = 42;
11993
11994 fn main() {
11995 println!("hello");
11996
11997 println!("world");
11998 }
11999 "#
12000 .unindent();
12001
12002 cx.set_state(
12003 &r#"
12004 use some::mod2;
12005
12006 const A: u32 = 42;
12007 const C: u32 = 42;
12008
12009 fn main(ˇ) {
12010 //println!("hello");
12011
12012 println!("world");
12013 //
12014 //
12015 }
12016 "#
12017 .unindent(),
12018 );
12019
12020 cx.set_diff_base(Some(&diff_base));
12021 executor.run_until_parked();
12022
12023 cx.update_editor(|editor, cx| {
12024 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12025 });
12026 executor.run_until_parked();
12027 cx.assert_diff_hunks(
12028 r#"
12029 - use some::mod1;
12030 use some::mod2;
12031
12032 const A: u32 = 42;
12033 - const B: u32 = 42;
12034 const C: u32 = 42;
12035
12036 fn main() {
12037 - println!("hello");
12038 + //println!("hello");
12039
12040 println!("world");
12041 + //
12042 + //
12043 }
12044 "#
12045 .unindent(),
12046 );
12047
12048 cx.set_diff_base(Some("new diff base!"));
12049 executor.run_until_parked();
12050 cx.assert_diff_hunks(
12051 r#"
12052 use some::mod2;
12053
12054 const A: u32 = 42;
12055 const C: u32 = 42;
12056
12057 fn main() {
12058 //println!("hello");
12059
12060 println!("world");
12061 //
12062 //
12063 }
12064 "#
12065 .unindent(),
12066 );
12067
12068 cx.update_editor(|editor, cx| {
12069 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12070 });
12071 executor.run_until_parked();
12072 cx.assert_diff_hunks(
12073 r#"
12074 - new diff base!
12075 + use some::mod2;
12076 +
12077 + const A: u32 = 42;
12078 + const C: u32 = 42;
12079 +
12080 + fn main() {
12081 + //println!("hello");
12082 +
12083 + println!("world");
12084 + //
12085 + //
12086 + }
12087 "#
12088 .unindent(),
12089 );
12090}
12091
12092#[gpui::test]
12093async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
12094 init_test(cx, |_| {});
12095
12096 let mut cx = EditorTestContext::new(cx).await;
12097
12098 let diff_base = r#"
12099 use some::mod1;
12100 use some::mod2;
12101
12102 const A: u32 = 42;
12103 const B: u32 = 42;
12104 const C: u32 = 42;
12105
12106 fn main() {
12107 println!("hello");
12108
12109 println!("world");
12110 }
12111
12112 fn another() {
12113 println!("another");
12114 }
12115
12116 fn another2() {
12117 println!("another2");
12118 }
12119 "#
12120 .unindent();
12121
12122 cx.set_state(
12123 &r#"
12124 «use some::mod2;
12125
12126 const A: u32 = 42;
12127 const C: u32 = 42;
12128
12129 fn main() {
12130 //println!("hello");
12131
12132 println!("world");
12133 //
12134 //ˇ»
12135 }
12136
12137 fn another() {
12138 println!("another");
12139 println!("another");
12140 }
12141
12142 println!("another2");
12143 }
12144 "#
12145 .unindent(),
12146 );
12147
12148 cx.set_diff_base(Some(&diff_base));
12149 executor.run_until_parked();
12150
12151 cx.update_editor(|editor, cx| {
12152 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12153 });
12154 executor.run_until_parked();
12155
12156 cx.assert_diff_hunks(
12157 r#"
12158 - use some::mod1;
12159 use some::mod2;
12160
12161 const A: u32 = 42;
12162 - const B: u32 = 42;
12163 const C: u32 = 42;
12164
12165 fn main() {
12166 - println!("hello");
12167 + //println!("hello");
12168
12169 println!("world");
12170 + //
12171 + //
12172 }
12173
12174 fn another() {
12175 println!("another");
12176 + println!("another");
12177 }
12178
12179 - fn another2() {
12180 println!("another2");
12181 }
12182 "#
12183 .unindent(),
12184 );
12185
12186 // Fold across some of the diff hunks. They should no longer appear expanded.
12187 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
12188 cx.executor().run_until_parked();
12189
12190 // Hunks are not shown if their position is within a fold
12191 cx.assert_diff_hunks(
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 - fn another2() {
12212 println!("another2");
12213 }
12214 "#
12215 .unindent(),
12216 );
12217
12218 cx.update_editor(|editor, cx| {
12219 editor.select_all(&SelectAll, cx);
12220 editor.unfold_lines(&UnfoldLines, cx);
12221 });
12222 cx.executor().run_until_parked();
12223
12224 // The deletions reappear when unfolding.
12225 cx.assert_diff_hunks(
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
12256#[gpui::test]
12257async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
12258 init_test(cx, |_| {});
12259
12260 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12261 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12262 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12263 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12264 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
12265 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
12266
12267 let buffer_1 = cx.new_model(|cx| {
12268 let mut buffer = Buffer::local(file_1_new.to_string(), cx);
12269 buffer.set_diff_base(Some(file_1_old.into()), cx);
12270 buffer
12271 });
12272 let buffer_2 = cx.new_model(|cx| {
12273 let mut buffer = Buffer::local(file_2_new.to_string(), cx);
12274 buffer.set_diff_base(Some(file_2_old.into()), cx);
12275 buffer
12276 });
12277 let buffer_3 = cx.new_model(|cx| {
12278 let mut buffer = Buffer::local(file_3_new.to_string(), cx);
12279 buffer.set_diff_base(Some(file_3_old.into()), cx);
12280 buffer
12281 });
12282
12283 let multi_buffer = cx.new_model(|cx| {
12284 let mut multibuffer = MultiBuffer::new(ReadWrite);
12285 multibuffer.push_excerpts(
12286 buffer_1.clone(),
12287 [
12288 ExcerptRange {
12289 context: Point::new(0, 0)..Point::new(3, 0),
12290 primary: None,
12291 },
12292 ExcerptRange {
12293 context: Point::new(5, 0)..Point::new(7, 0),
12294 primary: None,
12295 },
12296 ExcerptRange {
12297 context: Point::new(9, 0)..Point::new(10, 3),
12298 primary: None,
12299 },
12300 ],
12301 cx,
12302 );
12303 multibuffer.push_excerpts(
12304 buffer_2.clone(),
12305 [
12306 ExcerptRange {
12307 context: Point::new(0, 0)..Point::new(3, 0),
12308 primary: None,
12309 },
12310 ExcerptRange {
12311 context: Point::new(5, 0)..Point::new(7, 0),
12312 primary: None,
12313 },
12314 ExcerptRange {
12315 context: Point::new(9, 0)..Point::new(10, 3),
12316 primary: None,
12317 },
12318 ],
12319 cx,
12320 );
12321 multibuffer.push_excerpts(
12322 buffer_3.clone(),
12323 [
12324 ExcerptRange {
12325 context: Point::new(0, 0)..Point::new(3, 0),
12326 primary: None,
12327 },
12328 ExcerptRange {
12329 context: Point::new(5, 0)..Point::new(7, 0),
12330 primary: None,
12331 },
12332 ExcerptRange {
12333 context: Point::new(9, 0)..Point::new(10, 3),
12334 primary: None,
12335 },
12336 ],
12337 cx,
12338 );
12339 multibuffer
12340 });
12341
12342 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12343 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12344 cx.run_until_parked();
12345
12346 cx.assert_editor_state(
12347 &"
12348 ˇaaa
12349 ccc
12350 ddd
12351
12352 ggg
12353 hhh
12354
12355
12356 lll
12357 mmm
12358 NNN
12359
12360 qqq
12361 rrr
12362
12363 uuu
12364 111
12365 222
12366 333
12367
12368 666
12369 777
12370
12371 000
12372 !!!"
12373 .unindent(),
12374 );
12375
12376 cx.update_editor(|editor, cx| {
12377 editor.select_all(&SelectAll, cx);
12378 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12379 });
12380 cx.executor().run_until_parked();
12381
12382 cx.assert_diff_hunks(
12383 "
12384 aaa
12385 - bbb
12386 ccc
12387 ddd
12388
12389 ggg
12390 hhh
12391
12392
12393 lll
12394 mmm
12395 - nnn
12396 + NNN
12397
12398 qqq
12399 rrr
12400
12401 uuu
12402 111
12403 222
12404 333
12405
12406 + 666
12407 777
12408
12409 000
12410 !!!"
12411 .unindent(),
12412 );
12413}
12414
12415#[gpui::test]
12416async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
12417 init_test(cx, |_| {});
12418
12419 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
12420 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
12421
12422 let buffer = cx.new_model(|cx| {
12423 let mut buffer = Buffer::local(text.to_string(), cx);
12424 buffer.set_diff_base(Some(base.into()), cx);
12425 buffer
12426 });
12427
12428 let multi_buffer = cx.new_model(|cx| {
12429 let mut multibuffer = MultiBuffer::new(ReadWrite);
12430 multibuffer.push_excerpts(
12431 buffer.clone(),
12432 [
12433 ExcerptRange {
12434 context: Point::new(0, 0)..Point::new(2, 0),
12435 primary: None,
12436 },
12437 ExcerptRange {
12438 context: Point::new(5, 0)..Point::new(7, 0),
12439 primary: None,
12440 },
12441 ],
12442 cx,
12443 );
12444 multibuffer
12445 });
12446
12447 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12448 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12449 cx.run_until_parked();
12450
12451 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
12452 cx.executor().run_until_parked();
12453
12454 cx.assert_diff_hunks(
12455 "
12456 aaa
12457 - bbb
12458 + BBB
12459
12460 - ddd
12461 - eee
12462 + EEE
12463 fff
12464 "
12465 .unindent(),
12466 );
12467}
12468
12469#[gpui::test]
12470async fn test_edits_around_expanded_insertion_hunks(
12471 executor: BackgroundExecutor,
12472 cx: &mut gpui::TestAppContext,
12473) {
12474 init_test(cx, |_| {});
12475
12476 let mut cx = EditorTestContext::new(cx).await;
12477
12478 let diff_base = r#"
12479 use some::mod1;
12480 use some::mod2;
12481
12482 const A: u32 = 42;
12483
12484 fn main() {
12485 println!("hello");
12486
12487 println!("world");
12488 }
12489 "#
12490 .unindent();
12491 executor.run_until_parked();
12492 cx.set_state(
12493 &r#"
12494 use some::mod1;
12495 use some::mod2;
12496
12497 const A: u32 = 42;
12498 const B: u32 = 42;
12499 const C: u32 = 42;
12500 ˇ
12501
12502 fn main() {
12503 println!("hello");
12504
12505 println!("world");
12506 }
12507 "#
12508 .unindent(),
12509 );
12510
12511 cx.set_diff_base(Some(&diff_base));
12512 executor.run_until_parked();
12513
12514 cx.update_editor(|editor, cx| {
12515 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12516 });
12517 executor.run_until_parked();
12518
12519 cx.assert_diff_hunks(
12520 r#"
12521 use some::mod1;
12522 use some::mod2;
12523
12524 const A: u32 = 42;
12525 + const B: u32 = 42;
12526 + const C: u32 = 42;
12527 +
12528
12529 fn main() {
12530 println!("hello");
12531
12532 println!("world");
12533 }
12534 "#
12535 .unindent(),
12536 );
12537
12538 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
12539 executor.run_until_parked();
12540
12541 cx.assert_diff_hunks(
12542 r#"
12543 use some::mod1;
12544 use some::mod2;
12545
12546 const A: u32 = 42;
12547 + const B: u32 = 42;
12548 + const C: u32 = 42;
12549 + const D: u32 = 42;
12550 +
12551
12552 fn main() {
12553 println!("hello");
12554
12555 println!("world");
12556 }
12557 "#
12558 .unindent(),
12559 );
12560
12561 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
12562 executor.run_until_parked();
12563
12564 cx.assert_diff_hunks(
12565 r#"
12566 use some::mod1;
12567 use some::mod2;
12568
12569 const A: u32 = 42;
12570 + const B: u32 = 42;
12571 + const C: u32 = 42;
12572 + const D: u32 = 42;
12573 + const E: u32 = 42;
12574 +
12575
12576 fn main() {
12577 println!("hello");
12578
12579 println!("world");
12580 }
12581 "#
12582 .unindent(),
12583 );
12584
12585 cx.update_editor(|editor, cx| {
12586 editor.delete_line(&DeleteLine, cx);
12587 });
12588 executor.run_until_parked();
12589
12590 cx.assert_diff_hunks(
12591 r#"
12592 use some::mod1;
12593 use some::mod2;
12594
12595 const A: u32 = 42;
12596 + const B: u32 = 42;
12597 + const C: u32 = 42;
12598 + const D: u32 = 42;
12599 + const E: u32 = 42;
12600
12601 fn main() {
12602 println!("hello");
12603
12604 println!("world");
12605 }
12606 "#
12607 .unindent(),
12608 );
12609
12610 cx.update_editor(|editor, cx| {
12611 editor.move_up(&MoveUp, cx);
12612 editor.delete_line(&DeleteLine, cx);
12613 editor.move_up(&MoveUp, cx);
12614 editor.delete_line(&DeleteLine, cx);
12615 editor.move_up(&MoveUp, cx);
12616 editor.delete_line(&DeleteLine, cx);
12617 });
12618 executor.run_until_parked();
12619 cx.assert_editor_state(
12620 &r#"
12621 use some::mod1;
12622 use some::mod2;
12623
12624 const A: u32 = 42;
12625 const B: u32 = 42;
12626 ˇ
12627 fn main() {
12628 println!("hello");
12629
12630 println!("world");
12631 }
12632 "#
12633 .unindent(),
12634 );
12635
12636 cx.assert_diff_hunks(
12637 r#"
12638 use some::mod1;
12639 use some::mod2;
12640
12641 const A: u32 = 42;
12642 + const B: u32 = 42;
12643
12644 fn main() {
12645 println!("hello");
12646
12647 println!("world");
12648 }
12649 "#
12650 .unindent(),
12651 );
12652
12653 cx.update_editor(|editor, cx| {
12654 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
12655 editor.delete_line(&DeleteLine, cx);
12656 });
12657 executor.run_until_parked();
12658 cx.assert_diff_hunks(
12659 r#"
12660 use some::mod1;
12661 - use some::mod2;
12662 -
12663 - const A: u32 = 42;
12664
12665 fn main() {
12666 println!("hello");
12667
12668 println!("world");
12669 }
12670 "#
12671 .unindent(),
12672 );
12673}
12674
12675#[gpui::test]
12676async fn test_edits_around_expanded_deletion_hunks(
12677 executor: BackgroundExecutor,
12678 cx: &mut gpui::TestAppContext,
12679) {
12680 init_test(cx, |_| {});
12681
12682 let mut cx = EditorTestContext::new(cx).await;
12683
12684 let diff_base = r#"
12685 use some::mod1;
12686 use some::mod2;
12687
12688 const A: u32 = 42;
12689 const B: u32 = 42;
12690 const C: u32 = 42;
12691
12692
12693 fn main() {
12694 println!("hello");
12695
12696 println!("world");
12697 }
12698 "#
12699 .unindent();
12700 executor.run_until_parked();
12701 cx.set_state(
12702 &r#"
12703 use some::mod1;
12704 use some::mod2;
12705
12706 ˇconst B: u32 = 42;
12707 const C: u32 = 42;
12708
12709
12710 fn main() {
12711 println!("hello");
12712
12713 println!("world");
12714 }
12715 "#
12716 .unindent(),
12717 );
12718
12719 cx.set_diff_base(Some(&diff_base));
12720 executor.run_until_parked();
12721
12722 cx.update_editor(|editor, cx| {
12723 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12724 });
12725 executor.run_until_parked();
12726
12727 cx.assert_diff_hunks(
12728 r#"
12729 use some::mod1;
12730 use some::mod2;
12731
12732 - const A: u32 = 42;
12733 const B: u32 = 42;
12734 const C: u32 = 42;
12735
12736
12737 fn main() {
12738 println!("hello");
12739
12740 println!("world");
12741 }
12742 "#
12743 .unindent(),
12744 );
12745
12746 cx.update_editor(|editor, cx| {
12747 editor.delete_line(&DeleteLine, cx);
12748 });
12749 executor.run_until_parked();
12750 cx.assert_editor_state(
12751 &r#"
12752 use some::mod1;
12753 use some::mod2;
12754
12755 ˇconst C: u32 = 42;
12756
12757
12758 fn main() {
12759 println!("hello");
12760
12761 println!("world");
12762 }
12763 "#
12764 .unindent(),
12765 );
12766 cx.assert_diff_hunks(
12767 r#"
12768 use some::mod1;
12769 use some::mod2;
12770
12771 - const A: u32 = 42;
12772 - const B: u32 = 42;
12773 const C: u32 = 42;
12774
12775
12776 fn main() {
12777 println!("hello");
12778
12779 println!("world");
12780 }
12781 "#
12782 .unindent(),
12783 );
12784
12785 cx.update_editor(|editor, cx| {
12786 editor.delete_line(&DeleteLine, cx);
12787 });
12788 executor.run_until_parked();
12789 cx.assert_editor_state(
12790 &r#"
12791 use some::mod1;
12792 use some::mod2;
12793
12794 ˇ
12795
12796 fn main() {
12797 println!("hello");
12798
12799 println!("world");
12800 }
12801 "#
12802 .unindent(),
12803 );
12804 cx.assert_diff_hunks(
12805 r#"
12806 use some::mod1;
12807 use some::mod2;
12808
12809 - const A: u32 = 42;
12810 - const B: u32 = 42;
12811 - const C: u32 = 42;
12812
12813
12814 fn main() {
12815 println!("hello");
12816
12817 println!("world");
12818 }
12819 "#
12820 .unindent(),
12821 );
12822
12823 cx.update_editor(|editor, cx| {
12824 editor.handle_input("replacement", cx);
12825 });
12826 executor.run_until_parked();
12827 cx.assert_editor_state(
12828 &r#"
12829 use some::mod1;
12830 use some::mod2;
12831
12832 replacementˇ
12833
12834 fn main() {
12835 println!("hello");
12836
12837 println!("world");
12838 }
12839 "#
12840 .unindent(),
12841 );
12842 cx.assert_diff_hunks(
12843 r#"
12844 use some::mod1;
12845 use some::mod2;
12846
12847 - const A: u32 = 42;
12848 - const B: u32 = 42;
12849 - const C: u32 = 42;
12850 -
12851 + replacement
12852
12853 fn main() {
12854 println!("hello");
12855
12856 println!("world");
12857 }
12858 "#
12859 .unindent(),
12860 );
12861}
12862
12863#[gpui::test]
12864async fn test_edit_after_expanded_modification_hunk(
12865 executor: BackgroundExecutor,
12866 cx: &mut gpui::TestAppContext,
12867) {
12868 init_test(cx, |_| {});
12869
12870 let mut cx = EditorTestContext::new(cx).await;
12871
12872 let diff_base = r#"
12873 use some::mod1;
12874 use some::mod2;
12875
12876 const A: u32 = 42;
12877 const B: u32 = 42;
12878 const C: u32 = 42;
12879 const D: u32 = 42;
12880
12881
12882 fn main() {
12883 println!("hello");
12884
12885 println!("world");
12886 }"#
12887 .unindent();
12888
12889 cx.set_state(
12890 &r#"
12891 use some::mod1;
12892 use some::mod2;
12893
12894 const A: u32 = 42;
12895 const B: u32 = 42;
12896 const C: u32 = 43ˇ
12897 const D: u32 = 42;
12898
12899
12900 fn main() {
12901 println!("hello");
12902
12903 println!("world");
12904 }"#
12905 .unindent(),
12906 );
12907
12908 cx.set_diff_base(Some(&diff_base));
12909 executor.run_until_parked();
12910 cx.update_editor(|editor, cx| {
12911 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12912 });
12913 executor.run_until_parked();
12914
12915 cx.assert_diff_hunks(
12916 r#"
12917 use some::mod1;
12918 use some::mod2;
12919
12920 const A: u32 = 42;
12921 const B: u32 = 42;
12922 - const C: u32 = 42;
12923 + const C: u32 = 43
12924 const D: u32 = 42;
12925
12926
12927 fn main() {
12928 println!("hello");
12929
12930 println!("world");
12931 }"#
12932 .unindent(),
12933 );
12934
12935 cx.update_editor(|editor, cx| {
12936 editor.handle_input("\nnew_line\n", cx);
12937 });
12938 executor.run_until_parked();
12939
12940 cx.assert_diff_hunks(
12941 r#"
12942 use some::mod1;
12943 use some::mod2;
12944
12945 const A: u32 = 42;
12946 const B: u32 = 42;
12947 - const C: u32 = 42;
12948 + const C: u32 = 43
12949 + new_line
12950 +
12951 const D: u32 = 42;
12952
12953
12954 fn main() {
12955 println!("hello");
12956
12957 println!("world");
12958 }"#
12959 .unindent(),
12960 );
12961}
12962
12963async fn setup_indent_guides_editor(
12964 text: &str,
12965 cx: &mut gpui::TestAppContext,
12966) -> (BufferId, EditorTestContext) {
12967 init_test(cx, |_| {});
12968
12969 let mut cx = EditorTestContext::new(cx).await;
12970
12971 let buffer_id = cx.update_editor(|editor, cx| {
12972 editor.set_text(text, cx);
12973 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12974
12975 buffer_ids[0]
12976 });
12977
12978 (buffer_id, cx)
12979}
12980
12981fn assert_indent_guides(
12982 range: Range<u32>,
12983 expected: Vec<IndentGuide>,
12984 active_indices: Option<Vec<usize>>,
12985 cx: &mut EditorTestContext,
12986) {
12987 let indent_guides = cx.update_editor(|editor, cx| {
12988 let snapshot = editor.snapshot(cx).display_snapshot;
12989 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
12990 MultiBufferRow(range.start)..MultiBufferRow(range.end),
12991 true,
12992 &snapshot,
12993 cx,
12994 );
12995
12996 indent_guides.sort_by(|a, b| {
12997 a.depth.cmp(&b.depth).then(
12998 a.start_row
12999 .cmp(&b.start_row)
13000 .then(a.end_row.cmp(&b.end_row)),
13001 )
13002 });
13003 indent_guides
13004 });
13005
13006 if let Some(expected) = active_indices {
13007 let active_indices = cx.update_editor(|editor, cx| {
13008 let snapshot = editor.snapshot(cx).display_snapshot;
13009 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
13010 });
13011
13012 assert_eq!(
13013 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
13014 expected,
13015 "Active indent guide indices do not match"
13016 );
13017 }
13018
13019 let expected: Vec<_> = expected
13020 .into_iter()
13021 .map(|guide| MultiBufferIndentGuide {
13022 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
13023 buffer: guide,
13024 })
13025 .collect();
13026
13027 assert_eq!(indent_guides, expected, "Indent guides do not match");
13028}
13029
13030fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
13031 IndentGuide {
13032 buffer_id,
13033 start_row,
13034 end_row,
13035 depth,
13036 tab_size: 4,
13037 settings: IndentGuideSettings {
13038 enabled: true,
13039 line_width: 1,
13040 active_line_width: 1,
13041 ..Default::default()
13042 },
13043 }
13044}
13045
13046#[gpui::test]
13047async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13048 let (buffer_id, mut cx) = setup_indent_guides_editor(
13049 &"
13050 fn main() {
13051 let a = 1;
13052 }"
13053 .unindent(),
13054 cx,
13055 )
13056 .await;
13057
13058 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13059}
13060
13061#[gpui::test]
13062async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
13063 let (buffer_id, mut cx) = setup_indent_guides_editor(
13064 &"
13065 fn main() {
13066 let a = 1;
13067 let b = 2;
13068 }"
13069 .unindent(),
13070 cx,
13071 )
13072 .await;
13073
13074 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
13075}
13076
13077#[gpui::test]
13078async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
13079 let (buffer_id, mut cx) = setup_indent_guides_editor(
13080 &"
13081 fn main() {
13082 let a = 1;
13083 if a == 3 {
13084 let b = 2;
13085 } else {
13086 let c = 3;
13087 }
13088 }"
13089 .unindent(),
13090 cx,
13091 )
13092 .await;
13093
13094 assert_indent_guides(
13095 0..8,
13096 vec![
13097 indent_guide(buffer_id, 1, 6, 0),
13098 indent_guide(buffer_id, 3, 3, 1),
13099 indent_guide(buffer_id, 5, 5, 1),
13100 ],
13101 None,
13102 &mut cx,
13103 );
13104}
13105
13106#[gpui::test]
13107async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
13108 let (buffer_id, mut cx) = setup_indent_guides_editor(
13109 &"
13110 fn main() {
13111 let a = 1;
13112 let b = 2;
13113 let c = 3;
13114 }"
13115 .unindent(),
13116 cx,
13117 )
13118 .await;
13119
13120 assert_indent_guides(
13121 0..5,
13122 vec![
13123 indent_guide(buffer_id, 1, 3, 0),
13124 indent_guide(buffer_id, 2, 2, 1),
13125 ],
13126 None,
13127 &mut cx,
13128 );
13129}
13130
13131#[gpui::test]
13132async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
13133 let (buffer_id, mut cx) = setup_indent_guides_editor(
13134 &"
13135 fn main() {
13136 let a = 1;
13137
13138 let c = 3;
13139 }"
13140 .unindent(),
13141 cx,
13142 )
13143 .await;
13144
13145 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
13146}
13147
13148#[gpui::test]
13149async fn test_indent_guide_complex(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 if a == 3 {
13158 let b = 2;
13159 } else {
13160 let c = 3;
13161 }
13162 }"
13163 .unindent(),
13164 cx,
13165 )
13166 .await;
13167
13168 assert_indent_guides(
13169 0..11,
13170 vec![
13171 indent_guide(buffer_id, 1, 9, 0),
13172 indent_guide(buffer_id, 6, 6, 1),
13173 indent_guide(buffer_id, 8, 8, 1),
13174 ],
13175 None,
13176 &mut cx,
13177 );
13178}
13179
13180#[gpui::test]
13181async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
13182 let (buffer_id, mut cx) = setup_indent_guides_editor(
13183 &"
13184 fn main() {
13185 let a = 1;
13186
13187 let c = 3;
13188
13189 if a == 3 {
13190 let b = 2;
13191 } else {
13192 let c = 3;
13193 }
13194 }"
13195 .unindent(),
13196 cx,
13197 )
13198 .await;
13199
13200 assert_indent_guides(
13201 1..11,
13202 vec![
13203 indent_guide(buffer_id, 1, 9, 0),
13204 indent_guide(buffer_id, 6, 6, 1),
13205 indent_guide(buffer_id, 8, 8, 1),
13206 ],
13207 None,
13208 &mut cx,
13209 );
13210}
13211
13212#[gpui::test]
13213async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
13214 let (buffer_id, mut cx) = setup_indent_guides_editor(
13215 &"
13216 fn main() {
13217 let a = 1;
13218
13219 let c = 3;
13220
13221 if a == 3 {
13222 let b = 2;
13223 } else {
13224 let c = 3;
13225 }
13226 }"
13227 .unindent(),
13228 cx,
13229 )
13230 .await;
13231
13232 assert_indent_guides(
13233 1..10,
13234 vec![
13235 indent_guide(buffer_id, 1, 9, 0),
13236 indent_guide(buffer_id, 6, 6, 1),
13237 indent_guide(buffer_id, 8, 8, 1),
13238 ],
13239 None,
13240 &mut cx,
13241 );
13242}
13243
13244#[gpui::test]
13245async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
13246 let (buffer_id, mut cx) = setup_indent_guides_editor(
13247 &"
13248 block1
13249 block2
13250 block3
13251 block4
13252 block2
13253 block1
13254 block1"
13255 .unindent(),
13256 cx,
13257 )
13258 .await;
13259
13260 assert_indent_guides(
13261 1..10,
13262 vec![
13263 indent_guide(buffer_id, 1, 4, 0),
13264 indent_guide(buffer_id, 2, 3, 1),
13265 indent_guide(buffer_id, 3, 3, 2),
13266 ],
13267 None,
13268 &mut cx,
13269 );
13270}
13271
13272#[gpui::test]
13273async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
13274 let (buffer_id, mut cx) = setup_indent_guides_editor(
13275 &"
13276 block1
13277 block2
13278 block3
13279
13280 block1
13281 block1"
13282 .unindent(),
13283 cx,
13284 )
13285 .await;
13286
13287 assert_indent_guides(
13288 0..6,
13289 vec![
13290 indent_guide(buffer_id, 1, 2, 0),
13291 indent_guide(buffer_id, 2, 2, 1),
13292 ],
13293 None,
13294 &mut cx,
13295 );
13296}
13297
13298#[gpui::test]
13299async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
13300 let (buffer_id, mut cx) = setup_indent_guides_editor(
13301 &"
13302 block1
13303
13304
13305
13306 block2
13307 "
13308 .unindent(),
13309 cx,
13310 )
13311 .await;
13312
13313 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13314}
13315
13316#[gpui::test]
13317async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
13318 let (buffer_id, mut cx) = setup_indent_guides_editor(
13319 &"
13320 def a:
13321 \tb = 3
13322 \tif True:
13323 \t\tc = 4
13324 \t\td = 5
13325 \tprint(b)
13326 "
13327 .unindent(),
13328 cx,
13329 )
13330 .await;
13331
13332 assert_indent_guides(
13333 0..6,
13334 vec![
13335 indent_guide(buffer_id, 1, 6, 0),
13336 indent_guide(buffer_id, 3, 4, 1),
13337 ],
13338 None,
13339 &mut cx,
13340 );
13341}
13342
13343#[gpui::test]
13344async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13345 let (buffer_id, mut cx) = setup_indent_guides_editor(
13346 &"
13347 fn main() {
13348 let a = 1;
13349 }"
13350 .unindent(),
13351 cx,
13352 )
13353 .await;
13354
13355 cx.update_editor(|editor, cx| {
13356 editor.change_selections(None, cx, |s| {
13357 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13358 });
13359 });
13360
13361 assert_indent_guides(
13362 0..3,
13363 vec![indent_guide(buffer_id, 1, 1, 0)],
13364 Some(vec![0]),
13365 &mut cx,
13366 );
13367}
13368
13369#[gpui::test]
13370async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13371 let (buffer_id, mut cx) = setup_indent_guides_editor(
13372 &"
13373 fn main() {
13374 if 1 == 2 {
13375 let a = 1;
13376 }
13377 }"
13378 .unindent(),
13379 cx,
13380 )
13381 .await;
13382
13383 cx.update_editor(|editor, cx| {
13384 editor.change_selections(None, cx, |s| {
13385 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13386 });
13387 });
13388
13389 assert_indent_guides(
13390 0..4,
13391 vec![
13392 indent_guide(buffer_id, 1, 3, 0),
13393 indent_guide(buffer_id, 2, 2, 1),
13394 ],
13395 Some(vec![1]),
13396 &mut cx,
13397 );
13398
13399 cx.update_editor(|editor, cx| {
13400 editor.change_selections(None, cx, |s| {
13401 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13402 });
13403 });
13404
13405 assert_indent_guides(
13406 0..4,
13407 vec![
13408 indent_guide(buffer_id, 1, 3, 0),
13409 indent_guide(buffer_id, 2, 2, 1),
13410 ],
13411 Some(vec![1]),
13412 &mut cx,
13413 );
13414
13415 cx.update_editor(|editor, cx| {
13416 editor.change_selections(None, cx, |s| {
13417 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13418 });
13419 });
13420
13421 assert_indent_guides(
13422 0..4,
13423 vec![
13424 indent_guide(buffer_id, 1, 3, 0),
13425 indent_guide(buffer_id, 2, 2, 1),
13426 ],
13427 Some(vec![0]),
13428 &mut cx,
13429 );
13430}
13431
13432#[gpui::test]
13433async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13434 let (buffer_id, mut cx) = setup_indent_guides_editor(
13435 &"
13436 fn main() {
13437 let a = 1;
13438
13439 let b = 2;
13440 }"
13441 .unindent(),
13442 cx,
13443 )
13444 .await;
13445
13446 cx.update_editor(|editor, cx| {
13447 editor.change_selections(None, cx, |s| {
13448 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13449 });
13450 });
13451
13452 assert_indent_guides(
13453 0..5,
13454 vec![indent_guide(buffer_id, 1, 3, 0)],
13455 Some(vec![0]),
13456 &mut cx,
13457 );
13458}
13459
13460#[gpui::test]
13461async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13462 let (buffer_id, mut cx) = setup_indent_guides_editor(
13463 &"
13464 def m:
13465 a = 1
13466 pass"
13467 .unindent(),
13468 cx,
13469 )
13470 .await;
13471
13472 cx.update_editor(|editor, cx| {
13473 editor.change_selections(None, cx, |s| {
13474 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13475 });
13476 });
13477
13478 assert_indent_guides(
13479 0..3,
13480 vec![indent_guide(buffer_id, 1, 2, 0)],
13481 Some(vec![0]),
13482 &mut cx,
13483 );
13484}
13485
13486#[gpui::test]
13487fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13488 init_test(cx, |_| {});
13489
13490 let editor = cx.add_window(|cx| {
13491 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13492 build_editor(buffer, cx)
13493 });
13494
13495 let render_args = Arc::new(Mutex::new(None));
13496 let snapshot = editor
13497 .update(cx, |editor, cx| {
13498 let snapshot = editor.buffer().read(cx).snapshot(cx);
13499 let range =
13500 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13501
13502 struct RenderArgs {
13503 row: MultiBufferRow,
13504 folded: bool,
13505 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13506 }
13507
13508 let crease = Crease::inline(
13509 range,
13510 FoldPlaceholder::test(),
13511 {
13512 let toggle_callback = render_args.clone();
13513 move |row, folded, callback, _cx| {
13514 *toggle_callback.lock() = Some(RenderArgs {
13515 row,
13516 folded,
13517 callback,
13518 });
13519 div()
13520 }
13521 },
13522 |_row, _folded, _cx| div(),
13523 );
13524
13525 editor.insert_creases(Some(crease), cx);
13526 let snapshot = editor.snapshot(cx);
13527 let _div =
13528 snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13529 snapshot
13530 })
13531 .unwrap();
13532
13533 let render_args = render_args.lock().take().unwrap();
13534 assert_eq!(render_args.row, MultiBufferRow(1));
13535 assert!(!render_args.folded);
13536 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13537
13538 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13539 .unwrap();
13540 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13541 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13542
13543 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13544 .unwrap();
13545 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13546 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13547}
13548
13549#[gpui::test]
13550async fn test_input_text(cx: &mut gpui::TestAppContext) {
13551 init_test(cx, |_| {});
13552 let mut cx = EditorTestContext::new(cx).await;
13553
13554 cx.set_state(
13555 &r#"ˇone
13556 two
13557
13558 three
13559 fourˇ
13560 five
13561
13562 siˇx"#
13563 .unindent(),
13564 );
13565
13566 cx.dispatch_action(HandleInput(String::new()));
13567 cx.assert_editor_state(
13568 &r#"ˇone
13569 two
13570
13571 three
13572 fourˇ
13573 five
13574
13575 siˇx"#
13576 .unindent(),
13577 );
13578
13579 cx.dispatch_action(HandleInput("AAAA".to_string()));
13580 cx.assert_editor_state(
13581 &r#"AAAAˇone
13582 two
13583
13584 three
13585 fourAAAAˇ
13586 five
13587
13588 siAAAAˇx"#
13589 .unindent(),
13590 );
13591}
13592
13593#[gpui::test]
13594async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13595 init_test(cx, |_| {});
13596
13597 let mut cx = EditorTestContext::new(cx).await;
13598 cx.set_state(
13599 r#"let foo = 1;
13600let foo = 2;
13601let foo = 3;
13602let fooˇ = 4;
13603let foo = 5;
13604let foo = 6;
13605let foo = 7;
13606let foo = 8;
13607let foo = 9;
13608let foo = 10;
13609let foo = 11;
13610let foo = 12;
13611let foo = 13;
13612let foo = 14;
13613let foo = 15;"#,
13614 );
13615
13616 cx.update_editor(|e, cx| {
13617 assert_eq!(
13618 e.next_scroll_position,
13619 NextScrollCursorCenterTopBottom::Center,
13620 "Default next scroll direction is center",
13621 );
13622
13623 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13624 assert_eq!(
13625 e.next_scroll_position,
13626 NextScrollCursorCenterTopBottom::Top,
13627 "After center, next scroll direction should be top",
13628 );
13629
13630 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13631 assert_eq!(
13632 e.next_scroll_position,
13633 NextScrollCursorCenterTopBottom::Bottom,
13634 "After top, next scroll direction should be bottom",
13635 );
13636
13637 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13638 assert_eq!(
13639 e.next_scroll_position,
13640 NextScrollCursorCenterTopBottom::Center,
13641 "After bottom, scrolling should start over",
13642 );
13643
13644 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13645 assert_eq!(
13646 e.next_scroll_position,
13647 NextScrollCursorCenterTopBottom::Top,
13648 "Scrolling continues if retriggered fast enough"
13649 );
13650 });
13651
13652 cx.executor()
13653 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13654 cx.executor().run_until_parked();
13655 cx.update_editor(|e, _| {
13656 assert_eq!(
13657 e.next_scroll_position,
13658 NextScrollCursorCenterTopBottom::Center,
13659 "If scrolling is not triggered fast enough, it should reset"
13660 );
13661 });
13662}
13663
13664#[gpui::test]
13665async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13666 init_test(cx, |_| {});
13667 let mut cx = EditorLspTestContext::new_rust(
13668 lsp::ServerCapabilities {
13669 definition_provider: Some(lsp::OneOf::Left(true)),
13670 references_provider: Some(lsp::OneOf::Left(true)),
13671 ..lsp::ServerCapabilities::default()
13672 },
13673 cx,
13674 )
13675 .await;
13676
13677 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13678 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13679 move |params, _| async move {
13680 if empty_go_to_definition {
13681 Ok(None)
13682 } else {
13683 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13684 uri: params.text_document_position_params.text_document.uri,
13685 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13686 })))
13687 }
13688 },
13689 );
13690 let references =
13691 cx.lsp
13692 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13693 Ok(Some(vec![lsp::Location {
13694 uri: params.text_document_position.text_document.uri,
13695 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13696 }]))
13697 });
13698 (go_to_definition, references)
13699 };
13700
13701 cx.set_state(
13702 &r#"fn one() {
13703 let mut a = ˇtwo();
13704 }
13705
13706 fn two() {}"#
13707 .unindent(),
13708 );
13709 set_up_lsp_handlers(false, &mut cx);
13710 let navigated = cx
13711 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13712 .await
13713 .expect("Failed to navigate to definition");
13714 assert_eq!(
13715 navigated,
13716 Navigated::Yes,
13717 "Should have navigated to definition from the GetDefinition response"
13718 );
13719 cx.assert_editor_state(
13720 &r#"fn one() {
13721 let mut a = two();
13722 }
13723
13724 fn «twoˇ»() {}"#
13725 .unindent(),
13726 );
13727
13728 let editors = cx.update_workspace(|workspace, cx| {
13729 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13730 });
13731 cx.update_editor(|_, test_editor_cx| {
13732 assert_eq!(
13733 editors.len(),
13734 1,
13735 "Initially, only one, test, editor should be open in the workspace"
13736 );
13737 assert_eq!(
13738 test_editor_cx.view(),
13739 editors.last().expect("Asserted len is 1")
13740 );
13741 });
13742
13743 set_up_lsp_handlers(true, &mut cx);
13744 let navigated = cx
13745 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13746 .await
13747 .expect("Failed to navigate to lookup references");
13748 assert_eq!(
13749 navigated,
13750 Navigated::Yes,
13751 "Should have navigated to references as a fallback after empty GoToDefinition response"
13752 );
13753 // We should not change the selections in the existing file,
13754 // if opening another milti buffer with the references
13755 cx.assert_editor_state(
13756 &r#"fn one() {
13757 let mut a = two();
13758 }
13759
13760 fn «twoˇ»() {}"#
13761 .unindent(),
13762 );
13763 let editors = cx.update_workspace(|workspace, cx| {
13764 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13765 });
13766 cx.update_editor(|_, test_editor_cx| {
13767 assert_eq!(
13768 editors.len(),
13769 2,
13770 "After falling back to references search, we open a new editor with the results"
13771 );
13772 let references_fallback_text = editors
13773 .into_iter()
13774 .find(|new_editor| new_editor != test_editor_cx.view())
13775 .expect("Should have one non-test editor now")
13776 .read(test_editor_cx)
13777 .text(test_editor_cx);
13778 assert_eq!(
13779 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13780 "Should use the range from the references response and not the GoToDefinition one"
13781 );
13782 });
13783}
13784
13785#[gpui::test]
13786async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
13787 init_test(cx, |_| {});
13788
13789 let language = Arc::new(Language::new(
13790 LanguageConfig::default(),
13791 Some(tree_sitter_rust::LANGUAGE.into()),
13792 ));
13793
13794 let text = r#"
13795 #[cfg(test)]
13796 mod tests() {
13797 #[test]
13798 fn runnable_1() {
13799 let a = 1;
13800 }
13801
13802 #[test]
13803 fn runnable_2() {
13804 let a = 1;
13805 let b = 2;
13806 }
13807 }
13808 "#
13809 .unindent();
13810
13811 let fs = FakeFs::new(cx.executor());
13812 fs.insert_file("/file.rs", Default::default()).await;
13813
13814 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13815 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
13816 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13817 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
13818 let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
13819
13820 let editor = cx.new_view(|cx| {
13821 Editor::new(
13822 EditorMode::Full,
13823 multi_buffer,
13824 Some(project.clone()),
13825 true,
13826 cx,
13827 )
13828 });
13829
13830 editor.update(cx, |editor, cx| {
13831 editor.tasks.insert(
13832 (buffer.read(cx).remote_id(), 3),
13833 RunnableTasks {
13834 templates: vec![],
13835 offset: MultiBufferOffset(43),
13836 column: 0,
13837 extra_variables: HashMap::default(),
13838 context_range: BufferOffset(43)..BufferOffset(85),
13839 },
13840 );
13841 editor.tasks.insert(
13842 (buffer.read(cx).remote_id(), 8),
13843 RunnableTasks {
13844 templates: vec![],
13845 offset: MultiBufferOffset(86),
13846 column: 0,
13847 extra_variables: HashMap::default(),
13848 context_range: BufferOffset(86)..BufferOffset(191),
13849 },
13850 );
13851
13852 // Test finding task when cursor is inside function body
13853 editor.change_selections(None, cx, |s| {
13854 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
13855 });
13856 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13857 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
13858
13859 // Test finding task when cursor is on function name
13860 editor.change_selections(None, cx, |s| {
13861 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
13862 });
13863 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13864 assert_eq!(row, 8, "Should find task when cursor is on function name");
13865 });
13866}
13867
13868fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13869 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13870 point..point
13871}
13872
13873fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13874 let (text, ranges) = marked_text_ranges(marked_text, true);
13875 assert_eq!(view.text(cx), text);
13876 assert_eq!(
13877 view.selections.ranges(cx),
13878 ranges,
13879 "Assert selections are {}",
13880 marked_text
13881 );
13882}
13883
13884pub fn handle_signature_help_request(
13885 cx: &mut EditorLspTestContext,
13886 mocked_response: lsp::SignatureHelp,
13887) -> impl Future<Output = ()> {
13888 let mut request =
13889 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13890 let mocked_response = mocked_response.clone();
13891 async move { Ok(Some(mocked_response)) }
13892 });
13893
13894 async move {
13895 request.next().await;
13896 }
13897}
13898
13899/// Handle completion request passing a marked string specifying where the completion
13900/// should be triggered from using '|' character, what range should be replaced, and what completions
13901/// should be returned using '<' and '>' to delimit the range
13902pub fn handle_completion_request(
13903 cx: &mut EditorLspTestContext,
13904 marked_string: &str,
13905 completions: Vec<&'static str>,
13906 counter: Arc<AtomicUsize>,
13907) -> impl Future<Output = ()> {
13908 let complete_from_marker: TextRangeMarker = '|'.into();
13909 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13910 let (_, mut marked_ranges) = marked_text_ranges_by(
13911 marked_string,
13912 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13913 );
13914
13915 let complete_from_position =
13916 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13917 let replace_range =
13918 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13919
13920 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13921 let completions = completions.clone();
13922 counter.fetch_add(1, atomic::Ordering::Release);
13923 async move {
13924 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13925 assert_eq!(
13926 params.text_document_position.position,
13927 complete_from_position
13928 );
13929 Ok(Some(lsp::CompletionResponse::Array(
13930 completions
13931 .iter()
13932 .map(|completion_text| lsp::CompletionItem {
13933 label: completion_text.to_string(),
13934 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13935 range: replace_range,
13936 new_text: completion_text.to_string(),
13937 })),
13938 ..Default::default()
13939 })
13940 .collect(),
13941 )))
13942 }
13943 });
13944
13945 async move {
13946 request.next().await;
13947 }
13948}
13949
13950fn handle_resolve_completion_request(
13951 cx: &mut EditorLspTestContext,
13952 edits: Option<Vec<(&'static str, &'static str)>>,
13953) -> impl Future<Output = ()> {
13954 let edits = edits.map(|edits| {
13955 edits
13956 .iter()
13957 .map(|(marked_string, new_text)| {
13958 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13959 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13960 lsp::TextEdit::new(replace_range, new_text.to_string())
13961 })
13962 .collect::<Vec<_>>()
13963 });
13964
13965 let mut request =
13966 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13967 let edits = edits.clone();
13968 async move {
13969 Ok(lsp::CompletionItem {
13970 additional_text_edits: edits,
13971 ..Default::default()
13972 })
13973 }
13974 });
13975
13976 async move {
13977 request.next().await;
13978 }
13979}
13980
13981pub(crate) fn update_test_language_settings(
13982 cx: &mut TestAppContext,
13983 f: impl Fn(&mut AllLanguageSettingsContent),
13984) {
13985 cx.update(|cx| {
13986 SettingsStore::update_global(cx, |store, cx| {
13987 store.update_user_settings::<AllLanguageSettings>(cx, f);
13988 });
13989 });
13990}
13991
13992pub(crate) fn update_test_project_settings(
13993 cx: &mut TestAppContext,
13994 f: impl Fn(&mut ProjectSettings),
13995) {
13996 cx.update(|cx| {
13997 SettingsStore::update_global(cx, |store, cx| {
13998 store.update_user_settings::<ProjectSettings>(cx, f);
13999 });
14000 });
14001}
14002
14003pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
14004 cx.update(|cx| {
14005 assets::Assets.load_test_fonts(cx);
14006 let store = SettingsStore::test(cx);
14007 cx.set_global(store);
14008 theme::init(theme::LoadThemes::JustBase, cx);
14009 release_channel::init(SemanticVersion::default(), cx);
14010 client::init_settings(cx);
14011 language::init(cx);
14012 Project::init_settings(cx);
14013 workspace::init_settings(cx);
14014 crate::init(cx);
14015 });
14016
14017 update_test_language_settings(cx, f);
14018}
14019
14020#[track_caller]
14021fn assert_hunk_revert(
14022 not_reverted_text_with_selections: &str,
14023 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
14024 expected_reverted_text_with_selections: &str,
14025 base_text: &str,
14026 cx: &mut EditorLspTestContext,
14027) {
14028 cx.set_state(not_reverted_text_with_selections);
14029 cx.update_editor(|editor, cx| {
14030 editor
14031 .buffer()
14032 .read(cx)
14033 .as_singleton()
14034 .unwrap()
14035 .update(cx, |buffer, cx| {
14036 buffer.set_diff_base(Some(base_text.into()), cx);
14037 });
14038 });
14039 cx.executor().run_until_parked();
14040
14041 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
14042 let snapshot = editor.buffer().read(cx).snapshot(cx);
14043 let reverted_hunk_statuses = snapshot
14044 .git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
14045 .map(|hunk| hunk_status(&hunk))
14046 .collect::<Vec<_>>();
14047
14048 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
14049 reverted_hunk_statuses
14050 });
14051 cx.executor().run_until_parked();
14052 cx.assert_editor_state(expected_reverted_text_with_selections);
14053 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
14054}