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, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
13 WindowBounds, WindowOptions,
14};
15use indoc::indoc;
16use language::{
17 language_settings::{
18 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
19 },
20 BracketPairConfig,
21 Capability::ReadWrite,
22 FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
23 LanguageName, Override, ParsedMarkdown, Point,
24};
25use language_settings::{Formatter, FormatterList, IndentGuideSettings};
26use multi_buffer::MultiBufferIndentGuide;
27use parking_lot::Mutex;
28use project::{buffer_store::BufferChangeSet, FakeFs};
29use project::{
30 lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
31 project_settings::{LspSettings, ProjectSettings},
32};
33use serde_json::{self, json};
34use std::sync::atomic::AtomicUsize;
35use std::sync::atomic::{self, AtomicBool};
36use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
37use test::editor_lsp_test_context::rust_lang;
38use unindent::Unindent;
39use util::{
40 assert_set_eq,
41 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
42};
43use workspace::{
44 item::{FollowEvent, FollowableItem, Item, ItemHandle},
45 NavigationEntry, ViewId,
46};
47
48#[gpui::test]
49fn test_edit_events(cx: &mut TestAppContext) {
50 init_test(cx, |_| {});
51
52 let buffer = cx.new_model(|cx| {
53 let mut buffer = language::Buffer::local("123456", cx);
54 buffer.set_group_interval(Duration::from_secs(1));
55 buffer
56 });
57
58 let events = Rc::new(RefCell::new(Vec::new()));
59 let editor1 = cx.add_window({
60 let events = events.clone();
61 |cx| {
62 let view = cx.view().clone();
63 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
64 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
65 EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
66 _ => {}
67 })
68 .detach();
69 Editor::for_buffer(buffer.clone(), None, cx)
70 }
71 });
72
73 let editor2 = cx.add_window({
74 let events = events.clone();
75 |cx| {
76 cx.subscribe(
77 &cx.view().clone(),
78 move |_, _, event: &EditorEvent, _| match event {
79 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
80 EditorEvent::BufferEdited => {
81 events.borrow_mut().push(("editor2", "buffer edited"))
82 }
83 _ => {}
84 },
85 )
86 .detach();
87 Editor::for_buffer(buffer.clone(), None, cx)
88 }
89 });
90
91 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
92
93 // Mutating editor 1 will emit an `Edited` event only for that editor.
94 _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
95 assert_eq!(
96 mem::take(&mut *events.borrow_mut()),
97 [
98 ("editor1", "edited"),
99 ("editor1", "buffer edited"),
100 ("editor2", "buffer edited"),
101 ]
102 );
103
104 // Mutating editor 2 will emit an `Edited` event only for that editor.
105 _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
106 assert_eq!(
107 mem::take(&mut *events.borrow_mut()),
108 [
109 ("editor2", "edited"),
110 ("editor1", "buffer edited"),
111 ("editor2", "buffer edited"),
112 ]
113 );
114
115 // Undoing on editor 1 will emit an `Edited` event only for that editor.
116 _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
117 assert_eq!(
118 mem::take(&mut *events.borrow_mut()),
119 [
120 ("editor1", "edited"),
121 ("editor1", "buffer edited"),
122 ("editor2", "buffer edited"),
123 ]
124 );
125
126 // Redoing on editor 1 will emit an `Edited` event only for that editor.
127 _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
128 assert_eq!(
129 mem::take(&mut *events.borrow_mut()),
130 [
131 ("editor1", "edited"),
132 ("editor1", "buffer edited"),
133 ("editor2", "buffer edited"),
134 ]
135 );
136
137 // Undoing on editor 2 will emit an `Edited` event only for that editor.
138 _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
139 assert_eq!(
140 mem::take(&mut *events.borrow_mut()),
141 [
142 ("editor2", "edited"),
143 ("editor1", "buffer edited"),
144 ("editor2", "buffer edited"),
145 ]
146 );
147
148 // Redoing on editor 2 will emit an `Edited` event only for that editor.
149 _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
150 assert_eq!(
151 mem::take(&mut *events.borrow_mut()),
152 [
153 ("editor2", "edited"),
154 ("editor1", "buffer edited"),
155 ("editor2", "buffer edited"),
156 ]
157 );
158
159 // No event is emitted when the mutation is a no-op.
160 _ = editor2.update(cx, |editor, cx| {
161 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
162
163 editor.backspace(&Backspace, cx);
164 });
165 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
166}
167
168#[gpui::test]
169fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
170 init_test(cx, |_| {});
171
172 let mut now = Instant::now();
173 let group_interval = Duration::from_millis(1);
174 let buffer = cx.new_model(|cx| {
175 let mut buf = language::Buffer::local("123456", cx);
176 buf.set_group_interval(group_interval);
177 buf
178 });
179 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
180 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
181
182 _ = editor.update(cx, |editor, cx| {
183 editor.start_transaction_at(now, cx);
184 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
185
186 editor.insert("cd", cx);
187 editor.end_transaction_at(now, cx);
188 assert_eq!(editor.text(cx), "12cd56");
189 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
190
191 editor.start_transaction_at(now, cx);
192 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
193 editor.insert("e", cx);
194 editor.end_transaction_at(now, cx);
195 assert_eq!(editor.text(cx), "12cde6");
196 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
197
198 now += group_interval + Duration::from_millis(1);
199 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
200
201 // Simulate an edit in another editor
202 buffer.update(cx, |buffer, cx| {
203 buffer.start_transaction_at(now, cx);
204 buffer.edit([(0..1, "a")], None, cx);
205 buffer.edit([(1..1, "b")], None, cx);
206 buffer.end_transaction_at(now, cx);
207 });
208
209 assert_eq!(editor.text(cx), "ab2cde6");
210 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
211
212 // Last transaction happened past the group interval in a different editor.
213 // Undo it individually and don't restore selections.
214 editor.undo(&Undo, cx);
215 assert_eq!(editor.text(cx), "12cde6");
216 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
217
218 // First two transactions happened within the group interval in this editor.
219 // Undo them together and restore selections.
220 editor.undo(&Undo, cx);
221 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
222 assert_eq!(editor.text(cx), "123456");
223 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
224
225 // Redo the first two transactions together.
226 editor.redo(&Redo, cx);
227 assert_eq!(editor.text(cx), "12cde6");
228 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
229
230 // Redo the last transaction on its own.
231 editor.redo(&Redo, cx);
232 assert_eq!(editor.text(cx), "ab2cde6");
233 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
234
235 // Test empty transactions.
236 editor.start_transaction_at(now, cx);
237 editor.end_transaction_at(now, cx);
238 editor.undo(&Undo, cx);
239 assert_eq!(editor.text(cx), "12cde6");
240 });
241}
242
243#[gpui::test]
244fn test_ime_composition(cx: &mut TestAppContext) {
245 init_test(cx, |_| {});
246
247 let buffer = cx.new_model(|cx| {
248 let mut buffer = language::Buffer::local("abcde", cx);
249 // Ensure automatic grouping doesn't occur.
250 buffer.set_group_interval(Duration::ZERO);
251 buffer
252 });
253
254 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
255 cx.add_window(|cx| {
256 let mut editor = build_editor(buffer.clone(), cx);
257
258 // Start a new IME composition.
259 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
260 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
261 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
262 assert_eq!(editor.text(cx), "äbcde");
263 assert_eq!(
264 editor.marked_text_ranges(cx),
265 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
266 );
267
268 // Finalize IME composition.
269 editor.replace_text_in_range(None, "ā", cx);
270 assert_eq!(editor.text(cx), "ābcde");
271 assert_eq!(editor.marked_text_ranges(cx), None);
272
273 // IME composition edits are grouped and are undone/redone at once.
274 editor.undo(&Default::default(), cx);
275 assert_eq!(editor.text(cx), "abcde");
276 assert_eq!(editor.marked_text_ranges(cx), None);
277 editor.redo(&Default::default(), cx);
278 assert_eq!(editor.text(cx), "ābcde");
279 assert_eq!(editor.marked_text_ranges(cx), None);
280
281 // Start a new IME composition.
282 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
283 assert_eq!(
284 editor.marked_text_ranges(cx),
285 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
286 );
287
288 // Undoing during an IME composition cancels it.
289 editor.undo(&Default::default(), cx);
290 assert_eq!(editor.text(cx), "ābcde");
291 assert_eq!(editor.marked_text_ranges(cx), None);
292
293 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
294 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
295 assert_eq!(editor.text(cx), "ābcdè");
296 assert_eq!(
297 editor.marked_text_ranges(cx),
298 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
299 );
300
301 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
302 editor.replace_text_in_range(Some(4..999), "ę", cx);
303 assert_eq!(editor.text(cx), "ābcdę");
304 assert_eq!(editor.marked_text_ranges(cx), None);
305
306 // Start a new IME composition with multiple cursors.
307 editor.change_selections(None, cx, |s| {
308 s.select_ranges([
309 OffsetUtf16(1)..OffsetUtf16(1),
310 OffsetUtf16(3)..OffsetUtf16(3),
311 OffsetUtf16(5)..OffsetUtf16(5),
312 ])
313 });
314 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
315 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
316 assert_eq!(
317 editor.marked_text_ranges(cx),
318 Some(vec![
319 OffsetUtf16(0)..OffsetUtf16(3),
320 OffsetUtf16(4)..OffsetUtf16(7),
321 OffsetUtf16(8)..OffsetUtf16(11)
322 ])
323 );
324
325 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
326 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
327 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
328 assert_eq!(
329 editor.marked_text_ranges(cx),
330 Some(vec![
331 OffsetUtf16(1)..OffsetUtf16(2),
332 OffsetUtf16(5)..OffsetUtf16(6),
333 OffsetUtf16(9)..OffsetUtf16(10)
334 ])
335 );
336
337 // Finalize IME composition with multiple cursors.
338 editor.replace_text_in_range(Some(9..10), "2", cx);
339 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
340 assert_eq!(editor.marked_text_ranges(cx), None);
341
342 editor
343 });
344}
345
346#[gpui::test]
347fn test_selection_with_mouse(cx: &mut TestAppContext) {
348 init_test(cx, |_| {});
349
350 let editor = cx.add_window(|cx| {
351 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
352 build_editor(buffer, cx)
353 });
354
355 _ = editor.update(cx, |view, cx| {
356 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
357 });
358 assert_eq!(
359 editor
360 .update(cx, |view, cx| view.selections.display_ranges(cx))
361 .unwrap(),
362 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
363 );
364
365 _ = editor.update(cx, |view, cx| {
366 view.update_selection(
367 DisplayPoint::new(DisplayRow(3), 3),
368 0,
369 gpui::Point::<f32>::default(),
370 cx,
371 );
372 });
373
374 assert_eq!(
375 editor
376 .update(cx, |view, cx| view.selections.display_ranges(cx))
377 .unwrap(),
378 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
379 );
380
381 _ = editor.update(cx, |view, cx| {
382 view.update_selection(
383 DisplayPoint::new(DisplayRow(1), 1),
384 0,
385 gpui::Point::<f32>::default(),
386 cx,
387 );
388 });
389
390 assert_eq!(
391 editor
392 .update(cx, |view, cx| view.selections.display_ranges(cx))
393 .unwrap(),
394 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
395 );
396
397 _ = editor.update(cx, |view, cx| {
398 view.end_selection(cx);
399 view.update_selection(
400 DisplayPoint::new(DisplayRow(3), 3),
401 0,
402 gpui::Point::<f32>::default(),
403 cx,
404 );
405 });
406
407 assert_eq!(
408 editor
409 .update(cx, |view, cx| view.selections.display_ranges(cx))
410 .unwrap(),
411 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
412 );
413
414 _ = editor.update(cx, |view, cx| {
415 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
416 view.update_selection(
417 DisplayPoint::new(DisplayRow(0), 0),
418 0,
419 gpui::Point::<f32>::default(),
420 cx,
421 );
422 });
423
424 assert_eq!(
425 editor
426 .update(cx, |view, cx| view.selections.display_ranges(cx))
427 .unwrap(),
428 [
429 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
430 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
431 ]
432 );
433
434 _ = editor.update(cx, |view, cx| {
435 view.end_selection(cx);
436 });
437
438 assert_eq!(
439 editor
440 .update(cx, |view, cx| view.selections.display_ranges(cx))
441 .unwrap(),
442 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
443 );
444}
445
446#[gpui::test]
447fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
448 init_test(cx, |_| {});
449
450 let editor = cx.add_window(|cx| {
451 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
452 build_editor(buffer, cx)
453 });
454
455 _ = editor.update(cx, |view, cx| {
456 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
457 });
458
459 _ = editor.update(cx, |view, cx| {
460 view.end_selection(cx);
461 });
462
463 _ = editor.update(cx, |view, cx| {
464 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
465 });
466
467 _ = editor.update(cx, |view, cx| {
468 view.end_selection(cx);
469 });
470
471 assert_eq!(
472 editor
473 .update(cx, |view, cx| view.selections.display_ranges(cx))
474 .unwrap(),
475 [
476 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
477 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
478 ]
479 );
480
481 _ = editor.update(cx, |view, cx| {
482 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
483 });
484
485 _ = editor.update(cx, |view, cx| {
486 view.end_selection(cx);
487 });
488
489 assert_eq!(
490 editor
491 .update(cx, |view, cx| view.selections.display_ranges(cx))
492 .unwrap(),
493 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
494 );
495}
496
497#[gpui::test]
498fn test_canceling_pending_selection(cx: &mut TestAppContext) {
499 init_test(cx, |_| {});
500
501 let view = cx.add_window(|cx| {
502 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
503 build_editor(buffer, cx)
504 });
505
506 _ = view.update(cx, |view, cx| {
507 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
508 assert_eq!(
509 view.selections.display_ranges(cx),
510 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
511 );
512 });
513
514 _ = view.update(cx, |view, cx| {
515 view.update_selection(
516 DisplayPoint::new(DisplayRow(3), 3),
517 0,
518 gpui::Point::<f32>::default(),
519 cx,
520 );
521 assert_eq!(
522 view.selections.display_ranges(cx),
523 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
524 );
525 });
526
527 _ = view.update(cx, |view, cx| {
528 view.cancel(&Cancel, cx);
529 view.update_selection(
530 DisplayPoint::new(DisplayRow(1), 1),
531 0,
532 gpui::Point::<f32>::default(),
533 cx,
534 );
535 assert_eq!(
536 view.selections.display_ranges(cx),
537 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
538 );
539 });
540}
541
542#[gpui::test]
543fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
544 init_test(cx, |_| {});
545
546 let view = cx.add_window(|cx| {
547 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
548 build_editor(buffer, cx)
549 });
550
551 _ = view.update(cx, |view, cx| {
552 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
553 assert_eq!(
554 view.selections.display_ranges(cx),
555 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
556 );
557
558 view.move_down(&Default::default(), cx);
559 assert_eq!(
560 view.selections.display_ranges(cx),
561 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
562 );
563
564 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
565 assert_eq!(
566 view.selections.display_ranges(cx),
567 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
568 );
569
570 view.move_up(&Default::default(), cx);
571 assert_eq!(
572 view.selections.display_ranges(cx),
573 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
574 );
575 });
576}
577
578#[gpui::test]
579fn test_clone(cx: &mut TestAppContext) {
580 init_test(cx, |_| {});
581
582 let (text, selection_ranges) = marked_text_ranges(
583 indoc! {"
584 one
585 two
586 threeˇ
587 four
588 fiveˇ
589 "},
590 true,
591 );
592
593 let editor = cx.add_window(|cx| {
594 let buffer = MultiBuffer::build_simple(&text, cx);
595 build_editor(buffer, cx)
596 });
597
598 _ = editor.update(cx, |editor, cx| {
599 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
600 editor.fold_creases(
601 vec![
602 Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
603 Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
604 ],
605 true,
606 cx,
607 );
608 });
609
610 let cloned_editor = editor
611 .update(cx, |editor, cx| {
612 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
613 })
614 .unwrap()
615 .unwrap();
616
617 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
618 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
619
620 assert_eq!(
621 cloned_editor
622 .update(cx, |e, cx| e.display_text(cx))
623 .unwrap(),
624 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
625 );
626 assert_eq!(
627 cloned_snapshot
628 .folds_in_range(0..text.len())
629 .collect::<Vec<_>>(),
630 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
631 );
632 assert_set_eq!(
633 cloned_editor
634 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
635 .unwrap(),
636 editor
637 .update(cx, |editor, cx| editor.selections.ranges(cx))
638 .unwrap()
639 );
640 assert_set_eq!(
641 cloned_editor
642 .update(cx, |e, cx| e.selections.display_ranges(cx))
643 .unwrap(),
644 editor
645 .update(cx, |e, cx| e.selections.display_ranges(cx))
646 .unwrap()
647 );
648}
649
650#[gpui::test]
651async fn test_navigation_history(cx: &mut TestAppContext) {
652 init_test(cx, |_| {});
653
654 use workspace::item::Item;
655
656 let fs = FakeFs::new(cx.executor());
657 let project = Project::test(fs, [], cx).await;
658 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
659 let pane = workspace
660 .update(cx, |workspace, _| workspace.active_pane().clone())
661 .unwrap();
662
663 _ = workspace.update(cx, |_v, cx| {
664 cx.new_view(|cx| {
665 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
666 let mut editor = build_editor(buffer.clone(), cx);
667 let handle = cx.view();
668 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(handle)));
669
670 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
671 editor.nav_history.as_mut().unwrap().pop_backward(cx)
672 }
673
674 // Move the cursor a small distance.
675 // Nothing is added to the navigation history.
676 editor.change_selections(None, cx, |s| {
677 s.select_display_ranges([
678 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
679 ])
680 });
681 editor.change_selections(None, cx, |s| {
682 s.select_display_ranges([
683 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
684 ])
685 });
686 assert!(pop_history(&mut editor, cx).is_none());
687
688 // Move the cursor a large distance.
689 // The history can jump back to the previous position.
690 editor.change_selections(None, cx, |s| {
691 s.select_display_ranges([
692 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
693 ])
694 });
695 let nav_entry = pop_history(&mut editor, cx).unwrap();
696 editor.navigate(nav_entry.data.unwrap(), cx);
697 assert_eq!(nav_entry.item.id(), cx.entity_id());
698 assert_eq!(
699 editor.selections.display_ranges(cx),
700 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
701 );
702 assert!(pop_history(&mut editor, cx).is_none());
703
704 // Move the cursor a small distance via the mouse.
705 // Nothing is added to the navigation history.
706 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
707 editor.end_selection(cx);
708 assert_eq!(
709 editor.selections.display_ranges(cx),
710 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
711 );
712 assert!(pop_history(&mut editor, cx).is_none());
713
714 // Move the cursor a large distance via the mouse.
715 // The history can jump back to the previous position.
716 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
717 editor.end_selection(cx);
718 assert_eq!(
719 editor.selections.display_ranges(cx),
720 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
721 );
722 let nav_entry = pop_history(&mut editor, cx).unwrap();
723 editor.navigate(nav_entry.data.unwrap(), cx);
724 assert_eq!(nav_entry.item.id(), cx.entity_id());
725 assert_eq!(
726 editor.selections.display_ranges(cx),
727 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
728 );
729 assert!(pop_history(&mut editor, cx).is_none());
730
731 // Set scroll position to check later
732 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
733 let original_scroll_position = editor.scroll_manager.anchor();
734
735 // Jump to the end of the document and adjust scroll
736 editor.move_to_end(&MoveToEnd, cx);
737 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
738 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
739
740 let nav_entry = pop_history(&mut editor, cx).unwrap();
741 editor.navigate(nav_entry.data.unwrap(), cx);
742 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
743
744 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
745 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
746 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
747 let invalid_point = Point::new(9999, 0);
748 editor.navigate(
749 Box::new(NavigationData {
750 cursor_anchor: invalid_anchor,
751 cursor_position: invalid_point,
752 scroll_anchor: ScrollAnchor {
753 anchor: invalid_anchor,
754 offset: Default::default(),
755 },
756 scroll_top_row: invalid_point.row,
757 }),
758 cx,
759 );
760 assert_eq!(
761 editor.selections.display_ranges(cx),
762 &[editor.max_point(cx)..editor.max_point(cx)]
763 );
764 assert_eq!(
765 editor.scroll_position(cx),
766 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
767 );
768
769 editor
770 })
771 });
772}
773
774#[gpui::test]
775fn test_cancel(cx: &mut TestAppContext) {
776 init_test(cx, |_| {});
777
778 let view = cx.add_window(|cx| {
779 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
780 build_editor(buffer, cx)
781 });
782
783 _ = view.update(cx, |view, cx| {
784 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
785 view.update_selection(
786 DisplayPoint::new(DisplayRow(1), 1),
787 0,
788 gpui::Point::<f32>::default(),
789 cx,
790 );
791 view.end_selection(cx);
792
793 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
794 view.update_selection(
795 DisplayPoint::new(DisplayRow(0), 3),
796 0,
797 gpui::Point::<f32>::default(),
798 cx,
799 );
800 view.end_selection(cx);
801 assert_eq!(
802 view.selections.display_ranges(cx),
803 [
804 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
805 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
806 ]
807 );
808 });
809
810 _ = view.update(cx, |view, cx| {
811 view.cancel(&Cancel, cx);
812 assert_eq!(
813 view.selections.display_ranges(cx),
814 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
815 );
816 });
817
818 _ = view.update(cx, |view, cx| {
819 view.cancel(&Cancel, cx);
820 assert_eq!(
821 view.selections.display_ranges(cx),
822 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
823 );
824 });
825}
826
827#[gpui::test]
828fn test_fold_action(cx: &mut TestAppContext) {
829 init_test(cx, |_| {});
830
831 let view = cx.add_window(|cx| {
832 let buffer = MultiBuffer::build_simple(
833 &"
834 impl Foo {
835 // Hello!
836
837 fn a() {
838 1
839 }
840
841 fn b() {
842 2
843 }
844
845 fn c() {
846 3
847 }
848 }
849 "
850 .unindent(),
851 cx,
852 );
853 build_editor(buffer.clone(), cx)
854 });
855
856 _ = view.update(cx, |view, cx| {
857 view.change_selections(None, cx, |s| {
858 s.select_display_ranges([
859 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
860 ]);
861 });
862 view.fold(&Fold, cx);
863 assert_eq!(
864 view.display_text(cx),
865 "
866 impl Foo {
867 // Hello!
868
869 fn a() {
870 1
871 }
872
873 fn b() {⋯
874 }
875
876 fn c() {⋯
877 }
878 }
879 "
880 .unindent(),
881 );
882
883 view.fold(&Fold, cx);
884 assert_eq!(
885 view.display_text(cx),
886 "
887 impl Foo {⋯
888 }
889 "
890 .unindent(),
891 );
892
893 view.unfold_lines(&UnfoldLines, cx);
894 assert_eq!(
895 view.display_text(cx),
896 "
897 impl Foo {
898 // Hello!
899
900 fn a() {
901 1
902 }
903
904 fn b() {⋯
905 }
906
907 fn c() {⋯
908 }
909 }
910 "
911 .unindent(),
912 );
913
914 view.unfold_lines(&UnfoldLines, cx);
915 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
916 });
917}
918
919#[gpui::test]
920fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
921 init_test(cx, |_| {});
922
923 let view = cx.add_window(|cx| {
924 let buffer = MultiBuffer::build_simple(
925 &"
926 class Foo:
927 # Hello!
928
929 def a():
930 print(1)
931
932 def b():
933 print(2)
934
935 def c():
936 print(3)
937 "
938 .unindent(),
939 cx,
940 );
941 build_editor(buffer.clone(), cx)
942 });
943
944 _ = view.update(cx, |view, cx| {
945 view.change_selections(None, cx, |s| {
946 s.select_display_ranges([
947 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
948 ]);
949 });
950 view.fold(&Fold, cx);
951 assert_eq!(
952 view.display_text(cx),
953 "
954 class Foo:
955 # Hello!
956
957 def a():
958 print(1)
959
960 def b():⋯
961
962 def c():⋯
963 "
964 .unindent(),
965 );
966
967 view.fold(&Fold, cx);
968 assert_eq!(
969 view.display_text(cx),
970 "
971 class Foo:⋯
972 "
973 .unindent(),
974 );
975
976 view.unfold_lines(&UnfoldLines, cx);
977 assert_eq!(
978 view.display_text(cx),
979 "
980 class Foo:
981 # Hello!
982
983 def a():
984 print(1)
985
986 def b():⋯
987
988 def c():⋯
989 "
990 .unindent(),
991 );
992
993 view.unfold_lines(&UnfoldLines, cx);
994 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
995 });
996}
997
998#[gpui::test]
999fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
1000 init_test(cx, |_| {});
1001
1002 let view = cx.add_window(|cx| {
1003 let buffer = MultiBuffer::build_simple(
1004 &"
1005 class Foo:
1006 # Hello!
1007
1008 def a():
1009 print(1)
1010
1011 def b():
1012 print(2)
1013
1014
1015 def c():
1016 print(3)
1017
1018
1019 "
1020 .unindent(),
1021 cx,
1022 );
1023 build_editor(buffer.clone(), cx)
1024 });
1025
1026 _ = view.update(cx, |view, cx| {
1027 view.change_selections(None, cx, |s| {
1028 s.select_display_ranges([
1029 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1030 ]);
1031 });
1032 view.fold(&Fold, cx);
1033 assert_eq!(
1034 view.display_text(cx),
1035 "
1036 class Foo:
1037 # Hello!
1038
1039 def a():
1040 print(1)
1041
1042 def b():⋯
1043
1044
1045 def c():⋯
1046
1047
1048 "
1049 .unindent(),
1050 );
1051
1052 view.fold(&Fold, cx);
1053 assert_eq!(
1054 view.display_text(cx),
1055 "
1056 class Foo:⋯
1057
1058
1059 "
1060 .unindent(),
1061 );
1062
1063 view.unfold_lines(&UnfoldLines, cx);
1064 assert_eq!(
1065 view.display_text(cx),
1066 "
1067 class Foo:
1068 # Hello!
1069
1070 def a():
1071 print(1)
1072
1073 def b():⋯
1074
1075
1076 def c():⋯
1077
1078
1079 "
1080 .unindent(),
1081 );
1082
1083 view.unfold_lines(&UnfoldLines, cx);
1084 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1085 });
1086}
1087
1088#[gpui::test]
1089fn test_fold_at_level(cx: &mut TestAppContext) {
1090 init_test(cx, |_| {});
1091
1092 let view = cx.add_window(|cx| {
1093 let buffer = MultiBuffer::build_simple(
1094 &"
1095 class Foo:
1096 # Hello!
1097
1098 def a():
1099 print(1)
1100
1101 def b():
1102 print(2)
1103
1104
1105 class Bar:
1106 # World!
1107
1108 def a():
1109 print(1)
1110
1111 def b():
1112 print(2)
1113
1114
1115 "
1116 .unindent(),
1117 cx,
1118 );
1119 build_editor(buffer.clone(), cx)
1120 });
1121
1122 _ = view.update(cx, |view, cx| {
1123 view.fold_at_level(&FoldAtLevel { level: 2 }, cx);
1124 assert_eq!(
1125 view.display_text(cx),
1126 "
1127 class Foo:
1128 # Hello!
1129
1130 def a():⋯
1131
1132 def b():⋯
1133
1134
1135 class Bar:
1136 # World!
1137
1138 def a():⋯
1139
1140 def b():⋯
1141
1142
1143 "
1144 .unindent(),
1145 );
1146
1147 view.fold_at_level(&FoldAtLevel { level: 1 }, cx);
1148 assert_eq!(
1149 view.display_text(cx),
1150 "
1151 class Foo:⋯
1152
1153
1154 class Bar:⋯
1155
1156
1157 "
1158 .unindent(),
1159 );
1160
1161 view.unfold_all(&UnfoldAll, cx);
1162 view.fold_at_level(&FoldAtLevel { level: 0 }, cx);
1163 assert_eq!(
1164 view.display_text(cx),
1165 "
1166 class Foo:
1167 # Hello!
1168
1169 def a():
1170 print(1)
1171
1172 def b():
1173 print(2)
1174
1175
1176 class Bar:
1177 # World!
1178
1179 def a():
1180 print(1)
1181
1182 def b():
1183 print(2)
1184
1185
1186 "
1187 .unindent(),
1188 );
1189
1190 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1191 });
1192}
1193
1194#[gpui::test]
1195fn test_move_cursor(cx: &mut TestAppContext) {
1196 init_test(cx, |_| {});
1197
1198 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1199 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1200
1201 buffer.update(cx, |buffer, cx| {
1202 buffer.edit(
1203 vec![
1204 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1205 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1206 ],
1207 None,
1208 cx,
1209 );
1210 });
1211 _ = view.update(cx, |view, cx| {
1212 assert_eq!(
1213 view.selections.display_ranges(cx),
1214 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1215 );
1216
1217 view.move_down(&MoveDown, cx);
1218 assert_eq!(
1219 view.selections.display_ranges(cx),
1220 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1221 );
1222
1223 view.move_right(&MoveRight, cx);
1224 assert_eq!(
1225 view.selections.display_ranges(cx),
1226 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1227 );
1228
1229 view.move_left(&MoveLeft, cx);
1230 assert_eq!(
1231 view.selections.display_ranges(cx),
1232 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1233 );
1234
1235 view.move_up(&MoveUp, cx);
1236 assert_eq!(
1237 view.selections.display_ranges(cx),
1238 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1239 );
1240
1241 view.move_to_end(&MoveToEnd, cx);
1242 assert_eq!(
1243 view.selections.display_ranges(cx),
1244 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1245 );
1246
1247 view.move_to_beginning(&MoveToBeginning, cx);
1248 assert_eq!(
1249 view.selections.display_ranges(cx),
1250 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1251 );
1252
1253 view.change_selections(None, cx, |s| {
1254 s.select_display_ranges([
1255 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1256 ]);
1257 });
1258 view.select_to_beginning(&SelectToBeginning, cx);
1259 assert_eq!(
1260 view.selections.display_ranges(cx),
1261 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1262 );
1263
1264 view.select_to_end(&SelectToEnd, cx);
1265 assert_eq!(
1266 view.selections.display_ranges(cx),
1267 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1268 );
1269 });
1270}
1271
1272// TODO: Re-enable this test
1273#[cfg(target_os = "macos")]
1274#[gpui::test]
1275fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1276 init_test(cx, |_| {});
1277
1278 let view = cx.add_window(|cx| {
1279 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1280 build_editor(buffer.clone(), cx)
1281 });
1282
1283 assert_eq!('ⓐ'.len_utf8(), 3);
1284 assert_eq!('α'.len_utf8(), 2);
1285
1286 _ = view.update(cx, |view, cx| {
1287 view.fold_creases(
1288 vec![
1289 Crease::simple(Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1290 Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1291 Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1292 ],
1293 true,
1294 cx,
1295 );
1296 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1297
1298 view.move_right(&MoveRight, cx);
1299 assert_eq!(
1300 view.selections.display_ranges(cx),
1301 &[empty_range(0, "ⓐ".len())]
1302 );
1303 view.move_right(&MoveRight, cx);
1304 assert_eq!(
1305 view.selections.display_ranges(cx),
1306 &[empty_range(0, "ⓐⓑ".len())]
1307 );
1308 view.move_right(&MoveRight, cx);
1309 assert_eq!(
1310 view.selections.display_ranges(cx),
1311 &[empty_range(0, "ⓐⓑ⋯".len())]
1312 );
1313
1314 view.move_down(&MoveDown, cx);
1315 assert_eq!(
1316 view.selections.display_ranges(cx),
1317 &[empty_range(1, "ab⋯e".len())]
1318 );
1319 view.move_left(&MoveLeft, cx);
1320 assert_eq!(
1321 view.selections.display_ranges(cx),
1322 &[empty_range(1, "ab⋯".len())]
1323 );
1324 view.move_left(&MoveLeft, cx);
1325 assert_eq!(
1326 view.selections.display_ranges(cx),
1327 &[empty_range(1, "ab".len())]
1328 );
1329 view.move_left(&MoveLeft, cx);
1330 assert_eq!(
1331 view.selections.display_ranges(cx),
1332 &[empty_range(1, "a".len())]
1333 );
1334
1335 view.move_down(&MoveDown, cx);
1336 assert_eq!(
1337 view.selections.display_ranges(cx),
1338 &[empty_range(2, "α".len())]
1339 );
1340 view.move_right(&MoveRight, cx);
1341 assert_eq!(
1342 view.selections.display_ranges(cx),
1343 &[empty_range(2, "αβ".len())]
1344 );
1345 view.move_right(&MoveRight, cx);
1346 assert_eq!(
1347 view.selections.display_ranges(cx),
1348 &[empty_range(2, "αβ⋯".len())]
1349 );
1350 view.move_right(&MoveRight, cx);
1351 assert_eq!(
1352 view.selections.display_ranges(cx),
1353 &[empty_range(2, "αβ⋯ε".len())]
1354 );
1355
1356 view.move_up(&MoveUp, cx);
1357 assert_eq!(
1358 view.selections.display_ranges(cx),
1359 &[empty_range(1, "ab⋯e".len())]
1360 );
1361 view.move_down(&MoveDown, cx);
1362 assert_eq!(
1363 view.selections.display_ranges(cx),
1364 &[empty_range(2, "αβ⋯ε".len())]
1365 );
1366 view.move_up(&MoveUp, cx);
1367 assert_eq!(
1368 view.selections.display_ranges(cx),
1369 &[empty_range(1, "ab⋯e".len())]
1370 );
1371
1372 view.move_up(&MoveUp, cx);
1373 assert_eq!(
1374 view.selections.display_ranges(cx),
1375 &[empty_range(0, "ⓐⓑ".len())]
1376 );
1377 view.move_left(&MoveLeft, cx);
1378 assert_eq!(
1379 view.selections.display_ranges(cx),
1380 &[empty_range(0, "ⓐ".len())]
1381 );
1382 view.move_left(&MoveLeft, cx);
1383 assert_eq!(
1384 view.selections.display_ranges(cx),
1385 &[empty_range(0, "".len())]
1386 );
1387 });
1388}
1389
1390#[gpui::test]
1391fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1392 init_test(cx, |_| {});
1393
1394 let view = cx.add_window(|cx| {
1395 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1396 build_editor(buffer.clone(), cx)
1397 });
1398 _ = view.update(cx, |view, cx| {
1399 view.change_selections(None, cx, |s| {
1400 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1401 });
1402
1403 // moving above start of document should move selection to start of document,
1404 // but the next move down should still be at the original goal_x
1405 view.move_up(&MoveUp, cx);
1406 assert_eq!(
1407 view.selections.display_ranges(cx),
1408 &[empty_range(0, "".len())]
1409 );
1410
1411 view.move_down(&MoveDown, cx);
1412 assert_eq!(
1413 view.selections.display_ranges(cx),
1414 &[empty_range(1, "abcd".len())]
1415 );
1416
1417 view.move_down(&MoveDown, cx);
1418 assert_eq!(
1419 view.selections.display_ranges(cx),
1420 &[empty_range(2, "αβγ".len())]
1421 );
1422
1423 view.move_down(&MoveDown, cx);
1424 assert_eq!(
1425 view.selections.display_ranges(cx),
1426 &[empty_range(3, "abcd".len())]
1427 );
1428
1429 view.move_down(&MoveDown, cx);
1430 assert_eq!(
1431 view.selections.display_ranges(cx),
1432 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1433 );
1434
1435 // moving past end of document should not change goal_x
1436 view.move_down(&MoveDown, cx);
1437 assert_eq!(
1438 view.selections.display_ranges(cx),
1439 &[empty_range(5, "".len())]
1440 );
1441
1442 view.move_down(&MoveDown, cx);
1443 assert_eq!(
1444 view.selections.display_ranges(cx),
1445 &[empty_range(5, "".len())]
1446 );
1447
1448 view.move_up(&MoveUp, cx);
1449 assert_eq!(
1450 view.selections.display_ranges(cx),
1451 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1452 );
1453
1454 view.move_up(&MoveUp, cx);
1455 assert_eq!(
1456 view.selections.display_ranges(cx),
1457 &[empty_range(3, "abcd".len())]
1458 );
1459
1460 view.move_up(&MoveUp, cx);
1461 assert_eq!(
1462 view.selections.display_ranges(cx),
1463 &[empty_range(2, "αβγ".len())]
1464 );
1465 });
1466}
1467
1468#[gpui::test]
1469fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1470 init_test(cx, |_| {});
1471 let move_to_beg = MoveToBeginningOfLine {
1472 stop_at_soft_wraps: true,
1473 };
1474
1475 let move_to_end = MoveToEndOfLine {
1476 stop_at_soft_wraps: true,
1477 };
1478
1479 let view = cx.add_window(|cx| {
1480 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1481 build_editor(buffer, cx)
1482 });
1483 _ = view.update(cx, |view, cx| {
1484 view.change_selections(None, cx, |s| {
1485 s.select_display_ranges([
1486 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1487 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1488 ]);
1489 });
1490 });
1491
1492 _ = view.update(cx, |view, cx| {
1493 view.move_to_beginning_of_line(&move_to_beg, cx);
1494 assert_eq!(
1495 view.selections.display_ranges(cx),
1496 &[
1497 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1498 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1499 ]
1500 );
1501 });
1502
1503 _ = view.update(cx, |view, cx| {
1504 view.move_to_beginning_of_line(&move_to_beg, cx);
1505 assert_eq!(
1506 view.selections.display_ranges(cx),
1507 &[
1508 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1509 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1510 ]
1511 );
1512 });
1513
1514 _ = view.update(cx, |view, cx| {
1515 view.move_to_beginning_of_line(&move_to_beg, cx);
1516 assert_eq!(
1517 view.selections.display_ranges(cx),
1518 &[
1519 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1520 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1521 ]
1522 );
1523 });
1524
1525 _ = view.update(cx, |view, cx| {
1526 view.move_to_end_of_line(&move_to_end, cx);
1527 assert_eq!(
1528 view.selections.display_ranges(cx),
1529 &[
1530 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1531 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1532 ]
1533 );
1534 });
1535
1536 // Moving to the end of line again is a no-op.
1537 _ = view.update(cx, |view, cx| {
1538 view.move_to_end_of_line(&move_to_end, cx);
1539 assert_eq!(
1540 view.selections.display_ranges(cx),
1541 &[
1542 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1543 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1544 ]
1545 );
1546 });
1547
1548 _ = view.update(cx, |view, cx| {
1549 view.move_left(&MoveLeft, cx);
1550 view.select_to_beginning_of_line(
1551 &SelectToBeginningOfLine {
1552 stop_at_soft_wraps: true,
1553 },
1554 cx,
1555 );
1556 assert_eq!(
1557 view.selections.display_ranges(cx),
1558 &[
1559 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1560 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1561 ]
1562 );
1563 });
1564
1565 _ = view.update(cx, |view, cx| {
1566 view.select_to_beginning_of_line(
1567 &SelectToBeginningOfLine {
1568 stop_at_soft_wraps: true,
1569 },
1570 cx,
1571 );
1572 assert_eq!(
1573 view.selections.display_ranges(cx),
1574 &[
1575 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1576 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1577 ]
1578 );
1579 });
1580
1581 _ = view.update(cx, |view, cx| {
1582 view.select_to_beginning_of_line(
1583 &SelectToBeginningOfLine {
1584 stop_at_soft_wraps: true,
1585 },
1586 cx,
1587 );
1588 assert_eq!(
1589 view.selections.display_ranges(cx),
1590 &[
1591 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1592 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1593 ]
1594 );
1595 });
1596
1597 _ = view.update(cx, |view, cx| {
1598 view.select_to_end_of_line(
1599 &SelectToEndOfLine {
1600 stop_at_soft_wraps: true,
1601 },
1602 cx,
1603 );
1604 assert_eq!(
1605 view.selections.display_ranges(cx),
1606 &[
1607 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1608 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1609 ]
1610 );
1611 });
1612
1613 _ = view.update(cx, |view, cx| {
1614 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1615 assert_eq!(view.display_text(cx), "ab\n de");
1616 assert_eq!(
1617 view.selections.display_ranges(cx),
1618 &[
1619 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1620 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1621 ]
1622 );
1623 });
1624
1625 _ = view.update(cx, |view, cx| {
1626 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1627 assert_eq!(view.display_text(cx), "\n");
1628 assert_eq!(
1629 view.selections.display_ranges(cx),
1630 &[
1631 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1632 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1633 ]
1634 );
1635 });
1636}
1637
1638#[gpui::test]
1639fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1640 init_test(cx, |_| {});
1641 let move_to_beg = MoveToBeginningOfLine {
1642 stop_at_soft_wraps: false,
1643 };
1644
1645 let move_to_end = MoveToEndOfLine {
1646 stop_at_soft_wraps: false,
1647 };
1648
1649 let view = cx.add_window(|cx| {
1650 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1651 build_editor(buffer, cx)
1652 });
1653
1654 _ = view.update(cx, |view, cx| {
1655 view.set_wrap_width(Some(140.0.into()), cx);
1656
1657 // We expect the following lines after wrapping
1658 // ```
1659 // thequickbrownfox
1660 // jumpedoverthelazydo
1661 // gs
1662 // ```
1663 // The final `gs` was soft-wrapped onto a new line.
1664 assert_eq!(
1665 "thequickbrownfox\njumpedoverthelaz\nydogs",
1666 view.display_text(cx),
1667 );
1668
1669 // First, let's assert behavior on the first line, that was not soft-wrapped.
1670 // Start the cursor at the `k` on the first line
1671 view.change_selections(None, cx, |s| {
1672 s.select_display_ranges([
1673 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1674 ]);
1675 });
1676
1677 // Moving to the beginning of the line should put us at the beginning of the line.
1678 view.move_to_beginning_of_line(&move_to_beg, cx);
1679 assert_eq!(
1680 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1681 view.selections.display_ranges(cx)
1682 );
1683
1684 // Moving to the end of the line should put us at the end of the line.
1685 view.move_to_end_of_line(&move_to_end, cx);
1686 assert_eq!(
1687 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1688 view.selections.display_ranges(cx)
1689 );
1690
1691 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1692 // Start the cursor at the last line (`y` that was wrapped to a new line)
1693 view.change_selections(None, cx, |s| {
1694 s.select_display_ranges([
1695 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1696 ]);
1697 });
1698
1699 // Moving to the beginning of the line should put us at the start of the second line of
1700 // display text, i.e., the `j`.
1701 view.move_to_beginning_of_line(&move_to_beg, cx);
1702 assert_eq!(
1703 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1704 view.selections.display_ranges(cx)
1705 );
1706
1707 // Moving to the beginning of the line again should be a no-op.
1708 view.move_to_beginning_of_line(&move_to_beg, cx);
1709 assert_eq!(
1710 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1711 view.selections.display_ranges(cx)
1712 );
1713
1714 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1715 // next display line.
1716 view.move_to_end_of_line(&move_to_end, cx);
1717 assert_eq!(
1718 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1719 view.selections.display_ranges(cx)
1720 );
1721
1722 // Moving to the end of the line again should be a no-op.
1723 view.move_to_end_of_line(&move_to_end, cx);
1724 assert_eq!(
1725 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1726 view.selections.display_ranges(cx)
1727 );
1728 });
1729}
1730
1731#[gpui::test]
1732fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1733 init_test(cx, |_| {});
1734
1735 let view = cx.add_window(|cx| {
1736 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1737 build_editor(buffer, cx)
1738 });
1739 _ = view.update(cx, |view, cx| {
1740 view.change_selections(None, cx, |s| {
1741 s.select_display_ranges([
1742 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1743 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1744 ])
1745 });
1746
1747 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1748 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1749
1750 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1751 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1752
1753 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1754 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1755
1756 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1757 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1758
1759 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1760 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1761
1762 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1763 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1764
1765 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1766 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1767
1768 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1769 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1770
1771 view.move_right(&MoveRight, cx);
1772 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1773 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1774
1775 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1776 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1777
1778 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1779 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1780 });
1781}
1782
1783#[gpui::test]
1784fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1785 init_test(cx, |_| {});
1786
1787 let view = cx.add_window(|cx| {
1788 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1789 build_editor(buffer, cx)
1790 });
1791
1792 _ = view.update(cx, |view, cx| {
1793 view.set_wrap_width(Some(140.0.into()), cx);
1794 assert_eq!(
1795 view.display_text(cx),
1796 "use one::{\n two::three::\n four::five\n};"
1797 );
1798
1799 view.change_selections(None, cx, |s| {
1800 s.select_display_ranges([
1801 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1802 ]);
1803 });
1804
1805 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1806 assert_eq!(
1807 view.selections.display_ranges(cx),
1808 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1809 );
1810
1811 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1812 assert_eq!(
1813 view.selections.display_ranges(cx),
1814 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1815 );
1816
1817 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1818 assert_eq!(
1819 view.selections.display_ranges(cx),
1820 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1821 );
1822
1823 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1824 assert_eq!(
1825 view.selections.display_ranges(cx),
1826 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1827 );
1828
1829 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1830 assert_eq!(
1831 view.selections.display_ranges(cx),
1832 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1833 );
1834
1835 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1836 assert_eq!(
1837 view.selections.display_ranges(cx),
1838 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1839 );
1840 });
1841}
1842
1843#[gpui::test]
1844async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1845 init_test(cx, |_| {});
1846 let mut cx = EditorTestContext::new(cx).await;
1847
1848 let line_height = cx.editor(|editor, cx| {
1849 editor
1850 .style()
1851 .unwrap()
1852 .text
1853 .line_height_in_pixels(cx.rem_size())
1854 });
1855 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1856
1857 cx.set_state(
1858 &r#"ˇone
1859 two
1860
1861 three
1862 fourˇ
1863 five
1864
1865 six"#
1866 .unindent(),
1867 );
1868
1869 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1870 cx.assert_editor_state(
1871 &r#"one
1872 two
1873 ˇ
1874 three
1875 four
1876 five
1877 ˇ
1878 six"#
1879 .unindent(),
1880 );
1881
1882 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1883 cx.assert_editor_state(
1884 &r#"one
1885 two
1886
1887 three
1888 four
1889 five
1890 ˇ
1891 sixˇ"#
1892 .unindent(),
1893 );
1894
1895 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1896 cx.assert_editor_state(
1897 &r#"one
1898 two
1899
1900 three
1901 four
1902 five
1903
1904 sixˇ"#
1905 .unindent(),
1906 );
1907
1908 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1909 cx.assert_editor_state(
1910 &r#"one
1911 two
1912
1913 three
1914 four
1915 five
1916 ˇ
1917 six"#
1918 .unindent(),
1919 );
1920
1921 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1922 cx.assert_editor_state(
1923 &r#"one
1924 two
1925 ˇ
1926 three
1927 four
1928 five
1929
1930 six"#
1931 .unindent(),
1932 );
1933
1934 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1935 cx.assert_editor_state(
1936 &r#"ˇone
1937 two
1938
1939 three
1940 four
1941 five
1942
1943 six"#
1944 .unindent(),
1945 );
1946}
1947
1948#[gpui::test]
1949async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1950 init_test(cx, |_| {});
1951 let mut cx = EditorTestContext::new(cx).await;
1952 let line_height = cx.editor(|editor, cx| {
1953 editor
1954 .style()
1955 .unwrap()
1956 .text
1957 .line_height_in_pixels(cx.rem_size())
1958 });
1959 let window = cx.window;
1960 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1961
1962 cx.set_state(
1963 r#"ˇone
1964 two
1965 three
1966 four
1967 five
1968 six
1969 seven
1970 eight
1971 nine
1972 ten
1973 "#,
1974 );
1975
1976 cx.update_editor(|editor, cx| {
1977 assert_eq!(
1978 editor.snapshot(cx).scroll_position(),
1979 gpui::Point::new(0., 0.)
1980 );
1981 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1982 assert_eq!(
1983 editor.snapshot(cx).scroll_position(),
1984 gpui::Point::new(0., 3.)
1985 );
1986 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1987 assert_eq!(
1988 editor.snapshot(cx).scroll_position(),
1989 gpui::Point::new(0., 6.)
1990 );
1991 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1992 assert_eq!(
1993 editor.snapshot(cx).scroll_position(),
1994 gpui::Point::new(0., 3.)
1995 );
1996
1997 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1998 assert_eq!(
1999 editor.snapshot(cx).scroll_position(),
2000 gpui::Point::new(0., 1.)
2001 );
2002 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
2003 assert_eq!(
2004 editor.snapshot(cx).scroll_position(),
2005 gpui::Point::new(0., 3.)
2006 );
2007 });
2008}
2009
2010#[gpui::test]
2011async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
2012 init_test(cx, |_| {});
2013 let mut cx = EditorTestContext::new(cx).await;
2014
2015 let line_height = cx.update_editor(|editor, cx| {
2016 editor.set_vertical_scroll_margin(2, cx);
2017 editor
2018 .style()
2019 .unwrap()
2020 .text
2021 .line_height_in_pixels(cx.rem_size())
2022 });
2023 let window = cx.window;
2024 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2025
2026 cx.set_state(
2027 r#"ˇone
2028 two
2029 three
2030 four
2031 five
2032 six
2033 seven
2034 eight
2035 nine
2036 ten
2037 "#,
2038 );
2039 cx.update_editor(|editor, cx| {
2040 assert_eq!(
2041 editor.snapshot(cx).scroll_position(),
2042 gpui::Point::new(0., 0.0)
2043 );
2044 });
2045
2046 // Add a cursor below the visible area. Since both cursors cannot fit
2047 // on screen, the editor autoscrolls to reveal the newest cursor, and
2048 // allows the vertical scroll margin below that cursor.
2049 cx.update_editor(|editor, cx| {
2050 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2051 selections.select_ranges([
2052 Point::new(0, 0)..Point::new(0, 0),
2053 Point::new(6, 0)..Point::new(6, 0),
2054 ]);
2055 })
2056 });
2057 cx.update_editor(|editor, cx| {
2058 assert_eq!(
2059 editor.snapshot(cx).scroll_position(),
2060 gpui::Point::new(0., 3.0)
2061 );
2062 });
2063
2064 // Move down. The editor cursor scrolls down to track the newest cursor.
2065 cx.update_editor(|editor, cx| {
2066 editor.move_down(&Default::default(), cx);
2067 });
2068 cx.update_editor(|editor, cx| {
2069 assert_eq!(
2070 editor.snapshot(cx).scroll_position(),
2071 gpui::Point::new(0., 4.0)
2072 );
2073 });
2074
2075 // Add a cursor above the visible area. Since both cursors fit on screen,
2076 // the editor scrolls to show both.
2077 cx.update_editor(|editor, cx| {
2078 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2079 selections.select_ranges([
2080 Point::new(1, 0)..Point::new(1, 0),
2081 Point::new(6, 0)..Point::new(6, 0),
2082 ]);
2083 })
2084 });
2085 cx.update_editor(|editor, cx| {
2086 assert_eq!(
2087 editor.snapshot(cx).scroll_position(),
2088 gpui::Point::new(0., 1.0)
2089 );
2090 });
2091}
2092
2093#[gpui::test]
2094async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
2095 init_test(cx, |_| {});
2096 let mut cx = EditorTestContext::new(cx).await;
2097
2098 let line_height = cx.editor(|editor, cx| {
2099 editor
2100 .style()
2101 .unwrap()
2102 .text
2103 .line_height_in_pixels(cx.rem_size())
2104 });
2105 let window = cx.window;
2106 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2107 cx.set_state(
2108 &r#"
2109 ˇone
2110 two
2111 threeˇ
2112 four
2113 five
2114 six
2115 seven
2116 eight
2117 nine
2118 ten
2119 "#
2120 .unindent(),
2121 );
2122
2123 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2124 cx.assert_editor_state(
2125 &r#"
2126 one
2127 two
2128 three
2129 ˇfour
2130 five
2131 sixˇ
2132 seven
2133 eight
2134 nine
2135 ten
2136 "#
2137 .unindent(),
2138 );
2139
2140 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2141 cx.assert_editor_state(
2142 &r#"
2143 one
2144 two
2145 three
2146 four
2147 five
2148 six
2149 ˇseven
2150 eight
2151 nineˇ
2152 ten
2153 "#
2154 .unindent(),
2155 );
2156
2157 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2158 cx.assert_editor_state(
2159 &r#"
2160 one
2161 two
2162 three
2163 ˇfour
2164 five
2165 sixˇ
2166 seven
2167 eight
2168 nine
2169 ten
2170 "#
2171 .unindent(),
2172 );
2173
2174 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2175 cx.assert_editor_state(
2176 &r#"
2177 ˇone
2178 two
2179 threeˇ
2180 four
2181 five
2182 six
2183 seven
2184 eight
2185 nine
2186 ten
2187 "#
2188 .unindent(),
2189 );
2190
2191 // Test select collapsing
2192 cx.update_editor(|editor, cx| {
2193 editor.move_page_down(&MovePageDown::default(), cx);
2194 editor.move_page_down(&MovePageDown::default(), cx);
2195 editor.move_page_down(&MovePageDown::default(), cx);
2196 });
2197 cx.assert_editor_state(
2198 &r#"
2199 one
2200 two
2201 three
2202 four
2203 five
2204 six
2205 seven
2206 eight
2207 nine
2208 ˇten
2209 ˇ"#
2210 .unindent(),
2211 );
2212}
2213
2214#[gpui::test]
2215async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2216 init_test(cx, |_| {});
2217 let mut cx = EditorTestContext::new(cx).await;
2218 cx.set_state("one «two threeˇ» four");
2219 cx.update_editor(|editor, cx| {
2220 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2221 assert_eq!(editor.text(cx), " four");
2222 });
2223}
2224
2225#[gpui::test]
2226fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2227 init_test(cx, |_| {});
2228
2229 let view = cx.add_window(|cx| {
2230 let buffer = MultiBuffer::build_simple("one two three four", cx);
2231 build_editor(buffer.clone(), cx)
2232 });
2233
2234 _ = view.update(cx, |view, cx| {
2235 view.change_selections(None, cx, |s| {
2236 s.select_display_ranges([
2237 // an empty selection - the preceding word fragment is deleted
2238 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2239 // characters selected - they are deleted
2240 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2241 ])
2242 });
2243 view.delete_to_previous_word_start(
2244 &DeleteToPreviousWordStart {
2245 ignore_newlines: false,
2246 },
2247 cx,
2248 );
2249 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2250 });
2251
2252 _ = view.update(cx, |view, cx| {
2253 view.change_selections(None, cx, |s| {
2254 s.select_display_ranges([
2255 // an empty selection - the following word fragment is deleted
2256 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2257 // characters selected - they are deleted
2258 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2259 ])
2260 });
2261 view.delete_to_next_word_end(
2262 &DeleteToNextWordEnd {
2263 ignore_newlines: false,
2264 },
2265 cx,
2266 );
2267 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2268 });
2269}
2270
2271#[gpui::test]
2272fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2273 init_test(cx, |_| {});
2274
2275 let view = cx.add_window(|cx| {
2276 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2277 build_editor(buffer.clone(), cx)
2278 });
2279 let del_to_prev_word_start = DeleteToPreviousWordStart {
2280 ignore_newlines: false,
2281 };
2282 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2283 ignore_newlines: true,
2284 };
2285
2286 _ = view.update(cx, |view, cx| {
2287 view.change_selections(None, cx, |s| {
2288 s.select_display_ranges([
2289 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2290 ])
2291 });
2292 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2293 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2294 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2295 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2296 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2297 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\n");
2298 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2299 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2");
2300 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2301 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n");
2302 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2303 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2304 });
2305}
2306
2307#[gpui::test]
2308fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2309 init_test(cx, |_| {});
2310
2311 let view = cx.add_window(|cx| {
2312 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2313 build_editor(buffer.clone(), cx)
2314 });
2315 let del_to_next_word_end = DeleteToNextWordEnd {
2316 ignore_newlines: false,
2317 };
2318 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2319 ignore_newlines: true,
2320 };
2321
2322 _ = view.update(cx, |view, cx| {
2323 view.change_selections(None, cx, |s| {
2324 s.select_display_ranges([
2325 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2326 ])
2327 });
2328 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2329 assert_eq!(
2330 view.buffer.read(cx).read(cx).text(),
2331 "one\n two\nthree\n four"
2332 );
2333 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2334 assert_eq!(
2335 view.buffer.read(cx).read(cx).text(),
2336 "\n two\nthree\n four"
2337 );
2338 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2339 assert_eq!(view.buffer.read(cx).read(cx).text(), "two\nthree\n four");
2340 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2341 assert_eq!(view.buffer.read(cx).read(cx).text(), "\nthree\n four");
2342 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2343 assert_eq!(view.buffer.read(cx).read(cx).text(), "\n four");
2344 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2345 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2346 });
2347}
2348
2349#[gpui::test]
2350fn test_newline(cx: &mut TestAppContext) {
2351 init_test(cx, |_| {});
2352
2353 let view = cx.add_window(|cx| {
2354 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2355 build_editor(buffer.clone(), cx)
2356 });
2357
2358 _ = view.update(cx, |view, cx| {
2359 view.change_selections(None, cx, |s| {
2360 s.select_display_ranges([
2361 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2362 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2363 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2364 ])
2365 });
2366
2367 view.newline(&Newline, cx);
2368 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2369 });
2370}
2371
2372#[gpui::test]
2373fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2374 init_test(cx, |_| {});
2375
2376 let editor = cx.add_window(|cx| {
2377 let buffer = MultiBuffer::build_simple(
2378 "
2379 a
2380 b(
2381 X
2382 )
2383 c(
2384 X
2385 )
2386 "
2387 .unindent()
2388 .as_str(),
2389 cx,
2390 );
2391 let mut editor = build_editor(buffer.clone(), cx);
2392 editor.change_selections(None, cx, |s| {
2393 s.select_ranges([
2394 Point::new(2, 4)..Point::new(2, 5),
2395 Point::new(5, 4)..Point::new(5, 5),
2396 ])
2397 });
2398 editor
2399 });
2400
2401 _ = editor.update(cx, |editor, cx| {
2402 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2403 editor.buffer.update(cx, |buffer, cx| {
2404 buffer.edit(
2405 [
2406 (Point::new(1, 2)..Point::new(3, 0), ""),
2407 (Point::new(4, 2)..Point::new(6, 0), ""),
2408 ],
2409 None,
2410 cx,
2411 );
2412 assert_eq!(
2413 buffer.read(cx).text(),
2414 "
2415 a
2416 b()
2417 c()
2418 "
2419 .unindent()
2420 );
2421 });
2422 assert_eq!(
2423 editor.selections.ranges(cx),
2424 &[
2425 Point::new(1, 2)..Point::new(1, 2),
2426 Point::new(2, 2)..Point::new(2, 2),
2427 ],
2428 );
2429
2430 editor.newline(&Newline, cx);
2431 assert_eq!(
2432 editor.text(cx),
2433 "
2434 a
2435 b(
2436 )
2437 c(
2438 )
2439 "
2440 .unindent()
2441 );
2442
2443 // The selections are moved after the inserted newlines
2444 assert_eq!(
2445 editor.selections.ranges(cx),
2446 &[
2447 Point::new(2, 0)..Point::new(2, 0),
2448 Point::new(4, 0)..Point::new(4, 0),
2449 ],
2450 );
2451 });
2452}
2453
2454#[gpui::test]
2455async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2456 init_test(cx, |settings| {
2457 settings.defaults.tab_size = NonZeroU32::new(4)
2458 });
2459
2460 let language = Arc::new(
2461 Language::new(
2462 LanguageConfig::default(),
2463 Some(tree_sitter_rust::LANGUAGE.into()),
2464 )
2465 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2466 .unwrap(),
2467 );
2468
2469 let mut cx = EditorTestContext::new(cx).await;
2470 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2471 cx.set_state(indoc! {"
2472 const a: ˇA = (
2473 (ˇ
2474 «const_functionˇ»(ˇ),
2475 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2476 )ˇ
2477 ˇ);ˇ
2478 "});
2479
2480 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2481 cx.assert_editor_state(indoc! {"
2482 ˇ
2483 const a: A = (
2484 ˇ
2485 (
2486 ˇ
2487 ˇ
2488 const_function(),
2489 ˇ
2490 ˇ
2491 ˇ
2492 ˇ
2493 something_else,
2494 ˇ
2495 )
2496 ˇ
2497 ˇ
2498 );
2499 "});
2500}
2501
2502#[gpui::test]
2503async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2504 init_test(cx, |settings| {
2505 settings.defaults.tab_size = NonZeroU32::new(4)
2506 });
2507
2508 let language = Arc::new(
2509 Language::new(
2510 LanguageConfig::default(),
2511 Some(tree_sitter_rust::LANGUAGE.into()),
2512 )
2513 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2514 .unwrap(),
2515 );
2516
2517 let mut cx = EditorTestContext::new(cx).await;
2518 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2519 cx.set_state(indoc! {"
2520 const a: ˇA = (
2521 (ˇ
2522 «const_functionˇ»(ˇ),
2523 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2524 )ˇ
2525 ˇ);ˇ
2526 "});
2527
2528 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2529 cx.assert_editor_state(indoc! {"
2530 const a: A = (
2531 ˇ
2532 (
2533 ˇ
2534 const_function(),
2535 ˇ
2536 ˇ
2537 something_else,
2538 ˇ
2539 ˇ
2540 ˇ
2541 ˇ
2542 )
2543 ˇ
2544 );
2545 ˇ
2546 ˇ
2547 "});
2548}
2549
2550#[gpui::test]
2551async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2552 init_test(cx, |settings| {
2553 settings.defaults.tab_size = NonZeroU32::new(4)
2554 });
2555
2556 let language = Arc::new(Language::new(
2557 LanguageConfig {
2558 line_comments: vec!["//".into()],
2559 ..LanguageConfig::default()
2560 },
2561 None,
2562 ));
2563 {
2564 let mut cx = EditorTestContext::new(cx).await;
2565 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2566 cx.set_state(indoc! {"
2567 // Fooˇ
2568 "});
2569
2570 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2571 cx.assert_editor_state(indoc! {"
2572 // Foo
2573 //ˇ
2574 "});
2575 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2576 cx.set_state(indoc! {"
2577 ˇ// Foo
2578 "});
2579 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2580 cx.assert_editor_state(indoc! {"
2581
2582 ˇ// Foo
2583 "});
2584 }
2585 // Ensure that comment continuations can be disabled.
2586 update_test_language_settings(cx, |settings| {
2587 settings.defaults.extend_comment_on_newline = Some(false);
2588 });
2589 let mut cx = EditorTestContext::new(cx).await;
2590 cx.set_state(indoc! {"
2591 // Fooˇ
2592 "});
2593 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2594 cx.assert_editor_state(indoc! {"
2595 // Foo
2596 ˇ
2597 "});
2598}
2599
2600#[gpui::test]
2601fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2602 init_test(cx, |_| {});
2603
2604 let editor = cx.add_window(|cx| {
2605 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2606 let mut editor = build_editor(buffer.clone(), cx);
2607 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2608 editor
2609 });
2610
2611 _ = editor.update(cx, |editor, cx| {
2612 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2613 editor.buffer.update(cx, |buffer, cx| {
2614 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2615 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2616 });
2617 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2618
2619 editor.insert("Z", cx);
2620 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2621
2622 // The selections are moved after the inserted characters
2623 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2624 });
2625}
2626
2627#[gpui::test]
2628async fn test_tab(cx: &mut gpui::TestAppContext) {
2629 init_test(cx, |settings| {
2630 settings.defaults.tab_size = NonZeroU32::new(3)
2631 });
2632
2633 let mut cx = EditorTestContext::new(cx).await;
2634 cx.set_state(indoc! {"
2635 ˇabˇc
2636 ˇ🏀ˇ🏀ˇefg
2637 dˇ
2638 "});
2639 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2640 cx.assert_editor_state(indoc! {"
2641 ˇab ˇc
2642 ˇ🏀 ˇ🏀 ˇefg
2643 d ˇ
2644 "});
2645
2646 cx.set_state(indoc! {"
2647 a
2648 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2649 "});
2650 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2651 cx.assert_editor_state(indoc! {"
2652 a
2653 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2654 "});
2655}
2656
2657#[gpui::test]
2658async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2659 init_test(cx, |_| {});
2660
2661 let mut cx = EditorTestContext::new(cx).await;
2662 let language = Arc::new(
2663 Language::new(
2664 LanguageConfig::default(),
2665 Some(tree_sitter_rust::LANGUAGE.into()),
2666 )
2667 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2668 .unwrap(),
2669 );
2670 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2671
2672 // cursors that are already at the suggested indent level insert
2673 // a soft tab. cursors that are to the left of the suggested indent
2674 // auto-indent their line.
2675 cx.set_state(indoc! {"
2676 ˇ
2677 const a: B = (
2678 c(
2679 d(
2680 ˇ
2681 )
2682 ˇ
2683 ˇ )
2684 );
2685 "});
2686 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2687 cx.assert_editor_state(indoc! {"
2688 ˇ
2689 const a: B = (
2690 c(
2691 d(
2692 ˇ
2693 )
2694 ˇ
2695 ˇ)
2696 );
2697 "});
2698
2699 // handle auto-indent when there are multiple cursors on the same line
2700 cx.set_state(indoc! {"
2701 const a: B = (
2702 c(
2703 ˇ ˇ
2704 ˇ )
2705 );
2706 "});
2707 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2708 cx.assert_editor_state(indoc! {"
2709 const a: B = (
2710 c(
2711 ˇ
2712 ˇ)
2713 );
2714 "});
2715}
2716
2717#[gpui::test]
2718async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2719 init_test(cx, |settings| {
2720 settings.defaults.tab_size = NonZeroU32::new(4)
2721 });
2722
2723 let language = Arc::new(
2724 Language::new(
2725 LanguageConfig::default(),
2726 Some(tree_sitter_rust::LANGUAGE.into()),
2727 )
2728 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2729 .unwrap(),
2730 );
2731
2732 let mut cx = EditorTestContext::new(cx).await;
2733 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2734 cx.set_state(indoc! {"
2735 fn a() {
2736 if b {
2737 \t ˇc
2738 }
2739 }
2740 "});
2741
2742 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2743 cx.assert_editor_state(indoc! {"
2744 fn a() {
2745 if b {
2746 ˇc
2747 }
2748 }
2749 "});
2750}
2751
2752#[gpui::test]
2753async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2754 init_test(cx, |settings| {
2755 settings.defaults.tab_size = NonZeroU32::new(4);
2756 });
2757
2758 let mut cx = EditorTestContext::new(cx).await;
2759
2760 cx.set_state(indoc! {"
2761 «oneˇ» «twoˇ»
2762 three
2763 four
2764 "});
2765 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2766 cx.assert_editor_state(indoc! {"
2767 «oneˇ» «twoˇ»
2768 three
2769 four
2770 "});
2771
2772 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2773 cx.assert_editor_state(indoc! {"
2774 «oneˇ» «twoˇ»
2775 three
2776 four
2777 "});
2778
2779 // select across line ending
2780 cx.set_state(indoc! {"
2781 one two
2782 t«hree
2783 ˇ» four
2784 "});
2785 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2786 cx.assert_editor_state(indoc! {"
2787 one two
2788 t«hree
2789 ˇ» four
2790 "});
2791
2792 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2793 cx.assert_editor_state(indoc! {"
2794 one two
2795 t«hree
2796 ˇ» four
2797 "});
2798
2799 // Ensure that indenting/outdenting works when the cursor is at column 0.
2800 cx.set_state(indoc! {"
2801 one two
2802 ˇthree
2803 four
2804 "});
2805 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2806 cx.assert_editor_state(indoc! {"
2807 one two
2808 ˇthree
2809 four
2810 "});
2811
2812 cx.set_state(indoc! {"
2813 one two
2814 ˇ three
2815 four
2816 "});
2817 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2818 cx.assert_editor_state(indoc! {"
2819 one two
2820 ˇthree
2821 four
2822 "});
2823}
2824
2825#[gpui::test]
2826async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2827 init_test(cx, |settings| {
2828 settings.defaults.hard_tabs = Some(true);
2829 });
2830
2831 let mut cx = EditorTestContext::new(cx).await;
2832
2833 // select two ranges on one line
2834 cx.set_state(indoc! {"
2835 «oneˇ» «twoˇ»
2836 three
2837 four
2838 "});
2839 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2840 cx.assert_editor_state(indoc! {"
2841 \t«oneˇ» «twoˇ»
2842 three
2843 four
2844 "});
2845 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2846 cx.assert_editor_state(indoc! {"
2847 \t\t«oneˇ» «twoˇ»
2848 three
2849 four
2850 "});
2851 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2852 cx.assert_editor_state(indoc! {"
2853 \t«oneˇ» «twoˇ»
2854 three
2855 four
2856 "});
2857 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2858 cx.assert_editor_state(indoc! {"
2859 «oneˇ» «twoˇ»
2860 three
2861 four
2862 "});
2863
2864 // select across a line ending
2865 cx.set_state(indoc! {"
2866 one two
2867 t«hree
2868 ˇ»four
2869 "});
2870 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2871 cx.assert_editor_state(indoc! {"
2872 one two
2873 \tt«hree
2874 ˇ»four
2875 "});
2876 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2877 cx.assert_editor_state(indoc! {"
2878 one two
2879 \t\tt«hree
2880 ˇ»four
2881 "});
2882 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2883 cx.assert_editor_state(indoc! {"
2884 one two
2885 \tt«hree
2886 ˇ»four
2887 "});
2888 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2889 cx.assert_editor_state(indoc! {"
2890 one two
2891 t«hree
2892 ˇ»four
2893 "});
2894
2895 // Ensure that indenting/outdenting works when the cursor is at column 0.
2896 cx.set_state(indoc! {"
2897 one two
2898 ˇthree
2899 four
2900 "});
2901 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2902 cx.assert_editor_state(indoc! {"
2903 one two
2904 ˇthree
2905 four
2906 "});
2907 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2908 cx.assert_editor_state(indoc! {"
2909 one two
2910 \tˇthree
2911 four
2912 "});
2913 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2914 cx.assert_editor_state(indoc! {"
2915 one two
2916 ˇthree
2917 four
2918 "});
2919}
2920
2921#[gpui::test]
2922fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2923 init_test(cx, |settings| {
2924 settings.languages.extend([
2925 (
2926 "TOML".into(),
2927 LanguageSettingsContent {
2928 tab_size: NonZeroU32::new(2),
2929 ..Default::default()
2930 },
2931 ),
2932 (
2933 "Rust".into(),
2934 LanguageSettingsContent {
2935 tab_size: NonZeroU32::new(4),
2936 ..Default::default()
2937 },
2938 ),
2939 ]);
2940 });
2941
2942 let toml_language = Arc::new(Language::new(
2943 LanguageConfig {
2944 name: "TOML".into(),
2945 ..Default::default()
2946 },
2947 None,
2948 ));
2949 let rust_language = Arc::new(Language::new(
2950 LanguageConfig {
2951 name: "Rust".into(),
2952 ..Default::default()
2953 },
2954 None,
2955 ));
2956
2957 let toml_buffer =
2958 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2959 let rust_buffer = cx.new_model(|cx| {
2960 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2961 });
2962 let multibuffer = cx.new_model(|cx| {
2963 let mut multibuffer = MultiBuffer::new(ReadWrite);
2964 multibuffer.push_excerpts(
2965 toml_buffer.clone(),
2966 [ExcerptRange {
2967 context: Point::new(0, 0)..Point::new(2, 0),
2968 primary: None,
2969 }],
2970 cx,
2971 );
2972 multibuffer.push_excerpts(
2973 rust_buffer.clone(),
2974 [ExcerptRange {
2975 context: Point::new(0, 0)..Point::new(1, 0),
2976 primary: None,
2977 }],
2978 cx,
2979 );
2980 multibuffer
2981 });
2982
2983 cx.add_window(|cx| {
2984 let mut editor = build_editor(multibuffer, cx);
2985
2986 assert_eq!(
2987 editor.text(cx),
2988 indoc! {"
2989 a = 1
2990 b = 2
2991
2992 const c: usize = 3;
2993 "}
2994 );
2995
2996 select_ranges(
2997 &mut editor,
2998 indoc! {"
2999 «aˇ» = 1
3000 b = 2
3001
3002 «const c:ˇ» usize = 3;
3003 "},
3004 cx,
3005 );
3006
3007 editor.tab(&Tab, cx);
3008 assert_text_with_selections(
3009 &mut editor,
3010 indoc! {"
3011 «aˇ» = 1
3012 b = 2
3013
3014 «const c:ˇ» usize = 3;
3015 "},
3016 cx,
3017 );
3018 editor.tab_prev(&TabPrev, cx);
3019 assert_text_with_selections(
3020 &mut editor,
3021 indoc! {"
3022 «aˇ» = 1
3023 b = 2
3024
3025 «const c:ˇ» usize = 3;
3026 "},
3027 cx,
3028 );
3029
3030 editor
3031 });
3032}
3033
3034#[gpui::test]
3035async fn test_backspace(cx: &mut gpui::TestAppContext) {
3036 init_test(cx, |_| {});
3037
3038 let mut cx = EditorTestContext::new(cx).await;
3039
3040 // Basic backspace
3041 cx.set_state(indoc! {"
3042 onˇe two three
3043 fou«rˇ» five six
3044 seven «ˇeight nine
3045 »ten
3046 "});
3047 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3048 cx.assert_editor_state(indoc! {"
3049 oˇe two three
3050 fouˇ five six
3051 seven ˇten
3052 "});
3053
3054 // Test backspace inside and around indents
3055 cx.set_state(indoc! {"
3056 zero
3057 ˇone
3058 ˇtwo
3059 ˇ ˇ ˇ three
3060 ˇ ˇ four
3061 "});
3062 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3063 cx.assert_editor_state(indoc! {"
3064 zero
3065 ˇone
3066 ˇtwo
3067 ˇ threeˇ four
3068 "});
3069
3070 // Test backspace with line_mode set to true
3071 cx.update_editor(|e, _| e.selections.line_mode = true);
3072 cx.set_state(indoc! {"
3073 The ˇquick ˇbrown
3074 fox jumps over
3075 the lazy dog
3076 ˇThe qu«ick bˇ»rown"});
3077 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3078 cx.assert_editor_state(indoc! {"
3079 ˇfox jumps over
3080 the lazy dogˇ"});
3081}
3082
3083#[gpui::test]
3084async fn test_delete(cx: &mut gpui::TestAppContext) {
3085 init_test(cx, |_| {});
3086
3087 let mut cx = EditorTestContext::new(cx).await;
3088 cx.set_state(indoc! {"
3089 onˇe two three
3090 fou«rˇ» five six
3091 seven «ˇeight nine
3092 »ten
3093 "});
3094 cx.update_editor(|e, cx| e.delete(&Delete, cx));
3095 cx.assert_editor_state(indoc! {"
3096 onˇ two three
3097 fouˇ five six
3098 seven ˇten
3099 "});
3100
3101 // Test backspace with line_mode set to true
3102 cx.update_editor(|e, _| e.selections.line_mode = true);
3103 cx.set_state(indoc! {"
3104 The ˇquick ˇbrown
3105 fox «ˇjum»ps over
3106 the lazy dog
3107 ˇThe qu«ick bˇ»rown"});
3108 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3109 cx.assert_editor_state("ˇthe lazy dogˇ");
3110}
3111
3112#[gpui::test]
3113fn test_delete_line(cx: &mut TestAppContext) {
3114 init_test(cx, |_| {});
3115
3116 let view = cx.add_window(|cx| {
3117 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3118 build_editor(buffer, cx)
3119 });
3120 _ = view.update(cx, |view, cx| {
3121 view.change_selections(None, cx, |s| {
3122 s.select_display_ranges([
3123 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3124 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3125 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3126 ])
3127 });
3128 view.delete_line(&DeleteLine, cx);
3129 assert_eq!(view.display_text(cx), "ghi");
3130 assert_eq!(
3131 view.selections.display_ranges(cx),
3132 vec![
3133 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3134 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3135 ]
3136 );
3137 });
3138
3139 let view = cx.add_window(|cx| {
3140 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3141 build_editor(buffer, cx)
3142 });
3143 _ = view.update(cx, |view, cx| {
3144 view.change_selections(None, cx, |s| {
3145 s.select_display_ranges([
3146 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3147 ])
3148 });
3149 view.delete_line(&DeleteLine, cx);
3150 assert_eq!(view.display_text(cx), "ghi\n");
3151 assert_eq!(
3152 view.selections.display_ranges(cx),
3153 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3154 );
3155 });
3156}
3157
3158#[gpui::test]
3159fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3160 init_test(cx, |_| {});
3161
3162 cx.add_window(|cx| {
3163 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3164 let mut editor = build_editor(buffer.clone(), cx);
3165 let buffer = buffer.read(cx).as_singleton().unwrap();
3166
3167 assert_eq!(
3168 editor.selections.ranges::<Point>(cx),
3169 &[Point::new(0, 0)..Point::new(0, 0)]
3170 );
3171
3172 // When on single line, replace newline at end by space
3173 editor.join_lines(&JoinLines, cx);
3174 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3175 assert_eq!(
3176 editor.selections.ranges::<Point>(cx),
3177 &[Point::new(0, 3)..Point::new(0, 3)]
3178 );
3179
3180 // When multiple lines are selected, remove newlines that are spanned by the selection
3181 editor.change_selections(None, cx, |s| {
3182 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3183 });
3184 editor.join_lines(&JoinLines, cx);
3185 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3186 assert_eq!(
3187 editor.selections.ranges::<Point>(cx),
3188 &[Point::new(0, 11)..Point::new(0, 11)]
3189 );
3190
3191 // Undo should be transactional
3192 editor.undo(&Undo, cx);
3193 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3194 assert_eq!(
3195 editor.selections.ranges::<Point>(cx),
3196 &[Point::new(0, 5)..Point::new(2, 2)]
3197 );
3198
3199 // When joining an empty line don't insert a space
3200 editor.change_selections(None, cx, |s| {
3201 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3202 });
3203 editor.join_lines(&JoinLines, cx);
3204 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3205 assert_eq!(
3206 editor.selections.ranges::<Point>(cx),
3207 [Point::new(2, 3)..Point::new(2, 3)]
3208 );
3209
3210 // We can remove trailing newlines
3211 editor.join_lines(&JoinLines, cx);
3212 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3213 assert_eq!(
3214 editor.selections.ranges::<Point>(cx),
3215 [Point::new(2, 3)..Point::new(2, 3)]
3216 );
3217
3218 // We don't blow up on the last line
3219 editor.join_lines(&JoinLines, cx);
3220 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3221 assert_eq!(
3222 editor.selections.ranges::<Point>(cx),
3223 [Point::new(2, 3)..Point::new(2, 3)]
3224 );
3225
3226 // reset to test indentation
3227 editor.buffer.update(cx, |buffer, cx| {
3228 buffer.edit(
3229 [
3230 (Point::new(1, 0)..Point::new(1, 2), " "),
3231 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3232 ],
3233 None,
3234 cx,
3235 )
3236 });
3237
3238 // We remove any leading spaces
3239 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3240 editor.change_selections(None, cx, |s| {
3241 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3242 });
3243 editor.join_lines(&JoinLines, cx);
3244 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3245
3246 // We don't insert a space for a line containing only spaces
3247 editor.join_lines(&JoinLines, cx);
3248 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3249
3250 // We ignore any leading tabs
3251 editor.join_lines(&JoinLines, cx);
3252 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3253
3254 editor
3255 });
3256}
3257
3258#[gpui::test]
3259fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3260 init_test(cx, |_| {});
3261
3262 cx.add_window(|cx| {
3263 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3264 let mut editor = build_editor(buffer.clone(), cx);
3265 let buffer = buffer.read(cx).as_singleton().unwrap();
3266
3267 editor.change_selections(None, cx, |s| {
3268 s.select_ranges([
3269 Point::new(0, 2)..Point::new(1, 1),
3270 Point::new(1, 2)..Point::new(1, 2),
3271 Point::new(3, 1)..Point::new(3, 2),
3272 ])
3273 });
3274
3275 editor.join_lines(&JoinLines, cx);
3276 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3277
3278 assert_eq!(
3279 editor.selections.ranges::<Point>(cx),
3280 [
3281 Point::new(0, 7)..Point::new(0, 7),
3282 Point::new(1, 3)..Point::new(1, 3)
3283 ]
3284 );
3285 editor
3286 });
3287}
3288
3289#[gpui::test]
3290async fn test_join_lines_with_git_diff_base(
3291 executor: BackgroundExecutor,
3292 cx: &mut gpui::TestAppContext,
3293) {
3294 init_test(cx, |_| {});
3295
3296 let mut cx = EditorTestContext::new(cx).await;
3297
3298 let diff_base = r#"
3299 Line 0
3300 Line 1
3301 Line 2
3302 Line 3
3303 "#
3304 .unindent();
3305
3306 cx.set_state(
3307 &r#"
3308 ˇLine 0
3309 Line 1
3310 Line 2
3311 Line 3
3312 "#
3313 .unindent(),
3314 );
3315
3316 cx.set_diff_base(&diff_base);
3317 executor.run_until_parked();
3318
3319 // Join lines
3320 cx.update_editor(|editor, cx| {
3321 editor.join_lines(&JoinLines, cx);
3322 });
3323 executor.run_until_parked();
3324
3325 cx.assert_editor_state(
3326 &r#"
3327 Line 0ˇ Line 1
3328 Line 2
3329 Line 3
3330 "#
3331 .unindent(),
3332 );
3333 // Join again
3334 cx.update_editor(|editor, cx| {
3335 editor.join_lines(&JoinLines, cx);
3336 });
3337 executor.run_until_parked();
3338
3339 cx.assert_editor_state(
3340 &r#"
3341 Line 0 Line 1ˇ Line 2
3342 Line 3
3343 "#
3344 .unindent(),
3345 );
3346}
3347
3348#[gpui::test]
3349async fn test_custom_newlines_cause_no_false_positive_diffs(
3350 executor: BackgroundExecutor,
3351 cx: &mut gpui::TestAppContext,
3352) {
3353 init_test(cx, |_| {});
3354 let mut cx = EditorTestContext::new(cx).await;
3355 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3356 cx.set_diff_base("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3357 executor.run_until_parked();
3358
3359 cx.update_editor(|editor, cx| {
3360 let snapshot = editor.snapshot(cx);
3361 assert_eq!(
3362 snapshot
3363 .diff_map
3364 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
3365 .collect::<Vec<_>>(),
3366 Vec::new(),
3367 "Should not have any diffs for files with custom newlines"
3368 );
3369 });
3370}
3371
3372#[gpui::test]
3373async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3374 init_test(cx, |_| {});
3375
3376 let mut cx = EditorTestContext::new(cx).await;
3377
3378 // Test sort_lines_case_insensitive()
3379 cx.set_state(indoc! {"
3380 «z
3381 y
3382 x
3383 Z
3384 Y
3385 Xˇ»
3386 "});
3387 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3388 cx.assert_editor_state(indoc! {"
3389 «x
3390 X
3391 y
3392 Y
3393 z
3394 Zˇ»
3395 "});
3396
3397 // Test reverse_lines()
3398 cx.set_state(indoc! {"
3399 «5
3400 4
3401 3
3402 2
3403 1ˇ»
3404 "});
3405 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3406 cx.assert_editor_state(indoc! {"
3407 «1
3408 2
3409 3
3410 4
3411 5ˇ»
3412 "});
3413
3414 // Skip testing shuffle_line()
3415
3416 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3417 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3418
3419 // Don't manipulate when cursor is on single line, but expand the selection
3420 cx.set_state(indoc! {"
3421 ddˇdd
3422 ccc
3423 bb
3424 a
3425 "});
3426 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3427 cx.assert_editor_state(indoc! {"
3428 «ddddˇ»
3429 ccc
3430 bb
3431 a
3432 "});
3433
3434 // Basic manipulate case
3435 // Start selection moves to column 0
3436 // End of selection shrinks to fit shorter line
3437 cx.set_state(indoc! {"
3438 dd«d
3439 ccc
3440 bb
3441 aaaaaˇ»
3442 "});
3443 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3444 cx.assert_editor_state(indoc! {"
3445 «aaaaa
3446 bb
3447 ccc
3448 dddˇ»
3449 "});
3450
3451 // Manipulate case with newlines
3452 cx.set_state(indoc! {"
3453 dd«d
3454 ccc
3455
3456 bb
3457 aaaaa
3458
3459 ˇ»
3460 "});
3461 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3462 cx.assert_editor_state(indoc! {"
3463 «
3464
3465 aaaaa
3466 bb
3467 ccc
3468 dddˇ»
3469
3470 "});
3471
3472 // Adding new line
3473 cx.set_state(indoc! {"
3474 aa«a
3475 bbˇ»b
3476 "});
3477 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3478 cx.assert_editor_state(indoc! {"
3479 «aaa
3480 bbb
3481 added_lineˇ»
3482 "});
3483
3484 // Removing line
3485 cx.set_state(indoc! {"
3486 aa«a
3487 bbbˇ»
3488 "});
3489 cx.update_editor(|e, cx| {
3490 e.manipulate_lines(cx, |lines| {
3491 lines.pop();
3492 })
3493 });
3494 cx.assert_editor_state(indoc! {"
3495 «aaaˇ»
3496 "});
3497
3498 // Removing all lines
3499 cx.set_state(indoc! {"
3500 aa«a
3501 bbbˇ»
3502 "});
3503 cx.update_editor(|e, cx| {
3504 e.manipulate_lines(cx, |lines| {
3505 lines.drain(..);
3506 })
3507 });
3508 cx.assert_editor_state(indoc! {"
3509 ˇ
3510 "});
3511}
3512
3513#[gpui::test]
3514async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3515 init_test(cx, |_| {});
3516
3517 let mut cx = EditorTestContext::new(cx).await;
3518
3519 // Consider continuous selection as single selection
3520 cx.set_state(indoc! {"
3521 Aaa«aa
3522 cˇ»c«c
3523 bb
3524 aaaˇ»aa
3525 "});
3526 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3527 cx.assert_editor_state(indoc! {"
3528 «Aaaaa
3529 ccc
3530 bb
3531 aaaaaˇ»
3532 "});
3533
3534 cx.set_state(indoc! {"
3535 Aaa«aa
3536 cˇ»c«c
3537 bb
3538 aaaˇ»aa
3539 "});
3540 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3541 cx.assert_editor_state(indoc! {"
3542 «Aaaaa
3543 ccc
3544 bbˇ»
3545 "});
3546
3547 // Consider non continuous selection as distinct dedup operations
3548 cx.set_state(indoc! {"
3549 «aaaaa
3550 bb
3551 aaaaa
3552 aaaaaˇ»
3553
3554 aaa«aaˇ»
3555 "});
3556 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3557 cx.assert_editor_state(indoc! {"
3558 «aaaaa
3559 bbˇ»
3560
3561 «aaaaaˇ»
3562 "});
3563}
3564
3565#[gpui::test]
3566async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3567 init_test(cx, |_| {});
3568
3569 let mut cx = EditorTestContext::new(cx).await;
3570
3571 cx.set_state(indoc! {"
3572 «Aaa
3573 aAa
3574 Aaaˇ»
3575 "});
3576 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3577 cx.assert_editor_state(indoc! {"
3578 «Aaa
3579 aAaˇ»
3580 "});
3581
3582 cx.set_state(indoc! {"
3583 «Aaa
3584 aAa
3585 aaAˇ»
3586 "});
3587 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3588 cx.assert_editor_state(indoc! {"
3589 «Aaaˇ»
3590 "});
3591}
3592
3593#[gpui::test]
3594async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3595 init_test(cx, |_| {});
3596
3597 let mut cx = EditorTestContext::new(cx).await;
3598
3599 // Manipulate with multiple selections on a single line
3600 cx.set_state(indoc! {"
3601 dd«dd
3602 cˇ»c«c
3603 bb
3604 aaaˇ»aa
3605 "});
3606 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3607 cx.assert_editor_state(indoc! {"
3608 «aaaaa
3609 bb
3610 ccc
3611 ddddˇ»
3612 "});
3613
3614 // Manipulate with multiple disjoin selections
3615 cx.set_state(indoc! {"
3616 5«
3617 4
3618 3
3619 2
3620 1ˇ»
3621
3622 dd«dd
3623 ccc
3624 bb
3625 aaaˇ»aa
3626 "});
3627 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3628 cx.assert_editor_state(indoc! {"
3629 «1
3630 2
3631 3
3632 4
3633 5ˇ»
3634
3635 «aaaaa
3636 bb
3637 ccc
3638 ddddˇ»
3639 "});
3640
3641 // Adding lines on each selection
3642 cx.set_state(indoc! {"
3643 2«
3644 1ˇ»
3645
3646 bb«bb
3647 aaaˇ»aa
3648 "});
3649 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3650 cx.assert_editor_state(indoc! {"
3651 «2
3652 1
3653 added lineˇ»
3654
3655 «bbbb
3656 aaaaa
3657 added lineˇ»
3658 "});
3659
3660 // Removing lines on each selection
3661 cx.set_state(indoc! {"
3662 2«
3663 1ˇ»
3664
3665 bb«bb
3666 aaaˇ»aa
3667 "});
3668 cx.update_editor(|e, cx| {
3669 e.manipulate_lines(cx, |lines| {
3670 lines.pop();
3671 })
3672 });
3673 cx.assert_editor_state(indoc! {"
3674 «2ˇ»
3675
3676 «bbbbˇ»
3677 "});
3678}
3679
3680#[gpui::test]
3681async fn test_manipulate_text(cx: &mut TestAppContext) {
3682 init_test(cx, |_| {});
3683
3684 let mut cx = EditorTestContext::new(cx).await;
3685
3686 // Test convert_to_upper_case()
3687 cx.set_state(indoc! {"
3688 «hello worldˇ»
3689 "});
3690 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3691 cx.assert_editor_state(indoc! {"
3692 «HELLO WORLDˇ»
3693 "});
3694
3695 // Test convert_to_lower_case()
3696 cx.set_state(indoc! {"
3697 «HELLO WORLDˇ»
3698 "});
3699 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3700 cx.assert_editor_state(indoc! {"
3701 «hello worldˇ»
3702 "});
3703
3704 // Test multiple line, single selection case
3705 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3706 cx.set_state(indoc! {"
3707 «The quick brown
3708 fox jumps over
3709 the lazy dogˇ»
3710 "});
3711 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3712 cx.assert_editor_state(indoc! {"
3713 «The Quick Brown
3714 Fox Jumps Over
3715 The Lazy Dogˇ»
3716 "});
3717
3718 // Test multiple line, single selection case
3719 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3720 cx.set_state(indoc! {"
3721 «The quick brown
3722 fox jumps over
3723 the lazy dogˇ»
3724 "});
3725 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3726 cx.assert_editor_state(indoc! {"
3727 «TheQuickBrown
3728 FoxJumpsOver
3729 TheLazyDogˇ»
3730 "});
3731
3732 // From here on out, test more complex cases of manipulate_text()
3733
3734 // Test no selection case - should affect words cursors are in
3735 // Cursor at beginning, middle, and end of word
3736 cx.set_state(indoc! {"
3737 ˇhello big beauˇtiful worldˇ
3738 "});
3739 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3740 cx.assert_editor_state(indoc! {"
3741 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3742 "});
3743
3744 // Test multiple selections on a single line and across multiple lines
3745 cx.set_state(indoc! {"
3746 «Theˇ» quick «brown
3747 foxˇ» jumps «overˇ»
3748 the «lazyˇ» dog
3749 "});
3750 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3751 cx.assert_editor_state(indoc! {"
3752 «THEˇ» quick «BROWN
3753 FOXˇ» jumps «OVERˇ»
3754 the «LAZYˇ» dog
3755 "});
3756
3757 // Test case where text length grows
3758 cx.set_state(indoc! {"
3759 «tschüߡ»
3760 "});
3761 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3762 cx.assert_editor_state(indoc! {"
3763 «TSCHÜSSˇ»
3764 "});
3765
3766 // Test to make sure we don't crash when text shrinks
3767 cx.set_state(indoc! {"
3768 aaa_bbbˇ
3769 "});
3770 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3771 cx.assert_editor_state(indoc! {"
3772 «aaaBbbˇ»
3773 "});
3774
3775 // Test to make sure we all aware of the fact that each word can grow and shrink
3776 // Final selections should be aware of this fact
3777 cx.set_state(indoc! {"
3778 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3779 "});
3780 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3781 cx.assert_editor_state(indoc! {"
3782 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3783 "});
3784
3785 cx.set_state(indoc! {"
3786 «hElLo, WoRld!ˇ»
3787 "});
3788 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3789 cx.assert_editor_state(indoc! {"
3790 «HeLlO, wOrLD!ˇ»
3791 "});
3792}
3793
3794#[gpui::test]
3795fn test_duplicate_line(cx: &mut TestAppContext) {
3796 init_test(cx, |_| {});
3797
3798 let view = cx.add_window(|cx| {
3799 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3800 build_editor(buffer, cx)
3801 });
3802 _ = view.update(cx, |view, cx| {
3803 view.change_selections(None, cx, |s| {
3804 s.select_display_ranges([
3805 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3806 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3807 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3808 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3809 ])
3810 });
3811 view.duplicate_line_down(&DuplicateLineDown, cx);
3812 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3813 assert_eq!(
3814 view.selections.display_ranges(cx),
3815 vec![
3816 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3817 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3818 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3819 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3820 ]
3821 );
3822 });
3823
3824 let view = cx.add_window(|cx| {
3825 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3826 build_editor(buffer, cx)
3827 });
3828 _ = view.update(cx, |view, cx| {
3829 view.change_selections(None, cx, |s| {
3830 s.select_display_ranges([
3831 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3832 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3833 ])
3834 });
3835 view.duplicate_line_down(&DuplicateLineDown, cx);
3836 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3837 assert_eq!(
3838 view.selections.display_ranges(cx),
3839 vec![
3840 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3841 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3842 ]
3843 );
3844 });
3845
3846 // With `move_upwards` the selections stay in place, except for
3847 // the lines inserted above them
3848 let view = cx.add_window(|cx| {
3849 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3850 build_editor(buffer, cx)
3851 });
3852 _ = view.update(cx, |view, cx| {
3853 view.change_selections(None, cx, |s| {
3854 s.select_display_ranges([
3855 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3856 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3857 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3858 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3859 ])
3860 });
3861 view.duplicate_line_up(&DuplicateLineUp, cx);
3862 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3863 assert_eq!(
3864 view.selections.display_ranges(cx),
3865 vec![
3866 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3867 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3868 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3869 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3870 ]
3871 );
3872 });
3873
3874 let view = cx.add_window(|cx| {
3875 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3876 build_editor(buffer, cx)
3877 });
3878 _ = view.update(cx, |view, cx| {
3879 view.change_selections(None, cx, |s| {
3880 s.select_display_ranges([
3881 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3882 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3883 ])
3884 });
3885 view.duplicate_line_up(&DuplicateLineUp, cx);
3886 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3887 assert_eq!(
3888 view.selections.display_ranges(cx),
3889 vec![
3890 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3891 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3892 ]
3893 );
3894 });
3895}
3896
3897#[gpui::test]
3898fn test_move_line_up_down(cx: &mut TestAppContext) {
3899 init_test(cx, |_| {});
3900
3901 let view = cx.add_window(|cx| {
3902 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3903 build_editor(buffer, cx)
3904 });
3905 _ = view.update(cx, |view, cx| {
3906 view.fold_creases(
3907 vec![
3908 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3909 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3910 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3911 ],
3912 true,
3913 cx,
3914 );
3915 view.change_selections(None, cx, |s| {
3916 s.select_display_ranges([
3917 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3918 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3919 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3920 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3921 ])
3922 });
3923 assert_eq!(
3924 view.display_text(cx),
3925 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3926 );
3927
3928 view.move_line_up(&MoveLineUp, cx);
3929 assert_eq!(
3930 view.display_text(cx),
3931 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3932 );
3933 assert_eq!(
3934 view.selections.display_ranges(cx),
3935 vec![
3936 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3937 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3938 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3939 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3940 ]
3941 );
3942 });
3943
3944 _ = view.update(cx, |view, cx| {
3945 view.move_line_down(&MoveLineDown, cx);
3946 assert_eq!(
3947 view.display_text(cx),
3948 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3949 );
3950 assert_eq!(
3951 view.selections.display_ranges(cx),
3952 vec![
3953 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3954 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3955 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3956 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3957 ]
3958 );
3959 });
3960
3961 _ = view.update(cx, |view, cx| {
3962 view.move_line_down(&MoveLineDown, cx);
3963 assert_eq!(
3964 view.display_text(cx),
3965 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3966 );
3967 assert_eq!(
3968 view.selections.display_ranges(cx),
3969 vec![
3970 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3971 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3972 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3973 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3974 ]
3975 );
3976 });
3977
3978 _ = view.update(cx, |view, cx| {
3979 view.move_line_up(&MoveLineUp, cx);
3980 assert_eq!(
3981 view.display_text(cx),
3982 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3983 );
3984 assert_eq!(
3985 view.selections.display_ranges(cx),
3986 vec![
3987 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3988 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3989 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3990 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3991 ]
3992 );
3993 });
3994}
3995
3996#[gpui::test]
3997fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3998 init_test(cx, |_| {});
3999
4000 let editor = cx.add_window(|cx| {
4001 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4002 build_editor(buffer, cx)
4003 });
4004 _ = editor.update(cx, |editor, cx| {
4005 let snapshot = editor.buffer.read(cx).snapshot(cx);
4006 editor.insert_blocks(
4007 [BlockProperties {
4008 style: BlockStyle::Fixed,
4009 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4010 height: 1,
4011 render: Arc::new(|_| div().into_any()),
4012 priority: 0,
4013 }],
4014 Some(Autoscroll::fit()),
4015 cx,
4016 );
4017 editor.change_selections(None, cx, |s| {
4018 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4019 });
4020 editor.move_line_down(&MoveLineDown, cx);
4021 });
4022}
4023
4024#[gpui::test]
4025async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4026 init_test(cx, |_| {});
4027
4028 let mut cx = EditorTestContext::new(cx).await;
4029 cx.set_state(
4030 &"
4031 ˇzero
4032 one
4033 two
4034 three
4035 four
4036 five
4037 "
4038 .unindent(),
4039 );
4040
4041 // Create a four-line block that replaces three lines of text.
4042 cx.update_editor(|editor, cx| {
4043 let snapshot = editor.snapshot(cx);
4044 let snapshot = &snapshot.buffer_snapshot;
4045 let placement = BlockPlacement::Replace(
4046 snapshot.anchor_after(Point::new(1, 0))..snapshot.anchor_after(Point::new(3, 0)),
4047 );
4048 editor.insert_blocks(
4049 [BlockProperties {
4050 placement,
4051 height: 4,
4052 style: BlockStyle::Sticky,
4053 render: Arc::new(|_| gpui::div().into_any_element()),
4054 priority: 0,
4055 }],
4056 None,
4057 cx,
4058 );
4059 });
4060
4061 // Move down so that the cursor touches the block.
4062 cx.update_editor(|editor, cx| {
4063 editor.move_down(&Default::default(), cx);
4064 });
4065 cx.assert_editor_state(
4066 &"
4067 zero
4068 «one
4069 two
4070 threeˇ»
4071 four
4072 five
4073 "
4074 .unindent(),
4075 );
4076
4077 // Move down past the block.
4078 cx.update_editor(|editor, cx| {
4079 editor.move_down(&Default::default(), cx);
4080 });
4081 cx.assert_editor_state(
4082 &"
4083 zero
4084 one
4085 two
4086 three
4087 ˇfour
4088 five
4089 "
4090 .unindent(),
4091 );
4092}
4093
4094#[gpui::test]
4095fn test_transpose(cx: &mut TestAppContext) {
4096 init_test(cx, |_| {});
4097
4098 _ = cx.add_window(|cx| {
4099 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
4100 editor.set_style(EditorStyle::default(), cx);
4101 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
4102 editor.transpose(&Default::default(), cx);
4103 assert_eq!(editor.text(cx), "bac");
4104 assert_eq!(editor.selections.ranges(cx), [2..2]);
4105
4106 editor.transpose(&Default::default(), cx);
4107 assert_eq!(editor.text(cx), "bca");
4108 assert_eq!(editor.selections.ranges(cx), [3..3]);
4109
4110 editor.transpose(&Default::default(), cx);
4111 assert_eq!(editor.text(cx), "bac");
4112 assert_eq!(editor.selections.ranges(cx), [3..3]);
4113
4114 editor
4115 });
4116
4117 _ = cx.add_window(|cx| {
4118 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4119 editor.set_style(EditorStyle::default(), cx);
4120 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
4121 editor.transpose(&Default::default(), cx);
4122 assert_eq!(editor.text(cx), "acb\nde");
4123 assert_eq!(editor.selections.ranges(cx), [3..3]);
4124
4125 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4126 editor.transpose(&Default::default(), cx);
4127 assert_eq!(editor.text(cx), "acbd\ne");
4128 assert_eq!(editor.selections.ranges(cx), [5..5]);
4129
4130 editor.transpose(&Default::default(), cx);
4131 assert_eq!(editor.text(cx), "acbde\n");
4132 assert_eq!(editor.selections.ranges(cx), [6..6]);
4133
4134 editor.transpose(&Default::default(), cx);
4135 assert_eq!(editor.text(cx), "acbd\ne");
4136 assert_eq!(editor.selections.ranges(cx), [6..6]);
4137
4138 editor
4139 });
4140
4141 _ = cx.add_window(|cx| {
4142 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4143 editor.set_style(EditorStyle::default(), cx);
4144 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4145 editor.transpose(&Default::default(), cx);
4146 assert_eq!(editor.text(cx), "bacd\ne");
4147 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4148
4149 editor.transpose(&Default::default(), cx);
4150 assert_eq!(editor.text(cx), "bcade\n");
4151 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4152
4153 editor.transpose(&Default::default(), cx);
4154 assert_eq!(editor.text(cx), "bcda\ne");
4155 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4156
4157 editor.transpose(&Default::default(), cx);
4158 assert_eq!(editor.text(cx), "bcade\n");
4159 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4160
4161 editor.transpose(&Default::default(), cx);
4162 assert_eq!(editor.text(cx), "bcaed\n");
4163 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4164
4165 editor
4166 });
4167
4168 _ = cx.add_window(|cx| {
4169 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
4170 editor.set_style(EditorStyle::default(), cx);
4171 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4172 editor.transpose(&Default::default(), cx);
4173 assert_eq!(editor.text(cx), "🏀🍐✋");
4174 assert_eq!(editor.selections.ranges(cx), [8..8]);
4175
4176 editor.transpose(&Default::default(), cx);
4177 assert_eq!(editor.text(cx), "🏀✋🍐");
4178 assert_eq!(editor.selections.ranges(cx), [11..11]);
4179
4180 editor.transpose(&Default::default(), cx);
4181 assert_eq!(editor.text(cx), "🏀🍐✋");
4182 assert_eq!(editor.selections.ranges(cx), [11..11]);
4183
4184 editor
4185 });
4186}
4187
4188#[gpui::test]
4189async fn test_rewrap(cx: &mut TestAppContext) {
4190 init_test(cx, |_| {});
4191
4192 let mut cx = EditorTestContext::new(cx).await;
4193
4194 let language_with_c_comments = Arc::new(Language::new(
4195 LanguageConfig {
4196 line_comments: vec!["// ".into()],
4197 ..LanguageConfig::default()
4198 },
4199 None,
4200 ));
4201 let language_with_pound_comments = Arc::new(Language::new(
4202 LanguageConfig {
4203 line_comments: vec!["# ".into()],
4204 ..LanguageConfig::default()
4205 },
4206 None,
4207 ));
4208 let markdown_language = Arc::new(Language::new(
4209 LanguageConfig {
4210 name: "Markdown".into(),
4211 ..LanguageConfig::default()
4212 },
4213 None,
4214 ));
4215 let language_with_doc_comments = Arc::new(Language::new(
4216 LanguageConfig {
4217 line_comments: vec!["// ".into(), "/// ".into()],
4218 ..LanguageConfig::default()
4219 },
4220 Some(tree_sitter_rust::LANGUAGE.into()),
4221 ));
4222
4223 let plaintext_language = Arc::new(Language::new(
4224 LanguageConfig {
4225 name: "Plain Text".into(),
4226 ..LanguageConfig::default()
4227 },
4228 None,
4229 ));
4230
4231 assert_rewrap(
4232 indoc! {"
4233 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4234 "},
4235 indoc! {"
4236 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4237 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4238 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4239 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4240 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4241 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4242 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4243 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4244 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4245 // porttitor id. Aliquam id accumsan eros.
4246 "},
4247 language_with_c_comments.clone(),
4248 &mut cx,
4249 );
4250
4251 // Test that rewrapping works inside of a selection
4252 assert_rewrap(
4253 indoc! {"
4254 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ»
4255 "},
4256 indoc! {"
4257 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4258 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4259 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4260 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4261 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4262 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4263 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4264 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4265 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4266 // porttitor id. Aliquam id accumsan eros.ˇ»
4267 "},
4268 language_with_c_comments.clone(),
4269 &mut cx,
4270 );
4271
4272 // Test that cursors that expand to the same region are collapsed.
4273 assert_rewrap(
4274 indoc! {"
4275 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4276 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4277 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4278 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4279 "},
4280 indoc! {"
4281 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4282 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4283 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4284 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4285 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4286 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4287 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4288 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4289 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4290 // porttitor id. Aliquam id accumsan eros.
4291 "},
4292 language_with_c_comments.clone(),
4293 &mut cx,
4294 );
4295
4296 // Test that non-contiguous selections are treated separately.
4297 assert_rewrap(
4298 indoc! {"
4299 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4300 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4301 //
4302 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4303 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4304 "},
4305 indoc! {"
4306 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4307 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4308 // auctor, eu lacinia sapien scelerisque.
4309 //
4310 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4311 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4312 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4313 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4314 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4315 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4316 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4317 "},
4318 language_with_c_comments.clone(),
4319 &mut cx,
4320 );
4321
4322 // Test that different comment prefixes are supported.
4323 assert_rewrap(
4324 indoc! {"
4325 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4326 "},
4327 indoc! {"
4328 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4329 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4330 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4331 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4332 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4333 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4334 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4335 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4336 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4337 # accumsan eros.
4338 "},
4339 language_with_pound_comments.clone(),
4340 &mut cx,
4341 );
4342
4343 // Test that rewrapping is ignored outside of comments in most languages.
4344 assert_rewrap(
4345 indoc! {"
4346 /// Adds two numbers.
4347 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4348 fn add(a: u32, b: u32) -> u32 {
4349 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4350 }
4351 "},
4352 indoc! {"
4353 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4354 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4355 fn add(a: u32, b: u32) -> u32 {
4356 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4357 }
4358 "},
4359 language_with_doc_comments.clone(),
4360 &mut cx,
4361 );
4362
4363 // Test that rewrapping works in Markdown and Plain Text languages.
4364 assert_rewrap(
4365 indoc! {"
4366 # Hello
4367
4368 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4369 "},
4370 indoc! {"
4371 # Hello
4372
4373 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4374 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4375 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4376 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4377 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4378 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4379 Integer sit amet scelerisque nisi.
4380 "},
4381 markdown_language,
4382 &mut cx,
4383 );
4384
4385 assert_rewrap(
4386 indoc! {"
4387 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4388 "},
4389 indoc! {"
4390 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4391 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4392 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4393 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4394 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4395 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4396 Integer sit amet scelerisque nisi.
4397 "},
4398 plaintext_language,
4399 &mut cx,
4400 );
4401
4402 // Test rewrapping unaligned comments in a selection.
4403 assert_rewrap(
4404 indoc! {"
4405 fn foo() {
4406 if true {
4407 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4408 // Praesent semper egestas tellus id dignissim.ˇ»
4409 do_something();
4410 } else {
4411 //
4412 }
4413 }
4414 "},
4415 indoc! {"
4416 fn foo() {
4417 if true {
4418 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4419 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4420 // egestas tellus id dignissim.ˇ»
4421 do_something();
4422 } else {
4423 //
4424 }
4425 }
4426 "},
4427 language_with_doc_comments.clone(),
4428 &mut cx,
4429 );
4430
4431 assert_rewrap(
4432 indoc! {"
4433 fn foo() {
4434 if true {
4435 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4436 // Praesent semper egestas tellus id dignissim.»
4437 do_something();
4438 } else {
4439 //
4440 }
4441
4442 }
4443 "},
4444 indoc! {"
4445 fn foo() {
4446 if true {
4447 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4448 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4449 // egestas tellus id dignissim.»
4450 do_something();
4451 } else {
4452 //
4453 }
4454
4455 }
4456 "},
4457 language_with_doc_comments.clone(),
4458 &mut cx,
4459 );
4460
4461 #[track_caller]
4462 fn assert_rewrap(
4463 unwrapped_text: &str,
4464 wrapped_text: &str,
4465 language: Arc<Language>,
4466 cx: &mut EditorTestContext,
4467 ) {
4468 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4469 cx.set_state(unwrapped_text);
4470 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4471 cx.assert_editor_state(wrapped_text);
4472 }
4473}
4474
4475#[gpui::test]
4476async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4477 init_test(cx, |_| {});
4478
4479 let mut cx = EditorTestContext::new(cx).await;
4480
4481 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4482 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4483 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4484
4485 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4486 cx.set_state("two ˇfour ˇsix ˇ");
4487 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4488 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4489
4490 // Paste again but with only two cursors. Since the number of cursors doesn't
4491 // match the number of slices in the clipboard, the entire clipboard text
4492 // is pasted at each cursor.
4493 cx.set_state("ˇtwo one✅ four three six five ˇ");
4494 cx.update_editor(|e, cx| {
4495 e.handle_input("( ", cx);
4496 e.paste(&Paste, cx);
4497 e.handle_input(") ", cx);
4498 });
4499 cx.assert_editor_state(
4500 &([
4501 "( one✅ ",
4502 "three ",
4503 "five ) ˇtwo one✅ four three six five ( one✅ ",
4504 "three ",
4505 "five ) ˇ",
4506 ]
4507 .join("\n")),
4508 );
4509
4510 // Cut with three selections, one of which is full-line.
4511 cx.set_state(indoc! {"
4512 1«2ˇ»3
4513 4ˇ567
4514 «8ˇ»9"});
4515 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4516 cx.assert_editor_state(indoc! {"
4517 1ˇ3
4518 ˇ9"});
4519
4520 // Paste with three selections, noticing how the copied selection that was full-line
4521 // gets inserted before the second cursor.
4522 cx.set_state(indoc! {"
4523 1ˇ3
4524 9ˇ
4525 «oˇ»ne"});
4526 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4527 cx.assert_editor_state(indoc! {"
4528 12ˇ3
4529 4567
4530 9ˇ
4531 8ˇne"});
4532
4533 // Copy with a single cursor only, which writes the whole line into the clipboard.
4534 cx.set_state(indoc! {"
4535 The quick brown
4536 fox juˇmps over
4537 the lazy dog"});
4538 cx.update_editor(|e, cx| e.copy(&Copy, cx));
4539 assert_eq!(
4540 cx.read_from_clipboard()
4541 .and_then(|item| item.text().as_deref().map(str::to_string)),
4542 Some("fox jumps over\n".to_string())
4543 );
4544
4545 // Paste with three selections, noticing how the copied full-line selection is inserted
4546 // before the empty selections but replaces the selection that is non-empty.
4547 cx.set_state(indoc! {"
4548 Tˇhe quick brown
4549 «foˇ»x jumps over
4550 tˇhe lazy dog"});
4551 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4552 cx.assert_editor_state(indoc! {"
4553 fox jumps over
4554 Tˇhe quick brown
4555 fox jumps over
4556 ˇx jumps over
4557 fox jumps over
4558 tˇhe lazy dog"});
4559}
4560
4561#[gpui::test]
4562async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4563 init_test(cx, |_| {});
4564
4565 let mut cx = EditorTestContext::new(cx).await;
4566 let language = Arc::new(Language::new(
4567 LanguageConfig::default(),
4568 Some(tree_sitter_rust::LANGUAGE.into()),
4569 ));
4570 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4571
4572 // Cut an indented block, without the leading whitespace.
4573 cx.set_state(indoc! {"
4574 const a: B = (
4575 c(),
4576 «d(
4577 e,
4578 f
4579 )ˇ»
4580 );
4581 "});
4582 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4583 cx.assert_editor_state(indoc! {"
4584 const a: B = (
4585 c(),
4586 ˇ
4587 );
4588 "});
4589
4590 // Paste it at the same position.
4591 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4592 cx.assert_editor_state(indoc! {"
4593 const a: B = (
4594 c(),
4595 d(
4596 e,
4597 f
4598 )ˇ
4599 );
4600 "});
4601
4602 // Paste it at a line with a lower indent level.
4603 cx.set_state(indoc! {"
4604 ˇ
4605 const a: B = (
4606 c(),
4607 );
4608 "});
4609 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4610 cx.assert_editor_state(indoc! {"
4611 d(
4612 e,
4613 f
4614 )ˇ
4615 const a: B = (
4616 c(),
4617 );
4618 "});
4619
4620 // Cut an indented block, with the leading whitespace.
4621 cx.set_state(indoc! {"
4622 const a: B = (
4623 c(),
4624 « d(
4625 e,
4626 f
4627 )
4628 ˇ»);
4629 "});
4630 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4631 cx.assert_editor_state(indoc! {"
4632 const a: B = (
4633 c(),
4634 ˇ);
4635 "});
4636
4637 // Paste it at the same position.
4638 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4639 cx.assert_editor_state(indoc! {"
4640 const a: B = (
4641 c(),
4642 d(
4643 e,
4644 f
4645 )
4646 ˇ);
4647 "});
4648
4649 // Paste it at a line with a higher indent level.
4650 cx.set_state(indoc! {"
4651 const a: B = (
4652 c(),
4653 d(
4654 e,
4655 fˇ
4656 )
4657 );
4658 "});
4659 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4660 cx.assert_editor_state(indoc! {"
4661 const a: B = (
4662 c(),
4663 d(
4664 e,
4665 f d(
4666 e,
4667 f
4668 )
4669 ˇ
4670 )
4671 );
4672 "});
4673}
4674
4675#[gpui::test]
4676fn test_select_all(cx: &mut TestAppContext) {
4677 init_test(cx, |_| {});
4678
4679 let view = cx.add_window(|cx| {
4680 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4681 build_editor(buffer, cx)
4682 });
4683 _ = view.update(cx, |view, cx| {
4684 view.select_all(&SelectAll, cx);
4685 assert_eq!(
4686 view.selections.display_ranges(cx),
4687 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4688 );
4689 });
4690}
4691
4692#[gpui::test]
4693fn test_select_line(cx: &mut TestAppContext) {
4694 init_test(cx, |_| {});
4695
4696 let view = cx.add_window(|cx| {
4697 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4698 build_editor(buffer, cx)
4699 });
4700 _ = view.update(cx, |view, cx| {
4701 view.change_selections(None, cx, |s| {
4702 s.select_display_ranges([
4703 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4704 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4705 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4706 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4707 ])
4708 });
4709 view.select_line(&SelectLine, cx);
4710 assert_eq!(
4711 view.selections.display_ranges(cx),
4712 vec![
4713 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4714 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4715 ]
4716 );
4717 });
4718
4719 _ = view.update(cx, |view, cx| {
4720 view.select_line(&SelectLine, cx);
4721 assert_eq!(
4722 view.selections.display_ranges(cx),
4723 vec![
4724 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4725 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4726 ]
4727 );
4728 });
4729
4730 _ = view.update(cx, |view, cx| {
4731 view.select_line(&SelectLine, cx);
4732 assert_eq!(
4733 view.selections.display_ranges(cx),
4734 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4735 );
4736 });
4737}
4738
4739#[gpui::test]
4740fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4741 init_test(cx, |_| {});
4742
4743 let view = cx.add_window(|cx| {
4744 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4745 build_editor(buffer, cx)
4746 });
4747 _ = view.update(cx, |view, cx| {
4748 view.fold_creases(
4749 vec![
4750 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4751 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4752 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4753 ],
4754 true,
4755 cx,
4756 );
4757 view.change_selections(None, cx, |s| {
4758 s.select_display_ranges([
4759 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4760 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4761 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4762 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4763 ])
4764 });
4765 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4766 });
4767
4768 _ = view.update(cx, |view, cx| {
4769 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4770 assert_eq!(
4771 view.display_text(cx),
4772 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4773 );
4774 assert_eq!(
4775 view.selections.display_ranges(cx),
4776 [
4777 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4778 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4779 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4780 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4781 ]
4782 );
4783 });
4784
4785 _ = view.update(cx, |view, cx| {
4786 view.change_selections(None, cx, |s| {
4787 s.select_display_ranges([
4788 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4789 ])
4790 });
4791 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4792 assert_eq!(
4793 view.display_text(cx),
4794 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4795 );
4796 assert_eq!(
4797 view.selections.display_ranges(cx),
4798 [
4799 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4800 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4801 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4802 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4803 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4804 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4805 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4806 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4807 ]
4808 );
4809 });
4810}
4811
4812#[gpui::test]
4813async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4814 init_test(cx, |_| {});
4815
4816 let mut cx = EditorTestContext::new(cx).await;
4817
4818 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4819 cx.set_state(indoc!(
4820 r#"abc
4821 defˇghi
4822
4823 jk
4824 nlmo
4825 "#
4826 ));
4827
4828 cx.update_editor(|editor, cx| {
4829 editor.add_selection_above(&Default::default(), cx);
4830 });
4831
4832 cx.assert_editor_state(indoc!(
4833 r#"abcˇ
4834 defˇghi
4835
4836 jk
4837 nlmo
4838 "#
4839 ));
4840
4841 cx.update_editor(|editor, cx| {
4842 editor.add_selection_above(&Default::default(), cx);
4843 });
4844
4845 cx.assert_editor_state(indoc!(
4846 r#"abcˇ
4847 defˇghi
4848
4849 jk
4850 nlmo
4851 "#
4852 ));
4853
4854 cx.update_editor(|view, cx| {
4855 view.add_selection_below(&Default::default(), cx);
4856 });
4857
4858 cx.assert_editor_state(indoc!(
4859 r#"abc
4860 defˇghi
4861
4862 jk
4863 nlmo
4864 "#
4865 ));
4866
4867 cx.update_editor(|view, cx| {
4868 view.undo_selection(&Default::default(), cx);
4869 });
4870
4871 cx.assert_editor_state(indoc!(
4872 r#"abcˇ
4873 defˇghi
4874
4875 jk
4876 nlmo
4877 "#
4878 ));
4879
4880 cx.update_editor(|view, cx| {
4881 view.redo_selection(&Default::default(), cx);
4882 });
4883
4884 cx.assert_editor_state(indoc!(
4885 r#"abc
4886 defˇghi
4887
4888 jk
4889 nlmo
4890 "#
4891 ));
4892
4893 cx.update_editor(|view, cx| {
4894 view.add_selection_below(&Default::default(), cx);
4895 });
4896
4897 cx.assert_editor_state(indoc!(
4898 r#"abc
4899 defˇghi
4900
4901 jk
4902 nlmˇo
4903 "#
4904 ));
4905
4906 cx.update_editor(|view, cx| {
4907 view.add_selection_below(&Default::default(), cx);
4908 });
4909
4910 cx.assert_editor_state(indoc!(
4911 r#"abc
4912 defˇghi
4913
4914 jk
4915 nlmˇo
4916 "#
4917 ));
4918
4919 // change selections
4920 cx.set_state(indoc!(
4921 r#"abc
4922 def«ˇg»hi
4923
4924 jk
4925 nlmo
4926 "#
4927 ));
4928
4929 cx.update_editor(|view, cx| {
4930 view.add_selection_below(&Default::default(), cx);
4931 });
4932
4933 cx.assert_editor_state(indoc!(
4934 r#"abc
4935 def«ˇg»hi
4936
4937 jk
4938 nlm«ˇo»
4939 "#
4940 ));
4941
4942 cx.update_editor(|view, cx| {
4943 view.add_selection_below(&Default::default(), cx);
4944 });
4945
4946 cx.assert_editor_state(indoc!(
4947 r#"abc
4948 def«ˇg»hi
4949
4950 jk
4951 nlm«ˇo»
4952 "#
4953 ));
4954
4955 cx.update_editor(|view, cx| {
4956 view.add_selection_above(&Default::default(), cx);
4957 });
4958
4959 cx.assert_editor_state(indoc!(
4960 r#"abc
4961 def«ˇg»hi
4962
4963 jk
4964 nlmo
4965 "#
4966 ));
4967
4968 cx.update_editor(|view, cx| {
4969 view.add_selection_above(&Default::default(), cx);
4970 });
4971
4972 cx.assert_editor_state(indoc!(
4973 r#"abc
4974 def«ˇg»hi
4975
4976 jk
4977 nlmo
4978 "#
4979 ));
4980
4981 // Change selections again
4982 cx.set_state(indoc!(
4983 r#"a«bc
4984 defgˇ»hi
4985
4986 jk
4987 nlmo
4988 "#
4989 ));
4990
4991 cx.update_editor(|view, cx| {
4992 view.add_selection_below(&Default::default(), cx);
4993 });
4994
4995 cx.assert_editor_state(indoc!(
4996 r#"a«bcˇ»
4997 d«efgˇ»hi
4998
4999 j«kˇ»
5000 nlmo
5001 "#
5002 ));
5003
5004 cx.update_editor(|view, cx| {
5005 view.add_selection_below(&Default::default(), cx);
5006 });
5007 cx.assert_editor_state(indoc!(
5008 r#"a«bcˇ»
5009 d«efgˇ»hi
5010
5011 j«kˇ»
5012 n«lmoˇ»
5013 "#
5014 ));
5015 cx.update_editor(|view, cx| {
5016 view.add_selection_above(&Default::default(), cx);
5017 });
5018
5019 cx.assert_editor_state(indoc!(
5020 r#"a«bcˇ»
5021 d«efgˇ»hi
5022
5023 j«kˇ»
5024 nlmo
5025 "#
5026 ));
5027
5028 // Change selections again
5029 cx.set_state(indoc!(
5030 r#"abc
5031 d«ˇefghi
5032
5033 jk
5034 nlm»o
5035 "#
5036 ));
5037
5038 cx.update_editor(|view, cx| {
5039 view.add_selection_above(&Default::default(), cx);
5040 });
5041
5042 cx.assert_editor_state(indoc!(
5043 r#"a«ˇbc»
5044 d«ˇef»ghi
5045
5046 j«ˇk»
5047 n«ˇlm»o
5048 "#
5049 ));
5050
5051 cx.update_editor(|view, cx| {
5052 view.add_selection_below(&Default::default(), cx);
5053 });
5054
5055 cx.assert_editor_state(indoc!(
5056 r#"abc
5057 d«ˇef»ghi
5058
5059 j«ˇk»
5060 n«ˇlm»o
5061 "#
5062 ));
5063}
5064
5065#[gpui::test]
5066async fn test_select_next(cx: &mut gpui::TestAppContext) {
5067 init_test(cx, |_| {});
5068
5069 let mut cx = EditorTestContext::new(cx).await;
5070 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5071
5072 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5073 .unwrap();
5074 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5075
5076 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5077 .unwrap();
5078 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5079
5080 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5081 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5082
5083 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5084 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5085
5086 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5087 .unwrap();
5088 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5089
5090 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5091 .unwrap();
5092 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5093}
5094
5095#[gpui::test]
5096async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
5097 init_test(cx, |_| {});
5098
5099 let mut cx = EditorTestContext::new(cx).await;
5100 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5101
5102 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
5103 .unwrap();
5104 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5105}
5106
5107#[gpui::test]
5108async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5109 init_test(cx, |_| {});
5110
5111 let mut cx = EditorTestContext::new(cx).await;
5112 cx.set_state(
5113 r#"let foo = 2;
5114lˇet foo = 2;
5115let fooˇ = 2;
5116let foo = 2;
5117let foo = ˇ2;"#,
5118 );
5119
5120 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5121 .unwrap();
5122 cx.assert_editor_state(
5123 r#"let foo = 2;
5124«letˇ» foo = 2;
5125let «fooˇ» = 2;
5126let foo = 2;
5127let foo = «2ˇ»;"#,
5128 );
5129
5130 // noop for multiple selections with different contents
5131 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5132 .unwrap();
5133 cx.assert_editor_state(
5134 r#"let foo = 2;
5135«letˇ» foo = 2;
5136let «fooˇ» = 2;
5137let foo = 2;
5138let foo = «2ˇ»;"#,
5139 );
5140}
5141
5142#[gpui::test]
5143async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
5144 init_test(cx, |_| {});
5145
5146 let mut cx =
5147 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5148
5149 cx.assert_editor_state(indoc! {"
5150 ˇbbb
5151 ccc
5152
5153 bbb
5154 ccc
5155 "});
5156 cx.dispatch_action(SelectPrevious::default());
5157 cx.assert_editor_state(indoc! {"
5158 «bbbˇ»
5159 ccc
5160
5161 bbb
5162 ccc
5163 "});
5164 cx.dispatch_action(SelectPrevious::default());
5165 cx.assert_editor_state(indoc! {"
5166 «bbbˇ»
5167 ccc
5168
5169 «bbbˇ»
5170 ccc
5171 "});
5172}
5173
5174#[gpui::test]
5175async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
5176 init_test(cx, |_| {});
5177
5178 let mut cx = EditorTestContext::new(cx).await;
5179 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5180
5181 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5182 .unwrap();
5183 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5184
5185 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5186 .unwrap();
5187 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5188
5189 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5190 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5191
5192 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5193 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5194
5195 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5196 .unwrap();
5197 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5198
5199 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5200 .unwrap();
5201 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5202
5203 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5204 .unwrap();
5205 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5206}
5207
5208#[gpui::test]
5209async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5210 init_test(cx, |_| {});
5211
5212 let mut cx = EditorTestContext::new(cx).await;
5213 cx.set_state(
5214 r#"let foo = 2;
5215lˇet foo = 2;
5216let fooˇ = 2;
5217let foo = 2;
5218let foo = ˇ2;"#,
5219 );
5220
5221 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5222 .unwrap();
5223 cx.assert_editor_state(
5224 r#"let foo = 2;
5225«letˇ» foo = 2;
5226let «fooˇ» = 2;
5227let foo = 2;
5228let foo = «2ˇ»;"#,
5229 );
5230
5231 // noop for multiple selections with different contents
5232 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5233 .unwrap();
5234 cx.assert_editor_state(
5235 r#"let foo = 2;
5236«letˇ» foo = 2;
5237let «fooˇ» = 2;
5238let foo = 2;
5239let foo = «2ˇ»;"#,
5240 );
5241}
5242
5243#[gpui::test]
5244async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
5245 init_test(cx, |_| {});
5246
5247 let mut cx = EditorTestContext::new(cx).await;
5248 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5249
5250 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5251 .unwrap();
5252 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5253
5254 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5255 .unwrap();
5256 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5257
5258 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5259 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5260
5261 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5262 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5263
5264 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5265 .unwrap();
5266 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5267
5268 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5269 .unwrap();
5270 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5271}
5272
5273#[gpui::test]
5274async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5275 init_test(cx, |_| {});
5276
5277 let language = Arc::new(Language::new(
5278 LanguageConfig::default(),
5279 Some(tree_sitter_rust::LANGUAGE.into()),
5280 ));
5281
5282 let text = r#"
5283 use mod1::mod2::{mod3, mod4};
5284
5285 fn fn_1(param1: bool, param2: &str) {
5286 let var1 = "text";
5287 }
5288 "#
5289 .unindent();
5290
5291 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5292 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5293 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5294
5295 editor
5296 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5297 .await;
5298
5299 editor.update(cx, |view, cx| {
5300 view.change_selections(None, cx, |s| {
5301 s.select_display_ranges([
5302 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5303 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5304 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5305 ]);
5306 });
5307 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5308 });
5309 editor.update(cx, |editor, cx| {
5310 assert_text_with_selections(
5311 editor,
5312 indoc! {r#"
5313 use mod1::mod2::{mod3, «mod4ˇ»};
5314
5315 fn fn_1«ˇ(param1: bool, param2: &str)» {
5316 let var1 = "«textˇ»";
5317 }
5318 "#},
5319 cx,
5320 );
5321 });
5322
5323 editor.update(cx, |view, cx| {
5324 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5325 });
5326 editor.update(cx, |editor, cx| {
5327 assert_text_with_selections(
5328 editor,
5329 indoc! {r#"
5330 use mod1::mod2::«{mod3, mod4}ˇ»;
5331
5332 «ˇfn fn_1(param1: bool, param2: &str) {
5333 let var1 = "text";
5334 }»
5335 "#},
5336 cx,
5337 );
5338 });
5339
5340 editor.update(cx, |view, cx| {
5341 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5342 });
5343 assert_eq!(
5344 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5345 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5346 );
5347
5348 // Trying to expand the selected syntax node one more time has no effect.
5349 editor.update(cx, |view, cx| {
5350 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5351 });
5352 assert_eq!(
5353 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5354 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5355 );
5356
5357 editor.update(cx, |view, cx| {
5358 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5359 });
5360 editor.update(cx, |editor, cx| {
5361 assert_text_with_selections(
5362 editor,
5363 indoc! {r#"
5364 use mod1::mod2::«{mod3, mod4}ˇ»;
5365
5366 «ˇfn fn_1(param1: bool, param2: &str) {
5367 let var1 = "text";
5368 }»
5369 "#},
5370 cx,
5371 );
5372 });
5373
5374 editor.update(cx, |view, cx| {
5375 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5376 });
5377 editor.update(cx, |editor, cx| {
5378 assert_text_with_selections(
5379 editor,
5380 indoc! {r#"
5381 use mod1::mod2::{mod3, «mod4ˇ»};
5382
5383 fn fn_1«ˇ(param1: bool, param2: &str)» {
5384 let var1 = "«textˇ»";
5385 }
5386 "#},
5387 cx,
5388 );
5389 });
5390
5391 editor.update(cx, |view, cx| {
5392 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5393 });
5394 editor.update(cx, |editor, cx| {
5395 assert_text_with_selections(
5396 editor,
5397 indoc! {r#"
5398 use mod1::mod2::{mod3, mo«ˇ»d4};
5399
5400 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5401 let var1 = "te«ˇ»xt";
5402 }
5403 "#},
5404 cx,
5405 );
5406 });
5407
5408 // Trying to shrink the selected syntax node one more time has no effect.
5409 editor.update(cx, |view, cx| {
5410 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5411 });
5412 editor.update(cx, |editor, cx| {
5413 assert_text_with_selections(
5414 editor,
5415 indoc! {r#"
5416 use mod1::mod2::{mod3, mo«ˇ»d4};
5417
5418 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5419 let var1 = "te«ˇ»xt";
5420 }
5421 "#},
5422 cx,
5423 );
5424 });
5425
5426 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5427 // a fold.
5428 editor.update(cx, |view, cx| {
5429 view.fold_creases(
5430 vec![
5431 Crease::simple(
5432 Point::new(0, 21)..Point::new(0, 24),
5433 FoldPlaceholder::test(),
5434 ),
5435 Crease::simple(
5436 Point::new(3, 20)..Point::new(3, 22),
5437 FoldPlaceholder::test(),
5438 ),
5439 ],
5440 true,
5441 cx,
5442 );
5443 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5444 });
5445 editor.update(cx, |editor, cx| {
5446 assert_text_with_selections(
5447 editor,
5448 indoc! {r#"
5449 use mod1::mod2::«{mod3, mod4}ˇ»;
5450
5451 fn fn_1«ˇ(param1: bool, param2: &str)» {
5452 «let var1 = "text";ˇ»
5453 }
5454 "#},
5455 cx,
5456 );
5457 });
5458}
5459
5460#[gpui::test]
5461async fn test_autoindent(cx: &mut gpui::TestAppContext) {
5462 init_test(cx, |_| {});
5463
5464 let language = Arc::new(
5465 Language::new(
5466 LanguageConfig {
5467 brackets: BracketPairConfig {
5468 pairs: vec![
5469 BracketPair {
5470 start: "{".to_string(),
5471 end: "}".to_string(),
5472 close: false,
5473 surround: false,
5474 newline: true,
5475 },
5476 BracketPair {
5477 start: "(".to_string(),
5478 end: ")".to_string(),
5479 close: false,
5480 surround: false,
5481 newline: true,
5482 },
5483 ],
5484 ..Default::default()
5485 },
5486 ..Default::default()
5487 },
5488 Some(tree_sitter_rust::LANGUAGE.into()),
5489 )
5490 .with_indents_query(
5491 r#"
5492 (_ "(" ")" @end) @indent
5493 (_ "{" "}" @end) @indent
5494 "#,
5495 )
5496 .unwrap(),
5497 );
5498
5499 let text = "fn a() {}";
5500
5501 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5502 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5503 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5504 editor
5505 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5506 .await;
5507
5508 editor.update(cx, |editor, cx| {
5509 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5510 editor.newline(&Newline, cx);
5511 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5512 assert_eq!(
5513 editor.selections.ranges(cx),
5514 &[
5515 Point::new(1, 4)..Point::new(1, 4),
5516 Point::new(3, 4)..Point::new(3, 4),
5517 Point::new(5, 0)..Point::new(5, 0)
5518 ]
5519 );
5520 });
5521}
5522
5523#[gpui::test]
5524async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5525 init_test(cx, |_| {});
5526
5527 {
5528 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5529 cx.set_state(indoc! {"
5530 impl A {
5531
5532 fn b() {}
5533
5534 «fn c() {
5535
5536 }ˇ»
5537 }
5538 "});
5539
5540 cx.update_editor(|editor, cx| {
5541 editor.autoindent(&Default::default(), cx);
5542 });
5543
5544 cx.assert_editor_state(indoc! {"
5545 impl A {
5546
5547 fn b() {}
5548
5549 «fn c() {
5550
5551 }ˇ»
5552 }
5553 "});
5554 }
5555
5556 {
5557 let mut cx = EditorTestContext::new_multibuffer(
5558 cx,
5559 [indoc! { "
5560 impl A {
5561 «
5562 // a
5563 fn b(){}
5564 »
5565 «
5566 }
5567 fn c(){}
5568 »
5569 "}],
5570 );
5571
5572 let buffer = cx.update_editor(|editor, cx| {
5573 let buffer = editor.buffer().update(cx, |buffer, _| {
5574 buffer.all_buffers().iter().next().unwrap().clone()
5575 });
5576 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5577 buffer
5578 });
5579
5580 cx.run_until_parked();
5581 cx.update_editor(|editor, cx| {
5582 editor.select_all(&Default::default(), cx);
5583 editor.autoindent(&Default::default(), cx)
5584 });
5585 cx.run_until_parked();
5586
5587 cx.update(|cx| {
5588 pretty_assertions::assert_eq!(
5589 buffer.read(cx).text(),
5590 indoc! { "
5591 impl A {
5592
5593 // a
5594 fn b(){}
5595
5596
5597 }
5598 fn c(){}
5599
5600 " }
5601 )
5602 });
5603 }
5604}
5605
5606#[gpui::test]
5607async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5608 init_test(cx, |_| {});
5609
5610 let mut cx = EditorTestContext::new(cx).await;
5611
5612 let language = Arc::new(Language::new(
5613 LanguageConfig {
5614 brackets: BracketPairConfig {
5615 pairs: vec![
5616 BracketPair {
5617 start: "{".to_string(),
5618 end: "}".to_string(),
5619 close: true,
5620 surround: true,
5621 newline: true,
5622 },
5623 BracketPair {
5624 start: "(".to_string(),
5625 end: ")".to_string(),
5626 close: true,
5627 surround: true,
5628 newline: true,
5629 },
5630 BracketPair {
5631 start: "/*".to_string(),
5632 end: " */".to_string(),
5633 close: true,
5634 surround: true,
5635 newline: true,
5636 },
5637 BracketPair {
5638 start: "[".to_string(),
5639 end: "]".to_string(),
5640 close: false,
5641 surround: false,
5642 newline: true,
5643 },
5644 BracketPair {
5645 start: "\"".to_string(),
5646 end: "\"".to_string(),
5647 close: true,
5648 surround: true,
5649 newline: false,
5650 },
5651 BracketPair {
5652 start: "<".to_string(),
5653 end: ">".to_string(),
5654 close: false,
5655 surround: true,
5656 newline: true,
5657 },
5658 ],
5659 ..Default::default()
5660 },
5661 autoclose_before: "})]".to_string(),
5662 ..Default::default()
5663 },
5664 Some(tree_sitter_rust::LANGUAGE.into()),
5665 ));
5666
5667 cx.language_registry().add(language.clone());
5668 cx.update_buffer(|buffer, cx| {
5669 buffer.set_language(Some(language), cx);
5670 });
5671
5672 cx.set_state(
5673 &r#"
5674 🏀ˇ
5675 εˇ
5676 ❤️ˇ
5677 "#
5678 .unindent(),
5679 );
5680
5681 // autoclose multiple nested brackets at multiple cursors
5682 cx.update_editor(|view, cx| {
5683 view.handle_input("{", cx);
5684 view.handle_input("{", cx);
5685 view.handle_input("{", cx);
5686 });
5687 cx.assert_editor_state(
5688 &"
5689 🏀{{{ˇ}}}
5690 ε{{{ˇ}}}
5691 ❤️{{{ˇ}}}
5692 "
5693 .unindent(),
5694 );
5695
5696 // insert a different closing bracket
5697 cx.update_editor(|view, cx| {
5698 view.handle_input(")", cx);
5699 });
5700 cx.assert_editor_state(
5701 &"
5702 🏀{{{)ˇ}}}
5703 ε{{{)ˇ}}}
5704 ❤️{{{)ˇ}}}
5705 "
5706 .unindent(),
5707 );
5708
5709 // skip over the auto-closed brackets when typing a closing bracket
5710 cx.update_editor(|view, cx| {
5711 view.move_right(&MoveRight, cx);
5712 view.handle_input("}", cx);
5713 view.handle_input("}", cx);
5714 view.handle_input("}", cx);
5715 });
5716 cx.assert_editor_state(
5717 &"
5718 🏀{{{)}}}}ˇ
5719 ε{{{)}}}}ˇ
5720 ❤️{{{)}}}}ˇ
5721 "
5722 .unindent(),
5723 );
5724
5725 // autoclose multi-character pairs
5726 cx.set_state(
5727 &"
5728 ˇ
5729 ˇ
5730 "
5731 .unindent(),
5732 );
5733 cx.update_editor(|view, cx| {
5734 view.handle_input("/", cx);
5735 view.handle_input("*", cx);
5736 });
5737 cx.assert_editor_state(
5738 &"
5739 /*ˇ */
5740 /*ˇ */
5741 "
5742 .unindent(),
5743 );
5744
5745 // one cursor autocloses a multi-character pair, one cursor
5746 // does not autoclose.
5747 cx.set_state(
5748 &"
5749 /ˇ
5750 ˇ
5751 "
5752 .unindent(),
5753 );
5754 cx.update_editor(|view, cx| view.handle_input("*", cx));
5755 cx.assert_editor_state(
5756 &"
5757 /*ˇ */
5758 *ˇ
5759 "
5760 .unindent(),
5761 );
5762
5763 // Don't autoclose if the next character isn't whitespace and isn't
5764 // listed in the language's "autoclose_before" section.
5765 cx.set_state("ˇa b");
5766 cx.update_editor(|view, cx| view.handle_input("{", cx));
5767 cx.assert_editor_state("{ˇa b");
5768
5769 // Don't autoclose if `close` is false for the bracket pair
5770 cx.set_state("ˇ");
5771 cx.update_editor(|view, cx| view.handle_input("[", cx));
5772 cx.assert_editor_state("[ˇ");
5773
5774 // Surround with brackets if text is selected
5775 cx.set_state("«aˇ» b");
5776 cx.update_editor(|view, cx| view.handle_input("{", cx));
5777 cx.assert_editor_state("{«aˇ»} b");
5778
5779 // Autclose pair where the start and end characters are the same
5780 cx.set_state("aˇ");
5781 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5782 cx.assert_editor_state("a\"ˇ\"");
5783 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5784 cx.assert_editor_state("a\"\"ˇ");
5785
5786 // Don't autoclose pair if autoclose is disabled
5787 cx.set_state("ˇ");
5788 cx.update_editor(|view, cx| view.handle_input("<", cx));
5789 cx.assert_editor_state("<ˇ");
5790
5791 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5792 cx.set_state("«aˇ» b");
5793 cx.update_editor(|view, cx| view.handle_input("<", cx));
5794 cx.assert_editor_state("<«aˇ»> b");
5795}
5796
5797#[gpui::test]
5798async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5799 init_test(cx, |settings| {
5800 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5801 });
5802
5803 let mut cx = EditorTestContext::new(cx).await;
5804
5805 let language = Arc::new(Language::new(
5806 LanguageConfig {
5807 brackets: BracketPairConfig {
5808 pairs: vec![
5809 BracketPair {
5810 start: "{".to_string(),
5811 end: "}".to_string(),
5812 close: true,
5813 surround: true,
5814 newline: true,
5815 },
5816 BracketPair {
5817 start: "(".to_string(),
5818 end: ")".to_string(),
5819 close: true,
5820 surround: true,
5821 newline: true,
5822 },
5823 BracketPair {
5824 start: "[".to_string(),
5825 end: "]".to_string(),
5826 close: false,
5827 surround: false,
5828 newline: true,
5829 },
5830 ],
5831 ..Default::default()
5832 },
5833 autoclose_before: "})]".to_string(),
5834 ..Default::default()
5835 },
5836 Some(tree_sitter_rust::LANGUAGE.into()),
5837 ));
5838
5839 cx.language_registry().add(language.clone());
5840 cx.update_buffer(|buffer, cx| {
5841 buffer.set_language(Some(language), cx);
5842 });
5843
5844 cx.set_state(
5845 &"
5846 ˇ
5847 ˇ
5848 ˇ
5849 "
5850 .unindent(),
5851 );
5852
5853 // ensure only matching closing brackets are skipped over
5854 cx.update_editor(|view, cx| {
5855 view.handle_input("}", cx);
5856 view.move_left(&MoveLeft, cx);
5857 view.handle_input(")", cx);
5858 view.move_left(&MoveLeft, cx);
5859 });
5860 cx.assert_editor_state(
5861 &"
5862 ˇ)}
5863 ˇ)}
5864 ˇ)}
5865 "
5866 .unindent(),
5867 );
5868
5869 // skip-over closing brackets at multiple cursors
5870 cx.update_editor(|view, cx| {
5871 view.handle_input(")", cx);
5872 view.handle_input("}", cx);
5873 });
5874 cx.assert_editor_state(
5875 &"
5876 )}ˇ
5877 )}ˇ
5878 )}ˇ
5879 "
5880 .unindent(),
5881 );
5882
5883 // ignore non-close brackets
5884 cx.update_editor(|view, cx| {
5885 view.handle_input("]", cx);
5886 view.move_left(&MoveLeft, cx);
5887 view.handle_input("]", cx);
5888 });
5889 cx.assert_editor_state(
5890 &"
5891 )}]ˇ]
5892 )}]ˇ]
5893 )}]ˇ]
5894 "
5895 .unindent(),
5896 );
5897}
5898
5899#[gpui::test]
5900async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5901 init_test(cx, |_| {});
5902
5903 let mut cx = EditorTestContext::new(cx).await;
5904
5905 let html_language = Arc::new(
5906 Language::new(
5907 LanguageConfig {
5908 name: "HTML".into(),
5909 brackets: BracketPairConfig {
5910 pairs: vec![
5911 BracketPair {
5912 start: "<".into(),
5913 end: ">".into(),
5914 close: true,
5915 ..Default::default()
5916 },
5917 BracketPair {
5918 start: "{".into(),
5919 end: "}".into(),
5920 close: true,
5921 ..Default::default()
5922 },
5923 BracketPair {
5924 start: "(".into(),
5925 end: ")".into(),
5926 close: true,
5927 ..Default::default()
5928 },
5929 ],
5930 ..Default::default()
5931 },
5932 autoclose_before: "})]>".into(),
5933 ..Default::default()
5934 },
5935 Some(tree_sitter_html::language()),
5936 )
5937 .with_injection_query(
5938 r#"
5939 (script_element
5940 (raw_text) @content
5941 (#set! "language" "javascript"))
5942 "#,
5943 )
5944 .unwrap(),
5945 );
5946
5947 let javascript_language = Arc::new(Language::new(
5948 LanguageConfig {
5949 name: "JavaScript".into(),
5950 brackets: BracketPairConfig {
5951 pairs: vec![
5952 BracketPair {
5953 start: "/*".into(),
5954 end: " */".into(),
5955 close: true,
5956 ..Default::default()
5957 },
5958 BracketPair {
5959 start: "{".into(),
5960 end: "}".into(),
5961 close: true,
5962 ..Default::default()
5963 },
5964 BracketPair {
5965 start: "(".into(),
5966 end: ")".into(),
5967 close: true,
5968 ..Default::default()
5969 },
5970 ],
5971 ..Default::default()
5972 },
5973 autoclose_before: "})]>".into(),
5974 ..Default::default()
5975 },
5976 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
5977 ));
5978
5979 cx.language_registry().add(html_language.clone());
5980 cx.language_registry().add(javascript_language.clone());
5981
5982 cx.update_buffer(|buffer, cx| {
5983 buffer.set_language(Some(html_language), cx);
5984 });
5985
5986 cx.set_state(
5987 &r#"
5988 <body>ˇ
5989 <script>
5990 var x = 1;ˇ
5991 </script>
5992 </body>ˇ
5993 "#
5994 .unindent(),
5995 );
5996
5997 // Precondition: different languages are active at different locations.
5998 cx.update_editor(|editor, cx| {
5999 let snapshot = editor.snapshot(cx);
6000 let cursors = editor.selections.ranges::<usize>(cx);
6001 let languages = cursors
6002 .iter()
6003 .map(|c| snapshot.language_at(c.start).unwrap().name())
6004 .collect::<Vec<_>>();
6005 assert_eq!(
6006 languages,
6007 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6008 );
6009 });
6010
6011 // Angle brackets autoclose in HTML, but not JavaScript.
6012 cx.update_editor(|editor, cx| {
6013 editor.handle_input("<", cx);
6014 editor.handle_input("a", cx);
6015 });
6016 cx.assert_editor_state(
6017 &r#"
6018 <body><aˇ>
6019 <script>
6020 var x = 1;<aˇ
6021 </script>
6022 </body><aˇ>
6023 "#
6024 .unindent(),
6025 );
6026
6027 // Curly braces and parens autoclose in both HTML and JavaScript.
6028 cx.update_editor(|editor, cx| {
6029 editor.handle_input(" b=", cx);
6030 editor.handle_input("{", cx);
6031 editor.handle_input("c", cx);
6032 editor.handle_input("(", cx);
6033 });
6034 cx.assert_editor_state(
6035 &r#"
6036 <body><a b={c(ˇ)}>
6037 <script>
6038 var x = 1;<a b={c(ˇ)}
6039 </script>
6040 </body><a b={c(ˇ)}>
6041 "#
6042 .unindent(),
6043 );
6044
6045 // Brackets that were already autoclosed are skipped.
6046 cx.update_editor(|editor, cx| {
6047 editor.handle_input(")", cx);
6048 editor.handle_input("d", cx);
6049 editor.handle_input("}", cx);
6050 });
6051 cx.assert_editor_state(
6052 &r#"
6053 <body><a b={c()d}ˇ>
6054 <script>
6055 var x = 1;<a b={c()d}ˇ
6056 </script>
6057 </body><a b={c()d}ˇ>
6058 "#
6059 .unindent(),
6060 );
6061 cx.update_editor(|editor, cx| {
6062 editor.handle_input(">", cx);
6063 });
6064 cx.assert_editor_state(
6065 &r#"
6066 <body><a b={c()d}>ˇ
6067 <script>
6068 var x = 1;<a b={c()d}>ˇ
6069 </script>
6070 </body><a b={c()d}>ˇ
6071 "#
6072 .unindent(),
6073 );
6074
6075 // Reset
6076 cx.set_state(
6077 &r#"
6078 <body>ˇ
6079 <script>
6080 var x = 1;ˇ
6081 </script>
6082 </body>ˇ
6083 "#
6084 .unindent(),
6085 );
6086
6087 cx.update_editor(|editor, cx| {
6088 editor.handle_input("<", cx);
6089 });
6090 cx.assert_editor_state(
6091 &r#"
6092 <body><ˇ>
6093 <script>
6094 var x = 1;<ˇ
6095 </script>
6096 </body><ˇ>
6097 "#
6098 .unindent(),
6099 );
6100
6101 // When backspacing, the closing angle brackets are removed.
6102 cx.update_editor(|editor, cx| {
6103 editor.backspace(&Backspace, cx);
6104 });
6105 cx.assert_editor_state(
6106 &r#"
6107 <body>ˇ
6108 <script>
6109 var x = 1;ˇ
6110 </script>
6111 </body>ˇ
6112 "#
6113 .unindent(),
6114 );
6115
6116 // Block comments autoclose in JavaScript, but not HTML.
6117 cx.update_editor(|editor, cx| {
6118 editor.handle_input("/", cx);
6119 editor.handle_input("*", cx);
6120 });
6121 cx.assert_editor_state(
6122 &r#"
6123 <body>/*ˇ
6124 <script>
6125 var x = 1;/*ˇ */
6126 </script>
6127 </body>/*ˇ
6128 "#
6129 .unindent(),
6130 );
6131}
6132
6133#[gpui::test]
6134async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
6135 init_test(cx, |_| {});
6136
6137 let mut cx = EditorTestContext::new(cx).await;
6138
6139 let rust_language = Arc::new(
6140 Language::new(
6141 LanguageConfig {
6142 name: "Rust".into(),
6143 brackets: serde_json::from_value(json!([
6144 { "start": "{", "end": "}", "close": true, "newline": true },
6145 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6146 ]))
6147 .unwrap(),
6148 autoclose_before: "})]>".into(),
6149 ..Default::default()
6150 },
6151 Some(tree_sitter_rust::LANGUAGE.into()),
6152 )
6153 .with_override_query("(string_literal) @string")
6154 .unwrap(),
6155 );
6156
6157 cx.language_registry().add(rust_language.clone());
6158 cx.update_buffer(|buffer, cx| {
6159 buffer.set_language(Some(rust_language), cx);
6160 });
6161
6162 cx.set_state(
6163 &r#"
6164 let x = ˇ
6165 "#
6166 .unindent(),
6167 );
6168
6169 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6170 cx.update_editor(|editor, cx| {
6171 editor.handle_input("\"", cx);
6172 });
6173 cx.assert_editor_state(
6174 &r#"
6175 let x = "ˇ"
6176 "#
6177 .unindent(),
6178 );
6179
6180 // Inserting another quotation mark. The cursor moves across the existing
6181 // automatically-inserted quotation mark.
6182 cx.update_editor(|editor, cx| {
6183 editor.handle_input("\"", cx);
6184 });
6185 cx.assert_editor_state(
6186 &r#"
6187 let x = ""ˇ
6188 "#
6189 .unindent(),
6190 );
6191
6192 // Reset
6193 cx.set_state(
6194 &r#"
6195 let x = ˇ
6196 "#
6197 .unindent(),
6198 );
6199
6200 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6201 cx.update_editor(|editor, cx| {
6202 editor.handle_input("\"", cx);
6203 editor.handle_input(" ", cx);
6204 editor.move_left(&Default::default(), cx);
6205 editor.handle_input("\\", cx);
6206 editor.handle_input("\"", cx);
6207 });
6208 cx.assert_editor_state(
6209 &r#"
6210 let x = "\"ˇ "
6211 "#
6212 .unindent(),
6213 );
6214
6215 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6216 // mark. Nothing is inserted.
6217 cx.update_editor(|editor, cx| {
6218 editor.move_right(&Default::default(), cx);
6219 editor.handle_input("\"", cx);
6220 });
6221 cx.assert_editor_state(
6222 &r#"
6223 let x = "\" "ˇ
6224 "#
6225 .unindent(),
6226 );
6227}
6228
6229#[gpui::test]
6230async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
6231 init_test(cx, |_| {});
6232
6233 let language = Arc::new(Language::new(
6234 LanguageConfig {
6235 brackets: BracketPairConfig {
6236 pairs: vec![
6237 BracketPair {
6238 start: "{".to_string(),
6239 end: "}".to_string(),
6240 close: true,
6241 surround: true,
6242 newline: true,
6243 },
6244 BracketPair {
6245 start: "/* ".to_string(),
6246 end: "*/".to_string(),
6247 close: true,
6248 surround: true,
6249 ..Default::default()
6250 },
6251 ],
6252 ..Default::default()
6253 },
6254 ..Default::default()
6255 },
6256 Some(tree_sitter_rust::LANGUAGE.into()),
6257 ));
6258
6259 let text = r#"
6260 a
6261 b
6262 c
6263 "#
6264 .unindent();
6265
6266 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6267 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6268 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6269 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6270 .await;
6271
6272 view.update(cx, |view, cx| {
6273 view.change_selections(None, cx, |s| {
6274 s.select_display_ranges([
6275 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6276 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6277 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6278 ])
6279 });
6280
6281 view.handle_input("{", cx);
6282 view.handle_input("{", cx);
6283 view.handle_input("{", cx);
6284 assert_eq!(
6285 view.text(cx),
6286 "
6287 {{{a}}}
6288 {{{b}}}
6289 {{{c}}}
6290 "
6291 .unindent()
6292 );
6293 assert_eq!(
6294 view.selections.display_ranges(cx),
6295 [
6296 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6297 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6298 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6299 ]
6300 );
6301
6302 view.undo(&Undo, cx);
6303 view.undo(&Undo, cx);
6304 view.undo(&Undo, cx);
6305 assert_eq!(
6306 view.text(cx),
6307 "
6308 a
6309 b
6310 c
6311 "
6312 .unindent()
6313 );
6314 assert_eq!(
6315 view.selections.display_ranges(cx),
6316 [
6317 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6318 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6319 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6320 ]
6321 );
6322
6323 // Ensure inserting the first character of a multi-byte bracket pair
6324 // doesn't surround the selections with the bracket.
6325 view.handle_input("/", cx);
6326 assert_eq!(
6327 view.text(cx),
6328 "
6329 /
6330 /
6331 /
6332 "
6333 .unindent()
6334 );
6335 assert_eq!(
6336 view.selections.display_ranges(cx),
6337 [
6338 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6339 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6340 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6341 ]
6342 );
6343
6344 view.undo(&Undo, cx);
6345 assert_eq!(
6346 view.text(cx),
6347 "
6348 a
6349 b
6350 c
6351 "
6352 .unindent()
6353 );
6354 assert_eq!(
6355 view.selections.display_ranges(cx),
6356 [
6357 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6358 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6359 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6360 ]
6361 );
6362
6363 // Ensure inserting the last character of a multi-byte bracket pair
6364 // doesn't surround the selections with the bracket.
6365 view.handle_input("*", cx);
6366 assert_eq!(
6367 view.text(cx),
6368 "
6369 *
6370 *
6371 *
6372 "
6373 .unindent()
6374 );
6375 assert_eq!(
6376 view.selections.display_ranges(cx),
6377 [
6378 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6379 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6380 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6381 ]
6382 );
6383 });
6384}
6385
6386#[gpui::test]
6387async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6388 init_test(cx, |_| {});
6389
6390 let language = Arc::new(Language::new(
6391 LanguageConfig {
6392 brackets: BracketPairConfig {
6393 pairs: vec![BracketPair {
6394 start: "{".to_string(),
6395 end: "}".to_string(),
6396 close: true,
6397 surround: true,
6398 newline: true,
6399 }],
6400 ..Default::default()
6401 },
6402 autoclose_before: "}".to_string(),
6403 ..Default::default()
6404 },
6405 Some(tree_sitter_rust::LANGUAGE.into()),
6406 ));
6407
6408 let text = r#"
6409 a
6410 b
6411 c
6412 "#
6413 .unindent();
6414
6415 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6416 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6417 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6418 editor
6419 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6420 .await;
6421
6422 editor.update(cx, |editor, cx| {
6423 editor.change_selections(None, cx, |s| {
6424 s.select_ranges([
6425 Point::new(0, 1)..Point::new(0, 1),
6426 Point::new(1, 1)..Point::new(1, 1),
6427 Point::new(2, 1)..Point::new(2, 1),
6428 ])
6429 });
6430
6431 editor.handle_input("{", cx);
6432 editor.handle_input("{", cx);
6433 editor.handle_input("_", cx);
6434 assert_eq!(
6435 editor.text(cx),
6436 "
6437 a{{_}}
6438 b{{_}}
6439 c{{_}}
6440 "
6441 .unindent()
6442 );
6443 assert_eq!(
6444 editor.selections.ranges::<Point>(cx),
6445 [
6446 Point::new(0, 4)..Point::new(0, 4),
6447 Point::new(1, 4)..Point::new(1, 4),
6448 Point::new(2, 4)..Point::new(2, 4)
6449 ]
6450 );
6451
6452 editor.backspace(&Default::default(), cx);
6453 editor.backspace(&Default::default(), cx);
6454 assert_eq!(
6455 editor.text(cx),
6456 "
6457 a{}
6458 b{}
6459 c{}
6460 "
6461 .unindent()
6462 );
6463 assert_eq!(
6464 editor.selections.ranges::<Point>(cx),
6465 [
6466 Point::new(0, 2)..Point::new(0, 2),
6467 Point::new(1, 2)..Point::new(1, 2),
6468 Point::new(2, 2)..Point::new(2, 2)
6469 ]
6470 );
6471
6472 editor.delete_to_previous_word_start(&Default::default(), cx);
6473 assert_eq!(
6474 editor.text(cx),
6475 "
6476 a
6477 b
6478 c
6479 "
6480 .unindent()
6481 );
6482 assert_eq!(
6483 editor.selections.ranges::<Point>(cx),
6484 [
6485 Point::new(0, 1)..Point::new(0, 1),
6486 Point::new(1, 1)..Point::new(1, 1),
6487 Point::new(2, 1)..Point::new(2, 1)
6488 ]
6489 );
6490 });
6491}
6492
6493#[gpui::test]
6494async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6495 init_test(cx, |settings| {
6496 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6497 });
6498
6499 let mut cx = EditorTestContext::new(cx).await;
6500
6501 let language = Arc::new(Language::new(
6502 LanguageConfig {
6503 brackets: BracketPairConfig {
6504 pairs: vec![
6505 BracketPair {
6506 start: "{".to_string(),
6507 end: "}".to_string(),
6508 close: true,
6509 surround: true,
6510 newline: true,
6511 },
6512 BracketPair {
6513 start: "(".to_string(),
6514 end: ")".to_string(),
6515 close: true,
6516 surround: true,
6517 newline: true,
6518 },
6519 BracketPair {
6520 start: "[".to_string(),
6521 end: "]".to_string(),
6522 close: false,
6523 surround: true,
6524 newline: true,
6525 },
6526 ],
6527 ..Default::default()
6528 },
6529 autoclose_before: "})]".to_string(),
6530 ..Default::default()
6531 },
6532 Some(tree_sitter_rust::LANGUAGE.into()),
6533 ));
6534
6535 cx.language_registry().add(language.clone());
6536 cx.update_buffer(|buffer, cx| {
6537 buffer.set_language(Some(language), cx);
6538 });
6539
6540 cx.set_state(
6541 &"
6542 {(ˇ)}
6543 [[ˇ]]
6544 {(ˇ)}
6545 "
6546 .unindent(),
6547 );
6548
6549 cx.update_editor(|view, cx| {
6550 view.backspace(&Default::default(), cx);
6551 view.backspace(&Default::default(), cx);
6552 });
6553
6554 cx.assert_editor_state(
6555 &"
6556 ˇ
6557 ˇ]]
6558 ˇ
6559 "
6560 .unindent(),
6561 );
6562
6563 cx.update_editor(|view, cx| {
6564 view.handle_input("{", cx);
6565 view.handle_input("{", cx);
6566 view.move_right(&MoveRight, cx);
6567 view.move_right(&MoveRight, cx);
6568 view.move_left(&MoveLeft, cx);
6569 view.move_left(&MoveLeft, cx);
6570 view.backspace(&Default::default(), cx);
6571 });
6572
6573 cx.assert_editor_state(
6574 &"
6575 {ˇ}
6576 {ˇ}]]
6577 {ˇ}
6578 "
6579 .unindent(),
6580 );
6581
6582 cx.update_editor(|view, cx| {
6583 view.backspace(&Default::default(), cx);
6584 });
6585
6586 cx.assert_editor_state(
6587 &"
6588 ˇ
6589 ˇ]]
6590 ˇ
6591 "
6592 .unindent(),
6593 );
6594}
6595
6596#[gpui::test]
6597async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6598 init_test(cx, |_| {});
6599
6600 let language = Arc::new(Language::new(
6601 LanguageConfig::default(),
6602 Some(tree_sitter_rust::LANGUAGE.into()),
6603 ));
6604
6605 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6606 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6607 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6608 editor
6609 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6610 .await;
6611
6612 editor.update(cx, |editor, cx| {
6613 editor.set_auto_replace_emoji_shortcode(true);
6614
6615 editor.handle_input("Hello ", cx);
6616 editor.handle_input(":wave", cx);
6617 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6618
6619 editor.handle_input(":", cx);
6620 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6621
6622 editor.handle_input(" :smile", cx);
6623 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6624
6625 editor.handle_input(":", cx);
6626 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6627
6628 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6629 editor.handle_input(":wave", cx);
6630 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6631
6632 editor.handle_input(":", cx);
6633 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6634
6635 editor.handle_input(":1", cx);
6636 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6637
6638 editor.handle_input(":", cx);
6639 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6640
6641 // Ensure shortcode does not get replaced when it is part of a word
6642 editor.handle_input(" Test:wave", cx);
6643 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6644
6645 editor.handle_input(":", cx);
6646 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6647
6648 editor.set_auto_replace_emoji_shortcode(false);
6649
6650 // Ensure shortcode does not get replaced when auto replace is off
6651 editor.handle_input(" :wave", cx);
6652 assert_eq!(
6653 editor.text(cx),
6654 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6655 );
6656
6657 editor.handle_input(":", cx);
6658 assert_eq!(
6659 editor.text(cx),
6660 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6661 );
6662 });
6663}
6664
6665#[gpui::test]
6666async fn test_snippet_placeholder_choices(cx: &mut gpui::TestAppContext) {
6667 init_test(cx, |_| {});
6668
6669 let (text, insertion_ranges) = marked_text_ranges(
6670 indoc! {"
6671 ˇ
6672 "},
6673 false,
6674 );
6675
6676 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6677 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6678
6679 _ = editor.update(cx, |editor, cx| {
6680 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
6681
6682 editor
6683 .insert_snippet(&insertion_ranges, snippet, cx)
6684 .unwrap();
6685
6686 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6687 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6688 assert_eq!(editor.text(cx), expected_text);
6689 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6690 }
6691
6692 assert(
6693 editor,
6694 cx,
6695 indoc! {"
6696 type «» =•
6697 "},
6698 );
6699
6700 assert!(editor.context_menu_visible(), "There should be a matches");
6701 });
6702}
6703
6704#[gpui::test]
6705async fn test_snippets(cx: &mut gpui::TestAppContext) {
6706 init_test(cx, |_| {});
6707
6708 let (text, insertion_ranges) = marked_text_ranges(
6709 indoc! {"
6710 a.ˇ b
6711 a.ˇ b
6712 a.ˇ b
6713 "},
6714 false,
6715 );
6716
6717 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6718 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6719
6720 editor.update(cx, |editor, cx| {
6721 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6722
6723 editor
6724 .insert_snippet(&insertion_ranges, snippet, cx)
6725 .unwrap();
6726
6727 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6728 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6729 assert_eq!(editor.text(cx), expected_text);
6730 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6731 }
6732
6733 assert(
6734 editor,
6735 cx,
6736 indoc! {"
6737 a.f(«one», two, «three») b
6738 a.f(«one», two, «three») b
6739 a.f(«one», two, «three») b
6740 "},
6741 );
6742
6743 // Can't move earlier than the first tab stop
6744 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6745 assert(
6746 editor,
6747 cx,
6748 indoc! {"
6749 a.f(«one», two, «three») b
6750 a.f(«one», two, «three») b
6751 a.f(«one», two, «three») b
6752 "},
6753 );
6754
6755 assert!(editor.move_to_next_snippet_tabstop(cx));
6756 assert(
6757 editor,
6758 cx,
6759 indoc! {"
6760 a.f(one, «two», three) b
6761 a.f(one, «two», three) b
6762 a.f(one, «two», three) b
6763 "},
6764 );
6765
6766 editor.move_to_prev_snippet_tabstop(cx);
6767 assert(
6768 editor,
6769 cx,
6770 indoc! {"
6771 a.f(«one», two, «three») b
6772 a.f(«one», two, «three») b
6773 a.f(«one», two, «three») b
6774 "},
6775 );
6776
6777 assert!(editor.move_to_next_snippet_tabstop(cx));
6778 assert(
6779 editor,
6780 cx,
6781 indoc! {"
6782 a.f(one, «two», three) b
6783 a.f(one, «two», three) b
6784 a.f(one, «two», three) b
6785 "},
6786 );
6787 assert!(editor.move_to_next_snippet_tabstop(cx));
6788 assert(
6789 editor,
6790 cx,
6791 indoc! {"
6792 a.f(one, two, three)ˇ b
6793 a.f(one, two, three)ˇ b
6794 a.f(one, two, three)ˇ b
6795 "},
6796 );
6797
6798 // As soon as the last tab stop is reached, snippet state is gone
6799 editor.move_to_prev_snippet_tabstop(cx);
6800 assert(
6801 editor,
6802 cx,
6803 indoc! {"
6804 a.f(one, two, three)ˇ b
6805 a.f(one, two, three)ˇ b
6806 a.f(one, two, three)ˇ b
6807 "},
6808 );
6809 });
6810}
6811
6812#[gpui::test]
6813async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6814 init_test(cx, |_| {});
6815
6816 let fs = FakeFs::new(cx.executor());
6817 fs.insert_file("/file.rs", Default::default()).await;
6818
6819 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6820
6821 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6822 language_registry.add(rust_lang());
6823 let mut fake_servers = language_registry.register_fake_lsp(
6824 "Rust",
6825 FakeLspAdapter {
6826 capabilities: lsp::ServerCapabilities {
6827 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6828 ..Default::default()
6829 },
6830 ..Default::default()
6831 },
6832 );
6833
6834 let buffer = project
6835 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6836 .await
6837 .unwrap();
6838
6839 cx.executor().start_waiting();
6840 let fake_server = fake_servers.next().await.unwrap();
6841
6842 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6843 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6844 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6845 assert!(cx.read(|cx| editor.is_dirty(cx)));
6846
6847 let save = editor
6848 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6849 .unwrap();
6850 fake_server
6851 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6852 assert_eq!(
6853 params.text_document.uri,
6854 lsp::Url::from_file_path("/file.rs").unwrap()
6855 );
6856 assert_eq!(params.options.tab_size, 4);
6857 Ok(Some(vec![lsp::TextEdit::new(
6858 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6859 ", ".to_string(),
6860 )]))
6861 })
6862 .next()
6863 .await;
6864 cx.executor().start_waiting();
6865 save.await;
6866
6867 assert_eq!(
6868 editor.update(cx, |editor, cx| editor.text(cx)),
6869 "one, two\nthree\n"
6870 );
6871 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6872
6873 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6874 assert!(cx.read(|cx| editor.is_dirty(cx)));
6875
6876 // Ensure we can still save even if formatting hangs.
6877 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6878 assert_eq!(
6879 params.text_document.uri,
6880 lsp::Url::from_file_path("/file.rs").unwrap()
6881 );
6882 futures::future::pending::<()>().await;
6883 unreachable!()
6884 });
6885 let save = editor
6886 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6887 .unwrap();
6888 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6889 cx.executor().start_waiting();
6890 save.await;
6891 assert_eq!(
6892 editor.update(cx, |editor, cx| editor.text(cx)),
6893 "one\ntwo\nthree\n"
6894 );
6895 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6896
6897 // For non-dirty buffer, no formatting request should be sent
6898 let save = editor
6899 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6900 .unwrap();
6901 let _pending_format_request = fake_server
6902 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6903 panic!("Should not be invoked on non-dirty buffer");
6904 })
6905 .next();
6906 cx.executor().start_waiting();
6907 save.await;
6908
6909 // Set rust language override and assert overridden tabsize is sent to language server
6910 update_test_language_settings(cx, |settings| {
6911 settings.languages.insert(
6912 "Rust".into(),
6913 LanguageSettingsContent {
6914 tab_size: NonZeroU32::new(8),
6915 ..Default::default()
6916 },
6917 );
6918 });
6919
6920 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6921 assert!(cx.read(|cx| editor.is_dirty(cx)));
6922 let save = editor
6923 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6924 .unwrap();
6925 fake_server
6926 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6927 assert_eq!(
6928 params.text_document.uri,
6929 lsp::Url::from_file_path("/file.rs").unwrap()
6930 );
6931 assert_eq!(params.options.tab_size, 8);
6932 Ok(Some(vec![]))
6933 })
6934 .next()
6935 .await;
6936 cx.executor().start_waiting();
6937 save.await;
6938}
6939
6940#[gpui::test]
6941async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6942 init_test(cx, |_| {});
6943
6944 let cols = 4;
6945 let rows = 10;
6946 let sample_text_1 = sample_text(rows, cols, 'a');
6947 assert_eq!(
6948 sample_text_1,
6949 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6950 );
6951 let sample_text_2 = sample_text(rows, cols, 'l');
6952 assert_eq!(
6953 sample_text_2,
6954 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6955 );
6956 let sample_text_3 = sample_text(rows, cols, 'v');
6957 assert_eq!(
6958 sample_text_3,
6959 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6960 );
6961
6962 let fs = FakeFs::new(cx.executor());
6963 fs.insert_tree(
6964 "/a",
6965 json!({
6966 "main.rs": sample_text_1,
6967 "other.rs": sample_text_2,
6968 "lib.rs": sample_text_3,
6969 }),
6970 )
6971 .await;
6972
6973 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6974 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6975 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6976
6977 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6978 language_registry.add(rust_lang());
6979 let mut fake_servers = language_registry.register_fake_lsp(
6980 "Rust",
6981 FakeLspAdapter {
6982 capabilities: lsp::ServerCapabilities {
6983 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6984 ..Default::default()
6985 },
6986 ..Default::default()
6987 },
6988 );
6989
6990 let worktree = project.update(cx, |project, cx| {
6991 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
6992 assert_eq!(worktrees.len(), 1);
6993 worktrees.pop().unwrap()
6994 });
6995 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6996
6997 let buffer_1 = project
6998 .update(cx, |project, cx| {
6999 project.open_buffer((worktree_id, "main.rs"), cx)
7000 })
7001 .await
7002 .unwrap();
7003 let buffer_2 = project
7004 .update(cx, |project, cx| {
7005 project.open_buffer((worktree_id, "other.rs"), cx)
7006 })
7007 .await
7008 .unwrap();
7009 let buffer_3 = project
7010 .update(cx, |project, cx| {
7011 project.open_buffer((worktree_id, "lib.rs"), cx)
7012 })
7013 .await
7014 .unwrap();
7015
7016 let multi_buffer = cx.new_model(|cx| {
7017 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7018 multi_buffer.push_excerpts(
7019 buffer_1.clone(),
7020 [
7021 ExcerptRange {
7022 context: Point::new(0, 0)..Point::new(3, 0),
7023 primary: None,
7024 },
7025 ExcerptRange {
7026 context: Point::new(5, 0)..Point::new(7, 0),
7027 primary: None,
7028 },
7029 ExcerptRange {
7030 context: Point::new(9, 0)..Point::new(10, 4),
7031 primary: None,
7032 },
7033 ],
7034 cx,
7035 );
7036 multi_buffer.push_excerpts(
7037 buffer_2.clone(),
7038 [
7039 ExcerptRange {
7040 context: Point::new(0, 0)..Point::new(3, 0),
7041 primary: None,
7042 },
7043 ExcerptRange {
7044 context: Point::new(5, 0)..Point::new(7, 0),
7045 primary: None,
7046 },
7047 ExcerptRange {
7048 context: Point::new(9, 0)..Point::new(10, 4),
7049 primary: None,
7050 },
7051 ],
7052 cx,
7053 );
7054 multi_buffer.push_excerpts(
7055 buffer_3.clone(),
7056 [
7057 ExcerptRange {
7058 context: Point::new(0, 0)..Point::new(3, 0),
7059 primary: None,
7060 },
7061 ExcerptRange {
7062 context: Point::new(5, 0)..Point::new(7, 0),
7063 primary: None,
7064 },
7065 ExcerptRange {
7066 context: Point::new(9, 0)..Point::new(10, 4),
7067 primary: None,
7068 },
7069 ],
7070 cx,
7071 );
7072 multi_buffer
7073 });
7074 let multi_buffer_editor = cx.new_view(|cx| {
7075 Editor::new(
7076 EditorMode::Full,
7077 multi_buffer,
7078 Some(project.clone()),
7079 true,
7080 cx,
7081 )
7082 });
7083
7084 multi_buffer_editor.update(cx, |editor, cx| {
7085 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
7086 editor.insert("|one|two|three|", cx);
7087 });
7088 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7089 multi_buffer_editor.update(cx, |editor, cx| {
7090 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
7091 s.select_ranges(Some(60..70))
7092 });
7093 editor.insert("|four|five|six|", cx);
7094 });
7095 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7096
7097 // First two buffers should be edited, but not the third one.
7098 assert_eq!(
7099 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7100 "a|one|two|three|aa\nbbbb\ncccc\n\nffff\ngggg\n\njjjj\nllll\nmmmm\nnnnn|four|five|six|\nr\n\nuuuu\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
7101 );
7102 buffer_1.update(cx, |buffer, _| {
7103 assert!(buffer.is_dirty());
7104 assert_eq!(
7105 buffer.text(),
7106 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7107 )
7108 });
7109 buffer_2.update(cx, |buffer, _| {
7110 assert!(buffer.is_dirty());
7111 assert_eq!(
7112 buffer.text(),
7113 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7114 )
7115 });
7116 buffer_3.update(cx, |buffer, _| {
7117 assert!(!buffer.is_dirty());
7118 assert_eq!(buffer.text(), sample_text_3,)
7119 });
7120
7121 cx.executor().start_waiting();
7122 let save = multi_buffer_editor
7123 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7124 .unwrap();
7125
7126 let fake_server = fake_servers.next().await.unwrap();
7127 fake_server
7128 .server
7129 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7130 Ok(Some(vec![lsp::TextEdit::new(
7131 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7132 format!("[{} formatted]", params.text_document.uri),
7133 )]))
7134 })
7135 .detach();
7136 save.await;
7137
7138 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7139 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7140 assert_eq!(
7141 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7142 "a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
7143 );
7144 buffer_1.update(cx, |buffer, _| {
7145 assert!(!buffer.is_dirty());
7146 assert_eq!(
7147 buffer.text(),
7148 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
7149 )
7150 });
7151 buffer_2.update(cx, |buffer, _| {
7152 assert!(!buffer.is_dirty());
7153 assert_eq!(
7154 buffer.text(),
7155 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
7156 )
7157 });
7158 buffer_3.update(cx, |buffer, _| {
7159 assert!(!buffer.is_dirty());
7160 assert_eq!(buffer.text(), sample_text_3,)
7161 });
7162}
7163
7164#[gpui::test]
7165async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
7166 init_test(cx, |_| {});
7167
7168 let fs = FakeFs::new(cx.executor());
7169 fs.insert_file("/file.rs", Default::default()).await;
7170
7171 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7172
7173 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7174 language_registry.add(rust_lang());
7175 let mut fake_servers = language_registry.register_fake_lsp(
7176 "Rust",
7177 FakeLspAdapter {
7178 capabilities: lsp::ServerCapabilities {
7179 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7180 ..Default::default()
7181 },
7182 ..Default::default()
7183 },
7184 );
7185
7186 let buffer = project
7187 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7188 .await
7189 .unwrap();
7190
7191 cx.executor().start_waiting();
7192 let fake_server = fake_servers.next().await.unwrap();
7193
7194 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7195 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7196 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7197 assert!(cx.read(|cx| editor.is_dirty(cx)));
7198
7199 let save = editor
7200 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7201 .unwrap();
7202 fake_server
7203 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7204 assert_eq!(
7205 params.text_document.uri,
7206 lsp::Url::from_file_path("/file.rs").unwrap()
7207 );
7208 assert_eq!(params.options.tab_size, 4);
7209 Ok(Some(vec![lsp::TextEdit::new(
7210 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7211 ", ".to_string(),
7212 )]))
7213 })
7214 .next()
7215 .await;
7216 cx.executor().start_waiting();
7217 save.await;
7218 assert_eq!(
7219 editor.update(cx, |editor, cx| editor.text(cx)),
7220 "one, two\nthree\n"
7221 );
7222 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7223
7224 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7225 assert!(cx.read(|cx| editor.is_dirty(cx)));
7226
7227 // Ensure we can still save even if formatting hangs.
7228 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7229 move |params, _| async move {
7230 assert_eq!(
7231 params.text_document.uri,
7232 lsp::Url::from_file_path("/file.rs").unwrap()
7233 );
7234 futures::future::pending::<()>().await;
7235 unreachable!()
7236 },
7237 );
7238 let save = editor
7239 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7240 .unwrap();
7241 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7242 cx.executor().start_waiting();
7243 save.await;
7244 assert_eq!(
7245 editor.update(cx, |editor, cx| editor.text(cx)),
7246 "one\ntwo\nthree\n"
7247 );
7248 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7249
7250 // For non-dirty buffer, no formatting request should be sent
7251 let save = editor
7252 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7253 .unwrap();
7254 let _pending_format_request = fake_server
7255 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7256 panic!("Should not be invoked on non-dirty buffer");
7257 })
7258 .next();
7259 cx.executor().start_waiting();
7260 save.await;
7261
7262 // Set Rust language override and assert overridden tabsize is sent to language server
7263 update_test_language_settings(cx, |settings| {
7264 settings.languages.insert(
7265 "Rust".into(),
7266 LanguageSettingsContent {
7267 tab_size: NonZeroU32::new(8),
7268 ..Default::default()
7269 },
7270 );
7271 });
7272
7273 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
7274 assert!(cx.read(|cx| editor.is_dirty(cx)));
7275 let save = editor
7276 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7277 .unwrap();
7278 fake_server
7279 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7280 assert_eq!(
7281 params.text_document.uri,
7282 lsp::Url::from_file_path("/file.rs").unwrap()
7283 );
7284 assert_eq!(params.options.tab_size, 8);
7285 Ok(Some(vec![]))
7286 })
7287 .next()
7288 .await;
7289 cx.executor().start_waiting();
7290 save.await;
7291}
7292
7293#[gpui::test]
7294async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7295 init_test(cx, |settings| {
7296 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7297 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7298 ))
7299 });
7300
7301 let fs = FakeFs::new(cx.executor());
7302 fs.insert_file("/file.rs", Default::default()).await;
7303
7304 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7305
7306 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7307 language_registry.add(Arc::new(Language::new(
7308 LanguageConfig {
7309 name: "Rust".into(),
7310 matcher: LanguageMatcher {
7311 path_suffixes: vec!["rs".to_string()],
7312 ..Default::default()
7313 },
7314 ..LanguageConfig::default()
7315 },
7316 Some(tree_sitter_rust::LANGUAGE.into()),
7317 )));
7318 update_test_language_settings(cx, |settings| {
7319 // Enable Prettier formatting for the same buffer, and ensure
7320 // LSP is called instead of Prettier.
7321 settings.defaults.prettier = Some(PrettierSettings {
7322 allowed: true,
7323 ..PrettierSettings::default()
7324 });
7325 });
7326 let mut fake_servers = language_registry.register_fake_lsp(
7327 "Rust",
7328 FakeLspAdapter {
7329 capabilities: lsp::ServerCapabilities {
7330 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7331 ..Default::default()
7332 },
7333 ..Default::default()
7334 },
7335 );
7336
7337 let buffer = project
7338 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7339 .await
7340 .unwrap();
7341
7342 cx.executor().start_waiting();
7343 let fake_server = fake_servers.next().await.unwrap();
7344
7345 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7346 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7347 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7348
7349 let format = editor
7350 .update(cx, |editor, cx| {
7351 editor.perform_format(
7352 project.clone(),
7353 FormatTrigger::Manual,
7354 FormatTarget::Buffer,
7355 cx,
7356 )
7357 })
7358 .unwrap();
7359 fake_server
7360 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7361 assert_eq!(
7362 params.text_document.uri,
7363 lsp::Url::from_file_path("/file.rs").unwrap()
7364 );
7365 assert_eq!(params.options.tab_size, 4);
7366 Ok(Some(vec![lsp::TextEdit::new(
7367 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7368 ", ".to_string(),
7369 )]))
7370 })
7371 .next()
7372 .await;
7373 cx.executor().start_waiting();
7374 format.await;
7375 assert_eq!(
7376 editor.update(cx, |editor, cx| editor.text(cx)),
7377 "one, two\nthree\n"
7378 );
7379
7380 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7381 // Ensure we don't lock if formatting hangs.
7382 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7383 assert_eq!(
7384 params.text_document.uri,
7385 lsp::Url::from_file_path("/file.rs").unwrap()
7386 );
7387 futures::future::pending::<()>().await;
7388 unreachable!()
7389 });
7390 let format = editor
7391 .update(cx, |editor, cx| {
7392 editor.perform_format(project, FormatTrigger::Manual, FormatTarget::Buffer, cx)
7393 })
7394 .unwrap();
7395 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7396 cx.executor().start_waiting();
7397 format.await;
7398 assert_eq!(
7399 editor.update(cx, |editor, cx| editor.text(cx)),
7400 "one\ntwo\nthree\n"
7401 );
7402}
7403
7404#[gpui::test]
7405async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7406 init_test(cx, |_| {});
7407
7408 let mut cx = EditorLspTestContext::new_rust(
7409 lsp::ServerCapabilities {
7410 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7411 ..Default::default()
7412 },
7413 cx,
7414 )
7415 .await;
7416
7417 cx.set_state(indoc! {"
7418 one.twoˇ
7419 "});
7420
7421 // The format request takes a long time. When it completes, it inserts
7422 // a newline and an indent before the `.`
7423 cx.lsp
7424 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7425 let executor = cx.background_executor().clone();
7426 async move {
7427 executor.timer(Duration::from_millis(100)).await;
7428 Ok(Some(vec![lsp::TextEdit {
7429 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7430 new_text: "\n ".into(),
7431 }]))
7432 }
7433 });
7434
7435 // Submit a format request.
7436 let format_1 = cx
7437 .update_editor(|editor, cx| editor.format(&Format, cx))
7438 .unwrap();
7439 cx.executor().run_until_parked();
7440
7441 // Submit a second format request.
7442 let format_2 = cx
7443 .update_editor(|editor, cx| editor.format(&Format, cx))
7444 .unwrap();
7445 cx.executor().run_until_parked();
7446
7447 // Wait for both format requests to complete
7448 cx.executor().advance_clock(Duration::from_millis(200));
7449 cx.executor().start_waiting();
7450 format_1.await.unwrap();
7451 cx.executor().start_waiting();
7452 format_2.await.unwrap();
7453
7454 // The formatting edits only happens once.
7455 cx.assert_editor_state(indoc! {"
7456 one
7457 .twoˇ
7458 "});
7459}
7460
7461#[gpui::test]
7462async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7463 init_test(cx, |settings| {
7464 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7465 });
7466
7467 let mut cx = EditorLspTestContext::new_rust(
7468 lsp::ServerCapabilities {
7469 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7470 ..Default::default()
7471 },
7472 cx,
7473 )
7474 .await;
7475
7476 // Set up a buffer white some trailing whitespace and no trailing newline.
7477 cx.set_state(
7478 &[
7479 "one ", //
7480 "twoˇ", //
7481 "three ", //
7482 "four", //
7483 ]
7484 .join("\n"),
7485 );
7486
7487 // Submit a format request.
7488 let format = cx
7489 .update_editor(|editor, cx| editor.format(&Format, cx))
7490 .unwrap();
7491
7492 // Record which buffer changes have been sent to the language server
7493 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7494 cx.lsp
7495 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7496 let buffer_changes = buffer_changes.clone();
7497 move |params, _| {
7498 buffer_changes.lock().extend(
7499 params
7500 .content_changes
7501 .into_iter()
7502 .map(|e| (e.range.unwrap(), e.text)),
7503 );
7504 }
7505 });
7506
7507 // Handle formatting requests to the language server.
7508 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7509 let buffer_changes = buffer_changes.clone();
7510 move |_, _| {
7511 // When formatting is requested, trailing whitespace has already been stripped,
7512 // and the trailing newline has already been added.
7513 assert_eq!(
7514 &buffer_changes.lock()[1..],
7515 &[
7516 (
7517 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7518 "".into()
7519 ),
7520 (
7521 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7522 "".into()
7523 ),
7524 (
7525 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7526 "\n".into()
7527 ),
7528 ]
7529 );
7530
7531 // Insert blank lines between each line of the buffer.
7532 async move {
7533 Ok(Some(vec![
7534 lsp::TextEdit {
7535 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7536 new_text: "\n".into(),
7537 },
7538 lsp::TextEdit {
7539 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7540 new_text: "\n".into(),
7541 },
7542 ]))
7543 }
7544 }
7545 });
7546
7547 // After formatting the buffer, the trailing whitespace is stripped,
7548 // a newline is appended, and the edits provided by the language server
7549 // have been applied.
7550 format.await.unwrap();
7551 cx.assert_editor_state(
7552 &[
7553 "one", //
7554 "", //
7555 "twoˇ", //
7556 "", //
7557 "three", //
7558 "four", //
7559 "", //
7560 ]
7561 .join("\n"),
7562 );
7563
7564 // Undoing the formatting undoes the trailing whitespace removal, the
7565 // trailing newline, and the LSP edits.
7566 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7567 cx.assert_editor_state(
7568 &[
7569 "one ", //
7570 "twoˇ", //
7571 "three ", //
7572 "four", //
7573 ]
7574 .join("\n"),
7575 );
7576}
7577
7578#[gpui::test]
7579async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7580 cx: &mut gpui::TestAppContext,
7581) {
7582 init_test(cx, |_| {});
7583
7584 cx.update(|cx| {
7585 cx.update_global::<SettingsStore, _>(|settings, cx| {
7586 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7587 settings.auto_signature_help = Some(true);
7588 });
7589 });
7590 });
7591
7592 let mut cx = EditorLspTestContext::new_rust(
7593 lsp::ServerCapabilities {
7594 signature_help_provider: Some(lsp::SignatureHelpOptions {
7595 ..Default::default()
7596 }),
7597 ..Default::default()
7598 },
7599 cx,
7600 )
7601 .await;
7602
7603 let language = Language::new(
7604 LanguageConfig {
7605 name: "Rust".into(),
7606 brackets: BracketPairConfig {
7607 pairs: vec![
7608 BracketPair {
7609 start: "{".to_string(),
7610 end: "}".to_string(),
7611 close: true,
7612 surround: true,
7613 newline: true,
7614 },
7615 BracketPair {
7616 start: "(".to_string(),
7617 end: ")".to_string(),
7618 close: true,
7619 surround: true,
7620 newline: true,
7621 },
7622 BracketPair {
7623 start: "/*".to_string(),
7624 end: " */".to_string(),
7625 close: true,
7626 surround: true,
7627 newline: true,
7628 },
7629 BracketPair {
7630 start: "[".to_string(),
7631 end: "]".to_string(),
7632 close: false,
7633 surround: false,
7634 newline: true,
7635 },
7636 BracketPair {
7637 start: "\"".to_string(),
7638 end: "\"".to_string(),
7639 close: true,
7640 surround: true,
7641 newline: false,
7642 },
7643 BracketPair {
7644 start: "<".to_string(),
7645 end: ">".to_string(),
7646 close: false,
7647 surround: true,
7648 newline: true,
7649 },
7650 ],
7651 ..Default::default()
7652 },
7653 autoclose_before: "})]".to_string(),
7654 ..Default::default()
7655 },
7656 Some(tree_sitter_rust::LANGUAGE.into()),
7657 );
7658 let language = Arc::new(language);
7659
7660 cx.language_registry().add(language.clone());
7661 cx.update_buffer(|buffer, cx| {
7662 buffer.set_language(Some(language), cx);
7663 });
7664
7665 cx.set_state(
7666 &r#"
7667 fn main() {
7668 sampleˇ
7669 }
7670 "#
7671 .unindent(),
7672 );
7673
7674 cx.update_editor(|view, cx| {
7675 view.handle_input("(", cx);
7676 });
7677 cx.assert_editor_state(
7678 &"
7679 fn main() {
7680 sample(ˇ)
7681 }
7682 "
7683 .unindent(),
7684 );
7685
7686 let mocked_response = lsp::SignatureHelp {
7687 signatures: vec![lsp::SignatureInformation {
7688 label: "fn sample(param1: u8, param2: u8)".to_string(),
7689 documentation: None,
7690 parameters: Some(vec![
7691 lsp::ParameterInformation {
7692 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7693 documentation: None,
7694 },
7695 lsp::ParameterInformation {
7696 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7697 documentation: None,
7698 },
7699 ]),
7700 active_parameter: None,
7701 }],
7702 active_signature: Some(0),
7703 active_parameter: Some(0),
7704 };
7705 handle_signature_help_request(&mut cx, mocked_response).await;
7706
7707 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7708 .await;
7709
7710 cx.editor(|editor, _| {
7711 let signature_help_state = editor.signature_help_state.popover().cloned();
7712 assert!(signature_help_state.is_some());
7713 let ParsedMarkdown {
7714 text, highlights, ..
7715 } = signature_help_state.unwrap().parsed_content;
7716 assert_eq!(text, "param1: u8, param2: u8");
7717 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7718 });
7719}
7720
7721#[gpui::test]
7722async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7723 init_test(cx, |_| {});
7724
7725 cx.update(|cx| {
7726 cx.update_global::<SettingsStore, _>(|settings, cx| {
7727 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7728 settings.auto_signature_help = Some(false);
7729 settings.show_signature_help_after_edits = Some(false);
7730 });
7731 });
7732 });
7733
7734 let mut cx = EditorLspTestContext::new_rust(
7735 lsp::ServerCapabilities {
7736 signature_help_provider: Some(lsp::SignatureHelpOptions {
7737 ..Default::default()
7738 }),
7739 ..Default::default()
7740 },
7741 cx,
7742 )
7743 .await;
7744
7745 let language = Language::new(
7746 LanguageConfig {
7747 name: "Rust".into(),
7748 brackets: BracketPairConfig {
7749 pairs: vec![
7750 BracketPair {
7751 start: "{".to_string(),
7752 end: "}".to_string(),
7753 close: true,
7754 surround: true,
7755 newline: true,
7756 },
7757 BracketPair {
7758 start: "(".to_string(),
7759 end: ")".to_string(),
7760 close: true,
7761 surround: true,
7762 newline: true,
7763 },
7764 BracketPair {
7765 start: "/*".to_string(),
7766 end: " */".to_string(),
7767 close: true,
7768 surround: true,
7769 newline: true,
7770 },
7771 BracketPair {
7772 start: "[".to_string(),
7773 end: "]".to_string(),
7774 close: false,
7775 surround: false,
7776 newline: true,
7777 },
7778 BracketPair {
7779 start: "\"".to_string(),
7780 end: "\"".to_string(),
7781 close: true,
7782 surround: true,
7783 newline: false,
7784 },
7785 BracketPair {
7786 start: "<".to_string(),
7787 end: ">".to_string(),
7788 close: false,
7789 surround: true,
7790 newline: true,
7791 },
7792 ],
7793 ..Default::default()
7794 },
7795 autoclose_before: "})]".to_string(),
7796 ..Default::default()
7797 },
7798 Some(tree_sitter_rust::LANGUAGE.into()),
7799 );
7800 let language = Arc::new(language);
7801
7802 cx.language_registry().add(language.clone());
7803 cx.update_buffer(|buffer, cx| {
7804 buffer.set_language(Some(language), cx);
7805 });
7806
7807 // Ensure that signature_help is not called when no signature help is enabled.
7808 cx.set_state(
7809 &r#"
7810 fn main() {
7811 sampleˇ
7812 }
7813 "#
7814 .unindent(),
7815 );
7816 cx.update_editor(|view, cx| {
7817 view.handle_input("(", cx);
7818 });
7819 cx.assert_editor_state(
7820 &"
7821 fn main() {
7822 sample(ˇ)
7823 }
7824 "
7825 .unindent(),
7826 );
7827 cx.editor(|editor, _| {
7828 assert!(editor.signature_help_state.task().is_none());
7829 });
7830
7831 let mocked_response = lsp::SignatureHelp {
7832 signatures: vec![lsp::SignatureInformation {
7833 label: "fn sample(param1: u8, param2: u8)".to_string(),
7834 documentation: None,
7835 parameters: Some(vec![
7836 lsp::ParameterInformation {
7837 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7838 documentation: None,
7839 },
7840 lsp::ParameterInformation {
7841 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7842 documentation: None,
7843 },
7844 ]),
7845 active_parameter: None,
7846 }],
7847 active_signature: Some(0),
7848 active_parameter: Some(0),
7849 };
7850
7851 // Ensure that signature_help is called when enabled afte edits
7852 cx.update(|cx| {
7853 cx.update_global::<SettingsStore, _>(|settings, cx| {
7854 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7855 settings.auto_signature_help = Some(false);
7856 settings.show_signature_help_after_edits = Some(true);
7857 });
7858 });
7859 });
7860 cx.set_state(
7861 &r#"
7862 fn main() {
7863 sampleˇ
7864 }
7865 "#
7866 .unindent(),
7867 );
7868 cx.update_editor(|view, cx| {
7869 view.handle_input("(", cx);
7870 });
7871 cx.assert_editor_state(
7872 &"
7873 fn main() {
7874 sample(ˇ)
7875 }
7876 "
7877 .unindent(),
7878 );
7879 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7880 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7881 .await;
7882 cx.update_editor(|editor, _| {
7883 let signature_help_state = editor.signature_help_state.popover().cloned();
7884 assert!(signature_help_state.is_some());
7885 let ParsedMarkdown {
7886 text, highlights, ..
7887 } = signature_help_state.unwrap().parsed_content;
7888 assert_eq!(text, "param1: u8, param2: u8");
7889 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7890 editor.signature_help_state = SignatureHelpState::default();
7891 });
7892
7893 // Ensure that signature_help is called when auto signature help override is enabled
7894 cx.update(|cx| {
7895 cx.update_global::<SettingsStore, _>(|settings, cx| {
7896 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7897 settings.auto_signature_help = Some(true);
7898 settings.show_signature_help_after_edits = Some(false);
7899 });
7900 });
7901 });
7902 cx.set_state(
7903 &r#"
7904 fn main() {
7905 sampleˇ
7906 }
7907 "#
7908 .unindent(),
7909 );
7910 cx.update_editor(|view, cx| {
7911 view.handle_input("(", cx);
7912 });
7913 cx.assert_editor_state(
7914 &"
7915 fn main() {
7916 sample(ˇ)
7917 }
7918 "
7919 .unindent(),
7920 );
7921 handle_signature_help_request(&mut cx, mocked_response).await;
7922 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7923 .await;
7924 cx.editor(|editor, _| {
7925 let signature_help_state = editor.signature_help_state.popover().cloned();
7926 assert!(signature_help_state.is_some());
7927 let ParsedMarkdown {
7928 text, highlights, ..
7929 } = signature_help_state.unwrap().parsed_content;
7930 assert_eq!(text, "param1: u8, param2: u8");
7931 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7932 });
7933}
7934
7935#[gpui::test]
7936async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7937 init_test(cx, |_| {});
7938 cx.update(|cx| {
7939 cx.update_global::<SettingsStore, _>(|settings, cx| {
7940 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7941 settings.auto_signature_help = Some(true);
7942 });
7943 });
7944 });
7945
7946 let mut cx = EditorLspTestContext::new_rust(
7947 lsp::ServerCapabilities {
7948 signature_help_provider: Some(lsp::SignatureHelpOptions {
7949 ..Default::default()
7950 }),
7951 ..Default::default()
7952 },
7953 cx,
7954 )
7955 .await;
7956
7957 // A test that directly calls `show_signature_help`
7958 cx.update_editor(|editor, cx| {
7959 editor.show_signature_help(&ShowSignatureHelp, cx);
7960 });
7961
7962 let mocked_response = lsp::SignatureHelp {
7963 signatures: vec![lsp::SignatureInformation {
7964 label: "fn sample(param1: u8, param2: u8)".to_string(),
7965 documentation: None,
7966 parameters: Some(vec![
7967 lsp::ParameterInformation {
7968 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7969 documentation: None,
7970 },
7971 lsp::ParameterInformation {
7972 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7973 documentation: None,
7974 },
7975 ]),
7976 active_parameter: None,
7977 }],
7978 active_signature: Some(0),
7979 active_parameter: Some(0),
7980 };
7981 handle_signature_help_request(&mut cx, mocked_response).await;
7982
7983 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7984 .await;
7985
7986 cx.editor(|editor, _| {
7987 let signature_help_state = editor.signature_help_state.popover().cloned();
7988 assert!(signature_help_state.is_some());
7989 let ParsedMarkdown {
7990 text, highlights, ..
7991 } = signature_help_state.unwrap().parsed_content;
7992 assert_eq!(text, "param1: u8, param2: u8");
7993 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7994 });
7995
7996 // When exiting outside from inside the brackets, `signature_help` is closed.
7997 cx.set_state(indoc! {"
7998 fn main() {
7999 sample(ˇ);
8000 }
8001
8002 fn sample(param1: u8, param2: u8) {}
8003 "});
8004
8005 cx.update_editor(|editor, cx| {
8006 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
8007 });
8008
8009 let mocked_response = lsp::SignatureHelp {
8010 signatures: Vec::new(),
8011 active_signature: None,
8012 active_parameter: None,
8013 };
8014 handle_signature_help_request(&mut cx, mocked_response).await;
8015
8016 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8017 .await;
8018
8019 cx.editor(|editor, _| {
8020 assert!(!editor.signature_help_state.is_shown());
8021 });
8022
8023 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8024 cx.set_state(indoc! {"
8025 fn main() {
8026 sample(ˇ);
8027 }
8028
8029 fn sample(param1: u8, param2: u8) {}
8030 "});
8031
8032 let mocked_response = lsp::SignatureHelp {
8033 signatures: vec![lsp::SignatureInformation {
8034 label: "fn sample(param1: u8, param2: u8)".to_string(),
8035 documentation: None,
8036 parameters: Some(vec![
8037 lsp::ParameterInformation {
8038 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8039 documentation: None,
8040 },
8041 lsp::ParameterInformation {
8042 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8043 documentation: None,
8044 },
8045 ]),
8046 active_parameter: None,
8047 }],
8048 active_signature: Some(0),
8049 active_parameter: Some(0),
8050 };
8051 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8052 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8053 .await;
8054 cx.editor(|editor, _| {
8055 assert!(editor.signature_help_state.is_shown());
8056 });
8057
8058 // Restore the popover with more parameter input
8059 cx.set_state(indoc! {"
8060 fn main() {
8061 sample(param1, param2ˇ);
8062 }
8063
8064 fn sample(param1: u8, param2: u8) {}
8065 "});
8066
8067 let mocked_response = lsp::SignatureHelp {
8068 signatures: vec![lsp::SignatureInformation {
8069 label: "fn sample(param1: u8, param2: u8)".to_string(),
8070 documentation: None,
8071 parameters: Some(vec![
8072 lsp::ParameterInformation {
8073 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8074 documentation: None,
8075 },
8076 lsp::ParameterInformation {
8077 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8078 documentation: None,
8079 },
8080 ]),
8081 active_parameter: None,
8082 }],
8083 active_signature: Some(0),
8084 active_parameter: Some(1),
8085 };
8086 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8087 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8088 .await;
8089
8090 // When selecting a range, the popover is gone.
8091 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8092 cx.update_editor(|editor, cx| {
8093 editor.change_selections(None, cx, |s| {
8094 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8095 })
8096 });
8097 cx.assert_editor_state(indoc! {"
8098 fn main() {
8099 sample(param1, «ˇparam2»);
8100 }
8101
8102 fn sample(param1: u8, param2: u8) {}
8103 "});
8104 cx.editor(|editor, _| {
8105 assert!(!editor.signature_help_state.is_shown());
8106 });
8107
8108 // When unselecting again, the popover is back if within the brackets.
8109 cx.update_editor(|editor, cx| {
8110 editor.change_selections(None, cx, |s| {
8111 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8112 })
8113 });
8114 cx.assert_editor_state(indoc! {"
8115 fn main() {
8116 sample(param1, ˇparam2);
8117 }
8118
8119 fn sample(param1: u8, param2: u8) {}
8120 "});
8121 handle_signature_help_request(&mut cx, mocked_response).await;
8122 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8123 .await;
8124 cx.editor(|editor, _| {
8125 assert!(editor.signature_help_state.is_shown());
8126 });
8127
8128 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8129 cx.update_editor(|editor, cx| {
8130 editor.change_selections(None, cx, |s| {
8131 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8132 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8133 })
8134 });
8135 cx.assert_editor_state(indoc! {"
8136 fn main() {
8137 sample(param1, ˇparam2);
8138 }
8139
8140 fn sample(param1: u8, param2: u8) {}
8141 "});
8142
8143 let mocked_response = lsp::SignatureHelp {
8144 signatures: vec![lsp::SignatureInformation {
8145 label: "fn sample(param1: u8, param2: u8)".to_string(),
8146 documentation: None,
8147 parameters: Some(vec![
8148 lsp::ParameterInformation {
8149 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8150 documentation: None,
8151 },
8152 lsp::ParameterInformation {
8153 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8154 documentation: None,
8155 },
8156 ]),
8157 active_parameter: None,
8158 }],
8159 active_signature: Some(0),
8160 active_parameter: Some(1),
8161 };
8162 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8163 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8164 .await;
8165 cx.update_editor(|editor, cx| {
8166 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8167 });
8168 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8169 .await;
8170 cx.update_editor(|editor, cx| {
8171 editor.change_selections(None, cx, |s| {
8172 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8173 })
8174 });
8175 cx.assert_editor_state(indoc! {"
8176 fn main() {
8177 sample(param1, «ˇparam2»);
8178 }
8179
8180 fn sample(param1: u8, param2: u8) {}
8181 "});
8182 cx.update_editor(|editor, cx| {
8183 editor.change_selections(None, cx, |s| {
8184 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8185 })
8186 });
8187 cx.assert_editor_state(indoc! {"
8188 fn main() {
8189 sample(param1, ˇparam2);
8190 }
8191
8192 fn sample(param1: u8, param2: u8) {}
8193 "});
8194 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8195 .await;
8196}
8197
8198#[gpui::test]
8199async fn test_completion(cx: &mut gpui::TestAppContext) {
8200 init_test(cx, |_| {});
8201
8202 let mut cx = EditorLspTestContext::new_rust(
8203 lsp::ServerCapabilities {
8204 completion_provider: Some(lsp::CompletionOptions {
8205 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8206 resolve_provider: Some(true),
8207 ..Default::default()
8208 }),
8209 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8210 ..Default::default()
8211 },
8212 cx,
8213 )
8214 .await;
8215 let counter = Arc::new(AtomicUsize::new(0));
8216
8217 cx.set_state(indoc! {"
8218 oneˇ
8219 two
8220 three
8221 "});
8222 cx.simulate_keystroke(".");
8223 handle_completion_request(
8224 &mut cx,
8225 indoc! {"
8226 one.|<>
8227 two
8228 three
8229 "},
8230 vec!["first_completion", "second_completion"],
8231 counter.clone(),
8232 )
8233 .await;
8234 cx.condition(|editor, _| editor.context_menu_visible())
8235 .await;
8236 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8237
8238 let _handler = handle_signature_help_request(
8239 &mut cx,
8240 lsp::SignatureHelp {
8241 signatures: vec![lsp::SignatureInformation {
8242 label: "test signature".to_string(),
8243 documentation: None,
8244 parameters: Some(vec![lsp::ParameterInformation {
8245 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8246 documentation: None,
8247 }]),
8248 active_parameter: None,
8249 }],
8250 active_signature: None,
8251 active_parameter: None,
8252 },
8253 );
8254 cx.update_editor(|editor, cx| {
8255 assert!(
8256 !editor.signature_help_state.is_shown(),
8257 "No signature help was called for"
8258 );
8259 editor.show_signature_help(&ShowSignatureHelp, cx);
8260 });
8261 cx.run_until_parked();
8262 cx.update_editor(|editor, _| {
8263 assert!(
8264 !editor.signature_help_state.is_shown(),
8265 "No signature help should be shown when completions menu is open"
8266 );
8267 });
8268
8269 let apply_additional_edits = cx.update_editor(|editor, cx| {
8270 editor.context_menu_next(&Default::default(), cx);
8271 editor
8272 .confirm_completion(&ConfirmCompletion::default(), cx)
8273 .unwrap()
8274 });
8275 cx.assert_editor_state(indoc! {"
8276 one.second_completionˇ
8277 two
8278 three
8279 "});
8280
8281 handle_resolve_completion_request(
8282 &mut cx,
8283 Some(vec![
8284 (
8285 //This overlaps with the primary completion edit which is
8286 //misbehavior from the LSP spec, test that we filter it out
8287 indoc! {"
8288 one.second_ˇcompletion
8289 two
8290 threeˇ
8291 "},
8292 "overlapping additional edit",
8293 ),
8294 (
8295 indoc! {"
8296 one.second_completion
8297 two
8298 threeˇ
8299 "},
8300 "\nadditional edit",
8301 ),
8302 ]),
8303 )
8304 .await;
8305 apply_additional_edits.await.unwrap();
8306 cx.assert_editor_state(indoc! {"
8307 one.second_completionˇ
8308 two
8309 three
8310 additional edit
8311 "});
8312
8313 cx.set_state(indoc! {"
8314 one.second_completion
8315 twoˇ
8316 threeˇ
8317 additional edit
8318 "});
8319 cx.simulate_keystroke(" ");
8320 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8321 cx.simulate_keystroke("s");
8322 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8323
8324 cx.assert_editor_state(indoc! {"
8325 one.second_completion
8326 two sˇ
8327 three sˇ
8328 additional edit
8329 "});
8330 handle_completion_request(
8331 &mut cx,
8332 indoc! {"
8333 one.second_completion
8334 two s
8335 three <s|>
8336 additional edit
8337 "},
8338 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8339 counter.clone(),
8340 )
8341 .await;
8342 cx.condition(|editor, _| editor.context_menu_visible())
8343 .await;
8344 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8345
8346 cx.simulate_keystroke("i");
8347
8348 handle_completion_request(
8349 &mut cx,
8350 indoc! {"
8351 one.second_completion
8352 two si
8353 three <si|>
8354 additional edit
8355 "},
8356 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8357 counter.clone(),
8358 )
8359 .await;
8360 cx.condition(|editor, _| editor.context_menu_visible())
8361 .await;
8362 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8363
8364 let apply_additional_edits = cx.update_editor(|editor, cx| {
8365 editor
8366 .confirm_completion(&ConfirmCompletion::default(), cx)
8367 .unwrap()
8368 });
8369 cx.assert_editor_state(indoc! {"
8370 one.second_completion
8371 two sixth_completionˇ
8372 three sixth_completionˇ
8373 additional edit
8374 "});
8375
8376 handle_resolve_completion_request(&mut cx, None).await;
8377 apply_additional_edits.await.unwrap();
8378
8379 update_test_language_settings(&mut cx, |settings| {
8380 settings.defaults.show_completions_on_input = Some(false);
8381 });
8382 cx.set_state("editorˇ");
8383 cx.simulate_keystroke(".");
8384 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8385 cx.simulate_keystroke("c");
8386 cx.simulate_keystroke("l");
8387 cx.simulate_keystroke("o");
8388 cx.assert_editor_state("editor.cloˇ");
8389 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8390 cx.update_editor(|editor, cx| {
8391 editor.show_completions(&ShowCompletions { trigger: None }, cx);
8392 });
8393 handle_completion_request(
8394 &mut cx,
8395 "editor.<clo|>",
8396 vec!["close", "clobber"],
8397 counter.clone(),
8398 )
8399 .await;
8400 cx.condition(|editor, _| editor.context_menu_visible())
8401 .await;
8402 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8403
8404 let apply_additional_edits = cx.update_editor(|editor, cx| {
8405 editor
8406 .confirm_completion(&ConfirmCompletion::default(), cx)
8407 .unwrap()
8408 });
8409 cx.assert_editor_state("editor.closeˇ");
8410 handle_resolve_completion_request(&mut cx, None).await;
8411 apply_additional_edits.await.unwrap();
8412}
8413
8414#[gpui::test]
8415async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8416 init_test(cx, |_| {});
8417 let mut cx = EditorLspTestContext::new_rust(
8418 lsp::ServerCapabilities {
8419 completion_provider: Some(lsp::CompletionOptions {
8420 trigger_characters: Some(vec![".".to_string()]),
8421 ..Default::default()
8422 }),
8423 ..Default::default()
8424 },
8425 cx,
8426 )
8427 .await;
8428 cx.lsp
8429 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8430 Ok(Some(lsp::CompletionResponse::Array(vec![
8431 lsp::CompletionItem {
8432 label: "first".into(),
8433 ..Default::default()
8434 },
8435 lsp::CompletionItem {
8436 label: "last".into(),
8437 ..Default::default()
8438 },
8439 ])))
8440 });
8441 cx.set_state("variableˇ");
8442 cx.simulate_keystroke(".");
8443 cx.executor().run_until_parked();
8444
8445 cx.update_editor(|editor, _| {
8446 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8447 assert_eq!(
8448 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8449 &["first", "last"]
8450 );
8451 } else {
8452 panic!("expected completion menu to be open");
8453 }
8454 });
8455
8456 cx.update_editor(|editor, cx| {
8457 editor.move_page_down(&MovePageDown::default(), cx);
8458 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8459 assert!(
8460 menu.selected_item == 1,
8461 "expected PageDown to select the last item from the context menu"
8462 );
8463 } else {
8464 panic!("expected completion menu to stay open after PageDown");
8465 }
8466 });
8467
8468 cx.update_editor(|editor, cx| {
8469 editor.move_page_up(&MovePageUp::default(), cx);
8470 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8471 assert!(
8472 menu.selected_item == 0,
8473 "expected PageUp to select the first item from the context menu"
8474 );
8475 } else {
8476 panic!("expected completion menu to stay open after PageUp");
8477 }
8478 });
8479}
8480
8481#[gpui::test]
8482async fn test_completion_sort(cx: &mut gpui::TestAppContext) {
8483 init_test(cx, |_| {});
8484 let mut cx = EditorLspTestContext::new_rust(
8485 lsp::ServerCapabilities {
8486 completion_provider: Some(lsp::CompletionOptions {
8487 trigger_characters: Some(vec![".".to_string()]),
8488 ..Default::default()
8489 }),
8490 ..Default::default()
8491 },
8492 cx,
8493 )
8494 .await;
8495 cx.lsp
8496 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8497 Ok(Some(lsp::CompletionResponse::Array(vec![
8498 lsp::CompletionItem {
8499 label: "Range".into(),
8500 sort_text: Some("a".into()),
8501 ..Default::default()
8502 },
8503 lsp::CompletionItem {
8504 label: "r".into(),
8505 sort_text: Some("b".into()),
8506 ..Default::default()
8507 },
8508 lsp::CompletionItem {
8509 label: "ret".into(),
8510 sort_text: Some("c".into()),
8511 ..Default::default()
8512 },
8513 lsp::CompletionItem {
8514 label: "return".into(),
8515 sort_text: Some("d".into()),
8516 ..Default::default()
8517 },
8518 lsp::CompletionItem {
8519 label: "slice".into(),
8520 sort_text: Some("d".into()),
8521 ..Default::default()
8522 },
8523 ])))
8524 });
8525 cx.set_state("rˇ");
8526 cx.executor().run_until_parked();
8527 cx.update_editor(|editor, cx| {
8528 editor.show_completions(
8529 &ShowCompletions {
8530 trigger: Some("r".into()),
8531 },
8532 cx,
8533 );
8534 });
8535 cx.executor().run_until_parked();
8536
8537 cx.update_editor(|editor, _| {
8538 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
8539 assert_eq!(
8540 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
8541 &["r", "ret", "Range", "return"]
8542 );
8543 } else {
8544 panic!("expected completion menu to be open");
8545 }
8546 });
8547}
8548
8549#[gpui::test]
8550async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8551 init_test(cx, |_| {});
8552
8553 let mut cx = EditorLspTestContext::new_rust(
8554 lsp::ServerCapabilities {
8555 completion_provider: Some(lsp::CompletionOptions {
8556 trigger_characters: Some(vec![".".to_string()]),
8557 resolve_provider: Some(true),
8558 ..Default::default()
8559 }),
8560 ..Default::default()
8561 },
8562 cx,
8563 )
8564 .await;
8565
8566 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8567 cx.simulate_keystroke(".");
8568 let completion_item = lsp::CompletionItem {
8569 label: "Some".into(),
8570 kind: Some(lsp::CompletionItemKind::SNIPPET),
8571 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8572 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8573 kind: lsp::MarkupKind::Markdown,
8574 value: "```rust\nSome(2)\n```".to_string(),
8575 })),
8576 deprecated: Some(false),
8577 sort_text: Some("Some".to_string()),
8578 filter_text: Some("Some".to_string()),
8579 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8580 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8581 range: lsp::Range {
8582 start: lsp::Position {
8583 line: 0,
8584 character: 22,
8585 },
8586 end: lsp::Position {
8587 line: 0,
8588 character: 22,
8589 },
8590 },
8591 new_text: "Some(2)".to_string(),
8592 })),
8593 additional_text_edits: Some(vec![lsp::TextEdit {
8594 range: lsp::Range {
8595 start: lsp::Position {
8596 line: 0,
8597 character: 20,
8598 },
8599 end: lsp::Position {
8600 line: 0,
8601 character: 22,
8602 },
8603 },
8604 new_text: "".to_string(),
8605 }]),
8606 ..Default::default()
8607 };
8608
8609 let closure_completion_item = completion_item.clone();
8610 let counter = Arc::new(AtomicUsize::new(0));
8611 let counter_clone = counter.clone();
8612 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8613 let task_completion_item = closure_completion_item.clone();
8614 counter_clone.fetch_add(1, atomic::Ordering::Release);
8615 async move {
8616 Ok(Some(lsp::CompletionResponse::Array(vec![
8617 task_completion_item,
8618 ])))
8619 }
8620 });
8621
8622 cx.condition(|editor, _| editor.context_menu_visible())
8623 .await;
8624 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8625 assert!(request.next().await.is_some());
8626 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8627
8628 cx.simulate_keystroke("S");
8629 cx.simulate_keystroke("o");
8630 cx.simulate_keystroke("m");
8631 cx.condition(|editor, _| editor.context_menu_visible())
8632 .await;
8633 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
8634 assert!(request.next().await.is_some());
8635 assert!(request.next().await.is_some());
8636 assert!(request.next().await.is_some());
8637 request.close();
8638 assert!(request.next().await.is_none());
8639 assert_eq!(
8640 counter.load(atomic::Ordering::Acquire),
8641 4,
8642 "With the completions menu open, only one LSP request should happen per input"
8643 );
8644}
8645
8646#[gpui::test]
8647async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
8648 init_test(cx, |_| {});
8649 let mut cx = EditorTestContext::new(cx).await;
8650 let language = Arc::new(Language::new(
8651 LanguageConfig {
8652 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8653 ..Default::default()
8654 },
8655 Some(tree_sitter_rust::LANGUAGE.into()),
8656 ));
8657 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8658
8659 // If multiple selections intersect a line, the line is only toggled once.
8660 cx.set_state(indoc! {"
8661 fn a() {
8662 «//b();
8663 ˇ»// «c();
8664 //ˇ» d();
8665 }
8666 "});
8667
8668 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8669
8670 cx.assert_editor_state(indoc! {"
8671 fn a() {
8672 «b();
8673 c();
8674 ˇ» d();
8675 }
8676 "});
8677
8678 // The comment prefix is inserted at the same column for every line in a
8679 // selection.
8680 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8681
8682 cx.assert_editor_state(indoc! {"
8683 fn a() {
8684 // «b();
8685 // c();
8686 ˇ»// d();
8687 }
8688 "});
8689
8690 // If a selection ends at the beginning of a line, that line is not toggled.
8691 cx.set_selections_state(indoc! {"
8692 fn a() {
8693 // b();
8694 «// c();
8695 ˇ» // d();
8696 }
8697 "});
8698
8699 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8700
8701 cx.assert_editor_state(indoc! {"
8702 fn a() {
8703 // b();
8704 «c();
8705 ˇ» // d();
8706 }
8707 "});
8708
8709 // If a selection span a single line and is empty, the line is toggled.
8710 cx.set_state(indoc! {"
8711 fn a() {
8712 a();
8713 b();
8714 ˇ
8715 }
8716 "});
8717
8718 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8719
8720 cx.assert_editor_state(indoc! {"
8721 fn a() {
8722 a();
8723 b();
8724 //•ˇ
8725 }
8726 "});
8727
8728 // If a selection span multiple lines, empty lines are not toggled.
8729 cx.set_state(indoc! {"
8730 fn a() {
8731 «a();
8732
8733 c();ˇ»
8734 }
8735 "});
8736
8737 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8738
8739 cx.assert_editor_state(indoc! {"
8740 fn a() {
8741 // «a();
8742
8743 // c();ˇ»
8744 }
8745 "});
8746
8747 // If a selection includes multiple comment prefixes, all lines are uncommented.
8748 cx.set_state(indoc! {"
8749 fn a() {
8750 «// a();
8751 /// b();
8752 //! c();ˇ»
8753 }
8754 "});
8755
8756 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
8757
8758 cx.assert_editor_state(indoc! {"
8759 fn a() {
8760 «a();
8761 b();
8762 c();ˇ»
8763 }
8764 "});
8765}
8766
8767#[gpui::test]
8768async fn test_toggle_comment_ignore_indent(cx: &mut gpui::TestAppContext) {
8769 init_test(cx, |_| {});
8770 let mut cx = EditorTestContext::new(cx).await;
8771 let language = Arc::new(Language::new(
8772 LanguageConfig {
8773 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
8774 ..Default::default()
8775 },
8776 Some(tree_sitter_rust::LANGUAGE.into()),
8777 ));
8778 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
8779
8780 let toggle_comments = &ToggleComments {
8781 advance_downwards: false,
8782 ignore_indent: true,
8783 };
8784
8785 // If multiple selections intersect a line, the line is only toggled once.
8786 cx.set_state(indoc! {"
8787 fn a() {
8788 // «b();
8789 // c();
8790 // ˇ» d();
8791 }
8792 "});
8793
8794 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8795
8796 cx.assert_editor_state(indoc! {"
8797 fn a() {
8798 «b();
8799 c();
8800 ˇ» d();
8801 }
8802 "});
8803
8804 // The comment prefix is inserted at the beginning of each line
8805 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8806
8807 cx.assert_editor_state(indoc! {"
8808 fn a() {
8809 // «b();
8810 // c();
8811 // ˇ» d();
8812 }
8813 "});
8814
8815 // If a selection ends at the beginning of a line, that line is not toggled.
8816 cx.set_selections_state(indoc! {"
8817 fn a() {
8818 // b();
8819 // «c();
8820 ˇ»// d();
8821 }
8822 "});
8823
8824 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8825
8826 cx.assert_editor_state(indoc! {"
8827 fn a() {
8828 // b();
8829 «c();
8830 ˇ»// d();
8831 }
8832 "});
8833
8834 // If a selection span a single line and is empty, the line is toggled.
8835 cx.set_state(indoc! {"
8836 fn a() {
8837 a();
8838 b();
8839 ˇ
8840 }
8841 "});
8842
8843 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8844
8845 cx.assert_editor_state(indoc! {"
8846 fn a() {
8847 a();
8848 b();
8849 //ˇ
8850 }
8851 "});
8852
8853 // If a selection span multiple lines, empty lines are not toggled.
8854 cx.set_state(indoc! {"
8855 fn a() {
8856 «a();
8857
8858 c();ˇ»
8859 }
8860 "});
8861
8862 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8863
8864 cx.assert_editor_state(indoc! {"
8865 fn a() {
8866 // «a();
8867
8868 // c();ˇ»
8869 }
8870 "});
8871
8872 // If a selection includes multiple comment prefixes, all lines are uncommented.
8873 cx.set_state(indoc! {"
8874 fn a() {
8875 // «a();
8876 /// b();
8877 //! c();ˇ»
8878 }
8879 "});
8880
8881 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
8882
8883 cx.assert_editor_state(indoc! {"
8884 fn a() {
8885 «a();
8886 b();
8887 c();ˇ»
8888 }
8889 "});
8890}
8891
8892#[gpui::test]
8893async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
8894 init_test(cx, |_| {});
8895
8896 let language = Arc::new(Language::new(
8897 LanguageConfig {
8898 line_comments: vec!["// ".into()],
8899 ..Default::default()
8900 },
8901 Some(tree_sitter_rust::LANGUAGE.into()),
8902 ));
8903
8904 let mut cx = EditorTestContext::new(cx).await;
8905
8906 cx.language_registry().add(language.clone());
8907 cx.update_buffer(|buffer, cx| {
8908 buffer.set_language(Some(language), cx);
8909 });
8910
8911 let toggle_comments = &ToggleComments {
8912 advance_downwards: true,
8913 ignore_indent: false,
8914 };
8915
8916 // Single cursor on one line -> advance
8917 // Cursor moves horizontally 3 characters as well on non-blank line
8918 cx.set_state(indoc!(
8919 "fn a() {
8920 ˇdog();
8921 cat();
8922 }"
8923 ));
8924 cx.update_editor(|editor, cx| {
8925 editor.toggle_comments(toggle_comments, cx);
8926 });
8927 cx.assert_editor_state(indoc!(
8928 "fn a() {
8929 // dog();
8930 catˇ();
8931 }"
8932 ));
8933
8934 // Single selection on one line -> don't advance
8935 cx.set_state(indoc!(
8936 "fn a() {
8937 «dog()ˇ»;
8938 cat();
8939 }"
8940 ));
8941 cx.update_editor(|editor, cx| {
8942 editor.toggle_comments(toggle_comments, cx);
8943 });
8944 cx.assert_editor_state(indoc!(
8945 "fn a() {
8946 // «dog()ˇ»;
8947 cat();
8948 }"
8949 ));
8950
8951 // Multiple cursors on one line -> advance
8952 cx.set_state(indoc!(
8953 "fn a() {
8954 ˇdˇog();
8955 cat();
8956 }"
8957 ));
8958 cx.update_editor(|editor, cx| {
8959 editor.toggle_comments(toggle_comments, cx);
8960 });
8961 cx.assert_editor_state(indoc!(
8962 "fn a() {
8963 // dog();
8964 catˇ(ˇ);
8965 }"
8966 ));
8967
8968 // Multiple cursors on one line, with selection -> don't advance
8969 cx.set_state(indoc!(
8970 "fn a() {
8971 ˇdˇog«()ˇ»;
8972 cat();
8973 }"
8974 ));
8975 cx.update_editor(|editor, cx| {
8976 editor.toggle_comments(toggle_comments, cx);
8977 });
8978 cx.assert_editor_state(indoc!(
8979 "fn a() {
8980 // ˇdˇog«()ˇ»;
8981 cat();
8982 }"
8983 ));
8984
8985 // Single cursor on one line -> advance
8986 // Cursor moves to column 0 on blank line
8987 cx.set_state(indoc!(
8988 "fn a() {
8989 ˇdog();
8990
8991 cat();
8992 }"
8993 ));
8994 cx.update_editor(|editor, cx| {
8995 editor.toggle_comments(toggle_comments, cx);
8996 });
8997 cx.assert_editor_state(indoc!(
8998 "fn a() {
8999 // dog();
9000 ˇ
9001 cat();
9002 }"
9003 ));
9004
9005 // Single cursor on one line -> advance
9006 // Cursor starts and ends at column 0
9007 cx.set_state(indoc!(
9008 "fn a() {
9009 ˇ dog();
9010 cat();
9011 }"
9012 ));
9013 cx.update_editor(|editor, cx| {
9014 editor.toggle_comments(toggle_comments, cx);
9015 });
9016 cx.assert_editor_state(indoc!(
9017 "fn a() {
9018 // dog();
9019 ˇ cat();
9020 }"
9021 ));
9022}
9023
9024#[gpui::test]
9025async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
9026 init_test(cx, |_| {});
9027
9028 let mut cx = EditorTestContext::new(cx).await;
9029
9030 let html_language = Arc::new(
9031 Language::new(
9032 LanguageConfig {
9033 name: "HTML".into(),
9034 block_comment: Some(("<!-- ".into(), " -->".into())),
9035 ..Default::default()
9036 },
9037 Some(tree_sitter_html::language()),
9038 )
9039 .with_injection_query(
9040 r#"
9041 (script_element
9042 (raw_text) @content
9043 (#set! "language" "javascript"))
9044 "#,
9045 )
9046 .unwrap(),
9047 );
9048
9049 let javascript_language = Arc::new(Language::new(
9050 LanguageConfig {
9051 name: "JavaScript".into(),
9052 line_comments: vec!["// ".into()],
9053 ..Default::default()
9054 },
9055 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
9056 ));
9057
9058 cx.language_registry().add(html_language.clone());
9059 cx.language_registry().add(javascript_language.clone());
9060 cx.update_buffer(|buffer, cx| {
9061 buffer.set_language(Some(html_language), cx);
9062 });
9063
9064 // Toggle comments for empty selections
9065 cx.set_state(
9066 &r#"
9067 <p>A</p>ˇ
9068 <p>B</p>ˇ
9069 <p>C</p>ˇ
9070 "#
9071 .unindent(),
9072 );
9073 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9074 cx.assert_editor_state(
9075 &r#"
9076 <!-- <p>A</p>ˇ -->
9077 <!-- <p>B</p>ˇ -->
9078 <!-- <p>C</p>ˇ -->
9079 "#
9080 .unindent(),
9081 );
9082 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9083 cx.assert_editor_state(
9084 &r#"
9085 <p>A</p>ˇ
9086 <p>B</p>ˇ
9087 <p>C</p>ˇ
9088 "#
9089 .unindent(),
9090 );
9091
9092 // Toggle comments for mixture of empty and non-empty selections, where
9093 // multiple selections occupy a given line.
9094 cx.set_state(
9095 &r#"
9096 <p>A«</p>
9097 <p>ˇ»B</p>ˇ
9098 <p>C«</p>
9099 <p>ˇ»D</p>ˇ
9100 "#
9101 .unindent(),
9102 );
9103
9104 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9105 cx.assert_editor_state(
9106 &r#"
9107 <!-- <p>A«</p>
9108 <p>ˇ»B</p>ˇ -->
9109 <!-- <p>C«</p>
9110 <p>ˇ»D</p>ˇ -->
9111 "#
9112 .unindent(),
9113 );
9114 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9115 cx.assert_editor_state(
9116 &r#"
9117 <p>A«</p>
9118 <p>ˇ»B</p>ˇ
9119 <p>C«</p>
9120 <p>ˇ»D</p>ˇ
9121 "#
9122 .unindent(),
9123 );
9124
9125 // Toggle comments when different languages are active for different
9126 // selections.
9127 cx.set_state(
9128 &r#"
9129 ˇ<script>
9130 ˇvar x = new Y();
9131 ˇ</script>
9132 "#
9133 .unindent(),
9134 );
9135 cx.executor().run_until_parked();
9136 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9137 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
9138 // Uncommenting and commenting from this position brings in even more wrong artifacts.
9139 cx.assert_editor_state(
9140 &r#"
9141 <!-- ˇ<script> -->
9142 // ˇvar x = new Y();
9143 // ˇ</script>
9144 "#
9145 .unindent(),
9146 );
9147}
9148
9149#[gpui::test]
9150fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
9151 init_test(cx, |_| {});
9152
9153 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9154 let multibuffer = cx.new_model(|cx| {
9155 let mut multibuffer = MultiBuffer::new(ReadWrite);
9156 multibuffer.push_excerpts(
9157 buffer.clone(),
9158 [
9159 ExcerptRange {
9160 context: Point::new(0, 0)..Point::new(0, 4),
9161 primary: None,
9162 },
9163 ExcerptRange {
9164 context: Point::new(1, 0)..Point::new(1, 4),
9165 primary: None,
9166 },
9167 ],
9168 cx,
9169 );
9170 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
9171 multibuffer
9172 });
9173
9174 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9175 view.update(cx, |view, cx| {
9176 assert_eq!(view.text(cx), "aaaa\nbbbb");
9177 view.change_selections(None, cx, |s| {
9178 s.select_ranges([
9179 Point::new(0, 0)..Point::new(0, 0),
9180 Point::new(1, 0)..Point::new(1, 0),
9181 ])
9182 });
9183
9184 view.handle_input("X", cx);
9185 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
9186 assert_eq!(
9187 view.selections.ranges(cx),
9188 [
9189 Point::new(0, 1)..Point::new(0, 1),
9190 Point::new(1, 1)..Point::new(1, 1),
9191 ]
9192 );
9193
9194 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
9195 view.change_selections(None, cx, |s| {
9196 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
9197 });
9198 view.backspace(&Default::default(), cx);
9199 assert_eq!(view.text(cx), "Xa\nbbb");
9200 assert_eq!(
9201 view.selections.ranges(cx),
9202 [Point::new(1, 0)..Point::new(1, 0)]
9203 );
9204
9205 view.change_selections(None, cx, |s| {
9206 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
9207 });
9208 view.backspace(&Default::default(), cx);
9209 assert_eq!(view.text(cx), "X\nbb");
9210 assert_eq!(
9211 view.selections.ranges(cx),
9212 [Point::new(0, 1)..Point::new(0, 1)]
9213 );
9214 });
9215}
9216
9217#[gpui::test]
9218fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
9219 init_test(cx, |_| {});
9220
9221 let markers = vec![('[', ']').into(), ('(', ')').into()];
9222 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
9223 indoc! {"
9224 [aaaa
9225 (bbbb]
9226 cccc)",
9227 },
9228 markers.clone(),
9229 );
9230 let excerpt_ranges = markers.into_iter().map(|marker| {
9231 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9232 ExcerptRange {
9233 context,
9234 primary: None,
9235 }
9236 });
9237 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
9238 let multibuffer = cx.new_model(|cx| {
9239 let mut multibuffer = MultiBuffer::new(ReadWrite);
9240 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9241 multibuffer
9242 });
9243
9244 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9245 view.update(cx, |view, cx| {
9246 let (expected_text, selection_ranges) = marked_text_ranges(
9247 indoc! {"
9248 aaaa
9249 bˇbbb
9250 bˇbbˇb
9251 cccc"
9252 },
9253 true,
9254 );
9255 assert_eq!(view.text(cx), expected_text);
9256 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
9257
9258 view.handle_input("X", cx);
9259
9260 let (expected_text, expected_selections) = marked_text_ranges(
9261 indoc! {"
9262 aaaa
9263 bXˇbbXb
9264 bXˇbbXˇb
9265 cccc"
9266 },
9267 false,
9268 );
9269 assert_eq!(view.text(cx), expected_text);
9270 assert_eq!(view.selections.ranges(cx), expected_selections);
9271
9272 view.newline(&Newline, cx);
9273 let (expected_text, expected_selections) = marked_text_ranges(
9274 indoc! {"
9275 aaaa
9276 bX
9277 ˇbbX
9278 b
9279 bX
9280 ˇbbX
9281 ˇb
9282 cccc"
9283 },
9284 false,
9285 );
9286 assert_eq!(view.text(cx), expected_text);
9287 assert_eq!(view.selections.ranges(cx), expected_selections);
9288 });
9289}
9290
9291#[gpui::test]
9292fn test_refresh_selections(cx: &mut TestAppContext) {
9293 init_test(cx, |_| {});
9294
9295 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9296 let mut excerpt1_id = None;
9297 let multibuffer = cx.new_model(|cx| {
9298 let mut multibuffer = MultiBuffer::new(ReadWrite);
9299 excerpt1_id = multibuffer
9300 .push_excerpts(
9301 buffer.clone(),
9302 [
9303 ExcerptRange {
9304 context: Point::new(0, 0)..Point::new(1, 4),
9305 primary: None,
9306 },
9307 ExcerptRange {
9308 context: Point::new(1, 0)..Point::new(2, 4),
9309 primary: None,
9310 },
9311 ],
9312 cx,
9313 )
9314 .into_iter()
9315 .next();
9316 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9317 multibuffer
9318 });
9319
9320 let editor = cx.add_window(|cx| {
9321 let mut editor = build_editor(multibuffer.clone(), cx);
9322 let snapshot = editor.snapshot(cx);
9323 editor.change_selections(None, cx, |s| {
9324 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
9325 });
9326 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
9327 assert_eq!(
9328 editor.selections.ranges(cx),
9329 [
9330 Point::new(1, 3)..Point::new(1, 3),
9331 Point::new(2, 1)..Point::new(2, 1),
9332 ]
9333 );
9334 editor
9335 });
9336
9337 // Refreshing selections is a no-op when excerpts haven't changed.
9338 _ = editor.update(cx, |editor, cx| {
9339 editor.change_selections(None, cx, |s| s.refresh());
9340 assert_eq!(
9341 editor.selections.ranges(cx),
9342 [
9343 Point::new(1, 3)..Point::new(1, 3),
9344 Point::new(2, 1)..Point::new(2, 1),
9345 ]
9346 );
9347 });
9348
9349 multibuffer.update(cx, |multibuffer, cx| {
9350 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9351 });
9352 _ = editor.update(cx, |editor, cx| {
9353 // Removing an excerpt causes the first selection to become degenerate.
9354 assert_eq!(
9355 editor.selections.ranges(cx),
9356 [
9357 Point::new(0, 0)..Point::new(0, 0),
9358 Point::new(0, 1)..Point::new(0, 1)
9359 ]
9360 );
9361
9362 // Refreshing selections will relocate the first selection to the original buffer
9363 // location.
9364 editor.change_selections(None, cx, |s| s.refresh());
9365 assert_eq!(
9366 editor.selections.ranges(cx),
9367 [
9368 Point::new(0, 1)..Point::new(0, 1),
9369 Point::new(0, 3)..Point::new(0, 3)
9370 ]
9371 );
9372 assert!(editor.selections.pending_anchor().is_some());
9373 });
9374}
9375
9376#[gpui::test]
9377fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
9378 init_test(cx, |_| {});
9379
9380 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9381 let mut excerpt1_id = None;
9382 let multibuffer = cx.new_model(|cx| {
9383 let mut multibuffer = MultiBuffer::new(ReadWrite);
9384 excerpt1_id = multibuffer
9385 .push_excerpts(
9386 buffer.clone(),
9387 [
9388 ExcerptRange {
9389 context: Point::new(0, 0)..Point::new(1, 4),
9390 primary: None,
9391 },
9392 ExcerptRange {
9393 context: Point::new(1, 0)..Point::new(2, 4),
9394 primary: None,
9395 },
9396 ],
9397 cx,
9398 )
9399 .into_iter()
9400 .next();
9401 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9402 multibuffer
9403 });
9404
9405 let editor = cx.add_window(|cx| {
9406 let mut editor = build_editor(multibuffer.clone(), cx);
9407 let snapshot = editor.snapshot(cx);
9408 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
9409 assert_eq!(
9410 editor.selections.ranges(cx),
9411 [Point::new(1, 3)..Point::new(1, 3)]
9412 );
9413 editor
9414 });
9415
9416 multibuffer.update(cx, |multibuffer, cx| {
9417 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9418 });
9419 _ = editor.update(cx, |editor, cx| {
9420 assert_eq!(
9421 editor.selections.ranges(cx),
9422 [Point::new(0, 0)..Point::new(0, 0)]
9423 );
9424
9425 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
9426 editor.change_selections(None, cx, |s| s.refresh());
9427 assert_eq!(
9428 editor.selections.ranges(cx),
9429 [Point::new(0, 3)..Point::new(0, 3)]
9430 );
9431 assert!(editor.selections.pending_anchor().is_some());
9432 });
9433}
9434
9435#[gpui::test]
9436async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
9437 init_test(cx, |_| {});
9438
9439 let language = Arc::new(
9440 Language::new(
9441 LanguageConfig {
9442 brackets: BracketPairConfig {
9443 pairs: vec![
9444 BracketPair {
9445 start: "{".to_string(),
9446 end: "}".to_string(),
9447 close: true,
9448 surround: true,
9449 newline: true,
9450 },
9451 BracketPair {
9452 start: "/* ".to_string(),
9453 end: " */".to_string(),
9454 close: true,
9455 surround: true,
9456 newline: true,
9457 },
9458 ],
9459 ..Default::default()
9460 },
9461 ..Default::default()
9462 },
9463 Some(tree_sitter_rust::LANGUAGE.into()),
9464 )
9465 .with_indents_query("")
9466 .unwrap(),
9467 );
9468
9469 let text = concat!(
9470 "{ }\n", //
9471 " x\n", //
9472 " /* */\n", //
9473 "x\n", //
9474 "{{} }\n", //
9475 );
9476
9477 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
9478 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9479 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9480 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
9481 .await;
9482
9483 view.update(cx, |view, cx| {
9484 view.change_selections(None, cx, |s| {
9485 s.select_display_ranges([
9486 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
9487 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
9488 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
9489 ])
9490 });
9491 view.newline(&Newline, cx);
9492
9493 assert_eq!(
9494 view.buffer().read(cx).read(cx).text(),
9495 concat!(
9496 "{ \n", // Suppress rustfmt
9497 "\n", //
9498 "}\n", //
9499 " x\n", //
9500 " /* \n", //
9501 " \n", //
9502 " */\n", //
9503 "x\n", //
9504 "{{} \n", //
9505 "}\n", //
9506 )
9507 );
9508 });
9509}
9510
9511#[gpui::test]
9512fn test_highlighted_ranges(cx: &mut TestAppContext) {
9513 init_test(cx, |_| {});
9514
9515 let editor = cx.add_window(|cx| {
9516 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
9517 build_editor(buffer.clone(), cx)
9518 });
9519
9520 _ = editor.update(cx, |editor, cx| {
9521 struct Type1;
9522 struct Type2;
9523
9524 let buffer = editor.buffer.read(cx).snapshot(cx);
9525
9526 let anchor_range =
9527 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
9528
9529 editor.highlight_background::<Type1>(
9530 &[
9531 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
9532 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
9533 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
9534 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
9535 ],
9536 |_| Hsla::red(),
9537 cx,
9538 );
9539 editor.highlight_background::<Type2>(
9540 &[
9541 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
9542 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
9543 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
9544 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
9545 ],
9546 |_| Hsla::green(),
9547 cx,
9548 );
9549
9550 let snapshot = editor.snapshot(cx);
9551 let mut highlighted_ranges = editor.background_highlights_in_range(
9552 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
9553 &snapshot,
9554 cx.theme().colors(),
9555 );
9556 // Enforce a consistent ordering based on color without relying on the ordering of the
9557 // highlight's `TypeId` which is non-executor.
9558 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
9559 assert_eq!(
9560 highlighted_ranges,
9561 &[
9562 (
9563 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
9564 Hsla::red(),
9565 ),
9566 (
9567 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9568 Hsla::red(),
9569 ),
9570 (
9571 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9572 Hsla::green(),
9573 ),
9574 (
9575 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9576 Hsla::green(),
9577 ),
9578 ]
9579 );
9580 assert_eq!(
9581 editor.background_highlights_in_range(
9582 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9583 &snapshot,
9584 cx.theme().colors(),
9585 ),
9586 &[(
9587 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9588 Hsla::red(),
9589 )]
9590 );
9591 });
9592}
9593
9594#[gpui::test]
9595async fn test_following(cx: &mut gpui::TestAppContext) {
9596 init_test(cx, |_| {});
9597
9598 let fs = FakeFs::new(cx.executor());
9599 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9600
9601 let buffer = project.update(cx, |project, cx| {
9602 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9603 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9604 });
9605 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9606 let follower = cx.update(|cx| {
9607 cx.open_window(
9608 WindowOptions {
9609 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9610 gpui::Point::new(px(0.), px(0.)),
9611 gpui::Point::new(px(10.), px(80.)),
9612 ))),
9613 ..Default::default()
9614 },
9615 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9616 )
9617 .unwrap()
9618 });
9619
9620 let is_still_following = Rc::new(RefCell::new(true));
9621 let follower_edit_event_count = Rc::new(RefCell::new(0));
9622 let pending_update = Rc::new(RefCell::new(None));
9623 _ = follower.update(cx, {
9624 let update = pending_update.clone();
9625 let is_still_following = is_still_following.clone();
9626 let follower_edit_event_count = follower_edit_event_count.clone();
9627 |_, cx| {
9628 cx.subscribe(
9629 &leader.root_view(cx).unwrap(),
9630 move |_, leader, event, cx| {
9631 leader
9632 .read(cx)
9633 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9634 },
9635 )
9636 .detach();
9637
9638 cx.subscribe(
9639 &follower.root_view(cx).unwrap(),
9640 move |_, _, event: &EditorEvent, _cx| {
9641 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
9642 *is_still_following.borrow_mut() = false;
9643 }
9644
9645 if let EditorEvent::BufferEdited = event {
9646 *follower_edit_event_count.borrow_mut() += 1;
9647 }
9648 },
9649 )
9650 .detach();
9651 }
9652 });
9653
9654 // Update the selections only
9655 _ = leader.update(cx, |leader, cx| {
9656 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9657 });
9658 follower
9659 .update(cx, |follower, cx| {
9660 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9661 })
9662 .unwrap()
9663 .await
9664 .unwrap();
9665 _ = follower.update(cx, |follower, cx| {
9666 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
9667 });
9668 assert!(*is_still_following.borrow());
9669 assert_eq!(*follower_edit_event_count.borrow(), 0);
9670
9671 // Update the scroll position only
9672 _ = leader.update(cx, |leader, cx| {
9673 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9674 });
9675 follower
9676 .update(cx, |follower, cx| {
9677 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9678 })
9679 .unwrap()
9680 .await
9681 .unwrap();
9682 assert_eq!(
9683 follower
9684 .update(cx, |follower, cx| follower.scroll_position(cx))
9685 .unwrap(),
9686 gpui::Point::new(1.5, 3.5)
9687 );
9688 assert!(*is_still_following.borrow());
9689 assert_eq!(*follower_edit_event_count.borrow(), 0);
9690
9691 // Update the selections and scroll position. The follower's scroll position is updated
9692 // via autoscroll, not via the leader's exact scroll position.
9693 _ = leader.update(cx, |leader, cx| {
9694 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
9695 leader.request_autoscroll(Autoscroll::newest(), cx);
9696 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
9697 });
9698 follower
9699 .update(cx, |follower, cx| {
9700 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9701 })
9702 .unwrap()
9703 .await
9704 .unwrap();
9705 _ = follower.update(cx, |follower, cx| {
9706 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
9707 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
9708 });
9709 assert!(*is_still_following.borrow());
9710
9711 // Creating a pending selection that precedes another selection
9712 _ = leader.update(cx, |leader, cx| {
9713 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
9714 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
9715 });
9716 follower
9717 .update(cx, |follower, cx| {
9718 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9719 })
9720 .unwrap()
9721 .await
9722 .unwrap();
9723 _ = follower.update(cx, |follower, cx| {
9724 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
9725 });
9726 assert!(*is_still_following.borrow());
9727
9728 // Extend the pending selection so that it surrounds another selection
9729 _ = leader.update(cx, |leader, cx| {
9730 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
9731 });
9732 follower
9733 .update(cx, |follower, cx| {
9734 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
9735 })
9736 .unwrap()
9737 .await
9738 .unwrap();
9739 _ = follower.update(cx, |follower, cx| {
9740 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
9741 });
9742
9743 // Scrolling locally breaks the follow
9744 _ = follower.update(cx, |follower, cx| {
9745 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
9746 follower.set_scroll_anchor(
9747 ScrollAnchor {
9748 anchor: top_anchor,
9749 offset: gpui::Point::new(0.0, 0.5),
9750 },
9751 cx,
9752 );
9753 });
9754 assert!(!(*is_still_following.borrow()));
9755}
9756
9757#[gpui::test]
9758async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
9759 init_test(cx, |_| {});
9760
9761 let fs = FakeFs::new(cx.executor());
9762 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9763 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
9764 let pane = workspace
9765 .update(cx, |workspace, _| workspace.active_pane().clone())
9766 .unwrap();
9767
9768 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
9769
9770 let leader = pane.update(cx, |_, cx| {
9771 let multibuffer = cx.new_model(|_| MultiBuffer::new(ReadWrite));
9772 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
9773 });
9774
9775 // Start following the editor when it has no excerpts.
9776 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9777 let follower_1 = cx
9778 .update_window(*workspace.deref(), |_, cx| {
9779 Editor::from_state_proto(
9780 workspace.root_view(cx).unwrap(),
9781 ViewId {
9782 creator: Default::default(),
9783 id: 0,
9784 },
9785 &mut state_message,
9786 cx,
9787 )
9788 })
9789 .unwrap()
9790 .unwrap()
9791 .await
9792 .unwrap();
9793
9794 let update_message = Rc::new(RefCell::new(None));
9795 follower_1.update(cx, {
9796 let update = update_message.clone();
9797 |_, cx| {
9798 cx.subscribe(&leader, move |_, leader, event, cx| {
9799 leader
9800 .read(cx)
9801 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
9802 })
9803 .detach();
9804 }
9805 });
9806
9807 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
9808 (
9809 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
9810 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
9811 )
9812 });
9813
9814 // Insert some excerpts.
9815 leader.update(cx, |leader, cx| {
9816 leader.buffer.update(cx, |multibuffer, cx| {
9817 let excerpt_ids = multibuffer.push_excerpts(
9818 buffer_1.clone(),
9819 [
9820 ExcerptRange {
9821 context: 1..6,
9822 primary: None,
9823 },
9824 ExcerptRange {
9825 context: 12..15,
9826 primary: None,
9827 },
9828 ExcerptRange {
9829 context: 0..3,
9830 primary: None,
9831 },
9832 ],
9833 cx,
9834 );
9835 multibuffer.insert_excerpts_after(
9836 excerpt_ids[0],
9837 buffer_2.clone(),
9838 [
9839 ExcerptRange {
9840 context: 8..12,
9841 primary: None,
9842 },
9843 ExcerptRange {
9844 context: 0..6,
9845 primary: None,
9846 },
9847 ],
9848 cx,
9849 );
9850 });
9851 });
9852
9853 // Apply the update of adding the excerpts.
9854 follower_1
9855 .update(cx, |follower, cx| {
9856 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9857 })
9858 .await
9859 .unwrap();
9860 assert_eq!(
9861 follower_1.update(cx, |editor, cx| editor.text(cx)),
9862 leader.update(cx, |editor, cx| editor.text(cx))
9863 );
9864 update_message.borrow_mut().take();
9865
9866 // Start following separately after it already has excerpts.
9867 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
9868 let follower_2 = cx
9869 .update_window(*workspace.deref(), |_, cx| {
9870 Editor::from_state_proto(
9871 workspace.root_view(cx).unwrap().clone(),
9872 ViewId {
9873 creator: Default::default(),
9874 id: 0,
9875 },
9876 &mut state_message,
9877 cx,
9878 )
9879 })
9880 .unwrap()
9881 .unwrap()
9882 .await
9883 .unwrap();
9884 assert_eq!(
9885 follower_2.update(cx, |editor, cx| editor.text(cx)),
9886 leader.update(cx, |editor, cx| editor.text(cx))
9887 );
9888
9889 // Remove some excerpts.
9890 leader.update(cx, |leader, cx| {
9891 leader.buffer.update(cx, |multibuffer, cx| {
9892 let excerpt_ids = multibuffer.excerpt_ids();
9893 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
9894 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
9895 });
9896 });
9897
9898 // Apply the update of removing the excerpts.
9899 follower_1
9900 .update(cx, |follower, cx| {
9901 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9902 })
9903 .await
9904 .unwrap();
9905 follower_2
9906 .update(cx, |follower, cx| {
9907 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
9908 })
9909 .await
9910 .unwrap();
9911 update_message.borrow_mut().take();
9912 assert_eq!(
9913 follower_1.update(cx, |editor, cx| editor.text(cx)),
9914 leader.update(cx, |editor, cx| editor.text(cx))
9915 );
9916}
9917
9918#[gpui::test]
9919async fn go_to_prev_overlapping_diagnostic(
9920 executor: BackgroundExecutor,
9921 cx: &mut gpui::TestAppContext,
9922) {
9923 init_test(cx, |_| {});
9924
9925 let mut cx = EditorTestContext::new(cx).await;
9926 let lsp_store =
9927 cx.update_editor(|editor, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
9928
9929 cx.set_state(indoc! {"
9930 ˇfn func(abc def: i32) -> u32 {
9931 }
9932 "});
9933
9934 cx.update(|cx| {
9935 lsp_store.update(cx, |lsp_store, cx| {
9936 lsp_store
9937 .update_diagnostics(
9938 LanguageServerId(0),
9939 lsp::PublishDiagnosticsParams {
9940 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9941 version: None,
9942 diagnostics: vec![
9943 lsp::Diagnostic {
9944 range: lsp::Range::new(
9945 lsp::Position::new(0, 11),
9946 lsp::Position::new(0, 12),
9947 ),
9948 severity: Some(lsp::DiagnosticSeverity::ERROR),
9949 ..Default::default()
9950 },
9951 lsp::Diagnostic {
9952 range: lsp::Range::new(
9953 lsp::Position::new(0, 12),
9954 lsp::Position::new(0, 15),
9955 ),
9956 severity: Some(lsp::DiagnosticSeverity::ERROR),
9957 ..Default::default()
9958 },
9959 lsp::Diagnostic {
9960 range: lsp::Range::new(
9961 lsp::Position::new(0, 25),
9962 lsp::Position::new(0, 28),
9963 ),
9964 severity: Some(lsp::DiagnosticSeverity::ERROR),
9965 ..Default::default()
9966 },
9967 ],
9968 },
9969 &[],
9970 cx,
9971 )
9972 .unwrap()
9973 });
9974 });
9975
9976 executor.run_until_parked();
9977
9978 cx.update_editor(|editor, cx| {
9979 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9980 });
9981
9982 cx.assert_editor_state(indoc! {"
9983 fn func(abc def: i32) -> ˇu32 {
9984 }
9985 "});
9986
9987 cx.update_editor(|editor, cx| {
9988 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9989 });
9990
9991 cx.assert_editor_state(indoc! {"
9992 fn func(abc ˇdef: i32) -> u32 {
9993 }
9994 "});
9995
9996 cx.update_editor(|editor, cx| {
9997 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9998 });
9999
10000 cx.assert_editor_state(indoc! {"
10001 fn func(abcˇ def: i32) -> u32 {
10002 }
10003 "});
10004
10005 cx.update_editor(|editor, cx| {
10006 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10007 });
10008
10009 cx.assert_editor_state(indoc! {"
10010 fn func(abc def: i32) -> ˇu32 {
10011 }
10012 "});
10013}
10014
10015#[gpui::test]
10016async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
10017 init_test(cx, |_| {});
10018
10019 let mut cx = EditorTestContext::new(cx).await;
10020
10021 cx.set_state(indoc! {"
10022 fn func(abˇc def: i32) -> u32 {
10023 }
10024 "});
10025 let lsp_store =
10026 cx.update_editor(|editor, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10027
10028 cx.update(|cx| {
10029 lsp_store.update(cx, |lsp_store, cx| {
10030 lsp_store.update_diagnostics(
10031 LanguageServerId(0),
10032 lsp::PublishDiagnosticsParams {
10033 uri: lsp::Url::from_file_path("/root/file").unwrap(),
10034 version: None,
10035 diagnostics: vec![lsp::Diagnostic {
10036 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
10037 severity: Some(lsp::DiagnosticSeverity::ERROR),
10038 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
10039 ..Default::default()
10040 }],
10041 },
10042 &[],
10043 cx,
10044 )
10045 })
10046 }).unwrap();
10047 cx.run_until_parked();
10048 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
10049 cx.run_until_parked();
10050 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
10051}
10052
10053#[gpui::test]
10054async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10055 init_test(cx, |_| {});
10056
10057 let mut cx = EditorTestContext::new(cx).await;
10058
10059 let diff_base = r#"
10060 use some::mod;
10061
10062 const A: u32 = 42;
10063
10064 fn main() {
10065 println!("hello");
10066
10067 println!("world");
10068 }
10069 "#
10070 .unindent();
10071
10072 // Edits are modified, removed, modified, added
10073 cx.set_state(
10074 &r#"
10075 use some::modified;
10076
10077 ˇ
10078 fn main() {
10079 println!("hello there");
10080
10081 println!("around the");
10082 println!("world");
10083 }
10084 "#
10085 .unindent(),
10086 );
10087
10088 cx.set_diff_base(&diff_base);
10089 executor.run_until_parked();
10090
10091 cx.update_editor(|editor, cx| {
10092 //Wrap around the bottom of the buffer
10093 for _ in 0..3 {
10094 editor.go_to_next_hunk(&GoToHunk, cx);
10095 }
10096 });
10097
10098 cx.assert_editor_state(
10099 &r#"
10100 ˇuse some::modified;
10101
10102
10103 fn main() {
10104 println!("hello there");
10105
10106 println!("around the");
10107 println!("world");
10108 }
10109 "#
10110 .unindent(),
10111 );
10112
10113 cx.update_editor(|editor, cx| {
10114 //Wrap around the top of the buffer
10115 for _ in 0..2 {
10116 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10117 }
10118 });
10119
10120 cx.assert_editor_state(
10121 &r#"
10122 use some::modified;
10123
10124
10125 fn main() {
10126 ˇ println!("hello there");
10127
10128 println!("around the");
10129 println!("world");
10130 }
10131 "#
10132 .unindent(),
10133 );
10134
10135 cx.update_editor(|editor, cx| {
10136 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10137 });
10138
10139 cx.assert_editor_state(
10140 &r#"
10141 use some::modified;
10142
10143 ˇ
10144 fn main() {
10145 println!("hello there");
10146
10147 println!("around the");
10148 println!("world");
10149 }
10150 "#
10151 .unindent(),
10152 );
10153
10154 cx.update_editor(|editor, cx| {
10155 for _ in 0..3 {
10156 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10157 }
10158 });
10159
10160 cx.assert_editor_state(
10161 &r#"
10162 use some::modified;
10163
10164
10165 fn main() {
10166 ˇ println!("hello there");
10167
10168 println!("around the");
10169 println!("world");
10170 }
10171 "#
10172 .unindent(),
10173 );
10174
10175 cx.update_editor(|editor, cx| {
10176 editor.fold(&Fold, cx);
10177
10178 //Make sure that the fold only gets one hunk
10179 for _ in 0..4 {
10180 editor.go_to_next_hunk(&GoToHunk, cx);
10181 }
10182 });
10183
10184 cx.assert_editor_state(
10185 &r#"
10186 ˇuse some::modified;
10187
10188
10189 fn main() {
10190 println!("hello there");
10191
10192 println!("around the");
10193 println!("world");
10194 }
10195 "#
10196 .unindent(),
10197 );
10198}
10199
10200#[test]
10201fn test_split_words() {
10202 fn split(text: &str) -> Vec<&str> {
10203 split_words(text).collect()
10204 }
10205
10206 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
10207 assert_eq!(split("hello_world"), &["hello_", "world"]);
10208 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
10209 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
10210 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
10211 assert_eq!(split("helloworld"), &["helloworld"]);
10212
10213 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
10214}
10215
10216#[gpui::test]
10217async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
10218 init_test(cx, |_| {});
10219
10220 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
10221 let mut assert = |before, after| {
10222 let _state_context = cx.set_state(before);
10223 cx.update_editor(|editor, cx| {
10224 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
10225 });
10226 cx.assert_editor_state(after);
10227 };
10228
10229 // Outside bracket jumps to outside of matching bracket
10230 assert("console.logˇ(var);", "console.log(var)ˇ;");
10231 assert("console.log(var)ˇ;", "console.logˇ(var);");
10232
10233 // Inside bracket jumps to inside of matching bracket
10234 assert("console.log(ˇvar);", "console.log(varˇ);");
10235 assert("console.log(varˇ);", "console.log(ˇvar);");
10236
10237 // When outside a bracket and inside, favor jumping to the inside bracket
10238 assert(
10239 "console.log('foo', [1, 2, 3]ˇ);",
10240 "console.log(ˇ'foo', [1, 2, 3]);",
10241 );
10242 assert(
10243 "console.log(ˇ'foo', [1, 2, 3]);",
10244 "console.log('foo', [1, 2, 3]ˇ);",
10245 );
10246
10247 // Bias forward if two options are equally likely
10248 assert(
10249 "let result = curried_fun()ˇ();",
10250 "let result = curried_fun()()ˇ;",
10251 );
10252
10253 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
10254 assert(
10255 indoc! {"
10256 function test() {
10257 console.log('test')ˇ
10258 }"},
10259 indoc! {"
10260 function test() {
10261 console.logˇ('test')
10262 }"},
10263 );
10264}
10265
10266#[gpui::test]
10267async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
10268 init_test(cx, |_| {});
10269
10270 let fs = FakeFs::new(cx.executor());
10271 fs.insert_tree(
10272 "/a",
10273 json!({
10274 "main.rs": "fn main() { let a = 5; }",
10275 "other.rs": "// Test file",
10276 }),
10277 )
10278 .await;
10279 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10280
10281 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10282 language_registry.add(Arc::new(Language::new(
10283 LanguageConfig {
10284 name: "Rust".into(),
10285 matcher: LanguageMatcher {
10286 path_suffixes: vec!["rs".to_string()],
10287 ..Default::default()
10288 },
10289 brackets: BracketPairConfig {
10290 pairs: vec![BracketPair {
10291 start: "{".to_string(),
10292 end: "}".to_string(),
10293 close: true,
10294 surround: true,
10295 newline: true,
10296 }],
10297 disabled_scopes_by_bracket_ix: Vec::new(),
10298 },
10299 ..Default::default()
10300 },
10301 Some(tree_sitter_rust::LANGUAGE.into()),
10302 )));
10303 let mut fake_servers = language_registry.register_fake_lsp(
10304 "Rust",
10305 FakeLspAdapter {
10306 capabilities: lsp::ServerCapabilities {
10307 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
10308 first_trigger_character: "{".to_string(),
10309 more_trigger_character: None,
10310 }),
10311 ..Default::default()
10312 },
10313 ..Default::default()
10314 },
10315 );
10316
10317 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10318
10319 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10320
10321 let worktree_id = workspace
10322 .update(cx, |workspace, cx| {
10323 workspace.project().update(cx, |project, cx| {
10324 project.worktrees(cx).next().unwrap().read(cx).id()
10325 })
10326 })
10327 .unwrap();
10328
10329 let buffer = project
10330 .update(cx, |project, cx| {
10331 project.open_local_buffer("/a/main.rs", cx)
10332 })
10333 .await
10334 .unwrap();
10335 cx.executor().run_until_parked();
10336 cx.executor().start_waiting();
10337 let fake_server = fake_servers.next().await.unwrap();
10338 let editor_handle = workspace
10339 .update(cx, |workspace, cx| {
10340 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
10341 })
10342 .unwrap()
10343 .await
10344 .unwrap()
10345 .downcast::<Editor>()
10346 .unwrap();
10347
10348 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
10349 assert_eq!(
10350 params.text_document_position.text_document.uri,
10351 lsp::Url::from_file_path("/a/main.rs").unwrap(),
10352 );
10353 assert_eq!(
10354 params.text_document_position.position,
10355 lsp::Position::new(0, 21),
10356 );
10357
10358 Ok(Some(vec![lsp::TextEdit {
10359 new_text: "]".to_string(),
10360 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10361 }]))
10362 });
10363
10364 editor_handle.update(cx, |editor, cx| {
10365 editor.focus(cx);
10366 editor.change_selections(None, cx, |s| {
10367 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
10368 });
10369 editor.handle_input("{", cx);
10370 });
10371
10372 cx.executor().run_until_parked();
10373
10374 buffer.update(cx, |buffer, _| {
10375 assert_eq!(
10376 buffer.text(),
10377 "fn main() { let a = {5}; }",
10378 "No extra braces from on type formatting should appear in the buffer"
10379 )
10380 });
10381}
10382
10383#[gpui::test]
10384async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
10385 init_test(cx, |_| {});
10386
10387 let fs = FakeFs::new(cx.executor());
10388 fs.insert_tree(
10389 "/a",
10390 json!({
10391 "main.rs": "fn main() { let a = 5; }",
10392 "other.rs": "// Test file",
10393 }),
10394 )
10395 .await;
10396
10397 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10398
10399 let server_restarts = Arc::new(AtomicUsize::new(0));
10400 let closure_restarts = Arc::clone(&server_restarts);
10401 let language_server_name = "test language server";
10402 let language_name: LanguageName = "Rust".into();
10403
10404 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10405 language_registry.add(Arc::new(Language::new(
10406 LanguageConfig {
10407 name: language_name.clone(),
10408 matcher: LanguageMatcher {
10409 path_suffixes: vec!["rs".to_string()],
10410 ..Default::default()
10411 },
10412 ..Default::default()
10413 },
10414 Some(tree_sitter_rust::LANGUAGE.into()),
10415 )));
10416 let mut fake_servers = language_registry.register_fake_lsp(
10417 "Rust",
10418 FakeLspAdapter {
10419 name: language_server_name,
10420 initialization_options: Some(json!({
10421 "testOptionValue": true
10422 })),
10423 initializer: Some(Box::new(move |fake_server| {
10424 let task_restarts = Arc::clone(&closure_restarts);
10425 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
10426 task_restarts.fetch_add(1, atomic::Ordering::Release);
10427 futures::future::ready(Ok(()))
10428 });
10429 })),
10430 ..Default::default()
10431 },
10432 );
10433
10434 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10435 let _buffer = project
10436 .update(cx, |project, cx| {
10437 project.open_local_buffer("/a/main.rs", cx)
10438 })
10439 .await
10440 .unwrap();
10441 let _fake_server = fake_servers.next().await.unwrap();
10442 update_test_language_settings(cx, |language_settings| {
10443 language_settings.languages.insert(
10444 language_name.clone(),
10445 LanguageSettingsContent {
10446 tab_size: NonZeroU32::new(8),
10447 ..Default::default()
10448 },
10449 );
10450 });
10451 cx.executor().run_until_parked();
10452 assert_eq!(
10453 server_restarts.load(atomic::Ordering::Acquire),
10454 0,
10455 "Should not restart LSP server on an unrelated change"
10456 );
10457
10458 update_test_project_settings(cx, |project_settings| {
10459 project_settings.lsp.insert(
10460 "Some other server name".into(),
10461 LspSettings {
10462 binary: None,
10463 settings: None,
10464 initialization_options: Some(json!({
10465 "some other init value": false
10466 })),
10467 },
10468 );
10469 });
10470 cx.executor().run_until_parked();
10471 assert_eq!(
10472 server_restarts.load(atomic::Ordering::Acquire),
10473 0,
10474 "Should not restart LSP server on an unrelated LSP settings change"
10475 );
10476
10477 update_test_project_settings(cx, |project_settings| {
10478 project_settings.lsp.insert(
10479 language_server_name.into(),
10480 LspSettings {
10481 binary: None,
10482 settings: None,
10483 initialization_options: Some(json!({
10484 "anotherInitValue": false
10485 })),
10486 },
10487 );
10488 });
10489 cx.executor().run_until_parked();
10490 assert_eq!(
10491 server_restarts.load(atomic::Ordering::Acquire),
10492 1,
10493 "Should restart LSP server on a related LSP settings change"
10494 );
10495
10496 update_test_project_settings(cx, |project_settings| {
10497 project_settings.lsp.insert(
10498 language_server_name.into(),
10499 LspSettings {
10500 binary: None,
10501 settings: None,
10502 initialization_options: Some(json!({
10503 "anotherInitValue": false
10504 })),
10505 },
10506 );
10507 });
10508 cx.executor().run_until_parked();
10509 assert_eq!(
10510 server_restarts.load(atomic::Ordering::Acquire),
10511 1,
10512 "Should not restart LSP server on a related LSP settings change that is the same"
10513 );
10514
10515 update_test_project_settings(cx, |project_settings| {
10516 project_settings.lsp.insert(
10517 language_server_name.into(),
10518 LspSettings {
10519 binary: None,
10520 settings: None,
10521 initialization_options: None,
10522 },
10523 );
10524 });
10525 cx.executor().run_until_parked();
10526 assert_eq!(
10527 server_restarts.load(atomic::Ordering::Acquire),
10528 2,
10529 "Should restart LSP server on another related LSP settings change"
10530 );
10531}
10532
10533#[gpui::test]
10534async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10535 init_test(cx, |_| {});
10536
10537 let mut cx = EditorLspTestContext::new_rust(
10538 lsp::ServerCapabilities {
10539 completion_provider: Some(lsp::CompletionOptions {
10540 trigger_characters: Some(vec![".".to_string()]),
10541 resolve_provider: Some(true),
10542 ..Default::default()
10543 }),
10544 ..Default::default()
10545 },
10546 cx,
10547 )
10548 .await;
10549
10550 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10551 cx.simulate_keystroke(".");
10552 let completion_item = lsp::CompletionItem {
10553 label: "some".into(),
10554 kind: Some(lsp::CompletionItemKind::SNIPPET),
10555 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10556 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10557 kind: lsp::MarkupKind::Markdown,
10558 value: "```rust\nSome(2)\n```".to_string(),
10559 })),
10560 deprecated: Some(false),
10561 sort_text: Some("fffffff2".to_string()),
10562 filter_text: Some("some".to_string()),
10563 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10564 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10565 range: lsp::Range {
10566 start: lsp::Position {
10567 line: 0,
10568 character: 22,
10569 },
10570 end: lsp::Position {
10571 line: 0,
10572 character: 22,
10573 },
10574 },
10575 new_text: "Some(2)".to_string(),
10576 })),
10577 additional_text_edits: Some(vec![lsp::TextEdit {
10578 range: lsp::Range {
10579 start: lsp::Position {
10580 line: 0,
10581 character: 20,
10582 },
10583 end: lsp::Position {
10584 line: 0,
10585 character: 22,
10586 },
10587 },
10588 new_text: "".to_string(),
10589 }]),
10590 ..Default::default()
10591 };
10592
10593 let closure_completion_item = completion_item.clone();
10594 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10595 let task_completion_item = closure_completion_item.clone();
10596 async move {
10597 Ok(Some(lsp::CompletionResponse::Array(vec![
10598 task_completion_item,
10599 ])))
10600 }
10601 });
10602
10603 request.next().await;
10604
10605 cx.condition(|editor, _| editor.context_menu_visible())
10606 .await;
10607 let apply_additional_edits = cx.update_editor(|editor, cx| {
10608 editor
10609 .confirm_completion(&ConfirmCompletion::default(), cx)
10610 .unwrap()
10611 });
10612 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
10613
10614 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10615 let task_completion_item = completion_item.clone();
10616 async move { Ok(task_completion_item) }
10617 })
10618 .next()
10619 .await
10620 .unwrap();
10621 apply_additional_edits.await.unwrap();
10622 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10623}
10624
10625#[gpui::test]
10626async fn test_completions_resolve_updates_labels(cx: &mut gpui::TestAppContext) {
10627 init_test(cx, |_| {});
10628
10629 let mut cx = EditorLspTestContext::new_rust(
10630 lsp::ServerCapabilities {
10631 completion_provider: Some(lsp::CompletionOptions {
10632 trigger_characters: Some(vec![".".to_string()]),
10633 resolve_provider: Some(true),
10634 ..Default::default()
10635 }),
10636 ..Default::default()
10637 },
10638 cx,
10639 )
10640 .await;
10641
10642 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10643 cx.simulate_keystroke(".");
10644
10645 let completion_item = lsp::CompletionItem {
10646 label: "unresolved".to_string(),
10647 detail: None,
10648 documentation: None,
10649 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10650 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10651 new_text: ".unresolved".to_string(),
10652 })),
10653 ..lsp::CompletionItem::default()
10654 };
10655
10656 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10657 let item = completion_item.clone();
10658 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item]))) }
10659 })
10660 .next()
10661 .await;
10662
10663 cx.condition(|editor, _| editor.context_menu_visible())
10664 .await;
10665 cx.update_editor(|editor, _| {
10666 let context_menu = editor.context_menu.read();
10667 let context_menu = context_menu
10668 .as_ref()
10669 .expect("Should have the context menu deployed");
10670 match context_menu {
10671 CodeContextMenu::Completions(completions_menu) => {
10672 let completions = completions_menu.completions.read();
10673 assert_eq!(completions.len(), 1, "Should have one completion");
10674 assert_eq!(completions.get(0).unwrap().label.text, "unresolved");
10675 }
10676 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10677 }
10678 });
10679
10680 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| async move {
10681 Ok(lsp::CompletionItem {
10682 label: "resolved".to_string(),
10683 detail: Some("Now resolved!".to_string()),
10684 documentation: Some(lsp::Documentation::String("Docs".to_string())),
10685 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10686 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10687 new_text: ".resolved".to_string(),
10688 })),
10689 ..lsp::CompletionItem::default()
10690 })
10691 })
10692 .next()
10693 .await;
10694 cx.run_until_parked();
10695
10696 cx.update_editor(|editor, _| {
10697 let context_menu = editor.context_menu.read();
10698 let context_menu = context_menu
10699 .as_ref()
10700 .expect("Should have the context menu deployed");
10701 match context_menu {
10702 CodeContextMenu::Completions(completions_menu) => {
10703 let completions = completions_menu.completions.read();
10704 assert_eq!(completions.len(), 1, "Should have one completion");
10705 assert_eq!(
10706 completions.get(0).unwrap().label.text,
10707 "resolved",
10708 "Should update the completion label after resolving"
10709 );
10710 }
10711 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10712 }
10713 });
10714}
10715
10716#[gpui::test]
10717async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
10718 init_test(cx, |_| {});
10719
10720 let mut cx = EditorLspTestContext::new_rust(
10721 lsp::ServerCapabilities {
10722 completion_provider: Some(lsp::CompletionOptions {
10723 trigger_characters: Some(vec![".".to_string()]),
10724 resolve_provider: Some(true),
10725 ..Default::default()
10726 }),
10727 ..Default::default()
10728 },
10729 cx,
10730 )
10731 .await;
10732
10733 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10734 cx.simulate_keystroke(".");
10735
10736 let default_commit_characters = vec!["?".to_string()];
10737 let default_data = json!({ "very": "special"});
10738 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
10739 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
10740 let default_edit_range = lsp::Range {
10741 start: lsp::Position {
10742 line: 0,
10743 character: 5,
10744 },
10745 end: lsp::Position {
10746 line: 0,
10747 character: 5,
10748 },
10749 };
10750
10751 let resolve_requests_number = Arc::new(AtomicUsize::new(0));
10752 let expect_first_item = Arc::new(AtomicBool::new(true));
10753 cx.lsp
10754 .server
10755 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
10756 let closure_default_data = default_data.clone();
10757 let closure_resolve_requests_number = resolve_requests_number.clone();
10758 let closure_expect_first_item = expect_first_item.clone();
10759 let closure_default_commit_characters = default_commit_characters.clone();
10760 move |item_to_resolve, _| {
10761 closure_resolve_requests_number.fetch_add(1, atomic::Ordering::Release);
10762 let default_data = closure_default_data.clone();
10763 let default_commit_characters = closure_default_commit_characters.clone();
10764 let expect_first_item = closure_expect_first_item.clone();
10765 async move {
10766 if expect_first_item.load(atomic::Ordering::Acquire) {
10767 assert_eq!(
10768 item_to_resolve.label, "Some(2)",
10769 "Should have selected the first item"
10770 );
10771 assert_eq!(
10772 item_to_resolve.data,
10773 Some(json!({ "very": "special"})),
10774 "First item should bring its own data for resolving"
10775 );
10776 assert_eq!(
10777 item_to_resolve.commit_characters,
10778 Some(default_commit_characters),
10779 "First item had no own commit characters and should inherit the default ones"
10780 );
10781 assert!(
10782 matches!(
10783 item_to_resolve.text_edit,
10784 Some(lsp::CompletionTextEdit::InsertAndReplace { .. })
10785 ),
10786 "First item should bring its own edit range for resolving"
10787 );
10788 assert_eq!(
10789 item_to_resolve.insert_text_format,
10790 Some(default_insert_text_format),
10791 "First item had no own insert text format and should inherit the default one"
10792 );
10793 assert_eq!(
10794 item_to_resolve.insert_text_mode,
10795 Some(lsp::InsertTextMode::ADJUST_INDENTATION),
10796 "First item should bring its own insert text mode for resolving"
10797 );
10798 Ok(item_to_resolve)
10799 } else {
10800 assert_eq!(
10801 item_to_resolve.label, "vec![2]",
10802 "Should have selected the last item"
10803 );
10804 assert_eq!(
10805 item_to_resolve.data,
10806 Some(default_data),
10807 "Last item has no own resolve data and should inherit the default one"
10808 );
10809 assert_eq!(
10810 item_to_resolve.commit_characters,
10811 Some(default_commit_characters),
10812 "Last item had no own commit characters and should inherit the default ones"
10813 );
10814 assert_eq!(
10815 item_to_resolve.text_edit,
10816 Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10817 range: default_edit_range,
10818 new_text: "vec![2]".to_string()
10819 })),
10820 "Last item had no own edit range and should inherit the default one"
10821 );
10822 assert_eq!(
10823 item_to_resolve.insert_text_format,
10824 Some(lsp::InsertTextFormat::PLAIN_TEXT),
10825 "Last item should bring its own insert text format for resolving"
10826 );
10827 assert_eq!(
10828 item_to_resolve.insert_text_mode,
10829 Some(default_insert_text_mode),
10830 "Last item had no own insert text mode and should inherit the default one"
10831 );
10832
10833 Ok(item_to_resolve)
10834 }
10835 }
10836 }
10837 }).detach();
10838
10839 let completion_data = default_data.clone();
10840 let completion_characters = default_commit_characters.clone();
10841 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10842 let default_data = completion_data.clone();
10843 let default_commit_characters = completion_characters.clone();
10844 async move {
10845 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
10846 items: vec![
10847 lsp::CompletionItem {
10848 label: "Some(2)".into(),
10849 insert_text: Some("Some(2)".into()),
10850 data: Some(json!({ "very": "special"})),
10851 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
10852 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
10853 lsp::InsertReplaceEdit {
10854 new_text: "Some(2)".to_string(),
10855 insert: lsp::Range::default(),
10856 replace: lsp::Range::default(),
10857 },
10858 )),
10859 ..lsp::CompletionItem::default()
10860 },
10861 lsp::CompletionItem {
10862 label: "vec![2]".into(),
10863 insert_text: Some("vec![2]".into()),
10864 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
10865 ..lsp::CompletionItem::default()
10866 },
10867 ],
10868 item_defaults: Some(lsp::CompletionListItemDefaults {
10869 data: Some(default_data.clone()),
10870 commit_characters: Some(default_commit_characters.clone()),
10871 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
10872 default_edit_range,
10873 )),
10874 insert_text_format: Some(default_insert_text_format),
10875 insert_text_mode: Some(default_insert_text_mode),
10876 }),
10877 ..lsp::CompletionList::default()
10878 })))
10879 }
10880 })
10881 .next()
10882 .await;
10883
10884 cx.condition(|editor, _| editor.context_menu_visible())
10885 .await;
10886 cx.run_until_parked();
10887 cx.update_editor(|editor, _| {
10888 let menu = editor.context_menu.read();
10889 match menu.as_ref().expect("should have the completions menu") {
10890 CodeContextMenu::Completions(completions_menu) => {
10891 assert_eq!(
10892 completions_menu
10893 .matches
10894 .iter()
10895 .map(|c| c.string.as_str())
10896 .collect::<Vec<_>>(),
10897 vec!["Some(2)", "vec![2]"]
10898 );
10899 }
10900 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
10901 }
10902 });
10903 assert_eq!(
10904 resolve_requests_number.load(atomic::Ordering::Acquire),
10905 1,
10906 "While there are 2 items in the completion list, only 1 resolve request should have been sent, for the selected item"
10907 );
10908
10909 cx.update_editor(|editor, cx| {
10910 editor.context_menu_first(&ContextMenuFirst, cx);
10911 });
10912 cx.run_until_parked();
10913 assert_eq!(
10914 resolve_requests_number.load(atomic::Ordering::Acquire),
10915 2,
10916 "After re-selecting the first item, another resolve request should have been sent"
10917 );
10918
10919 expect_first_item.store(false, atomic::Ordering::Release);
10920 cx.update_editor(|editor, cx| {
10921 editor.context_menu_last(&ContextMenuLast, cx);
10922 });
10923 cx.run_until_parked();
10924 assert_eq!(
10925 resolve_requests_number.load(atomic::Ordering::Acquire),
10926 3,
10927 "After selecting the other item, another resolve request should have been sent"
10928 );
10929}
10930
10931#[gpui::test]
10932async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
10933 init_test(cx, |_| {});
10934
10935 let mut cx = EditorLspTestContext::new(
10936 Language::new(
10937 LanguageConfig {
10938 matcher: LanguageMatcher {
10939 path_suffixes: vec!["jsx".into()],
10940 ..Default::default()
10941 },
10942 overrides: [(
10943 "element".into(),
10944 LanguageConfigOverride {
10945 word_characters: Override::Set(['-'].into_iter().collect()),
10946 ..Default::default()
10947 },
10948 )]
10949 .into_iter()
10950 .collect(),
10951 ..Default::default()
10952 },
10953 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10954 )
10955 .with_override_query("(jsx_self_closing_element) @element")
10956 .unwrap(),
10957 lsp::ServerCapabilities {
10958 completion_provider: Some(lsp::CompletionOptions {
10959 trigger_characters: Some(vec![":".to_string()]),
10960 ..Default::default()
10961 }),
10962 ..Default::default()
10963 },
10964 cx,
10965 )
10966 .await;
10967
10968 cx.lsp
10969 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10970 Ok(Some(lsp::CompletionResponse::Array(vec![
10971 lsp::CompletionItem {
10972 label: "bg-blue".into(),
10973 ..Default::default()
10974 },
10975 lsp::CompletionItem {
10976 label: "bg-red".into(),
10977 ..Default::default()
10978 },
10979 lsp::CompletionItem {
10980 label: "bg-yellow".into(),
10981 ..Default::default()
10982 },
10983 ])))
10984 });
10985
10986 cx.set_state(r#"<p class="bgˇ" />"#);
10987
10988 // Trigger completion when typing a dash, because the dash is an extra
10989 // word character in the 'element' scope, which contains the cursor.
10990 cx.simulate_keystroke("-");
10991 cx.executor().run_until_parked();
10992 cx.update_editor(|editor, _| {
10993 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10994 assert_eq!(
10995 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10996 &["bg-red", "bg-blue", "bg-yellow"]
10997 );
10998 } else {
10999 panic!("expected completion menu to be open");
11000 }
11001 });
11002
11003 cx.simulate_keystroke("l");
11004 cx.executor().run_until_parked();
11005 cx.update_editor(|editor, _| {
11006 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
11007 assert_eq!(
11008 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
11009 &["bg-blue", "bg-yellow"]
11010 );
11011 } else {
11012 panic!("expected completion menu to be open");
11013 }
11014 });
11015
11016 // When filtering completions, consider the character after the '-' to
11017 // be the start of a subword.
11018 cx.set_state(r#"<p class="yelˇ" />"#);
11019 cx.simulate_keystroke("l");
11020 cx.executor().run_until_parked();
11021 cx.update_editor(|editor, _| {
11022 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
11023 assert_eq!(
11024 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
11025 &["bg-yellow"]
11026 );
11027 } else {
11028 panic!("expected completion menu to be open");
11029 }
11030 });
11031}
11032
11033#[gpui::test]
11034async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
11035 init_test(cx, |settings| {
11036 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
11037 FormatterList(vec![Formatter::Prettier].into()),
11038 ))
11039 });
11040
11041 let fs = FakeFs::new(cx.executor());
11042 fs.insert_file("/file.ts", Default::default()).await;
11043
11044 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
11045 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11046
11047 language_registry.add(Arc::new(Language::new(
11048 LanguageConfig {
11049 name: "TypeScript".into(),
11050 matcher: LanguageMatcher {
11051 path_suffixes: vec!["ts".to_string()],
11052 ..Default::default()
11053 },
11054 ..Default::default()
11055 },
11056 Some(tree_sitter_rust::LANGUAGE.into()),
11057 )));
11058 update_test_language_settings(cx, |settings| {
11059 settings.defaults.prettier = Some(PrettierSettings {
11060 allowed: true,
11061 ..PrettierSettings::default()
11062 });
11063 });
11064
11065 let test_plugin = "test_plugin";
11066 let _ = language_registry.register_fake_lsp(
11067 "TypeScript",
11068 FakeLspAdapter {
11069 prettier_plugins: vec![test_plugin],
11070 ..Default::default()
11071 },
11072 );
11073
11074 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
11075 let buffer = project
11076 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
11077 .await
11078 .unwrap();
11079
11080 let buffer_text = "one\ntwo\nthree\n";
11081 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
11082 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
11083 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
11084
11085 editor
11086 .update(cx, |editor, cx| {
11087 editor.perform_format(
11088 project.clone(),
11089 FormatTrigger::Manual,
11090 FormatTarget::Buffer,
11091 cx,
11092 )
11093 })
11094 .unwrap()
11095 .await;
11096 assert_eq!(
11097 editor.update(cx, |editor, cx| editor.text(cx)),
11098 buffer_text.to_string() + prettier_format_suffix,
11099 "Test prettier formatting was not applied to the original buffer text",
11100 );
11101
11102 update_test_language_settings(cx, |settings| {
11103 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
11104 });
11105 let format = editor.update(cx, |editor, cx| {
11106 editor.perform_format(
11107 project.clone(),
11108 FormatTrigger::Manual,
11109 FormatTarget::Buffer,
11110 cx,
11111 )
11112 });
11113 format.await.unwrap();
11114 assert_eq!(
11115 editor.update(cx, |editor, cx| editor.text(cx)),
11116 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
11117 "Autoformatting (via test prettier) was not applied to the original buffer text",
11118 );
11119}
11120
11121#[gpui::test]
11122async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
11123 init_test(cx, |_| {});
11124 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11125 let base_text = indoc! {r#"
11126 struct Row;
11127 struct Row1;
11128 struct Row2;
11129
11130 struct Row4;
11131 struct Row5;
11132 struct Row6;
11133
11134 struct Row8;
11135 struct Row9;
11136 struct Row10;"#};
11137
11138 // When addition hunks are not adjacent to carets, no hunk revert is performed
11139 assert_hunk_revert(
11140 indoc! {r#"struct Row;
11141 struct Row1;
11142 struct Row1.1;
11143 struct Row1.2;
11144 struct Row2;ˇ
11145
11146 struct Row4;
11147 struct Row5;
11148 struct Row6;
11149
11150 struct Row8;
11151 ˇstruct Row9;
11152 struct Row9.1;
11153 struct Row9.2;
11154 struct Row9.3;
11155 struct Row10;"#},
11156 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11157 indoc! {r#"struct Row;
11158 struct Row1;
11159 struct Row1.1;
11160 struct Row1.2;
11161 struct Row2;ˇ
11162
11163 struct Row4;
11164 struct Row5;
11165 struct Row6;
11166
11167 struct Row8;
11168 ˇstruct Row9;
11169 struct Row9.1;
11170 struct Row9.2;
11171 struct Row9.3;
11172 struct Row10;"#},
11173 base_text,
11174 &mut cx,
11175 );
11176 // Same for selections
11177 assert_hunk_revert(
11178 indoc! {r#"struct Row;
11179 struct Row1;
11180 struct Row2;
11181 struct Row2.1;
11182 struct Row2.2;
11183 «ˇ
11184 struct Row4;
11185 struct» Row5;
11186 «struct Row6;
11187 ˇ»
11188 struct Row9.1;
11189 struct Row9.2;
11190 struct Row9.3;
11191 struct Row8;
11192 struct Row9;
11193 struct Row10;"#},
11194 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11195 indoc! {r#"struct Row;
11196 struct Row1;
11197 struct Row2;
11198 struct Row2.1;
11199 struct Row2.2;
11200 «ˇ
11201 struct Row4;
11202 struct» Row5;
11203 «struct Row6;
11204 ˇ»
11205 struct Row9.1;
11206 struct Row9.2;
11207 struct Row9.3;
11208 struct Row8;
11209 struct Row9;
11210 struct Row10;"#},
11211 base_text,
11212 &mut cx,
11213 );
11214
11215 // When carets and selections intersect the addition hunks, those are reverted.
11216 // Adjacent carets got merged.
11217 assert_hunk_revert(
11218 indoc! {r#"struct Row;
11219 ˇ// something on the top
11220 struct Row1;
11221 struct Row2;
11222 struct Roˇw3.1;
11223 struct Row2.2;
11224 struct Row2.3;ˇ
11225
11226 struct Row4;
11227 struct ˇRow5.1;
11228 struct Row5.2;
11229 struct «Rowˇ»5.3;
11230 struct Row5;
11231 struct Row6;
11232 ˇ
11233 struct Row9.1;
11234 struct «Rowˇ»9.2;
11235 struct «ˇRow»9.3;
11236 struct Row8;
11237 struct Row9;
11238 «ˇ// something on bottom»
11239 struct Row10;"#},
11240 vec![
11241 DiffHunkStatus::Added,
11242 DiffHunkStatus::Added,
11243 DiffHunkStatus::Added,
11244 DiffHunkStatus::Added,
11245 DiffHunkStatus::Added,
11246 ],
11247 indoc! {r#"struct Row;
11248 ˇstruct Row1;
11249 struct Row2;
11250 ˇ
11251 struct Row4;
11252 ˇstruct Row5;
11253 struct Row6;
11254 ˇ
11255 ˇstruct Row8;
11256 struct Row9;
11257 ˇstruct Row10;"#},
11258 base_text,
11259 &mut cx,
11260 );
11261}
11262
11263#[gpui::test]
11264async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
11265 init_test(cx, |_| {});
11266 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11267 let base_text = indoc! {r#"
11268 struct Row;
11269 struct Row1;
11270 struct Row2;
11271
11272 struct Row4;
11273 struct Row5;
11274 struct Row6;
11275
11276 struct Row8;
11277 struct Row9;
11278 struct Row10;"#};
11279
11280 // Modification hunks behave the same as the addition ones.
11281 assert_hunk_revert(
11282 indoc! {r#"struct Row;
11283 struct Row1;
11284 struct Row33;
11285 ˇ
11286 struct Row4;
11287 struct Row5;
11288 struct Row6;
11289 ˇ
11290 struct Row99;
11291 struct Row9;
11292 struct Row10;"#},
11293 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11294 indoc! {r#"struct Row;
11295 struct Row1;
11296 struct Row33;
11297 ˇ
11298 struct Row4;
11299 struct Row5;
11300 struct Row6;
11301 ˇ
11302 struct Row99;
11303 struct Row9;
11304 struct Row10;"#},
11305 base_text,
11306 &mut cx,
11307 );
11308 assert_hunk_revert(
11309 indoc! {r#"struct Row;
11310 struct Row1;
11311 struct Row33;
11312 «ˇ
11313 struct Row4;
11314 struct» Row5;
11315 «struct Row6;
11316 ˇ»
11317 struct Row99;
11318 struct Row9;
11319 struct Row10;"#},
11320 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11321 indoc! {r#"struct Row;
11322 struct Row1;
11323 struct Row33;
11324 «ˇ
11325 struct Row4;
11326 struct» Row5;
11327 «struct Row6;
11328 ˇ»
11329 struct Row99;
11330 struct Row9;
11331 struct Row10;"#},
11332 base_text,
11333 &mut cx,
11334 );
11335
11336 assert_hunk_revert(
11337 indoc! {r#"ˇstruct Row1.1;
11338 struct Row1;
11339 «ˇstr»uct Row22;
11340
11341 struct ˇRow44;
11342 struct Row5;
11343 struct «Rˇ»ow66;ˇ
11344
11345 «struˇ»ct Row88;
11346 struct Row9;
11347 struct Row1011;ˇ"#},
11348 vec![
11349 DiffHunkStatus::Modified,
11350 DiffHunkStatus::Modified,
11351 DiffHunkStatus::Modified,
11352 DiffHunkStatus::Modified,
11353 DiffHunkStatus::Modified,
11354 DiffHunkStatus::Modified,
11355 ],
11356 indoc! {r#"struct Row;
11357 ˇstruct Row1;
11358 struct Row2;
11359 ˇ
11360 struct Row4;
11361 ˇstruct Row5;
11362 struct Row6;
11363 ˇ
11364 struct Row8;
11365 ˇstruct Row9;
11366 struct Row10;ˇ"#},
11367 base_text,
11368 &mut cx,
11369 );
11370}
11371
11372#[gpui::test]
11373async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
11374 init_test(cx, |_| {});
11375 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11376 let base_text = indoc! {r#"struct Row;
11377struct Row1;
11378struct Row2;
11379
11380struct Row4;
11381struct Row5;
11382struct Row6;
11383
11384struct Row8;
11385struct Row9;
11386struct Row10;"#};
11387
11388 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
11389 assert_hunk_revert(
11390 indoc! {r#"struct Row;
11391 struct Row2;
11392
11393 ˇstruct Row4;
11394 struct Row5;
11395 struct Row6;
11396 ˇ
11397 struct Row8;
11398 struct Row10;"#},
11399 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11400 indoc! {r#"struct Row;
11401 struct Row2;
11402
11403 ˇstruct Row4;
11404 struct Row5;
11405 struct Row6;
11406 ˇ
11407 struct Row8;
11408 struct Row10;"#},
11409 base_text,
11410 &mut cx,
11411 );
11412 assert_hunk_revert(
11413 indoc! {r#"struct Row;
11414 struct Row2;
11415
11416 «ˇstruct Row4;
11417 struct» Row5;
11418 «struct Row6;
11419 ˇ»
11420 struct Row8;
11421 struct Row10;"#},
11422 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11423 indoc! {r#"struct Row;
11424 struct Row2;
11425
11426 «ˇstruct Row4;
11427 struct» Row5;
11428 «struct Row6;
11429 ˇ»
11430 struct Row8;
11431 struct Row10;"#},
11432 base_text,
11433 &mut cx,
11434 );
11435
11436 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
11437 assert_hunk_revert(
11438 indoc! {r#"struct Row;
11439 ˇstruct Row2;
11440
11441 struct Row4;
11442 struct Row5;
11443 struct Row6;
11444
11445 struct Row8;ˇ
11446 struct Row10;"#},
11447 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11448 indoc! {r#"struct Row;
11449 struct Row1;
11450 ˇstruct Row2;
11451
11452 struct Row4;
11453 struct Row5;
11454 struct Row6;
11455
11456 struct Row8;ˇ
11457 struct Row9;
11458 struct Row10;"#},
11459 base_text,
11460 &mut cx,
11461 );
11462 assert_hunk_revert(
11463 indoc! {r#"struct Row;
11464 struct Row2«ˇ;
11465 struct Row4;
11466 struct» Row5;
11467 «struct Row6;
11468
11469 struct Row8;ˇ»
11470 struct Row10;"#},
11471 vec![
11472 DiffHunkStatus::Removed,
11473 DiffHunkStatus::Removed,
11474 DiffHunkStatus::Removed,
11475 ],
11476 indoc! {r#"struct Row;
11477 struct Row1;
11478 struct Row2«ˇ;
11479
11480 struct Row4;
11481 struct» Row5;
11482 «struct Row6;
11483
11484 struct Row8;ˇ»
11485 struct Row9;
11486 struct Row10;"#},
11487 base_text,
11488 &mut cx,
11489 );
11490}
11491
11492#[gpui::test]
11493async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
11494 init_test(cx, |_| {});
11495
11496 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
11497 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
11498 let base_text_3 =
11499 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
11500
11501 let text_1 = edit_first_char_of_every_line(base_text_1);
11502 let text_2 = edit_first_char_of_every_line(base_text_2);
11503 let text_3 = edit_first_char_of_every_line(base_text_3);
11504
11505 let buffer_1 = cx.new_model(|cx| Buffer::local(text_1.clone(), cx));
11506 let buffer_2 = cx.new_model(|cx| Buffer::local(text_2.clone(), cx));
11507 let buffer_3 = cx.new_model(|cx| Buffer::local(text_3.clone(), cx));
11508
11509 let multibuffer = cx.new_model(|cx| {
11510 let mut multibuffer = MultiBuffer::new(ReadWrite);
11511 multibuffer.push_excerpts(
11512 buffer_1.clone(),
11513 [
11514 ExcerptRange {
11515 context: Point::new(0, 0)..Point::new(3, 0),
11516 primary: None,
11517 },
11518 ExcerptRange {
11519 context: Point::new(5, 0)..Point::new(7, 0),
11520 primary: None,
11521 },
11522 ExcerptRange {
11523 context: Point::new(9, 0)..Point::new(10, 4),
11524 primary: None,
11525 },
11526 ],
11527 cx,
11528 );
11529 multibuffer.push_excerpts(
11530 buffer_2.clone(),
11531 [
11532 ExcerptRange {
11533 context: Point::new(0, 0)..Point::new(3, 0),
11534 primary: None,
11535 },
11536 ExcerptRange {
11537 context: Point::new(5, 0)..Point::new(7, 0),
11538 primary: None,
11539 },
11540 ExcerptRange {
11541 context: Point::new(9, 0)..Point::new(10, 4),
11542 primary: None,
11543 },
11544 ],
11545 cx,
11546 );
11547 multibuffer.push_excerpts(
11548 buffer_3.clone(),
11549 [
11550 ExcerptRange {
11551 context: Point::new(0, 0)..Point::new(3, 0),
11552 primary: None,
11553 },
11554 ExcerptRange {
11555 context: Point::new(5, 0)..Point::new(7, 0),
11556 primary: None,
11557 },
11558 ExcerptRange {
11559 context: Point::new(9, 0)..Point::new(10, 4),
11560 primary: None,
11561 },
11562 ],
11563 cx,
11564 );
11565 multibuffer
11566 });
11567
11568 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
11569 editor.update(cx, |editor, cx| {
11570 for (buffer, diff_base) in [
11571 (buffer_1.clone(), base_text_1),
11572 (buffer_2.clone(), base_text_2),
11573 (buffer_3.clone(), base_text_3),
11574 ] {
11575 let change_set = cx.new_model(|cx| {
11576 BufferChangeSet::new_with_base_text(
11577 diff_base.to_string(),
11578 buffer.read(cx).text_snapshot(),
11579 cx,
11580 )
11581 });
11582 editor.diff_map.add_change_set(change_set, cx)
11583 }
11584 });
11585 cx.executor().run_until_parked();
11586
11587 editor.update(cx, |editor, cx| {
11588 assert_eq!(editor.text(cx), "Xaaa\nXbbb\nXccc\n\nXfff\nXggg\n\nXjjj\nXlll\nXmmm\nXnnn\n\nXqqq\nXrrr\n\nXuuu\nXvvv\nXwww\nXxxx\n\nX{{{\nX|||\n\nX\u{7f}\u{7f}\u{7f}");
11589 editor.select_all(&SelectAll, cx);
11590 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11591 });
11592 cx.executor().run_until_parked();
11593
11594 // When all ranges are selected, all buffer hunks are reverted.
11595 editor.update(cx, |editor, cx| {
11596 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");
11597 });
11598 buffer_1.update(cx, |buffer, _| {
11599 assert_eq!(buffer.text(), base_text_1);
11600 });
11601 buffer_2.update(cx, |buffer, _| {
11602 assert_eq!(buffer.text(), base_text_2);
11603 });
11604 buffer_3.update(cx, |buffer, _| {
11605 assert_eq!(buffer.text(), base_text_3);
11606 });
11607
11608 editor.update(cx, |editor, cx| {
11609 editor.undo(&Default::default(), cx);
11610 });
11611
11612 editor.update(cx, |editor, cx| {
11613 editor.change_selections(None, cx, |s| {
11614 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
11615 });
11616 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11617 });
11618
11619 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
11620 // but not affect buffer_2 and its related excerpts.
11621 editor.update(cx, |editor, cx| {
11622 assert_eq!(
11623 editor.text(cx),
11624 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nXlll\nXmmm\nXnnn\n\nXqqq\nXrrr\n\nXuuu\nXvvv\nXwww\nXxxx\n\nX{{{\nX|||\n\nX\u{7f}\u{7f}\u{7f}"
11625 );
11626 });
11627 buffer_1.update(cx, |buffer, _| {
11628 assert_eq!(buffer.text(), base_text_1);
11629 });
11630 buffer_2.update(cx, |buffer, _| {
11631 assert_eq!(
11632 buffer.text(),
11633 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
11634 );
11635 });
11636 buffer_3.update(cx, |buffer, _| {
11637 assert_eq!(
11638 buffer.text(),
11639 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
11640 );
11641 });
11642
11643 fn edit_first_char_of_every_line(text: &str) -> String {
11644 text.split('\n')
11645 .map(|line| format!("X{}", &line[1..]))
11646 .collect::<Vec<_>>()
11647 .join("\n")
11648 }
11649}
11650
11651#[gpui::test]
11652async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
11653 init_test(cx, |_| {});
11654
11655 let cols = 4;
11656 let rows = 10;
11657 let sample_text_1 = sample_text(rows, cols, 'a');
11658 assert_eq!(
11659 sample_text_1,
11660 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11661 );
11662 let sample_text_2 = sample_text(rows, cols, 'l');
11663 assert_eq!(
11664 sample_text_2,
11665 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11666 );
11667 let sample_text_3 = sample_text(rows, cols, 'v');
11668 assert_eq!(
11669 sample_text_3,
11670 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11671 );
11672
11673 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11674 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11675 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11676
11677 let multi_buffer = cx.new_model(|cx| {
11678 let mut multibuffer = MultiBuffer::new(ReadWrite);
11679 multibuffer.push_excerpts(
11680 buffer_1.clone(),
11681 [
11682 ExcerptRange {
11683 context: Point::new(0, 0)..Point::new(3, 0),
11684 primary: None,
11685 },
11686 ExcerptRange {
11687 context: Point::new(5, 0)..Point::new(7, 0),
11688 primary: None,
11689 },
11690 ExcerptRange {
11691 context: Point::new(9, 0)..Point::new(10, 4),
11692 primary: None,
11693 },
11694 ],
11695 cx,
11696 );
11697 multibuffer.push_excerpts(
11698 buffer_2.clone(),
11699 [
11700 ExcerptRange {
11701 context: Point::new(0, 0)..Point::new(3, 0),
11702 primary: None,
11703 },
11704 ExcerptRange {
11705 context: Point::new(5, 0)..Point::new(7, 0),
11706 primary: None,
11707 },
11708 ExcerptRange {
11709 context: Point::new(9, 0)..Point::new(10, 4),
11710 primary: None,
11711 },
11712 ],
11713 cx,
11714 );
11715 multibuffer.push_excerpts(
11716 buffer_3.clone(),
11717 [
11718 ExcerptRange {
11719 context: Point::new(0, 0)..Point::new(3, 0),
11720 primary: None,
11721 },
11722 ExcerptRange {
11723 context: Point::new(5, 0)..Point::new(7, 0),
11724 primary: None,
11725 },
11726 ExcerptRange {
11727 context: Point::new(9, 0)..Point::new(10, 4),
11728 primary: None,
11729 },
11730 ],
11731 cx,
11732 );
11733 multibuffer
11734 });
11735
11736 let fs = FakeFs::new(cx.executor());
11737 fs.insert_tree(
11738 "/a",
11739 json!({
11740 "main.rs": sample_text_1,
11741 "other.rs": sample_text_2,
11742 "lib.rs": sample_text_3,
11743 }),
11744 )
11745 .await;
11746 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11747 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11748 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11749 let multi_buffer_editor = cx.new_view(|cx| {
11750 Editor::new(
11751 EditorMode::Full,
11752 multi_buffer,
11753 Some(project.clone()),
11754 true,
11755 cx,
11756 )
11757 });
11758 let multibuffer_item_id = workspace
11759 .update(cx, |workspace, cx| {
11760 assert!(
11761 workspace.active_item(cx).is_none(),
11762 "active item should be None before the first item is added"
11763 );
11764 workspace.add_item_to_active_pane(
11765 Box::new(multi_buffer_editor.clone()),
11766 None,
11767 true,
11768 cx,
11769 );
11770 let active_item = workspace
11771 .active_item(cx)
11772 .expect("should have an active item after adding the multi buffer");
11773 assert!(
11774 !active_item.is_singleton(cx),
11775 "A multi buffer was expected to active after adding"
11776 );
11777 active_item.item_id()
11778 })
11779 .unwrap();
11780 cx.executor().run_until_parked();
11781
11782 multi_buffer_editor.update(cx, |editor, cx| {
11783 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
11784 editor.open_excerpts(&OpenExcerpts, cx);
11785 });
11786 cx.executor().run_until_parked();
11787 let first_item_id = workspace
11788 .update(cx, |workspace, cx| {
11789 let active_item = workspace
11790 .active_item(cx)
11791 .expect("should have an active item after navigating into the 1st buffer");
11792 let first_item_id = active_item.item_id();
11793 assert_ne!(
11794 first_item_id, multibuffer_item_id,
11795 "Should navigate into the 1st buffer and activate it"
11796 );
11797 assert!(
11798 active_item.is_singleton(cx),
11799 "New active item should be a singleton buffer"
11800 );
11801 assert_eq!(
11802 active_item
11803 .act_as::<Editor>(cx)
11804 .expect("should have navigated into an editor for the 1st buffer")
11805 .read(cx)
11806 .text(cx),
11807 sample_text_1
11808 );
11809
11810 workspace
11811 .go_back(workspace.active_pane().downgrade(), cx)
11812 .detach_and_log_err(cx);
11813
11814 first_item_id
11815 })
11816 .unwrap();
11817 cx.executor().run_until_parked();
11818 workspace
11819 .update(cx, |workspace, cx| {
11820 let active_item = workspace
11821 .active_item(cx)
11822 .expect("should have an active item after navigating back");
11823 assert_eq!(
11824 active_item.item_id(),
11825 multibuffer_item_id,
11826 "Should navigate back to the multi buffer"
11827 );
11828 assert!(!active_item.is_singleton(cx));
11829 })
11830 .unwrap();
11831
11832 multi_buffer_editor.update(cx, |editor, cx| {
11833 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11834 s.select_ranges(Some(39..40))
11835 });
11836 editor.open_excerpts(&OpenExcerpts, cx);
11837 });
11838 cx.executor().run_until_parked();
11839 let second_item_id = workspace
11840 .update(cx, |workspace, cx| {
11841 let active_item = workspace
11842 .active_item(cx)
11843 .expect("should have an active item after navigating into the 2nd buffer");
11844 let second_item_id = active_item.item_id();
11845 assert_ne!(
11846 second_item_id, multibuffer_item_id,
11847 "Should navigate away from the multibuffer"
11848 );
11849 assert_ne!(
11850 second_item_id, first_item_id,
11851 "Should navigate into the 2nd buffer and activate it"
11852 );
11853 assert!(
11854 active_item.is_singleton(cx),
11855 "New active item should be a singleton buffer"
11856 );
11857 assert_eq!(
11858 active_item
11859 .act_as::<Editor>(cx)
11860 .expect("should have navigated into an editor")
11861 .read(cx)
11862 .text(cx),
11863 sample_text_2
11864 );
11865
11866 workspace
11867 .go_back(workspace.active_pane().downgrade(), cx)
11868 .detach_and_log_err(cx);
11869
11870 second_item_id
11871 })
11872 .unwrap();
11873 cx.executor().run_until_parked();
11874 workspace
11875 .update(cx, |workspace, cx| {
11876 let active_item = workspace
11877 .active_item(cx)
11878 .expect("should have an active item after navigating back from the 2nd buffer");
11879 assert_eq!(
11880 active_item.item_id(),
11881 multibuffer_item_id,
11882 "Should navigate back from the 2nd buffer to the multi buffer"
11883 );
11884 assert!(!active_item.is_singleton(cx));
11885 })
11886 .unwrap();
11887
11888 multi_buffer_editor.update(cx, |editor, cx| {
11889 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11890 s.select_ranges(Some(70..70))
11891 });
11892 editor.open_excerpts(&OpenExcerpts, cx);
11893 });
11894 cx.executor().run_until_parked();
11895 workspace
11896 .update(cx, |workspace, cx| {
11897 let active_item = workspace
11898 .active_item(cx)
11899 .expect("should have an active item after navigating into the 3rd buffer");
11900 let third_item_id = active_item.item_id();
11901 assert_ne!(
11902 third_item_id, multibuffer_item_id,
11903 "Should navigate into the 3rd buffer and activate it"
11904 );
11905 assert_ne!(third_item_id, first_item_id);
11906 assert_ne!(third_item_id, second_item_id);
11907 assert!(
11908 active_item.is_singleton(cx),
11909 "New active item should be a singleton buffer"
11910 );
11911 assert_eq!(
11912 active_item
11913 .act_as::<Editor>(cx)
11914 .expect("should have navigated into an editor")
11915 .read(cx)
11916 .text(cx),
11917 sample_text_3
11918 );
11919
11920 workspace
11921 .go_back(workspace.active_pane().downgrade(), cx)
11922 .detach_and_log_err(cx);
11923 })
11924 .unwrap();
11925 cx.executor().run_until_parked();
11926 workspace
11927 .update(cx, |workspace, cx| {
11928 let active_item = workspace
11929 .active_item(cx)
11930 .expect("should have an active item after navigating back from the 3rd buffer");
11931 assert_eq!(
11932 active_item.item_id(),
11933 multibuffer_item_id,
11934 "Should navigate back from the 3rd buffer to the multi buffer"
11935 );
11936 assert!(!active_item.is_singleton(cx));
11937 })
11938 .unwrap();
11939}
11940
11941#[gpui::test]
11942async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11943 init_test(cx, |_| {});
11944
11945 let mut cx = EditorTestContext::new(cx).await;
11946
11947 let diff_base = r#"
11948 use some::mod;
11949
11950 const A: u32 = 42;
11951
11952 fn main() {
11953 println!("hello");
11954
11955 println!("world");
11956 }
11957 "#
11958 .unindent();
11959
11960 cx.set_state(
11961 &r#"
11962 use some::modified;
11963
11964 ˇ
11965 fn main() {
11966 println!("hello there");
11967
11968 println!("around the");
11969 println!("world");
11970 }
11971 "#
11972 .unindent(),
11973 );
11974
11975 cx.set_diff_base(&diff_base);
11976 executor.run_until_parked();
11977
11978 cx.update_editor(|editor, cx| {
11979 editor.go_to_next_hunk(&GoToHunk, cx);
11980 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11981 });
11982 executor.run_until_parked();
11983 cx.assert_state_with_diff(
11984 r#"
11985 use some::modified;
11986
11987
11988 fn main() {
11989 - println!("hello");
11990 + ˇ println!("hello there");
11991
11992 println!("around the");
11993 println!("world");
11994 }
11995 "#
11996 .unindent(),
11997 );
11998
11999 cx.update_editor(|editor, cx| {
12000 for _ in 0..3 {
12001 editor.go_to_next_hunk(&GoToHunk, cx);
12002 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12003 }
12004 });
12005 executor.run_until_parked();
12006 cx.assert_state_with_diff(
12007 r#"
12008 - use some::mod;
12009 + use some::modified;
12010
12011 - const A: u32 = 42;
12012 ˇ
12013 fn main() {
12014 - println!("hello");
12015 + println!("hello there");
12016
12017 + println!("around the");
12018 println!("world");
12019 }
12020 "#
12021 .unindent(),
12022 );
12023
12024 cx.update_editor(|editor, cx| {
12025 editor.cancel(&Cancel, cx);
12026 });
12027
12028 cx.assert_state_with_diff(
12029 r#"
12030 use some::modified;
12031
12032 ˇ
12033 fn main() {
12034 println!("hello there");
12035
12036 println!("around the");
12037 println!("world");
12038 }
12039 "#
12040 .unindent(),
12041 );
12042}
12043
12044#[gpui::test]
12045async fn test_diff_base_change_with_expanded_diff_hunks(
12046 executor: BackgroundExecutor,
12047 cx: &mut gpui::TestAppContext,
12048) {
12049 init_test(cx, |_| {});
12050
12051 let mut cx = EditorTestContext::new(cx).await;
12052
12053 let diff_base = r#"
12054 use some::mod1;
12055 use some::mod2;
12056
12057 const A: u32 = 42;
12058 const B: u32 = 42;
12059 const C: u32 = 42;
12060
12061 fn main() {
12062 println!("hello");
12063
12064 println!("world");
12065 }
12066 "#
12067 .unindent();
12068
12069 cx.set_state(
12070 &r#"
12071 use some::mod2;
12072
12073 const A: u32 = 42;
12074 const C: u32 = 42;
12075
12076 fn main(ˇ) {
12077 //println!("hello");
12078
12079 println!("world");
12080 //
12081 //
12082 }
12083 "#
12084 .unindent(),
12085 );
12086
12087 cx.set_diff_base(&diff_base);
12088 executor.run_until_parked();
12089
12090 cx.update_editor(|editor, cx| {
12091 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12092 });
12093 executor.run_until_parked();
12094 cx.assert_state_with_diff(
12095 r#"
12096 - use some::mod1;
12097 use some::mod2;
12098
12099 const A: u32 = 42;
12100 - const B: u32 = 42;
12101 const C: u32 = 42;
12102
12103 fn main(ˇ) {
12104 - println!("hello");
12105 + //println!("hello");
12106
12107 println!("world");
12108 + //
12109 + //
12110 }
12111 "#
12112 .unindent(),
12113 );
12114
12115 cx.set_diff_base("new diff base!");
12116 executor.run_until_parked();
12117 cx.assert_state_with_diff(
12118 r#"
12119 use some::mod2;
12120
12121 const A: u32 = 42;
12122 const C: u32 = 42;
12123
12124 fn main(ˇ) {
12125 //println!("hello");
12126
12127 println!("world");
12128 //
12129 //
12130 }
12131 "#
12132 .unindent(),
12133 );
12134
12135 cx.update_editor(|editor, cx| {
12136 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12137 });
12138 executor.run_until_parked();
12139 cx.assert_state_with_diff(
12140 r#"
12141 - new diff base!
12142 + use some::mod2;
12143 +
12144 + const A: u32 = 42;
12145 + const C: u32 = 42;
12146 +
12147 + fn main(ˇ) {
12148 + //println!("hello");
12149 +
12150 + println!("world");
12151 + //
12152 + //
12153 + }
12154 "#
12155 .unindent(),
12156 );
12157}
12158
12159#[gpui::test]
12160async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
12161 init_test(cx, |_| {});
12162
12163 let mut cx = EditorTestContext::new(cx).await;
12164
12165 let diff_base = r#"
12166 use some::mod1;
12167 use some::mod2;
12168
12169 const A: u32 = 42;
12170 const B: u32 = 42;
12171 const C: u32 = 42;
12172
12173 fn main() {
12174 println!("hello");
12175
12176 println!("world");
12177 }
12178
12179 fn another() {
12180 println!("another");
12181 }
12182
12183 fn another2() {
12184 println!("another2");
12185 }
12186 "#
12187 .unindent();
12188
12189 cx.set_state(
12190 &r#"
12191 «use some::mod2;
12192
12193 const A: u32 = 42;
12194 const C: u32 = 42;
12195
12196 fn main() {
12197 //println!("hello");
12198
12199 println!("world");
12200 //
12201 //ˇ»
12202 }
12203
12204 fn another() {
12205 println!("another");
12206 println!("another");
12207 }
12208
12209 println!("another2");
12210 }
12211 "#
12212 .unindent(),
12213 );
12214
12215 cx.set_diff_base(&diff_base);
12216 executor.run_until_parked();
12217
12218 cx.update_editor(|editor, cx| {
12219 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12220 });
12221 executor.run_until_parked();
12222
12223 cx.assert_state_with_diff(
12224 r#"
12225 - use some::mod1;
12226 «use some::mod2;
12227
12228 const A: u32 = 42;
12229 - const B: u32 = 42;
12230 const C: u32 = 42;
12231
12232 fn main() {
12233 - println!("hello");
12234 + //println!("hello");
12235
12236 println!("world");
12237 + //
12238 + //ˇ»
12239 }
12240
12241 fn another() {
12242 println!("another");
12243 + println!("another");
12244 }
12245
12246 - fn another2() {
12247 println!("another2");
12248 }
12249 "#
12250 .unindent(),
12251 );
12252
12253 // Fold across some of the diff hunks. They should no longer appear expanded.
12254 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
12255 cx.executor().run_until_parked();
12256
12257 // Hunks are not shown if their position is within a fold
12258 cx.assert_state_with_diff(
12259 r#"
12260 «use some::mod2;
12261
12262 const A: u32 = 42;
12263 const C: u32 = 42;
12264
12265 fn main() {
12266 //println!("hello");
12267
12268 println!("world");
12269 //
12270 //ˇ»
12271 }
12272
12273 fn another() {
12274 println!("another");
12275 + println!("another");
12276 }
12277
12278 - fn another2() {
12279 println!("another2");
12280 }
12281 "#
12282 .unindent(),
12283 );
12284
12285 cx.update_editor(|editor, cx| {
12286 editor.select_all(&SelectAll, cx);
12287 editor.unfold_lines(&UnfoldLines, cx);
12288 });
12289 cx.executor().run_until_parked();
12290
12291 // The deletions reappear when unfolding.
12292 cx.assert_state_with_diff(
12293 r#"
12294 - use some::mod1;
12295 «use some::mod2;
12296
12297 const A: u32 = 42;
12298 - const B: u32 = 42;
12299 const C: u32 = 42;
12300
12301 fn main() {
12302 - println!("hello");
12303 + //println!("hello");
12304
12305 println!("world");
12306 + //
12307 + //
12308 }
12309
12310 fn another() {
12311 println!("another");
12312 + println!("another");
12313 }
12314
12315 - fn another2() {
12316 println!("another2");
12317 }
12318 ˇ»"#
12319 .unindent(),
12320 );
12321}
12322
12323#[gpui::test]
12324async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
12325 init_test(cx, |_| {});
12326
12327 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12328 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12329 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12330 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12331 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
12332 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
12333
12334 let buffer_1 = cx.new_model(|cx| Buffer::local(file_1_new.to_string(), cx));
12335 let buffer_2 = cx.new_model(|cx| Buffer::local(file_2_new.to_string(), cx));
12336 let buffer_3 = cx.new_model(|cx| Buffer::local(file_3_new.to_string(), cx));
12337
12338 let multi_buffer = cx.new_model(|cx| {
12339 let mut multibuffer = MultiBuffer::new(ReadWrite);
12340 multibuffer.push_excerpts(
12341 buffer_1.clone(),
12342 [
12343 ExcerptRange {
12344 context: Point::new(0, 0)..Point::new(3, 0),
12345 primary: None,
12346 },
12347 ExcerptRange {
12348 context: Point::new(5, 0)..Point::new(7, 0),
12349 primary: None,
12350 },
12351 ExcerptRange {
12352 context: Point::new(9, 0)..Point::new(10, 3),
12353 primary: None,
12354 },
12355 ],
12356 cx,
12357 );
12358 multibuffer.push_excerpts(
12359 buffer_2.clone(),
12360 [
12361 ExcerptRange {
12362 context: Point::new(0, 0)..Point::new(3, 0),
12363 primary: None,
12364 },
12365 ExcerptRange {
12366 context: Point::new(5, 0)..Point::new(7, 0),
12367 primary: None,
12368 },
12369 ExcerptRange {
12370 context: Point::new(9, 0)..Point::new(10, 3),
12371 primary: None,
12372 },
12373 ],
12374 cx,
12375 );
12376 multibuffer.push_excerpts(
12377 buffer_3.clone(),
12378 [
12379 ExcerptRange {
12380 context: Point::new(0, 0)..Point::new(3, 0),
12381 primary: None,
12382 },
12383 ExcerptRange {
12384 context: Point::new(5, 0)..Point::new(7, 0),
12385 primary: None,
12386 },
12387 ExcerptRange {
12388 context: Point::new(9, 0)..Point::new(10, 3),
12389 primary: None,
12390 },
12391 ],
12392 cx,
12393 );
12394 multibuffer
12395 });
12396
12397 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12398 editor
12399 .update(cx, |editor, cx| {
12400 for (buffer, diff_base) in [
12401 (buffer_1.clone(), file_1_old),
12402 (buffer_2.clone(), file_2_old),
12403 (buffer_3.clone(), file_3_old),
12404 ] {
12405 let change_set = cx.new_model(|cx| {
12406 BufferChangeSet::new_with_base_text(
12407 diff_base.to_string(),
12408 buffer.read(cx).text_snapshot(),
12409 cx,
12410 )
12411 });
12412 editor.diff_map.add_change_set(change_set, cx)
12413 }
12414 })
12415 .unwrap();
12416
12417 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12418 cx.run_until_parked();
12419
12420 cx.assert_editor_state(
12421 &"
12422 ˇaaa
12423 ccc
12424 ddd
12425
12426 ggg
12427 hhh
12428
12429
12430 lll
12431 mmm
12432 NNN
12433
12434 qqq
12435 rrr
12436
12437 uuu
12438 111
12439 222
12440 333
12441
12442 666
12443 777
12444
12445 000
12446 !!!"
12447 .unindent(),
12448 );
12449
12450 cx.update_editor(|editor, cx| {
12451 editor.select_all(&SelectAll, cx);
12452 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12453 });
12454 cx.executor().run_until_parked();
12455
12456 cx.assert_state_with_diff(
12457 "
12458 «aaa
12459 - bbb
12460 ccc
12461 ddd
12462
12463 ggg
12464 hhh
12465
12466
12467 lll
12468 mmm
12469 - nnn
12470 + NNN
12471
12472 qqq
12473 rrr
12474
12475 uuu
12476 111
12477 222
12478 333
12479
12480 + 666
12481 777
12482
12483 000
12484 !!!ˇ»"
12485 .unindent(),
12486 );
12487}
12488
12489#[gpui::test]
12490async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
12491 init_test(cx, |_| {});
12492
12493 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
12494 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
12495
12496 let buffer = cx.new_model(|cx| Buffer::local(text.to_string(), cx));
12497 let multi_buffer = cx.new_model(|cx| {
12498 let mut multibuffer = MultiBuffer::new(ReadWrite);
12499 multibuffer.push_excerpts(
12500 buffer.clone(),
12501 [
12502 ExcerptRange {
12503 context: Point::new(0, 0)..Point::new(2, 0),
12504 primary: None,
12505 },
12506 ExcerptRange {
12507 context: Point::new(5, 0)..Point::new(7, 0),
12508 primary: None,
12509 },
12510 ],
12511 cx,
12512 );
12513 multibuffer
12514 });
12515
12516 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12517 editor
12518 .update(cx, |editor, cx| {
12519 let buffer = buffer.read(cx).text_snapshot();
12520 let change_set = cx
12521 .new_model(|cx| BufferChangeSet::new_with_base_text(base.to_string(), buffer, cx));
12522 editor.diff_map.add_change_set(change_set, cx)
12523 })
12524 .unwrap();
12525
12526 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12527 cx.run_until_parked();
12528
12529 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
12530 cx.executor().run_until_parked();
12531
12532 cx.assert_state_with_diff(
12533 "
12534 ˇaaa
12535 - bbb
12536 + BBB
12537
12538 - ddd
12539 - eee
12540 + EEE
12541 fff
12542 "
12543 .unindent(),
12544 );
12545}
12546
12547#[gpui::test]
12548async fn test_edits_around_expanded_insertion_hunks(
12549 executor: BackgroundExecutor,
12550 cx: &mut gpui::TestAppContext,
12551) {
12552 init_test(cx, |_| {});
12553
12554 let mut cx = EditorTestContext::new(cx).await;
12555
12556 let diff_base = r#"
12557 use some::mod1;
12558 use some::mod2;
12559
12560 const A: u32 = 42;
12561
12562 fn main() {
12563 println!("hello");
12564
12565 println!("world");
12566 }
12567 "#
12568 .unindent();
12569 executor.run_until_parked();
12570 cx.set_state(
12571 &r#"
12572 use some::mod1;
12573 use some::mod2;
12574
12575 const A: u32 = 42;
12576 const B: u32 = 42;
12577 const C: u32 = 42;
12578 ˇ
12579
12580 fn main() {
12581 println!("hello");
12582
12583 println!("world");
12584 }
12585 "#
12586 .unindent(),
12587 );
12588
12589 cx.set_diff_base(&diff_base);
12590 executor.run_until_parked();
12591
12592 cx.update_editor(|editor, cx| {
12593 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12594 });
12595 executor.run_until_parked();
12596
12597 cx.assert_state_with_diff(
12598 r#"
12599 use some::mod1;
12600 use some::mod2;
12601
12602 const A: u32 = 42;
12603 + const B: u32 = 42;
12604 + const C: u32 = 42;
12605 + ˇ
12606
12607 fn main() {
12608 println!("hello");
12609
12610 println!("world");
12611 }
12612 "#
12613 .unindent(),
12614 );
12615
12616 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
12617 executor.run_until_parked();
12618
12619 cx.assert_state_with_diff(
12620 r#"
12621 use some::mod1;
12622 use some::mod2;
12623
12624 const A: u32 = 42;
12625 + const B: u32 = 42;
12626 + const C: u32 = 42;
12627 + const D: u32 = 42;
12628 + ˇ
12629
12630 fn main() {
12631 println!("hello");
12632
12633 println!("world");
12634 }
12635 "#
12636 .unindent(),
12637 );
12638
12639 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
12640 executor.run_until_parked();
12641
12642 cx.assert_state_with_diff(
12643 r#"
12644 use some::mod1;
12645 use some::mod2;
12646
12647 const A: u32 = 42;
12648 + const B: u32 = 42;
12649 + const C: u32 = 42;
12650 + const D: u32 = 42;
12651 + const E: u32 = 42;
12652 + ˇ
12653
12654 fn main() {
12655 println!("hello");
12656
12657 println!("world");
12658 }
12659 "#
12660 .unindent(),
12661 );
12662
12663 cx.update_editor(|editor, cx| {
12664 editor.delete_line(&DeleteLine, cx);
12665 });
12666 executor.run_until_parked();
12667
12668 cx.assert_state_with_diff(
12669 r#"
12670 use some::mod1;
12671 use some::mod2;
12672
12673 const A: u32 = 42;
12674 + const B: u32 = 42;
12675 + const C: u32 = 42;
12676 + const D: u32 = 42;
12677 + const E: u32 = 42;
12678 ˇ
12679 fn main() {
12680 println!("hello");
12681
12682 println!("world");
12683 }
12684 "#
12685 .unindent(),
12686 );
12687
12688 cx.update_editor(|editor, cx| {
12689 editor.move_up(&MoveUp, cx);
12690 editor.delete_line(&DeleteLine, cx);
12691 editor.move_up(&MoveUp, cx);
12692 editor.delete_line(&DeleteLine, cx);
12693 editor.move_up(&MoveUp, cx);
12694 editor.delete_line(&DeleteLine, cx);
12695 });
12696 executor.run_until_parked();
12697 cx.assert_state_with_diff(
12698 r#"
12699 use some::mod1;
12700 use some::mod2;
12701
12702 const A: u32 = 42;
12703 + const B: u32 = 42;
12704 ˇ
12705 fn main() {
12706 println!("hello");
12707
12708 println!("world");
12709 }
12710 "#
12711 .unindent(),
12712 );
12713
12714 cx.update_editor(|editor, cx| {
12715 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
12716 editor.delete_line(&DeleteLine, cx);
12717 });
12718 executor.run_until_parked();
12719 cx.assert_state_with_diff(
12720 r#"
12721 use some::mod1;
12722 - use some::mod2;
12723 -
12724 - const A: u32 = 42;
12725 ˇ
12726 fn main() {
12727 println!("hello");
12728
12729 println!("world");
12730 }
12731 "#
12732 .unindent(),
12733 );
12734}
12735
12736#[gpui::test]
12737async fn test_edits_around_expanded_deletion_hunks(
12738 executor: BackgroundExecutor,
12739 cx: &mut gpui::TestAppContext,
12740) {
12741 init_test(cx, |_| {});
12742
12743 let mut cx = EditorTestContext::new(cx).await;
12744
12745 let diff_base = r#"
12746 use some::mod1;
12747 use some::mod2;
12748
12749 const A: u32 = 42;
12750 const B: u32 = 42;
12751 const C: u32 = 42;
12752
12753
12754 fn main() {
12755 println!("hello");
12756
12757 println!("world");
12758 }
12759 "#
12760 .unindent();
12761 executor.run_until_parked();
12762 cx.set_state(
12763 &r#"
12764 use some::mod1;
12765 use some::mod2;
12766
12767 ˇconst B: u32 = 42;
12768 const C: u32 = 42;
12769
12770
12771 fn main() {
12772 println!("hello");
12773
12774 println!("world");
12775 }
12776 "#
12777 .unindent(),
12778 );
12779
12780 cx.set_diff_base(&diff_base);
12781 executor.run_until_parked();
12782
12783 cx.update_editor(|editor, cx| {
12784 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12785 });
12786 executor.run_until_parked();
12787
12788 cx.assert_state_with_diff(
12789 r#"
12790 use some::mod1;
12791 use some::mod2;
12792
12793 - const A: u32 = 42;
12794 ˇconst B: u32 = 42;
12795 const C: u32 = 42;
12796
12797
12798 fn main() {
12799 println!("hello");
12800
12801 println!("world");
12802 }
12803 "#
12804 .unindent(),
12805 );
12806
12807 cx.update_editor(|editor, cx| {
12808 editor.delete_line(&DeleteLine, cx);
12809 });
12810 executor.run_until_parked();
12811 cx.assert_state_with_diff(
12812 r#"
12813 use some::mod1;
12814 use some::mod2;
12815
12816 - const A: u32 = 42;
12817 - const B: u32 = 42;
12818 ˇconst C: u32 = 42;
12819
12820
12821 fn main() {
12822 println!("hello");
12823
12824 println!("world");
12825 }
12826 "#
12827 .unindent(),
12828 );
12829
12830 cx.update_editor(|editor, cx| {
12831 editor.delete_line(&DeleteLine, cx);
12832 });
12833 executor.run_until_parked();
12834 cx.assert_state_with_diff(
12835 r#"
12836 use some::mod1;
12837 use some::mod2;
12838
12839 - const A: u32 = 42;
12840 - const B: u32 = 42;
12841 - const C: u32 = 42;
12842 ˇ
12843
12844 fn main() {
12845 println!("hello");
12846
12847 println!("world");
12848 }
12849 "#
12850 .unindent(),
12851 );
12852
12853 cx.update_editor(|editor, cx| {
12854 editor.handle_input("replacement", cx);
12855 });
12856 executor.run_until_parked();
12857 cx.assert_state_with_diff(
12858 r#"
12859 use some::mod1;
12860 use some::mod2;
12861
12862 - const A: u32 = 42;
12863 - const B: u32 = 42;
12864 - const C: u32 = 42;
12865 -
12866 + replacementˇ
12867
12868 fn main() {
12869 println!("hello");
12870
12871 println!("world");
12872 }
12873 "#
12874 .unindent(),
12875 );
12876}
12877
12878#[gpui::test]
12879async fn test_edit_after_expanded_modification_hunk(
12880 executor: BackgroundExecutor,
12881 cx: &mut gpui::TestAppContext,
12882) {
12883 init_test(cx, |_| {});
12884
12885 let mut cx = EditorTestContext::new(cx).await;
12886
12887 let diff_base = r#"
12888 use some::mod1;
12889 use some::mod2;
12890
12891 const A: u32 = 42;
12892 const B: u32 = 42;
12893 const C: u32 = 42;
12894 const D: u32 = 42;
12895
12896
12897 fn main() {
12898 println!("hello");
12899
12900 println!("world");
12901 }"#
12902 .unindent();
12903
12904 cx.set_state(
12905 &r#"
12906 use some::mod1;
12907 use some::mod2;
12908
12909 const A: u32 = 42;
12910 const B: u32 = 42;
12911 const C: u32 = 43ˇ
12912 const D: u32 = 42;
12913
12914
12915 fn main() {
12916 println!("hello");
12917
12918 println!("world");
12919 }"#
12920 .unindent(),
12921 );
12922
12923 cx.set_diff_base(&diff_base);
12924 executor.run_until_parked();
12925 cx.update_editor(|editor, cx| {
12926 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12927 });
12928 executor.run_until_parked();
12929
12930 cx.assert_state_with_diff(
12931 r#"
12932 use some::mod1;
12933 use some::mod2;
12934
12935 const A: u32 = 42;
12936 const B: u32 = 42;
12937 - const C: u32 = 42;
12938 + const C: u32 = 43ˇ
12939 const D: u32 = 42;
12940
12941
12942 fn main() {
12943 println!("hello");
12944
12945 println!("world");
12946 }"#
12947 .unindent(),
12948 );
12949
12950 cx.update_editor(|editor, cx| {
12951 editor.handle_input("\nnew_line\n", cx);
12952 });
12953 executor.run_until_parked();
12954
12955 cx.assert_state_with_diff(
12956 r#"
12957 use some::mod1;
12958 use some::mod2;
12959
12960 const A: u32 = 42;
12961 const B: u32 = 42;
12962 - const C: u32 = 42;
12963 + const C: u32 = 43
12964 + new_line
12965 + ˇ
12966 const D: u32 = 42;
12967
12968
12969 fn main() {
12970 println!("hello");
12971
12972 println!("world");
12973 }"#
12974 .unindent(),
12975 );
12976}
12977
12978async fn setup_indent_guides_editor(
12979 text: &str,
12980 cx: &mut gpui::TestAppContext,
12981) -> (BufferId, EditorTestContext) {
12982 init_test(cx, |_| {});
12983
12984 let mut cx = EditorTestContext::new(cx).await;
12985
12986 let buffer_id = cx.update_editor(|editor, cx| {
12987 editor.set_text(text, cx);
12988 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12989
12990 buffer_ids[0]
12991 });
12992
12993 (buffer_id, cx)
12994}
12995
12996fn assert_indent_guides(
12997 range: Range<u32>,
12998 expected: Vec<IndentGuide>,
12999 active_indices: Option<Vec<usize>>,
13000 cx: &mut EditorTestContext,
13001) {
13002 let indent_guides = cx.update_editor(|editor, cx| {
13003 let snapshot = editor.snapshot(cx).display_snapshot;
13004 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
13005 MultiBufferRow(range.start)..MultiBufferRow(range.end),
13006 true,
13007 &snapshot,
13008 cx,
13009 );
13010
13011 indent_guides.sort_by(|a, b| {
13012 a.depth.cmp(&b.depth).then(
13013 a.start_row
13014 .cmp(&b.start_row)
13015 .then(a.end_row.cmp(&b.end_row)),
13016 )
13017 });
13018 indent_guides
13019 });
13020
13021 if let Some(expected) = active_indices {
13022 let active_indices = cx.update_editor(|editor, cx| {
13023 let snapshot = editor.snapshot(cx).display_snapshot;
13024 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
13025 });
13026
13027 assert_eq!(
13028 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
13029 expected,
13030 "Active indent guide indices do not match"
13031 );
13032 }
13033
13034 let expected: Vec<_> = expected
13035 .into_iter()
13036 .map(|guide| MultiBufferIndentGuide {
13037 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
13038 buffer: guide,
13039 })
13040 .collect();
13041
13042 assert_eq!(indent_guides, expected, "Indent guides do not match");
13043}
13044
13045fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
13046 IndentGuide {
13047 buffer_id,
13048 start_row,
13049 end_row,
13050 depth,
13051 tab_size: 4,
13052 settings: IndentGuideSettings {
13053 enabled: true,
13054 line_width: 1,
13055 active_line_width: 1,
13056 ..Default::default()
13057 },
13058 }
13059}
13060
13061#[gpui::test]
13062async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13063 let (buffer_id, mut cx) = setup_indent_guides_editor(
13064 &"
13065 fn main() {
13066 let a = 1;
13067 }"
13068 .unindent(),
13069 cx,
13070 )
13071 .await;
13072
13073 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13074}
13075
13076#[gpui::test]
13077async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
13078 let (buffer_id, mut cx) = setup_indent_guides_editor(
13079 &"
13080 fn main() {
13081 let a = 1;
13082 let b = 2;
13083 }"
13084 .unindent(),
13085 cx,
13086 )
13087 .await;
13088
13089 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
13090}
13091
13092#[gpui::test]
13093async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
13094 let (buffer_id, mut cx) = setup_indent_guides_editor(
13095 &"
13096 fn main() {
13097 let a = 1;
13098 if a == 3 {
13099 let b = 2;
13100 } else {
13101 let c = 3;
13102 }
13103 }"
13104 .unindent(),
13105 cx,
13106 )
13107 .await;
13108
13109 assert_indent_guides(
13110 0..8,
13111 vec![
13112 indent_guide(buffer_id, 1, 6, 0),
13113 indent_guide(buffer_id, 3, 3, 1),
13114 indent_guide(buffer_id, 5, 5, 1),
13115 ],
13116 None,
13117 &mut cx,
13118 );
13119}
13120
13121#[gpui::test]
13122async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
13123 let (buffer_id, mut cx) = setup_indent_guides_editor(
13124 &"
13125 fn main() {
13126 let a = 1;
13127 let b = 2;
13128 let c = 3;
13129 }"
13130 .unindent(),
13131 cx,
13132 )
13133 .await;
13134
13135 assert_indent_guides(
13136 0..5,
13137 vec![
13138 indent_guide(buffer_id, 1, 3, 0),
13139 indent_guide(buffer_id, 2, 2, 1),
13140 ],
13141 None,
13142 &mut cx,
13143 );
13144}
13145
13146#[gpui::test]
13147async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
13148 let (buffer_id, mut cx) = setup_indent_guides_editor(
13149 &"
13150 fn main() {
13151 let a = 1;
13152
13153 let c = 3;
13154 }"
13155 .unindent(),
13156 cx,
13157 )
13158 .await;
13159
13160 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
13161}
13162
13163#[gpui::test]
13164async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
13165 let (buffer_id, mut cx) = setup_indent_guides_editor(
13166 &"
13167 fn main() {
13168 let a = 1;
13169
13170 let c = 3;
13171
13172 if a == 3 {
13173 let b = 2;
13174 } else {
13175 let c = 3;
13176 }
13177 }"
13178 .unindent(),
13179 cx,
13180 )
13181 .await;
13182
13183 assert_indent_guides(
13184 0..11,
13185 vec![
13186 indent_guide(buffer_id, 1, 9, 0),
13187 indent_guide(buffer_id, 6, 6, 1),
13188 indent_guide(buffer_id, 8, 8, 1),
13189 ],
13190 None,
13191 &mut cx,
13192 );
13193}
13194
13195#[gpui::test]
13196async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
13197 let (buffer_id, mut cx) = setup_indent_guides_editor(
13198 &"
13199 fn main() {
13200 let a = 1;
13201
13202 let c = 3;
13203
13204 if a == 3 {
13205 let b = 2;
13206 } else {
13207 let c = 3;
13208 }
13209 }"
13210 .unindent(),
13211 cx,
13212 )
13213 .await;
13214
13215 assert_indent_guides(
13216 1..11,
13217 vec![
13218 indent_guide(buffer_id, 1, 9, 0),
13219 indent_guide(buffer_id, 6, 6, 1),
13220 indent_guide(buffer_id, 8, 8, 1),
13221 ],
13222 None,
13223 &mut cx,
13224 );
13225}
13226
13227#[gpui::test]
13228async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
13229 let (buffer_id, mut cx) = setup_indent_guides_editor(
13230 &"
13231 fn main() {
13232 let a = 1;
13233
13234 let c = 3;
13235
13236 if a == 3 {
13237 let b = 2;
13238 } else {
13239 let c = 3;
13240 }
13241 }"
13242 .unindent(),
13243 cx,
13244 )
13245 .await;
13246
13247 assert_indent_guides(
13248 1..10,
13249 vec![
13250 indent_guide(buffer_id, 1, 9, 0),
13251 indent_guide(buffer_id, 6, 6, 1),
13252 indent_guide(buffer_id, 8, 8, 1),
13253 ],
13254 None,
13255 &mut cx,
13256 );
13257}
13258
13259#[gpui::test]
13260async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
13261 let (buffer_id, mut cx) = setup_indent_guides_editor(
13262 &"
13263 block1
13264 block2
13265 block3
13266 block4
13267 block2
13268 block1
13269 block1"
13270 .unindent(),
13271 cx,
13272 )
13273 .await;
13274
13275 assert_indent_guides(
13276 1..10,
13277 vec![
13278 indent_guide(buffer_id, 1, 4, 0),
13279 indent_guide(buffer_id, 2, 3, 1),
13280 indent_guide(buffer_id, 3, 3, 2),
13281 ],
13282 None,
13283 &mut cx,
13284 );
13285}
13286
13287#[gpui::test]
13288async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
13289 let (buffer_id, mut cx) = setup_indent_guides_editor(
13290 &"
13291 block1
13292 block2
13293 block3
13294
13295 block1
13296 block1"
13297 .unindent(),
13298 cx,
13299 )
13300 .await;
13301
13302 assert_indent_guides(
13303 0..6,
13304 vec![
13305 indent_guide(buffer_id, 1, 2, 0),
13306 indent_guide(buffer_id, 2, 2, 1),
13307 ],
13308 None,
13309 &mut cx,
13310 );
13311}
13312
13313#[gpui::test]
13314async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
13315 let (buffer_id, mut cx) = setup_indent_guides_editor(
13316 &"
13317 block1
13318
13319
13320
13321 block2
13322 "
13323 .unindent(),
13324 cx,
13325 )
13326 .await;
13327
13328 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13329}
13330
13331#[gpui::test]
13332async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
13333 let (buffer_id, mut cx) = setup_indent_guides_editor(
13334 &"
13335 def a:
13336 \tb = 3
13337 \tif True:
13338 \t\tc = 4
13339 \t\td = 5
13340 \tprint(b)
13341 "
13342 .unindent(),
13343 cx,
13344 )
13345 .await;
13346
13347 assert_indent_guides(
13348 0..6,
13349 vec![
13350 indent_guide(buffer_id, 1, 6, 0),
13351 indent_guide(buffer_id, 3, 4, 1),
13352 ],
13353 None,
13354 &mut cx,
13355 );
13356}
13357
13358#[gpui::test]
13359async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13360 let (buffer_id, mut cx) = setup_indent_guides_editor(
13361 &"
13362 fn main() {
13363 let a = 1;
13364 }"
13365 .unindent(),
13366 cx,
13367 )
13368 .await;
13369
13370 cx.update_editor(|editor, cx| {
13371 editor.change_selections(None, cx, |s| {
13372 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13373 });
13374 });
13375
13376 assert_indent_guides(
13377 0..3,
13378 vec![indent_guide(buffer_id, 1, 1, 0)],
13379 Some(vec![0]),
13380 &mut cx,
13381 );
13382}
13383
13384#[gpui::test]
13385async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13386 let (buffer_id, mut cx) = setup_indent_guides_editor(
13387 &"
13388 fn main() {
13389 if 1 == 2 {
13390 let a = 1;
13391 }
13392 }"
13393 .unindent(),
13394 cx,
13395 )
13396 .await;
13397
13398 cx.update_editor(|editor, cx| {
13399 editor.change_selections(None, cx, |s| {
13400 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13401 });
13402 });
13403
13404 assert_indent_guides(
13405 0..4,
13406 vec![
13407 indent_guide(buffer_id, 1, 3, 0),
13408 indent_guide(buffer_id, 2, 2, 1),
13409 ],
13410 Some(vec![1]),
13411 &mut cx,
13412 );
13413
13414 cx.update_editor(|editor, cx| {
13415 editor.change_selections(None, cx, |s| {
13416 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13417 });
13418 });
13419
13420 assert_indent_guides(
13421 0..4,
13422 vec![
13423 indent_guide(buffer_id, 1, 3, 0),
13424 indent_guide(buffer_id, 2, 2, 1),
13425 ],
13426 Some(vec![1]),
13427 &mut cx,
13428 );
13429
13430 cx.update_editor(|editor, cx| {
13431 editor.change_selections(None, cx, |s| {
13432 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13433 });
13434 });
13435
13436 assert_indent_guides(
13437 0..4,
13438 vec![
13439 indent_guide(buffer_id, 1, 3, 0),
13440 indent_guide(buffer_id, 2, 2, 1),
13441 ],
13442 Some(vec![0]),
13443 &mut cx,
13444 );
13445}
13446
13447#[gpui::test]
13448async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13449 let (buffer_id, mut cx) = setup_indent_guides_editor(
13450 &"
13451 fn main() {
13452 let a = 1;
13453
13454 let b = 2;
13455 }"
13456 .unindent(),
13457 cx,
13458 )
13459 .await;
13460
13461 cx.update_editor(|editor, cx| {
13462 editor.change_selections(None, cx, |s| {
13463 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13464 });
13465 });
13466
13467 assert_indent_guides(
13468 0..5,
13469 vec![indent_guide(buffer_id, 1, 3, 0)],
13470 Some(vec![0]),
13471 &mut cx,
13472 );
13473}
13474
13475#[gpui::test]
13476async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13477 let (buffer_id, mut cx) = setup_indent_guides_editor(
13478 &"
13479 def m:
13480 a = 1
13481 pass"
13482 .unindent(),
13483 cx,
13484 )
13485 .await;
13486
13487 cx.update_editor(|editor, cx| {
13488 editor.change_selections(None, cx, |s| {
13489 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13490 });
13491 });
13492
13493 assert_indent_guides(
13494 0..3,
13495 vec![indent_guide(buffer_id, 1, 2, 0)],
13496 Some(vec![0]),
13497 &mut cx,
13498 );
13499}
13500
13501#[gpui::test]
13502fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13503 init_test(cx, |_| {});
13504
13505 let editor = cx.add_window(|cx| {
13506 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13507 build_editor(buffer, cx)
13508 });
13509
13510 let render_args = Arc::new(Mutex::new(None));
13511 let snapshot = editor
13512 .update(cx, |editor, cx| {
13513 let snapshot = editor.buffer().read(cx).snapshot(cx);
13514 let range =
13515 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13516
13517 struct RenderArgs {
13518 row: MultiBufferRow,
13519 folded: bool,
13520 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13521 }
13522
13523 let crease = Crease::inline(
13524 range,
13525 FoldPlaceholder::test(),
13526 {
13527 let toggle_callback = render_args.clone();
13528 move |row, folded, callback, _cx| {
13529 *toggle_callback.lock() = Some(RenderArgs {
13530 row,
13531 folded,
13532 callback,
13533 });
13534 div()
13535 }
13536 },
13537 |_row, _folded, _cx| div(),
13538 );
13539
13540 editor.insert_creases(Some(crease), cx);
13541 let snapshot = editor.snapshot(cx);
13542 let _div =
13543 snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13544 snapshot
13545 })
13546 .unwrap();
13547
13548 let render_args = render_args.lock().take().unwrap();
13549 assert_eq!(render_args.row, MultiBufferRow(1));
13550 assert!(!render_args.folded);
13551 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13552
13553 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13554 .unwrap();
13555 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13556 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13557
13558 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13559 .unwrap();
13560 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13561 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13562}
13563
13564#[gpui::test]
13565async fn test_input_text(cx: &mut gpui::TestAppContext) {
13566 init_test(cx, |_| {});
13567 let mut cx = EditorTestContext::new(cx).await;
13568
13569 cx.set_state(
13570 &r#"ˇone
13571 two
13572
13573 three
13574 fourˇ
13575 five
13576
13577 siˇx"#
13578 .unindent(),
13579 );
13580
13581 cx.dispatch_action(HandleInput(String::new()));
13582 cx.assert_editor_state(
13583 &r#"ˇone
13584 two
13585
13586 three
13587 fourˇ
13588 five
13589
13590 siˇx"#
13591 .unindent(),
13592 );
13593
13594 cx.dispatch_action(HandleInput("AAAA".to_string()));
13595 cx.assert_editor_state(
13596 &r#"AAAAˇone
13597 two
13598
13599 three
13600 fourAAAAˇ
13601 five
13602
13603 siAAAAˇx"#
13604 .unindent(),
13605 );
13606}
13607
13608#[gpui::test]
13609async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13610 init_test(cx, |_| {});
13611
13612 let mut cx = EditorTestContext::new(cx).await;
13613 cx.set_state(
13614 r#"let foo = 1;
13615let foo = 2;
13616let foo = 3;
13617let fooˇ = 4;
13618let foo = 5;
13619let foo = 6;
13620let foo = 7;
13621let foo = 8;
13622let foo = 9;
13623let foo = 10;
13624let foo = 11;
13625let foo = 12;
13626let foo = 13;
13627let foo = 14;
13628let foo = 15;"#,
13629 );
13630
13631 cx.update_editor(|e, cx| {
13632 assert_eq!(
13633 e.next_scroll_position,
13634 NextScrollCursorCenterTopBottom::Center,
13635 "Default next scroll direction is center",
13636 );
13637
13638 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13639 assert_eq!(
13640 e.next_scroll_position,
13641 NextScrollCursorCenterTopBottom::Top,
13642 "After center, next scroll direction should be top",
13643 );
13644
13645 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13646 assert_eq!(
13647 e.next_scroll_position,
13648 NextScrollCursorCenterTopBottom::Bottom,
13649 "After top, next scroll direction should be bottom",
13650 );
13651
13652 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13653 assert_eq!(
13654 e.next_scroll_position,
13655 NextScrollCursorCenterTopBottom::Center,
13656 "After bottom, scrolling should start over",
13657 );
13658
13659 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13660 assert_eq!(
13661 e.next_scroll_position,
13662 NextScrollCursorCenterTopBottom::Top,
13663 "Scrolling continues if retriggered fast enough"
13664 );
13665 });
13666
13667 cx.executor()
13668 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13669 cx.executor().run_until_parked();
13670 cx.update_editor(|e, _| {
13671 assert_eq!(
13672 e.next_scroll_position,
13673 NextScrollCursorCenterTopBottom::Center,
13674 "If scrolling is not triggered fast enough, it should reset"
13675 );
13676 });
13677}
13678
13679#[gpui::test]
13680async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13681 init_test(cx, |_| {});
13682 let mut cx = EditorLspTestContext::new_rust(
13683 lsp::ServerCapabilities {
13684 definition_provider: Some(lsp::OneOf::Left(true)),
13685 references_provider: Some(lsp::OneOf::Left(true)),
13686 ..lsp::ServerCapabilities::default()
13687 },
13688 cx,
13689 )
13690 .await;
13691
13692 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13693 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13694 move |params, _| async move {
13695 if empty_go_to_definition {
13696 Ok(None)
13697 } else {
13698 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13699 uri: params.text_document_position_params.text_document.uri,
13700 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13701 })))
13702 }
13703 },
13704 );
13705 let references =
13706 cx.lsp
13707 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13708 Ok(Some(vec![lsp::Location {
13709 uri: params.text_document_position.text_document.uri,
13710 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13711 }]))
13712 });
13713 (go_to_definition, references)
13714 };
13715
13716 cx.set_state(
13717 &r#"fn one() {
13718 let mut a = ˇtwo();
13719 }
13720
13721 fn two() {}"#
13722 .unindent(),
13723 );
13724 set_up_lsp_handlers(false, &mut cx);
13725 let navigated = cx
13726 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13727 .await
13728 .expect("Failed to navigate to definition");
13729 assert_eq!(
13730 navigated,
13731 Navigated::Yes,
13732 "Should have navigated to definition from the GetDefinition response"
13733 );
13734 cx.assert_editor_state(
13735 &r#"fn one() {
13736 let mut a = two();
13737 }
13738
13739 fn «twoˇ»() {}"#
13740 .unindent(),
13741 );
13742
13743 let editors = cx.update_workspace(|workspace, cx| {
13744 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13745 });
13746 cx.update_editor(|_, test_editor_cx| {
13747 assert_eq!(
13748 editors.len(),
13749 1,
13750 "Initially, only one, test, editor should be open in the workspace"
13751 );
13752 assert_eq!(
13753 test_editor_cx.view(),
13754 editors.last().expect("Asserted len is 1")
13755 );
13756 });
13757
13758 set_up_lsp_handlers(true, &mut cx);
13759 let navigated = cx
13760 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13761 .await
13762 .expect("Failed to navigate to lookup references");
13763 assert_eq!(
13764 navigated,
13765 Navigated::Yes,
13766 "Should have navigated to references as a fallback after empty GoToDefinition response"
13767 );
13768 // We should not change the selections in the existing file,
13769 // if opening another milti buffer with the references
13770 cx.assert_editor_state(
13771 &r#"fn one() {
13772 let mut a = two();
13773 }
13774
13775 fn «twoˇ»() {}"#
13776 .unindent(),
13777 );
13778 let editors = cx.update_workspace(|workspace, cx| {
13779 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13780 });
13781 cx.update_editor(|_, test_editor_cx| {
13782 assert_eq!(
13783 editors.len(),
13784 2,
13785 "After falling back to references search, we open a new editor with the results"
13786 );
13787 let references_fallback_text = editors
13788 .into_iter()
13789 .find(|new_editor| new_editor != test_editor_cx.view())
13790 .expect("Should have one non-test editor now")
13791 .read(test_editor_cx)
13792 .text(test_editor_cx);
13793 assert_eq!(
13794 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13795 "Should use the range from the references response and not the GoToDefinition one"
13796 );
13797 });
13798}
13799
13800#[gpui::test]
13801async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
13802 init_test(cx, |_| {});
13803
13804 let language = Arc::new(Language::new(
13805 LanguageConfig::default(),
13806 Some(tree_sitter_rust::LANGUAGE.into()),
13807 ));
13808
13809 let text = r#"
13810 #[cfg(test)]
13811 mod tests() {
13812 #[test]
13813 fn runnable_1() {
13814 let a = 1;
13815 }
13816
13817 #[test]
13818 fn runnable_2() {
13819 let a = 1;
13820 let b = 2;
13821 }
13822 }
13823 "#
13824 .unindent();
13825
13826 let fs = FakeFs::new(cx.executor());
13827 fs.insert_file("/file.rs", Default::default()).await;
13828
13829 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13830 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
13831 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13832 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
13833 let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
13834
13835 let editor = cx.new_view(|cx| {
13836 Editor::new(
13837 EditorMode::Full,
13838 multi_buffer,
13839 Some(project.clone()),
13840 true,
13841 cx,
13842 )
13843 });
13844
13845 editor.update(cx, |editor, cx| {
13846 editor.tasks.insert(
13847 (buffer.read(cx).remote_id(), 3),
13848 RunnableTasks {
13849 templates: vec![],
13850 offset: MultiBufferOffset(43),
13851 column: 0,
13852 extra_variables: HashMap::default(),
13853 context_range: BufferOffset(43)..BufferOffset(85),
13854 },
13855 );
13856 editor.tasks.insert(
13857 (buffer.read(cx).remote_id(), 8),
13858 RunnableTasks {
13859 templates: vec![],
13860 offset: MultiBufferOffset(86),
13861 column: 0,
13862 extra_variables: HashMap::default(),
13863 context_range: BufferOffset(86)..BufferOffset(191),
13864 },
13865 );
13866
13867 // Test finding task when cursor is inside function body
13868 editor.change_selections(None, cx, |s| {
13869 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
13870 });
13871 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13872 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
13873
13874 // Test finding task when cursor is on function name
13875 editor.change_selections(None, cx, |s| {
13876 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
13877 });
13878 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13879 assert_eq!(row, 8, "Should find task when cursor is on function name");
13880 });
13881}
13882
13883fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13884 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13885 point..point
13886}
13887
13888fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13889 let (text, ranges) = marked_text_ranges(marked_text, true);
13890 assert_eq!(view.text(cx), text);
13891 assert_eq!(
13892 view.selections.ranges(cx),
13893 ranges,
13894 "Assert selections are {}",
13895 marked_text
13896 );
13897}
13898
13899pub fn handle_signature_help_request(
13900 cx: &mut EditorLspTestContext,
13901 mocked_response: lsp::SignatureHelp,
13902) -> impl Future<Output = ()> {
13903 let mut request =
13904 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13905 let mocked_response = mocked_response.clone();
13906 async move { Ok(Some(mocked_response)) }
13907 });
13908
13909 async move {
13910 request.next().await;
13911 }
13912}
13913
13914/// Handle completion request passing a marked string specifying where the completion
13915/// should be triggered from using '|' character, what range should be replaced, and what completions
13916/// should be returned using '<' and '>' to delimit the range
13917pub fn handle_completion_request(
13918 cx: &mut EditorLspTestContext,
13919 marked_string: &str,
13920 completions: Vec<&'static str>,
13921 counter: Arc<AtomicUsize>,
13922) -> impl Future<Output = ()> {
13923 let complete_from_marker: TextRangeMarker = '|'.into();
13924 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13925 let (_, mut marked_ranges) = marked_text_ranges_by(
13926 marked_string,
13927 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13928 );
13929
13930 let complete_from_position =
13931 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13932 let replace_range =
13933 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13934
13935 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13936 let completions = completions.clone();
13937 counter.fetch_add(1, atomic::Ordering::Release);
13938 async move {
13939 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13940 assert_eq!(
13941 params.text_document_position.position,
13942 complete_from_position
13943 );
13944 Ok(Some(lsp::CompletionResponse::Array(
13945 completions
13946 .iter()
13947 .map(|completion_text| lsp::CompletionItem {
13948 label: completion_text.to_string(),
13949 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13950 range: replace_range,
13951 new_text: completion_text.to_string(),
13952 })),
13953 ..Default::default()
13954 })
13955 .collect(),
13956 )))
13957 }
13958 });
13959
13960 async move {
13961 request.next().await;
13962 }
13963}
13964
13965fn handle_resolve_completion_request(
13966 cx: &mut EditorLspTestContext,
13967 edits: Option<Vec<(&'static str, &'static str)>>,
13968) -> impl Future<Output = ()> {
13969 let edits = edits.map(|edits| {
13970 edits
13971 .iter()
13972 .map(|(marked_string, new_text)| {
13973 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13974 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13975 lsp::TextEdit::new(replace_range, new_text.to_string())
13976 })
13977 .collect::<Vec<_>>()
13978 });
13979
13980 let mut request =
13981 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13982 let edits = edits.clone();
13983 async move {
13984 Ok(lsp::CompletionItem {
13985 additional_text_edits: edits,
13986 ..Default::default()
13987 })
13988 }
13989 });
13990
13991 async move {
13992 request.next().await;
13993 }
13994}
13995
13996pub(crate) fn update_test_language_settings(
13997 cx: &mut TestAppContext,
13998 f: impl Fn(&mut AllLanguageSettingsContent),
13999) {
14000 cx.update(|cx| {
14001 SettingsStore::update_global(cx, |store, cx| {
14002 store.update_user_settings::<AllLanguageSettings>(cx, f);
14003 });
14004 });
14005}
14006
14007pub(crate) fn update_test_project_settings(
14008 cx: &mut TestAppContext,
14009 f: impl Fn(&mut ProjectSettings),
14010) {
14011 cx.update(|cx| {
14012 SettingsStore::update_global(cx, |store, cx| {
14013 store.update_user_settings::<ProjectSettings>(cx, f);
14014 });
14015 });
14016}
14017
14018pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
14019 cx.update(|cx| {
14020 assets::Assets.load_test_fonts(cx);
14021 let store = SettingsStore::test(cx);
14022 cx.set_global(store);
14023 theme::init(theme::LoadThemes::JustBase, cx);
14024 release_channel::init(SemanticVersion::default(), cx);
14025 client::init_settings(cx);
14026 language::init(cx);
14027 Project::init_settings(cx);
14028 workspace::init_settings(cx);
14029 crate::init(cx);
14030 });
14031
14032 update_test_language_settings(cx, f);
14033}
14034
14035#[track_caller]
14036fn assert_hunk_revert(
14037 not_reverted_text_with_selections: &str,
14038 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
14039 expected_reverted_text_with_selections: &str,
14040 base_text: &str,
14041 cx: &mut EditorLspTestContext,
14042) {
14043 cx.set_state(not_reverted_text_with_selections);
14044 cx.set_diff_base(base_text);
14045 cx.executor().run_until_parked();
14046
14047 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
14048 let snapshot = editor.snapshot(cx);
14049 let reverted_hunk_statuses = snapshot
14050 .diff_map
14051 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
14052 .map(|hunk| hunk_status(&hunk))
14053 .collect::<Vec<_>>();
14054
14055 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
14056 reverted_hunk_statuses
14057 });
14058 cx.executor().run_until_parked();
14059 cx.assert_editor_state(expected_reverted_text_with_selections);
14060 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
14061}