1use super::*;
2use crate::{
3 scroll::scroll_amount::ScrollAmount,
4 test::{
5 assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
6 editor_test_context::EditorTestContext, select_ranges,
7 },
8 JoinLines,
9};
10use futures::StreamExt;
11use gpui::{
12 div, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
13 WindowBounds, WindowOptions,
14};
15use indoc::indoc;
16use language::{
17 language_settings::{
18 AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings,
19 },
20 BracketPairConfig,
21 Capability::ReadWrite,
22 FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName,
23 Override, ParsedMarkdown, Point,
24};
25use language_settings::{Formatter, FormatterList, IndentGuideSettings};
26use multi_buffer::IndentGuide;
27use parking_lot::Mutex;
28use pretty_assertions::{assert_eq, assert_ne};
29use project::{buffer_store::BufferChangeSet, FakeFs};
30use project::{
31 lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
32 project_settings::{LspSettings, ProjectSettings},
33};
34use serde_json::{self, json};
35use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
36use std::{
37 iter,
38 sync::atomic::{self, AtomicUsize},
39};
40use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
41use unindent::Unindent;
42use util::{
43 assert_set_eq,
44 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
45};
46use workspace::{
47 item::{FollowEvent, FollowableItem, Item, ItemHandle},
48 NavigationEntry, ViewId,
49};
50
51#[gpui::test]
52fn test_edit_events(cx: &mut TestAppContext) {
53 init_test(cx, |_| {});
54
55 let buffer = cx.new_model(|cx| {
56 let mut buffer = language::Buffer::local("123456", cx);
57 buffer.set_group_interval(Duration::from_secs(1));
58 buffer
59 });
60
61 let events = Rc::new(RefCell::new(Vec::new()));
62 let editor1 = cx.add_window({
63 let events = events.clone();
64 |cx| {
65 let view = cx.view().clone();
66 cx.subscribe(&view, move |_, _, event: &EditorEvent, _| match event {
67 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
68 EditorEvent::BufferEdited => events.borrow_mut().push(("editor1", "buffer edited")),
69 _ => {}
70 })
71 .detach();
72 Editor::for_buffer(buffer.clone(), None, cx)
73 }
74 });
75
76 let editor2 = cx.add_window({
77 let events = events.clone();
78 |cx| {
79 cx.subscribe(
80 &cx.view().clone(),
81 move |_, _, event: &EditorEvent, _| match event {
82 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
83 EditorEvent::BufferEdited => {
84 events.borrow_mut().push(("editor2", "buffer edited"))
85 }
86 _ => {}
87 },
88 )
89 .detach();
90 Editor::for_buffer(buffer.clone(), None, cx)
91 }
92 });
93
94 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
95
96 // Mutating editor 1 will emit an `Edited` event only for that editor.
97 _ = editor1.update(cx, |editor, cx| editor.insert("X", cx));
98 assert_eq!(
99 mem::take(&mut *events.borrow_mut()),
100 [
101 ("editor1", "edited"),
102 ("editor1", "buffer edited"),
103 ("editor2", "buffer edited"),
104 ]
105 );
106
107 // Mutating editor 2 will emit an `Edited` event only for that editor.
108 _ = editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
109 assert_eq!(
110 mem::take(&mut *events.borrow_mut()),
111 [
112 ("editor2", "edited"),
113 ("editor1", "buffer edited"),
114 ("editor2", "buffer edited"),
115 ]
116 );
117
118 // Undoing on editor 1 will emit an `Edited` event only for that editor.
119 _ = editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
120 assert_eq!(
121 mem::take(&mut *events.borrow_mut()),
122 [
123 ("editor1", "edited"),
124 ("editor1", "buffer edited"),
125 ("editor2", "buffer edited"),
126 ]
127 );
128
129 // Redoing on editor 1 will emit an `Edited` event only for that editor.
130 _ = editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
131 assert_eq!(
132 mem::take(&mut *events.borrow_mut()),
133 [
134 ("editor1", "edited"),
135 ("editor1", "buffer edited"),
136 ("editor2", "buffer edited"),
137 ]
138 );
139
140 // Undoing on editor 2 will emit an `Edited` event only for that editor.
141 _ = editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
142 assert_eq!(
143 mem::take(&mut *events.borrow_mut()),
144 [
145 ("editor2", "edited"),
146 ("editor1", "buffer edited"),
147 ("editor2", "buffer edited"),
148 ]
149 );
150
151 // Redoing on editor 2 will emit an `Edited` event only for that editor.
152 _ = editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
153 assert_eq!(
154 mem::take(&mut *events.borrow_mut()),
155 [
156 ("editor2", "edited"),
157 ("editor1", "buffer edited"),
158 ("editor2", "buffer edited"),
159 ]
160 );
161
162 // No event is emitted when the mutation is a no-op.
163 _ = editor2.update(cx, |editor, cx| {
164 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
165
166 editor.backspace(&Backspace, cx);
167 });
168 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
169}
170
171#[gpui::test]
172fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
173 init_test(cx, |_| {});
174
175 let mut now = Instant::now();
176 let group_interval = Duration::from_millis(1);
177 let buffer = cx.new_model(|cx| {
178 let mut buf = language::Buffer::local("123456", cx);
179 buf.set_group_interval(group_interval);
180 buf
181 });
182 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
183 let editor = cx.add_window(|cx| build_editor(buffer.clone(), cx));
184
185 _ = editor.update(cx, |editor, cx| {
186 editor.start_transaction_at(now, cx);
187 editor.change_selections(None, cx, |s| s.select_ranges([2..4]));
188
189 editor.insert("cd", cx);
190 editor.end_transaction_at(now, cx);
191 assert_eq!(editor.text(cx), "12cd56");
192 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
193
194 editor.start_transaction_at(now, cx);
195 editor.change_selections(None, cx, |s| s.select_ranges([4..5]));
196 editor.insert("e", cx);
197 editor.end_transaction_at(now, cx);
198 assert_eq!(editor.text(cx), "12cde6");
199 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
200
201 now += group_interval + Duration::from_millis(1);
202 editor.change_selections(None, cx, |s| s.select_ranges([2..2]));
203
204 // Simulate an edit in another editor
205 buffer.update(cx, |buffer, cx| {
206 buffer.start_transaction_at(now, cx);
207 buffer.edit([(0..1, "a")], None, cx);
208 buffer.edit([(1..1, "b")], None, cx);
209 buffer.end_transaction_at(now, cx);
210 });
211
212 assert_eq!(editor.text(cx), "ab2cde6");
213 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
214
215 // Last transaction happened past the group interval in a different editor.
216 // Undo it individually and don't restore selections.
217 editor.undo(&Undo, cx);
218 assert_eq!(editor.text(cx), "12cde6");
219 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
220
221 // First two transactions happened within the group interval in this editor.
222 // Undo them together and restore selections.
223 editor.undo(&Undo, cx);
224 editor.undo(&Undo, cx); // Undo stack is empty here, so this is a no-op.
225 assert_eq!(editor.text(cx), "123456");
226 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
227
228 // Redo the first two transactions together.
229 editor.redo(&Redo, cx);
230 assert_eq!(editor.text(cx), "12cde6");
231 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
232
233 // Redo the last transaction on its own.
234 editor.redo(&Redo, cx);
235 assert_eq!(editor.text(cx), "ab2cde6");
236 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
237
238 // Test empty transactions.
239 editor.start_transaction_at(now, cx);
240 editor.end_transaction_at(now, cx);
241 editor.undo(&Undo, cx);
242 assert_eq!(editor.text(cx), "12cde6");
243 });
244}
245
246#[gpui::test]
247fn test_ime_composition(cx: &mut TestAppContext) {
248 init_test(cx, |_| {});
249
250 let buffer = cx.new_model(|cx| {
251 let mut buffer = language::Buffer::local("abcde", cx);
252 // Ensure automatic grouping doesn't occur.
253 buffer.set_group_interval(Duration::ZERO);
254 buffer
255 });
256
257 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
258 cx.add_window(|cx| {
259 let mut editor = build_editor(buffer.clone(), cx);
260
261 // Start a new IME composition.
262 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
263 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
264 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
265 assert_eq!(editor.text(cx), "äbcde");
266 assert_eq!(
267 editor.marked_text_ranges(cx),
268 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
269 );
270
271 // Finalize IME composition.
272 editor.replace_text_in_range(None, "ā", cx);
273 assert_eq!(editor.text(cx), "ābcde");
274 assert_eq!(editor.marked_text_ranges(cx), None);
275
276 // IME composition edits are grouped and are undone/redone at once.
277 editor.undo(&Default::default(), cx);
278 assert_eq!(editor.text(cx), "abcde");
279 assert_eq!(editor.marked_text_ranges(cx), None);
280 editor.redo(&Default::default(), cx);
281 assert_eq!(editor.text(cx), "ābcde");
282 assert_eq!(editor.marked_text_ranges(cx), None);
283
284 // Start a new IME composition.
285 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
286 assert_eq!(
287 editor.marked_text_ranges(cx),
288 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
289 );
290
291 // Undoing during an IME composition cancels it.
292 editor.undo(&Default::default(), cx);
293 assert_eq!(editor.text(cx), "ābcde");
294 assert_eq!(editor.marked_text_ranges(cx), None);
295
296 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
297 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
298 assert_eq!(editor.text(cx), "ābcdè");
299 assert_eq!(
300 editor.marked_text_ranges(cx),
301 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
302 );
303
304 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
305 editor.replace_text_in_range(Some(4..999), "ę", cx);
306 assert_eq!(editor.text(cx), "ābcdę");
307 assert_eq!(editor.marked_text_ranges(cx), None);
308
309 // Start a new IME composition with multiple cursors.
310 editor.change_selections(None, cx, |s| {
311 s.select_ranges([
312 OffsetUtf16(1)..OffsetUtf16(1),
313 OffsetUtf16(3)..OffsetUtf16(3),
314 OffsetUtf16(5)..OffsetUtf16(5),
315 ])
316 });
317 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
318 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
319 assert_eq!(
320 editor.marked_text_ranges(cx),
321 Some(vec![
322 OffsetUtf16(0)..OffsetUtf16(3),
323 OffsetUtf16(4)..OffsetUtf16(7),
324 OffsetUtf16(8)..OffsetUtf16(11)
325 ])
326 );
327
328 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
329 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
330 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
331 assert_eq!(
332 editor.marked_text_ranges(cx),
333 Some(vec![
334 OffsetUtf16(1)..OffsetUtf16(2),
335 OffsetUtf16(5)..OffsetUtf16(6),
336 OffsetUtf16(9)..OffsetUtf16(10)
337 ])
338 );
339
340 // Finalize IME composition with multiple cursors.
341 editor.replace_text_in_range(Some(9..10), "2", cx);
342 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
343 assert_eq!(editor.marked_text_ranges(cx), None);
344
345 editor
346 });
347}
348
349#[gpui::test]
350fn test_selection_with_mouse(cx: &mut TestAppContext) {
351 init_test(cx, |_| {});
352
353 let editor = cx.add_window(|cx| {
354 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
355 build_editor(buffer, cx)
356 });
357
358 _ = editor.update(cx, |view, cx| {
359 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
360 });
361 assert_eq!(
362 editor
363 .update(cx, |view, cx| view.selections.display_ranges(cx))
364 .unwrap(),
365 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
366 );
367
368 _ = editor.update(cx, |view, cx| {
369 view.update_selection(
370 DisplayPoint::new(DisplayRow(3), 3),
371 0,
372 gpui::Point::<f32>::default(),
373 cx,
374 );
375 });
376
377 assert_eq!(
378 editor
379 .update(cx, |view, cx| view.selections.display_ranges(cx))
380 .unwrap(),
381 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
382 );
383
384 _ = editor.update(cx, |view, cx| {
385 view.update_selection(
386 DisplayPoint::new(DisplayRow(1), 1),
387 0,
388 gpui::Point::<f32>::default(),
389 cx,
390 );
391 });
392
393 assert_eq!(
394 editor
395 .update(cx, |view, cx| view.selections.display_ranges(cx))
396 .unwrap(),
397 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
398 );
399
400 _ = editor.update(cx, |view, cx| {
401 view.end_selection(cx);
402 view.update_selection(
403 DisplayPoint::new(DisplayRow(3), 3),
404 0,
405 gpui::Point::<f32>::default(),
406 cx,
407 );
408 });
409
410 assert_eq!(
411 editor
412 .update(cx, |view, cx| view.selections.display_ranges(cx))
413 .unwrap(),
414 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
415 );
416
417 _ = editor.update(cx, |view, cx| {
418 view.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, cx);
419 view.update_selection(
420 DisplayPoint::new(DisplayRow(0), 0),
421 0,
422 gpui::Point::<f32>::default(),
423 cx,
424 );
425 });
426
427 assert_eq!(
428 editor
429 .update(cx, |view, cx| view.selections.display_ranges(cx))
430 .unwrap(),
431 [
432 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
433 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
434 ]
435 );
436
437 _ = editor.update(cx, |view, cx| {
438 view.end_selection(cx);
439 });
440
441 assert_eq!(
442 editor
443 .update(cx, |view, cx| view.selections.display_ranges(cx))
444 .unwrap(),
445 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
446 );
447}
448
449#[gpui::test]
450fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
451 init_test(cx, |_| {});
452
453 let editor = cx.add_window(|cx| {
454 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
455 build_editor(buffer, cx)
456 });
457
458 _ = editor.update(cx, |view, cx| {
459 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, cx);
460 });
461
462 _ = editor.update(cx, |view, cx| {
463 view.end_selection(cx);
464 });
465
466 _ = editor.update(cx, |view, cx| {
467 view.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, cx);
468 });
469
470 _ = editor.update(cx, |view, cx| {
471 view.end_selection(cx);
472 });
473
474 assert_eq!(
475 editor
476 .update(cx, |view, cx| view.selections.display_ranges(cx))
477 .unwrap(),
478 [
479 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
480 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
481 ]
482 );
483
484 _ = editor.update(cx, |view, cx| {
485 view.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, cx);
486 });
487
488 _ = editor.update(cx, |view, cx| {
489 view.end_selection(cx);
490 });
491
492 assert_eq!(
493 editor
494 .update(cx, |view, cx| view.selections.display_ranges(cx))
495 .unwrap(),
496 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
497 );
498}
499
500#[gpui::test]
501fn test_canceling_pending_selection(cx: &mut TestAppContext) {
502 init_test(cx, |_| {});
503
504 let view = cx.add_window(|cx| {
505 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
506 build_editor(buffer, cx)
507 });
508
509 _ = view.update(cx, |view, cx| {
510 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
511 assert_eq!(
512 view.selections.display_ranges(cx),
513 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
514 );
515 });
516
517 _ = view.update(cx, |view, cx| {
518 view.update_selection(
519 DisplayPoint::new(DisplayRow(3), 3),
520 0,
521 gpui::Point::<f32>::default(),
522 cx,
523 );
524 assert_eq!(
525 view.selections.display_ranges(cx),
526 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
527 );
528 });
529
530 _ = view.update(cx, |view, cx| {
531 view.cancel(&Cancel, cx);
532 view.update_selection(
533 DisplayPoint::new(DisplayRow(1), 1),
534 0,
535 gpui::Point::<f32>::default(),
536 cx,
537 );
538 assert_eq!(
539 view.selections.display_ranges(cx),
540 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
541 );
542 });
543}
544
545#[gpui::test]
546fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
547 init_test(cx, |_| {});
548
549 let view = cx.add_window(|cx| {
550 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
551 build_editor(buffer, cx)
552 });
553
554 _ = view.update(cx, |view, cx| {
555 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
556 assert_eq!(
557 view.selections.display_ranges(cx),
558 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
559 );
560
561 view.move_down(&Default::default(), cx);
562 assert_eq!(
563 view.selections.display_ranges(cx),
564 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
565 );
566
567 view.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, cx);
568 assert_eq!(
569 view.selections.display_ranges(cx),
570 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
571 );
572
573 view.move_up(&Default::default(), cx);
574 assert_eq!(
575 view.selections.display_ranges(cx),
576 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
577 );
578 });
579}
580
581#[gpui::test]
582fn test_clone(cx: &mut TestAppContext) {
583 init_test(cx, |_| {});
584
585 let (text, selection_ranges) = marked_text_ranges(
586 indoc! {"
587 one
588 two
589 threeˇ
590 four
591 fiveˇ
592 "},
593 true,
594 );
595
596 let editor = cx.add_window(|cx| {
597 let buffer = MultiBuffer::build_simple(&text, cx);
598 build_editor(buffer, cx)
599 });
600
601 _ = editor.update(cx, |editor, cx| {
602 editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
603 editor.fold_creases(
604 vec![
605 Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
606 Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
607 ],
608 true,
609 cx,
610 );
611 });
612
613 let cloned_editor = editor
614 .update(cx, |editor, cx| {
615 cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
616 })
617 .unwrap()
618 .unwrap();
619
620 let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
621 let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
622
623 assert_eq!(
624 cloned_editor
625 .update(cx, |e, cx| e.display_text(cx))
626 .unwrap(),
627 editor.update(cx, |e, cx| e.display_text(cx)).unwrap()
628 );
629 assert_eq!(
630 cloned_snapshot
631 .folds_in_range(0..text.len())
632 .collect::<Vec<_>>(),
633 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
634 );
635 assert_set_eq!(
636 cloned_editor
637 .update(cx, |editor, cx| editor.selections.ranges::<Point>(cx))
638 .unwrap(),
639 editor
640 .update(cx, |editor, cx| editor.selections.ranges(cx))
641 .unwrap()
642 );
643 assert_set_eq!(
644 cloned_editor
645 .update(cx, |e, cx| e.selections.display_ranges(cx))
646 .unwrap(),
647 editor
648 .update(cx, |e, cx| e.selections.display_ranges(cx))
649 .unwrap()
650 );
651}
652
653#[gpui::test]
654async fn test_navigation_history(cx: &mut TestAppContext) {
655 init_test(cx, |_| {});
656
657 use workspace::item::Item;
658
659 let fs = FakeFs::new(cx.executor());
660 let project = Project::test(fs, [], cx).await;
661 let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
662 let pane = workspace
663 .update(cx, |workspace, _| workspace.active_pane().clone())
664 .unwrap();
665
666 _ = workspace.update(cx, |_v, cx| {
667 cx.new_view(|cx| {
668 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
669 let mut editor = build_editor(buffer.clone(), cx);
670 let handle = cx.view();
671 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(handle)));
672
673 fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
674 editor.nav_history.as_mut().unwrap().pop_backward(cx)
675 }
676
677 // Move the cursor a small distance.
678 // Nothing is added to the navigation history.
679 editor.change_selections(None, cx, |s| {
680 s.select_display_ranges([
681 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
682 ])
683 });
684 editor.change_selections(None, cx, |s| {
685 s.select_display_ranges([
686 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
687 ])
688 });
689 assert!(pop_history(&mut editor, cx).is_none());
690
691 // Move the cursor a large distance.
692 // The history can jump back to the previous position.
693 editor.change_selections(None, cx, |s| {
694 s.select_display_ranges([
695 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
696 ])
697 });
698 let nav_entry = pop_history(&mut editor, cx).unwrap();
699 editor.navigate(nav_entry.data.unwrap(), cx);
700 assert_eq!(nav_entry.item.id(), cx.entity_id());
701 assert_eq!(
702 editor.selections.display_ranges(cx),
703 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
704 );
705 assert!(pop_history(&mut editor, cx).is_none());
706
707 // Move the cursor a small distance via the mouse.
708 // Nothing is added to the navigation history.
709 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, cx);
710 editor.end_selection(cx);
711 assert_eq!(
712 editor.selections.display_ranges(cx),
713 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
714 );
715 assert!(pop_history(&mut editor, cx).is_none());
716
717 // Move the cursor a large distance via the mouse.
718 // The history can jump back to the previous position.
719 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, cx);
720 editor.end_selection(cx);
721 assert_eq!(
722 editor.selections.display_ranges(cx),
723 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
724 );
725 let nav_entry = pop_history(&mut editor, cx).unwrap();
726 editor.navigate(nav_entry.data.unwrap(), cx);
727 assert_eq!(nav_entry.item.id(), cx.entity_id());
728 assert_eq!(
729 editor.selections.display_ranges(cx),
730 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
731 );
732 assert!(pop_history(&mut editor, cx).is_none());
733
734 // Set scroll position to check later
735 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
736 let original_scroll_position = editor.scroll_manager.anchor();
737
738 // Jump to the end of the document and adjust scroll
739 editor.move_to_end(&MoveToEnd, cx);
740 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
741 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
742
743 let nav_entry = pop_history(&mut editor, cx).unwrap();
744 editor.navigate(nav_entry.data.unwrap(), cx);
745 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
746
747 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
748 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
749 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
750 let invalid_point = Point::new(9999, 0);
751 editor.navigate(
752 Box::new(NavigationData {
753 cursor_anchor: invalid_anchor,
754 cursor_position: invalid_point,
755 scroll_anchor: ScrollAnchor {
756 anchor: invalid_anchor,
757 offset: Default::default(),
758 },
759 scroll_top_row: invalid_point.row,
760 }),
761 cx,
762 );
763 assert_eq!(
764 editor.selections.display_ranges(cx),
765 &[editor.max_point(cx)..editor.max_point(cx)]
766 );
767 assert_eq!(
768 editor.scroll_position(cx),
769 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
770 );
771
772 editor
773 })
774 });
775}
776
777#[gpui::test]
778fn test_cancel(cx: &mut TestAppContext) {
779 init_test(cx, |_| {});
780
781 let view = cx.add_window(|cx| {
782 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
783 build_editor(buffer, cx)
784 });
785
786 _ = view.update(cx, |view, cx| {
787 view.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, cx);
788 view.update_selection(
789 DisplayPoint::new(DisplayRow(1), 1),
790 0,
791 gpui::Point::<f32>::default(),
792 cx,
793 );
794 view.end_selection(cx);
795
796 view.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, cx);
797 view.update_selection(
798 DisplayPoint::new(DisplayRow(0), 3),
799 0,
800 gpui::Point::<f32>::default(),
801 cx,
802 );
803 view.end_selection(cx);
804 assert_eq!(
805 view.selections.display_ranges(cx),
806 [
807 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
808 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
809 ]
810 );
811 });
812
813 _ = view.update(cx, |view, cx| {
814 view.cancel(&Cancel, cx);
815 assert_eq!(
816 view.selections.display_ranges(cx),
817 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
818 );
819 });
820
821 _ = view.update(cx, |view, cx| {
822 view.cancel(&Cancel, cx);
823 assert_eq!(
824 view.selections.display_ranges(cx),
825 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
826 );
827 });
828}
829
830#[gpui::test]
831fn test_fold_action(cx: &mut TestAppContext) {
832 init_test(cx, |_| {});
833
834 let view = cx.add_window(|cx| {
835 let buffer = MultiBuffer::build_simple(
836 &"
837 impl Foo {
838 // Hello!
839
840 fn a() {
841 1
842 }
843
844 fn b() {
845 2
846 }
847
848 fn c() {
849 3
850 }
851 }
852 "
853 .unindent(),
854 cx,
855 );
856 build_editor(buffer.clone(), cx)
857 });
858
859 _ = view.update(cx, |view, cx| {
860 view.change_selections(None, cx, |s| {
861 s.select_display_ranges([
862 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
863 ]);
864 });
865 view.fold(&Fold, cx);
866 assert_eq!(
867 view.display_text(cx),
868 "
869 impl Foo {
870 // Hello!
871
872 fn a() {
873 1
874 }
875
876 fn b() {⋯
877 }
878
879 fn c() {⋯
880 }
881 }
882 "
883 .unindent(),
884 );
885
886 view.fold(&Fold, cx);
887 assert_eq!(
888 view.display_text(cx),
889 "
890 impl Foo {⋯
891 }
892 "
893 .unindent(),
894 );
895
896 view.unfold_lines(&UnfoldLines, cx);
897 assert_eq!(
898 view.display_text(cx),
899 "
900 impl Foo {
901 // Hello!
902
903 fn a() {
904 1
905 }
906
907 fn b() {⋯
908 }
909
910 fn c() {⋯
911 }
912 }
913 "
914 .unindent(),
915 );
916
917 view.unfold_lines(&UnfoldLines, cx);
918 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
919 });
920}
921
922#[gpui::test]
923fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
924 init_test(cx, |_| {});
925
926 let view = cx.add_window(|cx| {
927 let buffer = MultiBuffer::build_simple(
928 &"
929 class Foo:
930 # Hello!
931
932 def a():
933 print(1)
934
935 def b():
936 print(2)
937
938 def c():
939 print(3)
940 "
941 .unindent(),
942 cx,
943 );
944 build_editor(buffer.clone(), cx)
945 });
946
947 _ = view.update(cx, |view, cx| {
948 view.change_selections(None, cx, |s| {
949 s.select_display_ranges([
950 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
951 ]);
952 });
953 view.fold(&Fold, cx);
954 assert_eq!(
955 view.display_text(cx),
956 "
957 class Foo:
958 # Hello!
959
960 def a():
961 print(1)
962
963 def b():⋯
964
965 def c():⋯
966 "
967 .unindent(),
968 );
969
970 view.fold(&Fold, cx);
971 assert_eq!(
972 view.display_text(cx),
973 "
974 class Foo:⋯
975 "
976 .unindent(),
977 );
978
979 view.unfold_lines(&UnfoldLines, cx);
980 assert_eq!(
981 view.display_text(cx),
982 "
983 class Foo:
984 # Hello!
985
986 def a():
987 print(1)
988
989 def b():⋯
990
991 def c():⋯
992 "
993 .unindent(),
994 );
995
996 view.unfold_lines(&UnfoldLines, cx);
997 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
998 });
999}
1000
1001#[gpui::test]
1002fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
1003 init_test(cx, |_| {});
1004
1005 let view = cx.add_window(|cx| {
1006 let buffer = MultiBuffer::build_simple(
1007 &"
1008 class Foo:
1009 # Hello!
1010
1011 def a():
1012 print(1)
1013
1014 def b():
1015 print(2)
1016
1017
1018 def c():
1019 print(3)
1020
1021
1022 "
1023 .unindent(),
1024 cx,
1025 );
1026 build_editor(buffer.clone(), cx)
1027 });
1028
1029 _ = view.update(cx, |view, cx| {
1030 view.change_selections(None, cx, |s| {
1031 s.select_display_ranges([
1032 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1033 ]);
1034 });
1035 view.fold(&Fold, cx);
1036 assert_eq!(
1037 view.display_text(cx),
1038 "
1039 class Foo:
1040 # Hello!
1041
1042 def a():
1043 print(1)
1044
1045 def b():⋯
1046
1047
1048 def c():⋯
1049
1050
1051 "
1052 .unindent(),
1053 );
1054
1055 view.fold(&Fold, cx);
1056 assert_eq!(
1057 view.display_text(cx),
1058 "
1059 class Foo:⋯
1060
1061
1062 "
1063 .unindent(),
1064 );
1065
1066 view.unfold_lines(&UnfoldLines, cx);
1067 assert_eq!(
1068 view.display_text(cx),
1069 "
1070 class Foo:
1071 # Hello!
1072
1073 def a():
1074 print(1)
1075
1076 def b():⋯
1077
1078
1079 def c():⋯
1080
1081
1082 "
1083 .unindent(),
1084 );
1085
1086 view.unfold_lines(&UnfoldLines, cx);
1087 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1088 });
1089}
1090
1091#[gpui::test]
1092fn test_fold_at_level(cx: &mut TestAppContext) {
1093 init_test(cx, |_| {});
1094
1095 let view = cx.add_window(|cx| {
1096 let buffer = MultiBuffer::build_simple(
1097 &"
1098 class Foo:
1099 # Hello!
1100
1101 def a():
1102 print(1)
1103
1104 def b():
1105 print(2)
1106
1107
1108 class Bar:
1109 # World!
1110
1111 def a():
1112 print(1)
1113
1114 def b():
1115 print(2)
1116
1117
1118 "
1119 .unindent(),
1120 cx,
1121 );
1122 build_editor(buffer.clone(), cx)
1123 });
1124
1125 _ = view.update(cx, |view, cx| {
1126 view.fold_at_level(&FoldAtLevel { level: 2 }, cx);
1127 assert_eq!(
1128 view.display_text(cx),
1129 "
1130 class Foo:
1131 # Hello!
1132
1133 def a():⋯
1134
1135 def b():⋯
1136
1137
1138 class Bar:
1139 # World!
1140
1141 def a():⋯
1142
1143 def b():⋯
1144
1145
1146 "
1147 .unindent(),
1148 );
1149
1150 view.fold_at_level(&FoldAtLevel { level: 1 }, cx);
1151 assert_eq!(
1152 view.display_text(cx),
1153 "
1154 class Foo:⋯
1155
1156
1157 class Bar:⋯
1158
1159
1160 "
1161 .unindent(),
1162 );
1163
1164 view.unfold_all(&UnfoldAll, cx);
1165 view.fold_at_level(&FoldAtLevel { level: 0 }, cx);
1166 assert_eq!(
1167 view.display_text(cx),
1168 "
1169 class Foo:
1170 # Hello!
1171
1172 def a():
1173 print(1)
1174
1175 def b():
1176 print(2)
1177
1178
1179 class Bar:
1180 # World!
1181
1182 def a():
1183 print(1)
1184
1185 def b():
1186 print(2)
1187
1188
1189 "
1190 .unindent(),
1191 );
1192
1193 assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
1194 });
1195}
1196
1197#[gpui::test]
1198fn test_move_cursor(cx: &mut TestAppContext) {
1199 init_test(cx, |_| {});
1200
1201 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1202 let view = cx.add_window(|cx| build_editor(buffer.clone(), cx));
1203
1204 buffer.update(cx, |buffer, cx| {
1205 buffer.edit(
1206 vec![
1207 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1208 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1209 ],
1210 None,
1211 cx,
1212 );
1213 });
1214 _ = view.update(cx, |view, cx| {
1215 assert_eq!(
1216 view.selections.display_ranges(cx),
1217 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1218 );
1219
1220 view.move_down(&MoveDown, cx);
1221 assert_eq!(
1222 view.selections.display_ranges(cx),
1223 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1224 );
1225
1226 view.move_right(&MoveRight, cx);
1227 assert_eq!(
1228 view.selections.display_ranges(cx),
1229 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1230 );
1231
1232 view.move_left(&MoveLeft, cx);
1233 assert_eq!(
1234 view.selections.display_ranges(cx),
1235 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1236 );
1237
1238 view.move_up(&MoveUp, cx);
1239 assert_eq!(
1240 view.selections.display_ranges(cx),
1241 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1242 );
1243
1244 view.move_to_end(&MoveToEnd, cx);
1245 assert_eq!(
1246 view.selections.display_ranges(cx),
1247 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1248 );
1249
1250 view.move_to_beginning(&MoveToBeginning, cx);
1251 assert_eq!(
1252 view.selections.display_ranges(cx),
1253 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1254 );
1255
1256 view.change_selections(None, cx, |s| {
1257 s.select_display_ranges([
1258 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1259 ]);
1260 });
1261 view.select_to_beginning(&SelectToBeginning, cx);
1262 assert_eq!(
1263 view.selections.display_ranges(cx),
1264 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1265 );
1266
1267 view.select_to_end(&SelectToEnd, cx);
1268 assert_eq!(
1269 view.selections.display_ranges(cx),
1270 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1271 );
1272 });
1273}
1274
1275// TODO: Re-enable this test
1276#[cfg(target_os = "macos")]
1277#[gpui::test]
1278fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1279 init_test(cx, |_| {});
1280
1281 let view = cx.add_window(|cx| {
1282 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε", cx);
1283 build_editor(buffer.clone(), cx)
1284 });
1285
1286 assert_eq!('ⓐ'.len_utf8(), 3);
1287 assert_eq!('α'.len_utf8(), 2);
1288
1289 _ = view.update(cx, |view, cx| {
1290 view.fold_creases(
1291 vec![
1292 Crease::simple(Point::new(0, 6)..Point::new(0, 12), FoldPlaceholder::test()),
1293 Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1294 Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1295 ],
1296 true,
1297 cx,
1298 );
1299 assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε");
1300
1301 view.move_right(&MoveRight, cx);
1302 assert_eq!(
1303 view.selections.display_ranges(cx),
1304 &[empty_range(0, "ⓐ".len())]
1305 );
1306 view.move_right(&MoveRight, cx);
1307 assert_eq!(
1308 view.selections.display_ranges(cx),
1309 &[empty_range(0, "ⓐⓑ".len())]
1310 );
1311 view.move_right(&MoveRight, cx);
1312 assert_eq!(
1313 view.selections.display_ranges(cx),
1314 &[empty_range(0, "ⓐⓑ⋯".len())]
1315 );
1316
1317 view.move_down(&MoveDown, cx);
1318 assert_eq!(
1319 view.selections.display_ranges(cx),
1320 &[empty_range(1, "ab⋯e".len())]
1321 );
1322 view.move_left(&MoveLeft, cx);
1323 assert_eq!(
1324 view.selections.display_ranges(cx),
1325 &[empty_range(1, "ab⋯".len())]
1326 );
1327 view.move_left(&MoveLeft, cx);
1328 assert_eq!(
1329 view.selections.display_ranges(cx),
1330 &[empty_range(1, "ab".len())]
1331 );
1332 view.move_left(&MoveLeft, cx);
1333 assert_eq!(
1334 view.selections.display_ranges(cx),
1335 &[empty_range(1, "a".len())]
1336 );
1337
1338 view.move_down(&MoveDown, cx);
1339 assert_eq!(
1340 view.selections.display_ranges(cx),
1341 &[empty_range(2, "α".len())]
1342 );
1343 view.move_right(&MoveRight, cx);
1344 assert_eq!(
1345 view.selections.display_ranges(cx),
1346 &[empty_range(2, "αβ".len())]
1347 );
1348 view.move_right(&MoveRight, cx);
1349 assert_eq!(
1350 view.selections.display_ranges(cx),
1351 &[empty_range(2, "αβ⋯".len())]
1352 );
1353 view.move_right(&MoveRight, cx);
1354 assert_eq!(
1355 view.selections.display_ranges(cx),
1356 &[empty_range(2, "αβ⋯ε".len())]
1357 );
1358
1359 view.move_up(&MoveUp, cx);
1360 assert_eq!(
1361 view.selections.display_ranges(cx),
1362 &[empty_range(1, "ab⋯e".len())]
1363 );
1364 view.move_down(&MoveDown, cx);
1365 assert_eq!(
1366 view.selections.display_ranges(cx),
1367 &[empty_range(2, "αβ⋯ε".len())]
1368 );
1369 view.move_up(&MoveUp, cx);
1370 assert_eq!(
1371 view.selections.display_ranges(cx),
1372 &[empty_range(1, "ab⋯e".len())]
1373 );
1374
1375 view.move_up(&MoveUp, cx);
1376 assert_eq!(
1377 view.selections.display_ranges(cx),
1378 &[empty_range(0, "ⓐⓑ".len())]
1379 );
1380 view.move_left(&MoveLeft, cx);
1381 assert_eq!(
1382 view.selections.display_ranges(cx),
1383 &[empty_range(0, "ⓐ".len())]
1384 );
1385 view.move_left(&MoveLeft, cx);
1386 assert_eq!(
1387 view.selections.display_ranges(cx),
1388 &[empty_range(0, "".len())]
1389 );
1390 });
1391}
1392
1393#[gpui::test]
1394fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1395 init_test(cx, |_| {});
1396
1397 let view = cx.add_window(|cx| {
1398 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1399 build_editor(buffer.clone(), cx)
1400 });
1401 _ = view.update(cx, |view, cx| {
1402 view.change_selections(None, cx, |s| {
1403 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1404 });
1405
1406 // moving above start of document should move selection to start of document,
1407 // but the next move down should still be at the original goal_x
1408 view.move_up(&MoveUp, cx);
1409 assert_eq!(
1410 view.selections.display_ranges(cx),
1411 &[empty_range(0, "".len())]
1412 );
1413
1414 view.move_down(&MoveDown, cx);
1415 assert_eq!(
1416 view.selections.display_ranges(cx),
1417 &[empty_range(1, "abcd".len())]
1418 );
1419
1420 view.move_down(&MoveDown, cx);
1421 assert_eq!(
1422 view.selections.display_ranges(cx),
1423 &[empty_range(2, "αβγ".len())]
1424 );
1425
1426 view.move_down(&MoveDown, cx);
1427 assert_eq!(
1428 view.selections.display_ranges(cx),
1429 &[empty_range(3, "abcd".len())]
1430 );
1431
1432 view.move_down(&MoveDown, cx);
1433 assert_eq!(
1434 view.selections.display_ranges(cx),
1435 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1436 );
1437
1438 // moving past end of document should not change goal_x
1439 view.move_down(&MoveDown, cx);
1440 assert_eq!(
1441 view.selections.display_ranges(cx),
1442 &[empty_range(5, "".len())]
1443 );
1444
1445 view.move_down(&MoveDown, cx);
1446 assert_eq!(
1447 view.selections.display_ranges(cx),
1448 &[empty_range(5, "".len())]
1449 );
1450
1451 view.move_up(&MoveUp, cx);
1452 assert_eq!(
1453 view.selections.display_ranges(cx),
1454 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1455 );
1456
1457 view.move_up(&MoveUp, cx);
1458 assert_eq!(
1459 view.selections.display_ranges(cx),
1460 &[empty_range(3, "abcd".len())]
1461 );
1462
1463 view.move_up(&MoveUp, cx);
1464 assert_eq!(
1465 view.selections.display_ranges(cx),
1466 &[empty_range(2, "αβγ".len())]
1467 );
1468 });
1469}
1470
1471#[gpui::test]
1472fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1473 init_test(cx, |_| {});
1474 let move_to_beg = MoveToBeginningOfLine {
1475 stop_at_soft_wraps: true,
1476 };
1477
1478 let move_to_end = MoveToEndOfLine {
1479 stop_at_soft_wraps: true,
1480 };
1481
1482 let view = cx.add_window(|cx| {
1483 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1484 build_editor(buffer, cx)
1485 });
1486 _ = view.update(cx, |view, cx| {
1487 view.change_selections(None, cx, |s| {
1488 s.select_display_ranges([
1489 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1490 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1491 ]);
1492 });
1493 });
1494
1495 _ = view.update(cx, |view, cx| {
1496 view.move_to_beginning_of_line(&move_to_beg, cx);
1497 assert_eq!(
1498 view.selections.display_ranges(cx),
1499 &[
1500 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1501 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1502 ]
1503 );
1504 });
1505
1506 _ = view.update(cx, |view, cx| {
1507 view.move_to_beginning_of_line(&move_to_beg, cx);
1508 assert_eq!(
1509 view.selections.display_ranges(cx),
1510 &[
1511 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1512 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1513 ]
1514 );
1515 });
1516
1517 _ = view.update(cx, |view, cx| {
1518 view.move_to_beginning_of_line(&move_to_beg, cx);
1519 assert_eq!(
1520 view.selections.display_ranges(cx),
1521 &[
1522 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1523 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1524 ]
1525 );
1526 });
1527
1528 _ = view.update(cx, |view, cx| {
1529 view.move_to_end_of_line(&move_to_end, cx);
1530 assert_eq!(
1531 view.selections.display_ranges(cx),
1532 &[
1533 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1534 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1535 ]
1536 );
1537 });
1538
1539 // Moving to the end of line again is a no-op.
1540 _ = view.update(cx, |view, cx| {
1541 view.move_to_end_of_line(&move_to_end, cx);
1542 assert_eq!(
1543 view.selections.display_ranges(cx),
1544 &[
1545 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1546 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1547 ]
1548 );
1549 });
1550
1551 _ = view.update(cx, |view, cx| {
1552 view.move_left(&MoveLeft, cx);
1553 view.select_to_beginning_of_line(
1554 &SelectToBeginningOfLine {
1555 stop_at_soft_wraps: true,
1556 },
1557 cx,
1558 );
1559 assert_eq!(
1560 view.selections.display_ranges(cx),
1561 &[
1562 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1563 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1564 ]
1565 );
1566 });
1567
1568 _ = view.update(cx, |view, cx| {
1569 view.select_to_beginning_of_line(
1570 &SelectToBeginningOfLine {
1571 stop_at_soft_wraps: true,
1572 },
1573 cx,
1574 );
1575 assert_eq!(
1576 view.selections.display_ranges(cx),
1577 &[
1578 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1579 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1580 ]
1581 );
1582 });
1583
1584 _ = view.update(cx, |view, cx| {
1585 view.select_to_beginning_of_line(
1586 &SelectToBeginningOfLine {
1587 stop_at_soft_wraps: true,
1588 },
1589 cx,
1590 );
1591 assert_eq!(
1592 view.selections.display_ranges(cx),
1593 &[
1594 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1595 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1596 ]
1597 );
1598 });
1599
1600 _ = view.update(cx, |view, cx| {
1601 view.select_to_end_of_line(
1602 &SelectToEndOfLine {
1603 stop_at_soft_wraps: true,
1604 },
1605 cx,
1606 );
1607 assert_eq!(
1608 view.selections.display_ranges(cx),
1609 &[
1610 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1611 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1612 ]
1613 );
1614 });
1615
1616 _ = view.update(cx, |view, cx| {
1617 view.delete_to_end_of_line(&DeleteToEndOfLine, cx);
1618 assert_eq!(view.display_text(cx), "ab\n de");
1619 assert_eq!(
1620 view.selections.display_ranges(cx),
1621 &[
1622 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1623 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1624 ]
1625 );
1626 });
1627
1628 _ = view.update(cx, |view, cx| {
1629 view.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
1630 assert_eq!(view.display_text(cx), "\n");
1631 assert_eq!(
1632 view.selections.display_ranges(cx),
1633 &[
1634 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1635 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1636 ]
1637 );
1638 });
1639}
1640
1641#[gpui::test]
1642fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1643 init_test(cx, |_| {});
1644 let move_to_beg = MoveToBeginningOfLine {
1645 stop_at_soft_wraps: false,
1646 };
1647
1648 let move_to_end = MoveToEndOfLine {
1649 stop_at_soft_wraps: false,
1650 };
1651
1652 let view = cx.add_window(|cx| {
1653 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1654 build_editor(buffer, cx)
1655 });
1656
1657 _ = view.update(cx, |view, cx| {
1658 view.set_wrap_width(Some(140.0.into()), cx);
1659
1660 // We expect the following lines after wrapping
1661 // ```
1662 // thequickbrownfox
1663 // jumpedoverthelazydo
1664 // gs
1665 // ```
1666 // The final `gs` was soft-wrapped onto a new line.
1667 assert_eq!(
1668 "thequickbrownfox\njumpedoverthelaz\nydogs",
1669 view.display_text(cx),
1670 );
1671
1672 // First, let's assert behavior on the first line, that was not soft-wrapped.
1673 // Start the cursor at the `k` on the first line
1674 view.change_selections(None, cx, |s| {
1675 s.select_display_ranges([
1676 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1677 ]);
1678 });
1679
1680 // Moving to the beginning of the line should put us at the beginning of the line.
1681 view.move_to_beginning_of_line(&move_to_beg, cx);
1682 assert_eq!(
1683 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1684 view.selections.display_ranges(cx)
1685 );
1686
1687 // Moving to the end of the line should put us at the end of the line.
1688 view.move_to_end_of_line(&move_to_end, cx);
1689 assert_eq!(
1690 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1691 view.selections.display_ranges(cx)
1692 );
1693
1694 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1695 // Start the cursor at the last line (`y` that was wrapped to a new line)
1696 view.change_selections(None, cx, |s| {
1697 s.select_display_ranges([
1698 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1699 ]);
1700 });
1701
1702 // Moving to the beginning of the line should put us at the start of the second line of
1703 // display text, i.e., the `j`.
1704 view.move_to_beginning_of_line(&move_to_beg, cx);
1705 assert_eq!(
1706 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1707 view.selections.display_ranges(cx)
1708 );
1709
1710 // Moving to the beginning of the line again should be a no-op.
1711 view.move_to_beginning_of_line(&move_to_beg, cx);
1712 assert_eq!(
1713 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1714 view.selections.display_ranges(cx)
1715 );
1716
1717 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1718 // next display line.
1719 view.move_to_end_of_line(&move_to_end, cx);
1720 assert_eq!(
1721 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1722 view.selections.display_ranges(cx)
1723 );
1724
1725 // Moving to the end of the line again should be a no-op.
1726 view.move_to_end_of_line(&move_to_end, cx);
1727 assert_eq!(
1728 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1729 view.selections.display_ranges(cx)
1730 );
1731 });
1732}
1733
1734#[gpui::test]
1735fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1736 init_test(cx, |_| {});
1737
1738 let view = cx.add_window(|cx| {
1739 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1740 build_editor(buffer, cx)
1741 });
1742 _ = view.update(cx, |view, cx| {
1743 view.change_selections(None, cx, |s| {
1744 s.select_display_ranges([
1745 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1746 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1747 ])
1748 });
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_previous_word_start(&MoveToPreviousWordStart, 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_to_next_word_end(&MoveToNextWordEnd, cx);
1772 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", view, cx);
1773
1774 view.move_right(&MoveRight, cx);
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_previous_word_start(&SelectToPreviousWordStart, cx);
1779 assert_selection_ranges("use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}", view, cx);
1780
1781 view.select_to_next_word_end(&SelectToNextWordEnd, cx);
1782 assert_selection_ranges("use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}", view, cx);
1783 });
1784}
1785
1786#[gpui::test]
1787fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1788 init_test(cx, |_| {});
1789
1790 let view = cx.add_window(|cx| {
1791 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1792 build_editor(buffer, cx)
1793 });
1794
1795 _ = view.update(cx, |view, cx| {
1796 view.set_wrap_width(Some(140.0.into()), cx);
1797 assert_eq!(
1798 view.display_text(cx),
1799 "use one::{\n two::three::\n four::five\n};"
1800 );
1801
1802 view.change_selections(None, cx, |s| {
1803 s.select_display_ranges([
1804 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1805 ]);
1806 });
1807
1808 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1809 assert_eq!(
1810 view.selections.display_ranges(cx),
1811 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1812 );
1813
1814 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1815 assert_eq!(
1816 view.selections.display_ranges(cx),
1817 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1818 );
1819
1820 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1821 assert_eq!(
1822 view.selections.display_ranges(cx),
1823 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1824 );
1825
1826 view.move_to_next_word_end(&MoveToNextWordEnd, cx);
1827 assert_eq!(
1828 view.selections.display_ranges(cx),
1829 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1830 );
1831
1832 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1833 assert_eq!(
1834 view.selections.display_ranges(cx),
1835 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1836 );
1837
1838 view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
1839 assert_eq!(
1840 view.selections.display_ranges(cx),
1841 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1842 );
1843 });
1844}
1845
1846#[gpui::test]
1847async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
1848 init_test(cx, |_| {});
1849 let mut cx = EditorTestContext::new(cx).await;
1850
1851 let line_height = cx.editor(|editor, cx| {
1852 editor
1853 .style()
1854 .unwrap()
1855 .text
1856 .line_height_in_pixels(cx.rem_size())
1857 });
1858 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
1859
1860 cx.set_state(
1861 &r#"ˇone
1862 two
1863
1864 three
1865 fourˇ
1866 five
1867
1868 six"#
1869 .unindent(),
1870 );
1871
1872 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1873 cx.assert_editor_state(
1874 &r#"one
1875 two
1876 ˇ
1877 three
1878 four
1879 five
1880 ˇ
1881 six"#
1882 .unindent(),
1883 );
1884
1885 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1886 cx.assert_editor_state(
1887 &r#"one
1888 two
1889
1890 three
1891 four
1892 five
1893 ˇ
1894 sixˇ"#
1895 .unindent(),
1896 );
1897
1898 cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
1899 cx.assert_editor_state(
1900 &r#"one
1901 two
1902
1903 three
1904 four
1905 five
1906
1907 sixˇ"#
1908 .unindent(),
1909 );
1910
1911 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1912 cx.assert_editor_state(
1913 &r#"one
1914 two
1915
1916 three
1917 four
1918 five
1919 ˇ
1920 six"#
1921 .unindent(),
1922 );
1923
1924 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1925 cx.assert_editor_state(
1926 &r#"one
1927 two
1928 ˇ
1929 three
1930 four
1931 five
1932
1933 six"#
1934 .unindent(),
1935 );
1936
1937 cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
1938 cx.assert_editor_state(
1939 &r#"ˇone
1940 two
1941
1942 three
1943 four
1944 five
1945
1946 six"#
1947 .unindent(),
1948 );
1949}
1950
1951#[gpui::test]
1952async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
1953 init_test(cx, |_| {});
1954 let mut cx = EditorTestContext::new(cx).await;
1955 let line_height = cx.editor(|editor, cx| {
1956 editor
1957 .style()
1958 .unwrap()
1959 .text
1960 .line_height_in_pixels(cx.rem_size())
1961 });
1962 let window = cx.window;
1963 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
1964
1965 cx.set_state(
1966 r#"ˇone
1967 two
1968 three
1969 four
1970 five
1971 six
1972 seven
1973 eight
1974 nine
1975 ten
1976 "#,
1977 );
1978
1979 cx.update_editor(|editor, cx| {
1980 assert_eq!(
1981 editor.snapshot(cx).scroll_position(),
1982 gpui::Point::new(0., 0.)
1983 );
1984 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1985 assert_eq!(
1986 editor.snapshot(cx).scroll_position(),
1987 gpui::Point::new(0., 3.)
1988 );
1989 editor.scroll_screen(&ScrollAmount::Page(1.), cx);
1990 assert_eq!(
1991 editor.snapshot(cx).scroll_position(),
1992 gpui::Point::new(0., 6.)
1993 );
1994 editor.scroll_screen(&ScrollAmount::Page(-1.), cx);
1995 assert_eq!(
1996 editor.snapshot(cx).scroll_position(),
1997 gpui::Point::new(0., 3.)
1998 );
1999
2000 editor.scroll_screen(&ScrollAmount::Page(-0.5), cx);
2001 assert_eq!(
2002 editor.snapshot(cx).scroll_position(),
2003 gpui::Point::new(0., 1.)
2004 );
2005 editor.scroll_screen(&ScrollAmount::Page(0.5), cx);
2006 assert_eq!(
2007 editor.snapshot(cx).scroll_position(),
2008 gpui::Point::new(0., 3.)
2009 );
2010 });
2011}
2012
2013#[gpui::test]
2014async fn test_autoscroll(cx: &mut gpui::TestAppContext) {
2015 init_test(cx, |_| {});
2016 let mut cx = EditorTestContext::new(cx).await;
2017
2018 let line_height = cx.update_editor(|editor, cx| {
2019 editor.set_vertical_scroll_margin(2, cx);
2020 editor
2021 .style()
2022 .unwrap()
2023 .text
2024 .line_height_in_pixels(cx.rem_size())
2025 });
2026 let window = cx.window;
2027 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2028
2029 cx.set_state(
2030 r#"ˇone
2031 two
2032 three
2033 four
2034 five
2035 six
2036 seven
2037 eight
2038 nine
2039 ten
2040 "#,
2041 );
2042 cx.update_editor(|editor, cx| {
2043 assert_eq!(
2044 editor.snapshot(cx).scroll_position(),
2045 gpui::Point::new(0., 0.0)
2046 );
2047 });
2048
2049 // Add a cursor below the visible area. Since both cursors cannot fit
2050 // on screen, the editor autoscrolls to reveal the newest cursor, and
2051 // allows the vertical scroll margin below that cursor.
2052 cx.update_editor(|editor, cx| {
2053 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2054 selections.select_ranges([
2055 Point::new(0, 0)..Point::new(0, 0),
2056 Point::new(6, 0)..Point::new(6, 0),
2057 ]);
2058 })
2059 });
2060 cx.update_editor(|editor, cx| {
2061 assert_eq!(
2062 editor.snapshot(cx).scroll_position(),
2063 gpui::Point::new(0., 3.0)
2064 );
2065 });
2066
2067 // Move down. The editor cursor scrolls down to track the newest cursor.
2068 cx.update_editor(|editor, cx| {
2069 editor.move_down(&Default::default(), cx);
2070 });
2071 cx.update_editor(|editor, cx| {
2072 assert_eq!(
2073 editor.snapshot(cx).scroll_position(),
2074 gpui::Point::new(0., 4.0)
2075 );
2076 });
2077
2078 // Add a cursor above the visible area. Since both cursors fit on screen,
2079 // the editor scrolls to show both.
2080 cx.update_editor(|editor, cx| {
2081 editor.change_selections(Some(Autoscroll::fit()), cx, |selections| {
2082 selections.select_ranges([
2083 Point::new(1, 0)..Point::new(1, 0),
2084 Point::new(6, 0)..Point::new(6, 0),
2085 ]);
2086 })
2087 });
2088 cx.update_editor(|editor, cx| {
2089 assert_eq!(
2090 editor.snapshot(cx).scroll_position(),
2091 gpui::Point::new(0., 1.0)
2092 );
2093 });
2094}
2095
2096#[gpui::test]
2097async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
2098 init_test(cx, |_| {});
2099 let mut cx = EditorTestContext::new(cx).await;
2100
2101 let line_height = cx.editor(|editor, cx| {
2102 editor
2103 .style()
2104 .unwrap()
2105 .text
2106 .line_height_in_pixels(cx.rem_size())
2107 });
2108 let window = cx.window;
2109 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2110 cx.set_state(
2111 &r#"
2112 ˇone
2113 two
2114 threeˇ
2115 four
2116 five
2117 six
2118 seven
2119 eight
2120 nine
2121 ten
2122 "#
2123 .unindent(),
2124 );
2125
2126 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2127 cx.assert_editor_state(
2128 &r#"
2129 one
2130 two
2131 three
2132 ˇfour
2133 five
2134 sixˇ
2135 seven
2136 eight
2137 nine
2138 ten
2139 "#
2140 .unindent(),
2141 );
2142
2143 cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
2144 cx.assert_editor_state(
2145 &r#"
2146 one
2147 two
2148 three
2149 four
2150 five
2151 six
2152 ˇseven
2153 eight
2154 nineˇ
2155 ten
2156 "#
2157 .unindent(),
2158 );
2159
2160 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2161 cx.assert_editor_state(
2162 &r#"
2163 one
2164 two
2165 three
2166 ˇfour
2167 five
2168 sixˇ
2169 seven
2170 eight
2171 nine
2172 ten
2173 "#
2174 .unindent(),
2175 );
2176
2177 cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
2178 cx.assert_editor_state(
2179 &r#"
2180 ˇone
2181 two
2182 threeˇ
2183 four
2184 five
2185 six
2186 seven
2187 eight
2188 nine
2189 ten
2190 "#
2191 .unindent(),
2192 );
2193
2194 // Test select collapsing
2195 cx.update_editor(|editor, cx| {
2196 editor.move_page_down(&MovePageDown::default(), cx);
2197 editor.move_page_down(&MovePageDown::default(), cx);
2198 editor.move_page_down(&MovePageDown::default(), cx);
2199 });
2200 cx.assert_editor_state(
2201 &r#"
2202 one
2203 two
2204 three
2205 four
2206 five
2207 six
2208 seven
2209 eight
2210 nine
2211 ˇten
2212 ˇ"#
2213 .unindent(),
2214 );
2215}
2216
2217#[gpui::test]
2218async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
2219 init_test(cx, |_| {});
2220 let mut cx = EditorTestContext::new(cx).await;
2221 cx.set_state("one «two threeˇ» four");
2222 cx.update_editor(|editor, cx| {
2223 editor.delete_to_beginning_of_line(&DeleteToBeginningOfLine, cx);
2224 assert_eq!(editor.text(cx), " four");
2225 });
2226}
2227
2228#[gpui::test]
2229fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2230 init_test(cx, |_| {});
2231
2232 let view = cx.add_window(|cx| {
2233 let buffer = MultiBuffer::build_simple("one two three four", cx);
2234 build_editor(buffer.clone(), cx)
2235 });
2236
2237 _ = view.update(cx, |view, cx| {
2238 view.change_selections(None, cx, |s| {
2239 s.select_display_ranges([
2240 // an empty selection - the preceding word fragment is deleted
2241 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2242 // characters selected - they are deleted
2243 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2244 ])
2245 });
2246 view.delete_to_previous_word_start(
2247 &DeleteToPreviousWordStart {
2248 ignore_newlines: false,
2249 },
2250 cx,
2251 );
2252 assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
2253 });
2254
2255 _ = view.update(cx, |view, cx| {
2256 view.change_selections(None, cx, |s| {
2257 s.select_display_ranges([
2258 // an empty selection - the following word fragment is deleted
2259 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2260 // characters selected - they are deleted
2261 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2262 ])
2263 });
2264 view.delete_to_next_word_end(
2265 &DeleteToNextWordEnd {
2266 ignore_newlines: false,
2267 },
2268 cx,
2269 );
2270 assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
2271 });
2272}
2273
2274#[gpui::test]
2275fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2276 init_test(cx, |_| {});
2277
2278 let view = cx.add_window(|cx| {
2279 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2280 build_editor(buffer.clone(), cx)
2281 });
2282 let del_to_prev_word_start = DeleteToPreviousWordStart {
2283 ignore_newlines: false,
2284 };
2285 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2286 ignore_newlines: true,
2287 };
2288
2289 _ = view.update(cx, |view, cx| {
2290 view.change_selections(None, cx, |s| {
2291 s.select_display_ranges([
2292 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2293 ])
2294 });
2295 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2296 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2297 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2298 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2299 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2300 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2\n");
2301 view.delete_to_previous_word_start(&del_to_prev_word_start, cx);
2302 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n2");
2303 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2304 assert_eq!(view.buffer.read(cx).read(cx).text(), "one\n");
2305 view.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, cx);
2306 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2307 });
2308}
2309
2310#[gpui::test]
2311fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2312 init_test(cx, |_| {});
2313
2314 let view = cx.add_window(|cx| {
2315 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2316 build_editor(buffer.clone(), cx)
2317 });
2318 let del_to_next_word_end = DeleteToNextWordEnd {
2319 ignore_newlines: false,
2320 };
2321 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2322 ignore_newlines: true,
2323 };
2324
2325 _ = view.update(cx, |view, cx| {
2326 view.change_selections(None, cx, |s| {
2327 s.select_display_ranges([
2328 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2329 ])
2330 });
2331 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2332 assert_eq!(
2333 view.buffer.read(cx).read(cx).text(),
2334 "one\n two\nthree\n four"
2335 );
2336 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2337 assert_eq!(
2338 view.buffer.read(cx).read(cx).text(),
2339 "\n two\nthree\n four"
2340 );
2341 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2342 assert_eq!(view.buffer.read(cx).read(cx).text(), "two\nthree\n four");
2343 view.delete_to_next_word_end(&del_to_next_word_end, cx);
2344 assert_eq!(view.buffer.read(cx).read(cx).text(), "\nthree\n four");
2345 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2346 assert_eq!(view.buffer.read(cx).read(cx).text(), "\n four");
2347 view.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, cx);
2348 assert_eq!(view.buffer.read(cx).read(cx).text(), "");
2349 });
2350}
2351
2352#[gpui::test]
2353fn test_newline(cx: &mut TestAppContext) {
2354 init_test(cx, |_| {});
2355
2356 let view = cx.add_window(|cx| {
2357 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2358 build_editor(buffer.clone(), cx)
2359 });
2360
2361 _ = view.update(cx, |view, cx| {
2362 view.change_selections(None, cx, |s| {
2363 s.select_display_ranges([
2364 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2365 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2366 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2367 ])
2368 });
2369
2370 view.newline(&Newline, cx);
2371 assert_eq!(view.text(cx), "aa\naa\n \n bb\n bb\n");
2372 });
2373}
2374
2375#[gpui::test]
2376fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2377 init_test(cx, |_| {});
2378
2379 let editor = cx.add_window(|cx| {
2380 let buffer = MultiBuffer::build_simple(
2381 "
2382 a
2383 b(
2384 X
2385 )
2386 c(
2387 X
2388 )
2389 "
2390 .unindent()
2391 .as_str(),
2392 cx,
2393 );
2394 let mut editor = build_editor(buffer.clone(), cx);
2395 editor.change_selections(None, cx, |s| {
2396 s.select_ranges([
2397 Point::new(2, 4)..Point::new(2, 5),
2398 Point::new(5, 4)..Point::new(5, 5),
2399 ])
2400 });
2401 editor
2402 });
2403
2404 _ = editor.update(cx, |editor, cx| {
2405 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2406 editor.buffer.update(cx, |buffer, cx| {
2407 buffer.edit(
2408 [
2409 (Point::new(1, 2)..Point::new(3, 0), ""),
2410 (Point::new(4, 2)..Point::new(6, 0), ""),
2411 ],
2412 None,
2413 cx,
2414 );
2415 assert_eq!(
2416 buffer.read(cx).text(),
2417 "
2418 a
2419 b()
2420 c()
2421 "
2422 .unindent()
2423 );
2424 });
2425 assert_eq!(
2426 editor.selections.ranges(cx),
2427 &[
2428 Point::new(1, 2)..Point::new(1, 2),
2429 Point::new(2, 2)..Point::new(2, 2),
2430 ],
2431 );
2432
2433 editor.newline(&Newline, cx);
2434 assert_eq!(
2435 editor.text(cx),
2436 "
2437 a
2438 b(
2439 )
2440 c(
2441 )
2442 "
2443 .unindent()
2444 );
2445
2446 // The selections are moved after the inserted newlines
2447 assert_eq!(
2448 editor.selections.ranges(cx),
2449 &[
2450 Point::new(2, 0)..Point::new(2, 0),
2451 Point::new(4, 0)..Point::new(4, 0),
2452 ],
2453 );
2454 });
2455}
2456
2457#[gpui::test]
2458async fn test_newline_above(cx: &mut gpui::TestAppContext) {
2459 init_test(cx, |settings| {
2460 settings.defaults.tab_size = NonZeroU32::new(4)
2461 });
2462
2463 let language = Arc::new(
2464 Language::new(
2465 LanguageConfig::default(),
2466 Some(tree_sitter_rust::LANGUAGE.into()),
2467 )
2468 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2469 .unwrap(),
2470 );
2471
2472 let mut cx = EditorTestContext::new(cx).await;
2473 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2474 cx.set_state(indoc! {"
2475 const a: ˇA = (
2476 (ˇ
2477 «const_functionˇ»(ˇ),
2478 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2479 )ˇ
2480 ˇ);ˇ
2481 "});
2482
2483 cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
2484 cx.assert_editor_state(indoc! {"
2485 ˇ
2486 const a: A = (
2487 ˇ
2488 (
2489 ˇ
2490 ˇ
2491 const_function(),
2492 ˇ
2493 ˇ
2494 ˇ
2495 ˇ
2496 something_else,
2497 ˇ
2498 )
2499 ˇ
2500 ˇ
2501 );
2502 "});
2503}
2504
2505#[gpui::test]
2506async fn test_newline_below(cx: &mut gpui::TestAppContext) {
2507 init_test(cx, |settings| {
2508 settings.defaults.tab_size = NonZeroU32::new(4)
2509 });
2510
2511 let language = Arc::new(
2512 Language::new(
2513 LanguageConfig::default(),
2514 Some(tree_sitter_rust::LANGUAGE.into()),
2515 )
2516 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2517 .unwrap(),
2518 );
2519
2520 let mut cx = EditorTestContext::new(cx).await;
2521 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2522 cx.set_state(indoc! {"
2523 const a: ˇA = (
2524 (ˇ
2525 «const_functionˇ»(ˇ),
2526 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2527 )ˇ
2528 ˇ);ˇ
2529 "});
2530
2531 cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
2532 cx.assert_editor_state(indoc! {"
2533 const a: A = (
2534 ˇ
2535 (
2536 ˇ
2537 const_function(),
2538 ˇ
2539 ˇ
2540 something_else,
2541 ˇ
2542 ˇ
2543 ˇ
2544 ˇ
2545 )
2546 ˇ
2547 );
2548 ˇ
2549 ˇ
2550 "});
2551}
2552
2553#[gpui::test]
2554async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
2555 init_test(cx, |settings| {
2556 settings.defaults.tab_size = NonZeroU32::new(4)
2557 });
2558
2559 let language = Arc::new(Language::new(
2560 LanguageConfig {
2561 line_comments: vec!["//".into()],
2562 ..LanguageConfig::default()
2563 },
2564 None,
2565 ));
2566 {
2567 let mut cx = EditorTestContext::new(cx).await;
2568 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2569 cx.set_state(indoc! {"
2570 // Fooˇ
2571 "});
2572
2573 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2574 cx.assert_editor_state(indoc! {"
2575 // Foo
2576 //ˇ
2577 "});
2578 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2579 cx.set_state(indoc! {"
2580 ˇ// Foo
2581 "});
2582 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2583 cx.assert_editor_state(indoc! {"
2584
2585 ˇ// Foo
2586 "});
2587 }
2588 // Ensure that comment continuations can be disabled.
2589 update_test_language_settings(cx, |settings| {
2590 settings.defaults.extend_comment_on_newline = Some(false);
2591 });
2592 let mut cx = EditorTestContext::new(cx).await;
2593 cx.set_state(indoc! {"
2594 // Fooˇ
2595 "});
2596 cx.update_editor(|e, cx| e.newline(&Newline, cx));
2597 cx.assert_editor_state(indoc! {"
2598 // Foo
2599 ˇ
2600 "});
2601}
2602
2603#[gpui::test]
2604fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2605 init_test(cx, |_| {});
2606
2607 let editor = cx.add_window(|cx| {
2608 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2609 let mut editor = build_editor(buffer.clone(), cx);
2610 editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
2611 editor
2612 });
2613
2614 _ = editor.update(cx, |editor, cx| {
2615 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2616 editor.buffer.update(cx, |buffer, cx| {
2617 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2618 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2619 });
2620 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2621
2622 editor.insert("Z", cx);
2623 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2624
2625 // The selections are moved after the inserted characters
2626 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2627 });
2628}
2629
2630#[gpui::test]
2631async fn test_tab(cx: &mut gpui::TestAppContext) {
2632 init_test(cx, |settings| {
2633 settings.defaults.tab_size = NonZeroU32::new(3)
2634 });
2635
2636 let mut cx = EditorTestContext::new(cx).await;
2637 cx.set_state(indoc! {"
2638 ˇabˇc
2639 ˇ🏀ˇ🏀ˇefg
2640 dˇ
2641 "});
2642 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2643 cx.assert_editor_state(indoc! {"
2644 ˇab ˇc
2645 ˇ🏀 ˇ🏀 ˇefg
2646 d ˇ
2647 "});
2648
2649 cx.set_state(indoc! {"
2650 a
2651 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2652 "});
2653 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2654 cx.assert_editor_state(indoc! {"
2655 a
2656 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2657 "});
2658}
2659
2660#[gpui::test]
2661async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
2662 init_test(cx, |_| {});
2663
2664 let mut cx = EditorTestContext::new(cx).await;
2665 let language = Arc::new(
2666 Language::new(
2667 LanguageConfig::default(),
2668 Some(tree_sitter_rust::LANGUAGE.into()),
2669 )
2670 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2671 .unwrap(),
2672 );
2673 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2674
2675 // cursors that are already at the suggested indent level insert
2676 // a soft tab. cursors that are to the left of the suggested indent
2677 // auto-indent their line.
2678 cx.set_state(indoc! {"
2679 ˇ
2680 const a: B = (
2681 c(
2682 d(
2683 ˇ
2684 )
2685 ˇ
2686 ˇ )
2687 );
2688 "});
2689 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2690 cx.assert_editor_state(indoc! {"
2691 ˇ
2692 const a: B = (
2693 c(
2694 d(
2695 ˇ
2696 )
2697 ˇ
2698 ˇ)
2699 );
2700 "});
2701
2702 // handle auto-indent when there are multiple cursors on the same line
2703 cx.set_state(indoc! {"
2704 const a: B = (
2705 c(
2706 ˇ ˇ
2707 ˇ )
2708 );
2709 "});
2710 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2711 cx.assert_editor_state(indoc! {"
2712 const a: B = (
2713 c(
2714 ˇ
2715 ˇ)
2716 );
2717 "});
2718}
2719
2720#[gpui::test]
2721async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
2722 init_test(cx, |settings| {
2723 settings.defaults.tab_size = NonZeroU32::new(4)
2724 });
2725
2726 let language = Arc::new(
2727 Language::new(
2728 LanguageConfig::default(),
2729 Some(tree_sitter_rust::LANGUAGE.into()),
2730 )
2731 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2732 .unwrap(),
2733 );
2734
2735 let mut cx = EditorTestContext::new(cx).await;
2736 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2737 cx.set_state(indoc! {"
2738 fn a() {
2739 if b {
2740 \t ˇc
2741 }
2742 }
2743 "});
2744
2745 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2746 cx.assert_editor_state(indoc! {"
2747 fn a() {
2748 if b {
2749 ˇc
2750 }
2751 }
2752 "});
2753}
2754
2755#[gpui::test]
2756async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
2757 init_test(cx, |settings| {
2758 settings.defaults.tab_size = NonZeroU32::new(4);
2759 });
2760
2761 let mut cx = EditorTestContext::new(cx).await;
2762
2763 cx.set_state(indoc! {"
2764 «oneˇ» «twoˇ»
2765 three
2766 four
2767 "});
2768 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2769 cx.assert_editor_state(indoc! {"
2770 «oneˇ» «twoˇ»
2771 three
2772 four
2773 "});
2774
2775 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2776 cx.assert_editor_state(indoc! {"
2777 «oneˇ» «twoˇ»
2778 three
2779 four
2780 "});
2781
2782 // select across line ending
2783 cx.set_state(indoc! {"
2784 one two
2785 t«hree
2786 ˇ» four
2787 "});
2788 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2789 cx.assert_editor_state(indoc! {"
2790 one two
2791 t«hree
2792 ˇ» four
2793 "});
2794
2795 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2796 cx.assert_editor_state(indoc! {"
2797 one two
2798 t«hree
2799 ˇ» four
2800 "});
2801
2802 // Ensure that indenting/outdenting works when the cursor is at column 0.
2803 cx.set_state(indoc! {"
2804 one two
2805 ˇthree
2806 four
2807 "});
2808 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2809 cx.assert_editor_state(indoc! {"
2810 one two
2811 ˇthree
2812 four
2813 "});
2814
2815 cx.set_state(indoc! {"
2816 one two
2817 ˇ three
2818 four
2819 "});
2820 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2821 cx.assert_editor_state(indoc! {"
2822 one two
2823 ˇthree
2824 four
2825 "});
2826}
2827
2828#[gpui::test]
2829async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
2830 init_test(cx, |settings| {
2831 settings.defaults.hard_tabs = Some(true);
2832 });
2833
2834 let mut cx = EditorTestContext::new(cx).await;
2835
2836 // select two ranges on one line
2837 cx.set_state(indoc! {"
2838 «oneˇ» «twoˇ»
2839 three
2840 four
2841 "});
2842 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2843 cx.assert_editor_state(indoc! {"
2844 \t«oneˇ» «twoˇ»
2845 three
2846 four
2847 "});
2848 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2849 cx.assert_editor_state(indoc! {"
2850 \t\t«oneˇ» «twoˇ»
2851 three
2852 four
2853 "});
2854 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2855 cx.assert_editor_state(indoc! {"
2856 \t«oneˇ» «twoˇ»
2857 three
2858 four
2859 "});
2860 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2861 cx.assert_editor_state(indoc! {"
2862 «oneˇ» «twoˇ»
2863 three
2864 four
2865 "});
2866
2867 // select across a line ending
2868 cx.set_state(indoc! {"
2869 one two
2870 t«hree
2871 ˇ»four
2872 "});
2873 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2874 cx.assert_editor_state(indoc! {"
2875 one two
2876 \tt«hree
2877 ˇ»four
2878 "});
2879 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2880 cx.assert_editor_state(indoc! {"
2881 one two
2882 \t\tt«hree
2883 ˇ»four
2884 "});
2885 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2886 cx.assert_editor_state(indoc! {"
2887 one two
2888 \tt«hree
2889 ˇ»four
2890 "});
2891 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2892 cx.assert_editor_state(indoc! {"
2893 one two
2894 t«hree
2895 ˇ»four
2896 "});
2897
2898 // Ensure that indenting/outdenting works when the cursor is at column 0.
2899 cx.set_state(indoc! {"
2900 one two
2901 ˇthree
2902 four
2903 "});
2904 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2905 cx.assert_editor_state(indoc! {"
2906 one two
2907 ˇthree
2908 four
2909 "});
2910 cx.update_editor(|e, cx| e.tab(&Tab, cx));
2911 cx.assert_editor_state(indoc! {"
2912 one two
2913 \tˇthree
2914 four
2915 "});
2916 cx.update_editor(|e, cx| e.tab_prev(&TabPrev, cx));
2917 cx.assert_editor_state(indoc! {"
2918 one two
2919 ˇthree
2920 four
2921 "});
2922}
2923
2924#[gpui::test]
2925fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
2926 init_test(cx, |settings| {
2927 settings.languages.extend([
2928 (
2929 "TOML".into(),
2930 LanguageSettingsContent {
2931 tab_size: NonZeroU32::new(2),
2932 ..Default::default()
2933 },
2934 ),
2935 (
2936 "Rust".into(),
2937 LanguageSettingsContent {
2938 tab_size: NonZeroU32::new(4),
2939 ..Default::default()
2940 },
2941 ),
2942 ]);
2943 });
2944
2945 let toml_language = Arc::new(Language::new(
2946 LanguageConfig {
2947 name: "TOML".into(),
2948 ..Default::default()
2949 },
2950 None,
2951 ));
2952 let rust_language = Arc::new(Language::new(
2953 LanguageConfig {
2954 name: "Rust".into(),
2955 ..Default::default()
2956 },
2957 None,
2958 ));
2959
2960 let toml_buffer =
2961 cx.new_model(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
2962 let rust_buffer = cx.new_model(|cx| {
2963 Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx)
2964 });
2965 let multibuffer = cx.new_model(|cx| {
2966 let mut multibuffer = MultiBuffer::new(ReadWrite);
2967 multibuffer.push_excerpts(
2968 toml_buffer.clone(),
2969 [ExcerptRange {
2970 context: Point::new(0, 0)..Point::new(2, 0),
2971 primary: None,
2972 }],
2973 cx,
2974 );
2975 multibuffer.push_excerpts(
2976 rust_buffer.clone(),
2977 [ExcerptRange {
2978 context: Point::new(0, 0)..Point::new(1, 0),
2979 primary: None,
2980 }],
2981 cx,
2982 );
2983 multibuffer
2984 });
2985
2986 cx.add_window(|cx| {
2987 let mut editor = build_editor(multibuffer, cx);
2988
2989 assert_eq!(
2990 editor.text(cx),
2991 indoc! {"
2992 a = 1
2993 b = 2
2994
2995 const c: usize = 3;
2996 "}
2997 );
2998
2999 select_ranges(
3000 &mut editor,
3001 indoc! {"
3002 «aˇ» = 1
3003 b = 2
3004
3005 «const c:ˇ» usize = 3;
3006 "},
3007 cx,
3008 );
3009
3010 editor.tab(&Tab, cx);
3011 assert_text_with_selections(
3012 &mut editor,
3013 indoc! {"
3014 «aˇ» = 1
3015 b = 2
3016
3017 «const c:ˇ» usize = 3;
3018 "},
3019 cx,
3020 );
3021 editor.tab_prev(&TabPrev, cx);
3022 assert_text_with_selections(
3023 &mut editor,
3024 indoc! {"
3025 «aˇ» = 1
3026 b = 2
3027
3028 «const c:ˇ» usize = 3;
3029 "},
3030 cx,
3031 );
3032
3033 editor
3034 });
3035}
3036
3037#[gpui::test]
3038async fn test_backspace(cx: &mut gpui::TestAppContext) {
3039 init_test(cx, |_| {});
3040
3041 let mut cx = EditorTestContext::new(cx).await;
3042
3043 // Basic backspace
3044 cx.set_state(indoc! {"
3045 onˇe two three
3046 fou«rˇ» five six
3047 seven «ˇeight nine
3048 »ten
3049 "});
3050 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3051 cx.assert_editor_state(indoc! {"
3052 oˇe two three
3053 fouˇ five six
3054 seven ˇten
3055 "});
3056
3057 // Test backspace inside and around indents
3058 cx.set_state(indoc! {"
3059 zero
3060 ˇone
3061 ˇtwo
3062 ˇ ˇ ˇ three
3063 ˇ ˇ four
3064 "});
3065 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3066 cx.assert_editor_state(indoc! {"
3067 zero
3068 ˇone
3069 ˇtwo
3070 ˇ threeˇ four
3071 "});
3072
3073 // Test backspace with line_mode set to true
3074 cx.update_editor(|e, _| e.selections.line_mode = true);
3075 cx.set_state(indoc! {"
3076 The ˇquick ˇbrown
3077 fox jumps over
3078 the lazy dog
3079 ˇThe qu«ick bˇ»rown"});
3080 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3081 cx.assert_editor_state(indoc! {"
3082 ˇfox jumps over
3083 the lazy dogˇ"});
3084}
3085
3086#[gpui::test]
3087async fn test_delete(cx: &mut gpui::TestAppContext) {
3088 init_test(cx, |_| {});
3089
3090 let mut cx = EditorTestContext::new(cx).await;
3091 cx.set_state(indoc! {"
3092 onˇe two three
3093 fou«rˇ» five six
3094 seven «ˇeight nine
3095 »ten
3096 "});
3097 cx.update_editor(|e, cx| e.delete(&Delete, cx));
3098 cx.assert_editor_state(indoc! {"
3099 onˇ two three
3100 fouˇ five six
3101 seven ˇten
3102 "});
3103
3104 // Test backspace with line_mode set to true
3105 cx.update_editor(|e, _| e.selections.line_mode = true);
3106 cx.set_state(indoc! {"
3107 The ˇquick ˇbrown
3108 fox «ˇjum»ps over
3109 the lazy dog
3110 ˇThe qu«ick bˇ»rown"});
3111 cx.update_editor(|e, cx| e.backspace(&Backspace, cx));
3112 cx.assert_editor_state("ˇthe lazy dogˇ");
3113}
3114
3115#[gpui::test]
3116fn test_delete_line(cx: &mut TestAppContext) {
3117 init_test(cx, |_| {});
3118
3119 let view = cx.add_window(|cx| {
3120 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3121 build_editor(buffer, cx)
3122 });
3123 _ = view.update(cx, |view, cx| {
3124 view.change_selections(None, cx, |s| {
3125 s.select_display_ranges([
3126 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3127 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3128 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3129 ])
3130 });
3131 view.delete_line(&DeleteLine, cx);
3132 assert_eq!(view.display_text(cx), "ghi");
3133 assert_eq!(
3134 view.selections.display_ranges(cx),
3135 vec![
3136 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3137 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3138 ]
3139 );
3140 });
3141
3142 let view = cx.add_window(|cx| {
3143 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3144 build_editor(buffer, cx)
3145 });
3146 _ = view.update(cx, |view, cx| {
3147 view.change_selections(None, cx, |s| {
3148 s.select_display_ranges([
3149 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3150 ])
3151 });
3152 view.delete_line(&DeleteLine, cx);
3153 assert_eq!(view.display_text(cx), "ghi\n");
3154 assert_eq!(
3155 view.selections.display_ranges(cx),
3156 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3157 );
3158 });
3159}
3160
3161#[gpui::test]
3162fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3163 init_test(cx, |_| {});
3164
3165 cx.add_window(|cx| {
3166 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3167 let mut editor = build_editor(buffer.clone(), cx);
3168 let buffer = buffer.read(cx).as_singleton().unwrap();
3169
3170 assert_eq!(
3171 editor.selections.ranges::<Point>(cx),
3172 &[Point::new(0, 0)..Point::new(0, 0)]
3173 );
3174
3175 // When on single line, replace newline at end by space
3176 editor.join_lines(&JoinLines, cx);
3177 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3178 assert_eq!(
3179 editor.selections.ranges::<Point>(cx),
3180 &[Point::new(0, 3)..Point::new(0, 3)]
3181 );
3182
3183 // When multiple lines are selected, remove newlines that are spanned by the selection
3184 editor.change_selections(None, cx, |s| {
3185 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3186 });
3187 editor.join_lines(&JoinLines, cx);
3188 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3189 assert_eq!(
3190 editor.selections.ranges::<Point>(cx),
3191 &[Point::new(0, 11)..Point::new(0, 11)]
3192 );
3193
3194 // Undo should be transactional
3195 editor.undo(&Undo, cx);
3196 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3197 assert_eq!(
3198 editor.selections.ranges::<Point>(cx),
3199 &[Point::new(0, 5)..Point::new(2, 2)]
3200 );
3201
3202 // When joining an empty line don't insert a space
3203 editor.change_selections(None, cx, |s| {
3204 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3205 });
3206 editor.join_lines(&JoinLines, cx);
3207 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3208 assert_eq!(
3209 editor.selections.ranges::<Point>(cx),
3210 [Point::new(2, 3)..Point::new(2, 3)]
3211 );
3212
3213 // We can remove trailing newlines
3214 editor.join_lines(&JoinLines, cx);
3215 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3216 assert_eq!(
3217 editor.selections.ranges::<Point>(cx),
3218 [Point::new(2, 3)..Point::new(2, 3)]
3219 );
3220
3221 // We don't blow up on the last line
3222 editor.join_lines(&JoinLines, cx);
3223 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3224 assert_eq!(
3225 editor.selections.ranges::<Point>(cx),
3226 [Point::new(2, 3)..Point::new(2, 3)]
3227 );
3228
3229 // reset to test indentation
3230 editor.buffer.update(cx, |buffer, cx| {
3231 buffer.edit(
3232 [
3233 (Point::new(1, 0)..Point::new(1, 2), " "),
3234 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3235 ],
3236 None,
3237 cx,
3238 )
3239 });
3240
3241 // We remove any leading spaces
3242 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3243 editor.change_selections(None, cx, |s| {
3244 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3245 });
3246 editor.join_lines(&JoinLines, cx);
3247 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3248
3249 // We don't insert a space for a line containing only spaces
3250 editor.join_lines(&JoinLines, cx);
3251 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3252
3253 // We ignore any leading tabs
3254 editor.join_lines(&JoinLines, cx);
3255 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3256
3257 editor
3258 });
3259}
3260
3261#[gpui::test]
3262fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3263 init_test(cx, |_| {});
3264
3265 cx.add_window(|cx| {
3266 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3267 let mut editor = build_editor(buffer.clone(), cx);
3268 let buffer = buffer.read(cx).as_singleton().unwrap();
3269
3270 editor.change_selections(None, cx, |s| {
3271 s.select_ranges([
3272 Point::new(0, 2)..Point::new(1, 1),
3273 Point::new(1, 2)..Point::new(1, 2),
3274 Point::new(3, 1)..Point::new(3, 2),
3275 ])
3276 });
3277
3278 editor.join_lines(&JoinLines, cx);
3279 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3280
3281 assert_eq!(
3282 editor.selections.ranges::<Point>(cx),
3283 [
3284 Point::new(0, 7)..Point::new(0, 7),
3285 Point::new(1, 3)..Point::new(1, 3)
3286 ]
3287 );
3288 editor
3289 });
3290}
3291
3292#[gpui::test]
3293async fn test_join_lines_with_git_diff_base(
3294 executor: BackgroundExecutor,
3295 cx: &mut gpui::TestAppContext,
3296) {
3297 init_test(cx, |_| {});
3298
3299 let mut cx = EditorTestContext::new(cx).await;
3300
3301 let diff_base = r#"
3302 Line 0
3303 Line 1
3304 Line 2
3305 Line 3
3306 "#
3307 .unindent();
3308
3309 cx.set_state(
3310 &r#"
3311 ˇLine 0
3312 Line 1
3313 Line 2
3314 Line 3
3315 "#
3316 .unindent(),
3317 );
3318
3319 cx.set_diff_base(&diff_base);
3320 executor.run_until_parked();
3321
3322 // Join lines
3323 cx.update_editor(|editor, cx| {
3324 editor.join_lines(&JoinLines, cx);
3325 });
3326 executor.run_until_parked();
3327
3328 cx.assert_editor_state(
3329 &r#"
3330 Line 0ˇ Line 1
3331 Line 2
3332 Line 3
3333 "#
3334 .unindent(),
3335 );
3336 // Join again
3337 cx.update_editor(|editor, cx| {
3338 editor.join_lines(&JoinLines, cx);
3339 });
3340 executor.run_until_parked();
3341
3342 cx.assert_editor_state(
3343 &r#"
3344 Line 0 Line 1ˇ Line 2
3345 Line 3
3346 "#
3347 .unindent(),
3348 );
3349}
3350
3351#[gpui::test]
3352async fn test_custom_newlines_cause_no_false_positive_diffs(
3353 executor: BackgroundExecutor,
3354 cx: &mut gpui::TestAppContext,
3355) {
3356 init_test(cx, |_| {});
3357 let mut cx = EditorTestContext::new(cx).await;
3358 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3359 cx.set_diff_base("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3360 executor.run_until_parked();
3361
3362 cx.update_editor(|editor, cx| {
3363 let snapshot = editor.snapshot(cx);
3364 assert_eq!(
3365 snapshot
3366 .buffer_snapshot
3367 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
3368 .collect::<Vec<_>>(),
3369 Vec::new(),
3370 "Should not have any diffs for files with custom newlines"
3371 );
3372 });
3373}
3374
3375#[gpui::test]
3376async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3377 init_test(cx, |_| {});
3378
3379 let mut cx = EditorTestContext::new(cx).await;
3380
3381 // Test sort_lines_case_insensitive()
3382 cx.set_state(indoc! {"
3383 «z
3384 y
3385 x
3386 Z
3387 Y
3388 Xˇ»
3389 "});
3390 cx.update_editor(|e, cx| e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, cx));
3391 cx.assert_editor_state(indoc! {"
3392 «x
3393 X
3394 y
3395 Y
3396 z
3397 Zˇ»
3398 "});
3399
3400 // Test reverse_lines()
3401 cx.set_state(indoc! {"
3402 «5
3403 4
3404 3
3405 2
3406 1ˇ»
3407 "});
3408 cx.update_editor(|e, cx| e.reverse_lines(&ReverseLines, cx));
3409 cx.assert_editor_state(indoc! {"
3410 «1
3411 2
3412 3
3413 4
3414 5ˇ»
3415 "});
3416
3417 // Skip testing shuffle_line()
3418
3419 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3420 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3421
3422 // Don't manipulate when cursor is on single line, but expand the selection
3423 cx.set_state(indoc! {"
3424 ddˇdd
3425 ccc
3426 bb
3427 a
3428 "});
3429 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3430 cx.assert_editor_state(indoc! {"
3431 «ddddˇ»
3432 ccc
3433 bb
3434 a
3435 "});
3436
3437 // Basic manipulate case
3438 // Start selection moves to column 0
3439 // End of selection shrinks to fit shorter line
3440 cx.set_state(indoc! {"
3441 dd«d
3442 ccc
3443 bb
3444 aaaaaˇ»
3445 "});
3446 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3447 cx.assert_editor_state(indoc! {"
3448 «aaaaa
3449 bb
3450 ccc
3451 dddˇ»
3452 "});
3453
3454 // Manipulate case with newlines
3455 cx.set_state(indoc! {"
3456 dd«d
3457 ccc
3458
3459 bb
3460 aaaaa
3461
3462 ˇ»
3463 "});
3464 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3465 cx.assert_editor_state(indoc! {"
3466 «
3467
3468 aaaaa
3469 bb
3470 ccc
3471 dddˇ»
3472
3473 "});
3474
3475 // Adding new line
3476 cx.set_state(indoc! {"
3477 aa«a
3478 bbˇ»b
3479 "});
3480 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added_line")));
3481 cx.assert_editor_state(indoc! {"
3482 «aaa
3483 bbb
3484 added_lineˇ»
3485 "});
3486
3487 // Removing line
3488 cx.set_state(indoc! {"
3489 aa«a
3490 bbbˇ»
3491 "});
3492 cx.update_editor(|e, cx| {
3493 e.manipulate_lines(cx, |lines| {
3494 lines.pop();
3495 })
3496 });
3497 cx.assert_editor_state(indoc! {"
3498 «aaaˇ»
3499 "});
3500
3501 // Removing all lines
3502 cx.set_state(indoc! {"
3503 aa«a
3504 bbbˇ»
3505 "});
3506 cx.update_editor(|e, cx| {
3507 e.manipulate_lines(cx, |lines| {
3508 lines.drain(..);
3509 })
3510 });
3511 cx.assert_editor_state(indoc! {"
3512 ˇ
3513 "});
3514}
3515
3516#[gpui::test]
3517async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3518 init_test(cx, |_| {});
3519
3520 let mut cx = EditorTestContext::new(cx).await;
3521
3522 // Consider continuous selection as single selection
3523 cx.set_state(indoc! {"
3524 Aaa«aa
3525 cˇ»c«c
3526 bb
3527 aaaˇ»aa
3528 "});
3529 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3530 cx.assert_editor_state(indoc! {"
3531 «Aaaaa
3532 ccc
3533 bb
3534 aaaaaˇ»
3535 "});
3536
3537 cx.set_state(indoc! {"
3538 Aaa«aa
3539 cˇ»c«c
3540 bb
3541 aaaˇ»aa
3542 "});
3543 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3544 cx.assert_editor_state(indoc! {"
3545 «Aaaaa
3546 ccc
3547 bbˇ»
3548 "});
3549
3550 // Consider non continuous selection as distinct dedup operations
3551 cx.set_state(indoc! {"
3552 «aaaaa
3553 bb
3554 aaaaa
3555 aaaaaˇ»
3556
3557 aaa«aaˇ»
3558 "});
3559 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3560 cx.assert_editor_state(indoc! {"
3561 «aaaaa
3562 bbˇ»
3563
3564 «aaaaaˇ»
3565 "});
3566}
3567
3568#[gpui::test]
3569async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3570 init_test(cx, |_| {});
3571
3572 let mut cx = EditorTestContext::new(cx).await;
3573
3574 cx.set_state(indoc! {"
3575 «Aaa
3576 aAa
3577 Aaaˇ»
3578 "});
3579 cx.update_editor(|e, cx| e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, cx));
3580 cx.assert_editor_state(indoc! {"
3581 «Aaa
3582 aAaˇ»
3583 "});
3584
3585 cx.set_state(indoc! {"
3586 «Aaa
3587 aAa
3588 aaAˇ»
3589 "});
3590 cx.update_editor(|e, cx| e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, cx));
3591 cx.assert_editor_state(indoc! {"
3592 «Aaaˇ»
3593 "});
3594}
3595
3596#[gpui::test]
3597async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3598 init_test(cx, |_| {});
3599
3600 let mut cx = EditorTestContext::new(cx).await;
3601
3602 // Manipulate with multiple selections on a single line
3603 cx.set_state(indoc! {"
3604 dd«dd
3605 cˇ»c«c
3606 bb
3607 aaaˇ»aa
3608 "});
3609 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3610 cx.assert_editor_state(indoc! {"
3611 «aaaaa
3612 bb
3613 ccc
3614 ddddˇ»
3615 "});
3616
3617 // Manipulate with multiple disjoin selections
3618 cx.set_state(indoc! {"
3619 5«
3620 4
3621 3
3622 2
3623 1ˇ»
3624
3625 dd«dd
3626 ccc
3627 bb
3628 aaaˇ»aa
3629 "});
3630 cx.update_editor(|e, cx| e.sort_lines_case_sensitive(&SortLinesCaseSensitive, cx));
3631 cx.assert_editor_state(indoc! {"
3632 «1
3633 2
3634 3
3635 4
3636 5ˇ»
3637
3638 «aaaaa
3639 bb
3640 ccc
3641 ddddˇ»
3642 "});
3643
3644 // Adding lines on each selection
3645 cx.set_state(indoc! {"
3646 2«
3647 1ˇ»
3648
3649 bb«bb
3650 aaaˇ»aa
3651 "});
3652 cx.update_editor(|e, cx| e.manipulate_lines(cx, |lines| lines.push("added line")));
3653 cx.assert_editor_state(indoc! {"
3654 «2
3655 1
3656 added lineˇ»
3657
3658 «bbbb
3659 aaaaa
3660 added lineˇ»
3661 "});
3662
3663 // Removing lines on each selection
3664 cx.set_state(indoc! {"
3665 2«
3666 1ˇ»
3667
3668 bb«bb
3669 aaaˇ»aa
3670 "});
3671 cx.update_editor(|e, cx| {
3672 e.manipulate_lines(cx, |lines| {
3673 lines.pop();
3674 })
3675 });
3676 cx.assert_editor_state(indoc! {"
3677 «2ˇ»
3678
3679 «bbbbˇ»
3680 "});
3681}
3682
3683#[gpui::test]
3684async fn test_manipulate_text(cx: &mut TestAppContext) {
3685 init_test(cx, |_| {});
3686
3687 let mut cx = EditorTestContext::new(cx).await;
3688
3689 // Test convert_to_upper_case()
3690 cx.set_state(indoc! {"
3691 «hello worldˇ»
3692 "});
3693 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3694 cx.assert_editor_state(indoc! {"
3695 «HELLO WORLDˇ»
3696 "});
3697
3698 // Test convert_to_lower_case()
3699 cx.set_state(indoc! {"
3700 «HELLO WORLDˇ»
3701 "});
3702 cx.update_editor(|e, cx| e.convert_to_lower_case(&ConvertToLowerCase, cx));
3703 cx.assert_editor_state(indoc! {"
3704 «hello worldˇ»
3705 "});
3706
3707 // Test multiple line, single selection case
3708 cx.set_state(indoc! {"
3709 «The quick brown
3710 fox jumps over
3711 the lazy dogˇ»
3712 "});
3713 cx.update_editor(|e, cx| e.convert_to_title_case(&ConvertToTitleCase, cx));
3714 cx.assert_editor_state(indoc! {"
3715 «The Quick Brown
3716 Fox Jumps Over
3717 The Lazy Dogˇ»
3718 "});
3719
3720 // Test multiple line, single selection case
3721 cx.set_state(indoc! {"
3722 «The quick brown
3723 fox jumps over
3724 the lazy dogˇ»
3725 "});
3726 cx.update_editor(|e, cx| e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, cx));
3727 cx.assert_editor_state(indoc! {"
3728 «TheQuickBrown
3729 FoxJumpsOver
3730 TheLazyDogˇ»
3731 "});
3732
3733 // From here on out, test more complex cases of manipulate_text()
3734
3735 // Test no selection case - should affect words cursors are in
3736 // Cursor at beginning, middle, and end of word
3737 cx.set_state(indoc! {"
3738 ˇhello big beauˇtiful worldˇ
3739 "});
3740 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3741 cx.assert_editor_state(indoc! {"
3742 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3743 "});
3744
3745 // Test multiple selections on a single line and across multiple lines
3746 cx.set_state(indoc! {"
3747 «Theˇ» quick «brown
3748 foxˇ» jumps «overˇ»
3749 the «lazyˇ» dog
3750 "});
3751 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3752 cx.assert_editor_state(indoc! {"
3753 «THEˇ» quick «BROWN
3754 FOXˇ» jumps «OVERˇ»
3755 the «LAZYˇ» dog
3756 "});
3757
3758 // Test case where text length grows
3759 cx.set_state(indoc! {"
3760 «tschüߡ»
3761 "});
3762 cx.update_editor(|e, cx| e.convert_to_upper_case(&ConvertToUpperCase, cx));
3763 cx.assert_editor_state(indoc! {"
3764 «TSCHÜSSˇ»
3765 "});
3766
3767 // Test to make sure we don't crash when text shrinks
3768 cx.set_state(indoc! {"
3769 aaa_bbbˇ
3770 "});
3771 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3772 cx.assert_editor_state(indoc! {"
3773 «aaaBbbˇ»
3774 "});
3775
3776 // Test to make sure we all aware of the fact that each word can grow and shrink
3777 // Final selections should be aware of this fact
3778 cx.set_state(indoc! {"
3779 aaa_bˇbb bbˇb_ccc ˇccc_ddd
3780 "});
3781 cx.update_editor(|e, cx| e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, cx));
3782 cx.assert_editor_state(indoc! {"
3783 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
3784 "});
3785
3786 cx.set_state(indoc! {"
3787 «hElLo, WoRld!ˇ»
3788 "});
3789 cx.update_editor(|e, cx| e.convert_to_opposite_case(&ConvertToOppositeCase, cx));
3790 cx.assert_editor_state(indoc! {"
3791 «HeLlO, wOrLD!ˇ»
3792 "});
3793}
3794
3795#[gpui::test]
3796fn test_duplicate_line(cx: &mut TestAppContext) {
3797 init_test(cx, |_| {});
3798
3799 let view = cx.add_window(|cx| {
3800 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3801 build_editor(buffer, cx)
3802 });
3803 _ = view.update(cx, |view, cx| {
3804 view.change_selections(None, cx, |s| {
3805 s.select_display_ranges([
3806 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3807 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3808 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3809 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3810 ])
3811 });
3812 view.duplicate_line_down(&DuplicateLineDown, cx);
3813 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3814 assert_eq!(
3815 view.selections.display_ranges(cx),
3816 vec![
3817 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3818 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
3819 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3820 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3821 ]
3822 );
3823 });
3824
3825 let view = cx.add_window(|cx| {
3826 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3827 build_editor(buffer, cx)
3828 });
3829 _ = view.update(cx, |view, cx| {
3830 view.change_selections(None, cx, |s| {
3831 s.select_display_ranges([
3832 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3833 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3834 ])
3835 });
3836 view.duplicate_line_down(&DuplicateLineDown, cx);
3837 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3838 assert_eq!(
3839 view.selections.display_ranges(cx),
3840 vec![
3841 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
3842 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
3843 ]
3844 );
3845 });
3846
3847 // With `move_upwards` the selections stay in place, except for
3848 // the lines inserted above them
3849 let view = cx.add_window(|cx| {
3850 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3851 build_editor(buffer, cx)
3852 });
3853 _ = view.update(cx, |view, cx| {
3854 view.change_selections(None, cx, |s| {
3855 s.select_display_ranges([
3856 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3857 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3858 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
3859 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3860 ])
3861 });
3862 view.duplicate_line_up(&DuplicateLineUp, cx);
3863 assert_eq!(view.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
3864 assert_eq!(
3865 view.selections.display_ranges(cx),
3866 vec![
3867 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
3868 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
3869 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
3870 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
3871 ]
3872 );
3873 });
3874
3875 let view = cx.add_window(|cx| {
3876 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3877 build_editor(buffer, cx)
3878 });
3879 _ = view.update(cx, |view, cx| {
3880 view.change_selections(None, cx, |s| {
3881 s.select_display_ranges([
3882 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3883 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3884 ])
3885 });
3886 view.duplicate_line_up(&DuplicateLineUp, cx);
3887 assert_eq!(view.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
3888 assert_eq!(
3889 view.selections.display_ranges(cx),
3890 vec![
3891 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3892 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3893 ]
3894 );
3895 });
3896
3897 let view = cx.add_window(|cx| {
3898 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3899 build_editor(buffer, cx)
3900 });
3901 _ = view.update(cx, |view, cx| {
3902 view.change_selections(None, cx, |s| {
3903 s.select_display_ranges([
3904 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3905 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
3906 ])
3907 });
3908 view.duplicate_selection(&DuplicateSelection, cx);
3909 assert_eq!(view.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
3910 assert_eq!(
3911 view.selections.display_ranges(cx),
3912 vec![
3913 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
3914 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
3915 ]
3916 );
3917 });
3918}
3919
3920#[gpui::test]
3921fn test_move_line_up_down(cx: &mut TestAppContext) {
3922 init_test(cx, |_| {});
3923
3924 let view = cx.add_window(|cx| {
3925 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
3926 build_editor(buffer, cx)
3927 });
3928 _ = view.update(cx, |view, cx| {
3929 view.fold_creases(
3930 vec![
3931 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
3932 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
3933 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
3934 ],
3935 true,
3936 cx,
3937 );
3938 view.change_selections(None, cx, |s| {
3939 s.select_display_ranges([
3940 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3941 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3942 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3943 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
3944 ])
3945 });
3946 assert_eq!(
3947 view.display_text(cx),
3948 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
3949 );
3950
3951 view.move_line_up(&MoveLineUp, cx);
3952 assert_eq!(
3953 view.display_text(cx),
3954 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
3955 );
3956 assert_eq!(
3957 view.selections.display_ranges(cx),
3958 vec![
3959 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3960 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3961 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
3962 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
3963 ]
3964 );
3965 });
3966
3967 _ = view.update(cx, |view, cx| {
3968 view.move_line_down(&MoveLineDown, cx);
3969 assert_eq!(
3970 view.display_text(cx),
3971 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
3972 );
3973 assert_eq!(
3974 view.selections.display_ranges(cx),
3975 vec![
3976 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
3977 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3978 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3979 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3980 ]
3981 );
3982 });
3983
3984 _ = view.update(cx, |view, cx| {
3985 view.move_line_down(&MoveLineDown, cx);
3986 assert_eq!(
3987 view.display_text(cx),
3988 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
3989 );
3990 assert_eq!(
3991 view.selections.display_ranges(cx),
3992 vec![
3993 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
3994 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
3995 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
3996 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
3997 ]
3998 );
3999 });
4000
4001 _ = view.update(cx, |view, cx| {
4002 view.move_line_up(&MoveLineUp, cx);
4003 assert_eq!(
4004 view.display_text(cx),
4005 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
4006 );
4007 assert_eq!(
4008 view.selections.display_ranges(cx),
4009 vec![
4010 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4011 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4012 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4013 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4014 ]
4015 );
4016 });
4017}
4018
4019#[gpui::test]
4020fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4021 init_test(cx, |_| {});
4022
4023 let editor = cx.add_window(|cx| {
4024 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4025 build_editor(buffer, cx)
4026 });
4027 _ = editor.update(cx, |editor, cx| {
4028 let snapshot = editor.buffer.read(cx).snapshot(cx);
4029 editor.insert_blocks(
4030 [BlockProperties {
4031 style: BlockStyle::Fixed,
4032 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4033 height: 1,
4034 render: Arc::new(|_| div().into_any()),
4035 priority: 0,
4036 }],
4037 Some(Autoscroll::fit()),
4038 cx,
4039 );
4040 editor.change_selections(None, cx, |s| {
4041 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4042 });
4043 editor.move_line_down(&MoveLineDown, cx);
4044 });
4045}
4046
4047#[gpui::test]
4048async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4049 init_test(cx, |_| {});
4050
4051 let mut cx = EditorTestContext::new(cx).await;
4052 cx.set_state(
4053 &"
4054 ˇzero
4055 one
4056 two
4057 three
4058 four
4059 five
4060 "
4061 .unindent(),
4062 );
4063
4064 // Create a four-line block that replaces three lines of text.
4065 cx.update_editor(|editor, cx| {
4066 let snapshot = editor.snapshot(cx);
4067 let snapshot = &snapshot.buffer_snapshot;
4068 let placement = BlockPlacement::Replace(
4069 snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
4070 );
4071 editor.insert_blocks(
4072 [BlockProperties {
4073 placement,
4074 height: 4,
4075 style: BlockStyle::Sticky,
4076 render: Arc::new(|_| gpui::div().into_any_element()),
4077 priority: 0,
4078 }],
4079 None,
4080 cx,
4081 );
4082 });
4083
4084 // Move down so that the cursor touches the block.
4085 cx.update_editor(|editor, cx| {
4086 editor.move_down(&Default::default(), cx);
4087 });
4088 cx.assert_editor_state(
4089 &"
4090 zero
4091 «one
4092 two
4093 threeˇ»
4094 four
4095 five
4096 "
4097 .unindent(),
4098 );
4099
4100 // Move down past the block.
4101 cx.update_editor(|editor, cx| {
4102 editor.move_down(&Default::default(), cx);
4103 });
4104 cx.assert_editor_state(
4105 &"
4106 zero
4107 one
4108 two
4109 three
4110 ˇfour
4111 five
4112 "
4113 .unindent(),
4114 );
4115}
4116
4117#[gpui::test]
4118fn test_transpose(cx: &mut TestAppContext) {
4119 init_test(cx, |_| {});
4120
4121 _ = cx.add_window(|cx| {
4122 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
4123 editor.set_style(EditorStyle::default(), cx);
4124 editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
4125 editor.transpose(&Default::default(), cx);
4126 assert_eq!(editor.text(cx), "bac");
4127 assert_eq!(editor.selections.ranges(cx), [2..2]);
4128
4129 editor.transpose(&Default::default(), cx);
4130 assert_eq!(editor.text(cx), "bca");
4131 assert_eq!(editor.selections.ranges(cx), [3..3]);
4132
4133 editor.transpose(&Default::default(), cx);
4134 assert_eq!(editor.text(cx), "bac");
4135 assert_eq!(editor.selections.ranges(cx), [3..3]);
4136
4137 editor
4138 });
4139
4140 _ = cx.add_window(|cx| {
4141 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4142 editor.set_style(EditorStyle::default(), cx);
4143 editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
4144 editor.transpose(&Default::default(), cx);
4145 assert_eq!(editor.text(cx), "acb\nde");
4146 assert_eq!(editor.selections.ranges(cx), [3..3]);
4147
4148 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4149 editor.transpose(&Default::default(), cx);
4150 assert_eq!(editor.text(cx), "acbd\ne");
4151 assert_eq!(editor.selections.ranges(cx), [5..5]);
4152
4153 editor.transpose(&Default::default(), cx);
4154 assert_eq!(editor.text(cx), "acbde\n");
4155 assert_eq!(editor.selections.ranges(cx), [6..6]);
4156
4157 editor.transpose(&Default::default(), cx);
4158 assert_eq!(editor.text(cx), "acbd\ne");
4159 assert_eq!(editor.selections.ranges(cx), [6..6]);
4160
4161 editor
4162 });
4163
4164 _ = cx.add_window(|cx| {
4165 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
4166 editor.set_style(EditorStyle::default(), cx);
4167 editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4168 editor.transpose(&Default::default(), cx);
4169 assert_eq!(editor.text(cx), "bacd\ne");
4170 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4171
4172 editor.transpose(&Default::default(), cx);
4173 assert_eq!(editor.text(cx), "bcade\n");
4174 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4175
4176 editor.transpose(&Default::default(), cx);
4177 assert_eq!(editor.text(cx), "bcda\ne");
4178 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4179
4180 editor.transpose(&Default::default(), cx);
4181 assert_eq!(editor.text(cx), "bcade\n");
4182 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4183
4184 editor.transpose(&Default::default(), cx);
4185 assert_eq!(editor.text(cx), "bcaed\n");
4186 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4187
4188 editor
4189 });
4190
4191 _ = cx.add_window(|cx| {
4192 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
4193 editor.set_style(EditorStyle::default(), cx);
4194 editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
4195 editor.transpose(&Default::default(), cx);
4196 assert_eq!(editor.text(cx), "🏀🍐✋");
4197 assert_eq!(editor.selections.ranges(cx), [8..8]);
4198
4199 editor.transpose(&Default::default(), cx);
4200 assert_eq!(editor.text(cx), "🏀✋🍐");
4201 assert_eq!(editor.selections.ranges(cx), [11..11]);
4202
4203 editor.transpose(&Default::default(), cx);
4204 assert_eq!(editor.text(cx), "🏀🍐✋");
4205 assert_eq!(editor.selections.ranges(cx), [11..11]);
4206
4207 editor
4208 });
4209}
4210
4211#[gpui::test]
4212async fn test_rewrap(cx: &mut TestAppContext) {
4213 init_test(cx, |_| {});
4214
4215 let mut cx = EditorTestContext::new(cx).await;
4216
4217 let language_with_c_comments = Arc::new(Language::new(
4218 LanguageConfig {
4219 line_comments: vec!["// ".into()],
4220 ..LanguageConfig::default()
4221 },
4222 None,
4223 ));
4224 let language_with_pound_comments = Arc::new(Language::new(
4225 LanguageConfig {
4226 line_comments: vec!["# ".into()],
4227 ..LanguageConfig::default()
4228 },
4229 None,
4230 ));
4231 let markdown_language = Arc::new(Language::new(
4232 LanguageConfig {
4233 name: "Markdown".into(),
4234 ..LanguageConfig::default()
4235 },
4236 None,
4237 ));
4238 let language_with_doc_comments = Arc::new(Language::new(
4239 LanguageConfig {
4240 line_comments: vec!["// ".into(), "/// ".into()],
4241 ..LanguageConfig::default()
4242 },
4243 Some(tree_sitter_rust::LANGUAGE.into()),
4244 ));
4245
4246 let plaintext_language = Arc::new(Language::new(
4247 LanguageConfig {
4248 name: "Plain Text".into(),
4249 ..LanguageConfig::default()
4250 },
4251 None,
4252 ));
4253
4254 assert_rewrap(
4255 indoc! {"
4256 // ˇ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.
4257 "},
4258 indoc! {"
4259 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4260 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4261 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4262 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4263 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4264 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4265 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4266 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4267 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4268 // porttitor id. Aliquam id accumsan eros.
4269 "},
4270 language_with_c_comments.clone(),
4271 &mut cx,
4272 );
4273
4274 // Test that rewrapping works inside of a selection
4275 assert_rewrap(
4276 indoc! {"
4277 «// 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.ˇ»
4278 "},
4279 indoc! {"
4280 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4281 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4282 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4283 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4284 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4285 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4286 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4287 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4288 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4289 // porttitor id. Aliquam id accumsan eros.ˇ»
4290 "},
4291 language_with_c_comments.clone(),
4292 &mut cx,
4293 );
4294
4295 // Test that cursors that expand to the same region are collapsed.
4296 assert_rewrap(
4297 indoc! {"
4298 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4299 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4300 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4301 // ˇ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.
4302 "},
4303 indoc! {"
4304 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4305 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4306 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4307 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4308 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4309 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4310 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4311 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4312 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4313 // porttitor id. Aliquam id accumsan eros.
4314 "},
4315 language_with_c_comments.clone(),
4316 &mut cx,
4317 );
4318
4319 // Test that non-contiguous selections are treated separately.
4320 assert_rewrap(
4321 indoc! {"
4322 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4323 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4324 //
4325 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4326 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
4327 "},
4328 indoc! {"
4329 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4330 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4331 // auctor, eu lacinia sapien scelerisque.
4332 //
4333 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4334 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4335 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4336 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4337 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4338 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4339 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4340 "},
4341 language_with_c_comments.clone(),
4342 &mut cx,
4343 );
4344
4345 // Test that different comment prefixes are supported.
4346 assert_rewrap(
4347 indoc! {"
4348 # ˇ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.
4349 "},
4350 indoc! {"
4351 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4352 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4353 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4354 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4355 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4356 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4357 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4358 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4359 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4360 # accumsan eros.
4361 "},
4362 language_with_pound_comments.clone(),
4363 &mut cx,
4364 );
4365
4366 // Test that rewrapping is ignored outside of comments in most languages.
4367 assert_rewrap(
4368 indoc! {"
4369 /// Adds two numbers.
4370 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4371 fn add(a: u32, b: u32) -> u32 {
4372 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ˇ
4373 }
4374 "},
4375 indoc! {"
4376 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4377 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4378 fn add(a: u32, b: u32) -> u32 {
4379 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ˇ
4380 }
4381 "},
4382 language_with_doc_comments.clone(),
4383 &mut cx,
4384 );
4385
4386 // Test that rewrapping works in Markdown and Plain Text languages.
4387 assert_rewrap(
4388 indoc! {"
4389 # Hello
4390
4391 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.
4392 "},
4393 indoc! {"
4394 # Hello
4395
4396 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4397 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4398 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4399 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4400 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4401 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4402 Integer sit amet scelerisque nisi.
4403 "},
4404 markdown_language,
4405 &mut cx,
4406 );
4407
4408 assert_rewrap(
4409 indoc! {"
4410 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.
4411 "},
4412 indoc! {"
4413 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4414 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4415 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4416 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4417 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4418 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4419 Integer sit amet scelerisque nisi.
4420 "},
4421 plaintext_language,
4422 &mut cx,
4423 );
4424
4425 // Test rewrapping unaligned comments in a selection.
4426 assert_rewrap(
4427 indoc! {"
4428 fn foo() {
4429 if true {
4430 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4431 // Praesent semper egestas tellus id dignissim.ˇ»
4432 do_something();
4433 } else {
4434 //
4435 }
4436 }
4437 "},
4438 indoc! {"
4439 fn foo() {
4440 if true {
4441 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4442 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4443 // egestas tellus id dignissim.ˇ»
4444 do_something();
4445 } else {
4446 //
4447 }
4448 }
4449 "},
4450 language_with_doc_comments.clone(),
4451 &mut cx,
4452 );
4453
4454 assert_rewrap(
4455 indoc! {"
4456 fn foo() {
4457 if true {
4458 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4459 // Praesent semper egestas tellus id dignissim.»
4460 do_something();
4461 } else {
4462 //
4463 }
4464
4465 }
4466 "},
4467 indoc! {"
4468 fn foo() {
4469 if true {
4470 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4471 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4472 // egestas tellus id dignissim.»
4473 do_something();
4474 } else {
4475 //
4476 }
4477
4478 }
4479 "},
4480 language_with_doc_comments.clone(),
4481 &mut cx,
4482 );
4483
4484 #[track_caller]
4485 fn assert_rewrap(
4486 unwrapped_text: &str,
4487 wrapped_text: &str,
4488 language: Arc<Language>,
4489 cx: &mut EditorTestContext,
4490 ) {
4491 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4492 cx.set_state(unwrapped_text);
4493 cx.update_editor(|e, cx| e.rewrap(&Rewrap, cx));
4494 cx.assert_editor_state(wrapped_text);
4495 }
4496}
4497
4498#[gpui::test]
4499async fn test_clipboard(cx: &mut gpui::TestAppContext) {
4500 init_test(cx, |_| {});
4501
4502 let mut cx = EditorTestContext::new(cx).await;
4503
4504 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4505 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4506 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4507
4508 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4509 cx.set_state("two ˇfour ˇsix ˇ");
4510 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4511 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4512
4513 // Paste again but with only two cursors. Since the number of cursors doesn't
4514 // match the number of slices in the clipboard, the entire clipboard text
4515 // is pasted at each cursor.
4516 cx.set_state("ˇtwo one✅ four three six five ˇ");
4517 cx.update_editor(|e, cx| {
4518 e.handle_input("( ", cx);
4519 e.paste(&Paste, cx);
4520 e.handle_input(") ", cx);
4521 });
4522 cx.assert_editor_state(
4523 &([
4524 "( one✅ ",
4525 "three ",
4526 "five ) ˇtwo one✅ four three six five ( one✅ ",
4527 "three ",
4528 "five ) ˇ",
4529 ]
4530 .join("\n")),
4531 );
4532
4533 // Cut with three selections, one of which is full-line.
4534 cx.set_state(indoc! {"
4535 1«2ˇ»3
4536 4ˇ567
4537 «8ˇ»9"});
4538 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4539 cx.assert_editor_state(indoc! {"
4540 1ˇ3
4541 ˇ9"});
4542
4543 // Paste with three selections, noticing how the copied selection that was full-line
4544 // gets inserted before the second cursor.
4545 cx.set_state(indoc! {"
4546 1ˇ3
4547 9ˇ
4548 «oˇ»ne"});
4549 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4550 cx.assert_editor_state(indoc! {"
4551 12ˇ3
4552 4567
4553 9ˇ
4554 8ˇne"});
4555
4556 // Copy with a single cursor only, which writes the whole line into the clipboard.
4557 cx.set_state(indoc! {"
4558 The quick brown
4559 fox juˇmps over
4560 the lazy dog"});
4561 cx.update_editor(|e, cx| e.copy(&Copy, cx));
4562 assert_eq!(
4563 cx.read_from_clipboard()
4564 .and_then(|item| item.text().as_deref().map(str::to_string)),
4565 Some("fox jumps over\n".to_string())
4566 );
4567
4568 // Paste with three selections, noticing how the copied full-line selection is inserted
4569 // before the empty selections but replaces the selection that is non-empty.
4570 cx.set_state(indoc! {"
4571 Tˇhe quick brown
4572 «foˇ»x jumps over
4573 tˇhe lazy dog"});
4574 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4575 cx.assert_editor_state(indoc! {"
4576 fox jumps over
4577 Tˇhe quick brown
4578 fox jumps over
4579 ˇx jumps over
4580 fox jumps over
4581 tˇhe lazy dog"});
4582}
4583
4584#[gpui::test]
4585async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
4586 init_test(cx, |_| {});
4587
4588 let mut cx = EditorTestContext::new(cx).await;
4589 let language = Arc::new(Language::new(
4590 LanguageConfig::default(),
4591 Some(tree_sitter_rust::LANGUAGE.into()),
4592 ));
4593 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4594
4595 // Cut an indented block, without the leading whitespace.
4596 cx.set_state(indoc! {"
4597 const a: B = (
4598 c(),
4599 «d(
4600 e,
4601 f
4602 )ˇ»
4603 );
4604 "});
4605 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4606 cx.assert_editor_state(indoc! {"
4607 const a: B = (
4608 c(),
4609 ˇ
4610 );
4611 "});
4612
4613 // Paste it at the same position.
4614 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4615 cx.assert_editor_state(indoc! {"
4616 const a: B = (
4617 c(),
4618 d(
4619 e,
4620 f
4621 )ˇ
4622 );
4623 "});
4624
4625 // Paste it at a line with a lower indent level.
4626 cx.set_state(indoc! {"
4627 ˇ
4628 const a: B = (
4629 c(),
4630 );
4631 "});
4632 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4633 cx.assert_editor_state(indoc! {"
4634 d(
4635 e,
4636 f
4637 )ˇ
4638 const a: B = (
4639 c(),
4640 );
4641 "});
4642
4643 // Cut an indented block, with the leading whitespace.
4644 cx.set_state(indoc! {"
4645 const a: B = (
4646 c(),
4647 « d(
4648 e,
4649 f
4650 )
4651 ˇ»);
4652 "});
4653 cx.update_editor(|e, cx| e.cut(&Cut, cx));
4654 cx.assert_editor_state(indoc! {"
4655 const a: B = (
4656 c(),
4657 ˇ);
4658 "});
4659
4660 // Paste it at the same position.
4661 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4662 cx.assert_editor_state(indoc! {"
4663 const a: B = (
4664 c(),
4665 d(
4666 e,
4667 f
4668 )
4669 ˇ);
4670 "});
4671
4672 // Paste it at a line with a higher indent level.
4673 cx.set_state(indoc! {"
4674 const a: B = (
4675 c(),
4676 d(
4677 e,
4678 fˇ
4679 )
4680 );
4681 "});
4682 cx.update_editor(|e, cx| e.paste(&Paste, cx));
4683 cx.assert_editor_state(indoc! {"
4684 const a: B = (
4685 c(),
4686 d(
4687 e,
4688 f d(
4689 e,
4690 f
4691 )
4692 ˇ
4693 )
4694 );
4695 "});
4696}
4697
4698#[gpui::test]
4699fn test_select_all(cx: &mut TestAppContext) {
4700 init_test(cx, |_| {});
4701
4702 let view = cx.add_window(|cx| {
4703 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
4704 build_editor(buffer, cx)
4705 });
4706 _ = view.update(cx, |view, cx| {
4707 view.select_all(&SelectAll, cx);
4708 assert_eq!(
4709 view.selections.display_ranges(cx),
4710 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
4711 );
4712 });
4713}
4714
4715#[gpui::test]
4716fn test_select_line(cx: &mut TestAppContext) {
4717 init_test(cx, |_| {});
4718
4719 let view = cx.add_window(|cx| {
4720 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
4721 build_editor(buffer, cx)
4722 });
4723 _ = view.update(cx, |view, cx| {
4724 view.change_selections(None, cx, |s| {
4725 s.select_display_ranges([
4726 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4727 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4728 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4729 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
4730 ])
4731 });
4732 view.select_line(&SelectLine, cx);
4733 assert_eq!(
4734 view.selections.display_ranges(cx),
4735 vec![
4736 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
4737 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
4738 ]
4739 );
4740 });
4741
4742 _ = view.update(cx, |view, cx| {
4743 view.select_line(&SelectLine, cx);
4744 assert_eq!(
4745 view.selections.display_ranges(cx),
4746 vec![
4747 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
4748 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
4749 ]
4750 );
4751 });
4752
4753 _ = view.update(cx, |view, cx| {
4754 view.select_line(&SelectLine, cx);
4755 assert_eq!(
4756 view.selections.display_ranges(cx),
4757 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
4758 );
4759 });
4760}
4761
4762#[gpui::test]
4763fn test_split_selection_into_lines(cx: &mut TestAppContext) {
4764 init_test(cx, |_| {});
4765
4766 let view = cx.add_window(|cx| {
4767 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
4768 build_editor(buffer, cx)
4769 });
4770 _ = view.update(cx, |view, cx| {
4771 view.fold_creases(
4772 vec![
4773 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4774 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4775 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4776 ],
4777 true,
4778 cx,
4779 );
4780 view.change_selections(None, cx, |s| {
4781 s.select_display_ranges([
4782 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4783 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4784 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4785 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
4786 ])
4787 });
4788 assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i");
4789 });
4790
4791 _ = view.update(cx, |view, cx| {
4792 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4793 assert_eq!(
4794 view.display_text(cx),
4795 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
4796 );
4797 assert_eq!(
4798 view.selections.display_ranges(cx),
4799 [
4800 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4801 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4802 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4803 DisplayPoint::new(DisplayRow(5), 4)..DisplayPoint::new(DisplayRow(5), 4)
4804 ]
4805 );
4806 });
4807
4808 _ = view.update(cx, |view, cx| {
4809 view.change_selections(None, cx, |s| {
4810 s.select_display_ranges([
4811 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
4812 ])
4813 });
4814 view.split_selection_into_lines(&SplitSelectionIntoLines, cx);
4815 assert_eq!(
4816 view.display_text(cx),
4817 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
4818 );
4819 assert_eq!(
4820 view.selections.display_ranges(cx),
4821 [
4822 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
4823 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
4824 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
4825 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
4826 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
4827 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
4828 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5),
4829 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(7), 0)
4830 ]
4831 );
4832 });
4833}
4834
4835#[gpui::test]
4836async fn test_add_selection_above_below(cx: &mut TestAppContext) {
4837 init_test(cx, |_| {});
4838
4839 let mut cx = EditorTestContext::new(cx).await;
4840
4841 // let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
4842 cx.set_state(indoc!(
4843 r#"abc
4844 defˇghi
4845
4846 jk
4847 nlmo
4848 "#
4849 ));
4850
4851 cx.update_editor(|editor, cx| {
4852 editor.add_selection_above(&Default::default(), cx);
4853 });
4854
4855 cx.assert_editor_state(indoc!(
4856 r#"abcˇ
4857 defˇghi
4858
4859 jk
4860 nlmo
4861 "#
4862 ));
4863
4864 cx.update_editor(|editor, cx| {
4865 editor.add_selection_above(&Default::default(), cx);
4866 });
4867
4868 cx.assert_editor_state(indoc!(
4869 r#"abcˇ
4870 defˇghi
4871
4872 jk
4873 nlmo
4874 "#
4875 ));
4876
4877 cx.update_editor(|view, cx| {
4878 view.add_selection_below(&Default::default(), cx);
4879 });
4880
4881 cx.assert_editor_state(indoc!(
4882 r#"abc
4883 defˇghi
4884
4885 jk
4886 nlmo
4887 "#
4888 ));
4889
4890 cx.update_editor(|view, cx| {
4891 view.undo_selection(&Default::default(), cx);
4892 });
4893
4894 cx.assert_editor_state(indoc!(
4895 r#"abcˇ
4896 defˇghi
4897
4898 jk
4899 nlmo
4900 "#
4901 ));
4902
4903 cx.update_editor(|view, cx| {
4904 view.redo_selection(&Default::default(), cx);
4905 });
4906
4907 cx.assert_editor_state(indoc!(
4908 r#"abc
4909 defˇghi
4910
4911 jk
4912 nlmo
4913 "#
4914 ));
4915
4916 cx.update_editor(|view, cx| {
4917 view.add_selection_below(&Default::default(), cx);
4918 });
4919
4920 cx.assert_editor_state(indoc!(
4921 r#"abc
4922 defˇghi
4923
4924 jk
4925 nlmˇo
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ˇghi
4936
4937 jk
4938 nlmˇo
4939 "#
4940 ));
4941
4942 // change selections
4943 cx.set_state(indoc!(
4944 r#"abc
4945 def«ˇg»hi
4946
4947 jk
4948 nlmo
4949 "#
4950 ));
4951
4952 cx.update_editor(|view, cx| {
4953 view.add_selection_below(&Default::default(), cx);
4954 });
4955
4956 cx.assert_editor_state(indoc!(
4957 r#"abc
4958 def«ˇg»hi
4959
4960 jk
4961 nlm«ˇo»
4962 "#
4963 ));
4964
4965 cx.update_editor(|view, cx| {
4966 view.add_selection_below(&Default::default(), cx);
4967 });
4968
4969 cx.assert_editor_state(indoc!(
4970 r#"abc
4971 def«ˇg»hi
4972
4973 jk
4974 nlm«ˇo»
4975 "#
4976 ));
4977
4978 cx.update_editor(|view, cx| {
4979 view.add_selection_above(&Default::default(), cx);
4980 });
4981
4982 cx.assert_editor_state(indoc!(
4983 r#"abc
4984 def«ˇg»hi
4985
4986 jk
4987 nlmo
4988 "#
4989 ));
4990
4991 cx.update_editor(|view, cx| {
4992 view.add_selection_above(&Default::default(), cx);
4993 });
4994
4995 cx.assert_editor_state(indoc!(
4996 r#"abc
4997 def«ˇg»hi
4998
4999 jk
5000 nlmo
5001 "#
5002 ));
5003
5004 // Change selections again
5005 cx.set_state(indoc!(
5006 r#"a«bc
5007 defgˇ»hi
5008
5009 jk
5010 nlmo
5011 "#
5012 ));
5013
5014 cx.update_editor(|view, cx| {
5015 view.add_selection_below(&Default::default(), cx);
5016 });
5017
5018 cx.assert_editor_state(indoc!(
5019 r#"a«bcˇ»
5020 d«efgˇ»hi
5021
5022 j«kˇ»
5023 nlmo
5024 "#
5025 ));
5026
5027 cx.update_editor(|view, cx| {
5028 view.add_selection_below(&Default::default(), cx);
5029 });
5030 cx.assert_editor_state(indoc!(
5031 r#"a«bcˇ»
5032 d«efgˇ»hi
5033
5034 j«kˇ»
5035 n«lmoˇ»
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«efgˇ»hi
5045
5046 j«kˇ»
5047 nlmo
5048 "#
5049 ));
5050
5051 // Change selections again
5052 cx.set_state(indoc!(
5053 r#"abc
5054 d«ˇefghi
5055
5056 jk
5057 nlm»o
5058 "#
5059 ));
5060
5061 cx.update_editor(|view, cx| {
5062 view.add_selection_above(&Default::default(), cx);
5063 });
5064
5065 cx.assert_editor_state(indoc!(
5066 r#"a«ˇbc»
5067 d«ˇef»ghi
5068
5069 j«ˇk»
5070 n«ˇlm»o
5071 "#
5072 ));
5073
5074 cx.update_editor(|view, cx| {
5075 view.add_selection_below(&Default::default(), cx);
5076 });
5077
5078 cx.assert_editor_state(indoc!(
5079 r#"abc
5080 d«ˇef»ghi
5081
5082 j«ˇk»
5083 n«ˇlm»o
5084 "#
5085 ));
5086}
5087
5088#[gpui::test]
5089async fn test_select_next(cx: &mut gpui::TestAppContext) {
5090 init_test(cx, |_| {});
5091
5092 let mut cx = EditorTestContext::new(cx).await;
5093 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5094
5095 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5096 .unwrap();
5097 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5098
5099 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5100 .unwrap();
5101 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5102
5103 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5104 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5105
5106 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5107 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5108
5109 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5110 .unwrap();
5111 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5112
5113 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5114 .unwrap();
5115 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5116}
5117
5118#[gpui::test]
5119async fn test_select_all_matches(cx: &mut gpui::TestAppContext) {
5120 init_test(cx, |_| {});
5121
5122 let mut cx = EditorTestContext::new(cx).await;
5123 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5124
5125 cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx))
5126 .unwrap();
5127 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5128}
5129
5130#[gpui::test]
5131async fn test_select_next_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5132 init_test(cx, |_| {});
5133
5134 let mut cx = EditorTestContext::new(cx).await;
5135 cx.set_state(
5136 r#"let foo = 2;
5137lˇet foo = 2;
5138let fooˇ = 2;
5139let foo = 2;
5140let foo = ˇ2;"#,
5141 );
5142
5143 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5144 .unwrap();
5145 cx.assert_editor_state(
5146 r#"let foo = 2;
5147«letˇ» foo = 2;
5148let «fooˇ» = 2;
5149let foo = 2;
5150let foo = «2ˇ»;"#,
5151 );
5152
5153 // noop for multiple selections with different contents
5154 cx.update_editor(|e, cx| e.select_next(&SelectNext::default(), cx))
5155 .unwrap();
5156 cx.assert_editor_state(
5157 r#"let foo = 2;
5158«letˇ» foo = 2;
5159let «fooˇ» = 2;
5160let foo = 2;
5161let foo = «2ˇ»;"#,
5162 );
5163}
5164
5165#[gpui::test]
5166async fn test_select_previous_multibuffer(cx: &mut gpui::TestAppContext) {
5167 init_test(cx, |_| {});
5168
5169 let mut cx =
5170 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5171
5172 cx.assert_editor_state(indoc! {"
5173 ˇbbb
5174 ccc
5175
5176 bbb
5177 ccc
5178 "});
5179 cx.dispatch_action(SelectPrevious::default());
5180 cx.assert_editor_state(indoc! {"
5181 «bbbˇ»
5182 ccc
5183
5184 bbb
5185 ccc
5186 "});
5187 cx.dispatch_action(SelectPrevious::default());
5188 cx.assert_editor_state(indoc! {"
5189 «bbbˇ»
5190 ccc
5191
5192 «bbbˇ»
5193 ccc
5194 "});
5195}
5196
5197#[gpui::test]
5198async fn test_select_previous_with_single_caret(cx: &mut gpui::TestAppContext) {
5199 init_test(cx, |_| {});
5200
5201 let mut cx = EditorTestContext::new(cx).await;
5202 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5203
5204 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5205 .unwrap();
5206 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5207
5208 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5209 .unwrap();
5210 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5211
5212 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5213 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5214
5215 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5216 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5217
5218 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5219 .unwrap();
5220 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5221
5222 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5223 .unwrap();
5224 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5225
5226 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5227 .unwrap();
5228 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5229}
5230
5231#[gpui::test]
5232async fn test_select_previous_with_multiple_carets(cx: &mut gpui::TestAppContext) {
5233 init_test(cx, |_| {});
5234
5235 let mut cx = EditorTestContext::new(cx).await;
5236 cx.set_state(
5237 r#"let foo = 2;
5238lˇet foo = 2;
5239let fooˇ = 2;
5240let foo = 2;
5241let foo = ˇ2;"#,
5242 );
5243
5244 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5245 .unwrap();
5246 cx.assert_editor_state(
5247 r#"let foo = 2;
5248«letˇ» foo = 2;
5249let «fooˇ» = 2;
5250let foo = 2;
5251let foo = «2ˇ»;"#,
5252 );
5253
5254 // noop for multiple selections with different contents
5255 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5256 .unwrap();
5257 cx.assert_editor_state(
5258 r#"let foo = 2;
5259«letˇ» foo = 2;
5260let «fooˇ» = 2;
5261let foo = 2;
5262let foo = «2ˇ»;"#,
5263 );
5264}
5265
5266#[gpui::test]
5267async fn test_select_previous_with_single_selection(cx: &mut gpui::TestAppContext) {
5268 init_test(cx, |_| {});
5269
5270 let mut cx = EditorTestContext::new(cx).await;
5271 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5272
5273 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5274 .unwrap();
5275 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5276
5277 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5278 .unwrap();
5279 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5280
5281 cx.update_editor(|view, cx| view.undo_selection(&UndoSelection, cx));
5282 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5283
5284 cx.update_editor(|view, cx| view.redo_selection(&RedoSelection, cx));
5285 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5286
5287 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5288 .unwrap();
5289 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5290
5291 cx.update_editor(|e, cx| e.select_previous(&SelectPrevious::default(), cx))
5292 .unwrap();
5293 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5294}
5295
5296#[gpui::test]
5297async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
5298 init_test(cx, |_| {});
5299
5300 let language = Arc::new(Language::new(
5301 LanguageConfig::default(),
5302 Some(tree_sitter_rust::LANGUAGE.into()),
5303 ));
5304
5305 let text = r#"
5306 use mod1::mod2::{mod3, mod4};
5307
5308 fn fn_1(param1: bool, param2: &str) {
5309 let var1 = "text";
5310 }
5311 "#
5312 .unindent();
5313
5314 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5315 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5316 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5317
5318 editor
5319 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
5320 .await;
5321
5322 editor.update(cx, |view, cx| {
5323 view.change_selections(None, cx, |s| {
5324 s.select_display_ranges([
5325 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5326 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5327 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5328 ]);
5329 });
5330 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5331 });
5332 editor.update(cx, |editor, cx| {
5333 assert_text_with_selections(
5334 editor,
5335 indoc! {r#"
5336 use mod1::mod2::{mod3, «mod4ˇ»};
5337
5338 fn fn_1«ˇ(param1: bool, param2: &str)» {
5339 let var1 = "«textˇ»";
5340 }
5341 "#},
5342 cx,
5343 );
5344 });
5345
5346 editor.update(cx, |view, cx| {
5347 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5348 });
5349 editor.update(cx, |editor, cx| {
5350 assert_text_with_selections(
5351 editor,
5352 indoc! {r#"
5353 use mod1::mod2::«{mod3, mod4}ˇ»;
5354
5355 «ˇfn fn_1(param1: bool, param2: &str) {
5356 let var1 = "text";
5357 }»
5358 "#},
5359 cx,
5360 );
5361 });
5362
5363 editor.update(cx, |view, cx| {
5364 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5365 });
5366 assert_eq!(
5367 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5368 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5369 );
5370
5371 // Trying to expand the selected syntax node one more time has no effect.
5372 editor.update(cx, |view, cx| {
5373 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5374 });
5375 assert_eq!(
5376 editor.update(cx, |view, cx| view.selections.display_ranges(cx)),
5377 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5378 );
5379
5380 editor.update(cx, |view, cx| {
5381 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5382 });
5383 editor.update(cx, |editor, cx| {
5384 assert_text_with_selections(
5385 editor,
5386 indoc! {r#"
5387 use mod1::mod2::«{mod3, mod4}ˇ»;
5388
5389 «ˇfn fn_1(param1: bool, param2: &str) {
5390 let var1 = "text";
5391 }»
5392 "#},
5393 cx,
5394 );
5395 });
5396
5397 editor.update(cx, |view, cx| {
5398 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5399 });
5400 editor.update(cx, |editor, cx| {
5401 assert_text_with_selections(
5402 editor,
5403 indoc! {r#"
5404 use mod1::mod2::{mod3, «mod4ˇ»};
5405
5406 fn fn_1«ˇ(param1: bool, param2: &str)» {
5407 let var1 = "«textˇ»";
5408 }
5409 "#},
5410 cx,
5411 );
5412 });
5413
5414 editor.update(cx, |view, cx| {
5415 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5416 });
5417 editor.update(cx, |editor, cx| {
5418 assert_text_with_selections(
5419 editor,
5420 indoc! {r#"
5421 use mod1::mod2::{mod3, mo«ˇ»d4};
5422
5423 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5424 let var1 = "te«ˇ»xt";
5425 }
5426 "#},
5427 cx,
5428 );
5429 });
5430
5431 // Trying to shrink the selected syntax node one more time has no effect.
5432 editor.update(cx, |view, cx| {
5433 view.select_smaller_syntax_node(&SelectSmallerSyntaxNode, cx);
5434 });
5435 editor.update(cx, |editor, cx| {
5436 assert_text_with_selections(
5437 editor,
5438 indoc! {r#"
5439 use mod1::mod2::{mod3, mo«ˇ»d4};
5440
5441 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5442 let var1 = "te«ˇ»xt";
5443 }
5444 "#},
5445 cx,
5446 );
5447 });
5448
5449 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5450 // a fold.
5451 editor.update(cx, |view, cx| {
5452 view.fold_creases(
5453 vec![
5454 Crease::simple(
5455 Point::new(0, 21)..Point::new(0, 24),
5456 FoldPlaceholder::test(),
5457 ),
5458 Crease::simple(
5459 Point::new(3, 20)..Point::new(3, 22),
5460 FoldPlaceholder::test(),
5461 ),
5462 ],
5463 true,
5464 cx,
5465 );
5466 view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx);
5467 });
5468 editor.update(cx, |editor, cx| {
5469 assert_text_with_selections(
5470 editor,
5471 indoc! {r#"
5472 use mod1::mod2::«{mod3, mod4}ˇ»;
5473
5474 fn fn_1«ˇ(param1: bool, param2: &str)» {
5475 «let var1 = "text";ˇ»
5476 }
5477 "#},
5478 cx,
5479 );
5480 });
5481}
5482
5483#[gpui::test]
5484async fn test_fold_function_bodies(cx: &mut gpui::TestAppContext) {
5485 init_test(cx, |_| {});
5486
5487 let base_text = r#"
5488 impl A {
5489 // this is an unstaged comment
5490
5491 fn b() {
5492 c();
5493 }
5494
5495 // this is another unstaged comment
5496
5497 fn d() {
5498 // e
5499 // f
5500 }
5501 }
5502
5503 fn g() {
5504 // h
5505 }
5506 "#
5507 .unindent();
5508
5509 let text = r#"
5510 ˇimpl A {
5511
5512 fn b() {
5513 c();
5514 }
5515
5516 fn d() {
5517 // e
5518 // f
5519 }
5520 }
5521
5522 fn g() {
5523 // h
5524 }
5525 "#
5526 .unindent();
5527
5528 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5529 cx.set_state(&text);
5530 cx.set_diff_base(&base_text);
5531 cx.update_editor(|editor, cx| {
5532 editor.expand_all_diff_hunks(&Default::default(), cx);
5533 });
5534
5535 cx.assert_state_with_diff(
5536 "
5537 ˇimpl A {
5538 - // this is an unstaged comment
5539
5540 fn b() {
5541 c();
5542 }
5543
5544 - // this is another unstaged comment
5545 -
5546 fn d() {
5547 // e
5548 // f
5549 }
5550 }
5551
5552 fn g() {
5553 // h
5554 }
5555 "
5556 .unindent(),
5557 );
5558
5559 let expected_display_text = "
5560 impl A {
5561 // this is an unstaged comment
5562
5563 fn b() {
5564 ⋯
5565 }
5566
5567 // this is another unstaged comment
5568
5569 fn d() {
5570 ⋯
5571 }
5572 }
5573
5574 fn g() {
5575 ⋯
5576 }
5577 "
5578 .unindent();
5579
5580 cx.update_editor(|editor, cx| {
5581 editor.fold_function_bodies(&FoldFunctionBodies, cx);
5582 assert_eq!(editor.display_text(cx), expected_display_text);
5583 });
5584}
5585
5586#[gpui::test]
5587async fn test_autoindent(cx: &mut gpui::TestAppContext) {
5588 init_test(cx, |_| {});
5589
5590 let language = Arc::new(
5591 Language::new(
5592 LanguageConfig {
5593 brackets: BracketPairConfig {
5594 pairs: vec![
5595 BracketPair {
5596 start: "{".to_string(),
5597 end: "}".to_string(),
5598 close: false,
5599 surround: false,
5600 newline: true,
5601 },
5602 BracketPair {
5603 start: "(".to_string(),
5604 end: ")".to_string(),
5605 close: false,
5606 surround: false,
5607 newline: true,
5608 },
5609 ],
5610 ..Default::default()
5611 },
5612 ..Default::default()
5613 },
5614 Some(tree_sitter_rust::LANGUAGE.into()),
5615 )
5616 .with_indents_query(
5617 r#"
5618 (_ "(" ")" @end) @indent
5619 (_ "{" "}" @end) @indent
5620 "#,
5621 )
5622 .unwrap(),
5623 );
5624
5625 let text = "fn a() {}";
5626
5627 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
5628 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
5629 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
5630 editor
5631 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5632 .await;
5633
5634 editor.update(cx, |editor, cx| {
5635 editor.change_selections(None, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
5636 editor.newline(&Newline, cx);
5637 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
5638 assert_eq!(
5639 editor.selections.ranges(cx),
5640 &[
5641 Point::new(1, 4)..Point::new(1, 4),
5642 Point::new(3, 4)..Point::new(3, 4),
5643 Point::new(5, 0)..Point::new(5, 0)
5644 ]
5645 );
5646 });
5647}
5648
5649#[gpui::test]
5650async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
5651 init_test(cx, |_| {});
5652
5653 {
5654 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5655 cx.set_state(indoc! {"
5656 impl A {
5657
5658 fn b() {}
5659
5660 «fn c() {
5661
5662 }ˇ»
5663 }
5664 "});
5665
5666 cx.update_editor(|editor, cx| {
5667 editor.autoindent(&Default::default(), cx);
5668 });
5669
5670 cx.assert_editor_state(indoc! {"
5671 impl A {
5672
5673 fn b() {}
5674
5675 «fn c() {
5676
5677 }ˇ»
5678 }
5679 "});
5680 }
5681
5682 {
5683 let mut cx = EditorTestContext::new_multibuffer(
5684 cx,
5685 [indoc! { "
5686 impl A {
5687 «
5688 // a
5689 fn b(){}
5690 »
5691 «
5692 }
5693 fn c(){}
5694 »
5695 "}],
5696 );
5697
5698 let buffer = cx.update_editor(|editor, cx| {
5699 let buffer = editor.buffer().update(cx, |buffer, _| {
5700 buffer.all_buffers().iter().next().unwrap().clone()
5701 });
5702 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5703 buffer
5704 });
5705
5706 cx.run_until_parked();
5707 cx.update_editor(|editor, cx| {
5708 editor.select_all(&Default::default(), cx);
5709 editor.autoindent(&Default::default(), cx)
5710 });
5711 cx.run_until_parked();
5712
5713 cx.update(|cx| {
5714 pretty_assertions::assert_eq!(
5715 buffer.read(cx).text(),
5716 indoc! { "
5717 impl A {
5718
5719 // a
5720 fn b(){}
5721
5722
5723 }
5724 fn c(){}
5725
5726 " }
5727 )
5728 });
5729 }
5730}
5731
5732#[gpui::test]
5733async fn test_autoclose_and_auto_surround_pairs(cx: &mut gpui::TestAppContext) {
5734 init_test(cx, |_| {});
5735
5736 let mut cx = EditorTestContext::new(cx).await;
5737
5738 let language = Arc::new(Language::new(
5739 LanguageConfig {
5740 brackets: BracketPairConfig {
5741 pairs: vec![
5742 BracketPair {
5743 start: "{".to_string(),
5744 end: "}".to_string(),
5745 close: true,
5746 surround: true,
5747 newline: true,
5748 },
5749 BracketPair {
5750 start: "(".to_string(),
5751 end: ")".to_string(),
5752 close: true,
5753 surround: true,
5754 newline: true,
5755 },
5756 BracketPair {
5757 start: "/*".to_string(),
5758 end: " */".to_string(),
5759 close: true,
5760 surround: true,
5761 newline: true,
5762 },
5763 BracketPair {
5764 start: "[".to_string(),
5765 end: "]".to_string(),
5766 close: false,
5767 surround: false,
5768 newline: true,
5769 },
5770 BracketPair {
5771 start: "\"".to_string(),
5772 end: "\"".to_string(),
5773 close: true,
5774 surround: true,
5775 newline: false,
5776 },
5777 BracketPair {
5778 start: "<".to_string(),
5779 end: ">".to_string(),
5780 close: false,
5781 surround: true,
5782 newline: true,
5783 },
5784 ],
5785 ..Default::default()
5786 },
5787 autoclose_before: "})]".to_string(),
5788 ..Default::default()
5789 },
5790 Some(tree_sitter_rust::LANGUAGE.into()),
5791 ));
5792
5793 cx.language_registry().add(language.clone());
5794 cx.update_buffer(|buffer, cx| {
5795 buffer.set_language(Some(language), cx);
5796 });
5797
5798 cx.set_state(
5799 &r#"
5800 🏀ˇ
5801 εˇ
5802 ❤️ˇ
5803 "#
5804 .unindent(),
5805 );
5806
5807 // autoclose multiple nested brackets at multiple cursors
5808 cx.update_editor(|view, cx| {
5809 view.handle_input("{", cx);
5810 view.handle_input("{", cx);
5811 view.handle_input("{", cx);
5812 });
5813 cx.assert_editor_state(
5814 &"
5815 🏀{{{ˇ}}}
5816 ε{{{ˇ}}}
5817 ❤️{{{ˇ}}}
5818 "
5819 .unindent(),
5820 );
5821
5822 // insert a different closing bracket
5823 cx.update_editor(|view, cx| {
5824 view.handle_input(")", cx);
5825 });
5826 cx.assert_editor_state(
5827 &"
5828 🏀{{{)ˇ}}}
5829 ε{{{)ˇ}}}
5830 ❤️{{{)ˇ}}}
5831 "
5832 .unindent(),
5833 );
5834
5835 // skip over the auto-closed brackets when typing a closing bracket
5836 cx.update_editor(|view, cx| {
5837 view.move_right(&MoveRight, cx);
5838 view.handle_input("}", cx);
5839 view.handle_input("}", cx);
5840 view.handle_input("}", cx);
5841 });
5842 cx.assert_editor_state(
5843 &"
5844 🏀{{{)}}}}ˇ
5845 ε{{{)}}}}ˇ
5846 ❤️{{{)}}}}ˇ
5847 "
5848 .unindent(),
5849 );
5850
5851 // autoclose multi-character pairs
5852 cx.set_state(
5853 &"
5854 ˇ
5855 ˇ
5856 "
5857 .unindent(),
5858 );
5859 cx.update_editor(|view, cx| {
5860 view.handle_input("/", cx);
5861 view.handle_input("*", cx);
5862 });
5863 cx.assert_editor_state(
5864 &"
5865 /*ˇ */
5866 /*ˇ */
5867 "
5868 .unindent(),
5869 );
5870
5871 // one cursor autocloses a multi-character pair, one cursor
5872 // does not autoclose.
5873 cx.set_state(
5874 &"
5875 /ˇ
5876 ˇ
5877 "
5878 .unindent(),
5879 );
5880 cx.update_editor(|view, cx| view.handle_input("*", cx));
5881 cx.assert_editor_state(
5882 &"
5883 /*ˇ */
5884 *ˇ
5885 "
5886 .unindent(),
5887 );
5888
5889 // Don't autoclose if the next character isn't whitespace and isn't
5890 // listed in the language's "autoclose_before" section.
5891 cx.set_state("ˇa b");
5892 cx.update_editor(|view, cx| view.handle_input("{", cx));
5893 cx.assert_editor_state("{ˇa b");
5894
5895 // Don't autoclose if `close` is false for the bracket pair
5896 cx.set_state("ˇ");
5897 cx.update_editor(|view, cx| view.handle_input("[", cx));
5898 cx.assert_editor_state("[ˇ");
5899
5900 // Surround with brackets if text is selected
5901 cx.set_state("«aˇ» b");
5902 cx.update_editor(|view, cx| view.handle_input("{", cx));
5903 cx.assert_editor_state("{«aˇ»} b");
5904
5905 // Autclose pair where the start and end characters are the same
5906 cx.set_state("aˇ");
5907 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5908 cx.assert_editor_state("a\"ˇ\"");
5909 cx.update_editor(|view, cx| view.handle_input("\"", cx));
5910 cx.assert_editor_state("a\"\"ˇ");
5911
5912 // Don't autoclose pair if autoclose is disabled
5913 cx.set_state("ˇ");
5914 cx.update_editor(|view, cx| view.handle_input("<", cx));
5915 cx.assert_editor_state("<ˇ");
5916
5917 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
5918 cx.set_state("«aˇ» b");
5919 cx.update_editor(|view, cx| view.handle_input("<", cx));
5920 cx.assert_editor_state("<«aˇ»> b");
5921}
5922
5923#[gpui::test]
5924async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut gpui::TestAppContext) {
5925 init_test(cx, |settings| {
5926 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
5927 });
5928
5929 let mut cx = EditorTestContext::new(cx).await;
5930
5931 let language = Arc::new(Language::new(
5932 LanguageConfig {
5933 brackets: BracketPairConfig {
5934 pairs: vec![
5935 BracketPair {
5936 start: "{".to_string(),
5937 end: "}".to_string(),
5938 close: true,
5939 surround: true,
5940 newline: true,
5941 },
5942 BracketPair {
5943 start: "(".to_string(),
5944 end: ")".to_string(),
5945 close: true,
5946 surround: true,
5947 newline: true,
5948 },
5949 BracketPair {
5950 start: "[".to_string(),
5951 end: "]".to_string(),
5952 close: false,
5953 surround: false,
5954 newline: true,
5955 },
5956 ],
5957 ..Default::default()
5958 },
5959 autoclose_before: "})]".to_string(),
5960 ..Default::default()
5961 },
5962 Some(tree_sitter_rust::LANGUAGE.into()),
5963 ));
5964
5965 cx.language_registry().add(language.clone());
5966 cx.update_buffer(|buffer, cx| {
5967 buffer.set_language(Some(language), cx);
5968 });
5969
5970 cx.set_state(
5971 &"
5972 ˇ
5973 ˇ
5974 ˇ
5975 "
5976 .unindent(),
5977 );
5978
5979 // ensure only matching closing brackets are skipped over
5980 cx.update_editor(|view, cx| {
5981 view.handle_input("}", cx);
5982 view.move_left(&MoveLeft, cx);
5983 view.handle_input(")", cx);
5984 view.move_left(&MoveLeft, cx);
5985 });
5986 cx.assert_editor_state(
5987 &"
5988 ˇ)}
5989 ˇ)}
5990 ˇ)}
5991 "
5992 .unindent(),
5993 );
5994
5995 // skip-over closing brackets at multiple cursors
5996 cx.update_editor(|view, cx| {
5997 view.handle_input(")", cx);
5998 view.handle_input("}", cx);
5999 });
6000 cx.assert_editor_state(
6001 &"
6002 )}ˇ
6003 )}ˇ
6004 )}ˇ
6005 "
6006 .unindent(),
6007 );
6008
6009 // ignore non-close brackets
6010 cx.update_editor(|view, cx| {
6011 view.handle_input("]", cx);
6012 view.move_left(&MoveLeft, cx);
6013 view.handle_input("]", cx);
6014 });
6015 cx.assert_editor_state(
6016 &"
6017 )}]ˇ]
6018 )}]ˇ]
6019 )}]ˇ]
6020 "
6021 .unindent(),
6022 );
6023}
6024
6025#[gpui::test]
6026async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
6027 init_test(cx, |_| {});
6028
6029 let mut cx = EditorTestContext::new(cx).await;
6030
6031 let html_language = Arc::new(
6032 Language::new(
6033 LanguageConfig {
6034 name: "HTML".into(),
6035 brackets: BracketPairConfig {
6036 pairs: vec![
6037 BracketPair {
6038 start: "<".into(),
6039 end: ">".into(),
6040 close: true,
6041 ..Default::default()
6042 },
6043 BracketPair {
6044 start: "{".into(),
6045 end: "}".into(),
6046 close: true,
6047 ..Default::default()
6048 },
6049 BracketPair {
6050 start: "(".into(),
6051 end: ")".into(),
6052 close: true,
6053 ..Default::default()
6054 },
6055 ],
6056 ..Default::default()
6057 },
6058 autoclose_before: "})]>".into(),
6059 ..Default::default()
6060 },
6061 Some(tree_sitter_html::language()),
6062 )
6063 .with_injection_query(
6064 r#"
6065 (script_element
6066 (raw_text) @injection.content
6067 (#set! injection.language "javascript"))
6068 "#,
6069 )
6070 .unwrap(),
6071 );
6072
6073 let javascript_language = Arc::new(Language::new(
6074 LanguageConfig {
6075 name: "JavaScript".into(),
6076 brackets: BracketPairConfig {
6077 pairs: vec![
6078 BracketPair {
6079 start: "/*".into(),
6080 end: " */".into(),
6081 close: true,
6082 ..Default::default()
6083 },
6084 BracketPair {
6085 start: "{".into(),
6086 end: "}".into(),
6087 close: true,
6088 ..Default::default()
6089 },
6090 BracketPair {
6091 start: "(".into(),
6092 end: ")".into(),
6093 close: true,
6094 ..Default::default()
6095 },
6096 ],
6097 ..Default::default()
6098 },
6099 autoclose_before: "})]>".into(),
6100 ..Default::default()
6101 },
6102 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
6103 ));
6104
6105 cx.language_registry().add(html_language.clone());
6106 cx.language_registry().add(javascript_language.clone());
6107
6108 cx.update_buffer(|buffer, cx| {
6109 buffer.set_language(Some(html_language), cx);
6110 });
6111
6112 cx.set_state(
6113 &r#"
6114 <body>ˇ
6115 <script>
6116 var x = 1;ˇ
6117 </script>
6118 </body>ˇ
6119 "#
6120 .unindent(),
6121 );
6122
6123 // Precondition: different languages are active at different locations.
6124 cx.update_editor(|editor, cx| {
6125 let snapshot = editor.snapshot(cx);
6126 let cursors = editor.selections.ranges::<usize>(cx);
6127 let languages = cursors
6128 .iter()
6129 .map(|c| snapshot.language_at(c.start).unwrap().name())
6130 .collect::<Vec<_>>();
6131 assert_eq!(
6132 languages,
6133 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6134 );
6135 });
6136
6137 // Angle brackets autoclose in HTML, but not JavaScript.
6138 cx.update_editor(|editor, cx| {
6139 editor.handle_input("<", cx);
6140 editor.handle_input("a", cx);
6141 });
6142 cx.assert_editor_state(
6143 &r#"
6144 <body><aˇ>
6145 <script>
6146 var x = 1;<aˇ
6147 </script>
6148 </body><aˇ>
6149 "#
6150 .unindent(),
6151 );
6152
6153 // Curly braces and parens autoclose in both HTML and JavaScript.
6154 cx.update_editor(|editor, cx| {
6155 editor.handle_input(" b=", cx);
6156 editor.handle_input("{", cx);
6157 editor.handle_input("c", cx);
6158 editor.handle_input("(", cx);
6159 });
6160 cx.assert_editor_state(
6161 &r#"
6162 <body><a b={c(ˇ)}>
6163 <script>
6164 var x = 1;<a b={c(ˇ)}
6165 </script>
6166 </body><a b={c(ˇ)}>
6167 "#
6168 .unindent(),
6169 );
6170
6171 // Brackets that were already autoclosed are skipped.
6172 cx.update_editor(|editor, cx| {
6173 editor.handle_input(")", cx);
6174 editor.handle_input("d", cx);
6175 editor.handle_input("}", cx);
6176 });
6177 cx.assert_editor_state(
6178 &r#"
6179 <body><a b={c()d}ˇ>
6180 <script>
6181 var x = 1;<a b={c()d}ˇ
6182 </script>
6183 </body><a b={c()d}ˇ>
6184 "#
6185 .unindent(),
6186 );
6187 cx.update_editor(|editor, cx| {
6188 editor.handle_input(">", cx);
6189 });
6190 cx.assert_editor_state(
6191 &r#"
6192 <body><a b={c()d}>ˇ
6193 <script>
6194 var x = 1;<a b={c()d}>ˇ
6195 </script>
6196 </body><a b={c()d}>ˇ
6197 "#
6198 .unindent(),
6199 );
6200
6201 // Reset
6202 cx.set_state(
6203 &r#"
6204 <body>ˇ
6205 <script>
6206 var x = 1;ˇ
6207 </script>
6208 </body>ˇ
6209 "#
6210 .unindent(),
6211 );
6212
6213 cx.update_editor(|editor, cx| {
6214 editor.handle_input("<", cx);
6215 });
6216 cx.assert_editor_state(
6217 &r#"
6218 <body><ˇ>
6219 <script>
6220 var x = 1;<ˇ
6221 </script>
6222 </body><ˇ>
6223 "#
6224 .unindent(),
6225 );
6226
6227 // When backspacing, the closing angle brackets are removed.
6228 cx.update_editor(|editor, cx| {
6229 editor.backspace(&Backspace, cx);
6230 });
6231 cx.assert_editor_state(
6232 &r#"
6233 <body>ˇ
6234 <script>
6235 var x = 1;ˇ
6236 </script>
6237 </body>ˇ
6238 "#
6239 .unindent(),
6240 );
6241
6242 // Block comments autoclose in JavaScript, but not HTML.
6243 cx.update_editor(|editor, cx| {
6244 editor.handle_input("/", cx);
6245 editor.handle_input("*", cx);
6246 });
6247 cx.assert_editor_state(
6248 &r#"
6249 <body>/*ˇ
6250 <script>
6251 var x = 1;/*ˇ */
6252 </script>
6253 </body>/*ˇ
6254 "#
6255 .unindent(),
6256 );
6257}
6258
6259#[gpui::test]
6260async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
6261 init_test(cx, |_| {});
6262
6263 let mut cx = EditorTestContext::new(cx).await;
6264
6265 let rust_language = Arc::new(
6266 Language::new(
6267 LanguageConfig {
6268 name: "Rust".into(),
6269 brackets: serde_json::from_value(json!([
6270 { "start": "{", "end": "}", "close": true, "newline": true },
6271 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6272 ]))
6273 .unwrap(),
6274 autoclose_before: "})]>".into(),
6275 ..Default::default()
6276 },
6277 Some(tree_sitter_rust::LANGUAGE.into()),
6278 )
6279 .with_override_query("(string_literal) @string")
6280 .unwrap(),
6281 );
6282
6283 cx.language_registry().add(rust_language.clone());
6284 cx.update_buffer(|buffer, cx| {
6285 buffer.set_language(Some(rust_language), cx);
6286 });
6287
6288 cx.set_state(
6289 &r#"
6290 let x = ˇ
6291 "#
6292 .unindent(),
6293 );
6294
6295 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6296 cx.update_editor(|editor, cx| {
6297 editor.handle_input("\"", cx);
6298 });
6299 cx.assert_editor_state(
6300 &r#"
6301 let x = "ˇ"
6302 "#
6303 .unindent(),
6304 );
6305
6306 // Inserting another quotation mark. The cursor moves across the existing
6307 // automatically-inserted quotation mark.
6308 cx.update_editor(|editor, cx| {
6309 editor.handle_input("\"", cx);
6310 });
6311 cx.assert_editor_state(
6312 &r#"
6313 let x = ""ˇ
6314 "#
6315 .unindent(),
6316 );
6317
6318 // Reset
6319 cx.set_state(
6320 &r#"
6321 let x = ˇ
6322 "#
6323 .unindent(),
6324 );
6325
6326 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6327 cx.update_editor(|editor, cx| {
6328 editor.handle_input("\"", cx);
6329 editor.handle_input(" ", cx);
6330 editor.move_left(&Default::default(), cx);
6331 editor.handle_input("\\", cx);
6332 editor.handle_input("\"", cx);
6333 });
6334 cx.assert_editor_state(
6335 &r#"
6336 let x = "\"ˇ "
6337 "#
6338 .unindent(),
6339 );
6340
6341 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6342 // mark. Nothing is inserted.
6343 cx.update_editor(|editor, cx| {
6344 editor.move_right(&Default::default(), cx);
6345 editor.handle_input("\"", cx);
6346 });
6347 cx.assert_editor_state(
6348 &r#"
6349 let x = "\" "ˇ
6350 "#
6351 .unindent(),
6352 );
6353}
6354
6355#[gpui::test]
6356async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
6357 init_test(cx, |_| {});
6358
6359 let language = Arc::new(Language::new(
6360 LanguageConfig {
6361 brackets: BracketPairConfig {
6362 pairs: vec![
6363 BracketPair {
6364 start: "{".to_string(),
6365 end: "}".to_string(),
6366 close: true,
6367 surround: true,
6368 newline: true,
6369 },
6370 BracketPair {
6371 start: "/* ".to_string(),
6372 end: "*/".to_string(),
6373 close: true,
6374 surround: true,
6375 ..Default::default()
6376 },
6377 ],
6378 ..Default::default()
6379 },
6380 ..Default::default()
6381 },
6382 Some(tree_sitter_rust::LANGUAGE.into()),
6383 ));
6384
6385 let text = r#"
6386 a
6387 b
6388 c
6389 "#
6390 .unindent();
6391
6392 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6393 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6394 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6395 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6396 .await;
6397
6398 view.update(cx, |view, cx| {
6399 view.change_selections(None, cx, |s| {
6400 s.select_display_ranges([
6401 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6402 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6403 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6404 ])
6405 });
6406
6407 view.handle_input("{", cx);
6408 view.handle_input("{", cx);
6409 view.handle_input("{", cx);
6410 assert_eq!(
6411 view.text(cx),
6412 "
6413 {{{a}}}
6414 {{{b}}}
6415 {{{c}}}
6416 "
6417 .unindent()
6418 );
6419 assert_eq!(
6420 view.selections.display_ranges(cx),
6421 [
6422 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6423 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6424 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6425 ]
6426 );
6427
6428 view.undo(&Undo, cx);
6429 view.undo(&Undo, cx);
6430 view.undo(&Undo, cx);
6431 assert_eq!(
6432 view.text(cx),
6433 "
6434 a
6435 b
6436 c
6437 "
6438 .unindent()
6439 );
6440 assert_eq!(
6441 view.selections.display_ranges(cx),
6442 [
6443 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6444 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6445 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6446 ]
6447 );
6448
6449 // Ensure inserting the first character of a multi-byte bracket pair
6450 // doesn't surround the selections with the bracket.
6451 view.handle_input("/", cx);
6452 assert_eq!(
6453 view.text(cx),
6454 "
6455 /
6456 /
6457 /
6458 "
6459 .unindent()
6460 );
6461 assert_eq!(
6462 view.selections.display_ranges(cx),
6463 [
6464 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6465 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6466 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6467 ]
6468 );
6469
6470 view.undo(&Undo, cx);
6471 assert_eq!(
6472 view.text(cx),
6473 "
6474 a
6475 b
6476 c
6477 "
6478 .unindent()
6479 );
6480 assert_eq!(
6481 view.selections.display_ranges(cx),
6482 [
6483 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6484 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6485 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6486 ]
6487 );
6488
6489 // Ensure inserting the last character of a multi-byte bracket pair
6490 // doesn't surround the selections with the bracket.
6491 view.handle_input("*", cx);
6492 assert_eq!(
6493 view.text(cx),
6494 "
6495 *
6496 *
6497 *
6498 "
6499 .unindent()
6500 );
6501 assert_eq!(
6502 view.selections.display_ranges(cx),
6503 [
6504 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6505 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6506 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6507 ]
6508 );
6509 });
6510}
6511
6512#[gpui::test]
6513async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
6514 init_test(cx, |_| {});
6515
6516 let language = Arc::new(Language::new(
6517 LanguageConfig {
6518 brackets: BracketPairConfig {
6519 pairs: vec![BracketPair {
6520 start: "{".to_string(),
6521 end: "}".to_string(),
6522 close: true,
6523 surround: true,
6524 newline: true,
6525 }],
6526 ..Default::default()
6527 },
6528 autoclose_before: "}".to_string(),
6529 ..Default::default()
6530 },
6531 Some(tree_sitter_rust::LANGUAGE.into()),
6532 ));
6533
6534 let text = r#"
6535 a
6536 b
6537 c
6538 "#
6539 .unindent();
6540
6541 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
6542 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6543 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6544 editor
6545 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6546 .await;
6547
6548 editor.update(cx, |editor, cx| {
6549 editor.change_selections(None, cx, |s| {
6550 s.select_ranges([
6551 Point::new(0, 1)..Point::new(0, 1),
6552 Point::new(1, 1)..Point::new(1, 1),
6553 Point::new(2, 1)..Point::new(2, 1),
6554 ])
6555 });
6556
6557 editor.handle_input("{", cx);
6558 editor.handle_input("{", cx);
6559 editor.handle_input("_", cx);
6560 assert_eq!(
6561 editor.text(cx),
6562 "
6563 a{{_}}
6564 b{{_}}
6565 c{{_}}
6566 "
6567 .unindent()
6568 );
6569 assert_eq!(
6570 editor.selections.ranges::<Point>(cx),
6571 [
6572 Point::new(0, 4)..Point::new(0, 4),
6573 Point::new(1, 4)..Point::new(1, 4),
6574 Point::new(2, 4)..Point::new(2, 4)
6575 ]
6576 );
6577
6578 editor.backspace(&Default::default(), cx);
6579 editor.backspace(&Default::default(), cx);
6580 assert_eq!(
6581 editor.text(cx),
6582 "
6583 a{}
6584 b{}
6585 c{}
6586 "
6587 .unindent()
6588 );
6589 assert_eq!(
6590 editor.selections.ranges::<Point>(cx),
6591 [
6592 Point::new(0, 2)..Point::new(0, 2),
6593 Point::new(1, 2)..Point::new(1, 2),
6594 Point::new(2, 2)..Point::new(2, 2)
6595 ]
6596 );
6597
6598 editor.delete_to_previous_word_start(&Default::default(), cx);
6599 assert_eq!(
6600 editor.text(cx),
6601 "
6602 a
6603 b
6604 c
6605 "
6606 .unindent()
6607 );
6608 assert_eq!(
6609 editor.selections.ranges::<Point>(cx),
6610 [
6611 Point::new(0, 1)..Point::new(0, 1),
6612 Point::new(1, 1)..Point::new(1, 1),
6613 Point::new(2, 1)..Point::new(2, 1)
6614 ]
6615 );
6616 });
6617}
6618
6619#[gpui::test]
6620async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut gpui::TestAppContext) {
6621 init_test(cx, |settings| {
6622 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6623 });
6624
6625 let mut cx = EditorTestContext::new(cx).await;
6626
6627 let language = Arc::new(Language::new(
6628 LanguageConfig {
6629 brackets: BracketPairConfig {
6630 pairs: vec![
6631 BracketPair {
6632 start: "{".to_string(),
6633 end: "}".to_string(),
6634 close: true,
6635 surround: true,
6636 newline: true,
6637 },
6638 BracketPair {
6639 start: "(".to_string(),
6640 end: ")".to_string(),
6641 close: true,
6642 surround: true,
6643 newline: true,
6644 },
6645 BracketPair {
6646 start: "[".to_string(),
6647 end: "]".to_string(),
6648 close: false,
6649 surround: true,
6650 newline: true,
6651 },
6652 ],
6653 ..Default::default()
6654 },
6655 autoclose_before: "})]".to_string(),
6656 ..Default::default()
6657 },
6658 Some(tree_sitter_rust::LANGUAGE.into()),
6659 ));
6660
6661 cx.language_registry().add(language.clone());
6662 cx.update_buffer(|buffer, cx| {
6663 buffer.set_language(Some(language), cx);
6664 });
6665
6666 cx.set_state(
6667 &"
6668 {(ˇ)}
6669 [[ˇ]]
6670 {(ˇ)}
6671 "
6672 .unindent(),
6673 );
6674
6675 cx.update_editor(|view, cx| {
6676 view.backspace(&Default::default(), cx);
6677 view.backspace(&Default::default(), cx);
6678 });
6679
6680 cx.assert_editor_state(
6681 &"
6682 ˇ
6683 ˇ]]
6684 ˇ
6685 "
6686 .unindent(),
6687 );
6688
6689 cx.update_editor(|view, cx| {
6690 view.handle_input("{", cx);
6691 view.handle_input("{", cx);
6692 view.move_right(&MoveRight, cx);
6693 view.move_right(&MoveRight, cx);
6694 view.move_left(&MoveLeft, cx);
6695 view.move_left(&MoveLeft, cx);
6696 view.backspace(&Default::default(), cx);
6697 });
6698
6699 cx.assert_editor_state(
6700 &"
6701 {ˇ}
6702 {ˇ}]]
6703 {ˇ}
6704 "
6705 .unindent(),
6706 );
6707
6708 cx.update_editor(|view, cx| {
6709 view.backspace(&Default::default(), cx);
6710 });
6711
6712 cx.assert_editor_state(
6713 &"
6714 ˇ
6715 ˇ]]
6716 ˇ
6717 "
6718 .unindent(),
6719 );
6720}
6721
6722#[gpui::test]
6723async fn test_auto_replace_emoji_shortcode(cx: &mut gpui::TestAppContext) {
6724 init_test(cx, |_| {});
6725
6726 let language = Arc::new(Language::new(
6727 LanguageConfig::default(),
6728 Some(tree_sitter_rust::LANGUAGE.into()),
6729 ));
6730
6731 let buffer = cx.new_model(|cx| Buffer::local("", cx).with_language(language, cx));
6732 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6733 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6734 editor
6735 .condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
6736 .await;
6737
6738 editor.update(cx, |editor, cx| {
6739 editor.set_auto_replace_emoji_shortcode(true);
6740
6741 editor.handle_input("Hello ", cx);
6742 editor.handle_input(":wave", cx);
6743 assert_eq!(editor.text(cx), "Hello :wave".unindent());
6744
6745 editor.handle_input(":", cx);
6746 assert_eq!(editor.text(cx), "Hello 👋".unindent());
6747
6748 editor.handle_input(" :smile", cx);
6749 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
6750
6751 editor.handle_input(":", cx);
6752 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
6753
6754 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
6755 editor.handle_input(":wave", cx);
6756 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
6757
6758 editor.handle_input(":", cx);
6759 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
6760
6761 editor.handle_input(":1", cx);
6762 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
6763
6764 editor.handle_input(":", cx);
6765 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
6766
6767 // Ensure shortcode does not get replaced when it is part of a word
6768 editor.handle_input(" Test:wave", cx);
6769 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
6770
6771 editor.handle_input(":", cx);
6772 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
6773
6774 editor.set_auto_replace_emoji_shortcode(false);
6775
6776 // Ensure shortcode does not get replaced when auto replace is off
6777 editor.handle_input(" :wave", cx);
6778 assert_eq!(
6779 editor.text(cx),
6780 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
6781 );
6782
6783 editor.handle_input(":", cx);
6784 assert_eq!(
6785 editor.text(cx),
6786 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
6787 );
6788 });
6789}
6790
6791#[gpui::test]
6792async fn test_snippet_placeholder_choices(cx: &mut gpui::TestAppContext) {
6793 init_test(cx, |_| {});
6794
6795 let (text, insertion_ranges) = marked_text_ranges(
6796 indoc! {"
6797 ˇ
6798 "},
6799 false,
6800 );
6801
6802 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6803 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6804
6805 _ = editor.update(cx, |editor, cx| {
6806 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
6807
6808 editor
6809 .insert_snippet(&insertion_ranges, snippet, cx)
6810 .unwrap();
6811
6812 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6813 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6814 assert_eq!(editor.text(cx), expected_text);
6815 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6816 }
6817
6818 assert(
6819 editor,
6820 cx,
6821 indoc! {"
6822 type «» =•
6823 "},
6824 );
6825
6826 assert!(editor.context_menu_visible(), "There should be a matches");
6827 });
6828}
6829
6830#[gpui::test]
6831async fn test_snippets(cx: &mut gpui::TestAppContext) {
6832 init_test(cx, |_| {});
6833
6834 let (text, insertion_ranges) = marked_text_ranges(
6835 indoc! {"
6836 a.ˇ b
6837 a.ˇ b
6838 a.ˇ b
6839 "},
6840 false,
6841 );
6842
6843 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
6844 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
6845
6846 editor.update(cx, |editor, cx| {
6847 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
6848
6849 editor
6850 .insert_snippet(&insertion_ranges, snippet, cx)
6851 .unwrap();
6852
6853 fn assert(editor: &mut Editor, cx: &mut ViewContext<Editor>, marked_text: &str) {
6854 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
6855 assert_eq!(editor.text(cx), expected_text);
6856 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
6857 }
6858
6859 assert(
6860 editor,
6861 cx,
6862 indoc! {"
6863 a.f(«one», two, «three») b
6864 a.f(«one», two, «three») b
6865 a.f(«one», two, «three») b
6866 "},
6867 );
6868
6869 // Can't move earlier than the first tab stop
6870 assert!(!editor.move_to_prev_snippet_tabstop(cx));
6871 assert(
6872 editor,
6873 cx,
6874 indoc! {"
6875 a.f(«one», two, «three») b
6876 a.f(«one», two, «three») b
6877 a.f(«one», two, «three») b
6878 "},
6879 );
6880
6881 assert!(editor.move_to_next_snippet_tabstop(cx));
6882 assert(
6883 editor,
6884 cx,
6885 indoc! {"
6886 a.f(one, «two», three) b
6887 a.f(one, «two», three) b
6888 a.f(one, «two», three) b
6889 "},
6890 );
6891
6892 editor.move_to_prev_snippet_tabstop(cx);
6893 assert(
6894 editor,
6895 cx,
6896 indoc! {"
6897 a.f(«one», two, «three») b
6898 a.f(«one», two, «three») b
6899 a.f(«one», two, «three») b
6900 "},
6901 );
6902
6903 assert!(editor.move_to_next_snippet_tabstop(cx));
6904 assert(
6905 editor,
6906 cx,
6907 indoc! {"
6908 a.f(one, «two», three) b
6909 a.f(one, «two», three) b
6910 a.f(one, «two», three) b
6911 "},
6912 );
6913 assert!(editor.move_to_next_snippet_tabstop(cx));
6914 assert(
6915 editor,
6916 cx,
6917 indoc! {"
6918 a.f(one, two, three)ˇ b
6919 a.f(one, two, three)ˇ b
6920 a.f(one, two, three)ˇ b
6921 "},
6922 );
6923
6924 // As soon as the last tab stop is reached, snippet state is gone
6925 editor.move_to_prev_snippet_tabstop(cx);
6926 assert(
6927 editor,
6928 cx,
6929 indoc! {"
6930 a.f(one, two, three)ˇ b
6931 a.f(one, two, three)ˇ b
6932 a.f(one, two, three)ˇ b
6933 "},
6934 );
6935 });
6936}
6937
6938#[gpui::test]
6939async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
6940 init_test(cx, |_| {});
6941
6942 let fs = FakeFs::new(cx.executor());
6943 fs.insert_file("/file.rs", Default::default()).await;
6944
6945 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
6946
6947 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
6948 language_registry.add(rust_lang());
6949 let mut fake_servers = language_registry.register_fake_lsp(
6950 "Rust",
6951 FakeLspAdapter {
6952 capabilities: lsp::ServerCapabilities {
6953 document_formatting_provider: Some(lsp::OneOf::Left(true)),
6954 ..Default::default()
6955 },
6956 ..Default::default()
6957 },
6958 );
6959
6960 let buffer = project
6961 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
6962 .await
6963 .unwrap();
6964
6965 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
6966 let (editor, cx) =
6967 cx.add_window_view(|cx| build_editor_with_project(project.clone(), buffer, cx));
6968 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
6969 assert!(cx.read(|cx| editor.is_dirty(cx)));
6970
6971 cx.executor().start_waiting();
6972 let fake_server = fake_servers.next().await.unwrap();
6973
6974 let save = editor
6975 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
6976 .unwrap();
6977 fake_server
6978 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
6979 assert_eq!(
6980 params.text_document.uri,
6981 lsp::Url::from_file_path("/file.rs").unwrap()
6982 );
6983 assert_eq!(params.options.tab_size, 4);
6984 Ok(Some(vec![lsp::TextEdit::new(
6985 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
6986 ", ".to_string(),
6987 )]))
6988 })
6989 .next()
6990 .await;
6991 cx.executor().start_waiting();
6992 save.await;
6993
6994 assert_eq!(
6995 editor.update(cx, |editor, cx| editor.text(cx)),
6996 "one, two\nthree\n"
6997 );
6998 assert!(!cx.read(|cx| editor.is_dirty(cx)));
6999
7000 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7001 assert!(cx.read(|cx| editor.is_dirty(cx)));
7002
7003 // Ensure we can still save even if formatting hangs.
7004 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7005 assert_eq!(
7006 params.text_document.uri,
7007 lsp::Url::from_file_path("/file.rs").unwrap()
7008 );
7009 futures::future::pending::<()>().await;
7010 unreachable!()
7011 });
7012 let save = editor
7013 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7014 .unwrap();
7015 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7016 cx.executor().start_waiting();
7017 save.await;
7018 assert_eq!(
7019 editor.update(cx, |editor, cx| editor.text(cx)),
7020 "one\ntwo\nthree\n"
7021 );
7022 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7023
7024 // For non-dirty buffer, no formatting request should be sent
7025 let save = editor
7026 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7027 .unwrap();
7028 let _pending_format_request = fake_server
7029 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7030 panic!("Should not be invoked on non-dirty buffer");
7031 })
7032 .next();
7033 cx.executor().start_waiting();
7034 save.await;
7035
7036 // Set rust language override and assert overridden tabsize is sent to language server
7037 update_test_language_settings(cx, |settings| {
7038 settings.languages.insert(
7039 "Rust".into(),
7040 LanguageSettingsContent {
7041 tab_size: NonZeroU32::new(8),
7042 ..Default::default()
7043 },
7044 );
7045 });
7046
7047 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
7048 assert!(cx.read(|cx| editor.is_dirty(cx)));
7049 let save = editor
7050 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7051 .unwrap();
7052 fake_server
7053 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7054 assert_eq!(
7055 params.text_document.uri,
7056 lsp::Url::from_file_path("/file.rs").unwrap()
7057 );
7058 assert_eq!(params.options.tab_size, 8);
7059 Ok(Some(vec![]))
7060 })
7061 .next()
7062 .await;
7063 cx.executor().start_waiting();
7064 save.await;
7065}
7066
7067#[gpui::test]
7068async fn test_multibuffer_format_during_save(cx: &mut gpui::TestAppContext) {
7069 init_test(cx, |_| {});
7070
7071 let cols = 4;
7072 let rows = 10;
7073 let sample_text_1 = sample_text(rows, cols, 'a');
7074 assert_eq!(
7075 sample_text_1,
7076 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
7077 );
7078 let sample_text_2 = sample_text(rows, cols, 'l');
7079 assert_eq!(
7080 sample_text_2,
7081 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
7082 );
7083 let sample_text_3 = sample_text(rows, cols, 'v');
7084 assert_eq!(
7085 sample_text_3,
7086 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
7087 );
7088
7089 let fs = FakeFs::new(cx.executor());
7090 fs.insert_tree(
7091 "/a",
7092 json!({
7093 "main.rs": sample_text_1,
7094 "other.rs": sample_text_2,
7095 "lib.rs": sample_text_3,
7096 }),
7097 )
7098 .await;
7099
7100 let project = Project::test(fs, ["/a".as_ref()], cx).await;
7101 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
7102 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7103
7104 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7105 language_registry.add(rust_lang());
7106 let mut fake_servers = language_registry.register_fake_lsp(
7107 "Rust",
7108 FakeLspAdapter {
7109 capabilities: lsp::ServerCapabilities {
7110 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7111 ..Default::default()
7112 },
7113 ..Default::default()
7114 },
7115 );
7116
7117 let worktree = project.update(cx, |project, cx| {
7118 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
7119 assert_eq!(worktrees.len(), 1);
7120 worktrees.pop().unwrap()
7121 });
7122 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
7123
7124 let buffer_1 = project
7125 .update(cx, |project, cx| {
7126 project.open_buffer((worktree_id, "main.rs"), cx)
7127 })
7128 .await
7129 .unwrap();
7130 let buffer_2 = project
7131 .update(cx, |project, cx| {
7132 project.open_buffer((worktree_id, "other.rs"), cx)
7133 })
7134 .await
7135 .unwrap();
7136 let buffer_3 = project
7137 .update(cx, |project, cx| {
7138 project.open_buffer((worktree_id, "lib.rs"), cx)
7139 })
7140 .await
7141 .unwrap();
7142
7143 let multi_buffer = cx.new_model(|cx| {
7144 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7145 multi_buffer.push_excerpts(
7146 buffer_1.clone(),
7147 [
7148 ExcerptRange {
7149 context: Point::new(0, 0)..Point::new(3, 0),
7150 primary: None,
7151 },
7152 ExcerptRange {
7153 context: Point::new(5, 0)..Point::new(7, 0),
7154 primary: None,
7155 },
7156 ExcerptRange {
7157 context: Point::new(9, 0)..Point::new(10, 4),
7158 primary: None,
7159 },
7160 ],
7161 cx,
7162 );
7163 multi_buffer.push_excerpts(
7164 buffer_2.clone(),
7165 [
7166 ExcerptRange {
7167 context: Point::new(0, 0)..Point::new(3, 0),
7168 primary: None,
7169 },
7170 ExcerptRange {
7171 context: Point::new(5, 0)..Point::new(7, 0),
7172 primary: None,
7173 },
7174 ExcerptRange {
7175 context: Point::new(9, 0)..Point::new(10, 4),
7176 primary: None,
7177 },
7178 ],
7179 cx,
7180 );
7181 multi_buffer.push_excerpts(
7182 buffer_3.clone(),
7183 [
7184 ExcerptRange {
7185 context: Point::new(0, 0)..Point::new(3, 0),
7186 primary: None,
7187 },
7188 ExcerptRange {
7189 context: Point::new(5, 0)..Point::new(7, 0),
7190 primary: None,
7191 },
7192 ExcerptRange {
7193 context: Point::new(9, 0)..Point::new(10, 4),
7194 primary: None,
7195 },
7196 ],
7197 cx,
7198 );
7199 multi_buffer
7200 });
7201 let multi_buffer_editor = cx.new_view(|cx| {
7202 Editor::new(
7203 EditorMode::Full,
7204 multi_buffer,
7205 Some(project.clone()),
7206 true,
7207 cx,
7208 )
7209 });
7210
7211 multi_buffer_editor.update(cx, |editor, cx| {
7212 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
7213 editor.insert("|one|two|three|", cx);
7214 });
7215 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7216 multi_buffer_editor.update(cx, |editor, cx| {
7217 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
7218 s.select_ranges(Some(60..70))
7219 });
7220 editor.insert("|four|five|six|", cx);
7221 });
7222 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7223
7224 // First two buffers should be edited, but not the third one.
7225 assert_eq!(
7226 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7227 "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}",
7228 );
7229 buffer_1.update(cx, |buffer, _| {
7230 assert!(buffer.is_dirty());
7231 assert_eq!(
7232 buffer.text(),
7233 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7234 )
7235 });
7236 buffer_2.update(cx, |buffer, _| {
7237 assert!(buffer.is_dirty());
7238 assert_eq!(
7239 buffer.text(),
7240 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7241 )
7242 });
7243 buffer_3.update(cx, |buffer, _| {
7244 assert!(!buffer.is_dirty());
7245 assert_eq!(buffer.text(), sample_text_3,)
7246 });
7247 cx.executor().run_until_parked();
7248
7249 cx.executor().start_waiting();
7250 let save = multi_buffer_editor
7251 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7252 .unwrap();
7253
7254 let fake_server = fake_servers.next().await.unwrap();
7255 fake_server
7256 .server
7257 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7258 Ok(Some(vec![lsp::TextEdit::new(
7259 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7260 format!("[{} formatted]", params.text_document.uri),
7261 )]))
7262 })
7263 .detach();
7264 save.await;
7265
7266 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7267 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7268 assert_eq!(
7269 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7270 "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}",
7271 );
7272 buffer_1.update(cx, |buffer, _| {
7273 assert!(!buffer.is_dirty());
7274 assert_eq!(
7275 buffer.text(),
7276 "a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
7277 )
7278 });
7279 buffer_2.update(cx, |buffer, _| {
7280 assert!(!buffer.is_dirty());
7281 assert_eq!(
7282 buffer.text(),
7283 "lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n",
7284 )
7285 });
7286 buffer_3.update(cx, |buffer, _| {
7287 assert!(!buffer.is_dirty());
7288 assert_eq!(buffer.text(), sample_text_3,)
7289 });
7290}
7291
7292#[gpui::test]
7293async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
7294 init_test(cx, |_| {});
7295
7296 let fs = FakeFs::new(cx.executor());
7297 fs.insert_file("/file.rs", Default::default()).await;
7298
7299 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7300
7301 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7302 language_registry.add(rust_lang());
7303 let mut fake_servers = language_registry.register_fake_lsp(
7304 "Rust",
7305 FakeLspAdapter {
7306 capabilities: lsp::ServerCapabilities {
7307 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7308 ..Default::default()
7309 },
7310 ..Default::default()
7311 },
7312 );
7313
7314 let buffer = project
7315 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7316 .await
7317 .unwrap();
7318
7319 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7320 let (editor, cx) =
7321 cx.add_window_view(|cx| build_editor_with_project(project.clone(), buffer, cx));
7322 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7323 assert!(cx.read(|cx| editor.is_dirty(cx)));
7324
7325 cx.executor().start_waiting();
7326 let fake_server = fake_servers.next().await.unwrap();
7327
7328 let save = editor
7329 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7330 .unwrap();
7331 fake_server
7332 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7333 assert_eq!(
7334 params.text_document.uri,
7335 lsp::Url::from_file_path("/file.rs").unwrap()
7336 );
7337 assert_eq!(params.options.tab_size, 4);
7338 Ok(Some(vec![lsp::TextEdit::new(
7339 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7340 ", ".to_string(),
7341 )]))
7342 })
7343 .next()
7344 .await;
7345 cx.executor().start_waiting();
7346 save.await;
7347 assert_eq!(
7348 editor.update(cx, |editor, cx| editor.text(cx)),
7349 "one, two\nthree\n"
7350 );
7351 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7352
7353 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7354 assert!(cx.read(|cx| editor.is_dirty(cx)));
7355
7356 // Ensure we can still save even if formatting hangs.
7357 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7358 move |params, _| async move {
7359 assert_eq!(
7360 params.text_document.uri,
7361 lsp::Url::from_file_path("/file.rs").unwrap()
7362 );
7363 futures::future::pending::<()>().await;
7364 unreachable!()
7365 },
7366 );
7367 let save = editor
7368 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7369 .unwrap();
7370 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7371 cx.executor().start_waiting();
7372 save.await;
7373 assert_eq!(
7374 editor.update(cx, |editor, cx| editor.text(cx)),
7375 "one\ntwo\nthree\n"
7376 );
7377 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7378
7379 // For non-dirty buffer, no formatting request should be sent
7380 let save = editor
7381 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7382 .unwrap();
7383 let _pending_format_request = fake_server
7384 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7385 panic!("Should not be invoked on non-dirty buffer");
7386 })
7387 .next();
7388 cx.executor().start_waiting();
7389 save.await;
7390
7391 // Set Rust language override and assert overridden tabsize is sent to language server
7392 update_test_language_settings(cx, |settings| {
7393 settings.languages.insert(
7394 "Rust".into(),
7395 LanguageSettingsContent {
7396 tab_size: NonZeroU32::new(8),
7397 ..Default::default()
7398 },
7399 );
7400 });
7401
7402 editor.update(cx, |editor, cx| editor.set_text("somehting_new\n", cx));
7403 assert!(cx.read(|cx| editor.is_dirty(cx)));
7404 let save = editor
7405 .update(cx, |editor, cx| editor.save(true, project.clone(), cx))
7406 .unwrap();
7407 fake_server
7408 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7409 assert_eq!(
7410 params.text_document.uri,
7411 lsp::Url::from_file_path("/file.rs").unwrap()
7412 );
7413 assert_eq!(params.options.tab_size, 8);
7414 Ok(Some(vec![]))
7415 })
7416 .next()
7417 .await;
7418 cx.executor().start_waiting();
7419 save.await;
7420}
7421
7422#[gpui::test]
7423async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
7424 init_test(cx, |settings| {
7425 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7426 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7427 ))
7428 });
7429
7430 let fs = FakeFs::new(cx.executor());
7431 fs.insert_file("/file.rs", Default::default()).await;
7432
7433 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
7434
7435 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7436 language_registry.add(Arc::new(Language::new(
7437 LanguageConfig {
7438 name: "Rust".into(),
7439 matcher: LanguageMatcher {
7440 path_suffixes: vec!["rs".to_string()],
7441 ..Default::default()
7442 },
7443 ..LanguageConfig::default()
7444 },
7445 Some(tree_sitter_rust::LANGUAGE.into()),
7446 )));
7447 update_test_language_settings(cx, |settings| {
7448 // Enable Prettier formatting for the same buffer, and ensure
7449 // LSP is called instead of Prettier.
7450 settings.defaults.prettier = Some(PrettierSettings {
7451 allowed: true,
7452 ..PrettierSettings::default()
7453 });
7454 });
7455 let mut fake_servers = language_registry.register_fake_lsp(
7456 "Rust",
7457 FakeLspAdapter {
7458 capabilities: lsp::ServerCapabilities {
7459 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7460 ..Default::default()
7461 },
7462 ..Default::default()
7463 },
7464 );
7465
7466 let buffer = project
7467 .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
7468 .await
7469 .unwrap();
7470
7471 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
7472 let (editor, cx) =
7473 cx.add_window_view(|cx| build_editor_with_project(project.clone(), buffer, cx));
7474 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7475
7476 cx.executor().start_waiting();
7477 let fake_server = fake_servers.next().await.unwrap();
7478
7479 let format = editor
7480 .update(cx, |editor, cx| {
7481 editor.perform_format(
7482 project.clone(),
7483 FormatTrigger::Manual,
7484 FormatTarget::Buffers,
7485 cx,
7486 )
7487 })
7488 .unwrap();
7489 fake_server
7490 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7491 assert_eq!(
7492 params.text_document.uri,
7493 lsp::Url::from_file_path("/file.rs").unwrap()
7494 );
7495 assert_eq!(params.options.tab_size, 4);
7496 Ok(Some(vec![lsp::TextEdit::new(
7497 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7498 ", ".to_string(),
7499 )]))
7500 })
7501 .next()
7502 .await;
7503 cx.executor().start_waiting();
7504 format.await;
7505 assert_eq!(
7506 editor.update(cx, |editor, cx| editor.text(cx)),
7507 "one, two\nthree\n"
7508 );
7509
7510 editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
7511 // Ensure we don't lock if formatting hangs.
7512 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7513 assert_eq!(
7514 params.text_document.uri,
7515 lsp::Url::from_file_path("/file.rs").unwrap()
7516 );
7517 futures::future::pending::<()>().await;
7518 unreachable!()
7519 });
7520 let format = editor
7521 .update(cx, |editor, cx| {
7522 editor.perform_format(project, FormatTrigger::Manual, FormatTarget::Buffers, cx)
7523 })
7524 .unwrap();
7525 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7526 cx.executor().start_waiting();
7527 format.await;
7528 assert_eq!(
7529 editor.update(cx, |editor, cx| editor.text(cx)),
7530 "one\ntwo\nthree\n"
7531 );
7532}
7533
7534#[gpui::test]
7535async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
7536 init_test(cx, |_| {});
7537
7538 let mut cx = EditorLspTestContext::new_rust(
7539 lsp::ServerCapabilities {
7540 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7541 ..Default::default()
7542 },
7543 cx,
7544 )
7545 .await;
7546
7547 cx.set_state(indoc! {"
7548 one.twoˇ
7549 "});
7550
7551 // The format request takes a long time. When it completes, it inserts
7552 // a newline and an indent before the `.`
7553 cx.lsp
7554 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
7555 let executor = cx.background_executor().clone();
7556 async move {
7557 executor.timer(Duration::from_millis(100)).await;
7558 Ok(Some(vec![lsp::TextEdit {
7559 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
7560 new_text: "\n ".into(),
7561 }]))
7562 }
7563 });
7564
7565 // Submit a format request.
7566 let format_1 = cx
7567 .update_editor(|editor, cx| editor.format(&Format, cx))
7568 .unwrap();
7569 cx.executor().run_until_parked();
7570
7571 // Submit a second format request.
7572 let format_2 = cx
7573 .update_editor(|editor, cx| editor.format(&Format, cx))
7574 .unwrap();
7575 cx.executor().run_until_parked();
7576
7577 // Wait for both format requests to complete
7578 cx.executor().advance_clock(Duration::from_millis(200));
7579 cx.executor().start_waiting();
7580 format_1.await.unwrap();
7581 cx.executor().start_waiting();
7582 format_2.await.unwrap();
7583
7584 // The formatting edits only happens once.
7585 cx.assert_editor_state(indoc! {"
7586 one
7587 .twoˇ
7588 "});
7589}
7590
7591#[gpui::test]
7592async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
7593 init_test(cx, |settings| {
7594 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
7595 });
7596
7597 let mut cx = EditorLspTestContext::new_rust(
7598 lsp::ServerCapabilities {
7599 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7600 ..Default::default()
7601 },
7602 cx,
7603 )
7604 .await;
7605
7606 // Set up a buffer white some trailing whitespace and no trailing newline.
7607 cx.set_state(
7608 &[
7609 "one ", //
7610 "twoˇ", //
7611 "three ", //
7612 "four", //
7613 ]
7614 .join("\n"),
7615 );
7616
7617 // Submit a format request.
7618 let format = cx
7619 .update_editor(|editor, cx| editor.format(&Format, cx))
7620 .unwrap();
7621
7622 // Record which buffer changes have been sent to the language server
7623 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
7624 cx.lsp
7625 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
7626 let buffer_changes = buffer_changes.clone();
7627 move |params, _| {
7628 buffer_changes.lock().extend(
7629 params
7630 .content_changes
7631 .into_iter()
7632 .map(|e| (e.range.unwrap(), e.text)),
7633 );
7634 }
7635 });
7636
7637 // Handle formatting requests to the language server.
7638 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
7639 let buffer_changes = buffer_changes.clone();
7640 move |_, _| {
7641 // When formatting is requested, trailing whitespace has already been stripped,
7642 // and the trailing newline has already been added.
7643 assert_eq!(
7644 &buffer_changes.lock()[1..],
7645 &[
7646 (
7647 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
7648 "".into()
7649 ),
7650 (
7651 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
7652 "".into()
7653 ),
7654 (
7655 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
7656 "\n".into()
7657 ),
7658 ]
7659 );
7660
7661 // Insert blank lines between each line of the buffer.
7662 async move {
7663 Ok(Some(vec![
7664 lsp::TextEdit {
7665 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
7666 new_text: "\n".into(),
7667 },
7668 lsp::TextEdit {
7669 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
7670 new_text: "\n".into(),
7671 },
7672 ]))
7673 }
7674 }
7675 });
7676
7677 // After formatting the buffer, the trailing whitespace is stripped,
7678 // a newline is appended, and the edits provided by the language server
7679 // have been applied.
7680 format.await.unwrap();
7681 cx.assert_editor_state(
7682 &[
7683 "one", //
7684 "", //
7685 "twoˇ", //
7686 "", //
7687 "three", //
7688 "four", //
7689 "", //
7690 ]
7691 .join("\n"),
7692 );
7693
7694 // Undoing the formatting undoes the trailing whitespace removal, the
7695 // trailing newline, and the LSP edits.
7696 cx.update_buffer(|buffer, cx| buffer.undo(cx));
7697 cx.assert_editor_state(
7698 &[
7699 "one ", //
7700 "twoˇ", //
7701 "three ", //
7702 "four", //
7703 ]
7704 .join("\n"),
7705 );
7706}
7707
7708#[gpui::test]
7709async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
7710 cx: &mut gpui::TestAppContext,
7711) {
7712 init_test(cx, |_| {});
7713
7714 cx.update(|cx| {
7715 cx.update_global::<SettingsStore, _>(|settings, cx| {
7716 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7717 settings.auto_signature_help = Some(true);
7718 });
7719 });
7720 });
7721
7722 let mut cx = EditorLspTestContext::new_rust(
7723 lsp::ServerCapabilities {
7724 signature_help_provider: Some(lsp::SignatureHelpOptions {
7725 ..Default::default()
7726 }),
7727 ..Default::default()
7728 },
7729 cx,
7730 )
7731 .await;
7732
7733 let language = Language::new(
7734 LanguageConfig {
7735 name: "Rust".into(),
7736 brackets: BracketPairConfig {
7737 pairs: vec![
7738 BracketPair {
7739 start: "{".to_string(),
7740 end: "}".to_string(),
7741 close: true,
7742 surround: true,
7743 newline: true,
7744 },
7745 BracketPair {
7746 start: "(".to_string(),
7747 end: ")".to_string(),
7748 close: true,
7749 surround: true,
7750 newline: true,
7751 },
7752 BracketPair {
7753 start: "/*".to_string(),
7754 end: " */".to_string(),
7755 close: true,
7756 surround: true,
7757 newline: true,
7758 },
7759 BracketPair {
7760 start: "[".to_string(),
7761 end: "]".to_string(),
7762 close: false,
7763 surround: false,
7764 newline: true,
7765 },
7766 BracketPair {
7767 start: "\"".to_string(),
7768 end: "\"".to_string(),
7769 close: true,
7770 surround: true,
7771 newline: false,
7772 },
7773 BracketPair {
7774 start: "<".to_string(),
7775 end: ">".to_string(),
7776 close: false,
7777 surround: true,
7778 newline: true,
7779 },
7780 ],
7781 ..Default::default()
7782 },
7783 autoclose_before: "})]".to_string(),
7784 ..Default::default()
7785 },
7786 Some(tree_sitter_rust::LANGUAGE.into()),
7787 );
7788 let language = Arc::new(language);
7789
7790 cx.language_registry().add(language.clone());
7791 cx.update_buffer(|buffer, cx| {
7792 buffer.set_language(Some(language), cx);
7793 });
7794
7795 cx.set_state(
7796 &r#"
7797 fn main() {
7798 sampleˇ
7799 }
7800 "#
7801 .unindent(),
7802 );
7803
7804 cx.update_editor(|view, cx| {
7805 view.handle_input("(", cx);
7806 });
7807 cx.assert_editor_state(
7808 &"
7809 fn main() {
7810 sample(ˇ)
7811 }
7812 "
7813 .unindent(),
7814 );
7815
7816 let mocked_response = lsp::SignatureHelp {
7817 signatures: vec![lsp::SignatureInformation {
7818 label: "fn sample(param1: u8, param2: u8)".to_string(),
7819 documentation: None,
7820 parameters: Some(vec![
7821 lsp::ParameterInformation {
7822 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7823 documentation: None,
7824 },
7825 lsp::ParameterInformation {
7826 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7827 documentation: None,
7828 },
7829 ]),
7830 active_parameter: None,
7831 }],
7832 active_signature: Some(0),
7833 active_parameter: Some(0),
7834 };
7835 handle_signature_help_request(&mut cx, mocked_response).await;
7836
7837 cx.condition(|editor, _| editor.signature_help_state.is_shown())
7838 .await;
7839
7840 cx.editor(|editor, _| {
7841 let signature_help_state = editor.signature_help_state.popover().cloned();
7842 assert!(signature_help_state.is_some());
7843 let ParsedMarkdown {
7844 text, highlights, ..
7845 } = signature_help_state.unwrap().parsed_content;
7846 assert_eq!(text, "param1: u8, param2: u8");
7847 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
7848 });
7849}
7850
7851#[gpui::test]
7852async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui::TestAppContext) {
7853 init_test(cx, |_| {});
7854
7855 cx.update(|cx| {
7856 cx.update_global::<SettingsStore, _>(|settings, cx| {
7857 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7858 settings.auto_signature_help = Some(false);
7859 settings.show_signature_help_after_edits = Some(false);
7860 });
7861 });
7862 });
7863
7864 let mut cx = EditorLspTestContext::new_rust(
7865 lsp::ServerCapabilities {
7866 signature_help_provider: Some(lsp::SignatureHelpOptions {
7867 ..Default::default()
7868 }),
7869 ..Default::default()
7870 },
7871 cx,
7872 )
7873 .await;
7874
7875 let language = Language::new(
7876 LanguageConfig {
7877 name: "Rust".into(),
7878 brackets: BracketPairConfig {
7879 pairs: vec![
7880 BracketPair {
7881 start: "{".to_string(),
7882 end: "}".to_string(),
7883 close: true,
7884 surround: true,
7885 newline: true,
7886 },
7887 BracketPair {
7888 start: "(".to_string(),
7889 end: ")".to_string(),
7890 close: true,
7891 surround: true,
7892 newline: true,
7893 },
7894 BracketPair {
7895 start: "/*".to_string(),
7896 end: " */".to_string(),
7897 close: true,
7898 surround: true,
7899 newline: true,
7900 },
7901 BracketPair {
7902 start: "[".to_string(),
7903 end: "]".to_string(),
7904 close: false,
7905 surround: false,
7906 newline: true,
7907 },
7908 BracketPair {
7909 start: "\"".to_string(),
7910 end: "\"".to_string(),
7911 close: true,
7912 surround: true,
7913 newline: false,
7914 },
7915 BracketPair {
7916 start: "<".to_string(),
7917 end: ">".to_string(),
7918 close: false,
7919 surround: true,
7920 newline: true,
7921 },
7922 ],
7923 ..Default::default()
7924 },
7925 autoclose_before: "})]".to_string(),
7926 ..Default::default()
7927 },
7928 Some(tree_sitter_rust::LANGUAGE.into()),
7929 );
7930 let language = Arc::new(language);
7931
7932 cx.language_registry().add(language.clone());
7933 cx.update_buffer(|buffer, cx| {
7934 buffer.set_language(Some(language), cx);
7935 });
7936
7937 // Ensure that signature_help is not called when no signature help is enabled.
7938 cx.set_state(
7939 &r#"
7940 fn main() {
7941 sampleˇ
7942 }
7943 "#
7944 .unindent(),
7945 );
7946 cx.update_editor(|view, cx| {
7947 view.handle_input("(", cx);
7948 });
7949 cx.assert_editor_state(
7950 &"
7951 fn main() {
7952 sample(ˇ)
7953 }
7954 "
7955 .unindent(),
7956 );
7957 cx.editor(|editor, _| {
7958 assert!(editor.signature_help_state.task().is_none());
7959 });
7960
7961 let mocked_response = lsp::SignatureHelp {
7962 signatures: vec![lsp::SignatureInformation {
7963 label: "fn sample(param1: u8, param2: u8)".to_string(),
7964 documentation: None,
7965 parameters: Some(vec![
7966 lsp::ParameterInformation {
7967 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
7968 documentation: None,
7969 },
7970 lsp::ParameterInformation {
7971 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
7972 documentation: None,
7973 },
7974 ]),
7975 active_parameter: None,
7976 }],
7977 active_signature: Some(0),
7978 active_parameter: Some(0),
7979 };
7980
7981 // Ensure that signature_help is called when enabled afte edits
7982 cx.update(|cx| {
7983 cx.update_global::<SettingsStore, _>(|settings, cx| {
7984 settings.update_user_settings::<EditorSettings>(cx, |settings| {
7985 settings.auto_signature_help = Some(false);
7986 settings.show_signature_help_after_edits = Some(true);
7987 });
7988 });
7989 });
7990 cx.set_state(
7991 &r#"
7992 fn main() {
7993 sampleˇ
7994 }
7995 "#
7996 .unindent(),
7997 );
7998 cx.update_editor(|view, cx| {
7999 view.handle_input("(", cx);
8000 });
8001 cx.assert_editor_state(
8002 &"
8003 fn main() {
8004 sample(ˇ)
8005 }
8006 "
8007 .unindent(),
8008 );
8009 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8010 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8011 .await;
8012 cx.update_editor(|editor, _| {
8013 let signature_help_state = editor.signature_help_state.popover().cloned();
8014 assert!(signature_help_state.is_some());
8015 let ParsedMarkdown {
8016 text, highlights, ..
8017 } = signature_help_state.unwrap().parsed_content;
8018 assert_eq!(text, "param1: u8, param2: u8");
8019 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
8020 editor.signature_help_state = SignatureHelpState::default();
8021 });
8022
8023 // Ensure that signature_help is called when auto signature help override is enabled
8024 cx.update(|cx| {
8025 cx.update_global::<SettingsStore, _>(|settings, cx| {
8026 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8027 settings.auto_signature_help = Some(true);
8028 settings.show_signature_help_after_edits = Some(false);
8029 });
8030 });
8031 });
8032 cx.set_state(
8033 &r#"
8034 fn main() {
8035 sampleˇ
8036 }
8037 "#
8038 .unindent(),
8039 );
8040 cx.update_editor(|view, cx| {
8041 view.handle_input("(", cx);
8042 });
8043 cx.assert_editor_state(
8044 &"
8045 fn main() {
8046 sample(ˇ)
8047 }
8048 "
8049 .unindent(),
8050 );
8051 handle_signature_help_request(&mut cx, mocked_response).await;
8052 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8053 .await;
8054 cx.editor(|editor, _| {
8055 let signature_help_state = editor.signature_help_state.popover().cloned();
8056 assert!(signature_help_state.is_some());
8057 let ParsedMarkdown {
8058 text, highlights, ..
8059 } = signature_help_state.unwrap().parsed_content;
8060 assert_eq!(text, "param1: u8, param2: u8");
8061 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
8062 });
8063}
8064
8065#[gpui::test]
8066async fn test_signature_help(cx: &mut gpui::TestAppContext) {
8067 init_test(cx, |_| {});
8068 cx.update(|cx| {
8069 cx.update_global::<SettingsStore, _>(|settings, cx| {
8070 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8071 settings.auto_signature_help = Some(true);
8072 });
8073 });
8074 });
8075
8076 let mut cx = EditorLspTestContext::new_rust(
8077 lsp::ServerCapabilities {
8078 signature_help_provider: Some(lsp::SignatureHelpOptions {
8079 ..Default::default()
8080 }),
8081 ..Default::default()
8082 },
8083 cx,
8084 )
8085 .await;
8086
8087 // A test that directly calls `show_signature_help`
8088 cx.update_editor(|editor, cx| {
8089 editor.show_signature_help(&ShowSignatureHelp, cx);
8090 });
8091
8092 let mocked_response = lsp::SignatureHelp {
8093 signatures: vec![lsp::SignatureInformation {
8094 label: "fn sample(param1: u8, param2: u8)".to_string(),
8095 documentation: None,
8096 parameters: Some(vec![
8097 lsp::ParameterInformation {
8098 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8099 documentation: None,
8100 },
8101 lsp::ParameterInformation {
8102 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8103 documentation: None,
8104 },
8105 ]),
8106 active_parameter: None,
8107 }],
8108 active_signature: Some(0),
8109 active_parameter: Some(0),
8110 };
8111 handle_signature_help_request(&mut cx, mocked_response).await;
8112
8113 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8114 .await;
8115
8116 cx.editor(|editor, _| {
8117 let signature_help_state = editor.signature_help_state.popover().cloned();
8118 assert!(signature_help_state.is_some());
8119 let ParsedMarkdown {
8120 text, highlights, ..
8121 } = signature_help_state.unwrap().parsed_content;
8122 assert_eq!(text, "param1: u8, param2: u8");
8123 assert_eq!(highlights, vec![(0..10, SIGNATURE_HELP_HIGHLIGHT_CURRENT)]);
8124 });
8125
8126 // When exiting outside from inside the brackets, `signature_help` is closed.
8127 cx.set_state(indoc! {"
8128 fn main() {
8129 sample(ˇ);
8130 }
8131
8132 fn sample(param1: u8, param2: u8) {}
8133 "});
8134
8135 cx.update_editor(|editor, cx| {
8136 editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
8137 });
8138
8139 let mocked_response = lsp::SignatureHelp {
8140 signatures: Vec::new(),
8141 active_signature: None,
8142 active_parameter: None,
8143 };
8144 handle_signature_help_request(&mut cx, mocked_response).await;
8145
8146 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8147 .await;
8148
8149 cx.editor(|editor, _| {
8150 assert!(!editor.signature_help_state.is_shown());
8151 });
8152
8153 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8154 cx.set_state(indoc! {"
8155 fn main() {
8156 sample(ˇ);
8157 }
8158
8159 fn sample(param1: u8, param2: u8) {}
8160 "});
8161
8162 let mocked_response = lsp::SignatureHelp {
8163 signatures: vec![lsp::SignatureInformation {
8164 label: "fn sample(param1: u8, param2: u8)".to_string(),
8165 documentation: None,
8166 parameters: Some(vec![
8167 lsp::ParameterInformation {
8168 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8169 documentation: None,
8170 },
8171 lsp::ParameterInformation {
8172 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8173 documentation: None,
8174 },
8175 ]),
8176 active_parameter: None,
8177 }],
8178 active_signature: Some(0),
8179 active_parameter: Some(0),
8180 };
8181 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8182 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8183 .await;
8184 cx.editor(|editor, _| {
8185 assert!(editor.signature_help_state.is_shown());
8186 });
8187
8188 // Restore the popover with more parameter input
8189 cx.set_state(indoc! {"
8190 fn main() {
8191 sample(param1, param2ˇ);
8192 }
8193
8194 fn sample(param1: u8, param2: u8) {}
8195 "});
8196
8197 let mocked_response = lsp::SignatureHelp {
8198 signatures: vec![lsp::SignatureInformation {
8199 label: "fn sample(param1: u8, param2: u8)".to_string(),
8200 documentation: None,
8201 parameters: Some(vec![
8202 lsp::ParameterInformation {
8203 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8204 documentation: None,
8205 },
8206 lsp::ParameterInformation {
8207 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8208 documentation: None,
8209 },
8210 ]),
8211 active_parameter: None,
8212 }],
8213 active_signature: Some(0),
8214 active_parameter: Some(1),
8215 };
8216 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8217 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8218 .await;
8219
8220 // When selecting a range, the popover is gone.
8221 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8222 cx.update_editor(|editor, cx| {
8223 editor.change_selections(None, cx, |s| {
8224 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8225 })
8226 });
8227 cx.assert_editor_state(indoc! {"
8228 fn main() {
8229 sample(param1, «ˇparam2»);
8230 }
8231
8232 fn sample(param1: u8, param2: u8) {}
8233 "});
8234 cx.editor(|editor, _| {
8235 assert!(!editor.signature_help_state.is_shown());
8236 });
8237
8238 // When unselecting again, the popover is back if within the brackets.
8239 cx.update_editor(|editor, cx| {
8240 editor.change_selections(None, cx, |s| {
8241 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8242 })
8243 });
8244 cx.assert_editor_state(indoc! {"
8245 fn main() {
8246 sample(param1, ˇparam2);
8247 }
8248
8249 fn sample(param1: u8, param2: u8) {}
8250 "});
8251 handle_signature_help_request(&mut cx, mocked_response).await;
8252 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8253 .await;
8254 cx.editor(|editor, _| {
8255 assert!(editor.signature_help_state.is_shown());
8256 });
8257
8258 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8259 cx.update_editor(|editor, cx| {
8260 editor.change_selections(None, cx, |s| {
8261 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8262 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8263 })
8264 });
8265 cx.assert_editor_state(indoc! {"
8266 fn main() {
8267 sample(param1, ˇparam2);
8268 }
8269
8270 fn sample(param1: u8, param2: u8) {}
8271 "});
8272
8273 let mocked_response = lsp::SignatureHelp {
8274 signatures: vec![lsp::SignatureInformation {
8275 label: "fn sample(param1: u8, param2: u8)".to_string(),
8276 documentation: None,
8277 parameters: Some(vec![
8278 lsp::ParameterInformation {
8279 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8280 documentation: None,
8281 },
8282 lsp::ParameterInformation {
8283 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8284 documentation: None,
8285 },
8286 ]),
8287 active_parameter: None,
8288 }],
8289 active_signature: Some(0),
8290 active_parameter: Some(1),
8291 };
8292 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8293 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8294 .await;
8295 cx.update_editor(|editor, cx| {
8296 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8297 });
8298 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8299 .await;
8300 cx.update_editor(|editor, cx| {
8301 editor.change_selections(None, cx, |s| {
8302 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8303 })
8304 });
8305 cx.assert_editor_state(indoc! {"
8306 fn main() {
8307 sample(param1, «ˇparam2»);
8308 }
8309
8310 fn sample(param1: u8, param2: u8) {}
8311 "});
8312 cx.update_editor(|editor, cx| {
8313 editor.change_selections(None, cx, |s| {
8314 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8315 })
8316 });
8317 cx.assert_editor_state(indoc! {"
8318 fn main() {
8319 sample(param1, ˇparam2);
8320 }
8321
8322 fn sample(param1: u8, param2: u8) {}
8323 "});
8324 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8325 .await;
8326}
8327
8328#[gpui::test]
8329async fn test_completion(cx: &mut gpui::TestAppContext) {
8330 init_test(cx, |_| {});
8331
8332 let mut cx = EditorLspTestContext::new_rust(
8333 lsp::ServerCapabilities {
8334 completion_provider: Some(lsp::CompletionOptions {
8335 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8336 resolve_provider: Some(true),
8337 ..Default::default()
8338 }),
8339 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8340 ..Default::default()
8341 },
8342 cx,
8343 )
8344 .await;
8345 let counter = Arc::new(AtomicUsize::new(0));
8346
8347 cx.set_state(indoc! {"
8348 oneˇ
8349 two
8350 three
8351 "});
8352 cx.simulate_keystroke(".");
8353 handle_completion_request(
8354 &mut cx,
8355 indoc! {"
8356 one.|<>
8357 two
8358 three
8359 "},
8360 vec!["first_completion", "second_completion"],
8361 counter.clone(),
8362 )
8363 .await;
8364 cx.condition(|editor, _| editor.context_menu_visible())
8365 .await;
8366 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8367
8368 let _handler = handle_signature_help_request(
8369 &mut cx,
8370 lsp::SignatureHelp {
8371 signatures: vec![lsp::SignatureInformation {
8372 label: "test signature".to_string(),
8373 documentation: None,
8374 parameters: Some(vec![lsp::ParameterInformation {
8375 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
8376 documentation: None,
8377 }]),
8378 active_parameter: None,
8379 }],
8380 active_signature: None,
8381 active_parameter: None,
8382 },
8383 );
8384 cx.update_editor(|editor, cx| {
8385 assert!(
8386 !editor.signature_help_state.is_shown(),
8387 "No signature help was called for"
8388 );
8389 editor.show_signature_help(&ShowSignatureHelp, cx);
8390 });
8391 cx.run_until_parked();
8392 cx.update_editor(|editor, _| {
8393 assert!(
8394 !editor.signature_help_state.is_shown(),
8395 "No signature help should be shown when completions menu is open"
8396 );
8397 });
8398
8399 let apply_additional_edits = cx.update_editor(|editor, cx| {
8400 editor.context_menu_next(&Default::default(), cx);
8401 editor
8402 .confirm_completion(&ConfirmCompletion::default(), cx)
8403 .unwrap()
8404 });
8405 cx.assert_editor_state(indoc! {"
8406 one.second_completionˇ
8407 two
8408 three
8409 "});
8410
8411 handle_resolve_completion_request(
8412 &mut cx,
8413 Some(vec![
8414 (
8415 //This overlaps with the primary completion edit which is
8416 //misbehavior from the LSP spec, test that we filter it out
8417 indoc! {"
8418 one.second_ˇcompletion
8419 two
8420 threeˇ
8421 "},
8422 "overlapping additional edit",
8423 ),
8424 (
8425 indoc! {"
8426 one.second_completion
8427 two
8428 threeˇ
8429 "},
8430 "\nadditional edit",
8431 ),
8432 ]),
8433 )
8434 .await;
8435 apply_additional_edits.await.unwrap();
8436 cx.assert_editor_state(indoc! {"
8437 one.second_completionˇ
8438 two
8439 three
8440 additional edit
8441 "});
8442
8443 cx.set_state(indoc! {"
8444 one.second_completion
8445 twoˇ
8446 threeˇ
8447 additional edit
8448 "});
8449 cx.simulate_keystroke(" ");
8450 assert!(cx.editor(|e, _| e.context_menu.borrow_mut().is_none()));
8451 cx.simulate_keystroke("s");
8452 assert!(cx.editor(|e, _| e.context_menu.borrow_mut().is_none()));
8453
8454 cx.assert_editor_state(indoc! {"
8455 one.second_completion
8456 two sˇ
8457 three sˇ
8458 additional edit
8459 "});
8460 handle_completion_request(
8461 &mut cx,
8462 indoc! {"
8463 one.second_completion
8464 two s
8465 three <s|>
8466 additional edit
8467 "},
8468 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8469 counter.clone(),
8470 )
8471 .await;
8472 cx.condition(|editor, _| editor.context_menu_visible())
8473 .await;
8474 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
8475
8476 cx.simulate_keystroke("i");
8477
8478 handle_completion_request(
8479 &mut cx,
8480 indoc! {"
8481 one.second_completion
8482 two si
8483 three <si|>
8484 additional edit
8485 "},
8486 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
8487 counter.clone(),
8488 )
8489 .await;
8490 cx.condition(|editor, _| editor.context_menu_visible())
8491 .await;
8492 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
8493
8494 let apply_additional_edits = cx.update_editor(|editor, cx| {
8495 editor
8496 .confirm_completion(&ConfirmCompletion::default(), cx)
8497 .unwrap()
8498 });
8499 cx.assert_editor_state(indoc! {"
8500 one.second_completion
8501 two sixth_completionˇ
8502 three sixth_completionˇ
8503 additional edit
8504 "});
8505
8506 apply_additional_edits.await.unwrap();
8507
8508 update_test_language_settings(&mut cx, |settings| {
8509 settings.defaults.show_completions_on_input = Some(false);
8510 });
8511 cx.set_state("editorˇ");
8512 cx.simulate_keystroke(".");
8513 assert!(cx.editor(|e, _| e.context_menu.borrow_mut().is_none()));
8514 cx.simulate_keystroke("c");
8515 cx.simulate_keystroke("l");
8516 cx.simulate_keystroke("o");
8517 cx.assert_editor_state("editor.cloˇ");
8518 assert!(cx.editor(|e, _| e.context_menu.borrow_mut().is_none()));
8519 cx.update_editor(|editor, cx| {
8520 editor.show_completions(&ShowCompletions { trigger: None }, cx);
8521 });
8522 handle_completion_request(
8523 &mut cx,
8524 "editor.<clo|>",
8525 vec!["close", "clobber"],
8526 counter.clone(),
8527 )
8528 .await;
8529 cx.condition(|editor, _| editor.context_menu_visible())
8530 .await;
8531 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
8532
8533 let apply_additional_edits = cx.update_editor(|editor, cx| {
8534 editor
8535 .confirm_completion(&ConfirmCompletion::default(), cx)
8536 .unwrap()
8537 });
8538 cx.assert_editor_state("editor.closeˇ");
8539 handle_resolve_completion_request(&mut cx, None).await;
8540 apply_additional_edits.await.unwrap();
8541}
8542
8543#[gpui::test]
8544async fn test_multiline_completion(cx: &mut gpui::TestAppContext) {
8545 init_test(cx, |_| {});
8546
8547 let fs = FakeFs::new(cx.executor());
8548 fs.insert_tree(
8549 "/a",
8550 json!({
8551 "main.ts": "a",
8552 }),
8553 )
8554 .await;
8555
8556 let project = Project::test(fs, ["/a".as_ref()], cx).await;
8557 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8558 let typescript_language = Arc::new(Language::new(
8559 LanguageConfig {
8560 name: "TypeScript".into(),
8561 matcher: LanguageMatcher {
8562 path_suffixes: vec!["ts".to_string()],
8563 ..LanguageMatcher::default()
8564 },
8565 line_comments: vec!["// ".into()],
8566 ..LanguageConfig::default()
8567 },
8568 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
8569 ));
8570 language_registry.add(typescript_language.clone());
8571 let mut fake_servers = language_registry.register_fake_lsp(
8572 "TypeScript",
8573 FakeLspAdapter {
8574 capabilities: lsp::ServerCapabilities {
8575 completion_provider: Some(lsp::CompletionOptions {
8576 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8577 ..lsp::CompletionOptions::default()
8578 }),
8579 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8580 ..lsp::ServerCapabilities::default()
8581 },
8582 // Emulate vtsls label generation
8583 label_for_completion: Some(Box::new(|item, _| {
8584 let text = if let Some(description) = item
8585 .label_details
8586 .as_ref()
8587 .and_then(|label_details| label_details.description.as_ref())
8588 {
8589 format!("{} {}", item.label, description)
8590 } else if let Some(detail) = &item.detail {
8591 format!("{} {}", item.label, detail)
8592 } else {
8593 item.label.clone()
8594 };
8595 let len = text.len();
8596 Some(language::CodeLabel {
8597 text,
8598 runs: Vec::new(),
8599 filter_range: 0..len,
8600 })
8601 })),
8602 ..FakeLspAdapter::default()
8603 },
8604 );
8605 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
8606 let cx = &mut VisualTestContext::from_window(*workspace, cx);
8607 let worktree_id = workspace
8608 .update(cx, |workspace, cx| {
8609 workspace.project().update(cx, |project, cx| {
8610 project.worktrees(cx).next().unwrap().read(cx).id()
8611 })
8612 })
8613 .unwrap();
8614 let _buffer = project
8615 .update(cx, |project, cx| {
8616 project.open_local_buffer_with_lsp("/a/main.ts", cx)
8617 })
8618 .await
8619 .unwrap();
8620 let editor = workspace
8621 .update(cx, |workspace, cx| {
8622 workspace.open_path((worktree_id, "main.ts"), None, true, cx)
8623 })
8624 .unwrap()
8625 .await
8626 .unwrap()
8627 .downcast::<Editor>()
8628 .unwrap();
8629 let fake_server = fake_servers.next().await.unwrap();
8630
8631 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
8632 let multiline_label_2 = "a\nb\nc\n";
8633 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
8634 let multiline_description = "d\ne\nf\n";
8635 let multiline_detail_2 = "g\nh\ni\n";
8636
8637 let mut completion_handle =
8638 fake_server.handle_request::<lsp::request::Completion, _, _>(move |params, _| async move {
8639 Ok(Some(lsp::CompletionResponse::Array(vec![
8640 lsp::CompletionItem {
8641 label: multiline_label.to_string(),
8642 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8643 range: lsp::Range {
8644 start: lsp::Position {
8645 line: params.text_document_position.position.line,
8646 character: params.text_document_position.position.character,
8647 },
8648 end: lsp::Position {
8649 line: params.text_document_position.position.line,
8650 character: params.text_document_position.position.character,
8651 },
8652 },
8653 new_text: "new_text_1".to_string(),
8654 })),
8655 ..lsp::CompletionItem::default()
8656 },
8657 lsp::CompletionItem {
8658 label: "single line label 1".to_string(),
8659 detail: Some(multiline_detail.to_string()),
8660 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8661 range: lsp::Range {
8662 start: lsp::Position {
8663 line: params.text_document_position.position.line,
8664 character: params.text_document_position.position.character,
8665 },
8666 end: lsp::Position {
8667 line: params.text_document_position.position.line,
8668 character: params.text_document_position.position.character,
8669 },
8670 },
8671 new_text: "new_text_2".to_string(),
8672 })),
8673 ..lsp::CompletionItem::default()
8674 },
8675 lsp::CompletionItem {
8676 label: "single line label 2".to_string(),
8677 label_details: Some(lsp::CompletionItemLabelDetails {
8678 description: Some(multiline_description.to_string()),
8679 detail: None,
8680 }),
8681 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8682 range: lsp::Range {
8683 start: lsp::Position {
8684 line: params.text_document_position.position.line,
8685 character: params.text_document_position.position.character,
8686 },
8687 end: lsp::Position {
8688 line: params.text_document_position.position.line,
8689 character: params.text_document_position.position.character,
8690 },
8691 },
8692 new_text: "new_text_2".to_string(),
8693 })),
8694 ..lsp::CompletionItem::default()
8695 },
8696 lsp::CompletionItem {
8697 label: multiline_label_2.to_string(),
8698 detail: Some(multiline_detail_2.to_string()),
8699 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8700 range: lsp::Range {
8701 start: lsp::Position {
8702 line: params.text_document_position.position.line,
8703 character: params.text_document_position.position.character,
8704 },
8705 end: lsp::Position {
8706 line: params.text_document_position.position.line,
8707 character: params.text_document_position.position.character,
8708 },
8709 },
8710 new_text: "new_text_3".to_string(),
8711 })),
8712 ..lsp::CompletionItem::default()
8713 },
8714 lsp::CompletionItem {
8715 label: "Label with many spaces and \t but without newlines".to_string(),
8716 detail: Some(
8717 "Details with many spaces and \t but without newlines".to_string(),
8718 ),
8719 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8720 range: lsp::Range {
8721 start: lsp::Position {
8722 line: params.text_document_position.position.line,
8723 character: params.text_document_position.position.character,
8724 },
8725 end: lsp::Position {
8726 line: params.text_document_position.position.line,
8727 character: params.text_document_position.position.character,
8728 },
8729 },
8730 new_text: "new_text_4".to_string(),
8731 })),
8732 ..lsp::CompletionItem::default()
8733 },
8734 ])))
8735 });
8736
8737 editor.update(cx, |editor, cx| {
8738 editor.focus(cx);
8739 editor.move_to_end(&MoveToEnd, cx);
8740 editor.handle_input(".", cx);
8741 });
8742 cx.run_until_parked();
8743 completion_handle.next().await.unwrap();
8744
8745 editor.update(cx, |editor, _| {
8746 assert!(editor.context_menu_visible());
8747 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8748 {
8749 let completion_labels = menu
8750 .completions
8751 .borrow()
8752 .iter()
8753 .map(|c| c.label.text.clone())
8754 .collect::<Vec<_>>();
8755 assert_eq!(
8756 completion_labels,
8757 &[
8758 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
8759 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
8760 "single line label 2 d e f ",
8761 "a b c g h i ",
8762 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
8763 ],
8764 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
8765 );
8766
8767 for completion in menu
8768 .completions
8769 .borrow()
8770 .iter() {
8771 assert_eq!(
8772 completion.label.filter_range,
8773 0..completion.label.text.len(),
8774 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
8775 );
8776 }
8777
8778 } else {
8779 panic!("expected completion menu to be open");
8780 }
8781 });
8782}
8783
8784#[gpui::test]
8785async fn test_completion_page_up_down_keys(cx: &mut gpui::TestAppContext) {
8786 init_test(cx, |_| {});
8787 let mut cx = EditorLspTestContext::new_rust(
8788 lsp::ServerCapabilities {
8789 completion_provider: Some(lsp::CompletionOptions {
8790 trigger_characters: Some(vec![".".to_string()]),
8791 ..Default::default()
8792 }),
8793 ..Default::default()
8794 },
8795 cx,
8796 )
8797 .await;
8798 cx.lsp
8799 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8800 Ok(Some(lsp::CompletionResponse::Array(vec![
8801 lsp::CompletionItem {
8802 label: "first".into(),
8803 ..Default::default()
8804 },
8805 lsp::CompletionItem {
8806 label: "last".into(),
8807 ..Default::default()
8808 },
8809 ])))
8810 });
8811 cx.set_state("variableˇ");
8812 cx.simulate_keystroke(".");
8813 cx.executor().run_until_parked();
8814
8815 cx.update_editor(|editor, _| {
8816 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8817 {
8818 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
8819 } else {
8820 panic!("expected completion menu to be open");
8821 }
8822 });
8823
8824 cx.update_editor(|editor, cx| {
8825 editor.move_page_down(&MovePageDown::default(), cx);
8826 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8827 {
8828 assert!(
8829 menu.selected_item == 1,
8830 "expected PageDown to select the last item from the context menu"
8831 );
8832 } else {
8833 panic!("expected completion menu to stay open after PageDown");
8834 }
8835 });
8836
8837 cx.update_editor(|editor, cx| {
8838 editor.move_page_up(&MovePageUp::default(), cx);
8839 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8840 {
8841 assert!(
8842 menu.selected_item == 0,
8843 "expected PageUp to select the first item from the context menu"
8844 );
8845 } else {
8846 panic!("expected completion menu to stay open after PageUp");
8847 }
8848 });
8849}
8850
8851#[gpui::test]
8852async fn test_completion_sort(cx: &mut gpui::TestAppContext) {
8853 init_test(cx, |_| {});
8854 let mut cx = EditorLspTestContext::new_rust(
8855 lsp::ServerCapabilities {
8856 completion_provider: Some(lsp::CompletionOptions {
8857 trigger_characters: Some(vec![".".to_string()]),
8858 ..Default::default()
8859 }),
8860 ..Default::default()
8861 },
8862 cx,
8863 )
8864 .await;
8865 cx.lsp
8866 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
8867 Ok(Some(lsp::CompletionResponse::Array(vec![
8868 lsp::CompletionItem {
8869 label: "Range".into(),
8870 sort_text: Some("a".into()),
8871 ..Default::default()
8872 },
8873 lsp::CompletionItem {
8874 label: "r".into(),
8875 sort_text: Some("b".into()),
8876 ..Default::default()
8877 },
8878 lsp::CompletionItem {
8879 label: "ret".into(),
8880 sort_text: Some("c".into()),
8881 ..Default::default()
8882 },
8883 lsp::CompletionItem {
8884 label: "return".into(),
8885 sort_text: Some("d".into()),
8886 ..Default::default()
8887 },
8888 lsp::CompletionItem {
8889 label: "slice".into(),
8890 sort_text: Some("d".into()),
8891 ..Default::default()
8892 },
8893 ])))
8894 });
8895 cx.set_state("rˇ");
8896 cx.executor().run_until_parked();
8897 cx.update_editor(|editor, cx| {
8898 editor.show_completions(
8899 &ShowCompletions {
8900 trigger: Some("r".into()),
8901 },
8902 cx,
8903 );
8904 });
8905 cx.executor().run_until_parked();
8906
8907 cx.update_editor(|editor, _| {
8908 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
8909 {
8910 assert_eq!(
8911 completion_menu_entries(&menu),
8912 &["r", "ret", "Range", "return"]
8913 );
8914 } else {
8915 panic!("expected completion menu to be open");
8916 }
8917 });
8918}
8919
8920#[gpui::test]
8921async fn test_no_duplicated_completion_requests(cx: &mut gpui::TestAppContext) {
8922 init_test(cx, |_| {});
8923
8924 let mut cx = EditorLspTestContext::new_rust(
8925 lsp::ServerCapabilities {
8926 completion_provider: Some(lsp::CompletionOptions {
8927 trigger_characters: Some(vec![".".to_string()]),
8928 resolve_provider: Some(true),
8929 ..Default::default()
8930 }),
8931 ..Default::default()
8932 },
8933 cx,
8934 )
8935 .await;
8936
8937 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
8938 cx.simulate_keystroke(".");
8939 let completion_item = lsp::CompletionItem {
8940 label: "Some".into(),
8941 kind: Some(lsp::CompletionItemKind::SNIPPET),
8942 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
8943 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
8944 kind: lsp::MarkupKind::Markdown,
8945 value: "```rust\nSome(2)\n```".to_string(),
8946 })),
8947 deprecated: Some(false),
8948 sort_text: Some("Some".to_string()),
8949 filter_text: Some("Some".to_string()),
8950 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
8951 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
8952 range: lsp::Range {
8953 start: lsp::Position {
8954 line: 0,
8955 character: 22,
8956 },
8957 end: lsp::Position {
8958 line: 0,
8959 character: 22,
8960 },
8961 },
8962 new_text: "Some(2)".to_string(),
8963 })),
8964 additional_text_edits: Some(vec![lsp::TextEdit {
8965 range: lsp::Range {
8966 start: lsp::Position {
8967 line: 0,
8968 character: 20,
8969 },
8970 end: lsp::Position {
8971 line: 0,
8972 character: 22,
8973 },
8974 },
8975 new_text: "".to_string(),
8976 }]),
8977 ..Default::default()
8978 };
8979
8980 let closure_completion_item = completion_item.clone();
8981 let counter = Arc::new(AtomicUsize::new(0));
8982 let counter_clone = counter.clone();
8983 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
8984 let task_completion_item = closure_completion_item.clone();
8985 counter_clone.fetch_add(1, atomic::Ordering::Release);
8986 async move {
8987 Ok(Some(lsp::CompletionResponse::Array(vec![
8988 task_completion_item,
8989 ])))
8990 }
8991 });
8992
8993 cx.condition(|editor, _| editor.context_menu_visible())
8994 .await;
8995 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
8996 assert!(request.next().await.is_some());
8997 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
8998
8999 cx.simulate_keystroke("S");
9000 cx.simulate_keystroke("o");
9001 cx.simulate_keystroke("m");
9002 cx.condition(|editor, _| editor.context_menu_visible())
9003 .await;
9004 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
9005 assert!(request.next().await.is_some());
9006 assert!(request.next().await.is_some());
9007 assert!(request.next().await.is_some());
9008 request.close();
9009 assert!(request.next().await.is_none());
9010 assert_eq!(
9011 counter.load(atomic::Ordering::Acquire),
9012 4,
9013 "With the completions menu open, only one LSP request should happen per input"
9014 );
9015}
9016
9017#[gpui::test]
9018async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
9019 init_test(cx, |_| {});
9020 let mut cx = EditorTestContext::new(cx).await;
9021 let language = Arc::new(Language::new(
9022 LanguageConfig {
9023 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9024 ..Default::default()
9025 },
9026 Some(tree_sitter_rust::LANGUAGE.into()),
9027 ));
9028 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9029
9030 // If multiple selections intersect a line, the line is only toggled once.
9031 cx.set_state(indoc! {"
9032 fn a() {
9033 «//b();
9034 ˇ»// «c();
9035 //ˇ» d();
9036 }
9037 "});
9038
9039 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
9040
9041 cx.assert_editor_state(indoc! {"
9042 fn a() {
9043 «b();
9044 c();
9045 ˇ» d();
9046 }
9047 "});
9048
9049 // The comment prefix is inserted at the same column for every line in a
9050 // selection.
9051 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
9052
9053 cx.assert_editor_state(indoc! {"
9054 fn a() {
9055 // «b();
9056 // c();
9057 ˇ»// d();
9058 }
9059 "});
9060
9061 // If a selection ends at the beginning of a line, that line is not toggled.
9062 cx.set_selections_state(indoc! {"
9063 fn a() {
9064 // b();
9065 «// c();
9066 ˇ» // d();
9067 }
9068 "});
9069
9070 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
9071
9072 cx.assert_editor_state(indoc! {"
9073 fn a() {
9074 // b();
9075 «c();
9076 ˇ» // d();
9077 }
9078 "});
9079
9080 // If a selection span a single line and is empty, the line is toggled.
9081 cx.set_state(indoc! {"
9082 fn a() {
9083 a();
9084 b();
9085 ˇ
9086 }
9087 "});
9088
9089 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
9090
9091 cx.assert_editor_state(indoc! {"
9092 fn a() {
9093 a();
9094 b();
9095 //•ˇ
9096 }
9097 "});
9098
9099 // If a selection span multiple lines, empty lines are not toggled.
9100 cx.set_state(indoc! {"
9101 fn a() {
9102 «a();
9103
9104 c();ˇ»
9105 }
9106 "});
9107
9108 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
9109
9110 cx.assert_editor_state(indoc! {"
9111 fn a() {
9112 // «a();
9113
9114 // c();ˇ»
9115 }
9116 "});
9117
9118 // If a selection includes multiple comment prefixes, all lines are uncommented.
9119 cx.set_state(indoc! {"
9120 fn a() {
9121 «// a();
9122 /// b();
9123 //! c();ˇ»
9124 }
9125 "});
9126
9127 cx.update_editor(|e, cx| e.toggle_comments(&ToggleComments::default(), cx));
9128
9129 cx.assert_editor_state(indoc! {"
9130 fn a() {
9131 «a();
9132 b();
9133 c();ˇ»
9134 }
9135 "});
9136}
9137
9138#[gpui::test]
9139async fn test_toggle_comment_ignore_indent(cx: &mut gpui::TestAppContext) {
9140 init_test(cx, |_| {});
9141 let mut cx = EditorTestContext::new(cx).await;
9142 let language = Arc::new(Language::new(
9143 LanguageConfig {
9144 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9145 ..Default::default()
9146 },
9147 Some(tree_sitter_rust::LANGUAGE.into()),
9148 ));
9149 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9150
9151 let toggle_comments = &ToggleComments {
9152 advance_downwards: false,
9153 ignore_indent: true,
9154 };
9155
9156 // If multiple selections intersect a line, the line is only toggled once.
9157 cx.set_state(indoc! {"
9158 fn a() {
9159 // «b();
9160 // c();
9161 // ˇ» d();
9162 }
9163 "});
9164
9165 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
9166
9167 cx.assert_editor_state(indoc! {"
9168 fn a() {
9169 «b();
9170 c();
9171 ˇ» d();
9172 }
9173 "});
9174
9175 // The comment prefix is inserted at the beginning of each line
9176 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
9177
9178 cx.assert_editor_state(indoc! {"
9179 fn a() {
9180 // «b();
9181 // c();
9182 // ˇ» d();
9183 }
9184 "});
9185
9186 // If a selection ends at the beginning of a line, that line is not toggled.
9187 cx.set_selections_state(indoc! {"
9188 fn a() {
9189 // b();
9190 // «c();
9191 ˇ»// d();
9192 }
9193 "});
9194
9195 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
9196
9197 cx.assert_editor_state(indoc! {"
9198 fn a() {
9199 // b();
9200 «c();
9201 ˇ»// d();
9202 }
9203 "});
9204
9205 // If a selection span a single line and is empty, the line is toggled.
9206 cx.set_state(indoc! {"
9207 fn a() {
9208 a();
9209 b();
9210 ˇ
9211 }
9212 "});
9213
9214 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
9215
9216 cx.assert_editor_state(indoc! {"
9217 fn a() {
9218 a();
9219 b();
9220 //ˇ
9221 }
9222 "});
9223
9224 // If a selection span multiple lines, empty lines are not toggled.
9225 cx.set_state(indoc! {"
9226 fn a() {
9227 «a();
9228
9229 c();ˇ»
9230 }
9231 "});
9232
9233 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
9234
9235 cx.assert_editor_state(indoc! {"
9236 fn a() {
9237 // «a();
9238
9239 // c();ˇ»
9240 }
9241 "});
9242
9243 // If a selection includes multiple comment prefixes, all lines are uncommented.
9244 cx.set_state(indoc! {"
9245 fn a() {
9246 // «a();
9247 /// b();
9248 //! c();ˇ»
9249 }
9250 "});
9251
9252 cx.update_editor(|e, cx| e.toggle_comments(toggle_comments, cx));
9253
9254 cx.assert_editor_state(indoc! {"
9255 fn a() {
9256 «a();
9257 b();
9258 c();ˇ»
9259 }
9260 "});
9261}
9262
9263#[gpui::test]
9264async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
9265 init_test(cx, |_| {});
9266
9267 let language = Arc::new(Language::new(
9268 LanguageConfig {
9269 line_comments: vec!["// ".into()],
9270 ..Default::default()
9271 },
9272 Some(tree_sitter_rust::LANGUAGE.into()),
9273 ));
9274
9275 let mut cx = EditorTestContext::new(cx).await;
9276
9277 cx.language_registry().add(language.clone());
9278 cx.update_buffer(|buffer, cx| {
9279 buffer.set_language(Some(language), cx);
9280 });
9281
9282 let toggle_comments = &ToggleComments {
9283 advance_downwards: true,
9284 ignore_indent: false,
9285 };
9286
9287 // Single cursor on one line -> advance
9288 // Cursor moves horizontally 3 characters as well on non-blank line
9289 cx.set_state(indoc!(
9290 "fn a() {
9291 ˇdog();
9292 cat();
9293 }"
9294 ));
9295 cx.update_editor(|editor, cx| {
9296 editor.toggle_comments(toggle_comments, cx);
9297 });
9298 cx.assert_editor_state(indoc!(
9299 "fn a() {
9300 // dog();
9301 catˇ();
9302 }"
9303 ));
9304
9305 // Single selection on one line -> don't advance
9306 cx.set_state(indoc!(
9307 "fn a() {
9308 «dog()ˇ»;
9309 cat();
9310 }"
9311 ));
9312 cx.update_editor(|editor, cx| {
9313 editor.toggle_comments(toggle_comments, cx);
9314 });
9315 cx.assert_editor_state(indoc!(
9316 "fn a() {
9317 // «dog()ˇ»;
9318 cat();
9319 }"
9320 ));
9321
9322 // Multiple cursors on one line -> advance
9323 cx.set_state(indoc!(
9324 "fn a() {
9325 ˇdˇog();
9326 cat();
9327 }"
9328 ));
9329 cx.update_editor(|editor, cx| {
9330 editor.toggle_comments(toggle_comments, cx);
9331 });
9332 cx.assert_editor_state(indoc!(
9333 "fn a() {
9334 // dog();
9335 catˇ(ˇ);
9336 }"
9337 ));
9338
9339 // Multiple cursors on one line, with selection -> don't advance
9340 cx.set_state(indoc!(
9341 "fn a() {
9342 ˇdˇog«()ˇ»;
9343 cat();
9344 }"
9345 ));
9346 cx.update_editor(|editor, cx| {
9347 editor.toggle_comments(toggle_comments, cx);
9348 });
9349 cx.assert_editor_state(indoc!(
9350 "fn a() {
9351 // ˇdˇog«()ˇ»;
9352 cat();
9353 }"
9354 ));
9355
9356 // Single cursor on one line -> advance
9357 // Cursor moves to column 0 on blank line
9358 cx.set_state(indoc!(
9359 "fn a() {
9360 ˇdog();
9361
9362 cat();
9363 }"
9364 ));
9365 cx.update_editor(|editor, cx| {
9366 editor.toggle_comments(toggle_comments, cx);
9367 });
9368 cx.assert_editor_state(indoc!(
9369 "fn a() {
9370 // dog();
9371 ˇ
9372 cat();
9373 }"
9374 ));
9375
9376 // Single cursor on one line -> advance
9377 // Cursor starts and ends at column 0
9378 cx.set_state(indoc!(
9379 "fn a() {
9380 ˇ dog();
9381 cat();
9382 }"
9383 ));
9384 cx.update_editor(|editor, cx| {
9385 editor.toggle_comments(toggle_comments, cx);
9386 });
9387 cx.assert_editor_state(indoc!(
9388 "fn a() {
9389 // dog();
9390 ˇ cat();
9391 }"
9392 ));
9393}
9394
9395#[gpui::test]
9396async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
9397 init_test(cx, |_| {});
9398
9399 let mut cx = EditorTestContext::new(cx).await;
9400
9401 let html_language = Arc::new(
9402 Language::new(
9403 LanguageConfig {
9404 name: "HTML".into(),
9405 block_comment: Some(("<!-- ".into(), " -->".into())),
9406 ..Default::default()
9407 },
9408 Some(tree_sitter_html::language()),
9409 )
9410 .with_injection_query(
9411 r#"
9412 (script_element
9413 (raw_text) @injection.content
9414 (#set! injection.language "javascript"))
9415 "#,
9416 )
9417 .unwrap(),
9418 );
9419
9420 let javascript_language = Arc::new(Language::new(
9421 LanguageConfig {
9422 name: "JavaScript".into(),
9423 line_comments: vec!["// ".into()],
9424 ..Default::default()
9425 },
9426 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
9427 ));
9428
9429 cx.language_registry().add(html_language.clone());
9430 cx.language_registry().add(javascript_language.clone());
9431 cx.update_buffer(|buffer, cx| {
9432 buffer.set_language(Some(html_language), cx);
9433 });
9434
9435 // Toggle comments for empty selections
9436 cx.set_state(
9437 &r#"
9438 <p>A</p>ˇ
9439 <p>B</p>ˇ
9440 <p>C</p>ˇ
9441 "#
9442 .unindent(),
9443 );
9444 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9445 cx.assert_editor_state(
9446 &r#"
9447 <!-- <p>A</p>ˇ -->
9448 <!-- <p>B</p>ˇ -->
9449 <!-- <p>C</p>ˇ -->
9450 "#
9451 .unindent(),
9452 );
9453 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9454 cx.assert_editor_state(
9455 &r#"
9456 <p>A</p>ˇ
9457 <p>B</p>ˇ
9458 <p>C</p>ˇ
9459 "#
9460 .unindent(),
9461 );
9462
9463 // Toggle comments for mixture of empty and non-empty selections, where
9464 // multiple selections occupy a given line.
9465 cx.set_state(
9466 &r#"
9467 <p>A«</p>
9468 <p>ˇ»B</p>ˇ
9469 <p>C«</p>
9470 <p>ˇ»D</p>ˇ
9471 "#
9472 .unindent(),
9473 );
9474
9475 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9476 cx.assert_editor_state(
9477 &r#"
9478 <!-- <p>A«</p>
9479 <p>ˇ»B</p>ˇ -->
9480 <!-- <p>C«</p>
9481 <p>ˇ»D</p>ˇ -->
9482 "#
9483 .unindent(),
9484 );
9485 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9486 cx.assert_editor_state(
9487 &r#"
9488 <p>A«</p>
9489 <p>ˇ»B</p>ˇ
9490 <p>C«</p>
9491 <p>ˇ»D</p>ˇ
9492 "#
9493 .unindent(),
9494 );
9495
9496 // Toggle comments when different languages are active for different
9497 // selections.
9498 cx.set_state(
9499 &r#"
9500 ˇ<script>
9501 ˇvar x = new Y();
9502 ˇ</script>
9503 "#
9504 .unindent(),
9505 );
9506 cx.executor().run_until_parked();
9507 cx.update_editor(|editor, cx| editor.toggle_comments(&ToggleComments::default(), cx));
9508 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
9509 // Uncommenting and commenting from this position brings in even more wrong artifacts.
9510 cx.assert_editor_state(
9511 &r#"
9512 <!-- ˇ<script> -->
9513 // ˇvar x = new Y();
9514 // ˇ</script>
9515 "#
9516 .unindent(),
9517 );
9518}
9519
9520#[gpui::test]
9521fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
9522 init_test(cx, |_| {});
9523
9524 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9525 let multibuffer = cx.new_model(|cx| {
9526 let mut multibuffer = MultiBuffer::new(ReadWrite);
9527 multibuffer.push_excerpts(
9528 buffer.clone(),
9529 [
9530 ExcerptRange {
9531 context: Point::new(0, 0)..Point::new(0, 4),
9532 primary: None,
9533 },
9534 ExcerptRange {
9535 context: Point::new(1, 0)..Point::new(1, 4),
9536 primary: None,
9537 },
9538 ],
9539 cx,
9540 );
9541 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
9542 multibuffer
9543 });
9544
9545 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9546 view.update(cx, |view, cx| {
9547 assert_eq!(view.text(cx), "aaaa\nbbbb");
9548 view.change_selections(None, cx, |s| {
9549 s.select_ranges([
9550 Point::new(0, 0)..Point::new(0, 0),
9551 Point::new(1, 0)..Point::new(1, 0),
9552 ])
9553 });
9554
9555 view.handle_input("X", cx);
9556 assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
9557 assert_eq!(
9558 view.selections.ranges(cx),
9559 [
9560 Point::new(0, 1)..Point::new(0, 1),
9561 Point::new(1, 1)..Point::new(1, 1),
9562 ]
9563 );
9564
9565 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
9566 view.change_selections(None, cx, |s| {
9567 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
9568 });
9569 view.backspace(&Default::default(), cx);
9570 assert_eq!(view.text(cx), "Xa\nbbb");
9571 assert_eq!(
9572 view.selections.ranges(cx),
9573 [Point::new(1, 0)..Point::new(1, 0)]
9574 );
9575
9576 view.change_selections(None, cx, |s| {
9577 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
9578 });
9579 view.backspace(&Default::default(), cx);
9580 assert_eq!(view.text(cx), "X\nbb");
9581 assert_eq!(
9582 view.selections.ranges(cx),
9583 [Point::new(0, 1)..Point::new(0, 1)]
9584 );
9585 });
9586}
9587
9588#[gpui::test]
9589fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
9590 init_test(cx, |_| {});
9591
9592 let markers = vec![('[', ']').into(), ('(', ')').into()];
9593 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
9594 indoc! {"
9595 [aaaa
9596 (bbbb]
9597 cccc)",
9598 },
9599 markers.clone(),
9600 );
9601 let excerpt_ranges = markers.into_iter().map(|marker| {
9602 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
9603 ExcerptRange {
9604 context,
9605 primary: None,
9606 }
9607 });
9608 let buffer = cx.new_model(|cx| Buffer::local(initial_text, cx));
9609 let multibuffer = cx.new_model(|cx| {
9610 let mut multibuffer = MultiBuffer::new(ReadWrite);
9611 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
9612 multibuffer
9613 });
9614
9615 let (view, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
9616 view.update(cx, |view, cx| {
9617 let (expected_text, selection_ranges) = marked_text_ranges(
9618 indoc! {"
9619 aaaa
9620 bˇbbb
9621 bˇbbˇb
9622 cccc"
9623 },
9624 true,
9625 );
9626 assert_eq!(view.text(cx), expected_text);
9627 view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
9628
9629 view.handle_input("X", cx);
9630
9631 let (expected_text, expected_selections) = marked_text_ranges(
9632 indoc! {"
9633 aaaa
9634 bXˇbbXb
9635 bXˇbbXˇb
9636 cccc"
9637 },
9638 false,
9639 );
9640 assert_eq!(view.text(cx), expected_text);
9641 assert_eq!(view.selections.ranges(cx), expected_selections);
9642
9643 view.newline(&Newline, cx);
9644 let (expected_text, expected_selections) = marked_text_ranges(
9645 indoc! {"
9646 aaaa
9647 bX
9648 ˇbbX
9649 b
9650 bX
9651 ˇbbX
9652 ˇb
9653 cccc"
9654 },
9655 false,
9656 );
9657 assert_eq!(view.text(cx), expected_text);
9658 assert_eq!(view.selections.ranges(cx), expected_selections);
9659 });
9660}
9661
9662#[gpui::test]
9663fn test_refresh_selections(cx: &mut TestAppContext) {
9664 init_test(cx, |_| {});
9665
9666 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9667 let mut excerpt1_id = None;
9668 let multibuffer = cx.new_model(|cx| {
9669 let mut multibuffer = MultiBuffer::new(ReadWrite);
9670 excerpt1_id = multibuffer
9671 .push_excerpts(
9672 buffer.clone(),
9673 [
9674 ExcerptRange {
9675 context: Point::new(0, 0)..Point::new(1, 4),
9676 primary: None,
9677 },
9678 ExcerptRange {
9679 context: Point::new(1, 0)..Point::new(2, 4),
9680 primary: None,
9681 },
9682 ],
9683 cx,
9684 )
9685 .into_iter()
9686 .next();
9687 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9688 multibuffer
9689 });
9690
9691 let editor = cx.add_window(|cx| {
9692 let mut editor = build_editor(multibuffer.clone(), cx);
9693 let snapshot = editor.snapshot(cx);
9694 editor.change_selections(None, cx, |s| {
9695 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
9696 });
9697 editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
9698 assert_eq!(
9699 editor.selections.ranges(cx),
9700 [
9701 Point::new(1, 3)..Point::new(1, 3),
9702 Point::new(2, 1)..Point::new(2, 1),
9703 ]
9704 );
9705 editor
9706 });
9707
9708 // Refreshing selections is a no-op when excerpts haven't changed.
9709 _ = editor.update(cx, |editor, cx| {
9710 editor.change_selections(None, cx, |s| s.refresh());
9711 assert_eq!(
9712 editor.selections.ranges(cx),
9713 [
9714 Point::new(1, 3)..Point::new(1, 3),
9715 Point::new(2, 1)..Point::new(2, 1),
9716 ]
9717 );
9718 });
9719
9720 multibuffer.update(cx, |multibuffer, cx| {
9721 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9722 });
9723 _ = editor.update(cx, |editor, cx| {
9724 // Removing an excerpt causes the first selection to become degenerate.
9725 assert_eq!(
9726 editor.selections.ranges(cx),
9727 [
9728 Point::new(0, 0)..Point::new(0, 0),
9729 Point::new(0, 1)..Point::new(0, 1)
9730 ]
9731 );
9732
9733 // Refreshing selections will relocate the first selection to the original buffer
9734 // location.
9735 editor.change_selections(None, cx, |s| s.refresh());
9736 assert_eq!(
9737 editor.selections.ranges(cx),
9738 [
9739 Point::new(0, 1)..Point::new(0, 1),
9740 Point::new(0, 3)..Point::new(0, 3)
9741 ]
9742 );
9743 assert!(editor.selections.pending_anchor().is_some());
9744 });
9745}
9746
9747#[gpui::test]
9748fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
9749 init_test(cx, |_| {});
9750
9751 let buffer = cx.new_model(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
9752 let mut excerpt1_id = None;
9753 let multibuffer = cx.new_model(|cx| {
9754 let mut multibuffer = MultiBuffer::new(ReadWrite);
9755 excerpt1_id = multibuffer
9756 .push_excerpts(
9757 buffer.clone(),
9758 [
9759 ExcerptRange {
9760 context: Point::new(0, 0)..Point::new(1, 4),
9761 primary: None,
9762 },
9763 ExcerptRange {
9764 context: Point::new(1, 0)..Point::new(2, 4),
9765 primary: None,
9766 },
9767 ],
9768 cx,
9769 )
9770 .into_iter()
9771 .next();
9772 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
9773 multibuffer
9774 });
9775
9776 let editor = cx.add_window(|cx| {
9777 let mut editor = build_editor(multibuffer.clone(), cx);
9778 let snapshot = editor.snapshot(cx);
9779 editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
9780 assert_eq!(
9781 editor.selections.ranges(cx),
9782 [Point::new(1, 3)..Point::new(1, 3)]
9783 );
9784 editor
9785 });
9786
9787 multibuffer.update(cx, |multibuffer, cx| {
9788 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
9789 });
9790 _ = editor.update(cx, |editor, cx| {
9791 assert_eq!(
9792 editor.selections.ranges(cx),
9793 [Point::new(0, 0)..Point::new(0, 0)]
9794 );
9795
9796 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
9797 editor.change_selections(None, cx, |s| s.refresh());
9798 assert_eq!(
9799 editor.selections.ranges(cx),
9800 [Point::new(0, 3)..Point::new(0, 3)]
9801 );
9802 assert!(editor.selections.pending_anchor().is_some());
9803 });
9804}
9805
9806#[gpui::test]
9807async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
9808 init_test(cx, |_| {});
9809
9810 let language = Arc::new(
9811 Language::new(
9812 LanguageConfig {
9813 brackets: BracketPairConfig {
9814 pairs: vec![
9815 BracketPair {
9816 start: "{".to_string(),
9817 end: "}".to_string(),
9818 close: true,
9819 surround: true,
9820 newline: true,
9821 },
9822 BracketPair {
9823 start: "/* ".to_string(),
9824 end: " */".to_string(),
9825 close: true,
9826 surround: true,
9827 newline: true,
9828 },
9829 ],
9830 ..Default::default()
9831 },
9832 ..Default::default()
9833 },
9834 Some(tree_sitter_rust::LANGUAGE.into()),
9835 )
9836 .with_indents_query("")
9837 .unwrap(),
9838 );
9839
9840 let text = concat!(
9841 "{ }\n", //
9842 " x\n", //
9843 " /* */\n", //
9844 "x\n", //
9845 "{{} }\n", //
9846 );
9847
9848 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
9849 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
9850 let (view, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
9851 view.condition::<crate::EditorEvent>(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
9852 .await;
9853
9854 view.update(cx, |view, cx| {
9855 view.change_selections(None, cx, |s| {
9856 s.select_display_ranges([
9857 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
9858 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
9859 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
9860 ])
9861 });
9862 view.newline(&Newline, cx);
9863
9864 assert_eq!(
9865 view.buffer().read(cx).read(cx).text(),
9866 concat!(
9867 "{ \n", // Suppress rustfmt
9868 "\n", //
9869 "}\n", //
9870 " x\n", //
9871 " /* \n", //
9872 " \n", //
9873 " */\n", //
9874 "x\n", //
9875 "{{} \n", //
9876 "}\n", //
9877 )
9878 );
9879 });
9880}
9881
9882#[gpui::test]
9883fn test_highlighted_ranges(cx: &mut TestAppContext) {
9884 init_test(cx, |_| {});
9885
9886 let editor = cx.add_window(|cx| {
9887 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
9888 build_editor(buffer.clone(), cx)
9889 });
9890
9891 _ = editor.update(cx, |editor, cx| {
9892 struct Type1;
9893 struct Type2;
9894
9895 let buffer = editor.buffer.read(cx).snapshot(cx);
9896
9897 let anchor_range =
9898 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
9899
9900 editor.highlight_background::<Type1>(
9901 &[
9902 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
9903 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
9904 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
9905 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
9906 ],
9907 |_| Hsla::red(),
9908 cx,
9909 );
9910 editor.highlight_background::<Type2>(
9911 &[
9912 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
9913 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
9914 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
9915 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
9916 ],
9917 |_| Hsla::green(),
9918 cx,
9919 );
9920
9921 let snapshot = editor.snapshot(cx);
9922 let mut highlighted_ranges = editor.background_highlights_in_range(
9923 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
9924 &snapshot,
9925 cx.theme().colors(),
9926 );
9927 // Enforce a consistent ordering based on color without relying on the ordering of the
9928 // highlight's `TypeId` which is non-executor.
9929 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
9930 assert_eq!(
9931 highlighted_ranges,
9932 &[
9933 (
9934 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
9935 Hsla::red(),
9936 ),
9937 (
9938 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9939 Hsla::red(),
9940 ),
9941 (
9942 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
9943 Hsla::green(),
9944 ),
9945 (
9946 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
9947 Hsla::green(),
9948 ),
9949 ]
9950 );
9951 assert_eq!(
9952 editor.background_highlights_in_range(
9953 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
9954 &snapshot,
9955 cx.theme().colors(),
9956 ),
9957 &[(
9958 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
9959 Hsla::red(),
9960 )]
9961 );
9962 });
9963}
9964
9965#[gpui::test]
9966async fn test_following(cx: &mut gpui::TestAppContext) {
9967 init_test(cx, |_| {});
9968
9969 let fs = FakeFs::new(cx.executor());
9970 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
9971
9972 let buffer = project.update(cx, |project, cx| {
9973 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
9974 cx.new_model(|cx| MultiBuffer::singleton(buffer, cx))
9975 });
9976 let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx));
9977 let follower = cx.update(|cx| {
9978 cx.open_window(
9979 WindowOptions {
9980 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
9981 gpui::Point::new(px(0.), px(0.)),
9982 gpui::Point::new(px(10.), px(80.)),
9983 ))),
9984 ..Default::default()
9985 },
9986 |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
9987 )
9988 .unwrap()
9989 });
9990
9991 let is_still_following = Rc::new(RefCell::new(true));
9992 let follower_edit_event_count = Rc::new(RefCell::new(0));
9993 let pending_update = Rc::new(RefCell::new(None));
9994 _ = follower.update(cx, {
9995 let update = pending_update.clone();
9996 let is_still_following = is_still_following.clone();
9997 let follower_edit_event_count = follower_edit_event_count.clone();
9998 |_, cx| {
9999 cx.subscribe(
10000 &leader.root_view(cx).unwrap(),
10001 move |_, leader, event, cx| {
10002 leader
10003 .read(cx)
10004 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
10005 },
10006 )
10007 .detach();
10008
10009 cx.subscribe(
10010 &follower.root_view(cx).unwrap(),
10011 move |_, _, event: &EditorEvent, _cx| {
10012 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
10013 *is_still_following.borrow_mut() = false;
10014 }
10015
10016 if let EditorEvent::BufferEdited = event {
10017 *follower_edit_event_count.borrow_mut() += 1;
10018 }
10019 },
10020 )
10021 .detach();
10022 }
10023 });
10024
10025 // Update the selections only
10026 _ = leader.update(cx, |leader, cx| {
10027 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
10028 });
10029 follower
10030 .update(cx, |follower, cx| {
10031 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
10032 })
10033 .unwrap()
10034 .await
10035 .unwrap();
10036 _ = follower.update(cx, |follower, cx| {
10037 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
10038 });
10039 assert!(*is_still_following.borrow());
10040 assert_eq!(*follower_edit_event_count.borrow(), 0);
10041
10042 // Update the scroll position only
10043 _ = leader.update(cx, |leader, cx| {
10044 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
10045 });
10046 follower
10047 .update(cx, |follower, cx| {
10048 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
10049 })
10050 .unwrap()
10051 .await
10052 .unwrap();
10053 assert_eq!(
10054 follower
10055 .update(cx, |follower, cx| follower.scroll_position(cx))
10056 .unwrap(),
10057 gpui::Point::new(1.5, 3.5)
10058 );
10059 assert!(*is_still_following.borrow());
10060 assert_eq!(*follower_edit_event_count.borrow(), 0);
10061
10062 // Update the selections and scroll position. The follower's scroll position is updated
10063 // via autoscroll, not via the leader's exact scroll position.
10064 _ = leader.update(cx, |leader, cx| {
10065 leader.change_selections(None, cx, |s| s.select_ranges([0..0]));
10066 leader.request_autoscroll(Autoscroll::newest(), cx);
10067 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx);
10068 });
10069 follower
10070 .update(cx, |follower, cx| {
10071 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
10072 })
10073 .unwrap()
10074 .await
10075 .unwrap();
10076 _ = follower.update(cx, |follower, cx| {
10077 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
10078 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
10079 });
10080 assert!(*is_still_following.borrow());
10081
10082 // Creating a pending selection that precedes another selection
10083 _ = leader.update(cx, |leader, cx| {
10084 leader.change_selections(None, cx, |s| s.select_ranges([1..1]));
10085 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, cx);
10086 });
10087 follower
10088 .update(cx, |follower, cx| {
10089 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
10090 })
10091 .unwrap()
10092 .await
10093 .unwrap();
10094 _ = follower.update(cx, |follower, cx| {
10095 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
10096 });
10097 assert!(*is_still_following.borrow());
10098
10099 // Extend the pending selection so that it surrounds another selection
10100 _ = leader.update(cx, |leader, cx| {
10101 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, cx);
10102 });
10103 follower
10104 .update(cx, |follower, cx| {
10105 follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx)
10106 })
10107 .unwrap()
10108 .await
10109 .unwrap();
10110 _ = follower.update(cx, |follower, cx| {
10111 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
10112 });
10113
10114 // Scrolling locally breaks the follow
10115 _ = follower.update(cx, |follower, cx| {
10116 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
10117 follower.set_scroll_anchor(
10118 ScrollAnchor {
10119 anchor: top_anchor,
10120 offset: gpui::Point::new(0.0, 0.5),
10121 },
10122 cx,
10123 );
10124 });
10125 assert!(!(*is_still_following.borrow()));
10126}
10127
10128#[gpui::test]
10129async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
10130 init_test(cx, |_| {});
10131
10132 let fs = FakeFs::new(cx.executor());
10133 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10134 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10135 let pane = workspace
10136 .update(cx, |workspace, _| workspace.active_pane().clone())
10137 .unwrap();
10138
10139 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10140
10141 let leader = pane.update(cx, |_, cx| {
10142 let multibuffer = cx.new_model(|_| MultiBuffer::new(ReadWrite));
10143 cx.new_view(|cx| build_editor(multibuffer.clone(), cx))
10144 });
10145
10146 // Start following the editor when it has no excerpts.
10147 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
10148 let follower_1 = cx
10149 .update_window(*workspace.deref(), |_, cx| {
10150 Editor::from_state_proto(
10151 workspace.root_view(cx).unwrap(),
10152 ViewId {
10153 creator: Default::default(),
10154 id: 0,
10155 },
10156 &mut state_message,
10157 cx,
10158 )
10159 })
10160 .unwrap()
10161 .unwrap()
10162 .await
10163 .unwrap();
10164
10165 let update_message = Rc::new(RefCell::new(None));
10166 follower_1.update(cx, {
10167 let update = update_message.clone();
10168 |_, cx| {
10169 cx.subscribe(&leader, move |_, leader, event, cx| {
10170 leader
10171 .read(cx)
10172 .add_event_to_update_proto(event, &mut update.borrow_mut(), cx);
10173 })
10174 .detach();
10175 }
10176 });
10177
10178 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
10179 (
10180 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
10181 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
10182 )
10183 });
10184
10185 // Insert some excerpts.
10186 leader.update(cx, |leader, cx| {
10187 leader.buffer.update(cx, |multibuffer, cx| {
10188 let excerpt_ids = multibuffer.push_excerpts(
10189 buffer_1.clone(),
10190 [
10191 ExcerptRange {
10192 context: 1..6,
10193 primary: None,
10194 },
10195 ExcerptRange {
10196 context: 12..15,
10197 primary: None,
10198 },
10199 ExcerptRange {
10200 context: 0..3,
10201 primary: None,
10202 },
10203 ],
10204 cx,
10205 );
10206 multibuffer.insert_excerpts_after(
10207 excerpt_ids[0],
10208 buffer_2.clone(),
10209 [
10210 ExcerptRange {
10211 context: 8..12,
10212 primary: None,
10213 },
10214 ExcerptRange {
10215 context: 0..6,
10216 primary: None,
10217 },
10218 ],
10219 cx,
10220 );
10221 });
10222 });
10223
10224 // Apply the update of adding the excerpts.
10225 follower_1
10226 .update(cx, |follower, cx| {
10227 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
10228 })
10229 .await
10230 .unwrap();
10231 assert_eq!(
10232 follower_1.update(cx, |editor, cx| editor.text(cx)),
10233 leader.update(cx, |editor, cx| editor.text(cx))
10234 );
10235 update_message.borrow_mut().take();
10236
10237 // Start following separately after it already has excerpts.
10238 let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx));
10239 let follower_2 = cx
10240 .update_window(*workspace.deref(), |_, cx| {
10241 Editor::from_state_proto(
10242 workspace.root_view(cx).unwrap().clone(),
10243 ViewId {
10244 creator: Default::default(),
10245 id: 0,
10246 },
10247 &mut state_message,
10248 cx,
10249 )
10250 })
10251 .unwrap()
10252 .unwrap()
10253 .await
10254 .unwrap();
10255 assert_eq!(
10256 follower_2.update(cx, |editor, cx| editor.text(cx)),
10257 leader.update(cx, |editor, cx| editor.text(cx))
10258 );
10259
10260 // Remove some excerpts.
10261 leader.update(cx, |leader, cx| {
10262 leader.buffer.update(cx, |multibuffer, cx| {
10263 let excerpt_ids = multibuffer.excerpt_ids();
10264 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
10265 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
10266 });
10267 });
10268
10269 // Apply the update of removing the excerpts.
10270 follower_1
10271 .update(cx, |follower, cx| {
10272 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
10273 })
10274 .await
10275 .unwrap();
10276 follower_2
10277 .update(cx, |follower, cx| {
10278 follower.apply_update_proto(&project, update_message.borrow().clone().unwrap(), cx)
10279 })
10280 .await
10281 .unwrap();
10282 update_message.borrow_mut().take();
10283 assert_eq!(
10284 follower_1.update(cx, |editor, cx| editor.text(cx)),
10285 leader.update(cx, |editor, cx| editor.text(cx))
10286 );
10287}
10288
10289#[gpui::test]
10290async fn go_to_prev_overlapping_diagnostic(
10291 executor: BackgroundExecutor,
10292 cx: &mut gpui::TestAppContext,
10293) {
10294 init_test(cx, |_| {});
10295
10296 let mut cx = EditorTestContext::new(cx).await;
10297 let lsp_store =
10298 cx.update_editor(|editor, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10299
10300 cx.set_state(indoc! {"
10301 ˇfn func(abc def: i32) -> u32 {
10302 }
10303 "});
10304
10305 cx.update(|cx| {
10306 lsp_store.update(cx, |lsp_store, cx| {
10307 lsp_store
10308 .update_diagnostics(
10309 LanguageServerId(0),
10310 lsp::PublishDiagnosticsParams {
10311 uri: lsp::Url::from_file_path("/root/file").unwrap(),
10312 version: None,
10313 diagnostics: vec![
10314 lsp::Diagnostic {
10315 range: lsp::Range::new(
10316 lsp::Position::new(0, 11),
10317 lsp::Position::new(0, 12),
10318 ),
10319 severity: Some(lsp::DiagnosticSeverity::ERROR),
10320 ..Default::default()
10321 },
10322 lsp::Diagnostic {
10323 range: lsp::Range::new(
10324 lsp::Position::new(0, 12),
10325 lsp::Position::new(0, 15),
10326 ),
10327 severity: Some(lsp::DiagnosticSeverity::ERROR),
10328 ..Default::default()
10329 },
10330 lsp::Diagnostic {
10331 range: lsp::Range::new(
10332 lsp::Position::new(0, 25),
10333 lsp::Position::new(0, 28),
10334 ),
10335 severity: Some(lsp::DiagnosticSeverity::ERROR),
10336 ..Default::default()
10337 },
10338 ],
10339 },
10340 &[],
10341 cx,
10342 )
10343 .unwrap()
10344 });
10345 });
10346
10347 executor.run_until_parked();
10348
10349 cx.update_editor(|editor, cx| {
10350 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10351 });
10352
10353 cx.assert_editor_state(indoc! {"
10354 fn func(abc def: i32) -> ˇu32 {
10355 }
10356 "});
10357
10358 cx.update_editor(|editor, cx| {
10359 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10360 });
10361
10362 cx.assert_editor_state(indoc! {"
10363 fn func(abc ˇdef: i32) -> u32 {
10364 }
10365 "});
10366
10367 cx.update_editor(|editor, cx| {
10368 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10369 });
10370
10371 cx.assert_editor_state(indoc! {"
10372 fn func(abcˇ def: i32) -> u32 {
10373 }
10374 "});
10375
10376 cx.update_editor(|editor, cx| {
10377 editor.go_to_prev_diagnostic(&GoToPrevDiagnostic, cx);
10378 });
10379
10380 cx.assert_editor_state(indoc! {"
10381 fn func(abc def: i32) -> ˇu32 {
10382 }
10383 "});
10384}
10385
10386#[gpui::test]
10387async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
10388 init_test(cx, |_| {});
10389
10390 let mut cx = EditorTestContext::new(cx).await;
10391
10392 cx.set_state(indoc! {"
10393 fn func(abˇc def: i32) -> u32 {
10394 }
10395 "});
10396 let lsp_store =
10397 cx.update_editor(|editor, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
10398
10399 cx.update(|cx| {
10400 lsp_store.update(cx, |lsp_store, cx| {
10401 lsp_store.update_diagnostics(
10402 LanguageServerId(0),
10403 lsp::PublishDiagnosticsParams {
10404 uri: lsp::Url::from_file_path("/root/file").unwrap(),
10405 version: None,
10406 diagnostics: vec![lsp::Diagnostic {
10407 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
10408 severity: Some(lsp::DiagnosticSeverity::ERROR),
10409 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
10410 ..Default::default()
10411 }],
10412 },
10413 &[],
10414 cx,
10415 )
10416 })
10417 }).unwrap();
10418 cx.run_until_parked();
10419 cx.update_editor(|editor, cx| hover_popover::hover(editor, &Default::default(), cx));
10420 cx.run_until_parked();
10421 cx.update_editor(|editor, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
10422}
10423
10424#[gpui::test]
10425async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) {
10426 init_test(cx, |_| {});
10427
10428 let mut cx = EditorTestContext::new(cx).await;
10429
10430 let diff_base = r#"
10431 use some::mod;
10432
10433 const A: u32 = 42;
10434
10435 fn main() {
10436 println!("hello");
10437
10438 println!("world");
10439 }
10440 "#
10441 .unindent();
10442
10443 // Edits are modified, removed, modified, added
10444 cx.set_state(
10445 &r#"
10446 use some::modified;
10447
10448 ˇ
10449 fn main() {
10450 println!("hello there");
10451
10452 println!("around the");
10453 println!("world");
10454 }
10455 "#
10456 .unindent(),
10457 );
10458
10459 cx.set_diff_base(&diff_base);
10460 executor.run_until_parked();
10461
10462 cx.update_editor(|editor, cx| {
10463 //Wrap around the bottom of the buffer
10464 for _ in 0..3 {
10465 editor.go_to_next_hunk(&GoToHunk, cx);
10466 }
10467 });
10468
10469 cx.assert_editor_state(
10470 &r#"
10471 ˇuse some::modified;
10472
10473
10474 fn main() {
10475 println!("hello there");
10476
10477 println!("around the");
10478 println!("world");
10479 }
10480 "#
10481 .unindent(),
10482 );
10483
10484 cx.update_editor(|editor, cx| {
10485 //Wrap around the top of the buffer
10486 for _ in 0..2 {
10487 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10488 }
10489 });
10490
10491 cx.assert_editor_state(
10492 &r#"
10493 use some::modified;
10494
10495
10496 fn main() {
10497 ˇ println!("hello there");
10498
10499 println!("around the");
10500 println!("world");
10501 }
10502 "#
10503 .unindent(),
10504 );
10505
10506 cx.update_editor(|editor, cx| {
10507 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10508 });
10509
10510 cx.assert_editor_state(
10511 &r#"
10512 use some::modified;
10513
10514 ˇ
10515 fn main() {
10516 println!("hello there");
10517
10518 println!("around the");
10519 println!("world");
10520 }
10521 "#
10522 .unindent(),
10523 );
10524
10525 cx.update_editor(|editor, cx| {
10526 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10527 });
10528
10529 cx.assert_editor_state(
10530 &r#"
10531 ˇuse some::modified;
10532
10533
10534 fn main() {
10535 println!("hello there");
10536
10537 println!("around the");
10538 println!("world");
10539 }
10540 "#
10541 .unindent(),
10542 );
10543
10544 cx.update_editor(|editor, cx| {
10545 for _ in 0..2 {
10546 editor.go_to_prev_hunk(&GoToPrevHunk, cx);
10547 }
10548 });
10549
10550 cx.assert_editor_state(
10551 &r#"
10552 use some::modified;
10553
10554
10555 fn main() {
10556 ˇ println!("hello there");
10557
10558 println!("around the");
10559 println!("world");
10560 }
10561 "#
10562 .unindent(),
10563 );
10564
10565 cx.update_editor(|editor, cx| {
10566 editor.fold(&Fold, cx);
10567 });
10568
10569 cx.update_editor(|editor, cx| {
10570 editor.go_to_next_hunk(&GoToHunk, cx);
10571 });
10572
10573 cx.assert_editor_state(
10574 &r#"
10575 ˇuse some::modified;
10576
10577
10578 fn main() {
10579 println!("hello there");
10580
10581 println!("around the");
10582 println!("world");
10583 }
10584 "#
10585 .unindent(),
10586 );
10587}
10588
10589#[test]
10590fn test_split_words() {
10591 fn split(text: &str) -> Vec<&str> {
10592 split_words(text).collect()
10593 }
10594
10595 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
10596 assert_eq!(split("hello_world"), &["hello_", "world"]);
10597 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
10598 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
10599 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
10600 assert_eq!(split("helloworld"), &["helloworld"]);
10601
10602 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
10603}
10604
10605#[gpui::test]
10606async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
10607 init_test(cx, |_| {});
10608
10609 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
10610 let mut assert = |before, after| {
10611 let _state_context = cx.set_state(before);
10612 cx.run_until_parked();
10613 cx.update_editor(|editor, cx| {
10614 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, cx)
10615 });
10616 cx.assert_editor_state(after);
10617 };
10618
10619 // Outside bracket jumps to outside of matching bracket
10620 assert("console.logˇ(var);", "console.log(var)ˇ;");
10621 assert("console.log(var)ˇ;", "console.logˇ(var);");
10622
10623 // Inside bracket jumps to inside of matching bracket
10624 assert("console.log(ˇvar);", "console.log(varˇ);");
10625 assert("console.log(varˇ);", "console.log(ˇvar);");
10626
10627 // When outside a bracket and inside, favor jumping to the inside bracket
10628 assert(
10629 "console.log('foo', [1, 2, 3]ˇ);",
10630 "console.log(ˇ'foo', [1, 2, 3]);",
10631 );
10632 assert(
10633 "console.log(ˇ'foo', [1, 2, 3]);",
10634 "console.log('foo', [1, 2, 3]ˇ);",
10635 );
10636
10637 // Bias forward if two options are equally likely
10638 assert(
10639 "let result = curried_fun()ˇ();",
10640 "let result = curried_fun()()ˇ;",
10641 );
10642
10643 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
10644 assert(
10645 indoc! {"
10646 function test() {
10647 console.log('test')ˇ
10648 }"},
10649 indoc! {"
10650 function test() {
10651 console.logˇ('test')
10652 }"},
10653 );
10654}
10655
10656#[gpui::test]
10657async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
10658 init_test(cx, |_| {});
10659
10660 let fs = FakeFs::new(cx.executor());
10661 fs.insert_tree(
10662 "/a",
10663 json!({
10664 "main.rs": "fn main() { let a = 5; }",
10665 "other.rs": "// Test file",
10666 }),
10667 )
10668 .await;
10669 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10670
10671 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10672 language_registry.add(Arc::new(Language::new(
10673 LanguageConfig {
10674 name: "Rust".into(),
10675 matcher: LanguageMatcher {
10676 path_suffixes: vec!["rs".to_string()],
10677 ..Default::default()
10678 },
10679 brackets: BracketPairConfig {
10680 pairs: vec![BracketPair {
10681 start: "{".to_string(),
10682 end: "}".to_string(),
10683 close: true,
10684 surround: true,
10685 newline: true,
10686 }],
10687 disabled_scopes_by_bracket_ix: Vec::new(),
10688 },
10689 ..Default::default()
10690 },
10691 Some(tree_sitter_rust::LANGUAGE.into()),
10692 )));
10693 let mut fake_servers = language_registry.register_fake_lsp(
10694 "Rust",
10695 FakeLspAdapter {
10696 capabilities: lsp::ServerCapabilities {
10697 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
10698 first_trigger_character: "{".to_string(),
10699 more_trigger_character: None,
10700 }),
10701 ..Default::default()
10702 },
10703 ..Default::default()
10704 },
10705 );
10706
10707 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10708
10709 let cx = &mut VisualTestContext::from_window(*workspace, cx);
10710
10711 let worktree_id = workspace
10712 .update(cx, |workspace, cx| {
10713 workspace.project().update(cx, |project, cx| {
10714 project.worktrees(cx).next().unwrap().read(cx).id()
10715 })
10716 })
10717 .unwrap();
10718
10719 let buffer = project
10720 .update(cx, |project, cx| {
10721 project.open_local_buffer("/a/main.rs", cx)
10722 })
10723 .await
10724 .unwrap();
10725 let editor_handle = workspace
10726 .update(cx, |workspace, cx| {
10727 workspace.open_path((worktree_id, "main.rs"), None, true, cx)
10728 })
10729 .unwrap()
10730 .await
10731 .unwrap()
10732 .downcast::<Editor>()
10733 .unwrap();
10734
10735 cx.executor().start_waiting();
10736 let fake_server = fake_servers.next().await.unwrap();
10737
10738 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
10739 assert_eq!(
10740 params.text_document_position.text_document.uri,
10741 lsp::Url::from_file_path("/a/main.rs").unwrap(),
10742 );
10743 assert_eq!(
10744 params.text_document_position.position,
10745 lsp::Position::new(0, 21),
10746 );
10747
10748 Ok(Some(vec![lsp::TextEdit {
10749 new_text: "]".to_string(),
10750 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
10751 }]))
10752 });
10753
10754 editor_handle.update(cx, |editor, cx| {
10755 editor.focus(cx);
10756 editor.change_selections(None, cx, |s| {
10757 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
10758 });
10759 editor.handle_input("{", cx);
10760 });
10761
10762 cx.executor().run_until_parked();
10763
10764 buffer.update(cx, |buffer, _| {
10765 assert_eq!(
10766 buffer.text(),
10767 "fn main() { let a = {5}; }",
10768 "No extra braces from on type formatting should appear in the buffer"
10769 )
10770 });
10771}
10772
10773#[gpui::test]
10774async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::TestAppContext) {
10775 init_test(cx, |_| {});
10776
10777 let fs = FakeFs::new(cx.executor());
10778 fs.insert_tree(
10779 "/a",
10780 json!({
10781 "main.rs": "fn main() { let a = 5; }",
10782 "other.rs": "// Test file",
10783 }),
10784 )
10785 .await;
10786
10787 let project = Project::test(fs, ["/a".as_ref()], cx).await;
10788
10789 let server_restarts = Arc::new(AtomicUsize::new(0));
10790 let closure_restarts = Arc::clone(&server_restarts);
10791 let language_server_name = "test language server";
10792 let language_name: LanguageName = "Rust".into();
10793
10794 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10795 language_registry.add(Arc::new(Language::new(
10796 LanguageConfig {
10797 name: language_name.clone(),
10798 matcher: LanguageMatcher {
10799 path_suffixes: vec!["rs".to_string()],
10800 ..Default::default()
10801 },
10802 ..Default::default()
10803 },
10804 Some(tree_sitter_rust::LANGUAGE.into()),
10805 )));
10806 let mut fake_servers = language_registry.register_fake_lsp(
10807 "Rust",
10808 FakeLspAdapter {
10809 name: language_server_name,
10810 initialization_options: Some(json!({
10811 "testOptionValue": true
10812 })),
10813 initializer: Some(Box::new(move |fake_server| {
10814 let task_restarts = Arc::clone(&closure_restarts);
10815 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
10816 task_restarts.fetch_add(1, atomic::Ordering::Release);
10817 futures::future::ready(Ok(()))
10818 });
10819 })),
10820 ..Default::default()
10821 },
10822 );
10823
10824 let _window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
10825 let _buffer = project
10826 .update(cx, |project, cx| {
10827 project.open_local_buffer_with_lsp("/a/main.rs", cx)
10828 })
10829 .await
10830 .unwrap();
10831 let _fake_server = fake_servers.next().await.unwrap();
10832 update_test_language_settings(cx, |language_settings| {
10833 language_settings.languages.insert(
10834 language_name.clone(),
10835 LanguageSettingsContent {
10836 tab_size: NonZeroU32::new(8),
10837 ..Default::default()
10838 },
10839 );
10840 });
10841 cx.executor().run_until_parked();
10842 assert_eq!(
10843 server_restarts.load(atomic::Ordering::Acquire),
10844 0,
10845 "Should not restart LSP server on an unrelated change"
10846 );
10847
10848 update_test_project_settings(cx, |project_settings| {
10849 project_settings.lsp.insert(
10850 "Some other server name".into(),
10851 LspSettings {
10852 binary: None,
10853 settings: None,
10854 initialization_options: Some(json!({
10855 "some other init value": false
10856 })),
10857 },
10858 );
10859 });
10860 cx.executor().run_until_parked();
10861 assert_eq!(
10862 server_restarts.load(atomic::Ordering::Acquire),
10863 0,
10864 "Should not restart LSP server on an unrelated LSP settings change"
10865 );
10866
10867 update_test_project_settings(cx, |project_settings| {
10868 project_settings.lsp.insert(
10869 language_server_name.into(),
10870 LspSettings {
10871 binary: None,
10872 settings: None,
10873 initialization_options: Some(json!({
10874 "anotherInitValue": false
10875 })),
10876 },
10877 );
10878 });
10879 cx.executor().run_until_parked();
10880 assert_eq!(
10881 server_restarts.load(atomic::Ordering::Acquire),
10882 1,
10883 "Should restart LSP server on a related LSP settings change"
10884 );
10885
10886 update_test_project_settings(cx, |project_settings| {
10887 project_settings.lsp.insert(
10888 language_server_name.into(),
10889 LspSettings {
10890 binary: None,
10891 settings: None,
10892 initialization_options: Some(json!({
10893 "anotherInitValue": false
10894 })),
10895 },
10896 );
10897 });
10898 cx.executor().run_until_parked();
10899 assert_eq!(
10900 server_restarts.load(atomic::Ordering::Acquire),
10901 1,
10902 "Should not restart LSP server on a related LSP settings change that is the same"
10903 );
10904
10905 update_test_project_settings(cx, |project_settings| {
10906 project_settings.lsp.insert(
10907 language_server_name.into(),
10908 LspSettings {
10909 binary: None,
10910 settings: None,
10911 initialization_options: None,
10912 },
10913 );
10914 });
10915 cx.executor().run_until_parked();
10916 assert_eq!(
10917 server_restarts.load(atomic::Ordering::Acquire),
10918 2,
10919 "Should restart LSP server on another related LSP settings change"
10920 );
10921}
10922
10923#[gpui::test]
10924async fn test_completions_with_additional_edits(cx: &mut gpui::TestAppContext) {
10925 init_test(cx, |_| {});
10926
10927 let mut cx = EditorLspTestContext::new_rust(
10928 lsp::ServerCapabilities {
10929 completion_provider: Some(lsp::CompletionOptions {
10930 trigger_characters: Some(vec![".".to_string()]),
10931 resolve_provider: Some(true),
10932 ..Default::default()
10933 }),
10934 ..Default::default()
10935 },
10936 cx,
10937 )
10938 .await;
10939
10940 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10941 cx.simulate_keystroke(".");
10942 let completion_item = lsp::CompletionItem {
10943 label: "some".into(),
10944 kind: Some(lsp::CompletionItemKind::SNIPPET),
10945 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10946 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10947 kind: lsp::MarkupKind::Markdown,
10948 value: "```rust\nSome(2)\n```".to_string(),
10949 })),
10950 deprecated: Some(false),
10951 sort_text: Some("fffffff2".to_string()),
10952 filter_text: Some("some".to_string()),
10953 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10954 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10955 range: lsp::Range {
10956 start: lsp::Position {
10957 line: 0,
10958 character: 22,
10959 },
10960 end: lsp::Position {
10961 line: 0,
10962 character: 22,
10963 },
10964 },
10965 new_text: "Some(2)".to_string(),
10966 })),
10967 additional_text_edits: Some(vec![lsp::TextEdit {
10968 range: lsp::Range {
10969 start: lsp::Position {
10970 line: 0,
10971 character: 20,
10972 },
10973 end: lsp::Position {
10974 line: 0,
10975 character: 22,
10976 },
10977 },
10978 new_text: "".to_string(),
10979 }]),
10980 ..Default::default()
10981 };
10982
10983 let closure_completion_item = completion_item.clone();
10984 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10985 let task_completion_item = closure_completion_item.clone();
10986 async move {
10987 Ok(Some(lsp::CompletionResponse::Array(vec![
10988 task_completion_item,
10989 ])))
10990 }
10991 });
10992
10993 request.next().await;
10994
10995 cx.condition(|editor, _| editor.context_menu_visible())
10996 .await;
10997 let apply_additional_edits = cx.update_editor(|editor, cx| {
10998 editor
10999 .confirm_completion(&ConfirmCompletion::default(), cx)
11000 .unwrap()
11001 });
11002 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
11003
11004 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
11005 let task_completion_item = completion_item.clone();
11006 async move { Ok(task_completion_item) }
11007 })
11008 .next()
11009 .await
11010 .unwrap();
11011 apply_additional_edits.await.unwrap();
11012 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
11013}
11014
11015#[gpui::test]
11016async fn test_completions_resolve_updates_labels_if_filter_text_matches(
11017 cx: &mut gpui::TestAppContext,
11018) {
11019 init_test(cx, |_| {});
11020
11021 let mut cx = EditorLspTestContext::new_rust(
11022 lsp::ServerCapabilities {
11023 completion_provider: Some(lsp::CompletionOptions {
11024 trigger_characters: Some(vec![".".to_string()]),
11025 resolve_provider: Some(true),
11026 ..Default::default()
11027 }),
11028 ..Default::default()
11029 },
11030 cx,
11031 )
11032 .await;
11033
11034 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11035 cx.simulate_keystroke(".");
11036
11037 let item1 = lsp::CompletionItem {
11038 label: "id".to_string(),
11039 filter_text: Some("id".to_string()),
11040 detail: None,
11041 documentation: None,
11042 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11043 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11044 new_text: ".id".to_string(),
11045 })),
11046 ..lsp::CompletionItem::default()
11047 };
11048
11049 let item2 = lsp::CompletionItem {
11050 label: "other".to_string(),
11051 filter_text: Some("other".to_string()),
11052 detail: None,
11053 documentation: None,
11054 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11055 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11056 new_text: ".other".to_string(),
11057 })),
11058 ..lsp::CompletionItem::default()
11059 };
11060
11061 let item1 = item1.clone();
11062 cx.handle_request::<lsp::request::Completion, _, _>({
11063 let item1 = item1.clone();
11064 move |_, _, _| {
11065 let item1 = item1.clone();
11066 let item2 = item2.clone();
11067 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
11068 }
11069 })
11070 .next()
11071 .await;
11072
11073 cx.condition(|editor, _| editor.context_menu_visible())
11074 .await;
11075 cx.update_editor(|editor, _| {
11076 let context_menu = editor.context_menu.borrow_mut();
11077 let context_menu = context_menu
11078 .as_ref()
11079 .expect("Should have the context menu deployed");
11080 match context_menu {
11081 CodeContextMenu::Completions(completions_menu) => {
11082 let completions = completions_menu.completions.borrow_mut();
11083 assert_eq!(
11084 completions
11085 .iter()
11086 .map(|completion| &completion.label.text)
11087 .collect::<Vec<_>>(),
11088 vec!["id", "other"]
11089 )
11090 }
11091 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11092 }
11093 });
11094
11095 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
11096 let item1 = item1.clone();
11097 move |_, item_to_resolve, _| {
11098 let item1 = item1.clone();
11099 async move {
11100 if item1 == item_to_resolve {
11101 Ok(lsp::CompletionItem {
11102 label: "method id()".to_string(),
11103 filter_text: Some("id".to_string()),
11104 detail: Some("Now resolved!".to_string()),
11105 documentation: Some(lsp::Documentation::String("Docs".to_string())),
11106 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11107 range: lsp::Range::new(
11108 lsp::Position::new(0, 22),
11109 lsp::Position::new(0, 22),
11110 ),
11111 new_text: ".id".to_string(),
11112 })),
11113 ..lsp::CompletionItem::default()
11114 })
11115 } else {
11116 Ok(item_to_resolve)
11117 }
11118 }
11119 }
11120 })
11121 .next()
11122 .await
11123 .unwrap();
11124 cx.run_until_parked();
11125
11126 cx.update_editor(|editor, cx| {
11127 editor.context_menu_next(&Default::default(), cx);
11128 });
11129
11130 cx.update_editor(|editor, _| {
11131 let context_menu = editor.context_menu.borrow_mut();
11132 let context_menu = context_menu
11133 .as_ref()
11134 .expect("Should have the context menu deployed");
11135 match context_menu {
11136 CodeContextMenu::Completions(completions_menu) => {
11137 let completions = completions_menu.completions.borrow_mut();
11138 assert_eq!(
11139 completions
11140 .iter()
11141 .map(|completion| &completion.label.text)
11142 .collect::<Vec<_>>(),
11143 vec!["method id()", "other"],
11144 "Should update first completion label, but not second as the filter text did not match."
11145 );
11146 }
11147 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11148 }
11149 });
11150}
11151
11152#[gpui::test]
11153async fn test_completions_resolve_happens_once(cx: &mut gpui::TestAppContext) {
11154 init_test(cx, |_| {});
11155
11156 let mut cx = EditorLspTestContext::new_rust(
11157 lsp::ServerCapabilities {
11158 completion_provider: Some(lsp::CompletionOptions {
11159 trigger_characters: Some(vec![".".to_string()]),
11160 resolve_provider: Some(true),
11161 ..Default::default()
11162 }),
11163 ..Default::default()
11164 },
11165 cx,
11166 )
11167 .await;
11168
11169 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11170 cx.simulate_keystroke(".");
11171
11172 let unresolved_item_1 = lsp::CompletionItem {
11173 label: "id".to_string(),
11174 filter_text: Some("id".to_string()),
11175 detail: None,
11176 documentation: None,
11177 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11178 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11179 new_text: ".id".to_string(),
11180 })),
11181 ..lsp::CompletionItem::default()
11182 };
11183 let resolved_item_1 = lsp::CompletionItem {
11184 additional_text_edits: Some(vec![lsp::TextEdit {
11185 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
11186 new_text: "!!".to_string(),
11187 }]),
11188 ..unresolved_item_1.clone()
11189 };
11190 let unresolved_item_2 = lsp::CompletionItem {
11191 label: "other".to_string(),
11192 filter_text: Some("other".to_string()),
11193 detail: None,
11194 documentation: None,
11195 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11196 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11197 new_text: ".other".to_string(),
11198 })),
11199 ..lsp::CompletionItem::default()
11200 };
11201 let resolved_item_2 = lsp::CompletionItem {
11202 additional_text_edits: Some(vec![lsp::TextEdit {
11203 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
11204 new_text: "??".to_string(),
11205 }]),
11206 ..unresolved_item_2.clone()
11207 };
11208
11209 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
11210 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
11211 cx.lsp
11212 .server
11213 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
11214 let unresolved_item_1 = unresolved_item_1.clone();
11215 let resolved_item_1 = resolved_item_1.clone();
11216 let unresolved_item_2 = unresolved_item_2.clone();
11217 let resolved_item_2 = resolved_item_2.clone();
11218 let resolve_requests_1 = resolve_requests_1.clone();
11219 let resolve_requests_2 = resolve_requests_2.clone();
11220 move |unresolved_request, _| {
11221 let unresolved_item_1 = unresolved_item_1.clone();
11222 let resolved_item_1 = resolved_item_1.clone();
11223 let unresolved_item_2 = unresolved_item_2.clone();
11224 let resolved_item_2 = resolved_item_2.clone();
11225 let resolve_requests_1 = resolve_requests_1.clone();
11226 let resolve_requests_2 = resolve_requests_2.clone();
11227 async move {
11228 if unresolved_request == unresolved_item_1 {
11229 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
11230 Ok(resolved_item_1.clone())
11231 } else if unresolved_request == unresolved_item_2 {
11232 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
11233 Ok(resolved_item_2.clone())
11234 } else {
11235 panic!("Unexpected completion item {unresolved_request:?}")
11236 }
11237 }
11238 }
11239 })
11240 .detach();
11241
11242 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11243 let unresolved_item_1 = unresolved_item_1.clone();
11244 let unresolved_item_2 = unresolved_item_2.clone();
11245 async move {
11246 Ok(Some(lsp::CompletionResponse::Array(vec![
11247 unresolved_item_1,
11248 unresolved_item_2,
11249 ])))
11250 }
11251 })
11252 .next()
11253 .await;
11254
11255 cx.condition(|editor, _| editor.context_menu_visible())
11256 .await;
11257 cx.update_editor(|editor, _| {
11258 let context_menu = editor.context_menu.borrow_mut();
11259 let context_menu = context_menu
11260 .as_ref()
11261 .expect("Should have the context menu deployed");
11262 match context_menu {
11263 CodeContextMenu::Completions(completions_menu) => {
11264 let completions = completions_menu.completions.borrow_mut();
11265 assert_eq!(
11266 completions
11267 .iter()
11268 .map(|completion| &completion.label.text)
11269 .collect::<Vec<_>>(),
11270 vec!["id", "other"]
11271 )
11272 }
11273 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
11274 }
11275 });
11276 cx.run_until_parked();
11277
11278 cx.update_editor(|editor, cx| {
11279 editor.context_menu_next(&ContextMenuNext, cx);
11280 });
11281 cx.run_until_parked();
11282 cx.update_editor(|editor, cx| {
11283 editor.context_menu_prev(&ContextMenuPrev, cx);
11284 });
11285 cx.run_until_parked();
11286 cx.update_editor(|editor, cx| {
11287 editor.context_menu_next(&ContextMenuNext, cx);
11288 });
11289 cx.run_until_parked();
11290 cx.update_editor(|editor, cx| {
11291 editor
11292 .compose_completion(&ComposeCompletion::default(), cx)
11293 .expect("No task returned")
11294 })
11295 .await
11296 .expect("Completion failed");
11297 cx.run_until_parked();
11298
11299 cx.update_editor(|editor, cx| {
11300 assert_eq!(
11301 resolve_requests_1.load(atomic::Ordering::Acquire),
11302 1,
11303 "Should always resolve once despite multiple selections"
11304 );
11305 assert_eq!(
11306 resolve_requests_2.load(atomic::Ordering::Acquire),
11307 1,
11308 "Should always resolve once after multiple selections and applying the completion"
11309 );
11310 assert_eq!(
11311 editor.text(cx),
11312 "fn main() { let a = ??.other; }",
11313 "Should use resolved data when applying the completion"
11314 );
11315 });
11316}
11317
11318#[gpui::test]
11319async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
11320 init_test(cx, |_| {});
11321
11322 let item_0 = lsp::CompletionItem {
11323 label: "abs".into(),
11324 insert_text: Some("abs".into()),
11325 data: Some(json!({ "very": "special"})),
11326 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
11327 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
11328 lsp::InsertReplaceEdit {
11329 new_text: "abs".to_string(),
11330 insert: lsp::Range::default(),
11331 replace: lsp::Range::default(),
11332 },
11333 )),
11334 ..lsp::CompletionItem::default()
11335 };
11336 let items = iter::once(item_0.clone())
11337 .chain((11..51).map(|i| lsp::CompletionItem {
11338 label: format!("item_{}", i),
11339 insert_text: Some(format!("item_{}", i)),
11340 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
11341 ..lsp::CompletionItem::default()
11342 }))
11343 .collect::<Vec<_>>();
11344
11345 let default_commit_characters = vec!["?".to_string()];
11346 let default_data = json!({ "default": "data"});
11347 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
11348 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
11349 let default_edit_range = lsp::Range {
11350 start: lsp::Position {
11351 line: 0,
11352 character: 5,
11353 },
11354 end: lsp::Position {
11355 line: 0,
11356 character: 5,
11357 },
11358 };
11359
11360 let item_0_out = lsp::CompletionItem {
11361 commit_characters: Some(default_commit_characters.clone()),
11362 insert_text_format: Some(default_insert_text_format),
11363 ..item_0
11364 };
11365 let items_out = iter::once(item_0_out)
11366 .chain(items[1..].iter().map(|item| lsp::CompletionItem {
11367 commit_characters: Some(default_commit_characters.clone()),
11368 data: Some(default_data.clone()),
11369 insert_text_mode: Some(default_insert_text_mode),
11370 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
11371 range: default_edit_range,
11372 new_text: item.label.clone(),
11373 })),
11374 ..item.clone()
11375 }))
11376 .collect::<Vec<lsp::CompletionItem>>();
11377
11378 let mut cx = EditorLspTestContext::new_rust(
11379 lsp::ServerCapabilities {
11380 completion_provider: Some(lsp::CompletionOptions {
11381 trigger_characters: Some(vec![".".to_string()]),
11382 resolve_provider: Some(true),
11383 ..Default::default()
11384 }),
11385 ..Default::default()
11386 },
11387 cx,
11388 )
11389 .await;
11390
11391 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
11392 cx.simulate_keystroke(".");
11393
11394 let completion_data = default_data.clone();
11395 let completion_characters = default_commit_characters.clone();
11396 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
11397 let default_data = completion_data.clone();
11398 let default_commit_characters = completion_characters.clone();
11399 let items = items.clone();
11400 async move {
11401 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
11402 items,
11403 item_defaults: Some(lsp::CompletionListItemDefaults {
11404 data: Some(default_data.clone()),
11405 commit_characters: Some(default_commit_characters.clone()),
11406 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
11407 default_edit_range,
11408 )),
11409 insert_text_format: Some(default_insert_text_format),
11410 insert_text_mode: Some(default_insert_text_mode),
11411 }),
11412 ..lsp::CompletionList::default()
11413 })))
11414 }
11415 })
11416 .next()
11417 .await;
11418
11419 let resolved_items = Arc::new(Mutex::new(Vec::new()));
11420 cx.lsp
11421 .server
11422 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
11423 let closure_resolved_items = resolved_items.clone();
11424 move |item_to_resolve, _| {
11425 let closure_resolved_items = closure_resolved_items.clone();
11426 async move {
11427 closure_resolved_items.lock().push(item_to_resolve.clone());
11428 Ok(item_to_resolve)
11429 }
11430 }
11431 })
11432 .detach();
11433
11434 cx.condition(|editor, _| editor.context_menu_visible())
11435 .await;
11436 cx.run_until_parked();
11437 cx.update_editor(|editor, _| {
11438 let menu = editor.context_menu.borrow_mut();
11439 match menu.as_ref().expect("should have the completions menu") {
11440 CodeContextMenu::Completions(completions_menu) => {
11441 assert_eq!(
11442 completions_menu
11443 .entries
11444 .borrow()
11445 .iter()
11446 .flat_map(|c| match c {
11447 CompletionEntry::Match(mat) => Some(mat.string.clone()),
11448 _ => None,
11449 })
11450 .collect::<Vec<String>>(),
11451 items_out
11452 .iter()
11453 .map(|completion| completion.label.clone())
11454 .collect::<Vec<String>>()
11455 );
11456 }
11457 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
11458 }
11459 });
11460 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
11461 // with 4 from the end.
11462 assert_eq!(
11463 *resolved_items.lock(),
11464 [
11465 &items_out[0..16],
11466 &items_out[items_out.len() - 4..items_out.len()]
11467 ]
11468 .concat()
11469 .iter()
11470 .cloned()
11471 .collect::<Vec<lsp::CompletionItem>>()
11472 );
11473 resolved_items.lock().clear();
11474
11475 cx.update_editor(|editor, cx| {
11476 editor.context_menu_prev(&ContextMenuPrev, cx);
11477 });
11478 cx.run_until_parked();
11479 // Completions that have already been resolved are skipped.
11480 assert_eq!(
11481 *resolved_items.lock(),
11482 items_out[items_out.len() - 16..items_out.len() - 4]
11483 .iter()
11484 .cloned()
11485 .collect::<Vec<lsp::CompletionItem>>()
11486 );
11487 resolved_items.lock().clear();
11488}
11489
11490#[gpui::test]
11491async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui::TestAppContext) {
11492 init_test(cx, |_| {});
11493
11494 let mut cx = EditorLspTestContext::new(
11495 Language::new(
11496 LanguageConfig {
11497 matcher: LanguageMatcher {
11498 path_suffixes: vec!["jsx".into()],
11499 ..Default::default()
11500 },
11501 overrides: [(
11502 "element".into(),
11503 LanguageConfigOverride {
11504 word_characters: Override::Set(['-'].into_iter().collect()),
11505 ..Default::default()
11506 },
11507 )]
11508 .into_iter()
11509 .collect(),
11510 ..Default::default()
11511 },
11512 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
11513 )
11514 .with_override_query("(jsx_self_closing_element) @element")
11515 .unwrap(),
11516 lsp::ServerCapabilities {
11517 completion_provider: Some(lsp::CompletionOptions {
11518 trigger_characters: Some(vec![":".to_string()]),
11519 ..Default::default()
11520 }),
11521 ..Default::default()
11522 },
11523 cx,
11524 )
11525 .await;
11526
11527 cx.lsp
11528 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
11529 Ok(Some(lsp::CompletionResponse::Array(vec![
11530 lsp::CompletionItem {
11531 label: "bg-blue".into(),
11532 ..Default::default()
11533 },
11534 lsp::CompletionItem {
11535 label: "bg-red".into(),
11536 ..Default::default()
11537 },
11538 lsp::CompletionItem {
11539 label: "bg-yellow".into(),
11540 ..Default::default()
11541 },
11542 ])))
11543 });
11544
11545 cx.set_state(r#"<p class="bgˇ" />"#);
11546
11547 // Trigger completion when typing a dash, because the dash is an extra
11548 // word character in the 'element' scope, which contains the cursor.
11549 cx.simulate_keystroke("-");
11550 cx.executor().run_until_parked();
11551 cx.update_editor(|editor, _| {
11552 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11553 {
11554 assert_eq!(
11555 completion_menu_entries(&menu),
11556 &["bg-red", "bg-blue", "bg-yellow"]
11557 );
11558 } else {
11559 panic!("expected completion menu to be open");
11560 }
11561 });
11562
11563 cx.simulate_keystroke("l");
11564 cx.executor().run_until_parked();
11565 cx.update_editor(|editor, _| {
11566 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11567 {
11568 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
11569 } else {
11570 panic!("expected completion menu to be open");
11571 }
11572 });
11573
11574 // When filtering completions, consider the character after the '-' to
11575 // be the start of a subword.
11576 cx.set_state(r#"<p class="yelˇ" />"#);
11577 cx.simulate_keystroke("l");
11578 cx.executor().run_until_parked();
11579 cx.update_editor(|editor, _| {
11580 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
11581 {
11582 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
11583 } else {
11584 panic!("expected completion menu to be open");
11585 }
11586 });
11587}
11588
11589fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
11590 let entries = menu.entries.borrow();
11591 entries
11592 .iter()
11593 .flat_map(|e| match e {
11594 CompletionEntry::Match(mat) => Some(mat.string.clone()),
11595 _ => None,
11596 })
11597 .collect()
11598}
11599
11600#[gpui::test]
11601async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
11602 init_test(cx, |settings| {
11603 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
11604 FormatterList(vec![Formatter::Prettier].into()),
11605 ))
11606 });
11607
11608 let fs = FakeFs::new(cx.executor());
11609 fs.insert_file("/file.ts", Default::default()).await;
11610
11611 let project = Project::test(fs, ["/file.ts".as_ref()], cx).await;
11612 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11613
11614 language_registry.add(Arc::new(Language::new(
11615 LanguageConfig {
11616 name: "TypeScript".into(),
11617 matcher: LanguageMatcher {
11618 path_suffixes: vec!["ts".to_string()],
11619 ..Default::default()
11620 },
11621 ..Default::default()
11622 },
11623 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
11624 )));
11625 update_test_language_settings(cx, |settings| {
11626 settings.defaults.prettier = Some(PrettierSettings {
11627 allowed: true,
11628 ..PrettierSettings::default()
11629 });
11630 });
11631
11632 let test_plugin = "test_plugin";
11633 let _ = language_registry.register_fake_lsp(
11634 "TypeScript",
11635 FakeLspAdapter {
11636 prettier_plugins: vec![test_plugin],
11637 ..Default::default()
11638 },
11639 );
11640
11641 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
11642 let buffer = project
11643 .update(cx, |project, cx| project.open_local_buffer("/file.ts", cx))
11644 .await
11645 .unwrap();
11646
11647 let buffer_text = "one\ntwo\nthree\n";
11648 let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx));
11649 let (editor, cx) = cx.add_window_view(|cx| build_editor(buffer, cx));
11650 editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
11651
11652 editor
11653 .update(cx, |editor, cx| {
11654 editor.perform_format(
11655 project.clone(),
11656 FormatTrigger::Manual,
11657 FormatTarget::Buffers,
11658 cx,
11659 )
11660 })
11661 .unwrap()
11662 .await;
11663 assert_eq!(
11664 editor.update(cx, |editor, cx| editor.text(cx)),
11665 buffer_text.to_string() + prettier_format_suffix,
11666 "Test prettier formatting was not applied to the original buffer text",
11667 );
11668
11669 update_test_language_settings(cx, |settings| {
11670 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
11671 });
11672 let format = editor.update(cx, |editor, cx| {
11673 editor.perform_format(
11674 project.clone(),
11675 FormatTrigger::Manual,
11676 FormatTarget::Buffers,
11677 cx,
11678 )
11679 });
11680 format.await.unwrap();
11681 assert_eq!(
11682 editor.update(cx, |editor, cx| editor.text(cx)),
11683 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
11684 "Autoformatting (via test prettier) was not applied to the original buffer text",
11685 );
11686}
11687
11688#[gpui::test]
11689async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
11690 init_test(cx, |_| {});
11691 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11692 let base_text = indoc! {r#"
11693 struct Row;
11694 struct Row1;
11695 struct Row2;
11696
11697 struct Row4;
11698 struct Row5;
11699 struct Row6;
11700
11701 struct Row8;
11702 struct Row9;
11703 struct Row10;"#};
11704
11705 // When addition hunks are not adjacent to carets, no hunk revert is performed
11706 assert_hunk_revert(
11707 indoc! {r#"struct Row;
11708 struct Row1;
11709 struct Row1.1;
11710 struct Row1.2;
11711 struct Row2;ˇ
11712
11713 struct Row4;
11714 struct Row5;
11715 struct Row6;
11716
11717 struct Row8;
11718 ˇstruct Row9;
11719 struct Row9.1;
11720 struct Row9.2;
11721 struct Row9.3;
11722 struct Row10;"#},
11723 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11724 indoc! {r#"struct Row;
11725 struct Row1;
11726 struct Row1.1;
11727 struct Row1.2;
11728 struct Row2;ˇ
11729
11730 struct Row4;
11731 struct Row5;
11732 struct Row6;
11733
11734 struct Row8;
11735 ˇstruct Row9;
11736 struct Row9.1;
11737 struct Row9.2;
11738 struct Row9.3;
11739 struct Row10;"#},
11740 base_text,
11741 &mut cx,
11742 );
11743 // Same for selections
11744 assert_hunk_revert(
11745 indoc! {r#"struct Row;
11746 struct Row1;
11747 struct Row2;
11748 struct Row2.1;
11749 struct Row2.2;
11750 «ˇ
11751 struct Row4;
11752 struct» Row5;
11753 «struct Row6;
11754 ˇ»
11755 struct Row9.1;
11756 struct Row9.2;
11757 struct Row9.3;
11758 struct Row8;
11759 struct Row9;
11760 struct Row10;"#},
11761 vec![DiffHunkStatus::Added, DiffHunkStatus::Added],
11762 indoc! {r#"struct Row;
11763 struct Row1;
11764 struct Row2;
11765 struct Row2.1;
11766 struct Row2.2;
11767 «ˇ
11768 struct Row4;
11769 struct» Row5;
11770 «struct Row6;
11771 ˇ»
11772 struct Row9.1;
11773 struct Row9.2;
11774 struct Row9.3;
11775 struct Row8;
11776 struct Row9;
11777 struct Row10;"#},
11778 base_text,
11779 &mut cx,
11780 );
11781
11782 // When carets and selections intersect the addition hunks, those are reverted.
11783 // Adjacent carets got merged.
11784 assert_hunk_revert(
11785 indoc! {r#"struct Row;
11786 ˇ// something on the top
11787 struct Row1;
11788 struct Row2;
11789 struct Roˇw3.1;
11790 struct Row2.2;
11791 struct Row2.3;ˇ
11792
11793 struct Row4;
11794 struct ˇRow5.1;
11795 struct Row5.2;
11796 struct «Rowˇ»5.3;
11797 struct Row5;
11798 struct Row6;
11799 ˇ
11800 struct Row9.1;
11801 struct «Rowˇ»9.2;
11802 struct «ˇRow»9.3;
11803 struct Row8;
11804 struct Row9;
11805 «ˇ// something on bottom»
11806 struct Row10;"#},
11807 vec![
11808 DiffHunkStatus::Added,
11809 DiffHunkStatus::Added,
11810 DiffHunkStatus::Added,
11811 DiffHunkStatus::Added,
11812 DiffHunkStatus::Added,
11813 ],
11814 indoc! {r#"struct Row;
11815 ˇstruct Row1;
11816 struct Row2;
11817 ˇ
11818 struct Row4;
11819 ˇstruct Row5;
11820 struct Row6;
11821 ˇ
11822 ˇstruct Row8;
11823 struct Row9;
11824 ˇstruct Row10;"#},
11825 base_text,
11826 &mut cx,
11827 );
11828}
11829
11830#[gpui::test]
11831async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
11832 init_test(cx, |_| {});
11833 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11834 let base_text = indoc! {r#"
11835 struct Row;
11836 struct Row1;
11837 struct Row2;
11838
11839 struct Row4;
11840 struct Row5;
11841 struct Row6;
11842
11843 struct Row8;
11844 struct Row9;
11845 struct Row10;"#};
11846
11847 // Modification hunks behave the same as the addition ones.
11848 assert_hunk_revert(
11849 indoc! {r#"struct Row;
11850 struct Row1;
11851 struct Row33;
11852 ˇ
11853 struct Row4;
11854 struct Row5;
11855 struct Row6;
11856 ˇ
11857 struct Row99;
11858 struct Row9;
11859 struct Row10;"#},
11860 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11861 indoc! {r#"struct Row;
11862 struct Row1;
11863 struct Row33;
11864 ˇ
11865 struct Row4;
11866 struct Row5;
11867 struct Row6;
11868 ˇ
11869 struct Row99;
11870 struct Row9;
11871 struct Row10;"#},
11872 base_text,
11873 &mut cx,
11874 );
11875 assert_hunk_revert(
11876 indoc! {r#"struct Row;
11877 struct Row1;
11878 struct Row33;
11879 «ˇ
11880 struct Row4;
11881 struct» Row5;
11882 «struct Row6;
11883 ˇ»
11884 struct Row99;
11885 struct Row9;
11886 struct Row10;"#},
11887 vec![DiffHunkStatus::Modified, DiffHunkStatus::Modified],
11888 indoc! {r#"struct Row;
11889 struct Row1;
11890 struct Row33;
11891 «ˇ
11892 struct Row4;
11893 struct» Row5;
11894 «struct Row6;
11895 ˇ»
11896 struct Row99;
11897 struct Row9;
11898 struct Row10;"#},
11899 base_text,
11900 &mut cx,
11901 );
11902
11903 assert_hunk_revert(
11904 indoc! {r#"ˇstruct Row1.1;
11905 struct Row1;
11906 «ˇstr»uct Row22;
11907
11908 struct ˇRow44;
11909 struct Row5;
11910 struct «Rˇ»ow66;ˇ
11911
11912 «struˇ»ct Row88;
11913 struct Row9;
11914 struct Row1011;ˇ"#},
11915 vec![
11916 DiffHunkStatus::Modified,
11917 DiffHunkStatus::Modified,
11918 DiffHunkStatus::Modified,
11919 DiffHunkStatus::Modified,
11920 DiffHunkStatus::Modified,
11921 DiffHunkStatus::Modified,
11922 ],
11923 indoc! {r#"struct Row;
11924 ˇstruct Row1;
11925 struct Row2;
11926 ˇ
11927 struct Row4;
11928 ˇstruct Row5;
11929 struct Row6;
11930 ˇ
11931 struct Row8;
11932 ˇstruct Row9;
11933 struct Row10;ˇ"#},
11934 base_text,
11935 &mut cx,
11936 );
11937}
11938
11939#[gpui::test]
11940async fn test_deleting_over_diff_hunk(cx: &mut gpui::TestAppContext) {
11941 init_test(cx, |_| {});
11942 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11943 let base_text = indoc! {r#"
11944 one
11945
11946 two
11947 three
11948 "#};
11949
11950 cx.set_diff_base(base_text);
11951 cx.set_state("\nˇ\n");
11952 cx.executor().run_until_parked();
11953 cx.update_editor(|editor, cx| {
11954 editor.expand_selected_diff_hunks(cx);
11955 });
11956 cx.executor().run_until_parked();
11957 cx.update_editor(|editor, cx| {
11958 editor.backspace(&Default::default(), cx);
11959 });
11960 cx.run_until_parked();
11961 cx.assert_state_with_diff(
11962 indoc! {r#"
11963
11964 - two
11965 - threeˇ
11966 +
11967 "#}
11968 .to_string(),
11969 );
11970}
11971
11972#[gpui::test]
11973async fn test_deletion_reverts(cx: &mut gpui::TestAppContext) {
11974 init_test(cx, |_| {});
11975 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
11976 let base_text = indoc! {r#"struct Row;
11977struct Row1;
11978struct Row2;
11979
11980struct Row4;
11981struct Row5;
11982struct Row6;
11983
11984struct Row8;
11985struct Row9;
11986struct Row10;"#};
11987
11988 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
11989 assert_hunk_revert(
11990 indoc! {r#"struct Row;
11991 struct Row2;
11992
11993 ˇstruct Row4;
11994 struct Row5;
11995 struct Row6;
11996 ˇ
11997 struct Row8;
11998 struct Row10;"#},
11999 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
12000 indoc! {r#"struct Row;
12001 struct Row2;
12002
12003 ˇstruct Row4;
12004 struct Row5;
12005 struct Row6;
12006 ˇ
12007 struct Row8;
12008 struct Row10;"#},
12009 base_text,
12010 &mut cx,
12011 );
12012 assert_hunk_revert(
12013 indoc! {r#"struct Row;
12014 struct Row2;
12015
12016 «ˇstruct Row4;
12017 struct» Row5;
12018 «struct Row6;
12019 ˇ»
12020 struct Row8;
12021 struct Row10;"#},
12022 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
12023 indoc! {r#"struct Row;
12024 struct Row2;
12025
12026 «ˇstruct Row4;
12027 struct» Row5;
12028 «struct Row6;
12029 ˇ»
12030 struct Row8;
12031 struct Row10;"#},
12032 base_text,
12033 &mut cx,
12034 );
12035
12036 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
12037 assert_hunk_revert(
12038 indoc! {r#"struct Row;
12039 ˇstruct Row2;
12040
12041 struct Row4;
12042 struct Row5;
12043 struct Row6;
12044
12045 struct Row8;ˇ
12046 struct Row10;"#},
12047 vec![DiffHunkStatus::Removed, DiffHunkStatus::Removed],
12048 indoc! {r#"struct Row;
12049 struct Row1;
12050 ˇstruct Row2;
12051
12052 struct Row4;
12053 struct Row5;
12054 struct Row6;
12055
12056 struct Row8;ˇ
12057 struct Row9;
12058 struct Row10;"#},
12059 base_text,
12060 &mut cx,
12061 );
12062 assert_hunk_revert(
12063 indoc! {r#"struct Row;
12064 struct Row2«ˇ;
12065 struct Row4;
12066 struct» Row5;
12067 «struct Row6;
12068
12069 struct Row8;ˇ»
12070 struct Row10;"#},
12071 vec![
12072 DiffHunkStatus::Removed,
12073 DiffHunkStatus::Removed,
12074 DiffHunkStatus::Removed,
12075 ],
12076 indoc! {r#"struct Row;
12077 struct Row1;
12078 struct Row2«ˇ;
12079
12080 struct Row4;
12081 struct» Row5;
12082 «struct Row6;
12083
12084 struct Row8;ˇ»
12085 struct Row9;
12086 struct Row10;"#},
12087 base_text,
12088 &mut cx,
12089 );
12090}
12091
12092#[gpui::test]
12093async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
12094 init_test(cx, |_| {});
12095
12096 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
12097 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
12098 let base_text_3 =
12099 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
12100
12101 let text_1 = edit_first_char_of_every_line(base_text_1);
12102 let text_2 = edit_first_char_of_every_line(base_text_2);
12103 let text_3 = edit_first_char_of_every_line(base_text_3);
12104
12105 let buffer_1 = cx.new_model(|cx| Buffer::local(text_1.clone(), cx));
12106 let buffer_2 = cx.new_model(|cx| Buffer::local(text_2.clone(), cx));
12107 let buffer_3 = cx.new_model(|cx| Buffer::local(text_3.clone(), cx));
12108
12109 let multibuffer = cx.new_model(|cx| {
12110 let mut multibuffer = MultiBuffer::new(ReadWrite);
12111 multibuffer.push_excerpts(
12112 buffer_1.clone(),
12113 [
12114 ExcerptRange {
12115 context: Point::new(0, 0)..Point::new(3, 0),
12116 primary: None,
12117 },
12118 ExcerptRange {
12119 context: Point::new(5, 0)..Point::new(7, 0),
12120 primary: None,
12121 },
12122 ExcerptRange {
12123 context: Point::new(9, 0)..Point::new(10, 4),
12124 primary: None,
12125 },
12126 ],
12127 cx,
12128 );
12129 multibuffer.push_excerpts(
12130 buffer_2.clone(),
12131 [
12132 ExcerptRange {
12133 context: Point::new(0, 0)..Point::new(3, 0),
12134 primary: None,
12135 },
12136 ExcerptRange {
12137 context: Point::new(5, 0)..Point::new(7, 0),
12138 primary: None,
12139 },
12140 ExcerptRange {
12141 context: Point::new(9, 0)..Point::new(10, 4),
12142 primary: None,
12143 },
12144 ],
12145 cx,
12146 );
12147 multibuffer.push_excerpts(
12148 buffer_3.clone(),
12149 [
12150 ExcerptRange {
12151 context: Point::new(0, 0)..Point::new(3, 0),
12152 primary: None,
12153 },
12154 ExcerptRange {
12155 context: Point::new(5, 0)..Point::new(7, 0),
12156 primary: None,
12157 },
12158 ExcerptRange {
12159 context: Point::new(9, 0)..Point::new(10, 4),
12160 primary: None,
12161 },
12162 ],
12163 cx,
12164 );
12165 multibuffer
12166 });
12167
12168 let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
12169 editor.update(cx, |editor, cx| {
12170 for (buffer, diff_base) in [
12171 (buffer_1.clone(), base_text_1),
12172 (buffer_2.clone(), base_text_2),
12173 (buffer_3.clone(), base_text_3),
12174 ] {
12175 let change_set = cx.new_model(|cx| {
12176 BufferChangeSet::new_with_base_text(diff_base.to_string(), &buffer, cx)
12177 });
12178 editor
12179 .buffer
12180 .update(cx, |buffer, cx| buffer.add_change_set(change_set, cx));
12181 }
12182 });
12183 cx.executor().run_until_parked();
12184
12185 editor.update(cx, |editor, cx| {
12186 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}");
12187 editor.select_all(&SelectAll, cx);
12188 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
12189 });
12190 cx.executor().run_until_parked();
12191
12192 // When all ranges are selected, all buffer hunks are reverted.
12193 editor.update(cx, |editor, cx| {
12194 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");
12195 });
12196 buffer_1.update(cx, |buffer, _| {
12197 assert_eq!(buffer.text(), base_text_1);
12198 });
12199 buffer_2.update(cx, |buffer, _| {
12200 assert_eq!(buffer.text(), base_text_2);
12201 });
12202 buffer_3.update(cx, |buffer, _| {
12203 assert_eq!(buffer.text(), base_text_3);
12204 });
12205
12206 editor.update(cx, |editor, cx| {
12207 editor.undo(&Default::default(), cx);
12208 });
12209
12210 editor.update(cx, |editor, cx| {
12211 editor.change_selections(None, cx, |s| {
12212 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
12213 });
12214 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
12215 });
12216
12217 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
12218 // but not affect buffer_2 and its related excerpts.
12219 editor.update(cx, |editor, cx| {
12220 assert_eq!(
12221 editor.text(cx),
12222 "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}"
12223 );
12224 });
12225 buffer_1.update(cx, |buffer, _| {
12226 assert_eq!(buffer.text(), base_text_1);
12227 });
12228 buffer_2.update(cx, |buffer, _| {
12229 assert_eq!(
12230 buffer.text(),
12231 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
12232 );
12233 });
12234 buffer_3.update(cx, |buffer, _| {
12235 assert_eq!(
12236 buffer.text(),
12237 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
12238 );
12239 });
12240
12241 fn edit_first_char_of_every_line(text: &str) -> String {
12242 text.split('\n')
12243 .map(|line| format!("X{}", &line[1..]))
12244 .collect::<Vec<_>>()
12245 .join("\n")
12246 }
12247}
12248
12249#[gpui::test]
12250async fn test_mutlibuffer_in_navigation_history(cx: &mut gpui::TestAppContext) {
12251 init_test(cx, |_| {});
12252
12253 let cols = 4;
12254 let rows = 10;
12255 let sample_text_1 = sample_text(rows, cols, 'a');
12256 assert_eq!(
12257 sample_text_1,
12258 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
12259 );
12260 let sample_text_2 = sample_text(rows, cols, 'l');
12261 assert_eq!(
12262 sample_text_2,
12263 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
12264 );
12265 let sample_text_3 = sample_text(rows, cols, 'v');
12266 assert_eq!(
12267 sample_text_3,
12268 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
12269 );
12270
12271 let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
12272 let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
12273 let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
12274
12275 let multi_buffer = cx.new_model(|cx| {
12276 let mut multibuffer = MultiBuffer::new(ReadWrite);
12277 multibuffer.push_excerpts(
12278 buffer_1.clone(),
12279 [
12280 ExcerptRange {
12281 context: Point::new(0, 0)..Point::new(3, 0),
12282 primary: None,
12283 },
12284 ExcerptRange {
12285 context: Point::new(5, 0)..Point::new(7, 0),
12286 primary: None,
12287 },
12288 ExcerptRange {
12289 context: Point::new(9, 0)..Point::new(10, 4),
12290 primary: None,
12291 },
12292 ],
12293 cx,
12294 );
12295 multibuffer.push_excerpts(
12296 buffer_2.clone(),
12297 [
12298 ExcerptRange {
12299 context: Point::new(0, 0)..Point::new(3, 0),
12300 primary: None,
12301 },
12302 ExcerptRange {
12303 context: Point::new(5, 0)..Point::new(7, 0),
12304 primary: None,
12305 },
12306 ExcerptRange {
12307 context: Point::new(9, 0)..Point::new(10, 4),
12308 primary: None,
12309 },
12310 ],
12311 cx,
12312 );
12313 multibuffer.push_excerpts(
12314 buffer_3.clone(),
12315 [
12316 ExcerptRange {
12317 context: Point::new(0, 0)..Point::new(3, 0),
12318 primary: None,
12319 },
12320 ExcerptRange {
12321 context: Point::new(5, 0)..Point::new(7, 0),
12322 primary: None,
12323 },
12324 ExcerptRange {
12325 context: Point::new(9, 0)..Point::new(10, 4),
12326 primary: None,
12327 },
12328 ],
12329 cx,
12330 );
12331 multibuffer
12332 });
12333
12334 let fs = FakeFs::new(cx.executor());
12335 fs.insert_tree(
12336 "/a",
12337 json!({
12338 "main.rs": sample_text_1,
12339 "other.rs": sample_text_2,
12340 "lib.rs": sample_text_3,
12341 }),
12342 )
12343 .await;
12344 let project = Project::test(fs, ["/a".as_ref()], cx).await;
12345 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
12346 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12347 let multi_buffer_editor = cx.new_view(|cx| {
12348 Editor::new(
12349 EditorMode::Full,
12350 multi_buffer,
12351 Some(project.clone()),
12352 true,
12353 cx,
12354 )
12355 });
12356 let multibuffer_item_id = workspace
12357 .update(cx, |workspace, cx| {
12358 assert!(
12359 workspace.active_item(cx).is_none(),
12360 "active item should be None before the first item is added"
12361 );
12362 workspace.add_item_to_active_pane(
12363 Box::new(multi_buffer_editor.clone()),
12364 None,
12365 true,
12366 cx,
12367 );
12368 let active_item = workspace
12369 .active_item(cx)
12370 .expect("should have an active item after adding the multi buffer");
12371 assert!(
12372 !active_item.is_singleton(cx),
12373 "A multi buffer was expected to active after adding"
12374 );
12375 active_item.item_id()
12376 })
12377 .unwrap();
12378 cx.executor().run_until_parked();
12379
12380 multi_buffer_editor.update(cx, |editor, cx| {
12381 editor.change_selections(Some(Autoscroll::Next), cx, |s| s.select_ranges(Some(1..2)));
12382 editor.open_excerpts(&OpenExcerpts, cx);
12383 });
12384 cx.executor().run_until_parked();
12385 let first_item_id = workspace
12386 .update(cx, |workspace, cx| {
12387 let active_item = workspace
12388 .active_item(cx)
12389 .expect("should have an active item after navigating into the 1st buffer");
12390 let first_item_id = active_item.item_id();
12391 assert_ne!(
12392 first_item_id, multibuffer_item_id,
12393 "Should navigate into the 1st buffer and activate it"
12394 );
12395 assert!(
12396 active_item.is_singleton(cx),
12397 "New active item should be a singleton buffer"
12398 );
12399 assert_eq!(
12400 active_item
12401 .act_as::<Editor>(cx)
12402 .expect("should have navigated into an editor for the 1st buffer")
12403 .read(cx)
12404 .text(cx),
12405 sample_text_1
12406 );
12407
12408 workspace
12409 .go_back(workspace.active_pane().downgrade(), cx)
12410 .detach_and_log_err(cx);
12411
12412 first_item_id
12413 })
12414 .unwrap();
12415 cx.executor().run_until_parked();
12416 workspace
12417 .update(cx, |workspace, cx| {
12418 let active_item = workspace
12419 .active_item(cx)
12420 .expect("should have an active item after navigating back");
12421 assert_eq!(
12422 active_item.item_id(),
12423 multibuffer_item_id,
12424 "Should navigate back to the multi buffer"
12425 );
12426 assert!(!active_item.is_singleton(cx));
12427 })
12428 .unwrap();
12429
12430 multi_buffer_editor.update(cx, |editor, cx| {
12431 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
12432 s.select_ranges(Some(39..40))
12433 });
12434 editor.open_excerpts(&OpenExcerpts, cx);
12435 });
12436 cx.executor().run_until_parked();
12437 let second_item_id = workspace
12438 .update(cx, |workspace, cx| {
12439 let active_item = workspace
12440 .active_item(cx)
12441 .expect("should have an active item after navigating into the 2nd buffer");
12442 let second_item_id = active_item.item_id();
12443 assert_ne!(
12444 second_item_id, multibuffer_item_id,
12445 "Should navigate away from the multibuffer"
12446 );
12447 assert_ne!(
12448 second_item_id, first_item_id,
12449 "Should navigate into the 2nd buffer and activate it"
12450 );
12451 assert!(
12452 active_item.is_singleton(cx),
12453 "New active item should be a singleton buffer"
12454 );
12455 assert_eq!(
12456 active_item
12457 .act_as::<Editor>(cx)
12458 .expect("should have navigated into an editor")
12459 .read(cx)
12460 .text(cx),
12461 sample_text_2
12462 );
12463
12464 workspace
12465 .go_back(workspace.active_pane().downgrade(), cx)
12466 .detach_and_log_err(cx);
12467
12468 second_item_id
12469 })
12470 .unwrap();
12471 cx.executor().run_until_parked();
12472 workspace
12473 .update(cx, |workspace, cx| {
12474 let active_item = workspace
12475 .active_item(cx)
12476 .expect("should have an active item after navigating back from the 2nd buffer");
12477 assert_eq!(
12478 active_item.item_id(),
12479 multibuffer_item_id,
12480 "Should navigate back from the 2nd buffer to the multi buffer"
12481 );
12482 assert!(!active_item.is_singleton(cx));
12483 })
12484 .unwrap();
12485
12486 multi_buffer_editor.update(cx, |editor, cx| {
12487 editor.change_selections(Some(Autoscroll::Next), cx, |s| {
12488 s.select_ranges(Some(70..70))
12489 });
12490 editor.open_excerpts(&OpenExcerpts, cx);
12491 });
12492 cx.executor().run_until_parked();
12493 workspace
12494 .update(cx, |workspace, cx| {
12495 let active_item = workspace
12496 .active_item(cx)
12497 .expect("should have an active item after navigating into the 3rd buffer");
12498 let third_item_id = active_item.item_id();
12499 assert_ne!(
12500 third_item_id, multibuffer_item_id,
12501 "Should navigate into the 3rd buffer and activate it"
12502 );
12503 assert_ne!(third_item_id, first_item_id);
12504 assert_ne!(third_item_id, second_item_id);
12505 assert!(
12506 active_item.is_singleton(cx),
12507 "New active item should be a singleton buffer"
12508 );
12509 assert_eq!(
12510 active_item
12511 .act_as::<Editor>(cx)
12512 .expect("should have navigated into an editor")
12513 .read(cx)
12514 .text(cx),
12515 sample_text_3
12516 );
12517
12518 workspace
12519 .go_back(workspace.active_pane().downgrade(), cx)
12520 .detach_and_log_err(cx);
12521 })
12522 .unwrap();
12523 cx.executor().run_until_parked();
12524 workspace
12525 .update(cx, |workspace, cx| {
12526 let active_item = workspace
12527 .active_item(cx)
12528 .expect("should have an active item after navigating back from the 3rd buffer");
12529 assert_eq!(
12530 active_item.item_id(),
12531 multibuffer_item_id,
12532 "Should navigate back from the 3rd buffer to the multi buffer"
12533 );
12534 assert!(!active_item.is_singleton(cx));
12535 })
12536 .unwrap();
12537}
12538
12539#[gpui::test]
12540async fn test_toggle_selected_diff_hunks(
12541 executor: BackgroundExecutor,
12542 cx: &mut gpui::TestAppContext,
12543) {
12544 init_test(cx, |_| {});
12545
12546 let mut cx = EditorTestContext::new(cx).await;
12547
12548 let diff_base = r#"
12549 use some::mod;
12550
12551 const A: u32 = 42;
12552
12553 fn main() {
12554 println!("hello");
12555
12556 println!("world");
12557 }
12558 "#
12559 .unindent();
12560
12561 cx.set_state(
12562 &r#"
12563 use some::modified;
12564
12565 ˇ
12566 fn main() {
12567 println!("hello there");
12568
12569 println!("around the");
12570 println!("world");
12571 }
12572 "#
12573 .unindent(),
12574 );
12575
12576 cx.set_diff_base(&diff_base);
12577 executor.run_until_parked();
12578
12579 cx.update_editor(|editor, cx| {
12580 editor.go_to_next_hunk(&GoToHunk, cx);
12581 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, cx);
12582 });
12583 executor.run_until_parked();
12584 cx.assert_state_with_diff(
12585 r#"
12586 use some::modified;
12587
12588
12589 fn main() {
12590 - println!("hello");
12591 + ˇ println!("hello there");
12592
12593 println!("around the");
12594 println!("world");
12595 }
12596 "#
12597 .unindent(),
12598 );
12599
12600 cx.update_editor(|editor, cx| {
12601 for _ in 0..2 {
12602 editor.go_to_next_hunk(&GoToHunk, cx);
12603 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, cx);
12604 }
12605 });
12606 executor.run_until_parked();
12607 cx.assert_state_with_diff(
12608 r#"
12609 - use some::mod;
12610 + ˇuse some::modified;
12611
12612
12613 fn main() {
12614 - println!("hello");
12615 + println!("hello there");
12616
12617 + println!("around the");
12618 println!("world");
12619 }
12620 "#
12621 .unindent(),
12622 );
12623
12624 cx.update_editor(|editor, cx| {
12625 editor.go_to_next_hunk(&GoToHunk, cx);
12626 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, cx);
12627 });
12628 executor.run_until_parked();
12629 cx.assert_state_with_diff(
12630 r#"
12631 - use some::mod;
12632 + use some::modified;
12633
12634 - const A: u32 = 42;
12635 ˇ
12636 fn main() {
12637 - println!("hello");
12638 + println!("hello there");
12639
12640 + println!("around the");
12641 println!("world");
12642 }
12643 "#
12644 .unindent(),
12645 );
12646
12647 cx.update_editor(|editor, cx| {
12648 editor.cancel(&Cancel, cx);
12649 });
12650
12651 cx.assert_state_with_diff(
12652 r#"
12653 use some::modified;
12654
12655 ˇ
12656 fn main() {
12657 println!("hello there");
12658
12659 println!("around the");
12660 println!("world");
12661 }
12662 "#
12663 .unindent(),
12664 );
12665}
12666
12667#[gpui::test]
12668async fn test_diff_base_change_with_expanded_diff_hunks(
12669 executor: BackgroundExecutor,
12670 cx: &mut gpui::TestAppContext,
12671) {
12672 init_test(cx, |_| {});
12673
12674 let mut cx = EditorTestContext::new(cx).await;
12675
12676 let diff_base = r#"
12677 use some::mod1;
12678 use some::mod2;
12679
12680 const A: u32 = 42;
12681 const B: u32 = 42;
12682 const C: u32 = 42;
12683
12684 fn main() {
12685 println!("hello");
12686
12687 println!("world");
12688 }
12689 "#
12690 .unindent();
12691
12692 cx.set_state(
12693 &r#"
12694 use some::mod2;
12695
12696 const A: u32 = 42;
12697 const C: u32 = 42;
12698
12699 fn main(ˇ) {
12700 //println!("hello");
12701
12702 println!("world");
12703 //
12704 //
12705 }
12706 "#
12707 .unindent(),
12708 );
12709
12710 cx.set_diff_base(&diff_base);
12711 executor.run_until_parked();
12712
12713 cx.update_editor(|editor, cx| {
12714 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, 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 - const B: u32 = 42;
12724 const C: u32 = 42;
12725
12726 fn main(ˇ) {
12727 - println!("hello");
12728 + //println!("hello");
12729
12730 println!("world");
12731 + //
12732 + //
12733 }
12734 "#
12735 .unindent(),
12736 );
12737
12738 cx.set_diff_base("new diff base!");
12739 executor.run_until_parked();
12740 cx.assert_state_with_diff(
12741 r#"
12742 use some::mod2;
12743
12744 const A: u32 = 42;
12745 const C: u32 = 42;
12746
12747 fn main(ˇ) {
12748 //println!("hello");
12749
12750 println!("world");
12751 //
12752 //
12753 }
12754 "#
12755 .unindent(),
12756 );
12757
12758 cx.update_editor(|editor, cx| {
12759 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
12760 });
12761 executor.run_until_parked();
12762 cx.assert_state_with_diff(
12763 r#"
12764 - new diff base!
12765 + use some::mod2;
12766 +
12767 + const A: u32 = 42;
12768 + const C: u32 = 42;
12769 +
12770 + fn main(ˇ) {
12771 + //println!("hello");
12772 +
12773 + println!("world");
12774 + //
12775 + //
12776 + }
12777 "#
12778 .unindent(),
12779 );
12780}
12781
12782#[gpui::test]
12783async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) {
12784 init_test(cx, |_| {});
12785
12786 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12787 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
12788 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12789 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
12790 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
12791 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
12792
12793 let buffer_1 = cx.new_model(|cx| Buffer::local(file_1_new.to_string(), cx));
12794 let buffer_2 = cx.new_model(|cx| Buffer::local(file_2_new.to_string(), cx));
12795 let buffer_3 = cx.new_model(|cx| Buffer::local(file_3_new.to_string(), cx));
12796
12797 let multi_buffer = cx.new_model(|cx| {
12798 let mut multibuffer = MultiBuffer::new(ReadWrite);
12799 multibuffer.push_excerpts(
12800 buffer_1.clone(),
12801 [
12802 ExcerptRange {
12803 context: Point::new(0, 0)..Point::new(3, 0),
12804 primary: None,
12805 },
12806 ExcerptRange {
12807 context: Point::new(5, 0)..Point::new(7, 0),
12808 primary: None,
12809 },
12810 ExcerptRange {
12811 context: Point::new(9, 0)..Point::new(10, 3),
12812 primary: None,
12813 },
12814 ],
12815 cx,
12816 );
12817 multibuffer.push_excerpts(
12818 buffer_2.clone(),
12819 [
12820 ExcerptRange {
12821 context: Point::new(0, 0)..Point::new(3, 0),
12822 primary: None,
12823 },
12824 ExcerptRange {
12825 context: Point::new(5, 0)..Point::new(7, 0),
12826 primary: None,
12827 },
12828 ExcerptRange {
12829 context: Point::new(9, 0)..Point::new(10, 3),
12830 primary: None,
12831 },
12832 ],
12833 cx,
12834 );
12835 multibuffer.push_excerpts(
12836 buffer_3.clone(),
12837 [
12838 ExcerptRange {
12839 context: Point::new(0, 0)..Point::new(3, 0),
12840 primary: None,
12841 },
12842 ExcerptRange {
12843 context: Point::new(5, 0)..Point::new(7, 0),
12844 primary: None,
12845 },
12846 ExcerptRange {
12847 context: Point::new(9, 0)..Point::new(10, 3),
12848 primary: None,
12849 },
12850 ],
12851 cx,
12852 );
12853 multibuffer
12854 });
12855
12856 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12857 editor
12858 .update(cx, |editor, cx| {
12859 for (buffer, diff_base) in [
12860 (buffer_1.clone(), file_1_old),
12861 (buffer_2.clone(), file_2_old),
12862 (buffer_3.clone(), file_3_old),
12863 ] {
12864 let change_set = cx.new_model(|cx| {
12865 BufferChangeSet::new_with_base_text(diff_base.to_string(), &buffer, cx)
12866 });
12867 editor
12868 .buffer
12869 .update(cx, |buffer, cx| buffer.add_change_set(change_set, cx));
12870 }
12871 })
12872 .unwrap();
12873
12874 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12875 cx.run_until_parked();
12876
12877 cx.assert_editor_state(
12878 &"
12879 ˇaaa
12880 ccc
12881 ddd
12882
12883 ggg
12884 hhh
12885
12886
12887 lll
12888 mmm
12889 NNN
12890
12891 qqq
12892 rrr
12893
12894 uuu
12895 111
12896 222
12897 333
12898
12899 666
12900 777
12901
12902 000
12903 !!!"
12904 .unindent(),
12905 );
12906
12907 cx.update_editor(|editor, cx| {
12908 editor.select_all(&SelectAll, cx);
12909 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, cx);
12910 });
12911 cx.executor().run_until_parked();
12912
12913 cx.assert_state_with_diff(
12914 "
12915 «aaa
12916 - bbb
12917 ccc
12918 ddd
12919
12920 ggg
12921 hhh
12922
12923
12924 lll
12925 mmm
12926 - nnn
12927 + NNN
12928
12929 qqq
12930 rrr
12931
12932 uuu
12933 111
12934 222
12935 333
12936
12937 + 666
12938 777
12939
12940 000
12941 !!!ˇ»"
12942 .unindent(),
12943 );
12944}
12945
12946#[gpui::test]
12947async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext) {
12948 init_test(cx, |_| {});
12949
12950 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
12951 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
12952
12953 let buffer = cx.new_model(|cx| Buffer::local(text.to_string(), cx));
12954 let multi_buffer = cx.new_model(|cx| {
12955 let mut multibuffer = MultiBuffer::new(ReadWrite);
12956 multibuffer.push_excerpts(
12957 buffer.clone(),
12958 [
12959 ExcerptRange {
12960 context: Point::new(0, 0)..Point::new(2, 0),
12961 primary: None,
12962 },
12963 ExcerptRange {
12964 context: Point::new(5, 0)..Point::new(7, 0),
12965 primary: None,
12966 },
12967 ],
12968 cx,
12969 );
12970 multibuffer
12971 });
12972
12973 let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
12974 editor
12975 .update(cx, |editor, cx| {
12976 let change_set = cx
12977 .new_model(|cx| BufferChangeSet::new_with_base_text(base.to_string(), &buffer, cx));
12978 editor
12979 .buffer
12980 .update(cx, |buffer, cx| buffer.add_change_set(change_set, cx))
12981 })
12982 .unwrap();
12983
12984 let mut cx = EditorTestContext::for_editor(editor, cx).await;
12985 cx.run_until_parked();
12986
12987 cx.update_editor(|editor, cx| editor.expand_all_diff_hunks(&Default::default(), cx));
12988 cx.executor().run_until_parked();
12989
12990 cx.assert_state_with_diff(
12991 "
12992 ˇaaa
12993 - bbb
12994 + BBB
12995
12996 + EEE
12997 fff
12998 "
12999 .unindent(),
13000 );
13001}
13002
13003#[gpui::test]
13004async fn test_edits_around_expanded_insertion_hunks(
13005 executor: BackgroundExecutor,
13006 cx: &mut gpui::TestAppContext,
13007) {
13008 init_test(cx, |_| {});
13009
13010 let mut cx = EditorTestContext::new(cx).await;
13011
13012 let diff_base = r#"
13013 use some::mod1;
13014 use some::mod2;
13015
13016 const A: u32 = 42;
13017
13018 fn main() {
13019 println!("hello");
13020
13021 println!("world");
13022 }
13023 "#
13024 .unindent();
13025 executor.run_until_parked();
13026 cx.set_state(
13027 &r#"
13028 use some::mod1;
13029 use some::mod2;
13030
13031 const A: u32 = 42;
13032 const B: u32 = 42;
13033 const C: u32 = 42;
13034 ˇ
13035
13036 fn main() {
13037 println!("hello");
13038
13039 println!("world");
13040 }
13041 "#
13042 .unindent(),
13043 );
13044
13045 cx.set_diff_base(&diff_base);
13046 executor.run_until_parked();
13047
13048 cx.update_editor(|editor, cx| {
13049 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
13050 });
13051 executor.run_until_parked();
13052
13053 cx.assert_state_with_diff(
13054 r#"
13055 use some::mod1;
13056 use some::mod2;
13057
13058 const A: u32 = 42;
13059 + const B: u32 = 42;
13060 + const C: u32 = 42;
13061 + ˇ
13062
13063 fn main() {
13064 println!("hello");
13065
13066 println!("world");
13067 }
13068 "#
13069 .unindent(),
13070 );
13071
13072 cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
13073 executor.run_until_parked();
13074
13075 cx.assert_state_with_diff(
13076 r#"
13077 use some::mod1;
13078 use some::mod2;
13079
13080 const A: u32 = 42;
13081 + const B: u32 = 42;
13082 + const C: u32 = 42;
13083 + const D: u32 = 42;
13084 + ˇ
13085
13086 fn main() {
13087 println!("hello");
13088
13089 println!("world");
13090 }
13091 "#
13092 .unindent(),
13093 );
13094
13095 cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
13096 executor.run_until_parked();
13097
13098 cx.assert_state_with_diff(
13099 r#"
13100 use some::mod1;
13101 use some::mod2;
13102
13103 const A: u32 = 42;
13104 + const B: u32 = 42;
13105 + const C: u32 = 42;
13106 + const D: u32 = 42;
13107 + const E: u32 = 42;
13108 + ˇ
13109
13110 fn main() {
13111 println!("hello");
13112
13113 println!("world");
13114 }
13115 "#
13116 .unindent(),
13117 );
13118
13119 cx.update_editor(|editor, cx| {
13120 editor.delete_line(&DeleteLine, cx);
13121 });
13122 executor.run_until_parked();
13123
13124 cx.assert_state_with_diff(
13125 r#"
13126 use some::mod1;
13127 use some::mod2;
13128
13129 const A: u32 = 42;
13130 + const B: u32 = 42;
13131 + const C: u32 = 42;
13132 + const D: u32 = 42;
13133 + const E: u32 = 42;
13134 ˇ
13135 fn main() {
13136 println!("hello");
13137
13138 println!("world");
13139 }
13140 "#
13141 .unindent(),
13142 );
13143
13144 cx.update_editor(|editor, cx| {
13145 editor.move_up(&MoveUp, cx);
13146 editor.delete_line(&DeleteLine, cx);
13147 editor.move_up(&MoveUp, cx);
13148 editor.delete_line(&DeleteLine, cx);
13149 editor.move_up(&MoveUp, cx);
13150 editor.delete_line(&DeleteLine, cx);
13151 });
13152 executor.run_until_parked();
13153 cx.assert_state_with_diff(
13154 r#"
13155 use some::mod1;
13156 use some::mod2;
13157
13158 const A: u32 = 42;
13159 + const B: u32 = 42;
13160 ˇ
13161 fn main() {
13162 println!("hello");
13163
13164 println!("world");
13165 }
13166 "#
13167 .unindent(),
13168 );
13169
13170 cx.update_editor(|editor, cx| {
13171 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, cx);
13172 editor.delete_line(&DeleteLine, cx);
13173 });
13174 executor.run_until_parked();
13175 cx.assert_state_with_diff(
13176 r#"
13177 ˇ
13178 fn main() {
13179 println!("hello");
13180
13181 println!("world");
13182 }
13183 "#
13184 .unindent(),
13185 );
13186}
13187
13188#[gpui::test]
13189async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
13190 init_test(cx, |_| {});
13191
13192 let mut cx = EditorTestContext::new(cx).await;
13193 cx.set_diff_base(indoc! { "
13194 one
13195 two
13196 three
13197 four
13198 five
13199 "
13200 });
13201 cx.set_state(indoc! { "
13202 one
13203 ˇthree
13204 five
13205 "});
13206 cx.run_until_parked();
13207 cx.update_editor(|editor, cx| {
13208 editor.toggle_selected_diff_hunks(&Default::default(), cx);
13209 });
13210 cx.assert_state_with_diff(
13211 indoc! { "
13212 one
13213 - two
13214 ˇthree
13215 - four
13216 five
13217 "}
13218 .to_string(),
13219 );
13220 cx.update_editor(|editor, cx| {
13221 editor.toggle_selected_diff_hunks(&Default::default(), cx);
13222 });
13223
13224 cx.assert_state_with_diff(
13225 indoc! { "
13226 one
13227 ˇthree
13228 five
13229 "}
13230 .to_string(),
13231 );
13232}
13233
13234#[gpui::test]
13235async fn test_edits_around_expanded_deletion_hunks(
13236 executor: BackgroundExecutor,
13237 cx: &mut gpui::TestAppContext,
13238) {
13239 init_test(cx, |_| {});
13240
13241 let mut cx = EditorTestContext::new(cx).await;
13242
13243 let diff_base = r#"
13244 use some::mod1;
13245 use some::mod2;
13246
13247 const A: u32 = 42;
13248 const B: u32 = 42;
13249 const C: u32 = 42;
13250
13251
13252 fn main() {
13253 println!("hello");
13254
13255 println!("world");
13256 }
13257 "#
13258 .unindent();
13259 executor.run_until_parked();
13260 cx.set_state(
13261 &r#"
13262 use some::mod1;
13263 use some::mod2;
13264
13265 ˇconst B: u32 = 42;
13266 const C: u32 = 42;
13267
13268
13269 fn main() {
13270 println!("hello");
13271
13272 println!("world");
13273 }
13274 "#
13275 .unindent(),
13276 );
13277
13278 cx.set_diff_base(&diff_base);
13279 executor.run_until_parked();
13280
13281 cx.update_editor(|editor, cx| {
13282 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
13283 });
13284 executor.run_until_parked();
13285
13286 cx.assert_state_with_diff(
13287 r#"
13288 use some::mod1;
13289 use some::mod2;
13290
13291 - const A: u32 = 42;
13292 ˇconst B: u32 = 42;
13293 const C: u32 = 42;
13294
13295
13296 fn main() {
13297 println!("hello");
13298
13299 println!("world");
13300 }
13301 "#
13302 .unindent(),
13303 );
13304
13305 cx.update_editor(|editor, cx| {
13306 editor.delete_line(&DeleteLine, cx);
13307 });
13308 executor.run_until_parked();
13309 cx.assert_state_with_diff(
13310 r#"
13311 use some::mod1;
13312 use some::mod2;
13313
13314 - const A: u32 = 42;
13315 - const B: u32 = 42;
13316 ˇconst C: u32 = 42;
13317
13318
13319 fn main() {
13320 println!("hello");
13321
13322 println!("world");
13323 }
13324 "#
13325 .unindent(),
13326 );
13327
13328 cx.update_editor(|editor, cx| {
13329 editor.delete_line(&DeleteLine, cx);
13330 });
13331 executor.run_until_parked();
13332 cx.assert_state_with_diff(
13333 r#"
13334 use some::mod1;
13335 use some::mod2;
13336
13337 - const A: u32 = 42;
13338 - const B: u32 = 42;
13339 - const C: u32 = 42;
13340 ˇ
13341
13342 fn main() {
13343 println!("hello");
13344
13345 println!("world");
13346 }
13347 "#
13348 .unindent(),
13349 );
13350
13351 cx.update_editor(|editor, cx| {
13352 editor.handle_input("replacement", cx);
13353 });
13354 executor.run_until_parked();
13355 cx.assert_state_with_diff(
13356 r#"
13357 use some::mod1;
13358 use some::mod2;
13359
13360 - const A: u32 = 42;
13361 - const B: u32 = 42;
13362 - const C: u32 = 42;
13363 -
13364 + replacementˇ
13365
13366 fn main() {
13367 println!("hello");
13368
13369 println!("world");
13370 }
13371 "#
13372 .unindent(),
13373 );
13374}
13375
13376#[gpui::test]
13377async fn test_backspace_after_deletion_hunk(
13378 executor: BackgroundExecutor,
13379 cx: &mut gpui::TestAppContext,
13380) {
13381 init_test(cx, |_| {});
13382
13383 let mut cx = EditorTestContext::new(cx).await;
13384
13385 let base_text = r#"
13386 one
13387 two
13388 three
13389 four
13390 five
13391 "#
13392 .unindent();
13393 executor.run_until_parked();
13394 cx.set_state(
13395 &r#"
13396 one
13397 two
13398 fˇour
13399 five
13400 "#
13401 .unindent(),
13402 );
13403
13404 cx.set_diff_base(&base_text);
13405 executor.run_until_parked();
13406
13407 cx.update_editor(|editor, cx| {
13408 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
13409 });
13410 executor.run_until_parked();
13411
13412 cx.assert_state_with_diff(
13413 r#"
13414 one
13415 two
13416 - three
13417 fˇour
13418 five
13419 "#
13420 .unindent(),
13421 );
13422
13423 cx.update_editor(|editor, cx| {
13424 editor.backspace(&Backspace, cx);
13425 editor.backspace(&Backspace, cx);
13426 });
13427 executor.run_until_parked();
13428 cx.assert_state_with_diff(
13429 r#"
13430 one
13431 two
13432 - threeˇ
13433 - four
13434 + our
13435 five
13436 "#
13437 .unindent(),
13438 );
13439}
13440
13441#[gpui::test]
13442async fn test_edit_after_expanded_modification_hunk(
13443 executor: BackgroundExecutor,
13444 cx: &mut gpui::TestAppContext,
13445) {
13446 init_test(cx, |_| {});
13447
13448 let mut cx = EditorTestContext::new(cx).await;
13449
13450 let diff_base = r#"
13451 use some::mod1;
13452 use some::mod2;
13453
13454 const A: u32 = 42;
13455 const B: u32 = 42;
13456 const C: u32 = 42;
13457 const D: u32 = 42;
13458
13459
13460 fn main() {
13461 println!("hello");
13462
13463 println!("world");
13464 }"#
13465 .unindent();
13466
13467 cx.set_state(
13468 &r#"
13469 use some::mod1;
13470 use some::mod2;
13471
13472 const A: u32 = 42;
13473 const B: u32 = 42;
13474 const C: u32 = 43ˇ
13475 const D: u32 = 42;
13476
13477
13478 fn main() {
13479 println!("hello");
13480
13481 println!("world");
13482 }"#
13483 .unindent(),
13484 );
13485
13486 cx.set_diff_base(&diff_base);
13487 executor.run_until_parked();
13488 cx.update_editor(|editor, cx| {
13489 editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx);
13490 });
13491 executor.run_until_parked();
13492
13493 cx.assert_state_with_diff(
13494 r#"
13495 use some::mod1;
13496 use some::mod2;
13497
13498 const A: u32 = 42;
13499 const B: u32 = 42;
13500 - const C: u32 = 42;
13501 + const C: u32 = 43ˇ
13502 const D: u32 = 42;
13503
13504
13505 fn main() {
13506 println!("hello");
13507
13508 println!("world");
13509 }"#
13510 .unindent(),
13511 );
13512
13513 cx.update_editor(|editor, cx| {
13514 editor.handle_input("\nnew_line\n", cx);
13515 });
13516 executor.run_until_parked();
13517
13518 cx.assert_state_with_diff(
13519 r#"
13520 use some::mod1;
13521 use some::mod2;
13522
13523 const A: u32 = 42;
13524 const B: u32 = 42;
13525 - const C: u32 = 42;
13526 + const C: u32 = 43
13527 + new_line
13528 + ˇ
13529 const D: u32 = 42;
13530
13531
13532 fn main() {
13533 println!("hello");
13534
13535 println!("world");
13536 }"#
13537 .unindent(),
13538 );
13539}
13540
13541async fn setup_indent_guides_editor(
13542 text: &str,
13543 cx: &mut gpui::TestAppContext,
13544) -> (BufferId, EditorTestContext) {
13545 init_test(cx, |_| {});
13546
13547 let mut cx = EditorTestContext::new(cx).await;
13548
13549 let buffer_id = cx.update_editor(|editor, cx| {
13550 editor.set_text(text, cx);
13551 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
13552
13553 buffer_ids[0]
13554 });
13555
13556 (buffer_id, cx)
13557}
13558
13559fn assert_indent_guides(
13560 range: Range<u32>,
13561 expected: Vec<IndentGuide>,
13562 active_indices: Option<Vec<usize>>,
13563 cx: &mut EditorTestContext,
13564) {
13565 let indent_guides = cx.update_editor(|editor, cx| {
13566 let snapshot = editor.snapshot(cx).display_snapshot;
13567 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
13568 editor,
13569 MultiBufferRow(range.start)..MultiBufferRow(range.end),
13570 true,
13571 &snapshot,
13572 cx,
13573 );
13574
13575 indent_guides.sort_by(|a, b| {
13576 a.depth.cmp(&b.depth).then(
13577 a.start_row
13578 .cmp(&b.start_row)
13579 .then(a.end_row.cmp(&b.end_row)),
13580 )
13581 });
13582 indent_guides
13583 });
13584
13585 if let Some(expected) = active_indices {
13586 let active_indices = cx.update_editor(|editor, cx| {
13587 let snapshot = editor.snapshot(cx).display_snapshot;
13588 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, cx)
13589 });
13590
13591 assert_eq!(
13592 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
13593 expected,
13594 "Active indent guide indices do not match"
13595 );
13596 }
13597
13598 assert_eq!(indent_guides, expected, "Indent guides do not match");
13599}
13600
13601fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
13602 IndentGuide {
13603 buffer_id,
13604 start_row: MultiBufferRow(start_row),
13605 end_row: MultiBufferRow(end_row),
13606 depth,
13607 tab_size: 4,
13608 settings: IndentGuideSettings {
13609 enabled: true,
13610 line_width: 1,
13611 active_line_width: 1,
13612 ..Default::default()
13613 },
13614 }
13615}
13616
13617#[gpui::test]
13618async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13619 let (buffer_id, mut cx) = setup_indent_guides_editor(
13620 &"
13621 fn main() {
13622 let a = 1;
13623 }"
13624 .unindent(),
13625 cx,
13626 )
13627 .await;
13628
13629 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13630}
13631
13632#[gpui::test]
13633async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
13634 let (buffer_id, mut cx) = setup_indent_guides_editor(
13635 &"
13636 fn main() {
13637 let a = 1;
13638 let b = 2;
13639 }"
13640 .unindent(),
13641 cx,
13642 )
13643 .await;
13644
13645 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
13646}
13647
13648#[gpui::test]
13649async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
13650 let (buffer_id, mut cx) = setup_indent_guides_editor(
13651 &"
13652 fn main() {
13653 let a = 1;
13654 if a == 3 {
13655 let b = 2;
13656 } else {
13657 let c = 3;
13658 }
13659 }"
13660 .unindent(),
13661 cx,
13662 )
13663 .await;
13664
13665 assert_indent_guides(
13666 0..8,
13667 vec![
13668 indent_guide(buffer_id, 1, 6, 0),
13669 indent_guide(buffer_id, 3, 3, 1),
13670 indent_guide(buffer_id, 5, 5, 1),
13671 ],
13672 None,
13673 &mut cx,
13674 );
13675}
13676
13677#[gpui::test]
13678async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
13679 let (buffer_id, mut cx) = setup_indent_guides_editor(
13680 &"
13681 fn main() {
13682 let a = 1;
13683 let b = 2;
13684 let c = 3;
13685 }"
13686 .unindent(),
13687 cx,
13688 )
13689 .await;
13690
13691 assert_indent_guides(
13692 0..5,
13693 vec![
13694 indent_guide(buffer_id, 1, 3, 0),
13695 indent_guide(buffer_id, 2, 2, 1),
13696 ],
13697 None,
13698 &mut cx,
13699 );
13700}
13701
13702#[gpui::test]
13703async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext) {
13704 let (buffer_id, mut cx) = setup_indent_guides_editor(
13705 &"
13706 fn main() {
13707 let a = 1;
13708
13709 let c = 3;
13710 }"
13711 .unindent(),
13712 cx,
13713 )
13714 .await;
13715
13716 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
13717}
13718
13719#[gpui::test]
13720async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
13721 let (buffer_id, mut cx) = setup_indent_guides_editor(
13722 &"
13723 fn main() {
13724 let a = 1;
13725
13726 let c = 3;
13727
13728 if a == 3 {
13729 let b = 2;
13730 } else {
13731 let c = 3;
13732 }
13733 }"
13734 .unindent(),
13735 cx,
13736 )
13737 .await;
13738
13739 assert_indent_guides(
13740 0..11,
13741 vec![
13742 indent_guide(buffer_id, 1, 9, 0),
13743 indent_guide(buffer_id, 6, 6, 1),
13744 indent_guide(buffer_id, 8, 8, 1),
13745 ],
13746 None,
13747 &mut cx,
13748 );
13749}
13750
13751#[gpui::test]
13752async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
13753 let (buffer_id, mut cx) = setup_indent_guides_editor(
13754 &"
13755 fn main() {
13756 let a = 1;
13757
13758 let c = 3;
13759
13760 if a == 3 {
13761 let b = 2;
13762 } else {
13763 let c = 3;
13764 }
13765 }"
13766 .unindent(),
13767 cx,
13768 )
13769 .await;
13770
13771 assert_indent_guides(
13772 1..11,
13773 vec![
13774 indent_guide(buffer_id, 1, 9, 0),
13775 indent_guide(buffer_id, 6, 6, 1),
13776 indent_guide(buffer_id, 8, 8, 1),
13777 ],
13778 None,
13779 &mut cx,
13780 );
13781}
13782
13783#[gpui::test]
13784async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
13785 let (buffer_id, mut cx) = setup_indent_guides_editor(
13786 &"
13787 fn main() {
13788 let a = 1;
13789
13790 let c = 3;
13791
13792 if a == 3 {
13793 let b = 2;
13794 } else {
13795 let c = 3;
13796 }
13797 }"
13798 .unindent(),
13799 cx,
13800 )
13801 .await;
13802
13803 assert_indent_guides(
13804 1..10,
13805 vec![
13806 indent_guide(buffer_id, 1, 9, 0),
13807 indent_guide(buffer_id, 6, 6, 1),
13808 indent_guide(buffer_id, 8, 8, 1),
13809 ],
13810 None,
13811 &mut cx,
13812 );
13813}
13814
13815#[gpui::test]
13816async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
13817 let (buffer_id, mut cx) = setup_indent_guides_editor(
13818 &"
13819 block1
13820 block2
13821 block3
13822 block4
13823 block2
13824 block1
13825 block1"
13826 .unindent(),
13827 cx,
13828 )
13829 .await;
13830
13831 assert_indent_guides(
13832 1..10,
13833 vec![
13834 indent_guide(buffer_id, 1, 4, 0),
13835 indent_guide(buffer_id, 2, 3, 1),
13836 indent_guide(buffer_id, 3, 3, 2),
13837 ],
13838 None,
13839 &mut cx,
13840 );
13841}
13842
13843#[gpui::test]
13844async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext) {
13845 let (buffer_id, mut cx) = setup_indent_guides_editor(
13846 &"
13847 block1
13848 block2
13849 block3
13850
13851 block1
13852 block1"
13853 .unindent(),
13854 cx,
13855 )
13856 .await;
13857
13858 assert_indent_guides(
13859 0..6,
13860 vec![
13861 indent_guide(buffer_id, 1, 2, 0),
13862 indent_guide(buffer_id, 2, 2, 1),
13863 ],
13864 None,
13865 &mut cx,
13866 );
13867}
13868
13869#[gpui::test]
13870async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext) {
13871 let (buffer_id, mut cx) = setup_indent_guides_editor(
13872 &"
13873 block1
13874
13875
13876
13877 block2
13878 "
13879 .unindent(),
13880 cx,
13881 )
13882 .await;
13883
13884 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
13885}
13886
13887#[gpui::test]
13888async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
13889 let (buffer_id, mut cx) = setup_indent_guides_editor(
13890 &"
13891 def a:
13892 \tb = 3
13893 \tif True:
13894 \t\tc = 4
13895 \t\td = 5
13896 \tprint(b)
13897 "
13898 .unindent(),
13899 cx,
13900 )
13901 .await;
13902
13903 assert_indent_guides(
13904 0..6,
13905 vec![
13906 indent_guide(buffer_id, 1, 6, 0),
13907 indent_guide(buffer_id, 3, 4, 1),
13908 ],
13909 None,
13910 &mut cx,
13911 );
13912}
13913
13914#[gpui::test]
13915async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
13916 let (buffer_id, mut cx) = setup_indent_guides_editor(
13917 &"
13918 fn main() {
13919 let a = 1;
13920 }"
13921 .unindent(),
13922 cx,
13923 )
13924 .await;
13925
13926 cx.update_editor(|editor, cx| {
13927 editor.change_selections(None, cx, |s| {
13928 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13929 });
13930 });
13931
13932 assert_indent_guides(
13933 0..3,
13934 vec![indent_guide(buffer_id, 1, 1, 0)],
13935 Some(vec![0]),
13936 &mut cx,
13937 );
13938}
13939
13940#[gpui::test]
13941async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppContext) {
13942 let (buffer_id, mut cx) = setup_indent_guides_editor(
13943 &"
13944 fn main() {
13945 if 1 == 2 {
13946 let a = 1;
13947 }
13948 }"
13949 .unindent(),
13950 cx,
13951 )
13952 .await;
13953
13954 cx.update_editor(|editor, cx| {
13955 editor.change_selections(None, cx, |s| {
13956 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
13957 });
13958 });
13959
13960 assert_indent_guides(
13961 0..4,
13962 vec![
13963 indent_guide(buffer_id, 1, 3, 0),
13964 indent_guide(buffer_id, 2, 2, 1),
13965 ],
13966 Some(vec![1]),
13967 &mut cx,
13968 );
13969
13970 cx.update_editor(|editor, cx| {
13971 editor.change_selections(None, cx, |s| {
13972 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
13973 });
13974 });
13975
13976 assert_indent_guides(
13977 0..4,
13978 vec![
13979 indent_guide(buffer_id, 1, 3, 0),
13980 indent_guide(buffer_id, 2, 2, 1),
13981 ],
13982 Some(vec![1]),
13983 &mut cx,
13984 );
13985
13986 cx.update_editor(|editor, cx| {
13987 editor.change_selections(None, cx, |s| {
13988 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
13989 });
13990 });
13991
13992 assert_indent_guides(
13993 0..4,
13994 vec![
13995 indent_guide(buffer_id, 1, 3, 0),
13996 indent_guide(buffer_id, 2, 2, 1),
13997 ],
13998 Some(vec![0]),
13999 &mut cx,
14000 );
14001}
14002
14003#[gpui::test]
14004async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
14005 let (buffer_id, mut cx) = setup_indent_guides_editor(
14006 &"
14007 fn main() {
14008 let a = 1;
14009
14010 let b = 2;
14011 }"
14012 .unindent(),
14013 cx,
14014 )
14015 .await;
14016
14017 cx.update_editor(|editor, cx| {
14018 editor.change_selections(None, cx, |s| {
14019 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
14020 });
14021 });
14022
14023 assert_indent_guides(
14024 0..5,
14025 vec![indent_guide(buffer_id, 1, 3, 0)],
14026 Some(vec![0]),
14027 &mut cx,
14028 );
14029}
14030
14031#[gpui::test]
14032async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppContext) {
14033 let (buffer_id, mut cx) = setup_indent_guides_editor(
14034 &"
14035 def m:
14036 a = 1
14037 pass"
14038 .unindent(),
14039 cx,
14040 )
14041 .await;
14042
14043 cx.update_editor(|editor, cx| {
14044 editor.change_selections(None, cx, |s| {
14045 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
14046 });
14047 });
14048
14049 assert_indent_guides(
14050 0..3,
14051 vec![indent_guide(buffer_id, 1, 2, 0)],
14052 Some(vec![0]),
14053 &mut cx,
14054 );
14055}
14056
14057#[gpui::test]
14058async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut gpui::TestAppContext) {
14059 init_test(cx, |_| {});
14060 let mut cx = EditorTestContext::new(cx).await;
14061 let text = indoc! {
14062 "
14063 impl A {
14064 fn b() {
14065 0;
14066 3;
14067 5;
14068 6;
14069 7;
14070 }
14071 }
14072 "
14073 };
14074 let base_text = indoc! {
14075 "
14076 impl A {
14077 fn b() {
14078 0;
14079 1;
14080 2;
14081 3;
14082 4;
14083 }
14084 fn c() {
14085 5;
14086 6;
14087 7;
14088 }
14089 }
14090 "
14091 };
14092
14093 cx.update_editor(|editor, cx| {
14094 editor.set_text(text, cx);
14095
14096 editor.buffer().update(cx, |multibuffer, cx| {
14097 let buffer = multibuffer.as_singleton().unwrap();
14098 let change_set = cx.new_model(|cx| {
14099 let mut change_set = BufferChangeSet::new(&buffer, cx);
14100 change_set.recalculate_diff_sync(
14101 base_text.into(),
14102 buffer.read(cx).text_snapshot(),
14103 true,
14104 cx,
14105 );
14106 change_set
14107 });
14108
14109 multibuffer.set_all_diff_hunks_expanded(cx);
14110 multibuffer.add_change_set(change_set, cx);
14111
14112 buffer.read(cx).remote_id()
14113 })
14114 });
14115
14116 cx.assert_state_with_diff(
14117 indoc! { "
14118 impl A {
14119 fn b() {
14120 0;
14121 - 1;
14122 - 2;
14123 3;
14124 - 4;
14125 - }
14126 - fn c() {
14127 5;
14128 6;
14129 7;
14130 }
14131 }
14132 ˇ"
14133 }
14134 .to_string(),
14135 );
14136
14137 let mut actual_guides = cx.update_editor(|editor, cx| {
14138 editor
14139 .snapshot(cx)
14140 .buffer_snapshot
14141 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
14142 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
14143 .collect::<Vec<_>>()
14144 });
14145 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
14146 assert_eq!(
14147 actual_guides,
14148 vec![
14149 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
14150 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
14151 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
14152 ]
14153 );
14154}
14155
14156#[gpui::test]
14157fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
14158 init_test(cx, |_| {});
14159
14160 let editor = cx.add_window(|cx| {
14161 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
14162 build_editor(buffer, cx)
14163 });
14164
14165 let render_args = Arc::new(Mutex::new(None));
14166 let snapshot = editor
14167 .update(cx, |editor, cx| {
14168 let snapshot = editor.buffer().read(cx).snapshot(cx);
14169 let range =
14170 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
14171
14172 struct RenderArgs {
14173 row: MultiBufferRow,
14174 folded: bool,
14175 callback: Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
14176 }
14177
14178 let crease = Crease::inline(
14179 range,
14180 FoldPlaceholder::test(),
14181 {
14182 let toggle_callback = render_args.clone();
14183 move |row, folded, callback, _cx| {
14184 *toggle_callback.lock() = Some(RenderArgs {
14185 row,
14186 folded,
14187 callback,
14188 });
14189 div()
14190 }
14191 },
14192 |_row, _folded, _cx| div(),
14193 );
14194
14195 editor.insert_creases(Some(crease), cx);
14196 let snapshot = editor.snapshot(cx);
14197 let _div =
14198 snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.view().clone(), cx);
14199 snapshot
14200 })
14201 .unwrap();
14202
14203 let render_args = render_args.lock().take().unwrap();
14204 assert_eq!(render_args.row, MultiBufferRow(1));
14205 assert!(!render_args.folded);
14206 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
14207
14208 cx.update_window(*editor, |_, cx| (render_args.callback)(true, cx))
14209 .unwrap();
14210 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
14211 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
14212
14213 cx.update_window(*editor, |_, cx| (render_args.callback)(false, cx))
14214 .unwrap();
14215 let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
14216 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
14217}
14218
14219#[gpui::test]
14220async fn test_input_text(cx: &mut gpui::TestAppContext) {
14221 init_test(cx, |_| {});
14222 let mut cx = EditorTestContext::new(cx).await;
14223
14224 cx.set_state(
14225 &r#"ˇone
14226 two
14227
14228 three
14229 fourˇ
14230 five
14231
14232 siˇx"#
14233 .unindent(),
14234 );
14235
14236 cx.dispatch_action(HandleInput(String::new()));
14237 cx.assert_editor_state(
14238 &r#"ˇone
14239 two
14240
14241 three
14242 fourˇ
14243 five
14244
14245 siˇx"#
14246 .unindent(),
14247 );
14248
14249 cx.dispatch_action(HandleInput("AAAA".to_string()));
14250 cx.assert_editor_state(
14251 &r#"AAAAˇone
14252 two
14253
14254 three
14255 fourAAAAˇ
14256 five
14257
14258 siAAAAˇx"#
14259 .unindent(),
14260 );
14261}
14262
14263#[gpui::test]
14264async fn test_scroll_cursor_center_top_bottom(cx: &mut gpui::TestAppContext) {
14265 init_test(cx, |_| {});
14266
14267 let mut cx = EditorTestContext::new(cx).await;
14268 cx.set_state(
14269 r#"let foo = 1;
14270let foo = 2;
14271let foo = 3;
14272let fooˇ = 4;
14273let foo = 5;
14274let foo = 6;
14275let foo = 7;
14276let foo = 8;
14277let foo = 9;
14278let foo = 10;
14279let foo = 11;
14280let foo = 12;
14281let foo = 13;
14282let foo = 14;
14283let foo = 15;"#,
14284 );
14285
14286 cx.update_editor(|e, cx| {
14287 assert_eq!(
14288 e.next_scroll_position,
14289 NextScrollCursorCenterTopBottom::Center,
14290 "Default next scroll direction is center",
14291 );
14292
14293 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
14294 assert_eq!(
14295 e.next_scroll_position,
14296 NextScrollCursorCenterTopBottom::Top,
14297 "After center, next scroll direction should be top",
14298 );
14299
14300 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
14301 assert_eq!(
14302 e.next_scroll_position,
14303 NextScrollCursorCenterTopBottom::Bottom,
14304 "After top, next scroll direction should be bottom",
14305 );
14306
14307 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
14308 assert_eq!(
14309 e.next_scroll_position,
14310 NextScrollCursorCenterTopBottom::Center,
14311 "After bottom, scrolling should start over",
14312 );
14313
14314 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, cx);
14315 assert_eq!(
14316 e.next_scroll_position,
14317 NextScrollCursorCenterTopBottom::Top,
14318 "Scrolling continues if retriggered fast enough"
14319 );
14320 });
14321
14322 cx.executor()
14323 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
14324 cx.executor().run_until_parked();
14325 cx.update_editor(|e, _| {
14326 assert_eq!(
14327 e.next_scroll_position,
14328 NextScrollCursorCenterTopBottom::Center,
14329 "If scrolling is not triggered fast enough, it should reset"
14330 );
14331 });
14332}
14333
14334#[gpui::test]
14335async fn test_goto_definition_with_find_all_references_fallback(cx: &mut gpui::TestAppContext) {
14336 init_test(cx, |_| {});
14337 let mut cx = EditorLspTestContext::new_rust(
14338 lsp::ServerCapabilities {
14339 definition_provider: Some(lsp::OneOf::Left(true)),
14340 references_provider: Some(lsp::OneOf::Left(true)),
14341 ..lsp::ServerCapabilities::default()
14342 },
14343 cx,
14344 )
14345 .await;
14346
14347 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
14348 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
14349 move |params, _| async move {
14350 if empty_go_to_definition {
14351 Ok(None)
14352 } else {
14353 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
14354 uri: params.text_document_position_params.text_document.uri,
14355 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
14356 })))
14357 }
14358 },
14359 );
14360 let references =
14361 cx.lsp
14362 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
14363 Ok(Some(vec![lsp::Location {
14364 uri: params.text_document_position.text_document.uri,
14365 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
14366 }]))
14367 });
14368 (go_to_definition, references)
14369 };
14370
14371 cx.set_state(
14372 &r#"fn one() {
14373 let mut a = ˇtwo();
14374 }
14375
14376 fn two() {}"#
14377 .unindent(),
14378 );
14379 set_up_lsp_handlers(false, &mut cx);
14380 let navigated = cx
14381 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
14382 .await
14383 .expect("Failed to navigate to definition");
14384 assert_eq!(
14385 navigated,
14386 Navigated::Yes,
14387 "Should have navigated to definition from the GetDefinition response"
14388 );
14389 cx.assert_editor_state(
14390 &r#"fn one() {
14391 let mut a = two();
14392 }
14393
14394 fn «twoˇ»() {}"#
14395 .unindent(),
14396 );
14397
14398 let editors = cx.update_workspace(|workspace, cx| {
14399 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
14400 });
14401 cx.update_editor(|_, test_editor_cx| {
14402 assert_eq!(
14403 editors.len(),
14404 1,
14405 "Initially, only one, test, editor should be open in the workspace"
14406 );
14407 assert_eq!(
14408 test_editor_cx.view(),
14409 editors.last().expect("Asserted len is 1")
14410 );
14411 });
14412
14413 set_up_lsp_handlers(true, &mut cx);
14414 let navigated = cx
14415 .update_editor(|editor, cx| editor.go_to_definition(&GoToDefinition, cx))
14416 .await
14417 .expect("Failed to navigate to lookup references");
14418 assert_eq!(
14419 navigated,
14420 Navigated::Yes,
14421 "Should have navigated to references as a fallback after empty GoToDefinition response"
14422 );
14423 // We should not change the selections in the existing file,
14424 // if opening another milti buffer with the references
14425 cx.assert_editor_state(
14426 &r#"fn one() {
14427 let mut a = two();
14428 }
14429
14430 fn «twoˇ»() {}"#
14431 .unindent(),
14432 );
14433 let editors = cx.update_workspace(|workspace, cx| {
14434 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
14435 });
14436 cx.update_editor(|_, test_editor_cx| {
14437 assert_eq!(
14438 editors.len(),
14439 2,
14440 "After falling back to references search, we open a new editor with the results"
14441 );
14442 let references_fallback_text = editors
14443 .into_iter()
14444 .find(|new_editor| new_editor != test_editor_cx.view())
14445 .expect("Should have one non-test editor now")
14446 .read(test_editor_cx)
14447 .text(test_editor_cx);
14448 assert_eq!(
14449 references_fallback_text, "fn one() {\n let mut a = two();\n}",
14450 "Should use the range from the references response and not the GoToDefinition one"
14451 );
14452 });
14453}
14454
14455#[gpui::test]
14456async fn test_find_enclosing_node_with_task(cx: &mut gpui::TestAppContext) {
14457 init_test(cx, |_| {});
14458
14459 let language = Arc::new(Language::new(
14460 LanguageConfig::default(),
14461 Some(tree_sitter_rust::LANGUAGE.into()),
14462 ));
14463
14464 let text = r#"
14465 #[cfg(test)]
14466 mod tests() {
14467 #[test]
14468 fn runnable_1() {
14469 let a = 1;
14470 }
14471
14472 #[test]
14473 fn runnable_2() {
14474 let a = 1;
14475 let b = 2;
14476 }
14477 }
14478 "#
14479 .unindent();
14480
14481 let fs = FakeFs::new(cx.executor());
14482 fs.insert_file("/file.rs", Default::default()).await;
14483
14484 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14485 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
14486 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14487 let buffer = cx.new_model(|cx| Buffer::local(text, cx).with_language(language, cx));
14488 let multi_buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx));
14489
14490 let editor = cx.new_view(|cx| {
14491 Editor::new(
14492 EditorMode::Full,
14493 multi_buffer,
14494 Some(project.clone()),
14495 true,
14496 cx,
14497 )
14498 });
14499
14500 editor.update(cx, |editor, cx| {
14501 editor.tasks.insert(
14502 (buffer.read(cx).remote_id(), 3),
14503 RunnableTasks {
14504 templates: vec![],
14505 offset: MultiBufferOffset(43),
14506 column: 0,
14507 extra_variables: HashMap::default(),
14508 context_range: BufferOffset(43)..BufferOffset(85),
14509 },
14510 );
14511 editor.tasks.insert(
14512 (buffer.read(cx).remote_id(), 8),
14513 RunnableTasks {
14514 templates: vec![],
14515 offset: MultiBufferOffset(86),
14516 column: 0,
14517 extra_variables: HashMap::default(),
14518 context_range: BufferOffset(86)..BufferOffset(191),
14519 },
14520 );
14521
14522 // Test finding task when cursor is inside function body
14523 editor.change_selections(None, cx, |s| {
14524 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
14525 });
14526 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
14527 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
14528
14529 // Test finding task when cursor is on function name
14530 editor.change_selections(None, cx, |s| {
14531 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
14532 });
14533 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
14534 assert_eq!(row, 8, "Should find task when cursor is on function name");
14535 });
14536}
14537
14538#[gpui::test]
14539async fn test_multi_buffer_folding(cx: &mut gpui::TestAppContext) {
14540 init_test(cx, |_| {});
14541
14542 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
14543 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
14544 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
14545
14546 let fs = FakeFs::new(cx.executor());
14547 fs.insert_tree(
14548 "/a",
14549 json!({
14550 "first.rs": sample_text_1,
14551 "second.rs": sample_text_2,
14552 "third.rs": sample_text_3,
14553 }),
14554 )
14555 .await;
14556 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14557 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
14558 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14559 let worktree = project.update(cx, |project, cx| {
14560 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
14561 assert_eq!(worktrees.len(), 1);
14562 worktrees.pop().unwrap()
14563 });
14564 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
14565
14566 let buffer_1 = project
14567 .update(cx, |project, cx| {
14568 project.open_buffer((worktree_id, "first.rs"), cx)
14569 })
14570 .await
14571 .unwrap();
14572 let buffer_2 = project
14573 .update(cx, |project, cx| {
14574 project.open_buffer((worktree_id, "second.rs"), cx)
14575 })
14576 .await
14577 .unwrap();
14578 let buffer_3 = project
14579 .update(cx, |project, cx| {
14580 project.open_buffer((worktree_id, "third.rs"), cx)
14581 })
14582 .await
14583 .unwrap();
14584
14585 let multi_buffer = cx.new_model(|cx| {
14586 let mut multi_buffer = MultiBuffer::new(ReadWrite);
14587 multi_buffer.push_excerpts(
14588 buffer_1.clone(),
14589 [
14590 ExcerptRange {
14591 context: Point::new(0, 0)..Point::new(3, 0),
14592 primary: None,
14593 },
14594 ExcerptRange {
14595 context: Point::new(5, 0)..Point::new(7, 0),
14596 primary: None,
14597 },
14598 ExcerptRange {
14599 context: Point::new(9, 0)..Point::new(10, 4),
14600 primary: None,
14601 },
14602 ],
14603 cx,
14604 );
14605 multi_buffer.push_excerpts(
14606 buffer_2.clone(),
14607 [
14608 ExcerptRange {
14609 context: Point::new(0, 0)..Point::new(3, 0),
14610 primary: None,
14611 },
14612 ExcerptRange {
14613 context: Point::new(5, 0)..Point::new(7, 0),
14614 primary: None,
14615 },
14616 ExcerptRange {
14617 context: Point::new(9, 0)..Point::new(10, 4),
14618 primary: None,
14619 },
14620 ],
14621 cx,
14622 );
14623 multi_buffer.push_excerpts(
14624 buffer_3.clone(),
14625 [
14626 ExcerptRange {
14627 context: Point::new(0, 0)..Point::new(3, 0),
14628 primary: None,
14629 },
14630 ExcerptRange {
14631 context: Point::new(5, 0)..Point::new(7, 0),
14632 primary: None,
14633 },
14634 ExcerptRange {
14635 context: Point::new(9, 0)..Point::new(10, 4),
14636 primary: None,
14637 },
14638 ],
14639 cx,
14640 );
14641 multi_buffer
14642 });
14643 let multi_buffer_editor = cx.new_view(|cx| {
14644 Editor::new(
14645 EditorMode::Full,
14646 multi_buffer,
14647 Some(project.clone()),
14648 true,
14649 cx,
14650 )
14651 });
14652
14653 let full_text = "\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n";
14654 assert_eq!(
14655 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14656 full_text,
14657 );
14658
14659 multi_buffer_editor.update(cx, |editor, cx| {
14660 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
14661 });
14662 assert_eq!(
14663 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14664 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
14665 "After folding the first buffer, its text should not be displayed"
14666 );
14667
14668 multi_buffer_editor.update(cx, |editor, cx| {
14669 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
14670 });
14671 assert_eq!(
14672 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14673 "\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
14674 "After folding the second buffer, its text should not be displayed"
14675 );
14676
14677 multi_buffer_editor.update(cx, |editor, cx| {
14678 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
14679 });
14680 assert_eq!(
14681 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14682 "\n\n\n\n\n",
14683 "After folding the third buffer, its text should not be displayed"
14684 );
14685
14686 // Emulate selection inside the fold logic, that should work
14687 multi_buffer_editor.update(cx, |editor, cx| {
14688 editor.snapshot(cx).next_line_boundary(Point::new(0, 4));
14689 });
14690
14691 multi_buffer_editor.update(cx, |editor, cx| {
14692 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
14693 });
14694 assert_eq!(
14695 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14696 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
14697 "After unfolding the second buffer, its text should be displayed"
14698 );
14699
14700 multi_buffer_editor.update(cx, |editor, cx| {
14701 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
14702 });
14703 assert_eq!(
14704 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14705 "\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
14706 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
14707 );
14708
14709 multi_buffer_editor.update(cx, |editor, cx| {
14710 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
14711 });
14712 assert_eq!(
14713 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14714 full_text,
14715 "After unfolding the all buffers, all original text should be displayed"
14716 );
14717}
14718
14719#[gpui::test]
14720async fn test_multi_buffer_single_excerpts_folding(cx: &mut gpui::TestAppContext) {
14721 init_test(cx, |_| {});
14722
14723 let sample_text_1 = "1111\n2222\n3333".to_string();
14724 let sample_text_2 = "4444\n5555\n6666".to_string();
14725 let sample_text_3 = "7777\n8888\n9999".to_string();
14726
14727 let fs = FakeFs::new(cx.executor());
14728 fs.insert_tree(
14729 "/a",
14730 json!({
14731 "first.rs": sample_text_1,
14732 "second.rs": sample_text_2,
14733 "third.rs": sample_text_3,
14734 }),
14735 )
14736 .await;
14737 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14738 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
14739 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14740 let worktree = project.update(cx, |project, cx| {
14741 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
14742 assert_eq!(worktrees.len(), 1);
14743 worktrees.pop().unwrap()
14744 });
14745 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
14746
14747 let buffer_1 = project
14748 .update(cx, |project, cx| {
14749 project.open_buffer((worktree_id, "first.rs"), cx)
14750 })
14751 .await
14752 .unwrap();
14753 let buffer_2 = project
14754 .update(cx, |project, cx| {
14755 project.open_buffer((worktree_id, "second.rs"), cx)
14756 })
14757 .await
14758 .unwrap();
14759 let buffer_3 = project
14760 .update(cx, |project, cx| {
14761 project.open_buffer((worktree_id, "third.rs"), cx)
14762 })
14763 .await
14764 .unwrap();
14765
14766 let multi_buffer = cx.new_model(|cx| {
14767 let mut multi_buffer = MultiBuffer::new(ReadWrite);
14768 multi_buffer.push_excerpts(
14769 buffer_1.clone(),
14770 [ExcerptRange {
14771 context: Point::new(0, 0)..Point::new(3, 0),
14772 primary: None,
14773 }],
14774 cx,
14775 );
14776 multi_buffer.push_excerpts(
14777 buffer_2.clone(),
14778 [ExcerptRange {
14779 context: Point::new(0, 0)..Point::new(3, 0),
14780 primary: None,
14781 }],
14782 cx,
14783 );
14784 multi_buffer.push_excerpts(
14785 buffer_3.clone(),
14786 [ExcerptRange {
14787 context: Point::new(0, 0)..Point::new(3, 0),
14788 primary: None,
14789 }],
14790 cx,
14791 );
14792 multi_buffer
14793 });
14794
14795 let multi_buffer_editor = cx.new_view(|cx| {
14796 Editor::new(
14797 EditorMode::Full,
14798 multi_buffer,
14799 Some(project.clone()),
14800 true,
14801 cx,
14802 )
14803 });
14804
14805 let full_text = "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n";
14806 assert_eq!(
14807 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14808 full_text,
14809 );
14810
14811 multi_buffer_editor.update(cx, |editor, cx| {
14812 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
14813 });
14814 assert_eq!(
14815 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14816 "\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n",
14817 "After folding the first buffer, its text should not be displayed"
14818 );
14819
14820 multi_buffer_editor.update(cx, |editor, cx| {
14821 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
14822 });
14823
14824 assert_eq!(
14825 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14826 "\n\n\n\n\n\n\n7777\n8888\n9999\n",
14827 "After folding the second buffer, its text should not be displayed"
14828 );
14829
14830 multi_buffer_editor.update(cx, |editor, cx| {
14831 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
14832 });
14833 assert_eq!(
14834 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14835 "\n\n\n\n\n",
14836 "After folding the third buffer, its text should not be displayed"
14837 );
14838
14839 multi_buffer_editor.update(cx, |editor, cx| {
14840 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
14841 });
14842 assert_eq!(
14843 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14844 "\n\n\n\n\n4444\n5555\n6666\n\n\n",
14845 "After unfolding the second buffer, its text should be displayed"
14846 );
14847
14848 multi_buffer_editor.update(cx, |editor, cx| {
14849 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
14850 });
14851 assert_eq!(
14852 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14853 "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n",
14854 "After unfolding the first buffer, its text should be displayed"
14855 );
14856
14857 multi_buffer_editor.update(cx, |editor, cx| {
14858 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
14859 });
14860 assert_eq!(
14861 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14862 full_text,
14863 "After unfolding all buffers, all original text should be displayed"
14864 );
14865}
14866
14867#[gpui::test]
14868async fn test_multi_buffer_with_single_excerpt_folding(cx: &mut gpui::TestAppContext) {
14869 init_test(cx, |_| {});
14870
14871 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
14872
14873 let fs = FakeFs::new(cx.executor());
14874 fs.insert_tree(
14875 "/a",
14876 json!({
14877 "main.rs": sample_text,
14878 }),
14879 )
14880 .await;
14881 let project = Project::test(fs, ["/a".as_ref()], cx).await;
14882 let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
14883 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
14884 let worktree = project.update(cx, |project, cx| {
14885 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
14886 assert_eq!(worktrees.len(), 1);
14887 worktrees.pop().unwrap()
14888 });
14889 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
14890
14891 let buffer_1 = project
14892 .update(cx, |project, cx| {
14893 project.open_buffer((worktree_id, "main.rs"), cx)
14894 })
14895 .await
14896 .unwrap();
14897
14898 let multi_buffer = cx.new_model(|cx| {
14899 let mut multi_buffer = MultiBuffer::new(ReadWrite);
14900 multi_buffer.push_excerpts(
14901 buffer_1.clone(),
14902 [ExcerptRange {
14903 context: Point::new(0, 0)
14904 ..Point::new(
14905 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
14906 0,
14907 ),
14908 primary: None,
14909 }],
14910 cx,
14911 );
14912 multi_buffer
14913 });
14914 let multi_buffer_editor = cx.new_view(|cx| {
14915 Editor::new(
14916 EditorMode::Full,
14917 multi_buffer,
14918 Some(project.clone()),
14919 true,
14920 cx,
14921 )
14922 });
14923
14924 let selection_range = Point::new(1, 0)..Point::new(2, 0);
14925 multi_buffer_editor.update(cx, |editor, cx| {
14926 enum TestHighlight {}
14927 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
14928 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
14929 editor.highlight_text::<TestHighlight>(
14930 vec![highlight_range.clone()],
14931 HighlightStyle::color(Hsla::green()),
14932 cx,
14933 );
14934 editor.change_selections(None, cx, |s| s.select_ranges(Some(highlight_range)));
14935 });
14936
14937 let full_text = format!("\n\n\n{sample_text}\n");
14938 assert_eq!(
14939 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
14940 full_text,
14941 );
14942}
14943
14944#[gpui::test]
14945async fn test_inline_completion_text(cx: &mut TestAppContext) {
14946 init_test(cx, |_| {});
14947
14948 // Simple insertion
14949 assert_highlighted_edits(
14950 "Hello, world!",
14951 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
14952 true,
14953 cx,
14954 |highlighted_edits, cx| {
14955 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
14956 assert_eq!(highlighted_edits.highlights.len(), 1);
14957 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
14958 assert_eq!(
14959 highlighted_edits.highlights[0].1.background_color,
14960 Some(cx.theme().status().created_background)
14961 );
14962 },
14963 )
14964 .await;
14965
14966 // Replacement
14967 assert_highlighted_edits(
14968 "This is a test.",
14969 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
14970 false,
14971 cx,
14972 |highlighted_edits, cx| {
14973 assert_eq!(highlighted_edits.text, "That is a test.");
14974 assert_eq!(highlighted_edits.highlights.len(), 1);
14975 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
14976 assert_eq!(
14977 highlighted_edits.highlights[0].1.background_color,
14978 Some(cx.theme().status().created_background)
14979 );
14980 },
14981 )
14982 .await;
14983
14984 // Multiple edits
14985 assert_highlighted_edits(
14986 "Hello, world!",
14987 vec![
14988 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
14989 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
14990 ],
14991 false,
14992 cx,
14993 |highlighted_edits, cx| {
14994 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
14995 assert_eq!(highlighted_edits.highlights.len(), 2);
14996 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
14997 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
14998 assert_eq!(
14999 highlighted_edits.highlights[0].1.background_color,
15000 Some(cx.theme().status().created_background)
15001 );
15002 assert_eq!(
15003 highlighted_edits.highlights[1].1.background_color,
15004 Some(cx.theme().status().created_background)
15005 );
15006 },
15007 )
15008 .await;
15009
15010 // Multiple lines with edits
15011 assert_highlighted_edits(
15012 "First line\nSecond line\nThird line\nFourth line",
15013 vec![
15014 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
15015 (
15016 Point::new(2, 0)..Point::new(2, 10),
15017 "New third line".to_string(),
15018 ),
15019 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
15020 ],
15021 false,
15022 cx,
15023 |highlighted_edits, cx| {
15024 assert_eq!(
15025 highlighted_edits.text,
15026 "Second modified\nNew third line\nFourth updated line"
15027 );
15028 assert_eq!(highlighted_edits.highlights.len(), 3);
15029 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
15030 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
15031 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
15032 for highlight in &highlighted_edits.highlights {
15033 assert_eq!(
15034 highlight.1.background_color,
15035 Some(cx.theme().status().created_background)
15036 );
15037 }
15038 },
15039 )
15040 .await;
15041}
15042
15043#[gpui::test]
15044async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
15045 init_test(cx, |_| {});
15046
15047 // Deletion
15048 assert_highlighted_edits(
15049 "Hello, world!",
15050 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
15051 true,
15052 cx,
15053 |highlighted_edits, cx| {
15054 assert_eq!(highlighted_edits.text, "Hello, world!");
15055 assert_eq!(highlighted_edits.highlights.len(), 1);
15056 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
15057 assert_eq!(
15058 highlighted_edits.highlights[0].1.background_color,
15059 Some(cx.theme().status().deleted_background)
15060 );
15061 },
15062 )
15063 .await;
15064
15065 // Insertion
15066 assert_highlighted_edits(
15067 "Hello, world!",
15068 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
15069 true,
15070 cx,
15071 |highlighted_edits, cx| {
15072 assert_eq!(highlighted_edits.highlights.len(), 1);
15073 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
15074 assert_eq!(
15075 highlighted_edits.highlights[0].1.background_color,
15076 Some(cx.theme().status().created_background)
15077 );
15078 },
15079 )
15080 .await;
15081}
15082
15083async fn assert_highlighted_edits(
15084 text: &str,
15085 edits: Vec<(Range<Point>, String)>,
15086 include_deletions: bool,
15087 cx: &mut TestAppContext,
15088 assertion_fn: impl Fn(HighlightedEdits, &AppContext),
15089) {
15090 let window = cx.add_window(|cx| {
15091 let buffer = MultiBuffer::build_simple(text, cx);
15092 Editor::new(EditorMode::Full, buffer, None, true, cx)
15093 });
15094 let cx = &mut VisualTestContext::from_window(*window, cx);
15095
15096 let (buffer, snapshot) = window
15097 .update(cx, |editor, cx| {
15098 (
15099 editor.buffer().clone(),
15100 editor.buffer().read(cx).snapshot(cx),
15101 )
15102 })
15103 .unwrap();
15104
15105 let edits = edits
15106 .into_iter()
15107 .map(|(range, edit)| {
15108 (
15109 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
15110 edit,
15111 )
15112 })
15113 .collect::<Vec<_>>();
15114
15115 let text_anchor_edits = edits
15116 .clone()
15117 .into_iter()
15118 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
15119 .collect::<Vec<_>>();
15120
15121 let edit_preview = window
15122 .update(cx, |_, cx| {
15123 buffer
15124 .read(cx)
15125 .as_singleton()
15126 .unwrap()
15127 .read(cx)
15128 .preview_edits(text_anchor_edits.into(), cx)
15129 })
15130 .unwrap()
15131 .await;
15132
15133 cx.update(|cx| {
15134 let highlighted_edits = inline_completion_edit_text(
15135 &snapshot.as_singleton().unwrap().2,
15136 &edits,
15137 &edit_preview,
15138 include_deletions,
15139 cx,
15140 )
15141 .expect("Missing highlighted edits");
15142 assertion_fn(highlighted_edits, cx)
15143 });
15144}
15145
15146#[gpui::test]
15147async fn test_rename_with_duplicate_edits(cx: &mut gpui::TestAppContext) {
15148 init_test(cx, |_| {});
15149 let capabilities = lsp::ServerCapabilities {
15150 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
15151 prepare_provider: Some(true),
15152 work_done_progress_options: Default::default(),
15153 })),
15154 ..Default::default()
15155 };
15156 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
15157
15158 cx.set_state(indoc! {"
15159 struct Fˇoo {}
15160 "});
15161
15162 cx.update_editor(|editor, cx| {
15163 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
15164 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
15165 editor.highlight_background::<DocumentHighlightRead>(
15166 &[highlight_range],
15167 |c| c.editor_document_highlight_read_background,
15168 cx,
15169 );
15170 });
15171
15172 let mut prepare_rename_handler =
15173 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
15174 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
15175 start: lsp::Position {
15176 line: 0,
15177 character: 7,
15178 },
15179 end: lsp::Position {
15180 line: 0,
15181 character: 10,
15182 },
15183 })))
15184 });
15185 let prepare_rename_task = cx
15186 .update_editor(|e, cx| e.rename(&Rename, cx))
15187 .expect("Prepare rename was not started");
15188 prepare_rename_handler.next().await.unwrap();
15189 prepare_rename_task.await.expect("Prepare rename failed");
15190
15191 let mut rename_handler =
15192 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
15193 let edit = lsp::TextEdit {
15194 range: lsp::Range {
15195 start: lsp::Position {
15196 line: 0,
15197 character: 7,
15198 },
15199 end: lsp::Position {
15200 line: 0,
15201 character: 10,
15202 },
15203 },
15204 new_text: "FooRenamed".to_string(),
15205 };
15206 Ok(Some(lsp::WorkspaceEdit::new(
15207 // Specify the same edit twice
15208 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
15209 )))
15210 });
15211 let rename_task = cx
15212 .update_editor(|e, cx| e.confirm_rename(&ConfirmRename, cx))
15213 .expect("Confirm rename was not started");
15214 rename_handler.next().await.unwrap();
15215 rename_task.await.expect("Confirm rename failed");
15216 cx.run_until_parked();
15217
15218 // Despite two edits, only one is actually applied as those are identical
15219 cx.assert_editor_state(indoc! {"
15220 struct FooRenamedˇ {}
15221 "});
15222}
15223
15224#[gpui::test]
15225async fn test_rename_without_prepare(cx: &mut gpui::TestAppContext) {
15226 init_test(cx, |_| {});
15227 // These capabilities indicate that the server does not support prepare rename.
15228 let capabilities = lsp::ServerCapabilities {
15229 rename_provider: Some(lsp::OneOf::Left(true)),
15230 ..Default::default()
15231 };
15232 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
15233
15234 cx.set_state(indoc! {"
15235 struct Fˇoo {}
15236 "});
15237
15238 cx.update_editor(|editor, cx| {
15239 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
15240 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
15241 editor.highlight_background::<DocumentHighlightRead>(
15242 &[highlight_range],
15243 |c| c.editor_document_highlight_read_background,
15244 cx,
15245 );
15246 });
15247
15248 cx.update_editor(|e, cx| e.rename(&Rename, cx))
15249 .expect("Prepare rename was not started")
15250 .await
15251 .expect("Prepare rename failed");
15252
15253 let mut rename_handler =
15254 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
15255 let edit = lsp::TextEdit {
15256 range: lsp::Range {
15257 start: lsp::Position {
15258 line: 0,
15259 character: 7,
15260 },
15261 end: lsp::Position {
15262 line: 0,
15263 character: 10,
15264 },
15265 },
15266 new_text: "FooRenamed".to_string(),
15267 };
15268 Ok(Some(lsp::WorkspaceEdit::new(
15269 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
15270 )))
15271 });
15272 let rename_task = cx
15273 .update_editor(|e, cx| e.confirm_rename(&ConfirmRename, cx))
15274 .expect("Confirm rename was not started");
15275 rename_handler.next().await.unwrap();
15276 rename_task.await.expect("Confirm rename failed");
15277 cx.run_until_parked();
15278
15279 // Correct range is renamed, as `surrounding_word` is used to find it.
15280 cx.assert_editor_state(indoc! {"
15281 struct FooRenamedˇ {}
15282 "});
15283}
15284
15285fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
15286 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
15287 point..point
15288}
15289
15290fn assert_selection_ranges(marked_text: &str, view: &mut Editor, cx: &mut ViewContext<Editor>) {
15291 let (text, ranges) = marked_text_ranges(marked_text, true);
15292 assert_eq!(view.text(cx), text);
15293 assert_eq!(
15294 view.selections.ranges(cx),
15295 ranges,
15296 "Assert selections are {}",
15297 marked_text
15298 );
15299}
15300
15301pub fn handle_signature_help_request(
15302 cx: &mut EditorLspTestContext,
15303 mocked_response: lsp::SignatureHelp,
15304) -> impl Future<Output = ()> {
15305 let mut request =
15306 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
15307 let mocked_response = mocked_response.clone();
15308 async move { Ok(Some(mocked_response)) }
15309 });
15310
15311 async move {
15312 request.next().await;
15313 }
15314}
15315
15316/// Handle completion request passing a marked string specifying where the completion
15317/// should be triggered from using '|' character, what range should be replaced, and what completions
15318/// should be returned using '<' and '>' to delimit the range
15319pub fn handle_completion_request(
15320 cx: &mut EditorLspTestContext,
15321 marked_string: &str,
15322 completions: Vec<&'static str>,
15323 counter: Arc<AtomicUsize>,
15324) -> impl Future<Output = ()> {
15325 let complete_from_marker: TextRangeMarker = '|'.into();
15326 let replace_range_marker: TextRangeMarker = ('<', '>').into();
15327 let (_, mut marked_ranges) = marked_text_ranges_by(
15328 marked_string,
15329 vec![complete_from_marker.clone(), replace_range_marker.clone()],
15330 );
15331
15332 let complete_from_position =
15333 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
15334 let replace_range =
15335 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
15336
15337 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
15338 let completions = completions.clone();
15339 counter.fetch_add(1, atomic::Ordering::Release);
15340 async move {
15341 assert_eq!(params.text_document_position.text_document.uri, url.clone());
15342 assert_eq!(
15343 params.text_document_position.position,
15344 complete_from_position
15345 );
15346 Ok(Some(lsp::CompletionResponse::Array(
15347 completions
15348 .iter()
15349 .map(|completion_text| lsp::CompletionItem {
15350 label: completion_text.to_string(),
15351 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15352 range: replace_range,
15353 new_text: completion_text.to_string(),
15354 })),
15355 ..Default::default()
15356 })
15357 .collect(),
15358 )))
15359 }
15360 });
15361
15362 async move {
15363 request.next().await;
15364 }
15365}
15366
15367fn handle_resolve_completion_request(
15368 cx: &mut EditorLspTestContext,
15369 edits: Option<Vec<(&'static str, &'static str)>>,
15370) -> impl Future<Output = ()> {
15371 let edits = edits.map(|edits| {
15372 edits
15373 .iter()
15374 .map(|(marked_string, new_text)| {
15375 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
15376 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
15377 lsp::TextEdit::new(replace_range, new_text.to_string())
15378 })
15379 .collect::<Vec<_>>()
15380 });
15381
15382 let mut request =
15383 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
15384 let edits = edits.clone();
15385 async move {
15386 Ok(lsp::CompletionItem {
15387 additional_text_edits: edits,
15388 ..Default::default()
15389 })
15390 }
15391 });
15392
15393 async move {
15394 request.next().await;
15395 }
15396}
15397
15398pub(crate) fn update_test_language_settings(
15399 cx: &mut TestAppContext,
15400 f: impl Fn(&mut AllLanguageSettingsContent),
15401) {
15402 cx.update(|cx| {
15403 SettingsStore::update_global(cx, |store, cx| {
15404 store.update_user_settings::<AllLanguageSettings>(cx, f);
15405 });
15406 });
15407}
15408
15409pub(crate) fn update_test_project_settings(
15410 cx: &mut TestAppContext,
15411 f: impl Fn(&mut ProjectSettings),
15412) {
15413 cx.update(|cx| {
15414 SettingsStore::update_global(cx, |store, cx| {
15415 store.update_user_settings::<ProjectSettings>(cx, f);
15416 });
15417 });
15418}
15419
15420pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
15421 cx.update(|cx| {
15422 assets::Assets.load_test_fonts(cx);
15423 let store = SettingsStore::test(cx);
15424 cx.set_global(store);
15425 theme::init(theme::LoadThemes::JustBase, cx);
15426 release_channel::init(SemanticVersion::default(), cx);
15427 client::init_settings(cx);
15428 language::init(cx);
15429 Project::init_settings(cx);
15430 workspace::init_settings(cx);
15431 crate::init(cx);
15432 });
15433
15434 update_test_language_settings(cx, f);
15435}
15436
15437#[track_caller]
15438fn assert_hunk_revert(
15439 not_reverted_text_with_selections: &str,
15440 expected_hunk_statuses_before: Vec<DiffHunkStatus>,
15441 expected_reverted_text_with_selections: &str,
15442 base_text: &str,
15443 cx: &mut EditorLspTestContext,
15444) {
15445 cx.set_state(not_reverted_text_with_selections);
15446 cx.set_diff_base(base_text);
15447 cx.executor().run_until_parked();
15448
15449 let actual_hunk_statuses_before = cx.update_editor(|editor, cx| {
15450 let snapshot = editor.snapshot(cx);
15451 let reverted_hunk_statuses = snapshot
15452 .buffer_snapshot
15453 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
15454 .map(|hunk| hunk.status())
15455 .collect::<Vec<_>>();
15456
15457 editor.revert_selected_hunks(&RevertSelectedHunks, cx);
15458 reverted_hunk_statuses
15459 });
15460 cx.executor().run_until_parked();
15461 cx.assert_editor_state(expected_reverted_text_with_selections);
15462 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
15463}