1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
6 editor_test_context::EditorTestContext, select_ranges,
7 },
8 JoinLines,
9};
10use futures::StreamExt;
11use gpui::{
12 div, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, WindowBounds,
13 WindowOptions,
14};
15use indoc::indoc;
16use language::{
17 language_settings::{
18 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
19 },
20 BracketPairConfig,
21 Capability::ReadWrite,
22 FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
23 LanguageName, Override, ParsedMarkdown, Point,
24};
25use language_settings::{Formatter, FormatterList, IndentGuideSettings};
26use multi_buffer::MultiBufferIndentGuide;
27use parking_lot::Mutex;
28use project::{buffer_store::BufferChangeSet, FakeFs};
29use project::{
30 lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
31 project_settings::{LspSettings, ProjectSettings},
32};
33use serde_json::{self, json};
34use std::sync::atomic::AtomicUsize;
35use std::sync::atomic::{self, AtomicBool};
36use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
37use test::editor_lsp_test_context::rust_lang;
38use unindent::Unindent;
39use util::{
40 assert_set_eq,
41 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
42};
43use workspace::{
44 item::{FollowEvent, FollowableItem, Item, ItemHandle},
45 NavigationEntry, ViewId,
46};
47
48#[gpui::test]
49fn test_edit_events(cx: &mut TestAppContext) {
50 init_test(cx, |_| {});
51
52 let buffer = cx.new_model(|cx| {
53 let mut buffer = language::Buffer::local("123456", cx);
54 buffer.set_group_interval(Duration::from_secs(1));
55 buffer
56 });
57
58 let events = Rc::new(RefCell::new(Vec::new()));
59 let editor1 = cx.add_window({
60 let events = events.clone();
61 |cx| {
62 let view = cx.view().clone();
63 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
64 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
65 EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
66 _ => {}
67 })
68 .detach();
69 Editor::for_buffer(buffer.clone(), None, cx)
70 }
71 });
72
73 let editor2 = cx.add_window({
74 let events = events.clone();
75 |cx| {
76 cx.subscribe(
77 &cx.view().clone(),
78 move |_, _, event: &EditorEvent, _| match event {
79 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
80 EditorEvent::BufferEdited => {
81 events.borrow_mut().push(("editor2", "buffer edited"))
82 }
83 _ => {}
84 },
85 )
86 .detach();
87 Editor::for_buffer(buffer.clone(), None, cx)
88 }
89 });
90
91 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
92
93 // Mutating editor 1 will emit an `Edited` event only for that editor.
94 _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
95 assert_eq!(
96 mem::take(&mut *events.borrow_mut()),
97 [
98 ("editor1", "edited"),
99 ("editor1", "buffer edited"),
100 ("editor2", "buffer edited"),
101 ]
102 );
103
104 // Mutating editor 2 will emit an `Edited` event only for that editor.
105 _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
106 assert_eq!(
107 mem::take(&mut *events.borrow_mut()),
108 [
109 ("editor2", "edited"),
110 ("editor1", "buffer edited"),
111 ("editor2", "buffer edited"),
112 ]
113 );
114
115 // Undoing on editor 1 will emit an `Edited` event only for that editor.
116 _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
117 assert_eq!(
118 mem::take(&mut *events.borrow_mut()),
119 [
120 ("editor1", "edited"),
121 ("editor1", "buffer edited"),
122 ("editor2", "buffer edited"),
123 ]
124 );
125
126 // Redoing on editor 1 will emit an `Edited` event only for that editor.
127 _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
128 assert_eq!(
129 mem::take(&mut *events.borrow_mut()),
130 [
131 ("editor1", "edited"),
132 ("editor1", "buffer edited"),
133 ("editor2", "buffer edited"),
134 ]
135 );
136
137 // Undoing on editor 2 will emit an `Edited` event only for that editor.
138 _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
139 assert_eq!(
140 mem::take(&mut *events.borrow_mut()),
141 [
142 ("editor2", "edited"),
143 ("editor1", "buffer edited"),
144 ("editor2", "buffer edited"),
145 ]
146 );
147
148 // Redoing on editor 2 will emit an `Edited` event only for that editor.
149 _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
150 assert_eq!(
151 mem::take(&mut *events.borrow_mut()),
152 [
153 ("editor2", "edited"),
154 ("editor1", "buffer edited"),
155 ("editor2", "buffer edited"),
156 ]
157 );
158
159 // No event is emitted when the mutation is a no-op.
160 _ = editor2.update(cx, |editor, cx| {
161 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
162
163 editor.backspace(&Backspace, cx);
164 });
165 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
166}
167
168#[gpui::test]
169fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
170 init_test(cx, |_| {});
171
172 let mut now = Instant::now();
173 let group_interval = Duration::from_millis(1);
174 let buffer = cx.new_model(|cx| {
175 let mut buf = language::Buffer::local("123456", cx);
176 buf.set_group_interval(group_interval);
177 buf
178 });
179 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
180 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
181
182 _ = editor.update(cx, |editor, cx| {
183 editor.start_transaction_at(now, cx);
184 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
185
186 editor.insert("cd", cx);
187 editor.end_transaction_at(now, cx);
188 assert_eq!(editor.text(cx), "12cd56");
189 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
190
191 editor.start_transaction_at(now, cx);
192 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
193 editor.insert("e", cx);
194 editor.end_transaction_at(now, cx);
195 assert_eq!(editor.text(cx), "12cde6");
196 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
197
198 now += group_interval + Duration::from_millis(1);
199 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
200
201 // Simulate an edit in another editor
202 buffer.update(cx, |buffer, cx| {
203 buffer.start_transaction_at(now, cx);
204 buffer.edit([(0..1, "a")], None, cx);
205 buffer.edit([(1..1, "b")], None, cx);
206 buffer.end_transaction_at(now, cx);
207 });
208
209 assert_eq!(editor.text(cx), "ab2cde6");
210 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
211
212 // Last transaction happened past the group interval in a different editor.
213 // Undo it individually and don't restore selections.
214 editor.undo(&Undo, cx);
215 assert_eq!(editor.text(cx), "12cde6");
216 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
217
218 // First two transactions happened within the group interval in this editor.
219 // Undo them together and restore selections.
220 editor.undo(&Undo, cx);
221 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
222 assert_eq!(editor.text(cx), "123456");
223 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
224
225 // Redo the first two transactions together.
226 editor.redo(&Redo, cx);
227 assert_eq!(editor.text(cx), "12cde6");
228 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
229
230 // Redo the last transaction on its own.
231 editor.redo(&Redo, cx);
232 assert_eq!(editor.text(cx), "ab2cde6");
233 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
234
235 // Test empty transactions.
236 editor.start_transaction_at(now, cx);
237 editor.end_transaction_at(now, cx);
238 editor.undo(&Undo, cx);
239 assert_eq!(editor.text(cx), "12cde6");
240 });
241}
242
243#[gpui::test]
244fn test_ime_composition(cx: &mut TestAppContext) {
245 init_test(cx, |_| {});
246
247 let buffer = cx.new_model(|cx| {
248 let mut buffer = language::Buffer::local("abcde", cx);
249 // Ensure automatic grouping doesn't occur.
250 buffer.set_group_interval(Duration::ZERO);
251 buffer
252 });
253
254 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
255 cx.add_window(|cx| {
256 let mut editor = build_editor(buffer.clone(), cx);
257
258 // Start a new IME composition.
259 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
260 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
261 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
262 assert_eq!(editor.text(cx), "äbcde");
263 assert_eq!(
264 editor.marked_text_ranges(cx),
265 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
266 );
267
268 // Finalize IME composition.
269 editor.replace_text_in_range(None, "ā", cx);
270 assert_eq!(editor.text(cx), "ābcde");
271 assert_eq!(editor.marked_text_ranges(cx), None);
272
273 // IME composition edits are grouped and are undone/redone at once.
274 editor.undo(&Default::default(), cx);
275 assert_eq!(editor.text(cx), "abcde");
276 assert_eq!(editor.marked_text_ranges(cx), None);
277 editor.redo(&Default::default(), cx);
278 assert_eq!(editor.text(cx), "ābcde");
279 assert_eq!(editor.marked_text_ranges(cx), None);
280
281 // Start a new IME composition.
282 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
283 assert_eq!(
284 editor.marked_text_ranges(cx),
285 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
286 );
287
288 // Undoing during an IME composition cancels it.
289 editor.undo(&Default::default(), cx);
290 assert_eq!(editor.text(cx), "ābcde");
291 assert_eq!(editor.marked_text_ranges(cx), None);
292
293 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
294 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
295 assert_eq!(editor.text(cx), "ābcdè");
296 assert_eq!(
297 editor.marked_text_ranges(cx),
298 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
299 );
300
301 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
302 editor.replace_text_in_range(Some(4..999), "ę", cx);
303 assert_eq!(editor.text(cx), "ābcdę");
304 assert_eq!(editor.marked_text_ranges(cx), None);
305
306 // Start a new IME composition with multiple cursors.
307 editor.change_selections(None, cx, |s| {
308 s.select_ranges([
309 OffsetUtf16(1)..OffsetUtf16(1),
310 OffsetUtf16(3)..OffsetUtf16(3),
311 OffsetUtf16(5)..OffsetUtf16(5),
312 ])
313 });
314 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
315 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
316 assert_eq!(
317 editor.marked_text_ranges(cx),
318 Some(vec![
319 OffsetUtf16(0)..OffsetUtf16(3),
320 OffsetUtf16(4)..OffsetUtf16(7),
321 OffsetUtf16(8)..OffsetUtf16(11)
322 ])
323 );
324
325 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
326 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
327 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
328 assert_eq!(
329 editor.marked_text_ranges(cx),
330 Some(vec![
331 OffsetUtf16(1)..OffsetUtf16(2),
332 OffsetUtf16(5)..OffsetUtf16(6),
333 OffsetUtf16(9)..OffsetUtf16(10)
334 ])
335 );
336
337 // Finalize IME composition with multiple cursors.
338 editor.replace_text_in_range(Some(9..10), "2", cx);
339 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
340 assert_eq!(editor.marked_text_ranges(cx), None);
341
342 editor
343 });
344}
345
346#[gpui::test]
347fn test_selection_with_mouse(cx: &mut TestAppContext) {
348 init_test(cx, |_| {});
349
350 let editor = cx.add_window(|cx| {
351 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
352 build_editor(buffer, cx)
353 });
354
355 _ = editor.update(cx, |view, cx| {
356 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
357 });
358 assert_eq!(
359 editor
360 .update(cx, |view, cx| view.selections.display_ranges(cx))
361 .unwrap(),
362 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
363 );
364
365 _ = editor.update(cx, |view, cx| {
366 view.update_selection(
367 DisplayPoint::new(DisplayRow(3), 3),
368 0,
369 gpui::Point::<f32>::default(),
370 cx,
371 );
372 });
373
374 assert_eq!(
375 editor
376 .update(cx, |view, cx| view.selections.display_ranges(cx))
377 .unwrap(),
378 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
379 );
380
381 _ = editor.update(cx, |view, cx| {
382 view.update_selection(
383 DisplayPoint::new(DisplayRow(1), 1),
384 0,
385 gpui::Point::<f32>::default(),
386 cx,
387 );
388 });
389
390 assert_eq!(
391 editor
392 .update(cx, |view, cx| view.selections.display_ranges(cx))
393 .unwrap(),
394 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
395 );
396
397 _ = editor.update(cx, |view, cx| {
398 view.end_selection(cx);
399 view.update_selection(
400 DisplayPoint::new(DisplayRow(3), 3),
401 0,
402 gpui::Point::<f32>::default(),
403 cx,
404 );
405 });
406
407 assert_eq!(
408 editor
409 .update(cx, |view, cx| view.selections.display_ranges(cx))
410 .unwrap(),
411 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
412 );
413
414 _ = editor.update(cx, |view, cx| {
415 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
416 view.update_selection(
417 DisplayPoint::new(DisplayRow(0), 0),
418 0,
419 gpui::Point::<f32>::default(),
420 cx,
421 );
422 });
423
424 assert_eq!(
425 editor
426 .update(cx, |view, cx| view.selections.display_ranges(cx))
427 .unwrap(),
428 [
429 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
430 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
431 ]
432 );
433
434 _ = editor.update(cx, |view, cx| {
435 view.end_selection(cx);
436 });
437
438 assert_eq!(
439 editor
440 .update(cx, |view, cx| view.selections.display_ranges(cx))
441 .unwrap(),
442 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
443 );
444}
445
446#[gpui::test]
447fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
448 init_test(cx, |_| {});
449
450 let editor = cx.add_window(|cx| {
451 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
452 build_editor(buffer, cx)
453 });
454
455 _ = editor.update(cx, |view, cx| {
456 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
457 });
458
459 _ = editor.update(cx, |view, cx| {
460 view.end_selection(cx);
461 });
462
463 _ = editor.update(cx, |view, cx| {
464 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
465 });
466
467 _ = editor.update(cx, |view, cx| {
468 view.end_selection(cx);
469 });
470
471 assert_eq!(
472 editor
473 .update(cx, |view, cx| view.selections.display_ranges(cx))
474 .unwrap(),
475 [
476 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
477 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
478 ]
479 );
480
481 _ = editor.update(cx, |view, cx| {
482 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
483 });
484
485 _ = editor.update(cx, |view, cx| {
486 view.end_selection(cx);
487 });
488
489 assert_eq!(
490 editor
491 .update(cx, |view, cx| view.selections.display_ranges(cx))
492 .unwrap(),
493 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
494 );
495}
496
497#[gpui::test]
498fn test_canceling_pending_selection(cx: &mut TestAppContext) {
499 init_test(cx, |_| {});
500
501 let view = cx.add_window(|cx| {
502 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
503 build_editor(buffer, cx)
504 });
505
506 _ = view.update(cx, |view, cx| {
507 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
508 assert_eq!(
509 view.selections.display_ranges(cx),
510 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
511 );
512 });
513
514 _ = view.update(cx, |view, cx| {
515 view.update_selection(
516 DisplayPoint::new(DisplayRow(3), 3),
517 0,
518 gpui::Point::<f32>::default(),
519 cx,
520 );
521 assert_eq!(
522 view.selections.display_ranges(cx),
523 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
524 );
525 });
526
527 _ = view.update(cx, |view, cx| {
528 view.cancel(&Cancel, cx);
529 view.update_selection(
530 DisplayPoint::new(DisplayRow(1), 1),
531 0,
532 gpui::Point::<f32>::default(),
533 cx,
534 );
535 assert_eq!(
536 view.selections.display_ranges(cx),
537 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
538 );
539 });
540}
541
542#[gpui::test]
543fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
544 init_test(cx, |_| {});
545
546 let view = cx.add_window(|cx| {
547 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
548 build_editor(buffer, cx)
549 });
550
551 _ = view.update(cx, |view, cx| {
552 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
553 assert_eq!(
554 view.selections.display_ranges(cx),
555 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
556 );
557
558 view.move_down(&Default::default(), cx);
559 assert_eq!(
560 view.selections.display_ranges(cx),
561 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
562 );
563
564 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
565 assert_eq!(
566 view.selections.display_ranges(cx),
567 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
568 );
569
570 view.move_up(&Default::default(), cx);
571 assert_eq!(
572 view.selections.display_ranges(cx),
573 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
574 );
575 });
576}
577
578#[gpui::test]
579fn test_clone(cx: &mut TestAppContext) {
580 init_test(cx, |_| {});
581
582 let (text, selection_ranges) = marked_text_ranges(
583 indoc! {"
584 one
585 two
586 threeˇ
587 four
588 fiveˇ
589 "},
590 true,
591 );
592
593 let editor = cx.add_window(|cx| {
594 let buffer = MultiBuffer::build_simple(&text, cx);
595 build_editor(buffer, cx)
596 });
597
598 _ = editor.update(cx, |editor, cx| {
599 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
600 editor.fold_creases(
601 vec![
602 Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
603 Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
604 ],
605 true,
606 cx,
607 );
608 });
609
610 let cloned_editor = editor
611 .update(cx, |editor, cx| {
612 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
613 })
614 .unwrap()
615 .unwrap();
616
617 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
618 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
619
620 assert_eq!(
621 cloned_editor
622 .update(cx, |e, cx| e.display_text(cx))
623 .unwrap(),
624 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
625 );
626 assert_eq!(
627 cloned_snapshot
628 .folds_in_range(0..text.len())
629 .collect::<Vec<_>>(),
630 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
631 );
632 assert_set_eq!(
633 cloned_editor
634 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
635 .unwrap(),
636 editor
637 .update(cx, |editor, cx| editor.selections.ranges(cx))
638 .unwrap()
639 );
640 assert_set_eq!(
641 cloned_editor
642 .update(cx, |e, cx| e.selections.display_ranges(cx))
643 .unwrap(),
644 editor
645 .update(cx, |e, cx| e.selections.display_ranges(cx))
646 .unwrap()
647 );
648}
649
650#[gpui::test]
651async fn test_navigation_history(cx: &mut TestAppContext) {
652 init_test(cx, |_| {});
653
654 use workspace::item::Item;
655
656 let fs = FakeFs::new(cx.executor());
657 let project = Project::test(fs, [], cx).await;
658 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
659 let pane = workspace
660 .update(cx, |workspace, _| workspace.active_pane().clone())
661 .unwrap();
662
663 _ = workspace.update(cx, |_v, cx| {
664 cx.new_view(|cx| {
665 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
666 let mut editor = build_editor(buffer.clone(), cx);
667 let handle = cx.view();
668 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(handle)));
669
670 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
671 editor.nav_history.as_mut().unwrap().pop_backward(cx)
672 }
673
674 // Move the cursor a small distance.
675 // Nothing is added to the navigation history.
676 editor.change_selections(None, cx, |s| {
677 s.select_display_ranges([
678 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
679 ])
680 });
681 editor.change_selections(None, cx, |s| {
682 s.select_display_ranges([
683 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
684 ])
685 });
686 assert!(pop_history(&mut editor, cx).is_none());
687
688 // Move the cursor a large distance.
689 // The history can jump back to the previous position.
690 editor.change_selections(None, cx, |s| {
691 s.select_display_ranges([
692 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
693 ])
694 });
695 let nav_entry = pop_history(&mut editor, cx).unwrap();
696 editor.navigate(nav_entry.data.unwrap(), cx);
697 assert_eq!(nav_entry.item.id(), cx.entity_id());
698 assert_eq!(
699 editor.selections.display_ranges(cx),
700 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
701 );
702 assert!(pop_history(&mut editor, cx).is_none());
703
704 // Move the cursor a small distance via the mouse.
705 // Nothing is added to the navigation history.
706 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
707 editor.end_selection(cx);
708 assert_eq!(
709 editor.selections.display_ranges(cx),
710 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
711 );
712 assert!(pop_history(&mut editor, cx).is_none());
713
714 // Move the cursor a large distance via the mouse.
715 // The history can jump back to the previous position.
716 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
717 editor.end_selection(cx);
718 assert_eq!(
719 editor.selections.display_ranges(cx),
720 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
721 );
722 let nav_entry = pop_history(&mut editor, cx).unwrap();
723 editor.navigate(nav_entry.data.unwrap(), cx);
724 assert_eq!(nav_entry.item.id(), cx.entity_id());
725 assert_eq!(
726 editor.selections.display_ranges(cx),
727 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
728 );
729 assert!(pop_history(&mut editor, cx).is_none());
730
731 // Set scroll position to check later
732 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
733 let original_scroll_position = editor.scroll_manager.anchor();
734
735 // Jump to the end of the document and adjust scroll
736 editor.move_to_end(&MoveToEnd, cx);
737 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
738 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
739
740 let nav_entry = pop_history(&mut editor, cx).unwrap();
741 editor.navigate(nav_entry.data.unwrap(), cx);
742 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
743
744 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
745 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
746 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
747 let invalid_point = Point::new(9999, 0);
748 editor.navigate(
749 Box::new(NavigationData {
750 cursor_anchor: invalid_anchor,
751 cursor_position: invalid_point,
752 scroll_anchor: ScrollAnchor {
753 anchor: invalid_anchor,
754 offset: Default::default(),
755 },
756 scroll_top_row: invalid_point.row,
757 }),
758 cx,
759 );
760 assert_eq!(
761 editor.selections.display_ranges(cx),
762 &[editor.max_point(cx)..editor.max_point(cx)]
763 );
764 assert_eq!(
765 editor.scroll_position(cx),
766 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
767 );
768
769 editor
770 })
771 });
772}
773
774#[gpui::test]
775fn test_cancel(cx: &mut TestAppContext) {
776 init_test(cx, |_| {});
777
778 let view = cx.add_window(|cx| {
779 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
780 build_editor(buffer, cx)
781 });
782
783 _ = view.update(cx, |view, cx| {
784 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
785 view.update_selection(
786 DisplayPoint::new(DisplayRow(1), 1),
787 0,
788 gpui::Point::<f32>::default(),
789 cx,
790 );
791 view.end_selection(cx);
792
793 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
794 view.update_selection(
795 DisplayPoint::new(DisplayRow(0), 3),
796 0,
797 gpui::Point::<f32>::default(),
798 cx,
799 );
800 view.end_selection(cx);
801 assert_eq!(
802 view.selections.display_ranges(cx),
803 [
804 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
805 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
806 ]
807 );
808 });
809
810 _ = view.update(cx, |view, cx| {
811 view.cancel(&Cancel, cx);
812 assert_eq!(
813 view.selections.display_ranges(cx),
814 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
815 );
816 });
817
818 _ = view.update(cx, |view, cx| {
819 view.cancel(&Cancel, cx);
820 assert_eq!(
821 view.selections.display_ranges(cx),
822 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
823 );
824 });
825}
826
827#[gpui::test]
828fn test_fold_action(cx: &mut TestAppContext) {
829 init_test(cx, |_| {});
830
831 let view = cx.add_window(|cx| {
832 let buffer = MultiBuffer::build_simple(
833 &"
834 impl Foo {
835 // Hello!
836
837 fn a() {
838 1
839 }
840
841 fn b() {
842 2
843 }
844
845 fn c() {
846 3
847 }
848 }
849 "
850 .unindent(),
851 cx,
852 );
853 build_editor(buffer.clone(), cx)
854 });
855
856 _ = view.update(cx, |view, cx| {
857 view.change_selections(None, cx, |s| {
858 s.select_display_ranges([
859 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
860 ]);
861 });
862 view.fold(&Fold, cx);
863 assert_eq!(
864 view.display_text(cx),
865 "
866 impl Foo {
867 // Hello!
868
869 fn a() {
870 1
871 }
872
873 fn b() {⋯
874 }
875
876 fn c() {⋯
877 }
878 }
879 "
880 .unindent(),
881 );
882
883 view.fold(&Fold, cx);
884 assert_eq!(
885 view.display_text(cx),
886 "
887 impl Foo {⋯
888 }
889 "
890 .unindent(),
891 );
892
893 view.unfold_lines(&UnfoldLines, cx);
894 assert_eq!(
895 view.display_text(cx),
896 "
897 impl Foo {
898 // Hello!
899
900 fn a() {
901 1
902 }
903
904 fn b() {⋯
905 }
906
907 fn c() {⋯
908 }
909 }
910 "
911 .unindent(),
912 );
913
914 view.unfold_lines(&UnfoldLines, cx);
915 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
916 });
917}
918
919#[gpui::test]
920fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
921 init_test(cx, |_| {});
922
923 let view = cx.add_window(|cx| {
924 let buffer = MultiBuffer::build_simple(
925 &"
926 class Foo:
927 # Hello!
928
929 def a():
930 print(1)
931
932 def b():
933 print(2)
934
935 def c():
936 print(3)
937 "
938 .unindent(),
939 cx,
940 );
941 build_editor(buffer.clone(), cx)
942 });
943
944 _ = view.update(cx, |view, cx| {
945 view.change_selections(None, cx, |s| {
946 s.select_display_ranges([
947 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
948 ]);
949 });
950 view.fold(&Fold, cx);
951 assert_eq!(
952 view.display_text(cx),
953 "
954 class Foo:
955 # Hello!
956
957 def a():
958 print(1)
959
960 def b():⋯
961
962 def c():⋯
963 "
964 .unindent(),
965 );
966
967 view.fold(&Fold, cx);
968 assert_eq!(
969 view.display_text(cx),
970 "
971 class Foo:⋯
972 "
973 .unindent(),
974 );
975
976 view.unfold_lines(&UnfoldLines, cx);
977 assert_eq!(
978 view.display_text(cx),
979 "
980 class Foo:
981 # Hello!
982
983 def a():
984 print(1)
985
986 def b():⋯
987
988 def c():⋯
989 "
990 .unindent(),
991 );
992
993 view.unfold_lines(&UnfoldLines, cx);
994 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
995 });
996}
997
998#[gpui::test]
999fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
1000 init_test(cx, |_| {});
1001
1002 let view = cx.add_window(|cx| {
1003 let buffer = MultiBuffer::build_simple(
1004 &"
1005 class Foo:
1006 # Hello!
1007
1008 def a():
1009 print(1)
1010
1011 def b():
1012 print(2)
1013
1014
1015 def c():
1016 print(3)
1017
1018
1019 "
1020 .unindent(),
1021 cx,
1022 );
1023 build_editor(buffer.clone(), cx)
1024 });
1025
1026 _ = view.update(cx, |view, cx| {
1027 view.change_selections(None, cx, |s| {
1028 s.select_display_ranges([
1029 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1030 ]);
1031 });
1032 view.fold(&Fold, cx);
1033 assert_eq!(
1034 view.display_text(cx),
1035 "
1036 class Foo:
1037 # Hello!
1038
1039 def a():
1040 print(1)
1041
1042 def b():⋯
1043
1044
1045 def c():⋯
1046
1047
1048 "
1049 .unindent(),
1050 );
1051
1052 view.fold(&Fold, cx);
1053 assert_eq!(
1054 view.display_text(cx),
1055 "
1056 class Foo:⋯
1057
1058
1059 "
1060 .unindent(),
1061 );
1062
1063 view.unfold_lines(&UnfoldLines, cx);
1064 assert_eq!(
1065 view.display_text(cx),
1066 "
1067 class Foo:
1068 # Hello!
1069
1070 def a():
1071 print(1)
1072
1073 def b():⋯
1074
1075
1076 def c():⋯
1077
1078
1079 "
1080 .unindent(),
1081 );
1082
1083 view.unfold_lines(&UnfoldLines, cx);
1084 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1085 });
1086}
1087
1088#[gpui::test]
1089fn test_fold_at_level(cx: &mut TestAppContext) {
1090 init_test(cx, |_| {});
1091
1092 let view = cx.add_window(|cx| {
1093 let buffer = MultiBuffer::build_simple(
1094 &"
1095 class Foo:
1096 # Hello!
1097
1098 def a():
1099 print(1)
1100
1101 def b():
1102 print(2)
1103
1104
1105 class Bar:
1106 # World!
1107
1108 def a():
1109 print(1)
1110
1111 def b():
1112 print(2)
1113
1114
1115 "
1116 .unindent(),
1117 cx,
1118 );
1119 build_editor(buffer.clone(), cx)
1120 });
1121
1122 _ = view.update(cx, |view, cx| {
1123 view.fold_at_level(&FoldAtLevel { level: 2 }, cx);
1124 assert_eq!(
1125 view.display_text(cx),
1126 "
1127 class Foo:
1128 # Hello!
1129
1130 def a():⋯
1131
1132 def b():⋯
1133
1134
1135 class Bar:
1136 # World!
1137
1138 def a():⋯
1139
1140 def b():⋯
1141
1142
1143 "
1144 .unindent(),
1145 );
1146
1147 view.fold_at_level(&FoldAtLevel { level: 1 }, cx);
1148 assert_eq!(
1149 view.display_text(cx),
1150 "
1151 class Foo:⋯
1152
1153
1154 class Bar:⋯
1155
1156
1157 "
1158 .unindent(),
1159 );
1160
1161 view.unfold_all(&UnfoldAll, cx);
1162 view.fold_at_level(&FoldAtLevel { level: 0 }, cx);
1163 assert_eq!(
1164 view.display_text(cx),
1165 "
1166 class Foo:
1167 # Hello!
1168
1169 def a():
1170 print(1)
1171
1172 def b():
1173 print(2)
1174
1175
1176 class Bar:
1177 # World!
1178
1179 def a():
1180 print(1)
1181
1182 def b():
1183 print(2)
1184
1185
1186 "
1187 .unindent(),
1188 );
1189
1190 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1191 });
1192}
1193
1194#[gpui::test]
1195fn test_move_cursor(cx: &mut TestAppContext) {
1196 init_test(cx, |_| {});
1197
1198 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1199 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1200
1201 buffer.update(cx, |buffer, cx| {
1202 buffer.edit(
1203 vec![
1204 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1205 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1206 ],
1207 None,
1208 cx,
1209 );
1210 });
1211 _ = view.update(cx, |view, cx| {
1212 assert_eq!(
1213 view.selections.display_ranges(cx),
1214 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1215 );
1216
1217 view.move_down(&MoveDown, cx);
1218 assert_eq!(
1219 view.selections.display_ranges(cx),
1220 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1221 );
1222
1223 view.move_right(&MoveRight, cx);
1224 assert_eq!(
1225 view.selections.display_ranges(cx),
1226 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1227 );
1228
1229 view.move_left(&MoveLeft, cx);
1230 assert_eq!(
1231 view.selections.display_ranges(cx),
1232 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1233 );
1234
1235 view.move_up(&MoveUp, cx);
1236 assert_eq!(
1237 view.selections.display_ranges(cx),
1238 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1239 );
1240
1241 view.move_to_end(&MoveToEnd, cx);
1242 assert_eq!(
1243 view.selections.display_ranges(cx),
1244 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1245 );
1246
1247 view.move_to_beginning(&MoveToBeginning, cx);
1248 assert_eq!(
1249 view.selections.display_ranges(cx),
1250 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1251 );
1252
1253 view.change_selections(None, cx, |s| {
1254 s.select_display_ranges([
1255 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1256 ]);
1257 });
1258 view.select_to_beginning(&SelectToBeginning, cx);
1259 assert_eq!(
1260 view.selections.display_ranges(cx),
1261 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1262 );
1263
1264 view.select_to_end(&SelectToEnd, cx);
1265 assert_eq!(
1266 view.selections.display_ranges(cx),
1267 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1268 );
1269 });
1270}
1271
1272// TODO: Re-enable this test
1273#[cfg(target_os = "macos")]
1274#[gpui::test]
1275fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1276 init_test(cx, |_| {});
1277
1278 let view = cx.add_window(|cx| {
1279 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1280 build_editor(buffer.clone(), cx)
1281 });
1282
1283 assert_eq!('ⓐ'.len_utf8(), 3);
1284 assert_eq!('α'.len_utf8(), 2);
1285
1286 _ = view.update(cx, |view, cx| {
1287 view.fold_creases(
1288 vec![
1289 Crease::simple(Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1290 Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1291 Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1292 ],
1293 true,
1294 cx,
1295 );
1296 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1297
1298 view.move_right(&MoveRight, cx);
1299 assert_eq!(
1300 view.selections.display_ranges(cx),
1301 &[empty_range(0, "ⓐ".len())]
1302 );
1303 view.move_right(&MoveRight, cx);
1304 assert_eq!(
1305 view.selections.display_ranges(cx),
1306 &[empty_range(0, "ⓐⓑ".len())]
1307 );
1308 view.move_right(&MoveRight, cx);
1309 assert_eq!(
1310 view.selections.display_ranges(cx),
1311 &[empty_range(0, "ⓐⓑ⋯".len())]
1312 );
1313
1314 view.move_down(&MoveDown, cx);
1315 assert_eq!(
1316 view.selections.display_ranges(cx),
1317 &[empty_range(1, "ab⋯e".len())]
1318 );
1319 view.move_left(&MoveLeft, cx);
1320 assert_eq!(
1321 view.selections.display_ranges(cx),
1322 &[empty_range(1, "ab⋯".len())]
1323 );
1324 view.move_left(&MoveLeft, cx);
1325 assert_eq!(
1326 view.selections.display_ranges(cx),
1327 &[empty_range(1, "ab".len())]
1328 );
1329 view.move_left(&MoveLeft, cx);
1330 assert_eq!(
1331 view.selections.display_ranges(cx),
1332 &[empty_range(1, "a".len())]
1333 );
1334
1335 view.move_down(&MoveDown, cx);
1336 assert_eq!(
1337 view.selections.display_ranges(cx),
1338 &[empty_range(2, "α".len())]
1339 );
1340 view.move_right(&MoveRight, cx);
1341 assert_eq!(
1342 view.selections.display_ranges(cx),
1343 &[empty_range(2, "αβ".len())]
1344 );
1345 view.move_right(&MoveRight, cx);
1346 assert_eq!(
1347 view.selections.display_ranges(cx),
1348 &[empty_range(2, "αβ⋯".len())]
1349 );
1350 view.move_right(&MoveRight, cx);
1351 assert_eq!(
1352 view.selections.display_ranges(cx),
1353 &[empty_range(2, "αβ⋯ε".len())]
1354 );
1355
1356 view.move_up(&MoveUp, cx);
1357 assert_eq!(
1358 view.selections.display_ranges(cx),
1359 &[empty_range(1, "ab⋯e".len())]
1360 );
1361 view.move_down(&MoveDown, cx);
1362 assert_eq!(
1363 view.selections.display_ranges(cx),
1364 &[empty_range(2, "αβ⋯ε".len())]
1365 );
1366 view.move_up(&MoveUp, cx);
1367 assert_eq!(
1368 view.selections.display_ranges(cx),
1369 &[empty_range(1, "ab⋯e".len())]
1370 );
1371
1372 view.move_up(&MoveUp, cx);
1373 assert_eq!(
1374 view.selections.display_ranges(cx),
1375 &[empty_range(0, "ⓐⓑ".len())]
1376 );
1377 view.move_left(&MoveLeft, cx);
1378 assert_eq!(
1379 view.selections.display_ranges(cx),
1380 &[empty_range(0, "ⓐ".len())]
1381 );
1382 view.move_left(&MoveLeft, cx);
1383 assert_eq!(
1384 view.selections.display_ranges(cx),
1385 &[empty_range(0, "".len())]
1386 );
1387 });
1388}
1389
1390#[gpui::test]
1391fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1392 init_test(cx, |_| {});
1393
1394 let view = cx.add_window(|cx| {
1395 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1396 build_editor(buffer.clone(), cx)
1397 });
1398 _ = view.update(cx, |view, cx| {
1399 view.change_selections(None, cx, |s| {
1400 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1401 });
1402
1403 // moving above start of document should move selection to start of document,
1404 // but the next move down should still be at the original goal_x
1405 view.move_up(&MoveUp, cx);
1406 assert_eq!(
1407 view.selections.display_ranges(cx),
1408 &[empty_range(0, "".len())]
1409 );
1410
1411 view.move_down(&MoveDown, cx);
1412 assert_eq!(
1413 view.selections.display_ranges(cx),
1414 &[empty_range(1, "abcd".len())]
1415 );
1416
1417 view.move_down(&MoveDown, cx);
1418 assert_eq!(
1419 view.selections.display_ranges(cx),
1420 &[empty_range(2, "αβγ".len())]
1421 );
1422
1423 view.move_down(&MoveDown, cx);
1424 assert_eq!(
1425 view.selections.display_ranges(cx),
1426 &[empty_range(3, "abcd".len())]
1427 );
1428
1429 view.move_down(&MoveDown, cx);
1430 assert_eq!(
1431 view.selections.display_ranges(cx),
1432 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1433 );
1434
1435 // moving past end of document should not change goal_x
1436 view.move_down(&MoveDown, cx);
1437 assert_eq!(
1438 view.selections.display_ranges(cx),
1439 &[empty_range(5, "".len())]
1440 );
1441
1442 view.move_down(&MoveDown, cx);
1443 assert_eq!(
1444 view.selections.display_ranges(cx),
1445 &[empty_range(5, "".len())]
1446 );
1447
1448 view.move_up(&MoveUp, cx);
1449 assert_eq!(
1450 view.selections.display_ranges(cx),
1451 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1452 );
1453
1454 view.move_up(&MoveUp, cx);
1455 assert_eq!(
1456 view.selections.display_ranges(cx),
1457 &[empty_range(3, "abcd".len())]
1458 );
1459
1460 view.move_up(&MoveUp, cx);
1461 assert_eq!(
1462 view.selections.display_ranges(cx),
1463 &[empty_range(2, "αβγ".len())]
1464 );
1465 });
1466}
1467
1468#[gpui::test]
1469fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1470 init_test(cx, |_| {});
1471 let move_to_beg = MoveToBeginningOfLine {
1472 stop_at_soft_wraps: true,
1473 };
1474
1475 let move_to_end = MoveToEndOfLine {
1476 stop_at_soft_wraps: true,
1477 };
1478
1479 let view = cx.add_window(|cx| {
1480 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1481 build_editor(buffer, cx)
1482 });
1483 _ = view.update(cx, |view, cx| {
1484 view.change_selections(None, cx, |s| {
1485 s.select_display_ranges([
1486 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1487 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1488 ]);
1489 });
1490 });
1491
1492 _ = view.update(cx, |view, cx| {
1493 view.move_to_beginning_of_line(&move_to_beg, cx);
1494 assert_eq!(
1495 view.selections.display_ranges(cx),
1496 &[
1497 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1498 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1499 ]
1500 );
1501 });
1502
1503 _ = view.update(cx, |view, cx| {
1504 view.move_to_beginning_of_line(&move_to_beg, cx);
1505 assert_eq!(
1506 view.selections.display_ranges(cx),
1507 &[
1508 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1509 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1510 ]
1511 );
1512 });
1513
1514 _ = view.update(cx, |view, cx| {
1515 view.move_to_beginning_of_line(&move_to_beg, cx);
1516 assert_eq!(
1517 view.selections.display_ranges(cx),
1518 &[
1519 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1520 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1521 ]
1522 );
1523 });
1524
1525 _ = view.update(cx, |view, cx| {
1526 view.move_to_end_of_line(&move_to_end, cx);
1527 assert_eq!(
1528 view.selections.display_ranges(cx),
1529 &[
1530 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1531 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1532 ]
1533 );
1534 });
1535
1536 // Moving to the end of line again is a no-op.
1537 _ = view.update(cx, |view, cx| {
1538 view.move_to_end_of_line(&move_to_end, cx);
1539 assert_eq!(
1540 view.selections.display_ranges(cx),
1541 &[
1542 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1543 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1544 ]
1545 );
1546 });
1547
1548 _ = view.update(cx, |view, cx| {
1549 view.move_left(&MoveLeft, cx);
1550 view.select_to_beginning_of_line(
1551 &SelectToBeginningOfLine {
1552 stop_at_soft_wraps: true,
1553 },
1554 cx,
1555 );
1556 assert_eq!(
1557 view.selections.display_ranges(cx),
1558 &[
1559 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1560 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1561 ]
1562 );
1563 });
1564
1565 _ = view.update(cx, |view, cx| {
1566 view.select_to_beginning_of_line(
1567 &SelectToBeginningOfLine {
1568 stop_at_soft_wraps: true,
1569 },
1570 cx,
1571 );
1572 assert_eq!(
1573 view.selections.display_ranges(cx),
1574 &[
1575 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1576 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1577 ]
1578 );
1579 });
1580
1581 _ = view.update(cx, |view, cx| {
1582 view.select_to_beginning_of_line(
1583 &SelectToBeginningOfLine {
1584 stop_at_soft_wraps: true,
1585 },
1586 cx,
1587 );
1588 assert_eq!(
1589 view.selections.display_ranges(cx),
1590 &[
1591 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1592 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1593 ]
1594 );
1595 });
1596
1597 _ = view.update(cx, |view, cx| {
1598 view.select_to_end_of_line(
1599 &SelectToEndOfLine {
1600 stop_at_soft_wraps: true,
1601 },
1602 cx,
1603 );
1604 assert_eq!(
1605 view.selections.display_ranges(cx),
1606 &[
1607 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1608 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1609 ]
1610 );
1611 });
1612
1613 _ = view.update(cx, |view, cx| {
1614 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1615 assert_eq!(view.display_text(cx), "ab\n de");
1616 assert_eq!(
1617 view.selections.display_ranges(cx),
1618 &[
1619 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1620 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1621 ]
1622 );
1623 });
1624
1625 _ = view.update(cx, |view, cx| {
1626 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1627 assert_eq!(view.display_text(cx), "\n");
1628 assert_eq!(
1629 view.selections.display_ranges(cx),
1630 &[
1631 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1632 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1633 ]
1634 );
1635 });
1636}
1637
1638#[gpui::test]
1639fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1640 init_test(cx, |_| {});
1641 let move_to_beg = MoveToBeginningOfLine {
1642 stop_at_soft_wraps: false,
1643 };
1644
1645 let move_to_end = MoveToEndOfLine {
1646 stop_at_soft_wraps: false,
1647 };
1648
1649 let view = cx.add_window(|cx| {
1650 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1651 build_editor(buffer, cx)
1652 });
1653
1654 _ = view.update(cx, |view, cx| {
1655 view.set_wrap_width(Some(140.0.into()), cx);
1656
1657 // We expect the following lines after wrapping
1658 // ```
1659 // thequickbrownfox
1660 // jumpedoverthelazydo
1661 // gs
1662 // ```
1663 // The final `gs` was soft-wrapped onto a new line.
1664 assert_eq!(
1665 "thequickbrownfox\njumpedoverthelaz\nydogs",
1666 view.display_text(cx),
1667 );
1668
1669 // First, let's assert behavior on the first line, that was not soft-wrapped.
1670 // Start the cursor at the `k` on the first line
1671 view.change_selections(None, cx, |s| {
1672 s.select_display_ranges([
1673 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1674 ]);
1675 });
1676
1677 // Moving to the beginning of the line should put us at the beginning of the line.
1678 view.move_to_beginning_of_line(&move_to_beg, cx);
1679 assert_eq!(
1680 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1681 view.selections.display_ranges(cx)
1682 );
1683
1684 // Moving to the end of the line should put us at the end of the line.
1685 view.move_to_end_of_line(&move_to_end, cx);
1686 assert_eq!(
1687 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1688 view.selections.display_ranges(cx)
1689 );
1690
1691 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1692 // Start the cursor at the last line (`y` that was wrapped to a new line)
1693 view.change_selections(None, cx, |s| {
1694 s.select_display_ranges([
1695 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1696 ]);
1697 });
1698
1699 // Moving to the beginning of the line should put us at the start of the second line of
1700 // display text, i.e., the `j`.
1701 view.move_to_beginning_of_line(&move_to_beg, cx);
1702 assert_eq!(
1703 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1704 view.selections.display_ranges(cx)
1705 );
1706
1707 // Moving to the beginning of the line again should be a no-op.
1708 view.move_to_beginning_of_line(&move_to_beg, cx);
1709 assert_eq!(
1710 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1711 view.selections.display_ranges(cx)
1712 );
1713
1714 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1715 // next display line.
1716 view.move_to_end_of_line(&move_to_end, cx);
1717 assert_eq!(
1718 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1719 view.selections.display_ranges(cx)
1720 );
1721
1722 // Moving to the end of the line again should be a no-op.
1723 view.move_to_end_of_line(&move_to_end, cx);
1724 assert_eq!(
1725 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1726 view.selections.display_ranges(cx)
1727 );
1728 });
1729}
1730
1731#[gpui::test]
1732fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1733 init_test(cx, |_| {});
1734
1735 let view = cx.add_window(|cx| {
1736 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1737 build_editor(buffer, cx)
1738 });
1739 _ = view.update(cx, |view, cx| {
1740 view.change_selections(None, cx, |s| {
1741 s.select_display_ranges([
1742 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1743 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1744 ])
1745 });
1746
1747 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1748 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1749
1750 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1751 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", view, cx);
1752
1753 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1754 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", view, cx);
1755
1756 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1757 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1758
1759 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1760 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", view, cx);
1761
1762 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1763 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", view, cx);
1764
1765 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1766 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", view, cx);
1767
1768 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1769 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1770
1771 view.move_right(&MoveRight, cx);
1772 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1773 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1774
1775 view.select_to_previous_word_start(&SelectToPreviousWordStart, cx);
1776 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1777
1778 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1779 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1780 });
1781}
1782
1783#[gpui::test]
1784fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1785 init_test(cx, |_| {});
1786
1787 let view = cx.add_window(|cx| {
1788 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1789 build_editor(buffer, cx)
1790 });
1791
1792 _ = view.update(cx, |view, cx| {
1793 view.set_wrap_width(Some(140.0.into()), cx);
1794 assert_eq!(
1795 view.display_text(cx),
1796 "use one::{\n two::three::\n four::five\n};"
1797 );
1798
1799 view.change_selections(None, cx, |s| {
1800 s.select_display_ranges([
1801 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1802 ]);
1803 });
1804
1805 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1806 assert_eq!(
1807 view.selections.display_ranges(cx),
1808 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1809 );
1810
1811 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1812 assert_eq!(
1813 view.selections.display_ranges(cx),
1814 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1815 );
1816
1817 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1818 assert_eq!(
1819 view.selections.display_ranges(cx),
1820 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1821 );
1822
1823 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1824 assert_eq!(
1825 view.selections.display_ranges(cx),
1826 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1827 );
1828
1829 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1830 assert_eq!(
1831 view.selections.display_ranges(cx),
1832 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1833 );
1834
1835 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1836 assert_eq!(
1837 view.selections.display_ranges(cx),
1838 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1839 );
1840 });
1841}
1842
1843#[gpui::test]
1844async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1845 init_test(cx, |_| {});
1846 let mut cx = EditorTestContext::new(cx).await;
1847
1848 let line_height = cx.editor(|editor, cx| {
1849 editor
1850 .style()
1851 .unwrap()
1852 .text
1853 .line_height_in_pixels(cx.rem_size())
1854 });
1855 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1856
1857 cx.set_state(
1858 &r#"ˇone
1859 two
1860
1861 three
1862 fourˇ
1863 five
1864
1865 six"#
1866 .unindent(),
1867 );
1868
1869 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1870 cx.assert_editor_state(
1871 &r#"one
1872 two
1873 ˇ
1874 three
1875 four
1876 five
1877 ˇ
1878 six"#
1879 .unindent(),
1880 );
1881
1882 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1883 cx.assert_editor_state(
1884 &r#"one
1885 two
1886
1887 three
1888 four
1889 five
1890 ˇ
1891 sixˇ"#
1892 .unindent(),
1893 );
1894
1895 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1896 cx.assert_editor_state(
1897 &r#"one
1898 two
1899
1900 three
1901 four
1902 five
1903
1904 sixˇ"#
1905 .unindent(),
1906 );
1907
1908 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1909 cx.assert_editor_state(
1910 &r#"one
1911 two
1912
1913 three
1914 four
1915 five
1916 ˇ
1917 six"#
1918 .unindent(),
1919 );
1920
1921 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1922 cx.assert_editor_state(
1923 &r#"one
1924 two
1925 ˇ
1926 three
1927 four
1928 five
1929
1930 six"#
1931 .unindent(),
1932 );
1933
1934 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1935 cx.assert_editor_state(
1936 &r#"ˇone
1937 two
1938
1939 three
1940 four
1941 five
1942
1943 six"#
1944 .unindent(),
1945 );
1946}
1947
1948#[gpui::test]
1949async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1950 init_test(cx, |_| {});
1951 let mut cx = EditorTestContext::new(cx).await;
1952 let line_height = cx.editor(|editor, cx| {
1953 editor
1954 .style()
1955 .unwrap()
1956 .text
1957 .line_height_in_pixels(cx.rem_size())
1958 });
1959 let window = cx.window;
1960 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1961
1962 cx.set_state(
1963 r#"ˇone
1964 two
1965 three
1966 four
1967 five
1968 six
1969 seven
1970 eight
1971 nine
1972 ten
1973 "#,
1974 );
1975
1976 cx.update_editor(|editor, cx| {
1977 assert_eq!(
1978 editor.snapshot(cx).scroll_position(),
1979 gpui::Point::new(0., 0.)
1980 );
1981 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1982 assert_eq!(
1983 editor.snapshot(cx).scroll_position(),
1984 gpui::Point::new(0., 3.)
1985 );
1986 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1987 assert_eq!(
1988 editor.snapshot(cx).scroll_position(),
1989 gpui::Point::new(0., 6.)
1990 );
1991 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1992 assert_eq!(
1993 editor.snapshot(cx).scroll_position(),
1994 gpui::Point::new(0., 3.)
1995 );
1996
1997 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
1998 assert_eq!(
1999 editor.snapshot(cx).scroll_position(),
2000 gpui::Point::new(0., 1.)
2001 );
2002 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
2003 assert_eq!(
2004 editor.snapshot(cx).scroll_position(),
2005 gpui::Point::new(0., 3.)
2006 );
2007 });
2008}
2009
2010#[gpui::test]
2011async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
2012 init_test(cx, |_| {});
2013 let mut cx = EditorTestContext::new(cx).await;
2014
2015 let line_height = cx.update_editor(|editor, cx| {
2016 editor.set_vertical_scroll_margin(2, cx);
2017 editor
2018 .style()
2019 .unwrap()
2020 .text
2021 .line_height_in_pixels(cx.rem_size())
2022 });
2023 let window = cx.window;
2024 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2025
2026 cx.set_state(
2027 r#"ˇone
2028 two
2029 three
2030 four
2031 five
2032 six
2033 seven
2034 eight
2035 nine
2036 ten
2037 "#,
2038 );
2039 cx.update_editor(|editor, cx| {
2040 assert_eq!(
2041 editor.snapshot(cx).scroll_position(),
2042 gpui::Point::new(0., 0.0)
2043 );
2044 });
2045
2046 // Add a cursor below the visible area. Since both cursors cannot fit
2047 // on screen, the editor autoscrolls to reveal the newest cursor, and
2048 // allows the vertical scroll margin below that cursor.
2049 cx.update_editor(|editor, cx| {
2050 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2051 selections.select_ranges([
2052 Point::new(0, 0)..Point::new(0, 0),
2053 Point::new(6, 0)..Point::new(6, 0),
2054 ]);
2055 })
2056 });
2057 cx.update_editor(|editor, cx| {
2058 assert_eq!(
2059 editor.snapshot(cx).scroll_position(),
2060 gpui::Point::new(0., 3.0)
2061 );
2062 });
2063
2064 // Move down. The editor cursor scrolls down to track the newest cursor.
2065 cx.update_editor(|editor, cx| {
2066 editor.move_down(&Default::default(), cx);
2067 });
2068 cx.update_editor(|editor, cx| {
2069 assert_eq!(
2070 editor.snapshot(cx).scroll_position(),
2071 gpui::Point::new(0., 4.0)
2072 );
2073 });
2074
2075 // Add a cursor above the visible area. Since both cursors fit on screen,
2076 // the editor scrolls to show both.
2077 cx.update_editor(|editor, cx| {
2078 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2079 selections.select_ranges([
2080 Point::new(1, 0)..Point::new(1, 0),
2081 Point::new(6, 0)..Point::new(6, 0),
2082 ]);
2083 })
2084 });
2085 cx.update_editor(|editor, cx| {
2086 assert_eq!(
2087 editor.snapshot(cx).scroll_position(),
2088 gpui::Point::new(0., 1.0)
2089 );
2090 });
2091}
2092
2093#[gpui::test]
2094async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
2095 init_test(cx, |_| {});
2096 let mut cx = EditorTestContext::new(cx).await;
2097
2098 let line_height = cx.editor(|editor, cx| {
2099 editor
2100 .style()
2101 .unwrap()
2102 .text
2103 .line_height_in_pixels(cx.rem_size())
2104 });
2105 let window = cx.window;
2106 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2107 cx.set_state(
2108 &r#"
2109 ˇone
2110 two
2111 threeˇ
2112 four
2113 five
2114 six
2115 seven
2116 eight
2117 nine
2118 ten
2119 "#
2120 .unindent(),
2121 );
2122
2123 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2124 cx.assert_editor_state(
2125 &r#"
2126 one
2127 two
2128 three
2129 ˇfour
2130 five
2131 sixˇ
2132 seven
2133 eight
2134 nine
2135 ten
2136 "#
2137 .unindent(),
2138 );
2139
2140 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2141 cx.assert_editor_state(
2142 &r#"
2143 one
2144 two
2145 three
2146 four
2147 five
2148 six
2149 ˇseven
2150 eight
2151 nineˇ
2152 ten
2153 "#
2154 .unindent(),
2155 );
2156
2157 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2158 cx.assert_editor_state(
2159 &r#"
2160 one
2161 two
2162 three
2163 ˇfour
2164 five
2165 sixˇ
2166 seven
2167 eight
2168 nine
2169 ten
2170 "#
2171 .unindent(),
2172 );
2173
2174 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2175 cx.assert_editor_state(
2176 &r#"
2177 ˇone
2178 two
2179 threeˇ
2180 four
2181 five
2182 six
2183 seven
2184 eight
2185 nine
2186 ten
2187 "#
2188 .unindent(),
2189 );
2190
2191 // Test select collapsing
2192 cx.update_editor(|editor, cx| {
2193 editor.move_page_down(&MovePageDown::default(), cx);
2194 editor.move_page_down(&MovePageDown::default(), cx);
2195 editor.move_page_down(&MovePageDown::default(), cx);
2196 });
2197 cx.assert_editor_state(
2198 &r#"
2199 one
2200 two
2201 three
2202 four
2203 five
2204 six
2205 seven
2206 eight
2207 nine
2208 ˇten
2209 ˇ"#
2210 .unindent(),
2211 );
2212}
2213
2214#[gpui::test]
2215async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2216 init_test(cx, |_| {});
2217 let mut cx = EditorTestContext::new(cx).await;
2218 cx.set_state("one «two threeˇ» four");
2219 cx.update_editor(|editor, cx| {
2220 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2221 assert_eq!(editor.text(cx), " four");
2222 });
2223}
2224
2225#[gpui::test]
2226fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2227 init_test(cx, |_| {});
2228
2229 let view = cx.add_window(|cx| {
2230 let buffer = MultiBuffer::build_simple("one two three four", cx);
2231 build_editor(buffer.clone(), cx)
2232 });
2233
2234 _ = view.update(cx, |view, cx| {
2235 view.change_selections(None, cx, |s| {
2236 s.select_display_ranges([
2237 // an empty selection - the preceding word fragment is deleted
2238 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2239 // characters selected - they are deleted
2240 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2241 ])
2242 });
2243 view.delete_to_previous_word_start(
2244 &DeleteToPreviousWordStart {
2245 ignore_newlines: false,
2246 },
2247 cx,
2248 );
2249 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2250 });
2251
2252 _ = view.update(cx, |view, cx| {
2253 view.change_selections(None, cx, |s| {
2254 s.select_display_ranges([
2255 // an empty selection - the following word fragment is deleted
2256 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2257 // characters selected - they are deleted
2258 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2259 ])
2260 });
2261 view.delete_to_next_word_end(
2262 &DeleteToNextWordEnd {
2263 ignore_newlines: false,
2264 },
2265 cx,
2266 );
2267 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2268 });
2269}
2270
2271#[gpui::test]
2272fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2273 init_test(cx, |_| {});
2274
2275 let view = cx.add_window(|cx| {
2276 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2277 build_editor(buffer.clone(), cx)
2278 });
2279 let del_to_prev_word_start = DeleteToPreviousWordStart {
2280 ignore_newlines: false,
2281 };
2282 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2283 ignore_newlines: true,
2284 };
2285
2286 _ = view.update(cx, |view, cx| {
2287 view.change_selections(None, cx, |s| {
2288 s.select_display_ranges([
2289 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2290 ])
2291 });
2292 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2293 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2294 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2295 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2296 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2297 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\n");
2298 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2299 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2");
2300 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2301 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n");
2302 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2303 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2304 });
2305}
2306
2307#[gpui::test]
2308fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2309 init_test(cx, |_| {});
2310
2311 let view = cx.add_window(|cx| {
2312 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2313 build_editor(buffer.clone(), cx)
2314 });
2315 let del_to_next_word_end = DeleteToNextWordEnd {
2316 ignore_newlines: false,
2317 };
2318 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2319 ignore_newlines: true,
2320 };
2321
2322 _ = view.update(cx, |view, cx| {
2323 view.change_selections(None, cx, |s| {
2324 s.select_display_ranges([
2325 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2326 ])
2327 });
2328 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2329 assert_eq!(
2330 view.buffer.read(cx).read(cx).text(),
2331 "one\n two\nthree\n four"
2332 );
2333 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2334 assert_eq!(
2335 view.buffer.read(cx).read(cx).text(),
2336 "\n two\nthree\n four"
2337 );
2338 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2339 assert_eq!(view.buffer.read(cx).read(cx).text(), "two\nthree\n four");
2340 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2341 assert_eq!(view.buffer.read(cx).read(cx).text(), "\nthree\n four");
2342 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2343 assert_eq!(view.buffer.read(cx).read(cx).text(), "\n four");
2344 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2345 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2346 });
2347}
2348
2349#[gpui::test]
2350fn test_newline(cx: &mut TestAppContext) {
2351 init_test(cx, |_| {});
2352
2353 let view = cx.add_window(|cx| {
2354 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2355 build_editor(buffer.clone(), cx)
2356 });
2357
2358 _ = view.update(cx, |view, cx| {
2359 view.change_selections(None, cx, |s| {
2360 s.select_display_ranges([
2361 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2362 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2363 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2364 ])
2365 });
2366
2367 view.newline(&Newline, cx);
2368 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2369 });
2370}
2371
2372#[gpui::test]
2373fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2374 init_test(cx, |_| {});
2375
2376 let editor = cx.add_window(|cx| {
2377 let buffer = MultiBuffer::build_simple(
2378 "
2379 a
2380 b(
2381 X
2382 )
2383 c(
2384 X
2385 )
2386 "
2387 .unindent()
2388 .as_str(),
2389 cx,
2390 );
2391 let mut editor = build_editor(buffer.clone(), cx);
2392 editor.change_selections(None, cx, |s| {
2393 s.select_ranges([
2394 Point::new(2, 4)..Point::new(2, 5),
2395 Point::new(5, 4)..Point::new(5, 5),
2396 ])
2397 });
2398 editor
2399 });
2400
2401 _ = editor.update(cx, |editor, cx| {
2402 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2403 editor.buffer.update(cx, |buffer, cx| {
2404 buffer.edit(
2405 [
2406 (Point::new(1, 2)..Point::new(3, 0), ""),
2407 (Point::new(4, 2)..Point::new(6, 0), ""),
2408 ],
2409 None,
2410 cx,
2411 );
2412 assert_eq!(
2413 buffer.read(cx).text(),
2414 "
2415 a
2416 b()
2417 c()
2418 "
2419 .unindent()
2420 );
2421 });
2422 assert_eq!(
2423 editor.selections.ranges(cx),
2424 &[
2425 Point::new(1, 2)..Point::new(1, 2),
2426 Point::new(2, 2)..Point::new(2, 2),
2427 ],
2428 );
2429
2430 editor.newline(&Newline, cx);
2431 assert_eq!(
2432 editor.text(cx),
2433 "
2434 a
2435 b(
2436 )
2437 c(
2438 )
2439 "
2440 .unindent()
2441 );
2442
2443 // The selections are moved after the inserted newlines
2444 assert_eq!(
2445 editor.selections.ranges(cx),
2446 &[
2447 Point::new(2, 0)..Point::new(2, 0),
2448 Point::new(4, 0)..Point::new(4, 0),
2449 ],
2450 );
2451 });
2452}
2453
2454#[gpui::test]
2455async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2456 init_test(cx, |settings| {
2457 settings.defaults.tab_size = NonZeroU32::new(4)
2458 });
2459
2460 let language = Arc::new(
2461 Language::new(
2462 LanguageConfig::default(),
2463 Some(tree_sitter_rust::LANGUAGE.into()),
2464 )
2465 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2466 .unwrap(),
2467 );
2468
2469 let mut cx = EditorTestContext::new(cx).await;
2470 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2471 cx.set_state(indoc! {"
2472 const a: ˇA = (
2473 (ˇ
2474 «const_functionˇ»(ˇ),
2475 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2476 )ˇ
2477 ˇ);ˇ
2478 "});
2479
2480 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2481 cx.assert_editor_state(indoc! {"
2482 ˇ
2483 const a: A = (
2484 ˇ
2485 (
2486 ˇ
2487 ˇ
2488 const_function(),
2489 ˇ
2490 ˇ
2491 ˇ
2492 ˇ
2493 something_else,
2494 ˇ
2495 )
2496 ˇ
2497 ˇ
2498 );
2499 "});
2500}
2501
2502#[gpui::test]
2503async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2504 init_test(cx, |settings| {
2505 settings.defaults.tab_size = NonZeroU32::new(4)
2506 });
2507
2508 let language = Arc::new(
2509 Language::new(
2510 LanguageConfig::default(),
2511 Some(tree_sitter_rust::LANGUAGE.into()),
2512 )
2513 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2514 .unwrap(),
2515 );
2516
2517 let mut cx = EditorTestContext::new(cx).await;
2518 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2519 cx.set_state(indoc! {"
2520 const a: ˇA = (
2521 (ˇ
2522 «const_functionˇ»(ˇ),
2523 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2524 )ˇ
2525 ˇ);ˇ
2526 "});
2527
2528 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2529 cx.assert_editor_state(indoc! {"
2530 const a: A = (
2531 ˇ
2532 (
2533 ˇ
2534 const_function(),
2535 ˇ
2536 ˇ
2537 something_else,
2538 ˇ
2539 ˇ
2540 ˇ
2541 ˇ
2542 )
2543 ˇ
2544 );
2545 ˇ
2546 ˇ
2547 "});
2548}
2549
2550#[gpui::test]
2551async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2552 init_test(cx, |settings| {
2553 settings.defaults.tab_size = NonZeroU32::new(4)
2554 });
2555
2556 let language = Arc::new(Language::new(
2557 LanguageConfig {
2558 line_comments: vec!["//".into()],
2559 ..LanguageConfig::default()
2560 },
2561 None,
2562 ));
2563 {
2564 let mut cx = EditorTestContext::new(cx).await;
2565 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2566 cx.set_state(indoc! {"
2567 // Fooˇ
2568 "});
2569
2570 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2571 cx.assert_editor_state(indoc! {"
2572 // Foo
2573 //ˇ
2574 "});
2575 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2576 cx.set_state(indoc! {"
2577 ˇ// Foo
2578 "});
2579 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2580 cx.assert_editor_state(indoc! {"
2581
2582 ˇ// Foo
2583 "});
2584 }
2585 // Ensure that comment continuations can be disabled.
2586 update_test_language_settings(cx, |settings| {
2587 settings.defaults.extend_comment_on_newline = Some(false);
2588 });
2589 let mut cx = EditorTestContext::new(cx).await;
2590 cx.set_state(indoc! {"
2591 // Fooˇ
2592 "});
2593 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2594 cx.assert_editor_state(indoc! {"
2595 // Foo
2596 ˇ
2597 "});
2598}
2599
2600#[gpui::test]
2601fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2602 init_test(cx, |_| {});
2603
2604 let editor = cx.add_window(|cx| {
2605 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2606 let mut editor = build_editor(buffer.clone(), cx);
2607 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2608 editor
2609 });
2610
2611 _ = editor.update(cx, |editor, cx| {
2612 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2613 editor.buffer.update(cx, |buffer, cx| {
2614 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2615 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2616 });
2617 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2618
2619 editor.insert("Z", cx);
2620 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2621
2622 // The selections are moved after the inserted characters
2623 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2624 });
2625}
2626
2627#[gpui::test]
2628async fn test_tab(cx: &mut gpui::TestAppContext) {
2629 init_test(cx, |settings| {
2630 settings.defaults.tab_size = NonZeroU32::new(3)
2631 });
2632
2633 let mut cx = EditorTestContext::new(cx).await;
2634 cx.set_state(indoc! {"
2635 ˇabˇc
2636 ˇ🏀ˇ🏀ˇefg
2637 dˇ
2638 "});
2639 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2640 cx.assert_editor_state(indoc! {"
2641 ˇab ˇc
2642 ˇ🏀 ˇ🏀 ˇefg
2643 d ˇ
2644 "});
2645
2646 cx.set_state(indoc! {"
2647 a
2648 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2649 "});
2650 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2651 cx.assert_editor_state(indoc! {"
2652 a
2653 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2654 "});
2655}
2656
2657#[gpui::test]
2658async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2659 init_test(cx, |_| {});
2660
2661 let mut cx = EditorTestContext::new(cx).await;
2662 let language = Arc::new(
2663 Language::new(
2664 LanguageConfig::default(),
2665 Some(tree_sitter_rust::LANGUAGE.into()),
2666 )
2667 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2668 .unwrap(),
2669 );
2670 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2671
2672 // cursors that are already at the suggested indent level insert
2673 // a soft tab. cursors that are to the left of the suggested indent
2674 // auto-indent their line.
2675 cx.set_state(indoc! {"
2676 ˇ
2677 const a: B = (
2678 c(
2679 d(
2680 ˇ
2681 )
2682 ˇ
2683 ˇ )
2684 );
2685 "});
2686 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2687 cx.assert_editor_state(indoc! {"
2688 ˇ
2689 const a: B = (
2690 c(
2691 d(
2692 ˇ
2693 )
2694 ˇ
2695 ˇ)
2696 );
2697 "});
2698
2699 // handle auto-indent when there are multiple cursors on the same line
2700 cx.set_state(indoc! {"
2701 const a: B = (
2702 c(
2703 ˇ ˇ
2704 ˇ )
2705 );
2706 "});
2707 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2708 cx.assert_editor_state(indoc! {"
2709 const a: B = (
2710 c(
2711 ˇ
2712 ˇ)
2713 );
2714 "});
2715}
2716
2717#[gpui::test]
2718async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2719 init_test(cx, |settings| {
2720 settings.defaults.tab_size = NonZeroU32::new(4)
2721 });
2722
2723 let language = Arc::new(
2724 Language::new(
2725 LanguageConfig::default(),
2726 Some(tree_sitter_rust::LANGUAGE.into()),
2727 )
2728 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2729 .unwrap(),
2730 );
2731
2732 let mut cx = EditorTestContext::new(cx).await;
2733 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2734 cx.set_state(indoc! {"
2735 fn a() {
2736 if b {
2737 \t ˇc
2738 }
2739 }
2740 "});
2741
2742 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2743 cx.assert_editor_state(indoc! {"
2744 fn a() {
2745 if b {
2746 ˇc
2747 }
2748 }
2749 "});
2750}
2751
2752#[gpui::test]
2753async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2754 init_test(cx, |settings| {
2755 settings.defaults.tab_size = NonZeroU32::new(4);
2756 });
2757
2758 let mut cx = EditorTestContext::new(cx).await;
2759
2760 cx.set_state(indoc! {"
2761 «oneˇ» «twoˇ»
2762 three
2763 four
2764 "});
2765 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2766 cx.assert_editor_state(indoc! {"
2767 «oneˇ» «twoˇ»
2768 three
2769 four
2770 "});
2771
2772 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2773 cx.assert_editor_state(indoc! {"
2774 «oneˇ» «twoˇ»
2775 three
2776 four
2777 "});
2778
2779 // select across line ending
2780 cx.set_state(indoc! {"
2781 one two
2782 t«hree
2783 ˇ» four
2784 "});
2785 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2786 cx.assert_editor_state(indoc! {"
2787 one two
2788 t«hree
2789 ˇ» four
2790 "});
2791
2792 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2793 cx.assert_editor_state(indoc! {"
2794 one two
2795 t«hree
2796 ˇ» four
2797 "});
2798
2799 // Ensure that indenting/outdenting works when the cursor is at column 0.
2800 cx.set_state(indoc! {"
2801 one two
2802 ˇthree
2803 four
2804 "});
2805 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2806 cx.assert_editor_state(indoc! {"
2807 one two
2808 ˇthree
2809 four
2810 "});
2811
2812 cx.set_state(indoc! {"
2813 one two
2814 ˇ three
2815 four
2816 "});
2817 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2818 cx.assert_editor_state(indoc! {"
2819 one two
2820 ˇthree
2821 four
2822 "});
2823}
2824
2825#[gpui::test]
2826async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2827 init_test(cx, |settings| {
2828 settings.defaults.hard_tabs = Some(true);
2829 });
2830
2831 let mut cx = EditorTestContext::new(cx).await;
2832
2833 // select two ranges on one line
2834 cx.set_state(indoc! {"
2835 «oneˇ» «twoˇ»
2836 three
2837 four
2838 "});
2839 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2840 cx.assert_editor_state(indoc! {"
2841 \t«oneˇ» «twoˇ»
2842 three
2843 four
2844 "});
2845 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2846 cx.assert_editor_state(indoc! {"
2847 \t\t«oneˇ» «twoˇ»
2848 three
2849 four
2850 "});
2851 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2852 cx.assert_editor_state(indoc! {"
2853 \t«oneˇ» «twoˇ»
2854 three
2855 four
2856 "});
2857 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2858 cx.assert_editor_state(indoc! {"
2859 «oneˇ» «twoˇ»
2860 three
2861 four
2862 "});
2863
2864 // select across a line ending
2865 cx.set_state(indoc! {"
2866 one two
2867 t«hree
2868 ˇ»four
2869 "});
2870 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2871 cx.assert_editor_state(indoc! {"
2872 one two
2873 \tt«hree
2874 ˇ»four
2875 "});
2876 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2877 cx.assert_editor_state(indoc! {"
2878 one two
2879 \t\tt«hree
2880 ˇ»four
2881 "});
2882 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2883 cx.assert_editor_state(indoc! {"
2884 one two
2885 \tt«hree
2886 ˇ»four
2887 "});
2888 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2889 cx.assert_editor_state(indoc! {"
2890 one two
2891 t«hree
2892 ˇ»four
2893 "});
2894
2895 // Ensure that indenting/outdenting works when the cursor is at column 0.
2896 cx.set_state(indoc! {"
2897 one two
2898 ˇthree
2899 four
2900 "});
2901 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2902 cx.assert_editor_state(indoc! {"
2903 one two
2904 ˇthree
2905 four
2906 "});
2907 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2908 cx.assert_editor_state(indoc! {"
2909 one two
2910 \tˇthree
2911 four
2912 "});
2913 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2914 cx.assert_editor_state(indoc! {"
2915 one two
2916 ˇthree
2917 four
2918 "});
2919}
2920
2921#[gpui::test]
2922fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2923 init_test(cx, |settings| {
2924 settings.languages.extend([
2925 (
2926 "TOML".into(),
2927 LanguageSettingsContent {
2928 tab_size: NonZeroU32::new(2),
2929 ..Default::default()
2930 },
2931 ),
2932 (
2933 "Rust".into(),
2934 LanguageSettingsContent {
2935 tab_size: NonZeroU32::new(4),
2936 ..Default::default()
2937 },
2938 ),
2939 ]);
2940 });
2941
2942 let toml_language = Arc::new(Language::new(
2943 LanguageConfig {
2944 name: "TOML".into(),
2945 ..Default::default()
2946 },
2947 None,
2948 ));
2949 let rust_language = Arc::new(Language::new(
2950 LanguageConfig {
2951 name: "Rust".into(),
2952 ..Default::default()
2953 },
2954 None,
2955 ));
2956
2957 let toml_buffer =
2958 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2959 let rust_buffer = cx.new_model(|cx| {
2960 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2961 });
2962 let multibuffer = cx.new_model(|cx| {
2963 let mut multibuffer = MultiBuffer::new(ReadWrite);
2964 multibuffer.push_excerpts(
2965 toml_buffer.clone(),
2966 [ExcerptRange {
2967 context: Point::new(0, 0)..Point::new(2, 0),
2968 primary: None,
2969 }],
2970 cx,
2971 );
2972 multibuffer.push_excerpts(
2973 rust_buffer.clone(),
2974 [ExcerptRange {
2975 context: Point::new(0, 0)..Point::new(1, 0),
2976 primary: None,
2977 }],
2978 cx,
2979 );
2980 multibuffer
2981 });
2982
2983 cx.add_window(|cx| {
2984 let mut editor = build_editor(multibuffer, cx);
2985
2986 assert_eq!(
2987 editor.text(cx),
2988 indoc! {"
2989 a = 1
2990 b = 2
2991
2992 const c: usize = 3;
2993 "}
2994 );
2995
2996 select_ranges(
2997 &mut editor,
2998 indoc! {"
2999 «aˇ» = 1
3000 b = 2
3001
3002 «const c:ˇ» usize = 3;
3003 "},
3004 cx,
3005 );
3006
3007 editor.tab(&Tab, cx);
3008 assert_text_with_selections(
3009 &mut editor,
3010 indoc! {"
3011 «aˇ» = 1
3012 b = 2
3013
3014 «const c:ˇ» usize = 3;
3015 "},
3016 cx,
3017 );
3018 editor.tab_prev(&TabPrev, cx);
3019 assert_text_with_selections(
3020 &mut editor,
3021 indoc! {"
3022 «aˇ» = 1
3023 b = 2
3024
3025 «const c:ˇ» usize = 3;
3026 "},
3027 cx,
3028 );
3029
3030 editor
3031 });
3032}
3033
3034#[gpui::test]
3035async fn test_backspace(cx: &mut gpui::TestAppContext) {
3036 init_test(cx, |_| {});
3037
3038 let mut cx = EditorTestContext::new(cx).await;
3039
3040 // Basic backspace
3041 cx.set_state(indoc! {"
3042 onˇe two three
3043 fou«rˇ» five six
3044 seven «ˇeight nine
3045 »ten
3046 "});
3047 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3048 cx.assert_editor_state(indoc! {"
3049 oˇe two three
3050 fouˇ five six
3051 seven ˇten
3052 "});
3053
3054 // Test backspace inside and around indents
3055 cx.set_state(indoc! {"
3056 zero
3057 ˇone
3058 ˇtwo
3059 ˇ ˇ ˇ three
3060 ˇ ˇ four
3061 "});
3062 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3063 cx.assert_editor_state(indoc! {"
3064 zero
3065 ˇone
3066 ˇtwo
3067 ˇ threeˇ four
3068 "});
3069
3070 // Test backspace with line_mode set to true
3071 cx.update_editor(|e, _| e.selections.line_mode = true);
3072 cx.set_state(indoc! {"
3073 The ˇquick ˇbrown
3074 fox jumps over
3075 the lazy dog
3076 ˇThe qu«ick bˇ»rown"});
3077 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3078 cx.assert_editor_state(indoc! {"
3079 ˇfox jumps over
3080 the lazy dogˇ"});
3081}
3082
3083#[gpui::test]
3084async fn test_delete(cx: &mut gpui::TestAppContext) {
3085 init_test(cx, |_| {});
3086
3087 let mut cx = EditorTestContext::new(cx).await;
3088 cx.set_state(indoc! {"
3089 onˇe two three
3090 fou«rˇ» five six
3091 seven «ˇeight nine
3092 »ten
3093 "});
3094 cx.update_editor(|e, cx| e.delete(&Delete, cx));
3095 cx.assert_editor_state(indoc! {"
3096 onˇ two three
3097 fouˇ five six
3098 seven ˇten
3099 "});
3100
3101 // Test backspace with line_mode set to true
3102 cx.update_editor(|e, _| e.selections.line_mode = true);
3103 cx.set_state(indoc! {"
3104 The ˇquick ˇbrown
3105 fox «ˇjum»ps over
3106 the lazy dog
3107 ˇThe qu«ick bˇ»rown"});
3108 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3109 cx.assert_editor_state("ˇthe lazy dogˇ");
3110}
3111
3112#[gpui::test]
3113fn test_delete_line(cx: &mut TestAppContext) {
3114 init_test(cx, |_| {});
3115
3116 let view = cx.add_window(|cx| {
3117 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3118 build_editor(buffer, cx)
3119 });
3120 _ = view.update(cx, |view, cx| {
3121 view.change_selections(None, cx, |s| {
3122 s.select_display_ranges([
3123 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3124 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3125 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3126 ])
3127 });
3128 view.delete_line(&DeleteLine, cx);
3129 assert_eq!(view.display_text(cx), "ghi");
3130 assert_eq!(
3131 view.selections.display_ranges(cx),
3132 vec![
3133 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3134 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3135 ]
3136 );
3137 });
3138
3139 let view = cx.add_window(|cx| {
3140 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3141 build_editor(buffer, cx)
3142 });
3143 _ = view.update(cx, |view, cx| {
3144 view.change_selections(None, cx, |s| {
3145 s.select_display_ranges([
3146 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3147 ])
3148 });
3149 view.delete_line(&DeleteLine, cx);
3150 assert_eq!(view.display_text(cx), "ghi\n");
3151 assert_eq!(
3152 view.selections.display_ranges(cx),
3153 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3154 );
3155 });
3156}
3157
3158#[gpui::test]
3159fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3160 init_test(cx, |_| {});
3161
3162 cx.add_window(|cx| {
3163 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3164 let mut editor = build_editor(buffer.clone(), cx);
3165 let buffer = buffer.read(cx).as_singleton().unwrap();
3166
3167 assert_eq!(
3168 editor.selections.ranges::<Point>(cx),
3169 &[Point::new(0, 0)..Point::new(0, 0)]
3170 );
3171
3172 // When on single line, replace newline at end by space
3173 editor.join_lines(&JoinLines, cx);
3174 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3175 assert_eq!(
3176 editor.selections.ranges::<Point>(cx),
3177 &[Point::new(0, 3)..Point::new(0, 3)]
3178 );
3179
3180 // When multiple lines are selected, remove newlines that are spanned by the selection
3181 editor.change_selections(None, cx, |s| {
3182 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3183 });
3184 editor.join_lines(&JoinLines, cx);
3185 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3186 assert_eq!(
3187 editor.selections.ranges::<Point>(cx),
3188 &[Point::new(0, 11)..Point::new(0, 11)]
3189 );
3190
3191 // Undo should be transactional
3192 editor.undo(&Undo, cx);
3193 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3194 assert_eq!(
3195 editor.selections.ranges::<Point>(cx),
3196 &[Point::new(0, 5)..Point::new(2, 2)]
3197 );
3198
3199 // When joining an empty line don't insert a space
3200 editor.change_selections(None, cx, |s| {
3201 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3202 });
3203 editor.join_lines(&JoinLines, cx);
3204 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3205 assert_eq!(
3206 editor.selections.ranges::<Point>(cx),
3207 [Point::new(2, 3)..Point::new(2, 3)]
3208 );
3209
3210 // We can remove trailing newlines
3211 editor.join_lines(&JoinLines, cx);
3212 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3213 assert_eq!(
3214 editor.selections.ranges::<Point>(cx),
3215 [Point::new(2, 3)..Point::new(2, 3)]
3216 );
3217
3218 // We don't blow up on the last line
3219 editor.join_lines(&JoinLines, cx);
3220 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3221 assert_eq!(
3222 editor.selections.ranges::<Point>(cx),
3223 [Point::new(2, 3)..Point::new(2, 3)]
3224 );
3225
3226 // reset to test indentation
3227 editor.buffer.update(cx, |buffer, cx| {
3228 buffer.edit(
3229 [
3230 (Point::new(1, 0)..Point::new(1, 2), " "),
3231 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3232 ],
3233 None,
3234 cx,
3235 )
3236 });
3237
3238 // We remove any leading spaces
3239 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3240 editor.change_selections(None, cx, |s| {
3241 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3242 });
3243 editor.join_lines(&JoinLines, cx);
3244 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3245
3246 // We don't insert a space for a line containing only spaces
3247 editor.join_lines(&JoinLines, cx);
3248 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3249
3250 // We ignore any leading tabs
3251 editor.join_lines(&JoinLines, cx);
3252 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3253
3254 editor
3255 });
3256}
3257
3258#[gpui::test]
3259fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3260 init_test(cx, |_| {});
3261
3262 cx.add_window(|cx| {
3263 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3264 let mut editor = build_editor(buffer.clone(), cx);
3265 let buffer = buffer.read(cx).as_singleton().unwrap();
3266
3267 editor.change_selections(None, cx, |s| {
3268 s.select_ranges([
3269 Point::new(0, 2)..Point::new(1, 1),
3270 Point::new(1, 2)..Point::new(1, 2),
3271 Point::new(3, 1)..Point::new(3, 2),
3272 ])
3273 });
3274
3275 editor.join_lines(&JoinLines, cx);
3276 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3277
3278 assert_eq!(
3279 editor.selections.ranges::<Point>(cx),
3280 [
3281 Point::new(0, 7)..Point::new(0, 7),
3282 Point::new(1, 3)..Point::new(1, 3)
3283 ]
3284 );
3285 editor
3286 });
3287}
3288
3289#[gpui::test]
3290async fn test_join_lines_with_git_diff_base(
3291 executor: BackgroundExecutor,
3292 cx: &mut gpui::TestAppContext,
3293) {
3294 init_test(cx, |_| {});
3295
3296 let mut cx = EditorTestContext::new(cx).await;
3297
3298 let diff_base = r#"
3299 Line 0
3300 Line 1
3301 Line 2
3302 Line 3
3303 "#
3304 .unindent();
3305
3306 cx.set_state(
3307 &r#"
3308 ˇLine 0
3309 Line 1
3310 Line 2
3311 Line 3
3312 "#
3313 .unindent(),
3314 );
3315
3316 cx.set_diff_base(&diff_base);
3317 executor.run_until_parked();
3318
3319 // Join lines
3320 cx.update_editor(|editor, cx| {
3321 editor.join_lines(&JoinLines, cx);
3322 });
3323 executor.run_until_parked();
3324
3325 cx.assert_editor_state(
3326 &r#"
3327 Line 0ˇ Line 1
3328 Line 2
3329 Line 3
3330 "#
3331 .unindent(),
3332 );
3333 // Join again
3334 cx.update_editor(|editor, cx| {
3335 editor.join_lines(&JoinLines, cx);
3336 });
3337 executor.run_until_parked();
3338
3339 cx.assert_editor_state(
3340 &r#"
3341 Line 0 Line 1ˇ Line 2
3342 Line 3
3343 "#
3344 .unindent(),
3345 );
3346}
3347
3348#[gpui::test]
3349async fn test_custom_newlines_cause_no_false_positive_diffs(
3350 executor: BackgroundExecutor,
3351 cx: &mut gpui::TestAppContext,
3352) {
3353 init_test(cx, |_| {});
3354 let mut cx = EditorTestContext::new(cx).await;
3355 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3356 cx.set_diff_base("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3357 executor.run_until_parked();
3358
3359 cx.update_editor(|editor, cx| {
3360 let snapshot = editor.snapshot(cx);
3361 assert_eq!(
3362 snapshot
3363 .diff_map
3364 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
3365 .collect::<Vec<_>>(),
3366 Vec::new(),
3367 "Should not have any diffs for files with custom newlines"
3368 );
3369 });
3370}
3371
3372#[gpui::test]
3373async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3374 init_test(cx, |_| {});
3375
3376 let mut cx = EditorTestContext::new(cx).await;
3377
3378 // Test sort_lines_case_insensitive()
3379 cx.set_state(indoc! {"
3380 «z
3381 y
3382 x
3383 Z
3384 Y
3385 Xˇ»
3386 "});
3387 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3388 cx.assert_editor_state(indoc! {"
3389 «x
3390 X
3391 y
3392 Y
3393 z
3394 Zˇ»
3395 "});
3396
3397 // Test reverse_lines()
3398 cx.set_state(indoc! {"
3399 «5
3400 4
3401 3
3402 2
3403 1ˇ»
3404 "});
3405 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3406 cx.assert_editor_state(indoc! {"
3407 «1
3408 2
3409 3
3410 4
3411 5ˇ»
3412 "});
3413
3414 // Skip testing shuffle_line()
3415
3416 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3417 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3418
3419 // Don't manipulate when cursor is on single line, but expand the selection
3420 cx.set_state(indoc! {"
3421 ddˇdd
3422 ccc
3423 bb
3424 a
3425 "});
3426 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3427 cx.assert_editor_state(indoc! {"
3428 «ddddˇ»
3429 ccc
3430 bb
3431 a
3432 "});
3433
3434 // Basic manipulate case
3435 // Start selection moves to column 0
3436 // End of selection shrinks to fit shorter line
3437 cx.set_state(indoc! {"
3438 dd«d
3439 ccc
3440 bb
3441 aaaaaˇ»
3442 "});
3443 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3444 cx.assert_editor_state(indoc! {"
3445 «aaaaa
3446 bb
3447 ccc
3448 dddˇ»
3449 "});
3450
3451 // Manipulate case with newlines
3452 cx.set_state(indoc! {"
3453 dd«d
3454 ccc
3455
3456 bb
3457 aaaaa
3458
3459 ˇ»
3460 "});
3461 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3462 cx.assert_editor_state(indoc! {"
3463 «
3464
3465 aaaaa
3466 bb
3467 ccc
3468 dddˇ»
3469
3470 "});
3471
3472 // Adding new line
3473 cx.set_state(indoc! {"
3474 aa«a
3475 bbˇ»b
3476 "});
3477 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3478 cx.assert_editor_state(indoc! {"
3479 «aaa
3480 bbb
3481 added_lineˇ»
3482 "});
3483
3484 // Removing line
3485 cx.set_state(indoc! {"
3486 aa«a
3487 bbbˇ»
3488 "});
3489 cx.update_editor(|e, cx| {
3490 e.manipulate_lines(cx, |lines| {
3491 lines.pop();
3492 })
3493 });
3494 cx.assert_editor_state(indoc! {"
3495 «aaaˇ»
3496 "});
3497
3498 // Removing all lines
3499 cx.set_state(indoc! {"
3500 aa«a
3501 bbbˇ»
3502 "});
3503 cx.update_editor(|e, cx| {
3504 e.manipulate_lines(cx, |lines| {
3505 lines.drain(..);
3506 })
3507 });
3508 cx.assert_editor_state(indoc! {"
3509 ˇ
3510 "});
3511}
3512
3513#[gpui::test]
3514async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3515 init_test(cx, |_| {});
3516
3517 let mut cx = EditorTestContext::new(cx).await;
3518
3519 // Consider continuous selection as single selection
3520 cx.set_state(indoc! {"
3521 Aaa«aa
3522 cˇ»c«c
3523 bb
3524 aaaˇ»aa
3525 "});
3526 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3527 cx.assert_editor_state(indoc! {"
3528 «Aaaaa
3529 ccc
3530 bb
3531 aaaaaˇ»
3532 "});
3533
3534 cx.set_state(indoc! {"
3535 Aaa«aa
3536 cˇ»c«c
3537 bb
3538 aaaˇ»aa
3539 "});
3540 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3541 cx.assert_editor_state(indoc! {"
3542 «Aaaaa
3543 ccc
3544 bbˇ»
3545 "});
3546
3547 // Consider non continuous selection as distinct dedup operations
3548 cx.set_state(indoc! {"
3549 «aaaaa
3550 bb
3551 aaaaa
3552 aaaaaˇ»
3553
3554 aaa«aaˇ»
3555 "});
3556 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3557 cx.assert_editor_state(indoc! {"
3558 «aaaaa
3559 bbˇ»
3560
3561 «aaaaaˇ»
3562 "});
3563}
3564
3565#[gpui::test]
3566async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3567 init_test(cx, |_| {});
3568
3569 let mut cx = EditorTestContext::new(cx).await;
3570
3571 cx.set_state(indoc! {"
3572 «Aaa
3573 aAa
3574 Aaaˇ»
3575 "});
3576 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3577 cx.assert_editor_state(indoc! {"
3578 «Aaa
3579 aAaˇ»
3580 "});
3581
3582 cx.set_state(indoc! {"
3583 «Aaa
3584 aAa
3585 aaAˇ»
3586 "});
3587 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3588 cx.assert_editor_state(indoc! {"
3589 «Aaaˇ»
3590 "});
3591}
3592
3593#[gpui::test]
3594async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3595 init_test(cx, |_| {});
3596
3597 let mut cx = EditorTestContext::new(cx).await;
3598
3599 // Manipulate with multiple selections on a single line
3600 cx.set_state(indoc! {"
3601 dd«dd
3602 cˇ»c«c
3603 bb
3604 aaaˇ»aa
3605 "});
3606 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3607 cx.assert_editor_state(indoc! {"
3608 «aaaaa
3609 bb
3610 ccc
3611 ddddˇ»
3612 "});
3613
3614 // Manipulate with multiple disjoin selections
3615 cx.set_state(indoc! {"
3616 5«
3617 4
3618 3
3619 2
3620 1ˇ»
3621
3622 dd«dd
3623 ccc
3624 bb
3625 aaaˇ»aa
3626 "});
3627 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3628 cx.assert_editor_state(indoc! {"
3629 «1
3630 2
3631 3
3632 4
3633 5ˇ»
3634
3635 «aaaaa
3636 bb
3637 ccc
3638 ddddˇ»
3639 "});
3640
3641 // Adding lines on each selection
3642 cx.set_state(indoc! {"
3643 2«
3644 1ˇ»
3645
3646 bb«bb
3647 aaaˇ»aa
3648 "});
3649 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3650 cx.assert_editor_state(indoc! {"
3651 «2
3652 1
3653 added lineˇ»
3654
3655 «bbbb
3656 aaaaa
3657 added lineˇ»
3658 "});
3659
3660 // Removing lines on each selection
3661 cx.set_state(indoc! {"
3662 2«
3663 1ˇ»
3664
3665 bb«bb
3666 aaaˇ»aa
3667 "});
3668 cx.update_editor(|e, cx| {
3669 e.manipulate_lines(cx, |lines| {
3670 lines.pop();
3671 })
3672 });
3673 cx.assert_editor_state(indoc! {"
3674 «2ˇ»
3675
3676 «bbbbˇ»
3677 "});
3678}
3679
3680#[gpui::test]
3681async fn test_manipulate_text(cx: &mut TestAppContext) {
3682 init_test(cx, |_| {});
3683
3684 let mut cx = EditorTestContext::new(cx).await;
3685
3686 // Test convert_to_upper_case()
3687 cx.set_state(indoc! {"
3688 «hello worldˇ»
3689 "});
3690 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3691 cx.assert_editor_state(indoc! {"
3692 «HELLO WORLDˇ»
3693 "});
3694
3695 // Test convert_to_lower_case()
3696 cx.set_state(indoc! {"
3697 «HELLO WORLDˇ»
3698 "});
3699 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3700 cx.assert_editor_state(indoc! {"
3701 «hello worldˇ»
3702 "});
3703
3704 // Test multiple line, single selection case
3705 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3706 cx.set_state(indoc! {"
3707 «The quick brown
3708 fox jumps over
3709 the lazy dogˇ»
3710 "});
3711 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3712 cx.assert_editor_state(indoc! {"
3713 «The Quick Brown
3714 Fox Jumps Over
3715 The Lazy Dogˇ»
3716 "});
3717
3718 // Test multiple line, single selection case
3719 // Test code hack that covers the fact that to_case crate doesn't support '\n' as a word boundary
3720 cx.set_state(indoc! {"
3721 «The quick brown
3722 fox jumps over
3723 the lazy dogˇ»
3724 "});
3725 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3726 cx.assert_editor_state(indoc! {"
3727 «TheQuickBrown
3728 FoxJumpsOver
3729 TheLazyDogˇ»
3730 "});
3731
3732 // From here on out, test more complex cases of manipulate_text()
3733
3734 // Test no selection case - should affect words cursors are in
3735 // Cursor at beginning, middle, and end of word
3736 cx.set_state(indoc! {"
3737 ˇhello big beauˇtiful worldˇ
3738 "});
3739 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3740 cx.assert_editor_state(indoc! {"
3741 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3742 "});
3743
3744 // Test multiple selections on a single line and across multiple lines
3745 cx.set_state(indoc! {"
3746 «Theˇ» quick «brown
3747 foxˇ» jumps «overˇ»
3748 the «lazyˇ» dog
3749 "});
3750 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3751 cx.assert_editor_state(indoc! {"
3752 «THEˇ» quick «BROWN
3753 FOXˇ» jumps «OVERˇ»
3754 the «LAZYˇ» dog
3755 "});
3756
3757 // Test case where text length grows
3758 cx.set_state(indoc! {"
3759 «tschüߡ»
3760 "});
3761 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3762 cx.assert_editor_state(indoc! {"
3763 «TSCHÜSSˇ»
3764 "});
3765
3766 // Test to make sure we don't crash when text shrinks
3767 cx.set_state(indoc! {"
3768 aaa_bbbˇ
3769 "});
3770 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3771 cx.assert_editor_state(indoc! {"
3772 «aaaBbbˇ»
3773 "});
3774
3775 // Test to make sure we all aware of the fact that each word can grow and shrink
3776 // Final selections should be aware of this fact
3777 cx.set_state(indoc! {"
3778 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3779 "});
3780 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3781 cx.assert_editor_state(indoc! {"
3782 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3783 "});
3784
3785 cx.set_state(indoc! {"
3786 «hElLo, WoRld!ˇ»
3787 "});
3788 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3789 cx.assert_editor_state(indoc! {"
3790 «HeLlO, wOrLD!ˇ»
3791 "});
3792}
3793
3794#[gpui::test]
3795fn test_duplicate_line(cx: &mut TestAppContext) {
3796 init_test(cx, |_| {});
3797
3798 let view = cx.add_window(|cx| {
3799 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3800 build_editor(buffer, cx)
3801 });
3802 _ = view.update(cx, |view, cx| {
3803 view.change_selections(None, cx, |s| {
3804 s.select_display_ranges([
3805 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3806 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3807 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3808 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3809 ])
3810 });
3811 view.duplicate_line_down(&DuplicateLineDown, cx);
3812 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3813 assert_eq!(
3814 view.selections.display_ranges(cx),
3815 vec![
3816 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3817 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3818 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3819 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3820 ]
3821 );
3822 });
3823
3824 let view = cx.add_window(|cx| {
3825 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3826 build_editor(buffer, cx)
3827 });
3828 _ = view.update(cx, |view, cx| {
3829 view.change_selections(None, cx, |s| {
3830 s.select_display_ranges([
3831 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3832 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3833 ])
3834 });
3835 view.duplicate_line_down(&DuplicateLineDown, cx);
3836 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3837 assert_eq!(
3838 view.selections.display_ranges(cx),
3839 vec![
3840 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3841 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3842 ]
3843 );
3844 });
3845
3846 // With `move_upwards` the selections stay in place, except for
3847 // the lines inserted above them
3848 let view = cx.add_window(|cx| {
3849 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3850 build_editor(buffer, cx)
3851 });
3852 _ = view.update(cx, |view, cx| {
3853 view.change_selections(None, cx, |s| {
3854 s.select_display_ranges([
3855 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3856 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3857 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3858 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3859 ])
3860 });
3861 view.duplicate_line_up(&DuplicateLineUp, cx);
3862 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3863 assert_eq!(
3864 view.selections.display_ranges(cx),
3865 vec![
3866 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3867 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3868 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3869 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3870 ]
3871 );
3872 });
3873
3874 let view = cx.add_window(|cx| {
3875 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3876 build_editor(buffer, cx)
3877 });
3878 _ = view.update(cx, |view, cx| {
3879 view.change_selections(None, cx, |s| {
3880 s.select_display_ranges([
3881 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3882 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3883 ])
3884 });
3885 view.duplicate_line_up(&DuplicateLineUp, cx);
3886 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3887 assert_eq!(
3888 view.selections.display_ranges(cx),
3889 vec![
3890 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3891 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3892 ]
3893 );
3894 });
3895}
3896
3897#[gpui::test]
3898fn test_move_line_up_down(cx: &mut TestAppContext) {
3899 init_test(cx, |_| {});
3900
3901 let view = cx.add_window(|cx| {
3902 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3903 build_editor(buffer, cx)
3904 });
3905 _ = view.update(cx, |view, cx| {
3906 view.fold_creases(
3907 vec![
3908 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3909 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3910 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3911 ],
3912 true,
3913 cx,
3914 );
3915 view.change_selections(None, cx, |s| {
3916 s.select_display_ranges([
3917 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3918 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3919 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3920 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3921 ])
3922 });
3923 assert_eq!(
3924 view.display_text(cx),
3925 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3926 );
3927
3928 view.move_line_up(&MoveLineUp, cx);
3929 assert_eq!(
3930 view.display_text(cx),
3931 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3932 );
3933 assert_eq!(
3934 view.selections.display_ranges(cx),
3935 vec![
3936 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3937 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3938 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3939 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3940 ]
3941 );
3942 });
3943
3944 _ = view.update(cx, |view, cx| {
3945 view.move_line_down(&MoveLineDown, cx);
3946 assert_eq!(
3947 view.display_text(cx),
3948 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3949 );
3950 assert_eq!(
3951 view.selections.display_ranges(cx),
3952 vec![
3953 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3954 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3955 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3956 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3957 ]
3958 );
3959 });
3960
3961 _ = view.update(cx, |view, cx| {
3962 view.move_line_down(&MoveLineDown, cx);
3963 assert_eq!(
3964 view.display_text(cx),
3965 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3966 );
3967 assert_eq!(
3968 view.selections.display_ranges(cx),
3969 vec![
3970 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3971 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3972 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3973 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3974 ]
3975 );
3976 });
3977
3978 _ = view.update(cx, |view, cx| {
3979 view.move_line_up(&MoveLineUp, cx);
3980 assert_eq!(
3981 view.display_text(cx),
3982 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
3983 );
3984 assert_eq!(
3985 view.selections.display_ranges(cx),
3986 vec![
3987 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3988 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3989 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3990 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3991 ]
3992 );
3993 });
3994}
3995
3996#[gpui::test]
3997fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
3998 init_test(cx, |_| {});
3999
4000 let editor = cx.add_window(|cx| {
4001 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4002 build_editor(buffer, cx)
4003 });
4004 _ = editor.update(cx, |editor, cx| {
4005 let snapshot = editor.buffer.read(cx).snapshot(cx);
4006 editor.insert_blocks(
4007 [BlockProperties {
4008 style: BlockStyle::Fixed,
4009 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4010 height: 1,
4011 render: Arc::new(|_| div().into_any()),
4012 priority: 0,
4013 }],
4014 Some(Autoscroll::fit()),
4015 cx,
4016 );
4017 editor.change_selections(None, cx, |s| {
4018 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4019 });
4020 editor.move_line_down(&MoveLineDown, cx);
4021 });
4022}
4023
4024#[gpui::test]
4025async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4026 init_test(cx, |_| {});
4027
4028 let mut cx = EditorTestContext::new(cx).await;
4029 cx.set_state(
4030 &"
4031 ˇzero
4032 one
4033 two
4034 three
4035 four
4036 five
4037 "
4038 .unindent(),
4039 );
4040
4041 // Create a four-line block that replaces three lines of text.
4042 cx.update_editor(|editor, cx| {
4043 let snapshot = editor.snapshot(cx);
4044 let snapshot = &snapshot.buffer_snapshot;
4045 let placement = BlockPlacement::Replace(
4046 snapshot.anchor_after(Point::new(1, 0))..snapshot.anchor_after(Point::new(3, 0)),
4047 );
4048 editor.insert_blocks(
4049 [BlockProperties {
4050 placement,
4051 height: 4,
4052 style: BlockStyle::Sticky,
4053 render: Arc::new(|_| gpui::div().into_any_element()),
4054 priority: 0,
4055 }],
4056 None,
4057 cx,
4058 );
4059 });
4060
4061 // Move down so that the cursor touches the block.
4062 cx.update_editor(|editor, cx| {
4063 editor.move_down(&Default::default(), cx);
4064 });
4065 cx.assert_editor_state(
4066 &"
4067 zero
4068 «one
4069 two
4070 threeˇ»
4071 four
4072 five
4073 "
4074 .unindent(),
4075 );
4076
4077 // Move down past the block.
4078 cx.update_editor(|editor, cx| {
4079 editor.move_down(&Default::default(), cx);
4080 });
4081 cx.assert_editor_state(
4082 &"
4083 zero
4084 one
4085 two
4086 three
4087 ˇfour
4088 five
4089 "
4090 .unindent(),
4091 );
4092}
4093
4094#[gpui::test]
4095fn test_transpose(cx: &mut TestAppContext) {
4096 init_test(cx, |_| {});
4097
4098 _ = cx.add_window(|cx| {
4099 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
4100 editor.set_style(EditorStyle::default(), cx);
4101 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
4102 editor.transpose(&Default::default(), cx);
4103 assert_eq!(editor.text(cx), "bac");
4104 assert_eq!(editor.selections.ranges(cx), [2..2]);
4105
4106 editor.transpose(&Default::default(), cx);
4107 assert_eq!(editor.text(cx), "bca");
4108 assert_eq!(editor.selections.ranges(cx), [3..3]);
4109
4110 editor.transpose(&Default::default(), cx);
4111 assert_eq!(editor.text(cx), "bac");
4112 assert_eq!(editor.selections.ranges(cx), [3..3]);
4113
4114 editor
4115 });
4116
4117 _ = cx.add_window(|cx| {
4118 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4119 editor.set_style(EditorStyle::default(), cx);
4120 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
4121 editor.transpose(&Default::default(), cx);
4122 assert_eq!(editor.text(cx), "acb\nde");
4123 assert_eq!(editor.selections.ranges(cx), [3..3]);
4124
4125 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4126 editor.transpose(&Default::default(), cx);
4127 assert_eq!(editor.text(cx), "acbd\ne");
4128 assert_eq!(editor.selections.ranges(cx), [5..5]);
4129
4130 editor.transpose(&Default::default(), cx);
4131 assert_eq!(editor.text(cx), "acbde\n");
4132 assert_eq!(editor.selections.ranges(cx), [6..6]);
4133
4134 editor.transpose(&Default::default(), cx);
4135 assert_eq!(editor.text(cx), "acbd\ne");
4136 assert_eq!(editor.selections.ranges(cx), [6..6]);
4137
4138 editor
4139 });
4140
4141 _ = cx.add_window(|cx| {
4142 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4143 editor.set_style(EditorStyle::default(), cx);
4144 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4145 editor.transpose(&Default::default(), cx);
4146 assert_eq!(editor.text(cx), "bacd\ne");
4147 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4148
4149 editor.transpose(&Default::default(), cx);
4150 assert_eq!(editor.text(cx), "bcade\n");
4151 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4152
4153 editor.transpose(&Default::default(), cx);
4154 assert_eq!(editor.text(cx), "bcda\ne");
4155 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4156
4157 editor.transpose(&Default::default(), cx);
4158 assert_eq!(editor.text(cx), "bcade\n");
4159 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4160
4161 editor.transpose(&Default::default(), cx);
4162 assert_eq!(editor.text(cx), "bcaed\n");
4163 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4164
4165 editor
4166 });
4167
4168 _ = cx.add_window(|cx| {
4169 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
4170 editor.set_style(EditorStyle::default(), cx);
4171 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4172 editor.transpose(&Default::default(), cx);
4173 assert_eq!(editor.text(cx), "🏀🍐✋");
4174 assert_eq!(editor.selections.ranges(cx), [8..8]);
4175
4176 editor.transpose(&Default::default(), cx);
4177 assert_eq!(editor.text(cx), "🏀✋🍐");
4178 assert_eq!(editor.selections.ranges(cx), [11..11]);
4179
4180 editor.transpose(&Default::default(), cx);
4181 assert_eq!(editor.text(cx), "🏀🍐✋");
4182 assert_eq!(editor.selections.ranges(cx), [11..11]);
4183
4184 editor
4185 });
4186}
4187
4188#[gpui::test]
4189async fn test_rewrap(cx: &mut TestAppContext) {
4190 init_test(cx, |_| {});
4191
4192 let mut cx = EditorTestContext::new(cx).await;
4193
4194 let language_with_c_comments = Arc::new(Language::new(
4195 LanguageConfig {
4196 line_comments: vec!["// ".into()],
4197 ..LanguageConfig::default()
4198 },
4199 None,
4200 ));
4201 let language_with_pound_comments = Arc::new(Language::new(
4202 LanguageConfig {
4203 line_comments: vec!["# ".into()],
4204 ..LanguageConfig::default()
4205 },
4206 None,
4207 ));
4208 let markdown_language = Arc::new(Language::new(
4209 LanguageConfig {
4210 name: "Markdown".into(),
4211 ..LanguageConfig::default()
4212 },
4213 None,
4214 ));
4215 let language_with_doc_comments = Arc::new(Language::new(
4216 LanguageConfig {
4217 line_comments: vec!["// ".into(), "/// ".into()],
4218 ..LanguageConfig::default()
4219 },
4220 Some(tree_sitter_rust::LANGUAGE.into()),
4221 ));
4222
4223 let plaintext_language = Arc::new(Language::new(
4224 LanguageConfig {
4225 name: "Plain Text".into(),
4226 ..LanguageConfig::default()
4227 },
4228 None,
4229 ));
4230
4231 assert_rewrap(
4232 indoc! {"
4233 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4234 "},
4235 indoc! {"
4236 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4237 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4238 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4239 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4240 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4241 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4242 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4243 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4244 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4245 // porttitor id. Aliquam id accumsan eros.
4246 "},
4247 language_with_c_comments.clone(),
4248 &mut cx,
4249 );
4250
4251 // Test that rewrapping works inside of a selection
4252 assert_rewrap(
4253 indoc! {"
4254 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ»
4255 "},
4256 indoc! {"
4257 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4258 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4259 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4260 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4261 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4262 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4263 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4264 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4265 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4266 // porttitor id. Aliquam id accumsan eros.ˇ»
4267 "},
4268 language_with_c_comments.clone(),
4269 &mut cx,
4270 );
4271
4272 // Test that cursors that expand to the same region are collapsed.
4273 assert_rewrap(
4274 indoc! {"
4275 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4276 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4277 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4278 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4279 "},
4280 indoc! {"
4281 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4282 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4283 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4284 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4285 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4286 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4287 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4288 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4289 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4290 // porttitor id. Aliquam id accumsan eros.
4291 "},
4292 language_with_c_comments.clone(),
4293 &mut cx,
4294 );
4295
4296 // Test that non-contiguous selections are treated separately.
4297 assert_rewrap(
4298 indoc! {"
4299 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4300 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4301 //
4302 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4303 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4304 "},
4305 indoc! {"
4306 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4307 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4308 // auctor, eu lacinia sapien scelerisque.
4309 //
4310 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4311 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4312 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4313 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4314 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4315 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4316 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4317 "},
4318 language_with_c_comments.clone(),
4319 &mut cx,
4320 );
4321
4322 // Test that different comment prefixes are supported.
4323 assert_rewrap(
4324 indoc! {"
4325 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4326 "},
4327 indoc! {"
4328 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4329 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4330 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4331 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4332 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4333 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4334 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4335 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4336 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4337 # accumsan eros.
4338 "},
4339 language_with_pound_comments.clone(),
4340 &mut cx,
4341 );
4342
4343 // Test that rewrapping is ignored outside of comments in most languages.
4344 assert_rewrap(
4345 indoc! {"
4346 /// Adds two numbers.
4347 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4348 fn add(a: u32, b: u32) -> u32 {
4349 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4350 }
4351 "},
4352 indoc! {"
4353 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4354 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4355 fn add(a: u32, b: u32) -> u32 {
4356 a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
4357 }
4358 "},
4359 language_with_doc_comments.clone(),
4360 &mut cx,
4361 );
4362
4363 // Test that rewrapping works in Markdown and Plain Text languages.
4364 assert_rewrap(
4365 indoc! {"
4366 # Hello
4367
4368 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4369 "},
4370 indoc! {"
4371 # Hello
4372
4373 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4374 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4375 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4376 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4377 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4378 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4379 Integer sit amet scelerisque nisi.
4380 "},
4381 markdown_language,
4382 &mut cx,
4383 );
4384
4385 assert_rewrap(
4386 indoc! {"
4387 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
4388 "},
4389 indoc! {"
4390 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4391 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4392 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4393 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4394 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4395 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4396 Integer sit amet scelerisque nisi.
4397 "},
4398 plaintext_language,
4399 &mut cx,
4400 );
4401
4402 // Test rewrapping unaligned comments in a selection.
4403 assert_rewrap(
4404 indoc! {"
4405 fn foo() {
4406 if true {
4407 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4408 // Praesent semper egestas tellus id dignissim.ˇ»
4409 do_something();
4410 } else {
4411 //
4412 }
4413 }
4414 "},
4415 indoc! {"
4416 fn foo() {
4417 if true {
4418 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4419 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4420 // egestas tellus id dignissim.ˇ»
4421 do_something();
4422 } else {
4423 //
4424 }
4425 }
4426 "},
4427 language_with_doc_comments.clone(),
4428 &mut cx,
4429 );
4430
4431 assert_rewrap(
4432 indoc! {"
4433 fn foo() {
4434 if true {
4435 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4436 // Praesent semper egestas tellus id dignissim.»
4437 do_something();
4438 } else {
4439 //
4440 }
4441
4442 }
4443 "},
4444 indoc! {"
4445 fn foo() {
4446 if true {
4447 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4448 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4449 // egestas tellus id dignissim.»
4450 do_something();
4451 } else {
4452 //
4453 }
4454
4455 }
4456 "},
4457 language_with_doc_comments.clone(),
4458 &mut cx,
4459 );
4460
4461 #[track_caller]
4462 fn assert_rewrap(
4463 unwrapped_text: &str,
4464 wrapped_text: &str,
4465 language: Arc<Language>,
4466 cx: &mut EditorTestContext,
4467 ) {
4468 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4469 cx.set_state(unwrapped_text);
4470 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4471 cx.assert_editor_state(wrapped_text);
4472 }
4473}
4474
4475#[gpui::test]
4476async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4477 init_test(cx, |_| {});
4478
4479 let mut cx = EditorTestContext::new(cx).await;
4480
4481 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4482 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4483 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4484
4485 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4486 cx.set_state("two ˇfour ˇsix ˇ");
4487 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4488 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4489
4490 // Paste again but with only two cursors. Since the number of cursors doesn't
4491 // match the number of slices in the clipboard, the entire clipboard text
4492 // is pasted at each cursor.
4493 cx.set_state("ˇtwo one✅ four three six five ˇ");
4494 cx.update_editor(|e, cx| {
4495 e.handle_input("( ", cx);
4496 e.paste(&Paste, cx);
4497 e.handle_input(") ", cx);
4498 });
4499 cx.assert_editor_state(
4500 &([
4501 "( one✅ ",
4502 "three ",
4503 "five ) ˇtwo one✅ four three six five ( one✅ ",
4504 "three ",
4505 "five ) ˇ",
4506 ]
4507 .join("\n")),
4508 );
4509
4510 // Cut with three selections, one of which is full-line.
4511 cx.set_state(indoc! {"
4512 1«2ˇ»3
4513 4ˇ567
4514 «8ˇ»9"});
4515 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4516 cx.assert_editor_state(indoc! {"
4517 1ˇ3
4518 ˇ9"});
4519
4520 // Paste with three selections, noticing how the copied selection that was full-line
4521 // gets inserted before the second cursor.
4522 cx.set_state(indoc! {"
4523 1ˇ3
4524 9ˇ
4525 «oˇ»ne"});
4526 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4527 cx.assert_editor_state(indoc! {"
4528 12ˇ3
4529 4567
4530 9ˇ
4531 8ˇne"});
4532
4533 // Copy with a single cursor only, which writes the whole line into the clipboard.
4534 cx.set_state(indoc! {"
4535 The quick brown
4536 fox juˇmps over
4537 the lazy dog"});
4538 cx.update_editor(|e, cx| e.copy(&Copy, cx));
4539 assert_eq!(
4540 cx.read_from_clipboard()
4541 .and_then(|item| item.text().as_deref().map(str::to_string)),
4542 Some("fox jumps over\n".to_string())
4543 );
4544
4545 // Paste with three selections, noticing how the copied full-line selection is inserted
4546 // before the empty selections but replaces the selection that is non-empty.
4547 cx.set_state(indoc! {"
4548 Tˇhe quick brown
4549 «foˇ»x jumps over
4550 tˇhe lazy dog"});
4551 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4552 cx.assert_editor_state(indoc! {"
4553 fox jumps over
4554 Tˇhe quick brown
4555 fox jumps over
4556 ˇx jumps over
4557 fox jumps over
4558 tˇhe lazy dog"});
4559}
4560
4561#[gpui::test]
4562async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4563 init_test(cx, |_| {});
4564
4565 let mut cx = EditorTestContext::new(cx).await;
4566 let language = Arc::new(Language::new(
4567 LanguageConfig::default(),
4568 Some(tree_sitter_rust::LANGUAGE.into()),
4569 ));
4570 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4571
4572 // Cut an indented block, without the leading whitespace.
4573 cx.set_state(indoc! {"
4574 const a: B = (
4575 c(),
4576 «d(
4577 e,
4578 f
4579 )ˇ»
4580 );
4581 "});
4582 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4583 cx.assert_editor_state(indoc! {"
4584 const a: B = (
4585 c(),
4586 ˇ
4587 );
4588 "});
4589
4590 // Paste it at the same position.
4591 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4592 cx.assert_editor_state(indoc! {"
4593 const a: B = (
4594 c(),
4595 d(
4596 e,
4597 f
4598 )ˇ
4599 );
4600 "});
4601
4602 // Paste it at a line with a lower indent level.
4603 cx.set_state(indoc! {"
4604 ˇ
4605 const a: B = (
4606 c(),
4607 );
4608 "});
4609 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4610 cx.assert_editor_state(indoc! {"
4611 d(
4612 e,
4613 f
4614 )ˇ
4615 const a: B = (
4616 c(),
4617 );
4618 "});
4619
4620 // Cut an indented block, with the leading whitespace.
4621 cx.set_state(indoc! {"
4622 const a: B = (
4623 c(),
4624 « d(
4625 e,
4626 f
4627 )
4628 ˇ»);
4629 "});
4630 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4631 cx.assert_editor_state(indoc! {"
4632 const a: B = (
4633 c(),
4634 ˇ);
4635 "});
4636
4637 // Paste it at the same position.
4638 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4639 cx.assert_editor_state(indoc! {"
4640 const a: B = (
4641 c(),
4642 d(
4643 e,
4644 f
4645 )
4646 ˇ);
4647 "});
4648
4649 // Paste it at a line with a higher indent level.
4650 cx.set_state(indoc! {"
4651 const a: B = (
4652 c(),
4653 d(
4654 e,
4655 fˇ
4656 )
4657 );
4658 "});
4659 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4660 cx.assert_editor_state(indoc! {"
4661 const a: B = (
4662 c(),
4663 d(
4664 e,
4665 f d(
4666 e,
4667 f
4668 )
4669 ˇ
4670 )
4671 );
4672 "});
4673}
4674
4675#[gpui::test]
4676fn test_select_all(cx: &mut TestAppContext) {
4677 init_test(cx, |_| {});
4678
4679 let view = cx.add_window(|cx| {
4680 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4681 build_editor(buffer, cx)
4682 });
4683 _ = view.update(cx, |view, cx| {
4684 view.select_all(&SelectAll, cx);
4685 assert_eq!(
4686 view.selections.display_ranges(cx),
4687 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4688 );
4689 });
4690}
4691
4692#[gpui::test]
4693fn test_select_line(cx: &mut TestAppContext) {
4694 init_test(cx, |_| {});
4695
4696 let view = cx.add_window(|cx| {
4697 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4698 build_editor(buffer, cx)
4699 });
4700 _ = view.update(cx, |view, cx| {
4701 view.change_selections(None, cx, |s| {
4702 s.select_display_ranges([
4703 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4704 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4705 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4706 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4707 ])
4708 });
4709 view.select_line(&SelectLine, cx);
4710 assert_eq!(
4711 view.selections.display_ranges(cx),
4712 vec![
4713 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4714 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4715 ]
4716 );
4717 });
4718
4719 _ = view.update(cx, |view, cx| {
4720 view.select_line(&SelectLine, cx);
4721 assert_eq!(
4722 view.selections.display_ranges(cx),
4723 vec![
4724 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4725 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4726 ]
4727 );
4728 });
4729
4730 _ = view.update(cx, |view, cx| {
4731 view.select_line(&SelectLine, cx);
4732 assert_eq!(
4733 view.selections.display_ranges(cx),
4734 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4735 );
4736 });
4737}
4738
4739#[gpui::test]
4740fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4741 init_test(cx, |_| {});
4742
4743 let view = cx.add_window(|cx| {
4744 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4745 build_editor(buffer, cx)
4746 });
4747 _ = view.update(cx, |view, cx| {
4748 view.fold_creases(
4749 vec![
4750 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4751 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4752 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4753 ],
4754 true,
4755 cx,
4756 );
4757 view.change_selections(None, cx, |s| {
4758 s.select_display_ranges([
4759 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4760 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4761 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4762 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4763 ])
4764 });
4765 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4766 });
4767
4768 _ = view.update(cx, |view, cx| {
4769 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4770 assert_eq!(
4771 view.display_text(cx),
4772 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4773 );
4774 assert_eq!(
4775 view.selections.display_ranges(cx),
4776 [
4777 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4778 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4779 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4780 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4781 ]
4782 );
4783 });
4784
4785 _ = view.update(cx, |view, cx| {
4786 view.change_selections(None, cx, |s| {
4787 s.select_display_ranges([
4788 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4789 ])
4790 });
4791 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4792 assert_eq!(
4793 view.display_text(cx),
4794 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4795 );
4796 assert_eq!(
4797 view.selections.display_ranges(cx),
4798 [
4799 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4800 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4801 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4802 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4803 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4804 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4805 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4806 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4807 ]
4808 );
4809 });
4810}
4811
4812#[gpui::test]
4813async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4814 init_test(cx, |_| {});
4815
4816 let mut cx = EditorTestContext::new(cx).await;
4817
4818 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4819 cx.set_state(indoc!(
4820 r#"abc
4821 defˇghi
4822
4823 jk
4824 nlmo
4825 "#
4826 ));
4827
4828 cx.update_editor(|editor, cx| {
4829 editor.add_selection_above(&Default::default(), cx);
4830 });
4831
4832 cx.assert_editor_state(indoc!(
4833 r#"abcˇ
4834 defˇghi
4835
4836 jk
4837 nlmo
4838 "#
4839 ));
4840
4841 cx.update_editor(|editor, cx| {
4842 editor.add_selection_above(&Default::default(), cx);
4843 });
4844
4845 cx.assert_editor_state(indoc!(
4846 r#"abcˇ
4847 defˇghi
4848
4849 jk
4850 nlmo
4851 "#
4852 ));
4853
4854 cx.update_editor(|view, cx| {
4855 view.add_selection_below(&Default::default(), cx);
4856 });
4857
4858 cx.assert_editor_state(indoc!(
4859 r#"abc
4860 defˇghi
4861
4862 jk
4863 nlmo
4864 "#
4865 ));
4866
4867 cx.update_editor(|view, cx| {
4868 view.undo_selection(&Default::default(), cx);
4869 });
4870
4871 cx.assert_editor_state(indoc!(
4872 r#"abcˇ
4873 defˇghi
4874
4875 jk
4876 nlmo
4877 "#
4878 ));
4879
4880 cx.update_editor(|view, cx| {
4881 view.redo_selection(&Default::default(), cx);
4882 });
4883
4884 cx.assert_editor_state(indoc!(
4885 r#"abc
4886 defˇghi
4887
4888 jk
4889 nlmo
4890 "#
4891 ));
4892
4893 cx.update_editor(|view, cx| {
4894 view.add_selection_below(&Default::default(), cx);
4895 });
4896
4897 cx.assert_editor_state(indoc!(
4898 r#"abc
4899 defˇghi
4900
4901 jk
4902 nlmˇo
4903 "#
4904 ));
4905
4906 cx.update_editor(|view, cx| {
4907 view.add_selection_below(&Default::default(), cx);
4908 });
4909
4910 cx.assert_editor_state(indoc!(
4911 r#"abc
4912 defˇghi
4913
4914 jk
4915 nlmˇo
4916 "#
4917 ));
4918
4919 // change selections
4920 cx.set_state(indoc!(
4921 r#"abc
4922 def«ˇg»hi
4923
4924 jk
4925 nlmo
4926 "#
4927 ));
4928
4929 cx.update_editor(|view, cx| {
4930 view.add_selection_below(&Default::default(), cx);
4931 });
4932
4933 cx.assert_editor_state(indoc!(
4934 r#"abc
4935 def«ˇg»hi
4936
4937 jk
4938 nlm«ˇo»
4939 "#
4940 ));
4941
4942 cx.update_editor(|view, cx| {
4943 view.add_selection_below(&Default::default(), cx);
4944 });
4945
4946 cx.assert_editor_state(indoc!(
4947 r#"abc
4948 def«ˇg»hi
4949
4950 jk
4951 nlm«ˇo»
4952 "#
4953 ));
4954
4955 cx.update_editor(|view, cx| {
4956 view.add_selection_above(&Default::default(), cx);
4957 });
4958
4959 cx.assert_editor_state(indoc!(
4960 r#"abc
4961 def«ˇg»hi
4962
4963 jk
4964 nlmo
4965 "#
4966 ));
4967
4968 cx.update_editor(|view, cx| {
4969 view.add_selection_above(&Default::default(), cx);
4970 });
4971
4972 cx.assert_editor_state(indoc!(
4973 r#"abc
4974 def«ˇg»hi
4975
4976 jk
4977 nlmo
4978 "#
4979 ));
4980
4981 // Change selections again
4982 cx.set_state(indoc!(
4983 r#"a«bc
4984 defgˇ»hi
4985
4986 jk
4987 nlmo
4988 "#
4989 ));
4990
4991 cx.update_editor(|view, cx| {
4992 view.add_selection_below(&Default::default(), cx);
4993 });
4994
4995 cx.assert_editor_state(indoc!(
4996 r#"a«bcˇ»
4997 d«efgˇ»hi
4998
4999 j«kˇ»
5000 nlmo
5001 "#
5002 ));
5003
5004 cx.update_editor(|view, cx| {
5005 view.add_selection_below(&Default::default(), cx);
5006 });
5007 cx.assert_editor_state(indoc!(
5008 r#"a«bcˇ»
5009 d«efgˇ»hi
5010
5011 j«kˇ»
5012 n«lmoˇ»
5013 "#
5014 ));
5015 cx.update_editor(|view, cx| {
5016 view.add_selection_above(&Default::default(), cx);
5017 });
5018
5019 cx.assert_editor_state(indoc!(
5020 r#"a«bcˇ»
5021 d«efgˇ»hi
5022
5023 j«kˇ»
5024 nlmo
5025 "#
5026 ));
5027
5028 // Change selections again
5029 cx.set_state(indoc!(
5030 r#"abc
5031 d«ˇefghi
5032
5033 jk
5034 nlm»o
5035 "#
5036 ));
5037
5038 cx.update_editor(|view, cx| {
5039 view.add_selection_above(&Default::default(), cx);
5040 });
5041
5042 cx.assert_editor_state(indoc!(
5043 r#"a«ˇbc»
5044 d«ˇef»ghi
5045
5046 j«ˇk»
5047 n«ˇlm»o
5048 "#
5049 ));
5050
5051 cx.update_editor(|view, cx| {
5052 view.add_selection_below(&Default::default(), cx);
5053 });
5054
5055 cx.assert_editor_state(indoc!(
5056 r#"abc
5057 d«ˇef»ghi
5058
5059 j«ˇk»
5060 n«ˇlm»o
5061 "#
5062 ));
5063}
5064
5065#[gpui::test]
5066async fn test_select_next(cx: &mut gpui::TestAppContext) {
5067 init_test(cx, |_| {});
5068
5069 let mut cx = EditorTestContext::new(cx).await;
5070 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5071
5072 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5073 .unwrap();
5074 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5075
5076 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5077 .unwrap();
5078 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5079
5080 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5081 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5082
5083 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5084 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5085
5086 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5087 .unwrap();
5088 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5089
5090 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5091 .unwrap();
5092 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5093}
5094
5095#[gpui::test]
5096async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
5097 init_test(cx, |_| {});
5098
5099 let mut cx = EditorTestContext::new(cx).await;
5100 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5101
5102 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
5103 .unwrap();
5104 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5105}
5106
5107#[gpui::test]
5108async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5109 init_test(cx, |_| {});
5110
5111 let mut cx = EditorTestContext::new(cx).await;
5112 cx.set_state(
5113 r#"let foo = 2;
5114lˇet foo = 2;
5115let fooˇ = 2;
5116let foo = 2;
5117let foo = ˇ2;"#,
5118 );
5119
5120 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5121 .unwrap();
5122 cx.assert_editor_state(
5123 r#"let foo = 2;
5124«letˇ» foo = 2;
5125let «fooˇ» = 2;
5126let foo = 2;
5127let foo = «2ˇ»;"#,
5128 );
5129
5130 // noop for multiple selections with different contents
5131 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5132 .unwrap();
5133 cx.assert_editor_state(
5134 r#"let foo = 2;
5135«letˇ» foo = 2;
5136let «fooˇ» = 2;
5137let foo = 2;
5138let foo = «2ˇ»;"#,
5139 );
5140}
5141
5142#[gpui::test]
5143async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
5144 init_test(cx, |_| {});
5145
5146 let mut cx =
5147 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5148
5149 cx.assert_editor_state(indoc! {"
5150 ˇbbb
5151 ccc
5152
5153 bbb
5154 ccc
5155 "});
5156 cx.dispatch_action(SelectPrevious::default());
5157 cx.assert_editor_state(indoc! {"
5158 «bbbˇ»
5159 ccc
5160
5161 bbb
5162 ccc
5163 "});
5164 cx.dispatch_action(SelectPrevious::default());
5165 cx.assert_editor_state(indoc! {"
5166 «bbbˇ»
5167 ccc
5168
5169 «bbbˇ»
5170 ccc
5171 "});
5172}
5173
5174#[gpui::test]
5175async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
5176 init_test(cx, |_| {});
5177
5178 let mut cx = EditorTestContext::new(cx).await;
5179 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5180
5181 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5182 .unwrap();
5183 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5184
5185 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5186 .unwrap();
5187 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5188
5189 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5190 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5191
5192 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5193 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5194
5195 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5196 .unwrap();
5197 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5198
5199 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5200 .unwrap();
5201 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5202
5203 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5204 .unwrap();
5205 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5206}
5207
5208#[gpui::test]
5209async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5210 init_test(cx, |_| {});
5211
5212 let mut cx = EditorTestContext::new(cx).await;
5213 cx.set_state(
5214 r#"let foo = 2;
5215lˇet foo = 2;
5216let fooˇ = 2;
5217let foo = 2;
5218let foo = ˇ2;"#,
5219 );
5220
5221 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5222 .unwrap();
5223 cx.assert_editor_state(
5224 r#"let foo = 2;
5225«letˇ» foo = 2;
5226let «fooˇ» = 2;
5227let foo = 2;
5228let foo = «2ˇ»;"#,
5229 );
5230
5231 // noop for multiple selections with different contents
5232 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5233 .unwrap();
5234 cx.assert_editor_state(
5235 r#"let foo = 2;
5236«letˇ» foo = 2;
5237let «fooˇ» = 2;
5238let foo = 2;
5239let foo = «2ˇ»;"#,
5240 );
5241}
5242
5243#[gpui::test]
5244async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
5245 init_test(cx, |_| {});
5246
5247 let mut cx = EditorTestContext::new(cx).await;
5248 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5249
5250 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5251 .unwrap();
5252 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5253
5254 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5255 .unwrap();
5256 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5257
5258 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5259 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5260
5261 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5262 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5263
5264 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5265 .unwrap();
5266 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5267
5268 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5269 .unwrap();
5270 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5271}
5272
5273#[gpui::test]
5274async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5275 init_test(cx, |_| {});
5276
5277 let language = Arc::new(Language::new(
5278 LanguageConfig::default(),
5279 Some(tree_sitter_rust::LANGUAGE.into()),
5280 ));
5281
5282 let text = r#"
5283 use mod1::mod2::{mod3, mod4};
5284
5285 fn fn_1(param1: bool, param2: &str) {
5286 let var1 = "text";
5287 }
5288 "#
5289 .unindent();
5290
5291 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5292 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5293 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5294
5295 editor
5296 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5297 .await;
5298
5299 editor.update(cx, |view, cx| {
5300 view.change_selections(None, cx, |s| {
5301 s.select_display_ranges([
5302 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5303 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5304 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5305 ]);
5306 });
5307 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5308 });
5309 editor.update(cx, |editor, cx| {
5310 assert_text_with_selections(
5311 editor,
5312 indoc! {r#"
5313 use mod1::mod2::{mod3, «mod4ˇ»};
5314
5315 fn fn_1«ˇ(param1: bool, param2: &str)» {
5316 let var1 = "«textˇ»";
5317 }
5318 "#},
5319 cx,
5320 );
5321 });
5322
5323 editor.update(cx, |view, cx| {
5324 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5325 });
5326 editor.update(cx, |editor, cx| {
5327 assert_text_with_selections(
5328 editor,
5329 indoc! {r#"
5330 use mod1::mod2::«{mod3, mod4}ˇ»;
5331
5332 «ˇfn fn_1(param1: bool, param2: &str) {
5333 let var1 = "text";
5334 }»
5335 "#},
5336 cx,
5337 );
5338 });
5339
5340 editor.update(cx, |view, cx| {
5341 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5342 });
5343 assert_eq!(
5344 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5345 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5346 );
5347
5348 // Trying to expand the selected syntax node one more time has no effect.
5349 editor.update(cx, |view, cx| {
5350 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5351 });
5352 assert_eq!(
5353 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5354 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5355 );
5356
5357 editor.update(cx, |view, cx| {
5358 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5359 });
5360 editor.update(cx, |editor, cx| {
5361 assert_text_with_selections(
5362 editor,
5363 indoc! {r#"
5364 use mod1::mod2::«{mod3, mod4}ˇ»;
5365
5366 «ˇfn fn_1(param1: bool, param2: &str) {
5367 let var1 = "text";
5368 }»
5369 "#},
5370 cx,
5371 );
5372 });
5373
5374 editor.update(cx, |view, cx| {
5375 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5376 });
5377 editor.update(cx, |editor, cx| {
5378 assert_text_with_selections(
5379 editor,
5380 indoc! {r#"
5381 use mod1::mod2::{mod3, «mod4ˇ»};
5382
5383 fn fn_1«ˇ(param1: bool, param2: &str)» {
5384 let var1 = "«textˇ»";
5385 }
5386 "#},
5387 cx,
5388 );
5389 });
5390
5391 editor.update(cx, |view, cx| {
5392 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5393 });
5394 editor.update(cx, |editor, cx| {
5395 assert_text_with_selections(
5396 editor,
5397 indoc! {r#"
5398 use mod1::mod2::{mod3, mo«ˇ»d4};
5399
5400 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5401 let var1 = "te«ˇ»xt";
5402 }
5403 "#},
5404 cx,
5405 );
5406 });
5407
5408 // Trying to shrink the selected syntax node one more time has no effect.
5409 editor.update(cx, |view, cx| {
5410 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5411 });
5412 editor.update(cx, |editor, cx| {
5413 assert_text_with_selections(
5414 editor,
5415 indoc! {r#"
5416 use mod1::mod2::{mod3, mo«ˇ»d4};
5417
5418 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5419 let var1 = "te«ˇ»xt";
5420 }
5421 "#},
5422 cx,
5423 );
5424 });
5425
5426 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5427 // a fold.
5428 editor.update(cx, |view, cx| {
5429 view.fold_creases(
5430 vec![
5431 Crease::simple(
5432 Point::new(0, 21)..Point::new(0, 24),
5433 FoldPlaceholder::test(),
5434 ),
5435 Crease::simple(
5436 Point::new(3, 20)..Point::new(3, 22),
5437 FoldPlaceholder::test(),
5438 ),
5439 ],
5440 true,
5441 cx,
5442 );
5443 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5444 });
5445 editor.update(cx, |editor, cx| {
5446 assert_text_with_selections(
5447 editor,
5448 indoc! {r#"
5449 use mod1::mod2::«{mod3, mod4}ˇ»;
5450
5451 fn fn_1«ˇ(param1: bool, param2: &str)» {
5452 «let var1 = "text";ˇ»
5453 }
5454 "#},
5455 cx,
5456 );
5457 });
5458}
5459
5460#[gpui::test]
5461async fn test_autoindent(cx: &mut gpui::TestAppContext) {
5462 init_test(cx, |_| {});
5463
5464 let language = Arc::new(
5465 Language::new(
5466 LanguageConfig {
5467 brackets: BracketPairConfig {
5468 pairs: vec![
5469 BracketPair {
5470 start: "{".to_string(),
5471 end: "}".to_string(),
5472 close: false,
5473 surround: false,
5474 newline: true,
5475 },
5476 BracketPair {
5477 start: "(".to_string(),
5478 end: ")".to_string(),
5479 close: false,
5480 surround: false,
5481 newline: true,
5482 },
5483 ],
5484 ..Default::default()
5485 },
5486 ..Default::default()
5487 },
5488 Some(tree_sitter_rust::LANGUAGE.into()),
5489 )
5490 .with_indents_query(
5491 r#"
5492 (_ "(" ")" @end) @indent
5493 (_ "{" "}" @end) @indent
5494 "#,
5495 )
5496 .unwrap(),
5497 );
5498
5499 let text = "fn a() {}";
5500
5501 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5502 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5503 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5504 editor
5505 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5506 .await;
5507
5508 editor.update(cx, |editor, cx| {
5509 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5510 editor.newline(&Newline, cx);
5511 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5512 assert_eq!(
5513 editor.selections.ranges(cx),
5514 &[
5515 Point::new(1, 4)..Point::new(1, 4),
5516 Point::new(3, 4)..Point::new(3, 4),
5517 Point::new(5, 0)..Point::new(5, 0)
5518 ]
5519 );
5520 });
5521}
5522
5523#[gpui::test]
5524async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5525 init_test(cx, |_| {});
5526
5527 {
5528 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5529 cx.set_state(indoc! {"
5530 impl A {
5531
5532 fn b() {}
5533
5534 «fn c() {
5535
5536 }ˇ»
5537 }
5538 "});
5539
5540 cx.update_editor(|editor, cx| {
5541 editor.autoindent(&Default::default(), cx);
5542 });
5543
5544 cx.assert_editor_state(indoc! {"
5545 impl A {
5546
5547 fn b() {}
5548
5549 «fn c() {
5550
5551 }ˇ»
5552 }
5553 "});
5554 }
5555
5556 {
5557 let mut cx = EditorTestContext::new_multibuffer(
5558 cx,
5559 [indoc! { "
5560 impl A {
5561 «
5562 // a
5563 fn b(){}
5564 »
5565 «
5566 }
5567 fn c(){}
5568 »
5569 "}],
5570 );
5571
5572 let buffer = cx.update_editor(|editor, cx| {
5573 let buffer = editor.buffer().update(cx, |buffer, _| {
5574 buffer.all_buffers().iter().next().unwrap().clone()
5575 });
5576 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5577 buffer
5578 });
5579
5580 cx.run_until_parked();
5581 cx.update_editor(|editor, cx| {
5582 editor.select_all(&Default::default(), cx);
5583 editor.autoindent(&Default::default(), cx)
5584 });
5585 cx.run_until_parked();
5586
5587 cx.update(|cx| {
5588 pretty_assertions::assert_eq!(
5589 buffer.read(cx).text(),
5590 indoc! { "
5591 impl A {
5592
5593 // a
5594 fn b(){}
5595
5596
5597 }
5598 fn c(){}
5599
5600 " }
5601 )
5602 });
5603 }
5604}
5605
5606#[gpui::test]
5607async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5608 init_test(cx, |_| {});
5609
5610 let mut cx = EditorTestContext::new(cx).await;
5611
5612 let language = Arc::new(Language::new(
5613 LanguageConfig {
5614 brackets: BracketPairConfig {
5615 pairs: vec![
5616 BracketPair {
5617 start: "{".to_string(),
5618 end: "}".to_string(),
5619 close: true,
5620 surround: true,
5621 newline: true,
5622 },
5623 BracketPair {
5624 start: "(".to_string(),
5625 end: ")".to_string(),
5626 close: true,
5627 surround: true,
5628 newline: true,
5629 },
5630 BracketPair {
5631 start: "/*".to_string(),
5632 end: " */".to_string(),
5633 close: true,
5634 surround: true,
5635 newline: true,
5636 },
5637 BracketPair {
5638 start: "[".to_string(),
5639 end: "]".to_string(),
5640 close: false,
5641 surround: false,
5642 newline: true,
5643 },
5644 BracketPair {
5645 start: "\"".to_string(),
5646 end: "\"".to_string(),
5647 close: true,
5648 surround: true,
5649 newline: false,
5650 },
5651 BracketPair {
5652 start: "<".to_string(),
5653 end: ">".to_string(),
5654 close: false,
5655 surround: true,
5656 newline: true,
5657 },
5658 ],
5659 ..Default::default()
5660 },
5661 autoclose_before: "})]".to_string(),
5662 ..Default::default()
5663 },
5664 Some(tree_sitter_rust::LANGUAGE.into()),
5665 ));
5666
5667 cx.language_registry().add(language.clone());
5668 cx.update_buffer(|buffer, cx| {
5669 buffer.set_language(Some(language), cx);
5670 });
5671
5672 cx.set_state(
5673 &r#"
5674 🏀ˇ
5675 εˇ
5676 ❤️ˇ
5677 "#
5678 .unindent(),
5679 );
5680
5681 // autoclose multiple nested brackets at multiple cursors
5682 cx.update_editor(|view, cx| {
5683 view.handle_input("{", cx);
5684 view.handle_input("{", cx);
5685 view.handle_input("{", cx);
5686 });
5687 cx.assert_editor_state(
5688 &"
5689 🏀{{{ˇ}}}
5690 ε{{{ˇ}}}
5691 ❤️{{{ˇ}}}
5692 "
5693 .unindent(),
5694 );
5695
5696 // insert a different closing bracket
5697 cx.update_editor(|view, cx| {
5698 view.handle_input(")", cx);
5699 });
5700 cx.assert_editor_state(
5701 &"
5702 🏀{{{)ˇ}}}
5703 ε{{{)ˇ}}}
5704 ❤️{{{)ˇ}}}
5705 "
5706 .unindent(),
5707 );
5708
5709 // skip over the auto-closed brackets when typing a closing bracket
5710 cx.update_editor(|view, cx| {
5711 view.move_right(&MoveRight, cx);
5712 view.handle_input("}", cx);
5713 view.handle_input("}", cx);
5714 view.handle_input("}", cx);
5715 });
5716 cx.assert_editor_state(
5717 &"
5718 🏀{{{)}}}}ˇ
5719 ε{{{)}}}}ˇ
5720 ❤️{{{)}}}}ˇ
5721 "
5722 .unindent(),
5723 );
5724
5725 // autoclose multi-character pairs
5726 cx.set_state(
5727 &"
5728 ˇ
5729 ˇ
5730 "
5731 .unindent(),
5732 );
5733 cx.update_editor(|view, cx| {
5734 view.handle_input("/", cx);
5735 view.handle_input("*", cx);
5736 });
5737 cx.assert_editor_state(
5738 &"
5739 /*ˇ */
5740 /*ˇ */
5741 "
5742 .unindent(),
5743 );
5744
5745 // one cursor autocloses a multi-character pair, one cursor
5746 // does not autoclose.
5747 cx.set_state(
5748 &"
5749 /ˇ
5750 ˇ
5751 "
5752 .unindent(),
5753 );
5754 cx.update_editor(|view, cx| view.handle_input("*", cx));
5755 cx.assert_editor_state(
5756 &"
5757 /*ˇ */
5758 *ˇ
5759 "
5760 .unindent(),
5761 );
5762
5763 // Don't autoclose if the next character isn't whitespace and isn't
5764 // listed in the language's "autoclose_before" section.
5765 cx.set_state("ˇa b");
5766 cx.update_editor(|view, cx| view.handle_input("{", cx));
5767 cx.assert_editor_state("{ˇa b");
5768
5769 // Don't autoclose if `close` is false for the bracket pair
5770 cx.set_state("ˇ");
5771 cx.update_editor(|view, cx| view.handle_input("[", cx));
5772 cx.assert_editor_state("[ˇ");
5773
5774 // Surround with brackets if text is selected
5775 cx.set_state("«aˇ» b");
5776 cx.update_editor(|view, cx| view.handle_input("{", cx));
5777 cx.assert_editor_state("{«aˇ»} b");
5778
5779 // Autclose pair where the start and end characters are the same
5780 cx.set_state("aˇ");
5781 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5782 cx.assert_editor_state("a\"ˇ\"");
5783 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5784 cx.assert_editor_state("a\"\"ˇ");
5785
5786 // Don't autoclose pair if autoclose is disabled
5787 cx.set_state("ˇ");
5788 cx.update_editor(|view, cx| view.handle_input("<", cx));
5789 cx.assert_editor_state("<ˇ");
5790
5791 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5792 cx.set_state("«aˇ» b");
5793 cx.update_editor(|view, cx| view.handle_input("<", cx));
5794 cx.assert_editor_state("<«aˇ»> b");
5795}
5796
5797#[gpui::test]
5798async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5799 init_test(cx, |settings| {
5800 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5801 });
5802
5803 let mut cx = EditorTestContext::new(cx).await;
5804
5805 let language = Arc::new(Language::new(
5806 LanguageConfig {
5807 brackets: BracketPairConfig {
5808 pairs: vec![
5809 BracketPair {
5810 start: "{".to_string(),
5811 end: "}".to_string(),
5812 close: true,
5813 surround: true,
5814 newline: true,
5815 },
5816 BracketPair {
5817 start: "(".to_string(),
5818 end: ")".to_string(),
5819 close: true,
5820 surround: true,
5821 newline: true,
5822 },
5823 BracketPair {
5824 start: "[".to_string(),
5825 end: "]".to_string(),
5826 close: false,
5827 surround: false,
5828 newline: true,
5829 },
5830 ],
5831 ..Default::default()
5832 },
5833 autoclose_before: "})]".to_string(),
5834 ..Default::default()
5835 },
5836 Some(tree_sitter_rust::LANGUAGE.into()),
5837 ));
5838
5839 cx.language_registry().add(language.clone());
5840 cx.update_buffer(|buffer, cx| {
5841 buffer.set_language(Some(language), cx);
5842 });
5843
5844 cx.set_state(
5845 &"
5846 ˇ
5847 ˇ
5848 ˇ
5849 "
5850 .unindent(),
5851 );
5852
5853 // ensure only matching closing brackets are skipped over
5854 cx.update_editor(|view, cx| {
5855 view.handle_input("}", cx);
5856 view.move_left(&MoveLeft, cx);
5857 view.handle_input(")", cx);
5858 view.move_left(&MoveLeft, cx);
5859 });
5860 cx.assert_editor_state(
5861 &"
5862 ˇ)}
5863 ˇ)}
5864 ˇ)}
5865 "
5866 .unindent(),
5867 );
5868
5869 // skip-over closing brackets at multiple cursors
5870 cx.update_editor(|view, cx| {
5871 view.handle_input(")", cx);
5872 view.handle_input("}", cx);
5873 });
5874 cx.assert_editor_state(
5875 &"
5876 )}ˇ
5877 )}ˇ
5878 )}ˇ
5879 "
5880 .unindent(),
5881 );
5882
5883 // ignore non-close brackets
5884 cx.update_editor(|view, cx| {
5885 view.handle_input("]", cx);
5886 view.move_left(&MoveLeft, cx);
5887 view.handle_input("]", cx);
5888 });
5889 cx.assert_editor_state(
5890 &"
5891 )}]ˇ]
5892 )}]ˇ]
5893 )}]ˇ]
5894 "
5895 .unindent(),
5896 );
5897}
5898
5899#[gpui::test]
5900async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
5901 init_test(cx, |_| {});
5902
5903 let mut cx = EditorTestContext::new(cx).await;
5904
5905 let html_language = Arc::new(
5906 Language::new(
5907 LanguageConfig {
5908 name: "HTML".into(),
5909 brackets: BracketPairConfig {
5910 pairs: vec![
5911 BracketPair {
5912 start: "<".into(),
5913 end: ">".into(),
5914 close: true,
5915 ..Default::default()
5916 },
5917 BracketPair {
5918 start: "{".into(),
5919 end: "}".into(),
5920 close: true,
5921 ..Default::default()
5922 },
5923 BracketPair {
5924 start: "(".into(),
5925 end: ")".into(),
5926 close: true,
5927 ..Default::default()
5928 },
5929 ],
5930 ..Default::default()
5931 },
5932 autoclose_before: "})]>".into(),
5933 ..Default::default()
5934 },
5935 Some(tree_sitter_html::language()),
5936 )
5937 .with_injection_query(
5938 r#"
5939 (script_element
5940 (raw_text) @content
5941 (#set! "language" "javascript"))
5942 "#,
5943 )
5944 .unwrap(),
5945 );
5946
5947 let javascript_language = Arc::new(Language::new(
5948 LanguageConfig {
5949 name: "JavaScript".into(),
5950 brackets: BracketPairConfig {
5951 pairs: vec![
5952 BracketPair {
5953 start: "/*".into(),
5954 end: " */".into(),
5955 close: true,
5956 ..Default::default()
5957 },
5958 BracketPair {
5959 start: "{".into(),
5960 end: "}".into(),
5961 close: true,
5962 ..Default::default()
5963 },
5964 BracketPair {
5965 start: "(".into(),
5966 end: ")".into(),
5967 close: true,
5968 ..Default::default()
5969 },
5970 ],
5971 ..Default::default()
5972 },
5973 autoclose_before: "})]>".into(),
5974 ..Default::default()
5975 },
5976 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
5977 ));
5978
5979 cx.language_registry().add(html_language.clone());
5980 cx.language_registry().add(javascript_language.clone());
5981
5982 cx.update_buffer(|buffer, cx| {
5983 buffer.set_language(Some(html_language), cx);
5984 });
5985
5986 cx.set_state(
5987 &r#"
5988 <body>ˇ
5989 <script>
5990 var x = 1;ˇ
5991 </script>
5992 </body>ˇ
5993 "#
5994 .unindent(),
5995 );
5996
5997 // Precondition: different languages are active at different locations.
5998 cx.update_editor(|editor, cx| {
5999 let snapshot = editor.snapshot(cx);
6000 let cursors = editor.selections.ranges::<usize>(cx);
6001 let languages = cursors
6002 .iter()
6003 .map(|c| snapshot.language_at(c.start).unwrap().name())
6004 .collect::<Vec<_>>();
6005 assert_eq!(
6006 languages,
6007 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6008 );
6009 });
6010
6011 // Angle brackets autoclose in HTML, but not JavaScript.
6012 cx.update_editor(|editor, cx| {
6013 editor.handle_input("<", cx);
6014 editor.handle_input("a", cx);
6015 });
6016 cx.assert_editor_state(
6017 &r#"
6018 <body><aˇ>
6019 <script>
6020 var x = 1;<aˇ
6021 </script>
6022 </body><aˇ>
6023 "#
6024 .unindent(),
6025 );
6026
6027 // Curly braces and parens autoclose in both HTML and JavaScript.
6028 cx.update_editor(|editor, cx| {
6029 editor.handle_input(" b=", cx);
6030 editor.handle_input("{", cx);
6031 editor.handle_input("c", cx);
6032 editor.handle_input("(", cx);
6033 });
6034 cx.assert_editor_state(
6035 &r#"
6036 <body><a b={c(ˇ)}>
6037 <script>
6038 var x = 1;<a b={c(ˇ)}
6039 </script>
6040 </body><a b={c(ˇ)}>
6041 "#
6042 .unindent(),
6043 );
6044
6045 // Brackets that were already autoclosed are skipped.
6046 cx.update_editor(|editor, cx| {
6047 editor.handle_input(")", cx);
6048 editor.handle_input("d", cx);
6049 editor.handle_input("}", cx);
6050 });
6051 cx.assert_editor_state(
6052 &r#"
6053 <body><a b={c()d}ˇ>
6054 <script>
6055 var x = 1;<a b={c()d}ˇ
6056 </script>
6057 </body><a b={c()d}ˇ>
6058 "#
6059 .unindent(),
6060 );
6061 cx.update_editor(|editor, cx| {
6062 editor.handle_input(">", cx);
6063 });
6064 cx.assert_editor_state(
6065 &r#"
6066 <body><a b={c()d}>ˇ
6067 <script>
6068 var x = 1;<a b={c()d}>ˇ
6069 </script>
6070 </body><a b={c()d}>ˇ
6071 "#
6072 .unindent(),
6073 );
6074
6075 // Reset
6076 cx.set_state(
6077 &r#"
6078 <body>ˇ
6079 <script>
6080 var x = 1;ˇ
6081 </script>
6082 </body>ˇ
6083 "#
6084 .unindent(),
6085 );
6086
6087 cx.update_editor(|editor, cx| {
6088 editor.handle_input("<", cx);
6089 });
6090 cx.assert_editor_state(
6091 &r#"
6092 <body><ˇ>
6093 <script>
6094 var x = 1;<ˇ
6095 </script>
6096 </body><ˇ>
6097 "#
6098 .unindent(),
6099 );
6100
6101 // When backspacing, the closing angle brackets are removed.
6102 cx.update_editor(|editor, cx| {
6103 editor.backspace(&Backspace, cx);
6104 });
6105 cx.assert_editor_state(
6106 &r#"
6107 <body>ˇ
6108 <script>
6109 var x = 1;ˇ
6110 </script>
6111 </body>ˇ
6112 "#
6113 .unindent(),
6114 );
6115
6116 // Block comments autoclose in JavaScript, but not HTML.
6117 cx.update_editor(|editor, cx| {
6118 editor.handle_input("/", cx);
6119 editor.handle_input("*", cx);
6120 });
6121 cx.assert_editor_state(
6122 &r#"
6123 <body>/*ˇ
6124 <script>
6125 var x = 1;/*ˇ */
6126 </script>
6127 </body>/*ˇ
6128 "#
6129 .unindent(),
6130 );
6131}
6132
6133#[gpui::test]
6134async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
6135 init_test(cx, |_| {});
6136
6137 let mut cx = EditorTestContext::new(cx).await;
6138
6139 let rust_language = Arc::new(
6140 Language::new(
6141 LanguageConfig {
6142 name: "Rust".into(),
6143 brackets: serde_json::from_value(json!([
6144 { "start": "{", "end": "}", "close": true, "newline": true },
6145 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6146 ]))
6147 .unwrap(),
6148 autoclose_before: "})]>".into(),
6149 ..Default::default()
6150 },
6151 Some(tree_sitter_rust::LANGUAGE.into()),
6152 )
6153 .with_override_query("(string_literal) @string")
6154 .unwrap(),
6155 );
6156
6157 cx.language_registry().add(rust_language.clone());
6158 cx.update_buffer(|buffer, cx| {
6159 buffer.set_language(Some(rust_language), cx);
6160 });
6161
6162 cx.set_state(
6163 &r#"
6164 let x = ˇ
6165 "#
6166 .unindent(),
6167 );
6168
6169 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6170 cx.update_editor(|editor, cx| {
6171 editor.handle_input("\"", cx);
6172 });
6173 cx.assert_editor_state(
6174 &r#"
6175 let x = "ˇ"
6176 "#
6177 .unindent(),
6178 );
6179
6180 // Inserting another quotation mark. The cursor moves across the existing
6181 // automatically-inserted quotation mark.
6182 cx.update_editor(|editor, cx| {
6183 editor.handle_input("\"", cx);
6184 });
6185 cx.assert_editor_state(
6186 &r#"
6187 let x = ""ˇ
6188 "#
6189 .unindent(),
6190 );
6191
6192 // Reset
6193 cx.set_state(
6194 &r#"
6195 let x = ˇ
6196 "#
6197 .unindent(),
6198 );
6199
6200 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6201 cx.update_editor(|editor, cx| {
6202 editor.handle_input("\"", cx);
6203 editor.handle_input(" ", cx);
6204 editor.move_left(&Default::default(), cx);
6205 editor.handle_input("\\", cx);
6206 editor.handle_input("\"", cx);
6207 });
6208 cx.assert_editor_state(
6209 &r#"
6210 let x = "\"ˇ "
6211 "#
6212 .unindent(),
6213 );
6214
6215 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6216 // mark. Nothing is inserted.
6217 cx.update_editor(|editor, cx| {
6218 editor.move_right(&Default::default(), cx);
6219 editor.handle_input("\"", cx);
6220 });
6221 cx.assert_editor_state(
6222 &r#"
6223 let x = "\" "ˇ
6224 "#
6225 .unindent(),
6226 );
6227}
6228
6229#[gpui::test]
6230async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
6231 init_test(cx, |_| {});
6232
6233 let language = Arc::new(Language::new(
6234 LanguageConfig {
6235 brackets: BracketPairConfig {
6236 pairs: vec![
6237 BracketPair {
6238 start: "{".to_string(),
6239 end: "}".to_string(),
6240 close: true,
6241 surround: true,
6242 newline: true,
6243 },
6244 BracketPair {
6245 start: "/* ".to_string(),
6246 end: "*/".to_string(),
6247 close: true,
6248 surround: true,
6249 ..Default::default()
6250 },
6251 ],
6252 ..Default::default()
6253 },
6254 ..Default::default()
6255 },
6256 Some(tree_sitter_rust::LANGUAGE.into()),
6257 ));
6258
6259 let text = r#"
6260 a
6261 b
6262 c
6263 "#
6264 .unindent();
6265
6266 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6267 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6268 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6269 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6270 .await;
6271
6272 view.update(cx, |view, cx| {
6273 view.change_selections(None, cx, |s| {
6274 s.select_display_ranges([
6275 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6276 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6277 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6278 ])
6279 });
6280
6281 view.handle_input("{", cx);
6282 view.handle_input("{", cx);
6283 view.handle_input("{", cx);
6284 assert_eq!(
6285 view.text(cx),
6286 "
6287 {{{a}}}
6288 {{{b}}}
6289 {{{c}}}
6290 "
6291 .unindent()
6292 );
6293 assert_eq!(
6294 view.selections.display_ranges(cx),
6295 [
6296 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6297 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6298 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6299 ]
6300 );
6301
6302 view.undo(&Undo, cx);
6303 view.undo(&Undo, cx);
6304 view.undo(&Undo, cx);
6305 assert_eq!(
6306 view.text(cx),
6307 "
6308 a
6309 b
6310 c
6311 "
6312 .unindent()
6313 );
6314 assert_eq!(
6315 view.selections.display_ranges(cx),
6316 [
6317 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6318 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6319 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6320 ]
6321 );
6322
6323 // Ensure inserting the first character of a multi-byte bracket pair
6324 // doesn't surround the selections with the bracket.
6325 view.handle_input("/", cx);
6326 assert_eq!(
6327 view.text(cx),
6328 "
6329 /
6330 /
6331 /
6332 "
6333 .unindent()
6334 );
6335 assert_eq!(
6336 view.selections.display_ranges(cx),
6337 [
6338 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6339 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6340 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6341 ]
6342 );
6343
6344 view.undo(&Undo, cx);
6345 assert_eq!(
6346 view.text(cx),
6347 "
6348 a
6349 b
6350 c
6351 "
6352 .unindent()
6353 );
6354 assert_eq!(
6355 view.selections.display_ranges(cx),
6356 [
6357 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6358 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6359 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6360 ]
6361 );
6362
6363 // Ensure inserting the last character of a multi-byte bracket pair
6364 // doesn't surround the selections with the bracket.
6365 view.handle_input("*", cx);
6366 assert_eq!(
6367 view.text(cx),
6368 "
6369 *
6370 *
6371 *
6372 "
6373 .unindent()
6374 );
6375 assert_eq!(
6376 view.selections.display_ranges(cx),
6377 [
6378 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6379 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6380 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6381 ]
6382 );
6383 });
6384}
6385
6386#[gpui::test]
6387async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6388 init_test(cx, |_| {});
6389
6390 let language = Arc::new(Language::new(
6391 LanguageConfig {
6392 brackets: BracketPairConfig {
6393 pairs: vec![BracketPair {
6394 start: "{".to_string(),
6395 end: "}".to_string(),
6396 close: true,
6397 surround: true,
6398 newline: true,
6399 }],
6400 ..Default::default()
6401 },
6402 autoclose_before: "}".to_string(),
6403 ..Default::default()
6404 },
6405 Some(tree_sitter_rust::LANGUAGE.into()),
6406 ));
6407
6408 let text = r#"
6409 a
6410 b
6411 c
6412 "#
6413 .unindent();
6414
6415 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6416 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6417 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6418 editor
6419 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6420 .await;
6421
6422 editor.update(cx, |editor, cx| {
6423 editor.change_selections(None, cx, |s| {
6424 s.select_ranges([
6425 Point::new(0, 1)..Point::new(0, 1),
6426 Point::new(1, 1)..Point::new(1, 1),
6427 Point::new(2, 1)..Point::new(2, 1),
6428 ])
6429 });
6430
6431 editor.handle_input("{", cx);
6432 editor.handle_input("{", cx);
6433 editor.handle_input("_", cx);
6434 assert_eq!(
6435 editor.text(cx),
6436 "
6437 a{{_}}
6438 b{{_}}
6439 c{{_}}
6440 "
6441 .unindent()
6442 );
6443 assert_eq!(
6444 editor.selections.ranges::<Point>(cx),
6445 [
6446 Point::new(0, 4)..Point::new(0, 4),
6447 Point::new(1, 4)..Point::new(1, 4),
6448 Point::new(2, 4)..Point::new(2, 4)
6449 ]
6450 );
6451
6452 editor.backspace(&Default::default(), cx);
6453 editor.backspace(&Default::default(), cx);
6454 assert_eq!(
6455 editor.text(cx),
6456 "
6457 a{}
6458 b{}
6459 c{}
6460 "
6461 .unindent()
6462 );
6463 assert_eq!(
6464 editor.selections.ranges::<Point>(cx),
6465 [
6466 Point::new(0, 2)..Point::new(0, 2),
6467 Point::new(1, 2)..Point::new(1, 2),
6468 Point::new(2, 2)..Point::new(2, 2)
6469 ]
6470 );
6471
6472 editor.delete_to_previous_word_start(&Default::default(), cx);
6473 assert_eq!(
6474 editor.text(cx),
6475 "
6476 a
6477 b
6478 c
6479 "
6480 .unindent()
6481 );
6482 assert_eq!(
6483 editor.selections.ranges::<Point>(cx),
6484 [
6485 Point::new(0, 1)..Point::new(0, 1),
6486 Point::new(1, 1)..Point::new(1, 1),
6487 Point::new(2, 1)..Point::new(2, 1)
6488 ]
6489 );
6490 });
6491}
6492
6493#[gpui::test]
6494async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6495 init_test(cx, |settings| {
6496 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6497 });
6498
6499 let mut cx = EditorTestContext::new(cx).await;
6500
6501 let language = Arc::new(Language::new(
6502 LanguageConfig {
6503 brackets: BracketPairConfig {
6504 pairs: vec![
6505 BracketPair {
6506 start: "{".to_string(),
6507 end: "}".to_string(),
6508 close: true,
6509 surround: true,
6510 newline: true,
6511 },
6512 BracketPair {
6513 start: "(".to_string(),
6514 end: ")".to_string(),
6515 close: true,
6516 surround: true,
6517 newline: true,
6518 },
6519 BracketPair {
6520 start: "[".to_string(),
6521 end: "]".to_string(),
6522 close: false,
6523 surround: true,
6524 newline: true,
6525 },
6526 ],
6527 ..Default::default()
6528 },
6529 autoclose_before: "})]".to_string(),
6530 ..Default::default()
6531 },
6532 Some(tree_sitter_rust::LANGUAGE.into()),
6533 ));
6534
6535 cx.language_registry().add(language.clone());
6536 cx.update_buffer(|buffer, cx| {
6537 buffer.set_language(Some(language), cx);
6538 });
6539
6540 cx.set_state(
6541 &"
6542 {(ˇ)}
6543 [[ˇ]]
6544 {(ˇ)}
6545 "
6546 .unindent(),
6547 );
6548
6549 cx.update_editor(|view, cx| {
6550 view.backspace(&Default::default(), cx);
6551 view.backspace(&Default::default(), cx);
6552 });
6553
6554 cx.assert_editor_state(
6555 &"
6556 ˇ
6557 ˇ]]
6558 ˇ
6559 "
6560 .unindent(),
6561 );
6562
6563 cx.update_editor(|view, cx| {
6564 view.handle_input("{", cx);
6565 view.handle_input("{", cx);
6566 view.move_right(&MoveRight, cx);
6567 view.move_right(&MoveRight, cx);
6568 view.move_left(&MoveLeft, cx);
6569 view.move_left(&MoveLeft, cx);
6570 view.backspace(&Default::default(), cx);
6571 });
6572
6573 cx.assert_editor_state(
6574 &"
6575 {ˇ}
6576 {ˇ}]]
6577 {ˇ}
6578 "
6579 .unindent(),
6580 );
6581
6582 cx.update_editor(|view, cx| {
6583 view.backspace(&Default::default(), cx);
6584 });
6585
6586 cx.assert_editor_state(
6587 &"
6588 ˇ
6589 ˇ]]
6590 ˇ
6591 "
6592 .unindent(),
6593 );
6594}
6595
6596#[gpui::test]
6597async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6598 init_test(cx, |_| {});
6599
6600 let language = Arc::new(Language::new(
6601 LanguageConfig::default(),
6602 Some(tree_sitter_rust::LANGUAGE.into()),
6603 ));
6604
6605 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6606 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6607 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6608 editor
6609 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6610 .await;
6611
6612 editor.update(cx, |editor, cx| {
6613 editor.set_auto_replace_emoji_shortcode(true);
6614
6615 editor.handle_input("Hello ", cx);
6616 editor.handle_input(":wave", cx);
6617 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6618
6619 editor.handle_input(":", cx);
6620 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6621
6622 editor.handle_input(" :smile", cx);
6623 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6624
6625 editor.handle_input(":", cx);
6626 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6627
6628 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6629 editor.handle_input(":wave", cx);
6630 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6631
6632 editor.handle_input(":", cx);
6633 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6634
6635 editor.handle_input(":1", cx);
6636 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6637
6638 editor.handle_input(":", cx);
6639 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6640
6641 // Ensure shortcode does not get replaced when it is part of a word
6642 editor.handle_input(" Test:wave", cx);
6643 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6644
6645 editor.handle_input(":", cx);
6646 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6647
6648 editor.set_auto_replace_emoji_shortcode(false);
6649
6650 // Ensure shortcode does not get replaced when auto replace is off
6651 editor.handle_input(" :wave", cx);
6652 assert_eq!(
6653 editor.text(cx),
6654 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6655 );
6656
6657 editor.handle_input(":", cx);
6658 assert_eq!(
6659 editor.text(cx),
6660 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6661 );
6662 });
6663}
6664
6665#[gpui::test]
6666async fn test_snippet_placeholder_choices(cx: &mut gpui::TestAppContext) {
6667 init_test(cx, |_| {});
6668
6669 let (text, insertion_ranges) = marked_text_ranges(
6670 indoc! {"
6671 ˇ
6672 "},
6673 false,
6674 );
6675
6676 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6677 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6678
6679 _ = editor.update(cx, |editor, cx| {
6680 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
6681
6682 editor
6683 .insert_snippet(&insertion_ranges, snippet, cx)
6684 .unwrap();
6685
6686 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6687 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6688 assert_eq!(editor.text(cx), expected_text);
6689 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6690 }
6691
6692 assert(
6693 editor,
6694 cx,
6695 indoc! {"
6696 type «» =•
6697 "},
6698 );
6699
6700 assert!(editor.context_menu_visible(), "There should be a matches");
6701 });
6702}
6703
6704#[gpui::test]
6705async fn test_snippets(cx: &mut gpui::TestAppContext) {
6706 init_test(cx, |_| {});
6707
6708 let (text, insertion_ranges) = marked_text_ranges(
6709 indoc! {"
6710 a.ˇ b
6711 a.ˇ b
6712 a.ˇ b
6713 "},
6714 false,
6715 );
6716
6717 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6718 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6719
6720 editor.update(cx, |editor, cx| {
6721 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6722
6723 editor
6724 .insert_snippet(&insertion_ranges, snippet, cx)
6725 .unwrap();
6726
6727 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6728 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6729 assert_eq!(editor.text(cx), expected_text);
6730 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6731 }
6732
6733 assert(
6734 editor,
6735 cx,
6736 indoc! {"
6737 a.f(«one», two, «three») b
6738 a.f(«one», two, «three») b
6739 a.f(«one», two, «three») b
6740 "},
6741 );
6742
6743 // Can't move earlier than the first tab stop
6744 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6745 assert(
6746 editor,
6747 cx,
6748 indoc! {"
6749 a.f(«one», two, «three») b
6750 a.f(«one», two, «three») b
6751 a.f(«one», two, «three») b
6752 "},
6753 );
6754
6755 assert!(editor.move_to_next_snippet_tabstop(cx));
6756 assert(
6757 editor,
6758 cx,
6759 indoc! {"
6760 a.f(one, «two», three) b
6761 a.f(one, «two», three) b
6762 a.f(one, «two», three) b
6763 "},
6764 );
6765
6766 editor.move_to_prev_snippet_tabstop(cx);
6767 assert(
6768 editor,
6769 cx,
6770 indoc! {"
6771 a.f(«one», two, «three») b
6772 a.f(«one», two, «three») b
6773 a.f(«one», two, «three») b
6774 "},
6775 );
6776
6777 assert!(editor.move_to_next_snippet_tabstop(cx));
6778 assert(
6779 editor,
6780 cx,
6781 indoc! {"
6782 a.f(one, «two», three) b
6783 a.f(one, «two», three) b
6784 a.f(one, «two», three) b
6785 "},
6786 );
6787 assert!(editor.move_to_next_snippet_tabstop(cx));
6788 assert(
6789 editor,
6790 cx,
6791 indoc! {"
6792 a.f(one, two, three)ˇ b
6793 a.f(one, two, three)ˇ b
6794 a.f(one, two, three)ˇ b
6795 "},
6796 );
6797
6798 // As soon as the last tab stop is reached, snippet state is gone
6799 editor.move_to_prev_snippet_tabstop(cx);
6800 assert(
6801 editor,
6802 cx,
6803 indoc! {"
6804 a.f(one, two, three)ˇ b
6805 a.f(one, two, three)ˇ b
6806 a.f(one, two, three)ˇ b
6807 "},
6808 );
6809 });
6810}
6811
6812#[gpui::test]
6813async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6814 init_test(cx, |_| {});
6815
6816 let fs = FakeFs::new(cx.executor());
6817 fs.insert_file("/file.rs", Default::default()).await;
6818
6819 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6820
6821 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6822 language_registry.add(rust_lang());
6823 let mut fake_servers = language_registry.register_fake_lsp(
6824 "Rust",
6825 FakeLspAdapter {
6826 capabilities: lsp::ServerCapabilities {
6827 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6828 ..Default::default()
6829 },
6830 ..Default::default()
6831 },
6832 );
6833
6834 let buffer = project
6835 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6836 .await
6837 .unwrap();
6838
6839 cx.executor().start_waiting();
6840 let fake_server = fake_servers.next().await.unwrap();
6841
6842 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6843 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6844 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6845 assert!(cx.read(|cx| editor.is_dirty(cx)));
6846
6847 let save = editor
6848 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6849 .unwrap();
6850 fake_server
6851 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6852 assert_eq!(
6853 params.text_document.uri,
6854 lsp::Url::from_file_path("/file.rs").unwrap()
6855 );
6856 assert_eq!(params.options.tab_size, 4);
6857 Ok(Some(vec![lsp::TextEdit::new(
6858 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6859 ", ".to_string(),
6860 )]))
6861 })
6862 .next()
6863 .await;
6864 cx.executor().start_waiting();
6865 save.await;
6866
6867 assert_eq!(
6868 editor.update(cx, |editor, cx| editor.text(cx)),
6869 "one, two\nthree\n"
6870 );
6871 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6872
6873 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6874 assert!(cx.read(|cx| editor.is_dirty(cx)));
6875
6876 // Ensure we can still save even if formatting hangs.
6877 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6878 assert_eq!(
6879 params.text_document.uri,
6880 lsp::Url::from_file_path("/file.rs").unwrap()
6881 );
6882 futures::future::pending::<()>().await;
6883 unreachable!()
6884 });
6885 let save = editor
6886 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6887 .unwrap();
6888 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
6889 cx.executor().start_waiting();
6890 save.await;
6891 assert_eq!(
6892 editor.update(cx, |editor, cx| editor.text(cx)),
6893 "one\ntwo\nthree\n"
6894 );
6895 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6896
6897 // For non-dirty buffer, no formatting request should be sent
6898 let save = editor
6899 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6900 .unwrap();
6901 let _pending_format_request = fake_server
6902 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
6903 panic!("Should not be invoked on non-dirty buffer");
6904 })
6905 .next();
6906 cx.executor().start_waiting();
6907 save.await;
6908
6909 // Set rust language override and assert overridden tabsize is sent to language server
6910 update_test_language_settings(cx, |settings| {
6911 settings.languages.insert(
6912 "Rust".into(),
6913 LanguageSettingsContent {
6914 tab_size: NonZeroU32::new(8),
6915 ..Default::default()
6916 },
6917 );
6918 });
6919
6920 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
6921 assert!(cx.read(|cx| editor.is_dirty(cx)));
6922 let save = editor
6923 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6924 .unwrap();
6925 fake_server
6926 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6927 assert_eq!(
6928 params.text_document.uri,
6929 lsp::Url::from_file_path("/file.rs").unwrap()
6930 );
6931 assert_eq!(params.options.tab_size, 8);
6932 Ok(Some(vec![]))
6933 })
6934 .next()
6935 .await;
6936 cx.executor().start_waiting();
6937 save.await;
6938}
6939
6940#[gpui::test]
6941async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
6942 init_test(cx, |_| {});
6943
6944 let cols = 4;
6945 let rows = 10;
6946 let sample_text_1 = sample_text(rows, cols, 'a');
6947 assert_eq!(
6948 sample_text_1,
6949 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
6950 );
6951 let sample_text_2 = sample_text(rows, cols, 'l');
6952 assert_eq!(
6953 sample_text_2,
6954 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
6955 );
6956 let sample_text_3 = sample_text(rows, cols, 'v');
6957 assert_eq!(
6958 sample_text_3,
6959 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
6960 );
6961
6962 let fs = FakeFs::new(cx.executor());
6963 fs.insert_tree(
6964 "/a",
6965 json!({
6966 "main.rs": sample_text_1,
6967 "other.rs": sample_text_2,
6968 "lib.rs": sample_text_3,
6969 }),
6970 )
6971 .await;
6972
6973 let project = Project::test(fs, ["/a".as_ref()], cx).await;
6974 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
6975 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
6976
6977 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6978 language_registry.add(rust_lang());
6979 let mut fake_servers = language_registry.register_fake_lsp(
6980 "Rust",
6981 FakeLspAdapter {
6982 capabilities: lsp::ServerCapabilities {
6983 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6984 ..Default::default()
6985 },
6986 ..Default::default()
6987 },
6988 );
6989
6990 let worktree = project.update(cx, |project, cx| {
6991 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
6992 assert_eq!(worktrees.len(), 1);
6993 worktrees.pop().unwrap()
6994 });
6995 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
6996
6997 let buffer_1 = project
6998 .update(cx, |project, cx| {
6999 project.open_buffer((worktree_id, "main.rs"), cx)
7000 })
7001 .await
7002 .unwrap();
7003 let buffer_2 = project
7004 .update(cx, |project, cx| {
7005 project.open_buffer((worktree_id, "other.rs"), cx)
7006 })
7007 .await
7008 .unwrap();
7009 let buffer_3 = project
7010 .update(cx, |project, cx| {
7011 project.open_buffer((worktree_id, "lib.rs"), cx)
7012 })
7013 .await
7014 .unwrap();
7015
7016 let multi_buffer = cx.new_model(|cx| {
7017 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7018 multi_buffer.push_excerpts(
7019 buffer_1.clone(),
7020 [
7021 ExcerptRange {
7022 context: Point::new(0, 0)..Point::new(3, 0),
7023 primary: None,
7024 },
7025 ExcerptRange {
7026 context: Point::new(5, 0)..Point::new(7, 0),
7027 primary: None,
7028 },
7029 ExcerptRange {
7030 context: Point::new(9, 0)..Point::new(10, 4),
7031 primary: None,
7032 },
7033 ],
7034 cx,
7035 );
7036 multi_buffer.push_excerpts(
7037 buffer_2.clone(),
7038 [
7039 ExcerptRange {
7040 context: Point::new(0, 0)..Point::new(3, 0),
7041 primary: None,
7042 },
7043 ExcerptRange {
7044 context: Point::new(5, 0)..Point::new(7, 0),
7045 primary: None,
7046 },
7047 ExcerptRange {
7048 context: Point::new(9, 0)..Point::new(10, 4),
7049 primary: None,
7050 },
7051 ],
7052 cx,
7053 );
7054 multi_buffer.push_excerpts(
7055 buffer_3.clone(),
7056 [
7057 ExcerptRange {
7058 context: Point::new(0, 0)..Point::new(3, 0),
7059 primary: None,
7060 },
7061 ExcerptRange {
7062 context: Point::new(5, 0)..Point::new(7, 0),
7063 primary: None,
7064 },
7065 ExcerptRange {
7066 context: Point::new(9, 0)..Point::new(10, 4),
7067 primary: None,
7068 },
7069 ],
7070 cx,
7071 );
7072 multi_buffer
7073 });
7074 let multi_buffer_editor = cx.new_view(|cx| {
7075 Editor::new(
7076 EditorMode::Full,
7077 multi_buffer,
7078 Some(project.clone()),
7079 true,
7080 cx,
7081 )
7082 });
7083
7084 multi_buffer_editor.update(cx, |editor, cx| {
7085 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
7086 editor.insert("|one|two|three|", cx);
7087 });
7088 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7089 multi_buffer_editor.update(cx, |editor, cx| {
7090 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
7091 s.select_ranges(Some(60..70))
7092 });
7093 editor.insert("|four|five|six|", cx);
7094 });
7095 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7096
7097 // First two buffers should be edited, but not the third one.
7098 assert_eq!(
7099 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7100 "a|one|two|three|aa\nbbbb\ncccc\n\nffff\ngggg\n\njjjj\nllll\nmmmm\nnnnn|four|five|six|\nr\n\nuuuu\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
7101 );
7102 buffer_1.update(cx, |buffer, _| {
7103 assert!(buffer.is_dirty());
7104 assert_eq!(
7105 buffer.text(),
7106 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7107 )
7108 });
7109 buffer_2.update(cx, |buffer, _| {
7110 assert!(buffer.is_dirty());
7111 assert_eq!(
7112 buffer.text(),
7113 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7114 )
7115 });
7116 buffer_3.update(cx, |buffer, _| {
7117 assert!(!buffer.is_dirty());
7118 assert_eq!(buffer.text(), sample_text_3,)
7119 });
7120
7121 cx.executor().start_waiting();
7122 let save = multi_buffer_editor
7123 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7124 .unwrap();
7125
7126 let fake_server = fake_servers.next().await.unwrap();
7127 fake_server
7128 .server
7129 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7130 Ok(Some(vec![lsp::TextEdit::new(
7131 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7132 format!("[{} formatted]", params.text_document.uri),
7133 )]))
7134 })
7135 .detach();
7136 save.await;
7137
7138 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7139 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7140 assert_eq!(
7141 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7142 "a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
7143 );
7144 buffer_1.update(cx, |buffer, _| {
7145 assert!(!buffer.is_dirty());
7146 assert_eq!(
7147 buffer.text(),
7148 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
7149 )
7150 });
7151 buffer_2.update(cx, |buffer, _| {
7152 assert!(!buffer.is_dirty());
7153 assert_eq!(
7154 buffer.text(),
7155 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
7156 )
7157 });
7158 buffer_3.update(cx, |buffer, _| {
7159 assert!(!buffer.is_dirty());
7160 assert_eq!(buffer.text(), sample_text_3,)
7161 });
7162}
7163
7164#[gpui::test]
7165async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
7166 init_test(cx, |_| {});
7167
7168 let fs = FakeFs::new(cx.executor());
7169 fs.insert_file("/file.rs", Default::default()).await;
7170
7171 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7172
7173 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7174 language_registry.add(rust_lang());
7175 let mut fake_servers = language_registry.register_fake_lsp(
7176 "Rust",
7177 FakeLspAdapter {
7178 capabilities: lsp::ServerCapabilities {
7179 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7180 ..Default::default()
7181 },
7182 ..Default::default()
7183 },
7184 );
7185
7186 let buffer = project
7187 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7188 .await
7189 .unwrap();
7190
7191 cx.executor().start_waiting();
7192 let fake_server = fake_servers.next().await.unwrap();
7193
7194 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7195 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7196 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7197 assert!(cx.read(|cx| editor.is_dirty(cx)));
7198
7199 let save = editor
7200 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7201 .unwrap();
7202 fake_server
7203 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7204 assert_eq!(
7205 params.text_document.uri,
7206 lsp::Url::from_file_path("/file.rs").unwrap()
7207 );
7208 assert_eq!(params.options.tab_size, 4);
7209 Ok(Some(vec![lsp::TextEdit::new(
7210 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7211 ", ".to_string(),
7212 )]))
7213 })
7214 .next()
7215 .await;
7216 cx.executor().start_waiting();
7217 save.await;
7218 assert_eq!(
7219 editor.update(cx, |editor, cx| editor.text(cx)),
7220 "one, two\nthree\n"
7221 );
7222 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7223
7224 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7225 assert!(cx.read(|cx| editor.is_dirty(cx)));
7226
7227 // Ensure we can still save even if formatting hangs.
7228 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7229 move |params, _| async move {
7230 assert_eq!(
7231 params.text_document.uri,
7232 lsp::Url::from_file_path("/file.rs").unwrap()
7233 );
7234 futures::future::pending::<()>().await;
7235 unreachable!()
7236 },
7237 );
7238 let save = editor
7239 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7240 .unwrap();
7241 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7242 cx.executor().start_waiting();
7243 save.await;
7244 assert_eq!(
7245 editor.update(cx, |editor, cx| editor.text(cx)),
7246 "one\ntwo\nthree\n"
7247 );
7248 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7249
7250 // For non-dirty buffer, no formatting request should be sent
7251 let save = editor
7252 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7253 .unwrap();
7254 let _pending_format_request = fake_server
7255 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7256 panic!("Should not be invoked on non-dirty buffer");
7257 })
7258 .next();
7259 cx.executor().start_waiting();
7260 save.await;
7261
7262 // Set Rust language override and assert overridden tabsize is sent to language server
7263 update_test_language_settings(cx, |settings| {
7264 settings.languages.insert(
7265 "Rust".into(),
7266 LanguageSettingsContent {
7267 tab_size: NonZeroU32::new(8),
7268 ..Default::default()
7269 },
7270 );
7271 });
7272
7273 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
7274 assert!(cx.read(|cx| editor.is_dirty(cx)));
7275 let save = editor
7276 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7277 .unwrap();
7278 fake_server
7279 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7280 assert_eq!(
7281 params.text_document.uri,
7282 lsp::Url::from_file_path("/file.rs").unwrap()
7283 );
7284 assert_eq!(params.options.tab_size, 8);
7285 Ok(Some(vec![]))
7286 })
7287 .next()
7288 .await;
7289 cx.executor().start_waiting();
7290 save.await;
7291}
7292
7293#[gpui::test]
7294async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7295 init_test(cx, |settings| {
7296 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7297 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7298 ))
7299 });
7300
7301 let fs = FakeFs::new(cx.executor());
7302 fs.insert_file("/file.rs", Default::default()).await;
7303
7304 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7305
7306 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7307 language_registry.add(Arc::new(Language::new(
7308 LanguageConfig {
7309 name: "Rust".into(),
7310 matcher: LanguageMatcher {
7311 path_suffixes: vec!["rs".to_string()],
7312 ..Default::default()
7313 },
7314 ..LanguageConfig::default()
7315 },
7316 Some(tree_sitter_rust::LANGUAGE.into()),
7317 )));
7318 update_test_language_settings(cx, |settings| {
7319 // Enable Prettier formatting for the same buffer, and ensure
7320 // LSP is called instead of Prettier.
7321 settings.defaults.prettier = Some(PrettierSettings {
7322 allowed: true,
7323 ..PrettierSettings::default()
7324 });
7325 });
7326 let mut fake_servers = language_registry.register_fake_lsp(
7327 "Rust",
7328 FakeLspAdapter {
7329 capabilities: lsp::ServerCapabilities {
7330 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7331 ..Default::default()
7332 },
7333 ..Default::default()
7334 },
7335 );
7336
7337 let buffer = project
7338 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7339 .await
7340 .unwrap();
7341
7342 cx.executor().start_waiting();
7343 let fake_server = fake_servers.next().await.unwrap();
7344
7345 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7346 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
7347 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7348
7349 let format = editor
7350 .update(cx, |editor, cx| {
7351 editor.perform_format(
7352 project.clone(),
7353 FormatTrigger::Manual,
7354 FormatTarget::Buffer,
7355 cx,
7356 )
7357 })
7358 .unwrap();
7359 fake_server
7360 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7361 assert_eq!(
7362 params.text_document.uri,
7363 lsp::Url::from_file_path("/file.rs").unwrap()
7364 );
7365 assert_eq!(params.options.tab_size, 4);
7366 Ok(Some(vec![lsp::TextEdit::new(
7367 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7368 ", ".to_string(),
7369 )]))
7370 })
7371 .next()
7372 .await;
7373 cx.executor().start_waiting();
7374 format.await;
7375 assert_eq!(
7376 editor.update(cx, |editor, cx| editor.text(cx)),
7377 "one, two\nthree\n"
7378 );
7379
7380 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7381 // Ensure we don't lock if formatting hangs.
7382 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7383 assert_eq!(
7384 params.text_document.uri,
7385 lsp::Url::from_file_path("/file.rs").unwrap()
7386 );
7387 futures::future::pending::<()>().await;
7388 unreachable!()
7389 });
7390 let format = editor
7391 .update(cx, |editor, cx| {
7392 editor.perform_format(project, FormatTrigger::Manual, FormatTarget::Buffer, cx)
7393 })
7394 .unwrap();
7395 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7396 cx.executor().start_waiting();
7397 format.await;
7398 assert_eq!(
7399 editor.update(cx, |editor, cx| editor.text(cx)),
7400 "one\ntwo\nthree\n"
7401 );
7402}
7403
7404#[gpui::test]
7405async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7406 init_test(cx, |_| {});
7407
7408 let mut cx = EditorLspTestContext::new_rust(
7409 lsp::ServerCapabilities {
7410 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7411 ..Default::default()
7412 },
7413 cx,
7414 )
7415 .await;
7416
7417 cx.set_state(indoc! {"
7418 one.twoˇ
7419 "});
7420
7421 // The format request takes a long time. When it completes, it inserts
7422 // a newline and an indent before the `.`
7423 cx.lsp
7424 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7425 let executor = cx.background_executor().clone();
7426 async move {
7427 executor.timer(Duration::from_millis(100)).await;
7428 Ok(Some(vec![lsp::TextEdit {
7429 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7430 new_text: "\n ".into(),
7431 }]))
7432 }
7433 });
7434
7435 // Submit a format request.
7436 let format_1 = cx
7437 .update_editor(|editor, cx| editor.format(&Format, cx))
7438 .unwrap();
7439 cx.executor().run_until_parked();
7440
7441 // Submit a second format request.
7442 let format_2 = cx
7443 .update_editor(|editor, cx| editor.format(&Format, cx))
7444 .unwrap();
7445 cx.executor().run_until_parked();
7446
7447 // Wait for both format requests to complete
7448 cx.executor().advance_clock(Duration::from_millis(200));
7449 cx.executor().start_waiting();
7450 format_1.await.unwrap();
7451 cx.executor().start_waiting();
7452 format_2.await.unwrap();
7453
7454 // The formatting edits only happens once.
7455 cx.assert_editor_state(indoc! {"
7456 one
7457 .twoˇ
7458 "});
7459}
7460
7461#[gpui::test]
7462async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7463 init_test(cx, |settings| {
7464 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7465 });
7466
7467 let mut cx = EditorLspTestContext::new_rust(
7468 lsp::ServerCapabilities {
7469 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7470 ..Default::default()
7471 },
7472 cx,
7473 )
7474 .await;
7475
7476 // Set up a buffer white some trailing whitespace and no trailing newline.
7477 cx.set_state(
7478 &[
7479 "one ", //
7480 "twoˇ", //
7481 "three ", //
7482 "four", //
7483 ]
7484 .join("\n"),
7485 );
7486
7487 // Submit a format request.
7488 let format = cx
7489 .update_editor(|editor, cx| editor.format(&Format, cx))
7490 .unwrap();
7491
7492 // Record which buffer changes have been sent to the language server
7493 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7494 cx.lsp
7495 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7496 let buffer_changes = buffer_changes.clone();
7497 move |params, _| {
7498 buffer_changes.lock().extend(
7499 params
7500 .content_changes
7501 .into_iter()
7502 .map(|e| (e.range.unwrap(), e.text)),
7503 );
7504 }
7505 });
7506
7507 // Handle formatting requests to the language server.
7508 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7509 let buffer_changes = buffer_changes.clone();
7510 move |_, _| {
7511 // When formatting is requested, trailing whitespace has already been stripped,
7512 // and the trailing newline has already been added.
7513 assert_eq!(
7514 &buffer_changes.lock()[1..],
7515 &[
7516 (
7517 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7518 "".into()
7519 ),
7520 (
7521 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7522 "".into()
7523 ),
7524 (
7525 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7526 "\n".into()
7527 ),
7528 ]
7529 );
7530
7531 // Insert blank lines between each line of the buffer.
7532 async move {
7533 Ok(Some(vec![
7534 lsp::TextEdit {
7535 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7536 new_text: "\n".into(),
7537 },
7538 lsp::TextEdit {
7539 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7540 new_text: "\n".into(),
7541 },
7542 ]))
7543 }
7544 }
7545 });
7546
7547 // After formatting the buffer, the trailing whitespace is stripped,
7548 // a newline is appended, and the edits provided by the language server
7549 // have been applied.
7550 format.await.unwrap();
7551 cx.assert_editor_state(
7552 &[
7553 "one", //
7554 "", //
7555 "twoˇ", //
7556 "", //
7557 "three", //
7558 "four", //
7559 "", //
7560 ]
7561 .join("\n"),
7562 );
7563
7564 // Undoing the formatting undoes the trailing whitespace removal, the
7565 // trailing newline, and the LSP edits.
7566 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7567 cx.assert_editor_state(
7568 &[
7569 "one ", //
7570 "twoˇ", //
7571 "three ", //
7572 "four", //
7573 ]
7574 .join("\n"),
7575 );
7576}
7577
7578#[gpui::test]
7579async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7580 cx: &mut gpui::TestAppContext,
7581) {
7582 init_test(cx, |_| {});
7583
7584 cx.update(|cx| {
7585 cx.update_global::<SettingsStore, _>(|settings, cx| {
7586 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7587 settings.auto_signature_help = Some(true);
7588 });
7589 });
7590 });
7591
7592 let mut cx = EditorLspTestContext::new_rust(
7593 lsp::ServerCapabilities {
7594 signature_help_provider: Some(lsp::SignatureHelpOptions {
7595 ..Default::default()
7596 }),
7597 ..Default::default()
7598 },
7599 cx,
7600 )
7601 .await;
7602
7603 let language = Language::new(
7604 LanguageConfig {
7605 name: "Rust".into(),
7606 brackets: BracketPairConfig {
7607 pairs: vec![
7608 BracketPair {
7609 start: "{".to_string(),
7610 end: "}".to_string(),
7611 close: true,
7612 surround: true,
7613 newline: true,
7614 },
7615 BracketPair {
7616 start: "(".to_string(),
7617 end: ")".to_string(),
7618 close: true,
7619 surround: true,
7620 newline: true,
7621 },
7622 BracketPair {
7623 start: "/*".to_string(),
7624 end: " */".to_string(),
7625 close: true,
7626 surround: true,
7627 newline: true,
7628 },
7629 BracketPair {
7630 start: "[".to_string(),
7631 end: "]".to_string(),
7632 close: false,
7633 surround: false,
7634 newline: true,
7635 },
7636 BracketPair {
7637 start: "\"".to_string(),
7638 end: "\"".to_string(),
7639 close: true,
7640 surround: true,
7641 newline: false,
7642 },
7643 BracketPair {
7644 start: "<".to_string(),
7645 end: ">".to_string(),
7646 close: false,
7647 surround: true,
7648 newline: true,
7649 },
7650 ],
7651 ..Default::default()
7652 },
7653 autoclose_before: "})]".to_string(),
7654 ..Default::default()
7655 },
7656 Some(tree_sitter_rust::LANGUAGE.into()),
7657 );
7658 let language = Arc::new(language);
7659
7660 cx.language_registry().add(language.clone());
7661 cx.update_buffer(|buffer, cx| {
7662 buffer.set_language(Some(language), cx);
7663 });
7664
7665 cx.set_state(
7666 &r#"
7667 fn main() {
7668 sampleˇ
7669 }
7670 "#
7671 .unindent(),
7672 );
7673
7674 cx.update_editor(|view, cx| {
7675 view.handle_input("(", cx);
7676 });
7677 cx.assert_editor_state(
7678 &"
7679 fn main() {
7680 sample(ˇ)
7681 }
7682 "
7683 .unindent(),
7684 );
7685
7686 let mocked_response = lsp::SignatureHelp {
7687 signatures: vec![lsp::SignatureInformation {
7688 label: "fn sample(param1: u8, param2: u8)".to_string(),
7689 documentation: None,
7690 parameters: Some(vec![
7691 lsp::ParameterInformation {
7692 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7693 documentation: None,
7694 },
7695 lsp::ParameterInformation {
7696 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7697 documentation: None,
7698 },
7699 ]),
7700 active_parameter: None,
7701 }],
7702 active_signature: Some(0),
7703 active_parameter: Some(0),
7704 };
7705 handle_signature_help_request(&mut cx, mocked_response).await;
7706
7707 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7708 .await;
7709
7710 cx.editor(|editor, _| {
7711 let signature_help_state = editor.signature_help_state.popover().cloned();
7712 assert!(signature_help_state.is_some());
7713 let ParsedMarkdown {
7714 text, highlights, ..
7715 } = signature_help_state.unwrap().parsed_content;
7716 assert_eq!(text, "param1: u8, param2: u8");
7717 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7718 });
7719}
7720
7721#[gpui::test]
7722async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7723 init_test(cx, |_| {});
7724
7725 cx.update(|cx| {
7726 cx.update_global::<SettingsStore, _>(|settings, cx| {
7727 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7728 settings.auto_signature_help = Some(false);
7729 settings.show_signature_help_after_edits = Some(false);
7730 });
7731 });
7732 });
7733
7734 let mut cx = EditorLspTestContext::new_rust(
7735 lsp::ServerCapabilities {
7736 signature_help_provider: Some(lsp::SignatureHelpOptions {
7737 ..Default::default()
7738 }),
7739 ..Default::default()
7740 },
7741 cx,
7742 )
7743 .await;
7744
7745 let language = Language::new(
7746 LanguageConfig {
7747 name: "Rust".into(),
7748 brackets: BracketPairConfig {
7749 pairs: vec![
7750 BracketPair {
7751 start: "{".to_string(),
7752 end: "}".to_string(),
7753 close: true,
7754 surround: true,
7755 newline: true,
7756 },
7757 BracketPair {
7758 start: "(".to_string(),
7759 end: ")".to_string(),
7760 close: true,
7761 surround: true,
7762 newline: true,
7763 },
7764 BracketPair {
7765 start: "/*".to_string(),
7766 end: " */".to_string(),
7767 close: true,
7768 surround: true,
7769 newline: true,
7770 },
7771 BracketPair {
7772 start: "[".to_string(),
7773 end: "]".to_string(),
7774 close: false,
7775 surround: false,
7776 newline: true,
7777 },
7778 BracketPair {
7779 start: "\"".to_string(),
7780 end: "\"".to_string(),
7781 close: true,
7782 surround: true,
7783 newline: false,
7784 },
7785 BracketPair {
7786 start: "<".to_string(),
7787 end: ">".to_string(),
7788 close: false,
7789 surround: true,
7790 newline: true,
7791 },
7792 ],
7793 ..Default::default()
7794 },
7795 autoclose_before: "})]".to_string(),
7796 ..Default::default()
7797 },
7798 Some(tree_sitter_rust::LANGUAGE.into()),
7799 );
7800 let language = Arc::new(language);
7801
7802 cx.language_registry().add(language.clone());
7803 cx.update_buffer(|buffer, cx| {
7804 buffer.set_language(Some(language), cx);
7805 });
7806
7807 // Ensure that signature_help is not called when no signature help is enabled.
7808 cx.set_state(
7809 &r#"
7810 fn main() {
7811 sampleˇ
7812 }
7813 "#
7814 .unindent(),
7815 );
7816 cx.update_editor(|view, cx| {
7817 view.handle_input("(", cx);
7818 });
7819 cx.assert_editor_state(
7820 &"
7821 fn main() {
7822 sample(ˇ)
7823 }
7824 "
7825 .unindent(),
7826 );
7827 cx.editor(|editor, _| {
7828 assert!(editor.signature_help_state.task().is_none());
7829 });
7830
7831 let mocked_response = lsp::SignatureHelp {
7832 signatures: vec![lsp::SignatureInformation {
7833 label: "fn sample(param1: u8, param2: u8)".to_string(),
7834 documentation: None,
7835 parameters: Some(vec![
7836 lsp::ParameterInformation {
7837 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7838 documentation: None,
7839 },
7840 lsp::ParameterInformation {
7841 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7842 documentation: None,
7843 },
7844 ]),
7845 active_parameter: None,
7846 }],
7847 active_signature: Some(0),
7848 active_parameter: Some(0),
7849 };
7850
7851 // Ensure that signature_help is called when enabled afte edits
7852 cx.update(|cx| {
7853 cx.update_global::<SettingsStore, _>(|settings, cx| {
7854 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7855 settings.auto_signature_help = Some(false);
7856 settings.show_signature_help_after_edits = Some(true);
7857 });
7858 });
7859 });
7860 cx.set_state(
7861 &r#"
7862 fn main() {
7863 sampleˇ
7864 }
7865 "#
7866 .unindent(),
7867 );
7868 cx.update_editor(|view, cx| {
7869 view.handle_input("(", cx);
7870 });
7871 cx.assert_editor_state(
7872 &"
7873 fn main() {
7874 sample(ˇ)
7875 }
7876 "
7877 .unindent(),
7878 );
7879 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
7880 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7881 .await;
7882 cx.update_editor(|editor, _| {
7883 let signature_help_state = editor.signature_help_state.popover().cloned();
7884 assert!(signature_help_state.is_some());
7885 let ParsedMarkdown {
7886 text, highlights, ..
7887 } = signature_help_state.unwrap().parsed_content;
7888 assert_eq!(text, "param1: u8, param2: u8");
7889 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7890 editor.signature_help_state = SignatureHelpState::default();
7891 });
7892
7893 // Ensure that signature_help is called when auto signature help override is enabled
7894 cx.update(|cx| {
7895 cx.update_global::<SettingsStore, _>(|settings, cx| {
7896 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7897 settings.auto_signature_help = Some(true);
7898 settings.show_signature_help_after_edits = Some(false);
7899 });
7900 });
7901 });
7902 cx.set_state(
7903 &r#"
7904 fn main() {
7905 sampleˇ
7906 }
7907 "#
7908 .unindent(),
7909 );
7910 cx.update_editor(|view, cx| {
7911 view.handle_input("(", cx);
7912 });
7913 cx.assert_editor_state(
7914 &"
7915 fn main() {
7916 sample(ˇ)
7917 }
7918 "
7919 .unindent(),
7920 );
7921 handle_signature_help_request(&mut cx, mocked_response).await;
7922 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7923 .await;
7924 cx.editor(|editor, _| {
7925 let signature_help_state = editor.signature_help_state.popover().cloned();
7926 assert!(signature_help_state.is_some());
7927 let ParsedMarkdown {
7928 text, highlights, ..
7929 } = signature_help_state.unwrap().parsed_content;
7930 assert_eq!(text, "param1: u8, param2: u8");
7931 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7932 });
7933}
7934
7935#[gpui::test]
7936async fn test_signature_help(cx: &mut gpui::TestAppContext) {
7937 init_test(cx, |_| {});
7938 cx.update(|cx| {
7939 cx.update_global::<SettingsStore, _>(|settings, cx| {
7940 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7941 settings.auto_signature_help = Some(true);
7942 });
7943 });
7944 });
7945
7946 let mut cx = EditorLspTestContext::new_rust(
7947 lsp::ServerCapabilities {
7948 signature_help_provider: Some(lsp::SignatureHelpOptions {
7949 ..Default::default()
7950 }),
7951 ..Default::default()
7952 },
7953 cx,
7954 )
7955 .await;
7956
7957 // A test that directly calls `show_signature_help`
7958 cx.update_editor(|editor, cx| {
7959 editor.show_signature_help(&ShowSignatureHelp, cx);
7960 });
7961
7962 let mocked_response = lsp::SignatureHelp {
7963 signatures: vec![lsp::SignatureInformation {
7964 label: "fn sample(param1: u8, param2: u8)".to_string(),
7965 documentation: None,
7966 parameters: Some(vec![
7967 lsp::ParameterInformation {
7968 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7969 documentation: None,
7970 },
7971 lsp::ParameterInformation {
7972 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7973 documentation: None,
7974 },
7975 ]),
7976 active_parameter: None,
7977 }],
7978 active_signature: Some(0),
7979 active_parameter: Some(0),
7980 };
7981 handle_signature_help_request(&mut cx, mocked_response).await;
7982
7983 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7984 .await;
7985
7986 cx.editor(|editor, _| {
7987 let signature_help_state = editor.signature_help_state.popover().cloned();
7988 assert!(signature_help_state.is_some());
7989 let ParsedMarkdown {
7990 text, highlights, ..
7991 } = signature_help_state.unwrap().parsed_content;
7992 assert_eq!(text, "param1: u8, param2: u8");
7993 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7994 });
7995
7996 // When exiting outside from inside the brackets, `signature_help` is closed.
7997 cx.set_state(indoc! {"
7998 fn main() {
7999 sample(ˇ);
8000 }
8001
8002 fn sample(param1: u8, param2: u8) {}
8003 "});
8004
8005 cx.update_editor(|editor, cx| {
8006 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
8007 });
8008
8009 let mocked_response = lsp::SignatureHelp {
8010 signatures: Vec::new(),
8011 active_signature: None,
8012 active_parameter: None,
8013 };
8014 handle_signature_help_request(&mut cx, mocked_response).await;
8015
8016 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8017 .await;
8018
8019 cx.editor(|editor, _| {
8020 assert!(!editor.signature_help_state.is_shown());
8021 });
8022
8023 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8024 cx.set_state(indoc! {"
8025 fn main() {
8026 sample(ˇ);
8027 }
8028
8029 fn sample(param1: u8, param2: u8) {}
8030 "});
8031
8032 let mocked_response = lsp::SignatureHelp {
8033 signatures: vec![lsp::SignatureInformation {
8034 label: "fn sample(param1: u8, param2: u8)".to_string(),
8035 documentation: None,
8036 parameters: Some(vec![
8037 lsp::ParameterInformation {
8038 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8039 documentation: None,
8040 },
8041 lsp::ParameterInformation {
8042 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8043 documentation: None,
8044 },
8045 ]),
8046 active_parameter: None,
8047 }],
8048 active_signature: Some(0),
8049 active_parameter: Some(0),
8050 };
8051 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8052 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8053 .await;
8054 cx.editor(|editor, _| {
8055 assert!(editor.signature_help_state.is_shown());
8056 });
8057
8058 // Restore the popover with more parameter input
8059 cx.set_state(indoc! {"
8060 fn main() {
8061 sample(param1, param2ˇ);
8062 }
8063
8064 fn sample(param1: u8, param2: u8) {}
8065 "});
8066
8067 let mocked_response = lsp::SignatureHelp {
8068 signatures: vec![lsp::SignatureInformation {
8069 label: "fn sample(param1: u8, param2: u8)".to_string(),
8070 documentation: None,
8071 parameters: Some(vec![
8072 lsp::ParameterInformation {
8073 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8074 documentation: None,
8075 },
8076 lsp::ParameterInformation {
8077 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8078 documentation: None,
8079 },
8080 ]),
8081 active_parameter: None,
8082 }],
8083 active_signature: Some(0),
8084 active_parameter: Some(1),
8085 };
8086 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8087 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8088 .await;
8089
8090 // When selecting a range, the popover is gone.
8091 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8092 cx.update_editor(|editor, cx| {
8093 editor.change_selections(None, cx, |s| {
8094 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8095 })
8096 });
8097 cx.assert_editor_state(indoc! {"
8098 fn main() {
8099 sample(param1, «ˇparam2»);
8100 }
8101
8102 fn sample(param1: u8, param2: u8) {}
8103 "});
8104 cx.editor(|editor, _| {
8105 assert!(!editor.signature_help_state.is_shown());
8106 });
8107
8108 // When unselecting again, the popover is back if within the brackets.
8109 cx.update_editor(|editor, cx| {
8110 editor.change_selections(None, cx, |s| {
8111 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8112 })
8113 });
8114 cx.assert_editor_state(indoc! {"
8115 fn main() {
8116 sample(param1, ˇparam2);
8117 }
8118
8119 fn sample(param1: u8, param2: u8) {}
8120 "});
8121 handle_signature_help_request(&mut cx, mocked_response).await;
8122 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8123 .await;
8124 cx.editor(|editor, _| {
8125 assert!(editor.signature_help_state.is_shown());
8126 });
8127
8128 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8129 cx.update_editor(|editor, cx| {
8130 editor.change_selections(None, cx, |s| {
8131 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8132 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8133 })
8134 });
8135 cx.assert_editor_state(indoc! {"
8136 fn main() {
8137 sample(param1, ˇparam2);
8138 }
8139
8140 fn sample(param1: u8, param2: u8) {}
8141 "});
8142
8143 let mocked_response = lsp::SignatureHelp {
8144 signatures: vec![lsp::SignatureInformation {
8145 label: "fn sample(param1: u8, param2: u8)".to_string(),
8146 documentation: None,
8147 parameters: Some(vec![
8148 lsp::ParameterInformation {
8149 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8150 documentation: None,
8151 },
8152 lsp::ParameterInformation {
8153 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8154 documentation: None,
8155 },
8156 ]),
8157 active_parameter: None,
8158 }],
8159 active_signature: Some(0),
8160 active_parameter: Some(1),
8161 };
8162 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8163 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8164 .await;
8165 cx.update_editor(|editor, cx| {
8166 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8167 });
8168 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8169 .await;
8170 cx.update_editor(|editor, cx| {
8171 editor.change_selections(None, cx, |s| {
8172 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8173 })
8174 });
8175 cx.assert_editor_state(indoc! {"
8176 fn main() {
8177 sample(param1, «ˇparam2»);
8178 }
8179
8180 fn sample(param1: u8, param2: u8) {}
8181 "});
8182 cx.update_editor(|editor, cx| {
8183 editor.change_selections(None, cx, |s| {
8184 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8185 })
8186 });
8187 cx.assert_editor_state(indoc! {"
8188 fn main() {
8189 sample(param1, ˇparam2);
8190 }
8191
8192 fn sample(param1: u8, param2: u8) {}
8193 "});
8194 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8195 .await;
8196}
8197
8198#[gpui::test]
8199async fn test_completion(cx: &mut gpui::TestAppContext) {
8200 init_test(cx, |_| {});
8201
8202 let mut cx = EditorLspTestContext::new_rust(
8203 lsp::ServerCapabilities {
8204 completion_provider: Some(lsp::CompletionOptions {
8205 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8206 resolve_provider: Some(true),
8207 ..Default::default()
8208 }),
8209 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8210 ..Default::default()
8211 },
8212 cx,
8213 )
8214 .await;
8215 let counter = Arc::new(AtomicUsize::new(0));
8216
8217 cx.set_state(indoc! {"
8218 oneˇ
8219 two
8220 three
8221 "});
8222 cx.simulate_keystroke(".");
8223 handle_completion_request(
8224 &mut cx,
8225 indoc! {"
8226 one.|<>
8227 two
8228 three
8229 "},
8230 vec!["first_completion", "second_completion"],
8231 counter.clone(),
8232 )
8233 .await;
8234 cx.condition(|editor, _| editor.context_menu_visible())
8235 .await;
8236 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8237
8238 let _handler = handle_signature_help_request(
8239 &mut cx,
8240 lsp::SignatureHelp {
8241 signatures: vec![lsp::SignatureInformation {
8242 label: "test signature".to_string(),
8243 documentation: None,
8244 parameters: Some(vec![lsp::ParameterInformation {
8245 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8246 documentation: None,
8247 }]),
8248 active_parameter: None,
8249 }],
8250 active_signature: None,
8251 active_parameter: None,
8252 },
8253 );
8254 cx.update_editor(|editor, cx| {
8255 assert!(
8256 !editor.signature_help_state.is_shown(),
8257 "No signature help was called for"
8258 );
8259 editor.show_signature_help(&ShowSignatureHelp, cx);
8260 });
8261 cx.run_until_parked();
8262 cx.update_editor(|editor, _| {
8263 assert!(
8264 !editor.signature_help_state.is_shown(),
8265 "No signature help should be shown when completions menu is open"
8266 );
8267 });
8268
8269 let apply_additional_edits = cx.update_editor(|editor, cx| {
8270 editor.context_menu_next(&Default::default(), cx);
8271 editor
8272 .confirm_completion(&ConfirmCompletion::default(), cx)
8273 .unwrap()
8274 });
8275 cx.assert_editor_state(indoc! {"
8276 one.second_completionˇ
8277 two
8278 three
8279 "});
8280
8281 handle_resolve_completion_request(
8282 &mut cx,
8283 Some(vec![
8284 (
8285 //This overlaps with the primary completion edit which is
8286 //misbehavior from the LSP spec, test that we filter it out
8287 indoc! {"
8288 one.second_ˇcompletion
8289 two
8290 threeˇ
8291 "},
8292 "overlapping additional edit",
8293 ),
8294 (
8295 indoc! {"
8296 one.second_completion
8297 two
8298 threeˇ
8299 "},
8300 "\nadditional edit",
8301 ),
8302 ]),
8303 )
8304 .await;
8305 apply_additional_edits.await.unwrap();
8306 cx.assert_editor_state(indoc! {"
8307 one.second_completionˇ
8308 two
8309 three
8310 additional edit
8311 "});
8312
8313 cx.set_state(indoc! {"
8314 one.second_completion
8315 twoˇ
8316 threeˇ
8317 additional edit
8318 "});
8319 cx.simulate_keystroke(" ");
8320 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8321 cx.simulate_keystroke("s");
8322 assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
8323
8324 cx.assert_editor_state(indoc! {"
8325 one.second_completion
8326 two sˇ
8327 three sˇ
8328 additional edit
8329 "});
8330 handle_completion_request(
8331 &mut cx,
8332 indoc! {"
8333 one.second_completion
8334 two s
8335 three <s|>
8336 additional edit
8337 "},
8338 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8339 counter.clone(),
8340 )
8341 .await;
8342 cx.condition(|editor, _| editor.context_menu_visible())
8343 .await;
8344 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8345
8346 cx.simulate_keystroke("i");
8347
8348 handle_completion_request(
8349 &mut cx,
8350 indoc! {"
8351 one.second_completion
8352 two si
8353 three <si|>
8354 additional edit
8355 "},
8356 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8357 counter.clone(),
8358 )
8359 .await;
8360 cx.condition(|editor, _| editor.context_menu_visible())
8361 .await;
8362 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8363
8364 let apply_additional_edits = cx.update_editor(|editor, cx| {
8365 editor
8366 .confirm_completion(&ConfirmCompletion::default(), cx)
8367 .unwrap()
8368 });
8369 cx.assert_editor_state(indoc! {"
8370 one.second_completion
8371 two sixth_completionˇ
8372 three sixth_completionˇ
8373 additional edit
8374 "});
8375
8376 handle_resolve_completion_request(&mut cx, None).await;
8377 apply_additional_edits.await.unwrap();
8378
8379 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(ContextMenu::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(ContextMenu::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(ContextMenu::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(ContextMenu::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 project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
9927
9928 cx.set_state(indoc! {"
9929 ˇfn func(abc def: i32) -> u32 {
9930 }
9931 "});
9932
9933 cx.update(|cx| {
9934 project.update(cx, |project, cx| {
9935 project
9936 .update_diagnostics(
9937 LanguageServerId(0),
9938 lsp::PublishDiagnosticsParams {
9939 uri: lsp::Url::from_file_path("/root/file").unwrap(),
9940 version: None,
9941 diagnostics: vec![
9942 lsp::Diagnostic {
9943 range: lsp::Range::new(
9944 lsp::Position::new(0, 11),
9945 lsp::Position::new(0, 12),
9946 ),
9947 severity: Some(lsp::DiagnosticSeverity::ERROR),
9948 ..Default::default()
9949 },
9950 lsp::Diagnostic {
9951 range: lsp::Range::new(
9952 lsp::Position::new(0, 12),
9953 lsp::Position::new(0, 15),
9954 ),
9955 severity: Some(lsp::DiagnosticSeverity::ERROR),
9956 ..Default::default()
9957 },
9958 lsp::Diagnostic {
9959 range: lsp::Range::new(
9960 lsp::Position::new(0, 25),
9961 lsp::Position::new(0, 28),
9962 ),
9963 severity: Some(lsp::DiagnosticSeverity::ERROR),
9964 ..Default::default()
9965 },
9966 ],
9967 },
9968 &[],
9969 cx,
9970 )
9971 .unwrap()
9972 });
9973 });
9974
9975 executor.run_until_parked();
9976
9977 cx.update_editor(|editor, cx| {
9978 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9979 });
9980
9981 cx.assert_editor_state(indoc! {"
9982 fn func(abc def: i32) -> ˇu32 {
9983 }
9984 "});
9985
9986 cx.update_editor(|editor, cx| {
9987 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9988 });
9989
9990 cx.assert_editor_state(indoc! {"
9991 fn func(abc ˇdef: i32) -> u32 {
9992 }
9993 "});
9994
9995 cx.update_editor(|editor, cx| {
9996 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
9997 });
9998
9999 cx.assert_editor_state(indoc! {"
10000 fn func(abcˇ def: i32) -> u32 {
10001 }
10002 "});
10003
10004 cx.update_editor(|editor, cx| {
10005 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10006 });
10007
10008 cx.assert_editor_state(indoc! {"
10009 fn func(abc def: i32) -> ˇu32 {
10010 }
10011 "});
10012}
10013
10014#[gpui::test]
10015async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
10016 init_test(cx, |_| {});
10017
10018 let mut cx = EditorTestContext::new(cx).await;
10019
10020 cx.set_state(indoc! {"
10021 fn func(abˇc def: i32) -> u32 {
10022 }
10023 "});
10024 let project = cx.update_editor(|editor, _| editor.project.clone().unwrap());
10025
10026 cx.update(|cx| {
10027 project.update(cx, |project, cx| {
10028 project.update_diagnostics(
10029 LanguageServerId(0),
10030 lsp::PublishDiagnosticsParams {
10031 uri: lsp::Url::from_file_path("/root/file").unwrap(),
10032 version: None,
10033 diagnostics: vec![lsp::Diagnostic {
10034 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
10035 severity: Some(lsp::DiagnosticSeverity::ERROR),
10036 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
10037 ..Default::default()
10038 }],
10039 },
10040 &[],
10041 cx,
10042 )
10043 })
10044 }).unwrap();
10045 cx.run_until_parked();
10046 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
10047 cx.run_until_parked();
10048 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
10049}
10050
10051#[gpui::test]
10052async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10053 init_test(cx, |_| {});
10054
10055 let mut cx = EditorTestContext::new(cx).await;
10056
10057 let diff_base = r#"
10058 use some::mod;
10059
10060 const A: u32 = 42;
10061
10062 fn main() {
10063 println!("hello");
10064
10065 println!("world");
10066 }
10067 "#
10068 .unindent();
10069
10070 // Edits are modified, removed, modified, added
10071 cx.set_state(
10072 &r#"
10073 use some::modified;
10074
10075 ˇ
10076 fn main() {
10077 println!("hello there");
10078
10079 println!("around the");
10080 println!("world");
10081 }
10082 "#
10083 .unindent(),
10084 );
10085
10086 cx.set_diff_base(&diff_base);
10087 executor.run_until_parked();
10088
10089 cx.update_editor(|editor, cx| {
10090 //Wrap around the bottom of the buffer
10091 for _ in 0..3 {
10092 editor.go_to_next_hunk(&GoToHunk, cx);
10093 }
10094 });
10095
10096 cx.assert_editor_state(
10097 &r#"
10098 ˇuse some::modified;
10099
10100
10101 fn main() {
10102 println!("hello there");
10103
10104 println!("around the");
10105 println!("world");
10106 }
10107 "#
10108 .unindent(),
10109 );
10110
10111 cx.update_editor(|editor, cx| {
10112 //Wrap around the top of the buffer
10113 for _ in 0..2 {
10114 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10115 }
10116 });
10117
10118 cx.assert_editor_state(
10119 &r#"
10120 use some::modified;
10121
10122
10123 fn main() {
10124 ˇ println!("hello there");
10125
10126 println!("around the");
10127 println!("world");
10128 }
10129 "#
10130 .unindent(),
10131 );
10132
10133 cx.update_editor(|editor, cx| {
10134 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10135 });
10136
10137 cx.assert_editor_state(
10138 &r#"
10139 use some::modified;
10140
10141 ˇ
10142 fn main() {
10143 println!("hello there");
10144
10145 println!("around the");
10146 println!("world");
10147 }
10148 "#
10149 .unindent(),
10150 );
10151
10152 cx.update_editor(|editor, cx| {
10153 for _ in 0..3 {
10154 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10155 }
10156 });
10157
10158 cx.assert_editor_state(
10159 &r#"
10160 use some::modified;
10161
10162
10163 fn main() {
10164 ˇ println!("hello there");
10165
10166 println!("around the");
10167 println!("world");
10168 }
10169 "#
10170 .unindent(),
10171 );
10172
10173 cx.update_editor(|editor, cx| {
10174 editor.fold(&Fold, cx);
10175
10176 //Make sure that the fold only gets one hunk
10177 for _ in 0..4 {
10178 editor.go_to_next_hunk(&GoToHunk, cx);
10179 }
10180 });
10181
10182 cx.assert_editor_state(
10183 &r#"
10184 ˇuse some::modified;
10185
10186
10187 fn main() {
10188 println!("hello there");
10189
10190 println!("around the");
10191 println!("world");
10192 }
10193 "#
10194 .unindent(),
10195 );
10196}
10197
10198#[test]
10199fn test_split_words() {
10200 fn split(text: &str) -> Vec<&str> {
10201 split_words(text).collect()
10202 }
10203
10204 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
10205 assert_eq!(split("hello_world"), &["hello_", "world"]);
10206 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
10207 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
10208 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
10209 assert_eq!(split("helloworld"), &["helloworld"]);
10210
10211 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
10212}
10213
10214#[gpui::test]
10215async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
10216 init_test(cx, |_| {});
10217
10218 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
10219 let mut assert = |before, after| {
10220 let _state_context = cx.set_state(before);
10221 cx.update_editor(|editor, cx| {
10222 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
10223 });
10224 cx.assert_editor_state(after);
10225 };
10226
10227 // Outside bracket jumps to outside of matching bracket
10228 assert("console.logˇ(var);", "console.log(var)ˇ;");
10229 assert("console.log(var)ˇ;", "console.logˇ(var);");
10230
10231 // Inside bracket jumps to inside of matching bracket
10232 assert("console.log(ˇvar);", "console.log(varˇ);");
10233 assert("console.log(varˇ);", "console.log(ˇvar);");
10234
10235 // When outside a bracket and inside, favor jumping to the inside bracket
10236 assert(
10237 "console.log('foo', [1, 2, 3]ˇ);",
10238 "console.log(ˇ'foo', [1, 2, 3]);",
10239 );
10240 assert(
10241 "console.log(ˇ'foo', [1, 2, 3]);",
10242 "console.log('foo', [1, 2, 3]ˇ);",
10243 );
10244
10245 // Bias forward if two options are equally likely
10246 assert(
10247 "let result = curried_fun()ˇ();",
10248 "let result = curried_fun()()ˇ;",
10249 );
10250
10251 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
10252 assert(
10253 indoc! {"
10254 function test() {
10255 console.log('test')ˇ
10256 }"},
10257 indoc! {"
10258 function test() {
10259 console.logˇ('test')
10260 }"},
10261 );
10262}
10263
10264#[gpui::test]
10265async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
10266 init_test(cx, |_| {});
10267
10268 let fs = FakeFs::new(cx.executor());
10269 fs.insert_tree(
10270 "/a",
10271 json!({
10272 "main.rs": "fn main() { let a = 5; }",
10273 "other.rs": "// Test file",
10274 }),
10275 )
10276 .await;
10277 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10278
10279 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10280 language_registry.add(Arc::new(Language::new(
10281 LanguageConfig {
10282 name: "Rust".into(),
10283 matcher: LanguageMatcher {
10284 path_suffixes: vec!["rs".to_string()],
10285 ..Default::default()
10286 },
10287 brackets: BracketPairConfig {
10288 pairs: vec![BracketPair {
10289 start: "{".to_string(),
10290 end: "}".to_string(),
10291 close: true,
10292 surround: true,
10293 newline: true,
10294 }],
10295 disabled_scopes_by_bracket_ix: Vec::new(),
10296 },
10297 ..Default::default()
10298 },
10299 Some(tree_sitter_rust::LANGUAGE.into()),
10300 )));
10301 let mut fake_servers = language_registry.register_fake_lsp(
10302 "Rust",
10303 FakeLspAdapter {
10304 capabilities: lsp::ServerCapabilities {
10305 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
10306 first_trigger_character: "{".to_string(),
10307 more_trigger_character: None,
10308 }),
10309 ..Default::default()
10310 },
10311 ..Default::default()
10312 },
10313 );
10314
10315 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10316
10317 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10318
10319 let worktree_id = workspace
10320 .update(cx, |workspace, cx| {
10321 workspace.project().update(cx, |project, cx| {
10322 project.worktrees(cx).next().unwrap().read(cx).id()
10323 })
10324 })
10325 .unwrap();
10326
10327 let buffer = project
10328 .update(cx, |project, cx| {
10329 project.open_local_buffer("/a/main.rs", cx)
10330 })
10331 .await
10332 .unwrap();
10333 cx.executor().run_until_parked();
10334 cx.executor().start_waiting();
10335 let fake_server = fake_servers.next().await.unwrap();
10336 let editor_handle = workspace
10337 .update(cx, |workspace, cx| {
10338 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
10339 })
10340 .unwrap()
10341 .await
10342 .unwrap()
10343 .downcast::<Editor>()
10344 .unwrap();
10345
10346 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
10347 assert_eq!(
10348 params.text_document_position.text_document.uri,
10349 lsp::Url::from_file_path("/a/main.rs").unwrap(),
10350 );
10351 assert_eq!(
10352 params.text_document_position.position,
10353 lsp::Position::new(0, 21),
10354 );
10355
10356 Ok(Some(vec![lsp::TextEdit {
10357 new_text: "]".to_string(),
10358 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10359 }]))
10360 });
10361
10362 editor_handle.update(cx, |editor, cx| {
10363 editor.focus(cx);
10364 editor.change_selections(None, cx, |s| {
10365 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
10366 });
10367 editor.handle_input("{", cx);
10368 });
10369
10370 cx.executor().run_until_parked();
10371
10372 buffer.update(cx, |buffer, _| {
10373 assert_eq!(
10374 buffer.text(),
10375 "fn main() { let a = {5}; }",
10376 "No extra braces from on type formatting should appear in the buffer"
10377 )
10378 });
10379}
10380
10381#[gpui::test]
10382async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
10383 init_test(cx, |_| {});
10384
10385 let fs = FakeFs::new(cx.executor());
10386 fs.insert_tree(
10387 "/a",
10388 json!({
10389 "main.rs": "fn main() { let a = 5; }",
10390 "other.rs": "// Test file",
10391 }),
10392 )
10393 .await;
10394
10395 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10396
10397 let server_restarts = Arc::new(AtomicUsize::new(0));
10398 let closure_restarts = Arc::clone(&server_restarts);
10399 let language_server_name = "test language server";
10400 let language_name: LanguageName = "Rust".into();
10401
10402 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10403 language_registry.add(Arc::new(Language::new(
10404 LanguageConfig {
10405 name: language_name.clone(),
10406 matcher: LanguageMatcher {
10407 path_suffixes: vec!["rs".to_string()],
10408 ..Default::default()
10409 },
10410 ..Default::default()
10411 },
10412 Some(tree_sitter_rust::LANGUAGE.into()),
10413 )));
10414 let mut fake_servers = language_registry.register_fake_lsp(
10415 "Rust",
10416 FakeLspAdapter {
10417 name: language_server_name,
10418 initialization_options: Some(json!({
10419 "testOptionValue": true
10420 })),
10421 initializer: Some(Box::new(move |fake_server| {
10422 let task_restarts = Arc::clone(&closure_restarts);
10423 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
10424 task_restarts.fetch_add(1, atomic::Ordering::Release);
10425 futures::future::ready(Ok(()))
10426 });
10427 })),
10428 ..Default::default()
10429 },
10430 );
10431
10432 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10433 let _buffer = project
10434 .update(cx, |project, cx| {
10435 project.open_local_buffer("/a/main.rs", cx)
10436 })
10437 .await
10438 .unwrap();
10439 let _fake_server = fake_servers.next().await.unwrap();
10440 update_test_language_settings(cx, |language_settings| {
10441 language_settings.languages.insert(
10442 language_name.clone(),
10443 LanguageSettingsContent {
10444 tab_size: NonZeroU32::new(8),
10445 ..Default::default()
10446 },
10447 );
10448 });
10449 cx.executor().run_until_parked();
10450 assert_eq!(
10451 server_restarts.load(atomic::Ordering::Acquire),
10452 0,
10453 "Should not restart LSP server on an unrelated change"
10454 );
10455
10456 update_test_project_settings(cx, |project_settings| {
10457 project_settings.lsp.insert(
10458 "Some other server name".into(),
10459 LspSettings {
10460 binary: None,
10461 settings: None,
10462 initialization_options: Some(json!({
10463 "some other init value": false
10464 })),
10465 },
10466 );
10467 });
10468 cx.executor().run_until_parked();
10469 assert_eq!(
10470 server_restarts.load(atomic::Ordering::Acquire),
10471 0,
10472 "Should not restart LSP server on an unrelated LSP settings change"
10473 );
10474
10475 update_test_project_settings(cx, |project_settings| {
10476 project_settings.lsp.insert(
10477 language_server_name.into(),
10478 LspSettings {
10479 binary: None,
10480 settings: None,
10481 initialization_options: Some(json!({
10482 "anotherInitValue": false
10483 })),
10484 },
10485 );
10486 });
10487 cx.executor().run_until_parked();
10488 assert_eq!(
10489 server_restarts.load(atomic::Ordering::Acquire),
10490 1,
10491 "Should restart LSP server on a related LSP settings change"
10492 );
10493
10494 update_test_project_settings(cx, |project_settings| {
10495 project_settings.lsp.insert(
10496 language_server_name.into(),
10497 LspSettings {
10498 binary: None,
10499 settings: None,
10500 initialization_options: Some(json!({
10501 "anotherInitValue": false
10502 })),
10503 },
10504 );
10505 });
10506 cx.executor().run_until_parked();
10507 assert_eq!(
10508 server_restarts.load(atomic::Ordering::Acquire),
10509 1,
10510 "Should not restart LSP server on a related LSP settings change that is the same"
10511 );
10512
10513 update_test_project_settings(cx, |project_settings| {
10514 project_settings.lsp.insert(
10515 language_server_name.into(),
10516 LspSettings {
10517 binary: None,
10518 settings: None,
10519 initialization_options: None,
10520 },
10521 );
10522 });
10523 cx.executor().run_until_parked();
10524 assert_eq!(
10525 server_restarts.load(atomic::Ordering::Acquire),
10526 2,
10527 "Should restart LSP server on another related LSP settings change"
10528 );
10529}
10530
10531#[gpui::test]
10532async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10533 init_test(cx, |_| {});
10534
10535 let mut cx = EditorLspTestContext::new_rust(
10536 lsp::ServerCapabilities {
10537 completion_provider: Some(lsp::CompletionOptions {
10538 trigger_characters: Some(vec![".".to_string()]),
10539 resolve_provider: Some(true),
10540 ..Default::default()
10541 }),
10542 ..Default::default()
10543 },
10544 cx,
10545 )
10546 .await;
10547
10548 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10549 cx.simulate_keystroke(".");
10550 let completion_item = lsp::CompletionItem {
10551 label: "some".into(),
10552 kind: Some(lsp::CompletionItemKind::SNIPPET),
10553 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10554 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10555 kind: lsp::MarkupKind::Markdown,
10556 value: "```rust\nSome(2)\n```".to_string(),
10557 })),
10558 deprecated: Some(false),
10559 sort_text: Some("fffffff2".to_string()),
10560 filter_text: Some("some".to_string()),
10561 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10562 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10563 range: lsp::Range {
10564 start: lsp::Position {
10565 line: 0,
10566 character: 22,
10567 },
10568 end: lsp::Position {
10569 line: 0,
10570 character: 22,
10571 },
10572 },
10573 new_text: "Some(2)".to_string(),
10574 })),
10575 additional_text_edits: Some(vec![lsp::TextEdit {
10576 range: lsp::Range {
10577 start: lsp::Position {
10578 line: 0,
10579 character: 20,
10580 },
10581 end: lsp::Position {
10582 line: 0,
10583 character: 22,
10584 },
10585 },
10586 new_text: "".to_string(),
10587 }]),
10588 ..Default::default()
10589 };
10590
10591 let closure_completion_item = completion_item.clone();
10592 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10593 let task_completion_item = closure_completion_item.clone();
10594 async move {
10595 Ok(Some(lsp::CompletionResponse::Array(vec![
10596 task_completion_item,
10597 ])))
10598 }
10599 });
10600
10601 request.next().await;
10602
10603 cx.condition(|editor, _| editor.context_menu_visible())
10604 .await;
10605 let apply_additional_edits = cx.update_editor(|editor, cx| {
10606 editor
10607 .confirm_completion(&ConfirmCompletion::default(), cx)
10608 .unwrap()
10609 });
10610 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
10611
10612 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
10613 let task_completion_item = completion_item.clone();
10614 async move { Ok(task_completion_item) }
10615 })
10616 .next()
10617 .await
10618 .unwrap();
10619 apply_additional_edits.await.unwrap();
10620 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
10621}
10622
10623#[gpui::test]
10624async fn test_completions_resolve_updates_labels(cx: &mut gpui::TestAppContext) {
10625 init_test(cx, |_| {});
10626
10627 let mut cx = EditorLspTestContext::new_rust(
10628 lsp::ServerCapabilities {
10629 completion_provider: Some(lsp::CompletionOptions {
10630 trigger_characters: Some(vec![".".to_string()]),
10631 resolve_provider: Some(true),
10632 ..Default::default()
10633 }),
10634 ..Default::default()
10635 },
10636 cx,
10637 )
10638 .await;
10639
10640 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10641 cx.simulate_keystroke(".");
10642
10643 let completion_item = lsp::CompletionItem {
10644 label: "unresolved".to_string(),
10645 detail: None,
10646 documentation: None,
10647 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10648 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10649 new_text: ".unresolved".to_string(),
10650 })),
10651 ..lsp::CompletionItem::default()
10652 };
10653
10654 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10655 let item = completion_item.clone();
10656 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item]))) }
10657 })
10658 .next()
10659 .await;
10660
10661 cx.condition(|editor, _| editor.context_menu_visible())
10662 .await;
10663 cx.update_editor(|editor, _| {
10664 let context_menu = editor.context_menu.read();
10665 let context_menu = context_menu
10666 .as_ref()
10667 .expect("Should have the context menu deployed");
10668 match context_menu {
10669 ContextMenu::Completions(completions_menu) => {
10670 let completions = completions_menu.completions.read();
10671 assert_eq!(completions.len(), 1, "Should have one completion");
10672 assert_eq!(completions.get(0).unwrap().label.text, "unresolved");
10673 }
10674 ContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10675 }
10676 });
10677
10678 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| async move {
10679 Ok(lsp::CompletionItem {
10680 label: "resolved".to_string(),
10681 detail: Some("Now resolved!".to_string()),
10682 documentation: Some(lsp::Documentation::String("Docs".to_string())),
10683 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10684 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10685 new_text: ".resolved".to_string(),
10686 })),
10687 ..lsp::CompletionItem::default()
10688 })
10689 })
10690 .next()
10691 .await;
10692 cx.run_until_parked();
10693
10694 cx.update_editor(|editor, _| {
10695 let context_menu = editor.context_menu.read();
10696 let context_menu = context_menu
10697 .as_ref()
10698 .expect("Should have the context menu deployed");
10699 match context_menu {
10700 ContextMenu::Completions(completions_menu) => {
10701 let completions = completions_menu.completions.read();
10702 assert_eq!(completions.len(), 1, "Should have one completion");
10703 assert_eq!(
10704 completions.get(0).unwrap().label.text,
10705 "resolved",
10706 "Should update the completion label after resolving"
10707 );
10708 }
10709 ContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
10710 }
10711 });
10712}
10713
10714#[gpui::test]
10715async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
10716 init_test(cx, |_| {});
10717
10718 let mut cx = EditorLspTestContext::new_rust(
10719 lsp::ServerCapabilities {
10720 completion_provider: Some(lsp::CompletionOptions {
10721 trigger_characters: Some(vec![".".to_string()]),
10722 resolve_provider: Some(true),
10723 ..Default::default()
10724 }),
10725 ..Default::default()
10726 },
10727 cx,
10728 )
10729 .await;
10730
10731 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10732 cx.simulate_keystroke(".");
10733
10734 let default_commit_characters = vec!["?".to_string()];
10735 let default_data = json!({ "very": "special"});
10736 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
10737 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
10738 let default_edit_range = lsp::Range {
10739 start: lsp::Position {
10740 line: 0,
10741 character: 5,
10742 },
10743 end: lsp::Position {
10744 line: 0,
10745 character: 5,
10746 },
10747 };
10748
10749 let resolve_requests_number = Arc::new(AtomicUsize::new(0));
10750 let expect_first_item = Arc::new(AtomicBool::new(true));
10751 cx.lsp
10752 .server
10753 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
10754 let closure_default_data = default_data.clone();
10755 let closure_resolve_requests_number = resolve_requests_number.clone();
10756 let closure_expect_first_item = expect_first_item.clone();
10757 let closure_default_commit_characters = default_commit_characters.clone();
10758 move |item_to_resolve, _| {
10759 closure_resolve_requests_number.fetch_add(1, atomic::Ordering::Release);
10760 let default_data = closure_default_data.clone();
10761 let default_commit_characters = closure_default_commit_characters.clone();
10762 let expect_first_item = closure_expect_first_item.clone();
10763 async move {
10764 if expect_first_item.load(atomic::Ordering::Acquire) {
10765 assert_eq!(
10766 item_to_resolve.label, "Some(2)",
10767 "Should have selected the first item"
10768 );
10769 assert_eq!(
10770 item_to_resolve.data,
10771 Some(json!({ "very": "special"})),
10772 "First item should bring its own data for resolving"
10773 );
10774 assert_eq!(
10775 item_to_resolve.commit_characters,
10776 Some(default_commit_characters),
10777 "First item had no own commit characters and should inherit the default ones"
10778 );
10779 assert!(
10780 matches!(
10781 item_to_resolve.text_edit,
10782 Some(lsp::CompletionTextEdit::InsertAndReplace { .. })
10783 ),
10784 "First item should bring its own edit range for resolving"
10785 );
10786 assert_eq!(
10787 item_to_resolve.insert_text_format,
10788 Some(default_insert_text_format),
10789 "First item had no own insert text format and should inherit the default one"
10790 );
10791 assert_eq!(
10792 item_to_resolve.insert_text_mode,
10793 Some(lsp::InsertTextMode::ADJUST_INDENTATION),
10794 "First item should bring its own insert text mode for resolving"
10795 );
10796 Ok(item_to_resolve)
10797 } else {
10798 assert_eq!(
10799 item_to_resolve.label, "vec![2]",
10800 "Should have selected the last item"
10801 );
10802 assert_eq!(
10803 item_to_resolve.data,
10804 Some(default_data),
10805 "Last item has no own resolve data and should inherit the default one"
10806 );
10807 assert_eq!(
10808 item_to_resolve.commit_characters,
10809 Some(default_commit_characters),
10810 "Last item had no own commit characters and should inherit the default ones"
10811 );
10812 assert_eq!(
10813 item_to_resolve.text_edit,
10814 Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10815 range: default_edit_range,
10816 new_text: "vec![2]".to_string()
10817 })),
10818 "Last item had no own edit range and should inherit the default one"
10819 );
10820 assert_eq!(
10821 item_to_resolve.insert_text_format,
10822 Some(lsp::InsertTextFormat::PLAIN_TEXT),
10823 "Last item should bring its own insert text format for resolving"
10824 );
10825 assert_eq!(
10826 item_to_resolve.insert_text_mode,
10827 Some(default_insert_text_mode),
10828 "Last item had no own insert text mode and should inherit the default one"
10829 );
10830
10831 Ok(item_to_resolve)
10832 }
10833 }
10834 }
10835 }).detach();
10836
10837 let completion_data = default_data.clone();
10838 let completion_characters = default_commit_characters.clone();
10839 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10840 let default_data = completion_data.clone();
10841 let default_commit_characters = completion_characters.clone();
10842 async move {
10843 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
10844 items: vec![
10845 lsp::CompletionItem {
10846 label: "Some(2)".into(),
10847 insert_text: Some("Some(2)".into()),
10848 data: Some(json!({ "very": "special"})),
10849 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
10850 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
10851 lsp::InsertReplaceEdit {
10852 new_text: "Some(2)".to_string(),
10853 insert: lsp::Range::default(),
10854 replace: lsp::Range::default(),
10855 },
10856 )),
10857 ..lsp::CompletionItem::default()
10858 },
10859 lsp::CompletionItem {
10860 label: "vec![2]".into(),
10861 insert_text: Some("vec![2]".into()),
10862 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
10863 ..lsp::CompletionItem::default()
10864 },
10865 ],
10866 item_defaults: Some(lsp::CompletionListItemDefaults {
10867 data: Some(default_data.clone()),
10868 commit_characters: Some(default_commit_characters.clone()),
10869 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
10870 default_edit_range,
10871 )),
10872 insert_text_format: Some(default_insert_text_format),
10873 insert_text_mode: Some(default_insert_text_mode),
10874 }),
10875 ..lsp::CompletionList::default()
10876 })))
10877 }
10878 })
10879 .next()
10880 .await;
10881
10882 cx.condition(|editor, _| editor.context_menu_visible())
10883 .await;
10884 cx.run_until_parked();
10885 cx.update_editor(|editor, _| {
10886 let menu = editor.context_menu.read();
10887 match menu.as_ref().expect("should have the completions menu") {
10888 ContextMenu::Completions(completions_menu) => {
10889 assert_eq!(
10890 completions_menu
10891 .matches
10892 .iter()
10893 .map(|c| c.string.as_str())
10894 .collect::<Vec<_>>(),
10895 vec!["Some(2)", "vec![2]"]
10896 );
10897 }
10898 ContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
10899 }
10900 });
10901 assert_eq!(
10902 resolve_requests_number.load(atomic::Ordering::Acquire),
10903 1,
10904 "While there are 2 items in the completion list, only 1 resolve request should have been sent, for the selected item"
10905 );
10906
10907 cx.update_editor(|editor, cx| {
10908 editor.context_menu_first(&ContextMenuFirst, cx);
10909 });
10910 cx.run_until_parked();
10911 assert_eq!(
10912 resolve_requests_number.load(atomic::Ordering::Acquire),
10913 2,
10914 "After re-selecting the first item, another resolve request should have been sent"
10915 );
10916
10917 expect_first_item.store(false, atomic::Ordering::Release);
10918 cx.update_editor(|editor, cx| {
10919 editor.context_menu_last(&ContextMenuLast, cx);
10920 });
10921 cx.run_until_parked();
10922 assert_eq!(
10923 resolve_requests_number.load(atomic::Ordering::Acquire),
10924 3,
10925 "After selecting the other item, another resolve request should have been sent"
10926 );
10927}
10928
10929#[gpui::test]
10930async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
10931 init_test(cx, |_| {});
10932
10933 let mut cx = EditorLspTestContext::new(
10934 Language::new(
10935 LanguageConfig {
10936 matcher: LanguageMatcher {
10937 path_suffixes: vec!["jsx".into()],
10938 ..Default::default()
10939 },
10940 overrides: [(
10941 "element".into(),
10942 LanguageConfigOverride {
10943 word_characters: Override::Set(['-'].into_iter().collect()),
10944 ..Default::default()
10945 },
10946 )]
10947 .into_iter()
10948 .collect(),
10949 ..Default::default()
10950 },
10951 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10952 )
10953 .with_override_query("(jsx_self_closing_element) @element")
10954 .unwrap(),
10955 lsp::ServerCapabilities {
10956 completion_provider: Some(lsp::CompletionOptions {
10957 trigger_characters: Some(vec![":".to_string()]),
10958 ..Default::default()
10959 }),
10960 ..Default::default()
10961 },
10962 cx,
10963 )
10964 .await;
10965
10966 cx.lsp
10967 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10968 Ok(Some(lsp::CompletionResponse::Array(vec![
10969 lsp::CompletionItem {
10970 label: "bg-blue".into(),
10971 ..Default::default()
10972 },
10973 lsp::CompletionItem {
10974 label: "bg-red".into(),
10975 ..Default::default()
10976 },
10977 lsp::CompletionItem {
10978 label: "bg-yellow".into(),
10979 ..Default::default()
10980 },
10981 ])))
10982 });
10983
10984 cx.set_state(r#"<p class="bgˇ" />"#);
10985
10986 // Trigger completion when typing a dash, because the dash is an extra
10987 // word character in the 'element' scope, which contains the cursor.
10988 cx.simulate_keystroke("-");
10989 cx.executor().run_until_parked();
10990 cx.update_editor(|editor, _| {
10991 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
10992 assert_eq!(
10993 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
10994 &["bg-red", "bg-blue", "bg-yellow"]
10995 );
10996 } else {
10997 panic!("expected completion menu to be open");
10998 }
10999 });
11000
11001 cx.simulate_keystroke("l");
11002 cx.executor().run_until_parked();
11003 cx.update_editor(|editor, _| {
11004 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
11005 assert_eq!(
11006 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
11007 &["bg-blue", "bg-yellow"]
11008 );
11009 } else {
11010 panic!("expected completion menu to be open");
11011 }
11012 });
11013
11014 // When filtering completions, consider the character after the '-' to
11015 // be the start of a subword.
11016 cx.set_state(r#"<p class="yelˇ" />"#);
11017 cx.simulate_keystroke("l");
11018 cx.executor().run_until_parked();
11019 cx.update_editor(|editor, _| {
11020 if let Some(ContextMenu::Completions(menu)) = editor.context_menu.read().as_ref() {
11021 assert_eq!(
11022 menu.matches.iter().map(|m| &m.string).collect::<Vec<_>>(),
11023 &["bg-yellow"]
11024 );
11025 } else {
11026 panic!("expected completion menu to be open");
11027 }
11028 });
11029}
11030
11031#[gpui::test]
11032async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
11033 init_test(cx, |settings| {
11034 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
11035 FormatterList(vec![Formatter::Prettier].into()),
11036 ))
11037 });
11038
11039 let fs = FakeFs::new(cx.executor());
11040 fs.insert_file("/file.ts", Default::default()).await;
11041
11042 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
11043 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11044
11045 language_registry.add(Arc::new(Language::new(
11046 LanguageConfig {
11047 name: "TypeScript".into(),
11048 matcher: LanguageMatcher {
11049 path_suffixes: vec!["ts".to_string()],
11050 ..Default::default()
11051 },
11052 ..Default::default()
11053 },
11054 Some(tree_sitter_rust::LANGUAGE.into()),
11055 )));
11056 update_test_language_settings(cx, |settings| {
11057 settings.defaults.prettier = Some(PrettierSettings {
11058 allowed: true,
11059 ..PrettierSettings::default()
11060 });
11061 });
11062
11063 let test_plugin = "test_plugin";
11064 let _ = language_registry.register_fake_lsp(
11065 "TypeScript",
11066 FakeLspAdapter {
11067 prettier_plugins: vec![test_plugin],
11068 ..Default::default()
11069 },
11070 );
11071
11072 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
11073 let buffer = project
11074 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
11075 .await
11076 .unwrap();
11077
11078 let buffer_text = "one\ntwo\nthree\n";
11079 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
11080 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
11081 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
11082
11083 editor
11084 .update(cx, |editor, cx| {
11085 editor.perform_format(
11086 project.clone(),
11087 FormatTrigger::Manual,
11088 FormatTarget::Buffer,
11089 cx,
11090 )
11091 })
11092 .unwrap()
11093 .await;
11094 assert_eq!(
11095 editor.update(cx, |editor, cx| editor.text(cx)),
11096 buffer_text.to_string() + prettier_format_suffix,
11097 "Test prettier formatting was not applied to the original buffer text",
11098 );
11099
11100 update_test_language_settings(cx, |settings| {
11101 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
11102 });
11103 let format = editor.update(cx, |editor, cx| {
11104 editor.perform_format(
11105 project.clone(),
11106 FormatTrigger::Manual,
11107 FormatTarget::Buffer,
11108 cx,
11109 )
11110 });
11111 format.await.unwrap();
11112 assert_eq!(
11113 editor.update(cx, |editor, cx| editor.text(cx)),
11114 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
11115 "Autoformatting (via test prettier) was not applied to the original buffer text",
11116 );
11117}
11118
11119#[gpui::test]
11120async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
11121 init_test(cx, |_| {});
11122 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11123 let base_text = indoc! {r#"
11124 struct Row;
11125 struct Row1;
11126 struct Row2;
11127
11128 struct Row4;
11129 struct Row5;
11130 struct Row6;
11131
11132 struct Row8;
11133 struct Row9;
11134 struct Row10;"#};
11135
11136 // When addition hunks are not adjacent to carets, no hunk revert is performed
11137 assert_hunk_revert(
11138 indoc! {r#"struct Row;
11139 struct Row1;
11140 struct Row1.1;
11141 struct Row1.2;
11142 struct Row2;ˇ
11143
11144 struct Row4;
11145 struct Row5;
11146 struct Row6;
11147
11148 struct Row8;
11149 ˇstruct Row9;
11150 struct Row9.1;
11151 struct Row9.2;
11152 struct Row9.3;
11153 struct Row10;"#},
11154 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11155 indoc! {r#"struct Row;
11156 struct Row1;
11157 struct Row1.1;
11158 struct Row1.2;
11159 struct Row2;ˇ
11160
11161 struct Row4;
11162 struct Row5;
11163 struct Row6;
11164
11165 struct Row8;
11166 ˇstruct Row9;
11167 struct Row9.1;
11168 struct Row9.2;
11169 struct Row9.3;
11170 struct Row10;"#},
11171 base_text,
11172 &mut cx,
11173 );
11174 // Same for selections
11175 assert_hunk_revert(
11176 indoc! {r#"struct Row;
11177 struct Row1;
11178 struct Row2;
11179 struct Row2.1;
11180 struct Row2.2;
11181 «ˇ
11182 struct Row4;
11183 struct» Row5;
11184 «struct Row6;
11185 ˇ»
11186 struct Row9.1;
11187 struct Row9.2;
11188 struct Row9.3;
11189 struct Row8;
11190 struct Row9;
11191 struct Row10;"#},
11192 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11193 indoc! {r#"struct Row;
11194 struct Row1;
11195 struct Row2;
11196 struct Row2.1;
11197 struct Row2.2;
11198 «ˇ
11199 struct Row4;
11200 struct» Row5;
11201 «struct Row6;
11202 ˇ»
11203 struct Row9.1;
11204 struct Row9.2;
11205 struct Row9.3;
11206 struct Row8;
11207 struct Row9;
11208 struct Row10;"#},
11209 base_text,
11210 &mut cx,
11211 );
11212
11213 // When carets and selections intersect the addition hunks, those are reverted.
11214 // Adjacent carets got merged.
11215 assert_hunk_revert(
11216 indoc! {r#"struct Row;
11217 ˇ// something on the top
11218 struct Row1;
11219 struct Row2;
11220 struct Roˇw3.1;
11221 struct Row2.2;
11222 struct Row2.3;ˇ
11223
11224 struct Row4;
11225 struct ˇRow5.1;
11226 struct Row5.2;
11227 struct «Rowˇ»5.3;
11228 struct Row5;
11229 struct Row6;
11230 ˇ
11231 struct Row9.1;
11232 struct «Rowˇ»9.2;
11233 struct «ˇRow»9.3;
11234 struct Row8;
11235 struct Row9;
11236 «ˇ// something on bottom»
11237 struct Row10;"#},
11238 vec![
11239 DiffHunkStatus::Added,
11240 DiffHunkStatus::Added,
11241 DiffHunkStatus::Added,
11242 DiffHunkStatus::Added,
11243 DiffHunkStatus::Added,
11244 ],
11245 indoc! {r#"struct Row;
11246 ˇstruct Row1;
11247 struct Row2;
11248 ˇ
11249 struct Row4;
11250 ˇstruct Row5;
11251 struct Row6;
11252 ˇ
11253 ˇstruct Row8;
11254 struct Row9;
11255 ˇstruct Row10;"#},
11256 base_text,
11257 &mut cx,
11258 );
11259}
11260
11261#[gpui::test]
11262async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
11263 init_test(cx, |_| {});
11264 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11265 let base_text = indoc! {r#"
11266 struct Row;
11267 struct Row1;
11268 struct Row2;
11269
11270 struct Row4;
11271 struct Row5;
11272 struct Row6;
11273
11274 struct Row8;
11275 struct Row9;
11276 struct Row10;"#};
11277
11278 // Modification hunks behave the same as the addition ones.
11279 assert_hunk_revert(
11280 indoc! {r#"struct Row;
11281 struct Row1;
11282 struct Row33;
11283 ˇ
11284 struct Row4;
11285 struct Row5;
11286 struct Row6;
11287 ˇ
11288 struct Row99;
11289 struct Row9;
11290 struct Row10;"#},
11291 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11292 indoc! {r#"struct Row;
11293 struct Row1;
11294 struct Row33;
11295 ˇ
11296 struct Row4;
11297 struct Row5;
11298 struct Row6;
11299 ˇ
11300 struct Row99;
11301 struct Row9;
11302 struct Row10;"#},
11303 base_text,
11304 &mut cx,
11305 );
11306 assert_hunk_revert(
11307 indoc! {r#"struct Row;
11308 struct Row1;
11309 struct Row33;
11310 «ˇ
11311 struct Row4;
11312 struct» Row5;
11313 «struct Row6;
11314 ˇ»
11315 struct Row99;
11316 struct Row9;
11317 struct Row10;"#},
11318 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11319 indoc! {r#"struct Row;
11320 struct Row1;
11321 struct Row33;
11322 «ˇ
11323 struct Row4;
11324 struct» Row5;
11325 «struct Row6;
11326 ˇ»
11327 struct Row99;
11328 struct Row9;
11329 struct Row10;"#},
11330 base_text,
11331 &mut cx,
11332 );
11333
11334 assert_hunk_revert(
11335 indoc! {r#"ˇstruct Row1.1;
11336 struct Row1;
11337 «ˇstr»uct Row22;
11338
11339 struct ˇRow44;
11340 struct Row5;
11341 struct «Rˇ»ow66;ˇ
11342
11343 «struˇ»ct Row88;
11344 struct Row9;
11345 struct Row1011;ˇ"#},
11346 vec![
11347 DiffHunkStatus::Modified,
11348 DiffHunkStatus::Modified,
11349 DiffHunkStatus::Modified,
11350 DiffHunkStatus::Modified,
11351 DiffHunkStatus::Modified,
11352 DiffHunkStatus::Modified,
11353 ],
11354 indoc! {r#"struct Row;
11355 ˇstruct Row1;
11356 struct Row2;
11357 ˇ
11358 struct Row4;
11359 ˇstruct Row5;
11360 struct Row6;
11361 ˇ
11362 struct Row8;
11363 ˇstruct Row9;
11364 struct Row10;ˇ"#},
11365 base_text,
11366 &mut cx,
11367 );
11368}
11369
11370#[gpui::test]
11371async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
11372 init_test(cx, |_| {});
11373 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11374 let base_text = indoc! {r#"struct Row;
11375struct Row1;
11376struct Row2;
11377
11378struct Row4;
11379struct Row5;
11380struct Row6;
11381
11382struct Row8;
11383struct Row9;
11384struct Row10;"#};
11385
11386 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
11387 assert_hunk_revert(
11388 indoc! {r#"struct Row;
11389 struct Row2;
11390
11391 ˇstruct Row4;
11392 struct Row5;
11393 struct Row6;
11394 ˇ
11395 struct Row8;
11396 struct Row10;"#},
11397 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11398 indoc! {r#"struct Row;
11399 struct Row2;
11400
11401 ˇstruct Row4;
11402 struct Row5;
11403 struct Row6;
11404 ˇ
11405 struct Row8;
11406 struct Row10;"#},
11407 base_text,
11408 &mut cx,
11409 );
11410 assert_hunk_revert(
11411 indoc! {r#"struct Row;
11412 struct Row2;
11413
11414 «ˇstruct Row4;
11415 struct» Row5;
11416 «struct Row6;
11417 ˇ»
11418 struct Row8;
11419 struct Row10;"#},
11420 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11421 indoc! {r#"struct Row;
11422 struct Row2;
11423
11424 «ˇstruct Row4;
11425 struct» Row5;
11426 «struct Row6;
11427 ˇ»
11428 struct Row8;
11429 struct Row10;"#},
11430 base_text,
11431 &mut cx,
11432 );
11433
11434 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
11435 assert_hunk_revert(
11436 indoc! {r#"struct Row;
11437 ˇstruct Row2;
11438
11439 struct Row4;
11440 struct Row5;
11441 struct Row6;
11442
11443 struct Row8;ˇ
11444 struct Row10;"#},
11445 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
11446 indoc! {r#"struct Row;
11447 struct Row1;
11448 ˇstruct Row2;
11449
11450 struct Row4;
11451 struct Row5;
11452 struct Row6;
11453
11454 struct Row8;ˇ
11455 struct Row9;
11456 struct Row10;"#},
11457 base_text,
11458 &mut cx,
11459 );
11460 assert_hunk_revert(
11461 indoc! {r#"struct Row;
11462 struct Row2«ˇ;
11463 struct Row4;
11464 struct» Row5;
11465 «struct Row6;
11466
11467 struct Row8;ˇ»
11468 struct Row10;"#},
11469 vec![
11470 DiffHunkStatus::Removed,
11471 DiffHunkStatus::Removed,
11472 DiffHunkStatus::Removed,
11473 ],
11474 indoc! {r#"struct Row;
11475 struct Row1;
11476 struct Row2«ˇ;
11477
11478 struct Row4;
11479 struct» Row5;
11480 «struct Row6;
11481
11482 struct Row8;ˇ»
11483 struct Row9;
11484 struct Row10;"#},
11485 base_text,
11486 &mut cx,
11487 );
11488}
11489
11490#[gpui::test]
11491async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
11492 init_test(cx, |_| {});
11493
11494 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
11495 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
11496 let base_text_3 =
11497 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
11498
11499 let text_1 = edit_first_char_of_every_line(base_text_1);
11500 let text_2 = edit_first_char_of_every_line(base_text_2);
11501 let text_3 = edit_first_char_of_every_line(base_text_3);
11502
11503 let buffer_1 = cx.new_model(|cx| Buffer::local(text_1.clone(), cx));
11504 let buffer_2 = cx.new_model(|cx| Buffer::local(text_2.clone(), cx));
11505 let buffer_3 = cx.new_model(|cx| Buffer::local(text_3.clone(), cx));
11506
11507 let multibuffer = cx.new_model(|cx| {
11508 let mut multibuffer = MultiBuffer::new(ReadWrite);
11509 multibuffer.push_excerpts(
11510 buffer_1.clone(),
11511 [
11512 ExcerptRange {
11513 context: Point::new(0, 0)..Point::new(3, 0),
11514 primary: None,
11515 },
11516 ExcerptRange {
11517 context: Point::new(5, 0)..Point::new(7, 0),
11518 primary: None,
11519 },
11520 ExcerptRange {
11521 context: Point::new(9, 0)..Point::new(10, 4),
11522 primary: None,
11523 },
11524 ],
11525 cx,
11526 );
11527 multibuffer.push_excerpts(
11528 buffer_2.clone(),
11529 [
11530 ExcerptRange {
11531 context: Point::new(0, 0)..Point::new(3, 0),
11532 primary: None,
11533 },
11534 ExcerptRange {
11535 context: Point::new(5, 0)..Point::new(7, 0),
11536 primary: None,
11537 },
11538 ExcerptRange {
11539 context: Point::new(9, 0)..Point::new(10, 4),
11540 primary: None,
11541 },
11542 ],
11543 cx,
11544 );
11545 multibuffer.push_excerpts(
11546 buffer_3.clone(),
11547 [
11548 ExcerptRange {
11549 context: Point::new(0, 0)..Point::new(3, 0),
11550 primary: None,
11551 },
11552 ExcerptRange {
11553 context: Point::new(5, 0)..Point::new(7, 0),
11554 primary: None,
11555 },
11556 ExcerptRange {
11557 context: Point::new(9, 0)..Point::new(10, 4),
11558 primary: None,
11559 },
11560 ],
11561 cx,
11562 );
11563 multibuffer
11564 });
11565
11566 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
11567 editor.update(cx, |editor, cx| {
11568 for (buffer, diff_base) in [
11569 (buffer_1.clone(), base_text_1),
11570 (buffer_2.clone(), base_text_2),
11571 (buffer_3.clone(), base_text_3),
11572 ] {
11573 let change_set = cx.new_model(|cx| {
11574 BufferChangeSet::new_with_base_text(
11575 diff_base.to_string(),
11576 buffer.read(cx).text_snapshot(),
11577 cx,
11578 )
11579 });
11580 editor.diff_map.add_change_set(change_set, cx)
11581 }
11582 });
11583 cx.executor().run_until_parked();
11584
11585 editor.update(cx, |editor, cx| {
11586 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}");
11587 editor.select_all(&SelectAll, cx);
11588 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11589 });
11590 cx.executor().run_until_parked();
11591
11592 // When all ranges are selected, all buffer hunks are reverted.
11593 editor.update(cx, |editor, cx| {
11594 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");
11595 });
11596 buffer_1.update(cx, |buffer, _| {
11597 assert_eq!(buffer.text(), base_text_1);
11598 });
11599 buffer_2.update(cx, |buffer, _| {
11600 assert_eq!(buffer.text(), base_text_2);
11601 });
11602 buffer_3.update(cx, |buffer, _| {
11603 assert_eq!(buffer.text(), base_text_3);
11604 });
11605
11606 editor.update(cx, |editor, cx| {
11607 editor.undo(&Default::default(), cx);
11608 });
11609
11610 editor.update(cx, |editor, cx| {
11611 editor.change_selections(None, cx, |s| {
11612 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
11613 });
11614 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
11615 });
11616
11617 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
11618 // but not affect buffer_2 and its related excerpts.
11619 editor.update(cx, |editor, cx| {
11620 assert_eq!(
11621 editor.text(cx),
11622 "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}"
11623 );
11624 });
11625 buffer_1.update(cx, |buffer, _| {
11626 assert_eq!(buffer.text(), base_text_1);
11627 });
11628 buffer_2.update(cx, |buffer, _| {
11629 assert_eq!(
11630 buffer.text(),
11631 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
11632 );
11633 });
11634 buffer_3.update(cx, |buffer, _| {
11635 assert_eq!(
11636 buffer.text(),
11637 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
11638 );
11639 });
11640
11641 fn edit_first_char_of_every_line(text: &str) -> String {
11642 text.split('\n')
11643 .map(|line| format!("X{}", &line[1..]))
11644 .collect::<Vec<_>>()
11645 .join("\n")
11646 }
11647}
11648
11649#[gpui::test]
11650async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
11651 init_test(cx, |_| {});
11652
11653 let cols = 4;
11654 let rows = 10;
11655 let sample_text_1 = sample_text(rows, cols, 'a');
11656 assert_eq!(
11657 sample_text_1,
11658 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11659 );
11660 let sample_text_2 = sample_text(rows, cols, 'l');
11661 assert_eq!(
11662 sample_text_2,
11663 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11664 );
11665 let sample_text_3 = sample_text(rows, cols, 'v');
11666 assert_eq!(
11667 sample_text_3,
11668 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11669 );
11670
11671 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
11672 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
11673 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
11674
11675 let multi_buffer = cx.new_model(|cx| {
11676 let mut multibuffer = MultiBuffer::new(ReadWrite);
11677 multibuffer.push_excerpts(
11678 buffer_1.clone(),
11679 [
11680 ExcerptRange {
11681 context: Point::new(0, 0)..Point::new(3, 0),
11682 primary: None,
11683 },
11684 ExcerptRange {
11685 context: Point::new(5, 0)..Point::new(7, 0),
11686 primary: None,
11687 },
11688 ExcerptRange {
11689 context: Point::new(9, 0)..Point::new(10, 4),
11690 primary: None,
11691 },
11692 ],
11693 cx,
11694 );
11695 multibuffer.push_excerpts(
11696 buffer_2.clone(),
11697 [
11698 ExcerptRange {
11699 context: Point::new(0, 0)..Point::new(3, 0),
11700 primary: None,
11701 },
11702 ExcerptRange {
11703 context: Point::new(5, 0)..Point::new(7, 0),
11704 primary: None,
11705 },
11706 ExcerptRange {
11707 context: Point::new(9, 0)..Point::new(10, 4),
11708 primary: None,
11709 },
11710 ],
11711 cx,
11712 );
11713 multibuffer.push_excerpts(
11714 buffer_3.clone(),
11715 [
11716 ExcerptRange {
11717 context: Point::new(0, 0)..Point::new(3, 0),
11718 primary: None,
11719 },
11720 ExcerptRange {
11721 context: Point::new(5, 0)..Point::new(7, 0),
11722 primary: None,
11723 },
11724 ExcerptRange {
11725 context: Point::new(9, 0)..Point::new(10, 4),
11726 primary: None,
11727 },
11728 ],
11729 cx,
11730 );
11731 multibuffer
11732 });
11733
11734 let fs = FakeFs::new(cx.executor());
11735 fs.insert_tree(
11736 "/a",
11737 json!({
11738 "main.rs": sample_text_1,
11739 "other.rs": sample_text_2,
11740 "lib.rs": sample_text_3,
11741 }),
11742 )
11743 .await;
11744 let project = Project::test(fs, ["/a".as_ref()], cx).await;
11745 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
11746 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11747 let multi_buffer_editor = cx.new_view(|cx| {
11748 Editor::new(
11749 EditorMode::Full,
11750 multi_buffer,
11751 Some(project.clone()),
11752 true,
11753 cx,
11754 )
11755 });
11756 let multibuffer_item_id = workspace
11757 .update(cx, |workspace, cx| {
11758 assert!(
11759 workspace.active_item(cx).is_none(),
11760 "active item should be None before the first item is added"
11761 );
11762 workspace.add_item_to_active_pane(
11763 Box::new(multi_buffer_editor.clone()),
11764 None,
11765 true,
11766 cx,
11767 );
11768 let active_item = workspace
11769 .active_item(cx)
11770 .expect("should have an active item after adding the multi buffer");
11771 assert!(
11772 !active_item.is_singleton(cx),
11773 "A multi buffer was expected to active after adding"
11774 );
11775 active_item.item_id()
11776 })
11777 .unwrap();
11778 cx.executor().run_until_parked();
11779
11780 multi_buffer_editor.update(cx, |editor, cx| {
11781 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
11782 editor.open_excerpts(&OpenExcerpts, cx);
11783 });
11784 cx.executor().run_until_parked();
11785 let first_item_id = workspace
11786 .update(cx, |workspace, cx| {
11787 let active_item = workspace
11788 .active_item(cx)
11789 .expect("should have an active item after navigating into the 1st buffer");
11790 let first_item_id = active_item.item_id();
11791 assert_ne!(
11792 first_item_id, multibuffer_item_id,
11793 "Should navigate into the 1st buffer and activate it"
11794 );
11795 assert!(
11796 active_item.is_singleton(cx),
11797 "New active item should be a singleton buffer"
11798 );
11799 assert_eq!(
11800 active_item
11801 .act_as::<Editor>(cx)
11802 .expect("should have navigated into an editor for the 1st buffer")
11803 .read(cx)
11804 .text(cx),
11805 sample_text_1
11806 );
11807
11808 workspace
11809 .go_back(workspace.active_pane().downgrade(), cx)
11810 .detach_and_log_err(cx);
11811
11812 first_item_id
11813 })
11814 .unwrap();
11815 cx.executor().run_until_parked();
11816 workspace
11817 .update(cx, |workspace, cx| {
11818 let active_item = workspace
11819 .active_item(cx)
11820 .expect("should have an active item after navigating back");
11821 assert_eq!(
11822 active_item.item_id(),
11823 multibuffer_item_id,
11824 "Should navigate back to the multi buffer"
11825 );
11826 assert!(!active_item.is_singleton(cx));
11827 })
11828 .unwrap();
11829
11830 multi_buffer_editor.update(cx, |editor, cx| {
11831 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11832 s.select_ranges(Some(39..40))
11833 });
11834 editor.open_excerpts(&OpenExcerpts, cx);
11835 });
11836 cx.executor().run_until_parked();
11837 let second_item_id = workspace
11838 .update(cx, |workspace, cx| {
11839 let active_item = workspace
11840 .active_item(cx)
11841 .expect("should have an active item after navigating into the 2nd buffer");
11842 let second_item_id = active_item.item_id();
11843 assert_ne!(
11844 second_item_id, multibuffer_item_id,
11845 "Should navigate away from the multibuffer"
11846 );
11847 assert_ne!(
11848 second_item_id, first_item_id,
11849 "Should navigate into the 2nd buffer and activate it"
11850 );
11851 assert!(
11852 active_item.is_singleton(cx),
11853 "New active item should be a singleton buffer"
11854 );
11855 assert_eq!(
11856 active_item
11857 .act_as::<Editor>(cx)
11858 .expect("should have navigated into an editor")
11859 .read(cx)
11860 .text(cx),
11861 sample_text_2
11862 );
11863
11864 workspace
11865 .go_back(workspace.active_pane().downgrade(), cx)
11866 .detach_and_log_err(cx);
11867
11868 second_item_id
11869 })
11870 .unwrap();
11871 cx.executor().run_until_parked();
11872 workspace
11873 .update(cx, |workspace, cx| {
11874 let active_item = workspace
11875 .active_item(cx)
11876 .expect("should have an active item after navigating back from the 2nd buffer");
11877 assert_eq!(
11878 active_item.item_id(),
11879 multibuffer_item_id,
11880 "Should navigate back from the 2nd buffer to the multi buffer"
11881 );
11882 assert!(!active_item.is_singleton(cx));
11883 })
11884 .unwrap();
11885
11886 multi_buffer_editor.update(cx, |editor, cx| {
11887 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
11888 s.select_ranges(Some(70..70))
11889 });
11890 editor.open_excerpts(&OpenExcerpts, cx);
11891 });
11892 cx.executor().run_until_parked();
11893 workspace
11894 .update(cx, |workspace, cx| {
11895 let active_item = workspace
11896 .active_item(cx)
11897 .expect("should have an active item after navigating into the 3rd buffer");
11898 let third_item_id = active_item.item_id();
11899 assert_ne!(
11900 third_item_id, multibuffer_item_id,
11901 "Should navigate into the 3rd buffer and activate it"
11902 );
11903 assert_ne!(third_item_id, first_item_id);
11904 assert_ne!(third_item_id, second_item_id);
11905 assert!(
11906 active_item.is_singleton(cx),
11907 "New active item should be a singleton buffer"
11908 );
11909 assert_eq!(
11910 active_item
11911 .act_as::<Editor>(cx)
11912 .expect("should have navigated into an editor")
11913 .read(cx)
11914 .text(cx),
11915 sample_text_3
11916 );
11917
11918 workspace
11919 .go_back(workspace.active_pane().downgrade(), cx)
11920 .detach_and_log_err(cx);
11921 })
11922 .unwrap();
11923 cx.executor().run_until_parked();
11924 workspace
11925 .update(cx, |workspace, cx| {
11926 let active_item = workspace
11927 .active_item(cx)
11928 .expect("should have an active item after navigating back from the 3rd buffer");
11929 assert_eq!(
11930 active_item.item_id(),
11931 multibuffer_item_id,
11932 "Should navigate back from the 3rd buffer to the multi buffer"
11933 );
11934 assert!(!active_item.is_singleton(cx));
11935 })
11936 .unwrap();
11937}
11938
11939#[gpui::test]
11940async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
11941 init_test(cx, |_| {});
11942
11943 let mut cx = EditorTestContext::new(cx).await;
11944
11945 let diff_base = r#"
11946 use some::mod;
11947
11948 const A: u32 = 42;
11949
11950 fn main() {
11951 println!("hello");
11952
11953 println!("world");
11954 }
11955 "#
11956 .unindent();
11957
11958 cx.set_state(
11959 &r#"
11960 use some::modified;
11961
11962 ˇ
11963 fn main() {
11964 println!("hello there");
11965
11966 println!("around the");
11967 println!("world");
11968 }
11969 "#
11970 .unindent(),
11971 );
11972
11973 cx.set_diff_base(&diff_base);
11974 executor.run_until_parked();
11975
11976 cx.update_editor(|editor, cx| {
11977 editor.go_to_next_hunk(&GoToHunk, cx);
11978 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
11979 });
11980 executor.run_until_parked();
11981 cx.assert_state_with_diff(
11982 r#"
11983 use some::modified;
11984
11985
11986 fn main() {
11987 - println!("hello");
11988 + ˇ println!("hello there");
11989
11990 println!("around the");
11991 println!("world");
11992 }
11993 "#
11994 .unindent(),
11995 );
11996
11997 cx.update_editor(|editor, cx| {
11998 for _ in 0..3 {
11999 editor.go_to_next_hunk(&GoToHunk, cx);
12000 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12001 }
12002 });
12003 executor.run_until_parked();
12004 cx.assert_state_with_diff(
12005 r#"
12006 - use some::mod;
12007 + use some::modified;
12008
12009 - const A: u32 = 42;
12010 ˇ
12011 fn main() {
12012 - println!("hello");
12013 + println!("hello there");
12014
12015 + println!("around the");
12016 println!("world");
12017 }
12018 "#
12019 .unindent(),
12020 );
12021
12022 cx.update_editor(|editor, cx| {
12023 editor.cancel(&Cancel, cx);
12024 });
12025
12026 cx.assert_state_with_diff(
12027 r#"
12028 use some::modified;
12029
12030 ˇ
12031 fn main() {
12032 println!("hello there");
12033
12034 println!("around the");
12035 println!("world");
12036 }
12037 "#
12038 .unindent(),
12039 );
12040}
12041
12042#[gpui::test]
12043async fn test_diff_base_change_with_expanded_diff_hunks(
12044 executor: BackgroundExecutor,
12045 cx: &mut gpui::TestAppContext,
12046) {
12047 init_test(cx, |_| {});
12048
12049 let mut cx = EditorTestContext::new(cx).await;
12050
12051 let diff_base = r#"
12052 use some::mod1;
12053 use some::mod2;
12054
12055 const A: u32 = 42;
12056 const B: u32 = 42;
12057 const C: u32 = 42;
12058
12059 fn main() {
12060 println!("hello");
12061
12062 println!("world");
12063 }
12064 "#
12065 .unindent();
12066
12067 cx.set_state(
12068 &r#"
12069 use some::mod2;
12070
12071 const A: u32 = 42;
12072 const C: u32 = 42;
12073
12074 fn main(ˇ) {
12075 //println!("hello");
12076
12077 println!("world");
12078 //
12079 //
12080 }
12081 "#
12082 .unindent(),
12083 );
12084
12085 cx.set_diff_base(&diff_base);
12086 executor.run_until_parked();
12087
12088 cx.update_editor(|editor, cx| {
12089 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12090 });
12091 executor.run_until_parked();
12092 cx.assert_state_with_diff(
12093 r#"
12094 - use some::mod1;
12095 use some::mod2;
12096
12097 const A: u32 = 42;
12098 - const B: u32 = 42;
12099 const C: u32 = 42;
12100
12101 fn main(ˇ) {
12102 - println!("hello");
12103 + //println!("hello");
12104
12105 println!("world");
12106 + //
12107 + //
12108 }
12109 "#
12110 .unindent(),
12111 );
12112
12113 cx.set_diff_base("new diff base!");
12114 executor.run_until_parked();
12115 cx.assert_state_with_diff(
12116 r#"
12117 use some::mod2;
12118
12119 const A: u32 = 42;
12120 const C: u32 = 42;
12121
12122 fn main(ˇ) {
12123 //println!("hello");
12124
12125 println!("world");
12126 //
12127 //
12128 }
12129 "#
12130 .unindent(),
12131 );
12132
12133 cx.update_editor(|editor, cx| {
12134 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12135 });
12136 executor.run_until_parked();
12137 cx.assert_state_with_diff(
12138 r#"
12139 - new diff base!
12140 + use some::mod2;
12141 +
12142 + const A: u32 = 42;
12143 + const C: u32 = 42;
12144 +
12145 + fn main(ˇ) {
12146 + //println!("hello");
12147 +
12148 + println!("world");
12149 + //
12150 + //
12151 + }
12152 "#
12153 .unindent(),
12154 );
12155}
12156
12157#[gpui::test]
12158async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
12159 init_test(cx, |_| {});
12160
12161 let mut cx = EditorTestContext::new(cx).await;
12162
12163 let diff_base = r#"
12164 use some::mod1;
12165 use some::mod2;
12166
12167 const A: u32 = 42;
12168 const B: u32 = 42;
12169 const C: u32 = 42;
12170
12171 fn main() {
12172 println!("hello");
12173
12174 println!("world");
12175 }
12176
12177 fn another() {
12178 println!("another");
12179 }
12180
12181 fn another2() {
12182 println!("another2");
12183 }
12184 "#
12185 .unindent();
12186
12187 cx.set_state(
12188 &r#"
12189 «use some::mod2;
12190
12191 const A: u32 = 42;
12192 const C: u32 = 42;
12193
12194 fn main() {
12195 //println!("hello");
12196
12197 println!("world");
12198 //
12199 //ˇ»
12200 }
12201
12202 fn another() {
12203 println!("another");
12204 println!("another");
12205 }
12206
12207 println!("another2");
12208 }
12209 "#
12210 .unindent(),
12211 );
12212
12213 cx.set_diff_base(&diff_base);
12214 executor.run_until_parked();
12215
12216 cx.update_editor(|editor, cx| {
12217 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12218 });
12219 executor.run_until_parked();
12220
12221 cx.assert_state_with_diff(
12222 r#"
12223 - use some::mod1;
12224 «use some::mod2;
12225
12226 const A: u32 = 42;
12227 - const B: u32 = 42;
12228 const C: u32 = 42;
12229
12230 fn main() {
12231 - println!("hello");
12232 + //println!("hello");
12233
12234 println!("world");
12235 + //
12236 + //ˇ»
12237 }
12238
12239 fn another() {
12240 println!("another");
12241 + println!("another");
12242 }
12243
12244 - fn another2() {
12245 println!("another2");
12246 }
12247 "#
12248 .unindent(),
12249 );
12250
12251 // Fold across some of the diff hunks. They should no longer appear expanded.
12252 cx.update_editor(|editor, cx| editor.fold_selected_ranges(&FoldSelectedRanges, cx));
12253 cx.executor().run_until_parked();
12254
12255 // Hunks are not shown if their position is within a fold
12256 cx.assert_state_with_diff(
12257 r#"
12258 «use some::mod2;
12259
12260 const A: u32 = 42;
12261 const C: u32 = 42;
12262
12263 fn main() {
12264 //println!("hello");
12265
12266 println!("world");
12267 //
12268 //ˇ»
12269 }
12270
12271 fn another() {
12272 println!("another");
12273 + println!("another");
12274 }
12275
12276 - fn another2() {
12277 println!("another2");
12278 }
12279 "#
12280 .unindent(),
12281 );
12282
12283 cx.update_editor(|editor, cx| {
12284 editor.select_all(&SelectAll, cx);
12285 editor.unfold_lines(&UnfoldLines, cx);
12286 });
12287 cx.executor().run_until_parked();
12288
12289 // The deletions reappear when unfolding.
12290 cx.assert_state_with_diff(
12291 r#"
12292 - use some::mod1;
12293 «use some::mod2;
12294
12295 const A: u32 = 42;
12296 - const B: u32 = 42;
12297 const C: u32 = 42;
12298
12299 fn main() {
12300 - println!("hello");
12301 + //println!("hello");
12302
12303 println!("world");
12304 + //
12305 + //
12306 }
12307
12308 fn another() {
12309 println!("another");
12310 + println!("another");
12311 }
12312
12313 - fn another2() {
12314 println!("another2");
12315 }
12316 ˇ»"#
12317 .unindent(),
12318 );
12319}
12320
12321#[gpui::test]
12322async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
12323 init_test(cx, |_| {});
12324
12325 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12326 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12327 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12328 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12329 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
12330 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
12331
12332 let buffer_1 = cx.new_model(|cx| Buffer::local(file_1_new.to_string(), cx));
12333 let buffer_2 = cx.new_model(|cx| Buffer::local(file_2_new.to_string(), cx));
12334 let buffer_3 = cx.new_model(|cx| Buffer::local(file_3_new.to_string(), cx));
12335
12336 let multi_buffer = cx.new_model(|cx| {
12337 let mut multibuffer = MultiBuffer::new(ReadWrite);
12338 multibuffer.push_excerpts(
12339 buffer_1.clone(),
12340 [
12341 ExcerptRange {
12342 context: Point::new(0, 0)..Point::new(3, 0),
12343 primary: None,
12344 },
12345 ExcerptRange {
12346 context: Point::new(5, 0)..Point::new(7, 0),
12347 primary: None,
12348 },
12349 ExcerptRange {
12350 context: Point::new(9, 0)..Point::new(10, 3),
12351 primary: None,
12352 },
12353 ],
12354 cx,
12355 );
12356 multibuffer.push_excerpts(
12357 buffer_2.clone(),
12358 [
12359 ExcerptRange {
12360 context: Point::new(0, 0)..Point::new(3, 0),
12361 primary: None,
12362 },
12363 ExcerptRange {
12364 context: Point::new(5, 0)..Point::new(7, 0),
12365 primary: None,
12366 },
12367 ExcerptRange {
12368 context: Point::new(9, 0)..Point::new(10, 3),
12369 primary: None,
12370 },
12371 ],
12372 cx,
12373 );
12374 multibuffer.push_excerpts(
12375 buffer_3.clone(),
12376 [
12377 ExcerptRange {
12378 context: Point::new(0, 0)..Point::new(3, 0),
12379 primary: None,
12380 },
12381 ExcerptRange {
12382 context: Point::new(5, 0)..Point::new(7, 0),
12383 primary: None,
12384 },
12385 ExcerptRange {
12386 context: Point::new(9, 0)..Point::new(10, 3),
12387 primary: None,
12388 },
12389 ],
12390 cx,
12391 );
12392 multibuffer
12393 });
12394
12395 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12396 editor
12397 .update(cx, |editor, cx| {
12398 for (buffer, diff_base) in [
12399 (buffer_1.clone(), file_1_old),
12400 (buffer_2.clone(), file_2_old),
12401 (buffer_3.clone(), file_3_old),
12402 ] {
12403 let change_set = cx.new_model(|cx| {
12404 BufferChangeSet::new_with_base_text(
12405 diff_base.to_string(),
12406 buffer.read(cx).text_snapshot(),
12407 cx,
12408 )
12409 });
12410 editor.diff_map.add_change_set(change_set, cx)
12411 }
12412 })
12413 .unwrap();
12414
12415 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12416 cx.run_until_parked();
12417
12418 cx.assert_editor_state(
12419 &"
12420 ˇaaa
12421 ccc
12422 ddd
12423
12424 ggg
12425 hhh
12426
12427
12428 lll
12429 mmm
12430 NNN
12431
12432 qqq
12433 rrr
12434
12435 uuu
12436 111
12437 222
12438 333
12439
12440 666
12441 777
12442
12443 000
12444 !!!"
12445 .unindent(),
12446 );
12447
12448 cx.update_editor(|editor, cx| {
12449 editor.select_all(&SelectAll, cx);
12450 editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
12451 });
12452 cx.executor().run_until_parked();
12453
12454 cx.assert_state_with_diff(
12455 "
12456 «aaa
12457 - bbb
12458 ccc
12459 ddd
12460
12461 ggg
12462 hhh
12463
12464
12465 lll
12466 mmm
12467 - nnn
12468 + NNN
12469
12470 qqq
12471 rrr
12472
12473 uuu
12474 111
12475 222
12476 333
12477
12478 + 666
12479 777
12480
12481 000
12482 !!!ˇ»"
12483 .unindent(),
12484 );
12485}
12486
12487#[gpui::test]
12488async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
12489 init_test(cx, |_| {});
12490
12491 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
12492 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
12493
12494 let buffer = cx.new_model(|cx| Buffer::local(text.to_string(), cx));
12495 let multi_buffer = cx.new_model(|cx| {
12496 let mut multibuffer = MultiBuffer::new(ReadWrite);
12497 multibuffer.push_excerpts(
12498 buffer.clone(),
12499 [
12500 ExcerptRange {
12501 context: Point::new(0, 0)..Point::new(2, 0),
12502 primary: None,
12503 },
12504 ExcerptRange {
12505 context: Point::new(5, 0)..Point::new(7, 0),
12506 primary: None,
12507 },
12508 ],
12509 cx,
12510 );
12511 multibuffer
12512 });
12513
12514 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12515 editor
12516 .update(cx, |editor, cx| {
12517 let buffer = buffer.read(cx).text_snapshot();
12518 let change_set = cx
12519 .new_model(|cx| BufferChangeSet::new_with_base_text(base.to_string(), buffer, cx));
12520 editor.diff_map.add_change_set(change_set, cx)
12521 })
12522 .unwrap();
12523
12524 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12525 cx.run_until_parked();
12526
12527 cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
12528 cx.executor().run_until_parked();
12529
12530 cx.assert_state_with_diff(
12531 "
12532 ˇaaa
12533 - bbb
12534 + BBB
12535
12536 - ddd
12537 - eee
12538 + EEE
12539 fff
12540 "
12541 .unindent(),
12542 );
12543}
12544
12545#[gpui::test]
12546async fn test_edits_around_expanded_insertion_hunks(
12547 executor: BackgroundExecutor,
12548 cx: &mut gpui::TestAppContext,
12549) {
12550 init_test(cx, |_| {});
12551
12552 let mut cx = EditorTestContext::new(cx).await;
12553
12554 let diff_base = r#"
12555 use some::mod1;
12556 use some::mod2;
12557
12558 const A: u32 = 42;
12559
12560 fn main() {
12561 println!("hello");
12562
12563 println!("world");
12564 }
12565 "#
12566 .unindent();
12567 executor.run_until_parked();
12568 cx.set_state(
12569 &r#"
12570 use some::mod1;
12571 use some::mod2;
12572
12573 const A: u32 = 42;
12574 const B: u32 = 42;
12575 const C: u32 = 42;
12576 ˇ
12577
12578 fn main() {
12579 println!("hello");
12580
12581 println!("world");
12582 }
12583 "#
12584 .unindent(),
12585 );
12586
12587 cx.set_diff_base(&diff_base);
12588 executor.run_until_parked();
12589
12590 cx.update_editor(|editor, cx| {
12591 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12592 });
12593 executor.run_until_parked();
12594
12595 cx.assert_state_with_diff(
12596 r#"
12597 use some::mod1;
12598 use some::mod2;
12599
12600 const A: u32 = 42;
12601 + const B: u32 = 42;
12602 + const C: u32 = 42;
12603 + ˇ
12604
12605 fn main() {
12606 println!("hello");
12607
12608 println!("world");
12609 }
12610 "#
12611 .unindent(),
12612 );
12613
12614 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
12615 executor.run_until_parked();
12616
12617 cx.assert_state_with_diff(
12618 r#"
12619 use some::mod1;
12620 use some::mod2;
12621
12622 const A: u32 = 42;
12623 + const B: u32 = 42;
12624 + const C: u32 = 42;
12625 + const D: u32 = 42;
12626 + ˇ
12627
12628 fn main() {
12629 println!("hello");
12630
12631 println!("world");
12632 }
12633 "#
12634 .unindent(),
12635 );
12636
12637 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
12638 executor.run_until_parked();
12639
12640 cx.assert_state_with_diff(
12641 r#"
12642 use some::mod1;
12643 use some::mod2;
12644
12645 const A: u32 = 42;
12646 + const B: u32 = 42;
12647 + const C: u32 = 42;
12648 + const D: u32 = 42;
12649 + const E: u32 = 42;
12650 + ˇ
12651
12652 fn main() {
12653 println!("hello");
12654
12655 println!("world");
12656 }
12657 "#
12658 .unindent(),
12659 );
12660
12661 cx.update_editor(|editor, cx| {
12662 editor.delete_line(&DeleteLine, cx);
12663 });
12664 executor.run_until_parked();
12665
12666 cx.assert_state_with_diff(
12667 r#"
12668 use some::mod1;
12669 use some::mod2;
12670
12671 const A: u32 = 42;
12672 + const B: u32 = 42;
12673 + const C: u32 = 42;
12674 + const D: u32 = 42;
12675 + const E: u32 = 42;
12676 ˇ
12677 fn main() {
12678 println!("hello");
12679
12680 println!("world");
12681 }
12682 "#
12683 .unindent(),
12684 );
12685
12686 cx.update_editor(|editor, cx| {
12687 editor.move_up(&MoveUp, cx);
12688 editor.delete_line(&DeleteLine, 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 });
12694 executor.run_until_parked();
12695 cx.assert_state_with_diff(
12696 r#"
12697 use some::mod1;
12698 use some::mod2;
12699
12700 const A: u32 = 42;
12701 + const B: u32 = 42;
12702 ˇ
12703 fn main() {
12704 println!("hello");
12705
12706 println!("world");
12707 }
12708 "#
12709 .unindent(),
12710 );
12711
12712 cx.update_editor(|editor, cx| {
12713 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
12714 editor.delete_line(&DeleteLine, cx);
12715 });
12716 executor.run_until_parked();
12717 cx.assert_state_with_diff(
12718 r#"
12719 use some::mod1;
12720 - use some::mod2;
12721 -
12722 - const A: u32 = 42;
12723 ˇ
12724 fn main() {
12725 println!("hello");
12726
12727 println!("world");
12728 }
12729 "#
12730 .unindent(),
12731 );
12732}
12733
12734#[gpui::test]
12735async fn test_edits_around_expanded_deletion_hunks(
12736 executor: BackgroundExecutor,
12737 cx: &mut gpui::TestAppContext,
12738) {
12739 init_test(cx, |_| {});
12740
12741 let mut cx = EditorTestContext::new(cx).await;
12742
12743 let diff_base = r#"
12744 use some::mod1;
12745 use some::mod2;
12746
12747 const A: u32 = 42;
12748 const B: u32 = 42;
12749 const C: u32 = 42;
12750
12751
12752 fn main() {
12753 println!("hello");
12754
12755 println!("world");
12756 }
12757 "#
12758 .unindent();
12759 executor.run_until_parked();
12760 cx.set_state(
12761 &r#"
12762 use some::mod1;
12763 use some::mod2;
12764
12765 ˇconst B: u32 = 42;
12766 const C: u32 = 42;
12767
12768
12769 fn main() {
12770 println!("hello");
12771
12772 println!("world");
12773 }
12774 "#
12775 .unindent(),
12776 );
12777
12778 cx.set_diff_base(&diff_base);
12779 executor.run_until_parked();
12780
12781 cx.update_editor(|editor, cx| {
12782 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12783 });
12784 executor.run_until_parked();
12785
12786 cx.assert_state_with_diff(
12787 r#"
12788 use some::mod1;
12789 use some::mod2;
12790
12791 - const A: u32 = 42;
12792 ˇconst B: u32 = 42;
12793 const C: u32 = 42;
12794
12795
12796 fn main() {
12797 println!("hello");
12798
12799 println!("world");
12800 }
12801 "#
12802 .unindent(),
12803 );
12804
12805 cx.update_editor(|editor, cx| {
12806 editor.delete_line(&DeleteLine, cx);
12807 });
12808 executor.run_until_parked();
12809 cx.assert_state_with_diff(
12810 r#"
12811 use some::mod1;
12812 use some::mod2;
12813
12814 - const A: u32 = 42;
12815 - const B: u32 = 42;
12816 ˇconst C: u32 = 42;
12817
12818
12819 fn main() {
12820 println!("hello");
12821
12822 println!("world");
12823 }
12824 "#
12825 .unindent(),
12826 );
12827
12828 cx.update_editor(|editor, cx| {
12829 editor.delete_line(&DeleteLine, cx);
12830 });
12831 executor.run_until_parked();
12832 cx.assert_state_with_diff(
12833 r#"
12834 use some::mod1;
12835 use some::mod2;
12836
12837 - const A: u32 = 42;
12838 - const B: u32 = 42;
12839 - const C: u32 = 42;
12840 ˇ
12841
12842 fn main() {
12843 println!("hello");
12844
12845 println!("world");
12846 }
12847 "#
12848 .unindent(),
12849 );
12850
12851 cx.update_editor(|editor, cx| {
12852 editor.handle_input("replacement", cx);
12853 });
12854 executor.run_until_parked();
12855 cx.assert_state_with_diff(
12856 r#"
12857 use some::mod1;
12858 use some::mod2;
12859
12860 - const A: u32 = 42;
12861 - const B: u32 = 42;
12862 - const C: u32 = 42;
12863 -
12864 + replacementˇ
12865
12866 fn main() {
12867 println!("hello");
12868
12869 println!("world");
12870 }
12871 "#
12872 .unindent(),
12873 );
12874}
12875
12876#[gpui::test]
12877async fn test_edit_after_expanded_modification_hunk(
12878 executor: BackgroundExecutor,
12879 cx: &mut gpui::TestAppContext,
12880) {
12881 init_test(cx, |_| {});
12882
12883 let mut cx = EditorTestContext::new(cx).await;
12884
12885 let diff_base = r#"
12886 use some::mod1;
12887 use some::mod2;
12888
12889 const A: u32 = 42;
12890 const B: u32 = 42;
12891 const C: u32 = 42;
12892 const D: u32 = 42;
12893
12894
12895 fn main() {
12896 println!("hello");
12897
12898 println!("world");
12899 }"#
12900 .unindent();
12901
12902 cx.set_state(
12903 &r#"
12904 use some::mod1;
12905 use some::mod2;
12906
12907 const A: u32 = 42;
12908 const B: u32 = 42;
12909 const C: u32 = 43ˇ
12910 const D: u32 = 42;
12911
12912
12913 fn main() {
12914 println!("hello");
12915
12916 println!("world");
12917 }"#
12918 .unindent(),
12919 );
12920
12921 cx.set_diff_base(&diff_base);
12922 executor.run_until_parked();
12923 cx.update_editor(|editor, cx| {
12924 editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
12925 });
12926 executor.run_until_parked();
12927
12928 cx.assert_state_with_diff(
12929 r#"
12930 use some::mod1;
12931 use some::mod2;
12932
12933 const A: u32 = 42;
12934 const B: u32 = 42;
12935 - const C: u32 = 42;
12936 + const C: u32 = 43ˇ
12937 const D: u32 = 42;
12938
12939
12940 fn main() {
12941 println!("hello");
12942
12943 println!("world");
12944 }"#
12945 .unindent(),
12946 );
12947
12948 cx.update_editor(|editor, cx| {
12949 editor.handle_input("\nnew_line\n", cx);
12950 });
12951 executor.run_until_parked();
12952
12953 cx.assert_state_with_diff(
12954 r#"
12955 use some::mod1;
12956 use some::mod2;
12957
12958 const A: u32 = 42;
12959 const B: u32 = 42;
12960 - const C: u32 = 42;
12961 + const C: u32 = 43
12962 + new_line
12963 + ˇ
12964 const D: u32 = 42;
12965
12966
12967 fn main() {
12968 println!("hello");
12969
12970 println!("world");
12971 }"#
12972 .unindent(),
12973 );
12974}
12975
12976async fn setup_indent_guides_editor(
12977 text: &str,
12978 cx: &mut gpui::TestAppContext,
12979) -> (BufferId, EditorTestContext) {
12980 init_test(cx, |_| {});
12981
12982 let mut cx = EditorTestContext::new(cx).await;
12983
12984 let buffer_id = cx.update_editor(|editor, cx| {
12985 editor.set_text(text, cx);
12986 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
12987
12988 buffer_ids[0]
12989 });
12990
12991 (buffer_id, cx)
12992}
12993
12994fn assert_indent_guides(
12995 range: Range<u32>,
12996 expected: Vec<IndentGuide>,
12997 active_indices: Option<Vec<usize>>,
12998 cx: &mut EditorTestContext,
12999) {
13000 let indent_guides = cx.update_editor(|editor, cx| {
13001 let snapshot = editor.snapshot(cx).display_snapshot;
13002 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
13003 MultiBufferRow(range.start)..MultiBufferRow(range.end),
13004 true,
13005 &snapshot,
13006 cx,
13007 );
13008
13009 indent_guides.sort_by(|a, b| {
13010 a.depth.cmp(&b.depth).then(
13011 a.start_row
13012 .cmp(&b.start_row)
13013 .then(a.end_row.cmp(&b.end_row)),
13014 )
13015 });
13016 indent_guides
13017 });
13018
13019 if let Some(expected) = active_indices {
13020 let active_indices = cx.update_editor(|editor, cx| {
13021 let snapshot = editor.snapshot(cx).display_snapshot;
13022 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
13023 });
13024
13025 assert_eq!(
13026 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
13027 expected,
13028 "Active indent guide indices do not match"
13029 );
13030 }
13031
13032 let expected: Vec<_> = expected
13033 .into_iter()
13034 .map(|guide| MultiBufferIndentGuide {
13035 multibuffer_row_range: MultiBufferRow(guide.start_row)..MultiBufferRow(guide.end_row),
13036 buffer: guide,
13037 })
13038 .collect();
13039
13040 assert_eq!(indent_guides, expected, "Indent guides do not match");
13041}
13042
13043fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
13044 IndentGuide {
13045 buffer_id,
13046 start_row,
13047 end_row,
13048 depth,
13049 tab_size: 4,
13050 settings: IndentGuideSettings {
13051 enabled: true,
13052 line_width: 1,
13053 active_line_width: 1,
13054 ..Default::default()
13055 },
13056 }
13057}
13058
13059#[gpui::test]
13060async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13061 let (buffer_id, mut cx) = setup_indent_guides_editor(
13062 &"
13063 fn main() {
13064 let a = 1;
13065 }"
13066 .unindent(),
13067 cx,
13068 )
13069 .await;
13070
13071 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13072}
13073
13074#[gpui::test]
13075async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
13076 let (buffer_id, mut cx) = setup_indent_guides_editor(
13077 &"
13078 fn main() {
13079 let a = 1;
13080 let b = 2;
13081 }"
13082 .unindent(),
13083 cx,
13084 )
13085 .await;
13086
13087 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
13088}
13089
13090#[gpui::test]
13091async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
13092 let (buffer_id, mut cx) = setup_indent_guides_editor(
13093 &"
13094 fn main() {
13095 let a = 1;
13096 if a == 3 {
13097 let b = 2;
13098 } else {
13099 let c = 3;
13100 }
13101 }"
13102 .unindent(),
13103 cx,
13104 )
13105 .await;
13106
13107 assert_indent_guides(
13108 0..8,
13109 vec![
13110 indent_guide(buffer_id, 1, 6, 0),
13111 indent_guide(buffer_id, 3, 3, 1),
13112 indent_guide(buffer_id, 5, 5, 1),
13113 ],
13114 None,
13115 &mut cx,
13116 );
13117}
13118
13119#[gpui::test]
13120async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
13121 let (buffer_id, mut cx) = setup_indent_guides_editor(
13122 &"
13123 fn main() {
13124 let a = 1;
13125 let b = 2;
13126 let c = 3;
13127 }"
13128 .unindent(),
13129 cx,
13130 )
13131 .await;
13132
13133 assert_indent_guides(
13134 0..5,
13135 vec![
13136 indent_guide(buffer_id, 1, 3, 0),
13137 indent_guide(buffer_id, 2, 2, 1),
13138 ],
13139 None,
13140 &mut cx,
13141 );
13142}
13143
13144#[gpui::test]
13145async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
13146 let (buffer_id, mut cx) = setup_indent_guides_editor(
13147 &"
13148 fn main() {
13149 let a = 1;
13150
13151 let c = 3;
13152 }"
13153 .unindent(),
13154 cx,
13155 )
13156 .await;
13157
13158 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
13159}
13160
13161#[gpui::test]
13162async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
13163 let (buffer_id, mut cx) = setup_indent_guides_editor(
13164 &"
13165 fn main() {
13166 let a = 1;
13167
13168 let c = 3;
13169
13170 if a == 3 {
13171 let b = 2;
13172 } else {
13173 let c = 3;
13174 }
13175 }"
13176 .unindent(),
13177 cx,
13178 )
13179 .await;
13180
13181 assert_indent_guides(
13182 0..11,
13183 vec![
13184 indent_guide(buffer_id, 1, 9, 0),
13185 indent_guide(buffer_id, 6, 6, 1),
13186 indent_guide(buffer_id, 8, 8, 1),
13187 ],
13188 None,
13189 &mut cx,
13190 );
13191}
13192
13193#[gpui::test]
13194async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
13195 let (buffer_id, mut cx) = setup_indent_guides_editor(
13196 &"
13197 fn main() {
13198 let a = 1;
13199
13200 let c = 3;
13201
13202 if a == 3 {
13203 let b = 2;
13204 } else {
13205 let c = 3;
13206 }
13207 }"
13208 .unindent(),
13209 cx,
13210 )
13211 .await;
13212
13213 assert_indent_guides(
13214 1..11,
13215 vec![
13216 indent_guide(buffer_id, 1, 9, 0),
13217 indent_guide(buffer_id, 6, 6, 1),
13218 indent_guide(buffer_id, 8, 8, 1),
13219 ],
13220 None,
13221 &mut cx,
13222 );
13223}
13224
13225#[gpui::test]
13226async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
13227 let (buffer_id, mut cx) = setup_indent_guides_editor(
13228 &"
13229 fn main() {
13230 let a = 1;
13231
13232 let c = 3;
13233
13234 if a == 3 {
13235 let b = 2;
13236 } else {
13237 let c = 3;
13238 }
13239 }"
13240 .unindent(),
13241 cx,
13242 )
13243 .await;
13244
13245 assert_indent_guides(
13246 1..10,
13247 vec![
13248 indent_guide(buffer_id, 1, 9, 0),
13249 indent_guide(buffer_id, 6, 6, 1),
13250 indent_guide(buffer_id, 8, 8, 1),
13251 ],
13252 None,
13253 &mut cx,
13254 );
13255}
13256
13257#[gpui::test]
13258async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
13259 let (buffer_id, mut cx) = setup_indent_guides_editor(
13260 &"
13261 block1
13262 block2
13263 block3
13264 block4
13265 block2
13266 block1
13267 block1"
13268 .unindent(),
13269 cx,
13270 )
13271 .await;
13272
13273 assert_indent_guides(
13274 1..10,
13275 vec![
13276 indent_guide(buffer_id, 1, 4, 0),
13277 indent_guide(buffer_id, 2, 3, 1),
13278 indent_guide(buffer_id, 3, 3, 2),
13279 ],
13280 None,
13281 &mut cx,
13282 );
13283}
13284
13285#[gpui::test]
13286async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
13287 let (buffer_id, mut cx) = setup_indent_guides_editor(
13288 &"
13289 block1
13290 block2
13291 block3
13292
13293 block1
13294 block1"
13295 .unindent(),
13296 cx,
13297 )
13298 .await;
13299
13300 assert_indent_guides(
13301 0..6,
13302 vec![
13303 indent_guide(buffer_id, 1, 2, 0),
13304 indent_guide(buffer_id, 2, 2, 1),
13305 ],
13306 None,
13307 &mut cx,
13308 );
13309}
13310
13311#[gpui::test]
13312async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
13313 let (buffer_id, mut cx) = setup_indent_guides_editor(
13314 &"
13315 block1
13316
13317
13318
13319 block2
13320 "
13321 .unindent(),
13322 cx,
13323 )
13324 .await;
13325
13326 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13327}
13328
13329#[gpui::test]
13330async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
13331 let (buffer_id, mut cx) = setup_indent_guides_editor(
13332 &"
13333 def a:
13334 \tb = 3
13335 \tif True:
13336 \t\tc = 4
13337 \t\td = 5
13338 \tprint(b)
13339 "
13340 .unindent(),
13341 cx,
13342 )
13343 .await;
13344
13345 assert_indent_guides(
13346 0..6,
13347 vec![
13348 indent_guide(buffer_id, 1, 6, 0),
13349 indent_guide(buffer_id, 3, 4, 1),
13350 ],
13351 None,
13352 &mut cx,
13353 );
13354}
13355
13356#[gpui::test]
13357async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13358 let (buffer_id, mut cx) = setup_indent_guides_editor(
13359 &"
13360 fn main() {
13361 let a = 1;
13362 }"
13363 .unindent(),
13364 cx,
13365 )
13366 .await;
13367
13368 cx.update_editor(|editor, cx| {
13369 editor.change_selections(None, cx, |s| {
13370 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13371 });
13372 });
13373
13374 assert_indent_guides(
13375 0..3,
13376 vec![indent_guide(buffer_id, 1, 1, 0)],
13377 Some(vec![0]),
13378 &mut cx,
13379 );
13380}
13381
13382#[gpui::test]
13383async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13384 let (buffer_id, mut cx) = setup_indent_guides_editor(
13385 &"
13386 fn main() {
13387 if 1 == 2 {
13388 let a = 1;
13389 }
13390 }"
13391 .unindent(),
13392 cx,
13393 )
13394 .await;
13395
13396 cx.update_editor(|editor, cx| {
13397 editor.change_selections(None, cx, |s| {
13398 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13399 });
13400 });
13401
13402 assert_indent_guides(
13403 0..4,
13404 vec![
13405 indent_guide(buffer_id, 1, 3, 0),
13406 indent_guide(buffer_id, 2, 2, 1),
13407 ],
13408 Some(vec![1]),
13409 &mut cx,
13410 );
13411
13412 cx.update_editor(|editor, cx| {
13413 editor.change_selections(None, cx, |s| {
13414 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13415 });
13416 });
13417
13418 assert_indent_guides(
13419 0..4,
13420 vec![
13421 indent_guide(buffer_id, 1, 3, 0),
13422 indent_guide(buffer_id, 2, 2, 1),
13423 ],
13424 Some(vec![1]),
13425 &mut cx,
13426 );
13427
13428 cx.update_editor(|editor, cx| {
13429 editor.change_selections(None, cx, |s| {
13430 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13431 });
13432 });
13433
13434 assert_indent_guides(
13435 0..4,
13436 vec![
13437 indent_guide(buffer_id, 1, 3, 0),
13438 indent_guide(buffer_id, 2, 2, 1),
13439 ],
13440 Some(vec![0]),
13441 &mut cx,
13442 );
13443}
13444
13445#[gpui::test]
13446async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
13447 let (buffer_id, mut cx) = setup_indent_guides_editor(
13448 &"
13449 fn main() {
13450 let a = 1;
13451
13452 let b = 2;
13453 }"
13454 .unindent(),
13455 cx,
13456 )
13457 .await;
13458
13459 cx.update_editor(|editor, cx| {
13460 editor.change_selections(None, cx, |s| {
13461 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13462 });
13463 });
13464
13465 assert_indent_guides(
13466 0..5,
13467 vec![indent_guide(buffer_id, 1, 3, 0)],
13468 Some(vec![0]),
13469 &mut cx,
13470 );
13471}
13472
13473#[gpui::test]
13474async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
13475 let (buffer_id, mut cx) = setup_indent_guides_editor(
13476 &"
13477 def m:
13478 a = 1
13479 pass"
13480 .unindent(),
13481 cx,
13482 )
13483 .await;
13484
13485 cx.update_editor(|editor, cx| {
13486 editor.change_selections(None, cx, |s| {
13487 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13488 });
13489 });
13490
13491 assert_indent_guides(
13492 0..3,
13493 vec![indent_guide(buffer_id, 1, 2, 0)],
13494 Some(vec![0]),
13495 &mut cx,
13496 );
13497}
13498
13499#[gpui::test]
13500fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
13501 init_test(cx, |_| {});
13502
13503 let editor = cx.add_window(|cx| {
13504 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
13505 build_editor(buffer, cx)
13506 });
13507
13508 let render_args = Arc::new(Mutex::new(None));
13509 let snapshot = editor
13510 .update(cx, |editor, cx| {
13511 let snapshot = editor.buffer().read(cx).snapshot(cx);
13512 let range =
13513 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
13514
13515 struct RenderArgs {
13516 row: MultiBufferRow,
13517 folded: bool,
13518 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
13519 }
13520
13521 let crease = Crease::inline(
13522 range,
13523 FoldPlaceholder::test(),
13524 {
13525 let toggle_callback = render_args.clone();
13526 move |row, folded, callback, _cx| {
13527 *toggle_callback.lock() = Some(RenderArgs {
13528 row,
13529 folded,
13530 callback,
13531 });
13532 div()
13533 }
13534 },
13535 |_row, _folded, _cx| div(),
13536 );
13537
13538 editor.insert_creases(Some(crease), cx);
13539 let snapshot = editor.snapshot(cx);
13540 let _div =
13541 snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
13542 snapshot
13543 })
13544 .unwrap();
13545
13546 let render_args = render_args.lock().take().unwrap();
13547 assert_eq!(render_args.row, MultiBufferRow(1));
13548 assert!(!render_args.folded);
13549 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13550
13551 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
13552 .unwrap();
13553 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13554 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
13555
13556 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
13557 .unwrap();
13558 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
13559 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
13560}
13561
13562#[gpui::test]
13563async fn test_input_text(cx: &mut gpui::TestAppContext) {
13564 init_test(cx, |_| {});
13565 let mut cx = EditorTestContext::new(cx).await;
13566
13567 cx.set_state(
13568 &r#"ˇone
13569 two
13570
13571 three
13572 fourˇ
13573 five
13574
13575 siˇx"#
13576 .unindent(),
13577 );
13578
13579 cx.dispatch_action(HandleInput(String::new()));
13580 cx.assert_editor_state(
13581 &r#"ˇone
13582 two
13583
13584 three
13585 fourˇ
13586 five
13587
13588 siˇx"#
13589 .unindent(),
13590 );
13591
13592 cx.dispatch_action(HandleInput("AAAA".to_string()));
13593 cx.assert_editor_state(
13594 &r#"AAAAˇone
13595 two
13596
13597 three
13598 fourAAAAˇ
13599 five
13600
13601 siAAAAˇx"#
13602 .unindent(),
13603 );
13604}
13605
13606#[gpui::test]
13607async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
13608 init_test(cx, |_| {});
13609
13610 let mut cx = EditorTestContext::new(cx).await;
13611 cx.set_state(
13612 r#"let foo = 1;
13613let foo = 2;
13614let foo = 3;
13615let fooˇ = 4;
13616let foo = 5;
13617let foo = 6;
13618let foo = 7;
13619let foo = 8;
13620let foo = 9;
13621let foo = 10;
13622let foo = 11;
13623let foo = 12;
13624let foo = 13;
13625let foo = 14;
13626let foo = 15;"#,
13627 );
13628
13629 cx.update_editor(|e, cx| {
13630 assert_eq!(
13631 e.next_scroll_position,
13632 NextScrollCursorCenterTopBottom::Center,
13633 "Default next scroll direction is center",
13634 );
13635
13636 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13637 assert_eq!(
13638 e.next_scroll_position,
13639 NextScrollCursorCenterTopBottom::Top,
13640 "After center, next scroll direction should be top",
13641 );
13642
13643 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13644 assert_eq!(
13645 e.next_scroll_position,
13646 NextScrollCursorCenterTopBottom::Bottom,
13647 "After top, next scroll direction should be bottom",
13648 );
13649
13650 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13651 assert_eq!(
13652 e.next_scroll_position,
13653 NextScrollCursorCenterTopBottom::Center,
13654 "After bottom, scrolling should start over",
13655 );
13656
13657 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
13658 assert_eq!(
13659 e.next_scroll_position,
13660 NextScrollCursorCenterTopBottom::Top,
13661 "Scrolling continues if retriggered fast enough"
13662 );
13663 });
13664
13665 cx.executor()
13666 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
13667 cx.executor().run_until_parked();
13668 cx.update_editor(|e, _| {
13669 assert_eq!(
13670 e.next_scroll_position,
13671 NextScrollCursorCenterTopBottom::Center,
13672 "If scrolling is not triggered fast enough, it should reset"
13673 );
13674 });
13675}
13676
13677#[gpui::test]
13678async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
13679 init_test(cx, |_| {});
13680 let mut cx = EditorLspTestContext::new_rust(
13681 lsp::ServerCapabilities {
13682 definition_provider: Some(lsp::OneOf::Left(true)),
13683 references_provider: Some(lsp::OneOf::Left(true)),
13684 ..lsp::ServerCapabilities::default()
13685 },
13686 cx,
13687 )
13688 .await;
13689
13690 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
13691 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
13692 move |params, _| async move {
13693 if empty_go_to_definition {
13694 Ok(None)
13695 } else {
13696 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
13697 uri: params.text_document_position_params.text_document.uri,
13698 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
13699 })))
13700 }
13701 },
13702 );
13703 let references =
13704 cx.lsp
13705 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
13706 Ok(Some(vec![lsp::Location {
13707 uri: params.text_document_position.text_document.uri,
13708 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
13709 }]))
13710 });
13711 (go_to_definition, references)
13712 };
13713
13714 cx.set_state(
13715 &r#"fn one() {
13716 let mut a = ˇtwo();
13717 }
13718
13719 fn two() {}"#
13720 .unindent(),
13721 );
13722 set_up_lsp_handlers(false, &mut cx);
13723 let navigated = cx
13724 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13725 .await
13726 .expect("Failed to navigate to definition");
13727 assert_eq!(
13728 navigated,
13729 Navigated::Yes,
13730 "Should have navigated to definition from the GetDefinition response"
13731 );
13732 cx.assert_editor_state(
13733 &r#"fn one() {
13734 let mut a = two();
13735 }
13736
13737 fn «twoˇ»() {}"#
13738 .unindent(),
13739 );
13740
13741 let editors = cx.update_workspace(|workspace, cx| {
13742 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13743 });
13744 cx.update_editor(|_, test_editor_cx| {
13745 assert_eq!(
13746 editors.len(),
13747 1,
13748 "Initially, only one, test, editor should be open in the workspace"
13749 );
13750 assert_eq!(
13751 test_editor_cx.view(),
13752 editors.last().expect("Asserted len is 1")
13753 );
13754 });
13755
13756 set_up_lsp_handlers(true, &mut cx);
13757 let navigated = cx
13758 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
13759 .await
13760 .expect("Failed to navigate to lookup references");
13761 assert_eq!(
13762 navigated,
13763 Navigated::Yes,
13764 "Should have navigated to references as a fallback after empty GoToDefinition response"
13765 );
13766 // We should not change the selections in the existing file,
13767 // if opening another milti buffer with the references
13768 cx.assert_editor_state(
13769 &r#"fn one() {
13770 let mut a = two();
13771 }
13772
13773 fn «twoˇ»() {}"#
13774 .unindent(),
13775 );
13776 let editors = cx.update_workspace(|workspace, cx| {
13777 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
13778 });
13779 cx.update_editor(|_, test_editor_cx| {
13780 assert_eq!(
13781 editors.len(),
13782 2,
13783 "After falling back to references search, we open a new editor with the results"
13784 );
13785 let references_fallback_text = editors
13786 .into_iter()
13787 .find(|new_editor| new_editor != test_editor_cx.view())
13788 .expect("Should have one non-test editor now")
13789 .read(test_editor_cx)
13790 .text(test_editor_cx);
13791 assert_eq!(
13792 references_fallback_text, "fn one() {\n let mut a = two();\n}",
13793 "Should use the range from the references response and not the GoToDefinition one"
13794 );
13795 });
13796}
13797
13798#[gpui::test]
13799async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
13800 init_test(cx, |_| {});
13801
13802 let language = Arc::new(Language::new(
13803 LanguageConfig::default(),
13804 Some(tree_sitter_rust::LANGUAGE.into()),
13805 ));
13806
13807 let text = r#"
13808 #[cfg(test)]
13809 mod tests() {
13810 #[test]
13811 fn runnable_1() {
13812 let a = 1;
13813 }
13814
13815 #[test]
13816 fn runnable_2() {
13817 let a = 1;
13818 let b = 2;
13819 }
13820 }
13821 "#
13822 .unindent();
13823
13824 let fs = FakeFs::new(cx.executor());
13825 fs.insert_file("/file.rs", Default::default()).await;
13826
13827 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13828 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
13829 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13830 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
13831 let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
13832
13833 let editor = cx.new_view(|cx| {
13834 Editor::new(
13835 EditorMode::Full,
13836 multi_buffer,
13837 Some(project.clone()),
13838 true,
13839 cx,
13840 )
13841 });
13842
13843 editor.update(cx, |editor, cx| {
13844 editor.tasks.insert(
13845 (buffer.read(cx).remote_id(), 3),
13846 RunnableTasks {
13847 templates: vec![],
13848 offset: MultiBufferOffset(43),
13849 column: 0,
13850 extra_variables: HashMap::default(),
13851 context_range: BufferOffset(43)..BufferOffset(85),
13852 },
13853 );
13854 editor.tasks.insert(
13855 (buffer.read(cx).remote_id(), 8),
13856 RunnableTasks {
13857 templates: vec![],
13858 offset: MultiBufferOffset(86),
13859 column: 0,
13860 extra_variables: HashMap::default(),
13861 context_range: BufferOffset(86)..BufferOffset(191),
13862 },
13863 );
13864
13865 // Test finding task when cursor is inside function body
13866 editor.change_selections(None, cx, |s| {
13867 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
13868 });
13869 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13870 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
13871
13872 // Test finding task when cursor is on function name
13873 editor.change_selections(None, cx, |s| {
13874 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
13875 });
13876 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
13877 assert_eq!(row, 8, "Should find task when cursor is on function name");
13878 });
13879}
13880
13881fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
13882 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
13883 point..point
13884}
13885
13886fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
13887 let (text, ranges) = marked_text_ranges(marked_text, true);
13888 assert_eq!(view.text(cx), text);
13889 assert_eq!(
13890 view.selections.ranges(cx),
13891 ranges,
13892 "Assert selections are {}",
13893 marked_text
13894 );
13895}
13896
13897pub fn handle_signature_help_request(
13898 cx: &mut EditorLspTestContext,
13899 mocked_response: lsp::SignatureHelp,
13900) -> impl Future<Output = ()> {
13901 let mut request =
13902 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
13903 let mocked_response = mocked_response.clone();
13904 async move { Ok(Some(mocked_response)) }
13905 });
13906
13907 async move {
13908 request.next().await;
13909 }
13910}
13911
13912/// Handle completion request passing a marked string specifying where the completion
13913/// should be triggered from using '|' character, what range should be replaced, and what completions
13914/// should be returned using '<' and '>' to delimit the range
13915pub fn handle_completion_request(
13916 cx: &mut EditorLspTestContext,
13917 marked_string: &str,
13918 completions: Vec<&'static str>,
13919 counter: Arc<AtomicUsize>,
13920) -> impl Future<Output = ()> {
13921 let complete_from_marker: TextRangeMarker = '|'.into();
13922 let replace_range_marker: TextRangeMarker = ('<', '>').into();
13923 let (_, mut marked_ranges) = marked_text_ranges_by(
13924 marked_string,
13925 vec![complete_from_marker.clone(), replace_range_marker.clone()],
13926 );
13927
13928 let complete_from_position =
13929 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
13930 let replace_range =
13931 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
13932
13933 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
13934 let completions = completions.clone();
13935 counter.fetch_add(1, atomic::Ordering::Release);
13936 async move {
13937 assert_eq!(params.text_document_position.text_document.uri, url.clone());
13938 assert_eq!(
13939 params.text_document_position.position,
13940 complete_from_position
13941 );
13942 Ok(Some(lsp::CompletionResponse::Array(
13943 completions
13944 .iter()
13945 .map(|completion_text| lsp::CompletionItem {
13946 label: completion_text.to_string(),
13947 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13948 range: replace_range,
13949 new_text: completion_text.to_string(),
13950 })),
13951 ..Default::default()
13952 })
13953 .collect(),
13954 )))
13955 }
13956 });
13957
13958 async move {
13959 request.next().await;
13960 }
13961}
13962
13963fn handle_resolve_completion_request(
13964 cx: &mut EditorLspTestContext,
13965 edits: Option<Vec<(&'static str, &'static str)>>,
13966) -> impl Future<Output = ()> {
13967 let edits = edits.map(|edits| {
13968 edits
13969 .iter()
13970 .map(|(marked_string, new_text)| {
13971 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
13972 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
13973 lsp::TextEdit::new(replace_range, new_text.to_string())
13974 })
13975 .collect::<Vec<_>>()
13976 });
13977
13978 let mut request =
13979 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
13980 let edits = edits.clone();
13981 async move {
13982 Ok(lsp::CompletionItem {
13983 additional_text_edits: edits,
13984 ..Default::default()
13985 })
13986 }
13987 });
13988
13989 async move {
13990 request.next().await;
13991 }
13992}
13993
13994pub(crate) fn update_test_language_settings(
13995 cx: &mut TestAppContext,
13996 f: impl Fn(&mut AllLanguageSettingsContent),
13997) {
13998 cx.update(|cx| {
13999 SettingsStore::update_global(cx, |store, cx| {
14000 store.update_user_settings::<AllLanguageSettings>(cx, f);
14001 });
14002 });
14003}
14004
14005pub(crate) fn update_test_project_settings(
14006 cx: &mut TestAppContext,
14007 f: impl Fn(&mut ProjectSettings),
14008) {
14009 cx.update(|cx| {
14010 SettingsStore::update_global(cx, |store, cx| {
14011 store.update_user_settings::<ProjectSettings>(cx, f);
14012 });
14013 });
14014}
14015
14016pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
14017 cx.update(|cx| {
14018 assets::Assets.load_test_fonts(cx);
14019 let store = SettingsStore::test(cx);
14020 cx.set_global(store);
14021 theme::init(theme::LoadThemes::JustBase, cx);
14022 release_channel::init(SemanticVersion::default(), cx);
14023 client::init_settings(cx);
14024 language::init(cx);
14025 Project::init_settings(cx);
14026 workspace::init_settings(cx);
14027 crate::init(cx);
14028 });
14029
14030 update_test_language_settings(cx, f);
14031}
14032
14033#[track_caller]
14034fn assert_hunk_revert(
14035 not_reverted_text_with_selections: &str,
14036 expected_not_reverted_hunk_statuses: Vec<DiffHunkStatus>,
14037 expected_reverted_text_with_selections: &str,
14038 base_text: &str,
14039 cx: &mut EditorLspTestContext,
14040) {
14041 cx.set_state(not_reverted_text_with_selections);
14042 cx.set_diff_base(base_text);
14043 cx.executor().run_until_parked();
14044
14045 let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
14046 let snapshot = editor.snapshot(cx);
14047 let reverted_hunk_statuses = snapshot
14048 .diff_map
14049 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
14050 .map(|hunk| hunk_status(&hunk))
14051 .collect::<Vec<_>>();
14052
14053 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
14054 reverted_hunk_statuses
14055 });
14056 cx.executor().run_until_parked();
14057 cx.assert_editor_state(expected_reverted_text_with_selections);
14058 assert_eq!(reverted_hunk_statuses, expected_not_reverted_hunk_statuses);
14059}