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 buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
11use futures::StreamExt;
12use gpui::{
13 div, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
14 WindowBounds, WindowOptions,
15};
16use indoc::indoc;
17use language::{
18 language_settings::{
19 AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings,
20 LanguageSettingsContent, PrettierSettings,
21 },
22 BracketPairConfig,
23 Capability::ReadWrite,
24 FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName,
25 Override, Point,
26};
27use language_settings::{Formatter, FormatterList, IndentGuideSettings};
28use multi_buffer::{IndentGuide, PathKey};
29use parking_lot::Mutex;
30use pretty_assertions::{assert_eq, assert_ne};
31use project::project_settings::{LspSettings, ProjectSettings};
32use project::FakeFs;
33use serde_json::{self, json};
34use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
35use std::{
36 iter,
37 sync::atomic::{self, AtomicUsize},
38};
39use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
40use text::ToPoint as _;
41use unindent::Unindent;
42use util::{
43 assert_set_eq, path,
44 test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
45 uri,
46};
47use workspace::{
48 item::{FollowEvent, FollowableItem, Item, ItemHandle},
49 NavigationEntry, ViewId,
50};
51
52#[gpui::test]
53fn test_edit_events(cx: &mut TestAppContext) {
54 init_test(cx, |_| {});
55
56 let buffer = cx.new(|cx| {
57 let mut buffer = language::Buffer::local("123456", cx);
58 buffer.set_group_interval(Duration::from_secs(1));
59 buffer
60 });
61
62 let events = Rc::new(RefCell::new(Vec::new()));
63 let editor1 = cx.add_window({
64 let events = events.clone();
65 |window, cx| {
66 let entity = cx.entity().clone();
67 cx.subscribe_in(
68 &entity,
69 window,
70 move |_, _, event: &EditorEvent, _, _| match event {
71 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
72 EditorEvent::BufferEdited => {
73 events.borrow_mut().push(("editor1", "buffer edited"))
74 }
75 _ => {}
76 },
77 )
78 .detach();
79 Editor::for_buffer(buffer.clone(), None, window, cx)
80 }
81 });
82
83 let editor2 = cx.add_window({
84 let events = events.clone();
85 |window, cx| {
86 cx.subscribe_in(
87 &cx.entity().clone(),
88 window,
89 move |_, _, event: &EditorEvent, _, _| match event {
90 EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
91 EditorEvent::BufferEdited => {
92 events.borrow_mut().push(("editor2", "buffer edited"))
93 }
94 _ => {}
95 },
96 )
97 .detach();
98 Editor::for_buffer(buffer.clone(), None, window, cx)
99 }
100 });
101
102 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
103
104 // Mutating editor 1 will emit an `Edited` event only for that editor.
105 _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
106 assert_eq!(
107 mem::take(&mut *events.borrow_mut()),
108 [
109 ("editor1", "edited"),
110 ("editor1", "buffer edited"),
111 ("editor2", "buffer edited"),
112 ]
113 );
114
115 // Mutating editor 2 will emit an `Edited` event only for that editor.
116 _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
117 assert_eq!(
118 mem::take(&mut *events.borrow_mut()),
119 [
120 ("editor2", "edited"),
121 ("editor1", "buffer edited"),
122 ("editor2", "buffer edited"),
123 ]
124 );
125
126 // Undoing on editor 1 will emit an `Edited` event only for that editor.
127 _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
128 assert_eq!(
129 mem::take(&mut *events.borrow_mut()),
130 [
131 ("editor1", "edited"),
132 ("editor1", "buffer edited"),
133 ("editor2", "buffer edited"),
134 ]
135 );
136
137 // Redoing on editor 1 will emit an `Edited` event only for that editor.
138 _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
139 assert_eq!(
140 mem::take(&mut *events.borrow_mut()),
141 [
142 ("editor1", "edited"),
143 ("editor1", "buffer edited"),
144 ("editor2", "buffer edited"),
145 ]
146 );
147
148 // Undoing on editor 2 will emit an `Edited` event only for that editor.
149 _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
150 assert_eq!(
151 mem::take(&mut *events.borrow_mut()),
152 [
153 ("editor2", "edited"),
154 ("editor1", "buffer edited"),
155 ("editor2", "buffer edited"),
156 ]
157 );
158
159 // Redoing on editor 2 will emit an `Edited` event only for that editor.
160 _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
161 assert_eq!(
162 mem::take(&mut *events.borrow_mut()),
163 [
164 ("editor2", "edited"),
165 ("editor1", "buffer edited"),
166 ("editor2", "buffer edited"),
167 ]
168 );
169
170 // No event is emitted when the mutation is a no-op.
171 _ = editor2.update(cx, |editor, window, cx| {
172 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
173
174 editor.backspace(&Backspace, window, cx);
175 });
176 assert_eq!(mem::take(&mut *events.borrow_mut()), []);
177}
178
179#[gpui::test]
180fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
181 init_test(cx, |_| {});
182
183 let mut now = Instant::now();
184 let group_interval = Duration::from_millis(1);
185 let buffer = cx.new(|cx| {
186 let mut buf = language::Buffer::local("123456", cx);
187 buf.set_group_interval(group_interval);
188 buf
189 });
190 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
191 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
192
193 _ = editor.update(cx, |editor, window, cx| {
194 editor.start_transaction_at(now, window, cx);
195 editor.change_selections(None, window, cx, |s| s.select_ranges([2..4]));
196
197 editor.insert("cd", window, cx);
198 editor.end_transaction_at(now, cx);
199 assert_eq!(editor.text(cx), "12cd56");
200 assert_eq!(editor.selections.ranges(cx), vec![4..4]);
201
202 editor.start_transaction_at(now, window, cx);
203 editor.change_selections(None, window, cx, |s| s.select_ranges([4..5]));
204 editor.insert("e", window, cx);
205 editor.end_transaction_at(now, cx);
206 assert_eq!(editor.text(cx), "12cde6");
207 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
208
209 now += group_interval + Duration::from_millis(1);
210 editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]));
211
212 // Simulate an edit in another editor
213 buffer.update(cx, |buffer, cx| {
214 buffer.start_transaction_at(now, cx);
215 buffer.edit([(0..1, "a")], None, cx);
216 buffer.edit([(1..1, "b")], None, cx);
217 buffer.end_transaction_at(now, cx);
218 });
219
220 assert_eq!(editor.text(cx), "ab2cde6");
221 assert_eq!(editor.selections.ranges(cx), vec![3..3]);
222
223 // Last transaction happened past the group interval in a different editor.
224 // Undo it individually and don't restore selections.
225 editor.undo(&Undo, window, cx);
226 assert_eq!(editor.text(cx), "12cde6");
227 assert_eq!(editor.selections.ranges(cx), vec![2..2]);
228
229 // First two transactions happened within the group interval in this editor.
230 // Undo them together and restore selections.
231 editor.undo(&Undo, window, cx);
232 editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
233 assert_eq!(editor.text(cx), "123456");
234 assert_eq!(editor.selections.ranges(cx), vec![0..0]);
235
236 // Redo the first two transactions together.
237 editor.redo(&Redo, window, cx);
238 assert_eq!(editor.text(cx), "12cde6");
239 assert_eq!(editor.selections.ranges(cx), vec![5..5]);
240
241 // Redo the last transaction on its own.
242 editor.redo(&Redo, window, cx);
243 assert_eq!(editor.text(cx), "ab2cde6");
244 assert_eq!(editor.selections.ranges(cx), vec![6..6]);
245
246 // Test empty transactions.
247 editor.start_transaction_at(now, window, cx);
248 editor.end_transaction_at(now, cx);
249 editor.undo(&Undo, window, cx);
250 assert_eq!(editor.text(cx), "12cde6");
251 });
252}
253
254#[gpui::test]
255fn test_ime_composition(cx: &mut TestAppContext) {
256 init_test(cx, |_| {});
257
258 let buffer = cx.new(|cx| {
259 let mut buffer = language::Buffer::local("abcde", cx);
260 // Ensure automatic grouping doesn't occur.
261 buffer.set_group_interval(Duration::ZERO);
262 buffer
263 });
264
265 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
266 cx.add_window(|window, cx| {
267 let mut editor = build_editor(buffer.clone(), window, cx);
268
269 // Start a new IME composition.
270 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
271 editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
272 editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
273 assert_eq!(editor.text(cx), "äbcde");
274 assert_eq!(
275 editor.marked_text_ranges(cx),
276 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
277 );
278
279 // Finalize IME composition.
280 editor.replace_text_in_range(None, "ā", window, cx);
281 assert_eq!(editor.text(cx), "ābcde");
282 assert_eq!(editor.marked_text_ranges(cx), None);
283
284 // IME composition edits are grouped and are undone/redone at once.
285 editor.undo(&Default::default(), window, cx);
286 assert_eq!(editor.text(cx), "abcde");
287 assert_eq!(editor.marked_text_ranges(cx), None);
288 editor.redo(&Default::default(), window, cx);
289 assert_eq!(editor.text(cx), "ābcde");
290 assert_eq!(editor.marked_text_ranges(cx), None);
291
292 // Start a new IME composition.
293 editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
294 assert_eq!(
295 editor.marked_text_ranges(cx),
296 Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
297 );
298
299 // Undoing during an IME composition cancels it.
300 editor.undo(&Default::default(), window, cx);
301 assert_eq!(editor.text(cx), "ābcde");
302 assert_eq!(editor.marked_text_ranges(cx), None);
303
304 // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
305 editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
306 assert_eq!(editor.text(cx), "ābcdè");
307 assert_eq!(
308 editor.marked_text_ranges(cx),
309 Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
310 );
311
312 // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
313 editor.replace_text_in_range(Some(4..999), "ę", window, cx);
314 assert_eq!(editor.text(cx), "ābcdę");
315 assert_eq!(editor.marked_text_ranges(cx), None);
316
317 // Start a new IME composition with multiple cursors.
318 editor.change_selections(None, window, cx, |s| {
319 s.select_ranges([
320 OffsetUtf16(1)..OffsetUtf16(1),
321 OffsetUtf16(3)..OffsetUtf16(3),
322 OffsetUtf16(5)..OffsetUtf16(5),
323 ])
324 });
325 editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
326 assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
327 assert_eq!(
328 editor.marked_text_ranges(cx),
329 Some(vec![
330 OffsetUtf16(0)..OffsetUtf16(3),
331 OffsetUtf16(4)..OffsetUtf16(7),
332 OffsetUtf16(8)..OffsetUtf16(11)
333 ])
334 );
335
336 // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
337 editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
338 assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
339 assert_eq!(
340 editor.marked_text_ranges(cx),
341 Some(vec![
342 OffsetUtf16(1)..OffsetUtf16(2),
343 OffsetUtf16(5)..OffsetUtf16(6),
344 OffsetUtf16(9)..OffsetUtf16(10)
345 ])
346 );
347
348 // Finalize IME composition with multiple cursors.
349 editor.replace_text_in_range(Some(9..10), "2", window, cx);
350 assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
351 assert_eq!(editor.marked_text_ranges(cx), None);
352
353 editor
354 });
355}
356
357#[gpui::test]
358fn test_selection_with_mouse(cx: &mut TestAppContext) {
359 init_test(cx, |_| {});
360
361 let editor = cx.add_window(|window, cx| {
362 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
363 build_editor(buffer, window, cx)
364 });
365
366 _ = editor.update(cx, |editor, window, cx| {
367 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
368 });
369 assert_eq!(
370 editor
371 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
372 .unwrap(),
373 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
374 );
375
376 _ = editor.update(cx, |editor, window, cx| {
377 editor.update_selection(
378 DisplayPoint::new(DisplayRow(3), 3),
379 0,
380 gpui::Point::<f32>::default(),
381 window,
382 cx,
383 );
384 });
385
386 assert_eq!(
387 editor
388 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
389 .unwrap(),
390 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
391 );
392
393 _ = editor.update(cx, |editor, window, cx| {
394 editor.update_selection(
395 DisplayPoint::new(DisplayRow(1), 1),
396 0,
397 gpui::Point::<f32>::default(),
398 window,
399 cx,
400 );
401 });
402
403 assert_eq!(
404 editor
405 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
406 .unwrap(),
407 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
408 );
409
410 _ = editor.update(cx, |editor, window, cx| {
411 editor.end_selection(window, cx);
412 editor.update_selection(
413 DisplayPoint::new(DisplayRow(3), 3),
414 0,
415 gpui::Point::<f32>::default(),
416 window,
417 cx,
418 );
419 });
420
421 assert_eq!(
422 editor
423 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
424 .unwrap(),
425 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
426 );
427
428 _ = editor.update(cx, |editor, window, cx| {
429 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
430 editor.update_selection(
431 DisplayPoint::new(DisplayRow(0), 0),
432 0,
433 gpui::Point::<f32>::default(),
434 window,
435 cx,
436 );
437 });
438
439 assert_eq!(
440 editor
441 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
442 .unwrap(),
443 [
444 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
445 DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
446 ]
447 );
448
449 _ = editor.update(cx, |editor, window, cx| {
450 editor.end_selection(window, cx);
451 });
452
453 assert_eq!(
454 editor
455 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
456 .unwrap(),
457 [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
458 );
459}
460
461#[gpui::test]
462fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
463 init_test(cx, |_| {});
464
465 let editor = cx.add_window(|window, cx| {
466 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
467 build_editor(buffer, window, cx)
468 });
469
470 _ = editor.update(cx, |editor, window, cx| {
471 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
472 });
473
474 _ = editor.update(cx, |editor, window, cx| {
475 editor.end_selection(window, cx);
476 });
477
478 _ = editor.update(cx, |editor, window, cx| {
479 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
480 });
481
482 _ = editor.update(cx, |editor, window, cx| {
483 editor.end_selection(window, cx);
484 });
485
486 assert_eq!(
487 editor
488 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
489 .unwrap(),
490 [
491 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
492 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
493 ]
494 );
495
496 _ = editor.update(cx, |editor, window, cx| {
497 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
498 });
499
500 _ = editor.update(cx, |editor, window, cx| {
501 editor.end_selection(window, cx);
502 });
503
504 assert_eq!(
505 editor
506 .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
507 .unwrap(),
508 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
509 );
510}
511
512#[gpui::test]
513fn test_canceling_pending_selection(cx: &mut TestAppContext) {
514 init_test(cx, |_| {});
515
516 let editor = cx.add_window(|window, cx| {
517 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
518 build_editor(buffer, window, cx)
519 });
520
521 _ = editor.update(cx, |editor, window, cx| {
522 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
523 assert_eq!(
524 editor.selections.display_ranges(cx),
525 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
526 );
527 });
528
529 _ = editor.update(cx, |editor, window, cx| {
530 editor.update_selection(
531 DisplayPoint::new(DisplayRow(3), 3),
532 0,
533 gpui::Point::<f32>::default(),
534 window,
535 cx,
536 );
537 assert_eq!(
538 editor.selections.display_ranges(cx),
539 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
540 );
541 });
542
543 _ = editor.update(cx, |editor, window, cx| {
544 editor.cancel(&Cancel, window, cx);
545 editor.update_selection(
546 DisplayPoint::new(DisplayRow(1), 1),
547 0,
548 gpui::Point::<f32>::default(),
549 window,
550 cx,
551 );
552 assert_eq!(
553 editor.selections.display_ranges(cx),
554 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
555 );
556 });
557}
558
559#[gpui::test]
560fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
561 init_test(cx, |_| {});
562
563 let editor = cx.add_window(|window, cx| {
564 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
565 build_editor(buffer, window, cx)
566 });
567
568 _ = editor.update(cx, |editor, window, cx| {
569 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
570 assert_eq!(
571 editor.selections.display_ranges(cx),
572 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
573 );
574
575 editor.move_down(&Default::default(), window, cx);
576 assert_eq!(
577 editor.selections.display_ranges(cx),
578 [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
579 );
580
581 editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
582 assert_eq!(
583 editor.selections.display_ranges(cx),
584 [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
585 );
586
587 editor.move_up(&Default::default(), window, cx);
588 assert_eq!(
589 editor.selections.display_ranges(cx),
590 [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
591 );
592 });
593}
594
595#[gpui::test]
596fn test_clone(cx: &mut TestAppContext) {
597 init_test(cx, |_| {});
598
599 let (text, selection_ranges) = marked_text_ranges(
600 indoc! {"
601 one
602 two
603 threeˇ
604 four
605 fiveˇ
606 "},
607 true,
608 );
609
610 let editor = cx.add_window(|window, cx| {
611 let buffer = MultiBuffer::build_simple(&text, cx);
612 build_editor(buffer, window, cx)
613 });
614
615 _ = editor.update(cx, |editor, window, cx| {
616 editor.change_selections(None, window, cx, |s| {
617 s.select_ranges(selection_ranges.clone())
618 });
619 editor.fold_creases(
620 vec![
621 Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
622 Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
623 ],
624 true,
625 window,
626 cx,
627 );
628 });
629
630 let cloned_editor = editor
631 .update(cx, |editor, _, cx| {
632 cx.open_window(Default::default(), |window, cx| {
633 cx.new(|cx| editor.clone(window, cx))
634 })
635 })
636 .unwrap()
637 .unwrap();
638
639 let snapshot = editor
640 .update(cx, |e, window, cx| e.snapshot(window, cx))
641 .unwrap();
642 let cloned_snapshot = cloned_editor
643 .update(cx, |e, window, cx| e.snapshot(window, cx))
644 .unwrap();
645
646 assert_eq!(
647 cloned_editor
648 .update(cx, |e, _, cx| e.display_text(cx))
649 .unwrap(),
650 editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
651 );
652 assert_eq!(
653 cloned_snapshot
654 .folds_in_range(0..text.len())
655 .collect::<Vec<_>>(),
656 snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
657 );
658 assert_set_eq!(
659 cloned_editor
660 .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
661 .unwrap(),
662 editor
663 .update(cx, |editor, _, cx| editor.selections.ranges(cx))
664 .unwrap()
665 );
666 assert_set_eq!(
667 cloned_editor
668 .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
669 .unwrap(),
670 editor
671 .update(cx, |e, _, cx| e.selections.display_ranges(cx))
672 .unwrap()
673 );
674}
675
676#[gpui::test]
677async fn test_navigation_history(cx: &mut TestAppContext) {
678 init_test(cx, |_| {});
679
680 use workspace::item::Item;
681
682 let fs = FakeFs::new(cx.executor());
683 let project = Project::test(fs, [], cx).await;
684 let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
685 let pane = workspace
686 .update(cx, |workspace, _, _| workspace.active_pane().clone())
687 .unwrap();
688
689 _ = workspace.update(cx, |_v, window, cx| {
690 cx.new(|cx| {
691 let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
692 let mut editor = build_editor(buffer.clone(), window, cx);
693 let handle = cx.entity();
694 editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
695
696 fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
697 editor.nav_history.as_mut().unwrap().pop_backward(cx)
698 }
699
700 // Move the cursor a small distance.
701 // Nothing is added to the navigation history.
702 editor.change_selections(None, window, cx, |s| {
703 s.select_display_ranges([
704 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
705 ])
706 });
707 editor.change_selections(None, window, cx, |s| {
708 s.select_display_ranges([
709 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
710 ])
711 });
712 assert!(pop_history(&mut editor, cx).is_none());
713
714 // Move the cursor a large distance.
715 // The history can jump back to the previous position.
716 editor.change_selections(None, window, cx, |s| {
717 s.select_display_ranges([
718 DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
719 ])
720 });
721 let nav_entry = pop_history(&mut editor, cx).unwrap();
722 editor.navigate(nav_entry.data.unwrap(), window, cx);
723 assert_eq!(nav_entry.item.id(), cx.entity_id());
724 assert_eq!(
725 editor.selections.display_ranges(cx),
726 &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
727 );
728 assert!(pop_history(&mut editor, cx).is_none());
729
730 // Move the cursor a small distance via the mouse.
731 // Nothing is added to the navigation history.
732 editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
733 editor.end_selection(window, cx);
734 assert_eq!(
735 editor.selections.display_ranges(cx),
736 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
737 );
738 assert!(pop_history(&mut editor, cx).is_none());
739
740 // Move the cursor a large distance via the mouse.
741 // The history can jump back to the previous position.
742 editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
743 editor.end_selection(window, cx);
744 assert_eq!(
745 editor.selections.display_ranges(cx),
746 &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
747 );
748 let nav_entry = pop_history(&mut editor, cx).unwrap();
749 editor.navigate(nav_entry.data.unwrap(), window, cx);
750 assert_eq!(nav_entry.item.id(), cx.entity_id());
751 assert_eq!(
752 editor.selections.display_ranges(cx),
753 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
754 );
755 assert!(pop_history(&mut editor, cx).is_none());
756
757 // Set scroll position to check later
758 editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
759 let original_scroll_position = editor.scroll_manager.anchor();
760
761 // Jump to the end of the document and adjust scroll
762 editor.move_to_end(&MoveToEnd, window, cx);
763 editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
764 assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
765
766 let nav_entry = pop_history(&mut editor, cx).unwrap();
767 editor.navigate(nav_entry.data.unwrap(), window, cx);
768 assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
769
770 // Ensure we don't panic when navigation data contains invalid anchors *and* points.
771 let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
772 invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
773 let invalid_point = Point::new(9999, 0);
774 editor.navigate(
775 Box::new(NavigationData {
776 cursor_anchor: invalid_anchor,
777 cursor_position: invalid_point,
778 scroll_anchor: ScrollAnchor {
779 anchor: invalid_anchor,
780 offset: Default::default(),
781 },
782 scroll_top_row: invalid_point.row,
783 }),
784 window,
785 cx,
786 );
787 assert_eq!(
788 editor.selections.display_ranges(cx),
789 &[editor.max_point(cx)..editor.max_point(cx)]
790 );
791 assert_eq!(
792 editor.scroll_position(cx),
793 gpui::Point::new(0., editor.max_point(cx).row().as_f32())
794 );
795
796 editor
797 })
798 });
799}
800
801#[gpui::test]
802fn test_cancel(cx: &mut TestAppContext) {
803 init_test(cx, |_| {});
804
805 let editor = cx.add_window(|window, cx| {
806 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
807 build_editor(buffer, window, cx)
808 });
809
810 _ = editor.update(cx, |editor, window, cx| {
811 editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
812 editor.update_selection(
813 DisplayPoint::new(DisplayRow(1), 1),
814 0,
815 gpui::Point::<f32>::default(),
816 window,
817 cx,
818 );
819 editor.end_selection(window, cx);
820
821 editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
822 editor.update_selection(
823 DisplayPoint::new(DisplayRow(0), 3),
824 0,
825 gpui::Point::<f32>::default(),
826 window,
827 cx,
828 );
829 editor.end_selection(window, cx);
830 assert_eq!(
831 editor.selections.display_ranges(cx),
832 [
833 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
834 DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
835 ]
836 );
837 });
838
839 _ = editor.update(cx, |editor, window, cx| {
840 editor.cancel(&Cancel, window, cx);
841 assert_eq!(
842 editor.selections.display_ranges(cx),
843 [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
844 );
845 });
846
847 _ = editor.update(cx, |editor, window, cx| {
848 editor.cancel(&Cancel, window, cx);
849 assert_eq!(
850 editor.selections.display_ranges(cx),
851 [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
852 );
853 });
854}
855
856#[gpui::test]
857fn test_fold_action(cx: &mut TestAppContext) {
858 init_test(cx, |_| {});
859
860 let editor = cx.add_window(|window, cx| {
861 let buffer = MultiBuffer::build_simple(
862 &"
863 impl Foo {
864 // Hello!
865
866 fn a() {
867 1
868 }
869
870 fn b() {
871 2
872 }
873
874 fn c() {
875 3
876 }
877 }
878 "
879 .unindent(),
880 cx,
881 );
882 build_editor(buffer.clone(), window, cx)
883 });
884
885 _ = editor.update(cx, |editor, window, cx| {
886 editor.change_selections(None, window, cx, |s| {
887 s.select_display_ranges([
888 DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
889 ]);
890 });
891 editor.fold(&Fold, window, cx);
892 assert_eq!(
893 editor.display_text(cx),
894 "
895 impl Foo {
896 // Hello!
897
898 fn a() {
899 1
900 }
901
902 fn b() {⋯
903 }
904
905 fn c() {⋯
906 }
907 }
908 "
909 .unindent(),
910 );
911
912 editor.fold(&Fold, window, cx);
913 assert_eq!(
914 editor.display_text(cx),
915 "
916 impl Foo {⋯
917 }
918 "
919 .unindent(),
920 );
921
922 editor.unfold_lines(&UnfoldLines, window, cx);
923 assert_eq!(
924 editor.display_text(cx),
925 "
926 impl Foo {
927 // Hello!
928
929 fn a() {
930 1
931 }
932
933 fn b() {⋯
934 }
935
936 fn c() {⋯
937 }
938 }
939 "
940 .unindent(),
941 );
942
943 editor.unfold_lines(&UnfoldLines, window, cx);
944 assert_eq!(
945 editor.display_text(cx),
946 editor.buffer.read(cx).read(cx).text()
947 );
948 });
949}
950
951#[gpui::test]
952fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
953 init_test(cx, |_| {});
954
955 let editor = cx.add_window(|window, cx| {
956 let buffer = MultiBuffer::build_simple(
957 &"
958 class Foo:
959 # Hello!
960
961 def a():
962 print(1)
963
964 def b():
965 print(2)
966
967 def c():
968 print(3)
969 "
970 .unindent(),
971 cx,
972 );
973 build_editor(buffer.clone(), window, cx)
974 });
975
976 _ = editor.update(cx, |editor, window, cx| {
977 editor.change_selections(None, window, cx, |s| {
978 s.select_display_ranges([
979 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
980 ]);
981 });
982 editor.fold(&Fold, window, cx);
983 assert_eq!(
984 editor.display_text(cx),
985 "
986 class Foo:
987 # Hello!
988
989 def a():
990 print(1)
991
992 def b():⋯
993
994 def c():⋯
995 "
996 .unindent(),
997 );
998
999 editor.fold(&Fold, window, cx);
1000 assert_eq!(
1001 editor.display_text(cx),
1002 "
1003 class Foo:⋯
1004 "
1005 .unindent(),
1006 );
1007
1008 editor.unfold_lines(&UnfoldLines, window, cx);
1009 assert_eq!(
1010 editor.display_text(cx),
1011 "
1012 class Foo:
1013 # Hello!
1014
1015 def a():
1016 print(1)
1017
1018 def b():⋯
1019
1020 def c():⋯
1021 "
1022 .unindent(),
1023 );
1024
1025 editor.unfold_lines(&UnfoldLines, window, cx);
1026 assert_eq!(
1027 editor.display_text(cx),
1028 editor.buffer.read(cx).read(cx).text()
1029 );
1030 });
1031}
1032
1033#[gpui::test]
1034fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
1035 init_test(cx, |_| {});
1036
1037 let editor = cx.add_window(|window, cx| {
1038 let buffer = MultiBuffer::build_simple(
1039 &"
1040 class Foo:
1041 # Hello!
1042
1043 def a():
1044 print(1)
1045
1046 def b():
1047 print(2)
1048
1049
1050 def c():
1051 print(3)
1052
1053
1054 "
1055 .unindent(),
1056 cx,
1057 );
1058 build_editor(buffer.clone(), window, cx)
1059 });
1060
1061 _ = editor.update(cx, |editor, window, cx| {
1062 editor.change_selections(None, window, cx, |s| {
1063 s.select_display_ranges([
1064 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
1065 ]);
1066 });
1067 editor.fold(&Fold, window, cx);
1068 assert_eq!(
1069 editor.display_text(cx),
1070 "
1071 class Foo:
1072 # Hello!
1073
1074 def a():
1075 print(1)
1076
1077 def b():⋯
1078
1079
1080 def c():⋯
1081
1082
1083 "
1084 .unindent(),
1085 );
1086
1087 editor.fold(&Fold, window, cx);
1088 assert_eq!(
1089 editor.display_text(cx),
1090 "
1091 class Foo:⋯
1092
1093
1094 "
1095 .unindent(),
1096 );
1097
1098 editor.unfold_lines(&UnfoldLines, window, cx);
1099 assert_eq!(
1100 editor.display_text(cx),
1101 "
1102 class Foo:
1103 # Hello!
1104
1105 def a():
1106 print(1)
1107
1108 def b():⋯
1109
1110
1111 def c():⋯
1112
1113
1114 "
1115 .unindent(),
1116 );
1117
1118 editor.unfold_lines(&UnfoldLines, window, cx);
1119 assert_eq!(
1120 editor.display_text(cx),
1121 editor.buffer.read(cx).read(cx).text()
1122 );
1123 });
1124}
1125
1126#[gpui::test]
1127fn test_fold_at_level(cx: &mut TestAppContext) {
1128 init_test(cx, |_| {});
1129
1130 let editor = cx.add_window(|window, cx| {
1131 let buffer = MultiBuffer::build_simple(
1132 &"
1133 class Foo:
1134 # Hello!
1135
1136 def a():
1137 print(1)
1138
1139 def b():
1140 print(2)
1141
1142
1143 class Bar:
1144 # World!
1145
1146 def a():
1147 print(1)
1148
1149 def b():
1150 print(2)
1151
1152
1153 "
1154 .unindent(),
1155 cx,
1156 );
1157 build_editor(buffer.clone(), window, cx)
1158 });
1159
1160 _ = editor.update(cx, |editor, window, cx| {
1161 editor.fold_at_level(&FoldAtLevel(2), window, cx);
1162 assert_eq!(
1163 editor.display_text(cx),
1164 "
1165 class Foo:
1166 # Hello!
1167
1168 def a():⋯
1169
1170 def b():⋯
1171
1172
1173 class Bar:
1174 # World!
1175
1176 def a():⋯
1177
1178 def b():⋯
1179
1180
1181 "
1182 .unindent(),
1183 );
1184
1185 editor.fold_at_level(&FoldAtLevel(1), window, cx);
1186 assert_eq!(
1187 editor.display_text(cx),
1188 "
1189 class Foo:⋯
1190
1191
1192 class Bar:⋯
1193
1194
1195 "
1196 .unindent(),
1197 );
1198
1199 editor.unfold_all(&UnfoldAll, window, cx);
1200 editor.fold_at_level(&FoldAtLevel(0), window, cx);
1201 assert_eq!(
1202 editor.display_text(cx),
1203 "
1204 class Foo:
1205 # Hello!
1206
1207 def a():
1208 print(1)
1209
1210 def b():
1211 print(2)
1212
1213
1214 class Bar:
1215 # World!
1216
1217 def a():
1218 print(1)
1219
1220 def b():
1221 print(2)
1222
1223
1224 "
1225 .unindent(),
1226 );
1227
1228 assert_eq!(
1229 editor.display_text(cx),
1230 editor.buffer.read(cx).read(cx).text()
1231 );
1232 });
1233}
1234
1235#[gpui::test]
1236fn test_move_cursor(cx: &mut TestAppContext) {
1237 init_test(cx, |_| {});
1238
1239 let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
1240 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
1241
1242 buffer.update(cx, |buffer, cx| {
1243 buffer.edit(
1244 vec![
1245 (Point::new(1, 0)..Point::new(1, 0), "\t"),
1246 (Point::new(1, 1)..Point::new(1, 1), "\t"),
1247 ],
1248 None,
1249 cx,
1250 );
1251 });
1252 _ = editor.update(cx, |editor, window, cx| {
1253 assert_eq!(
1254 editor.selections.display_ranges(cx),
1255 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1256 );
1257
1258 editor.move_down(&MoveDown, window, cx);
1259 assert_eq!(
1260 editor.selections.display_ranges(cx),
1261 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1262 );
1263
1264 editor.move_right(&MoveRight, window, cx);
1265 assert_eq!(
1266 editor.selections.display_ranges(cx),
1267 &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
1268 );
1269
1270 editor.move_left(&MoveLeft, window, cx);
1271 assert_eq!(
1272 editor.selections.display_ranges(cx),
1273 &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
1274 );
1275
1276 editor.move_up(&MoveUp, window, cx);
1277 assert_eq!(
1278 editor.selections.display_ranges(cx),
1279 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1280 );
1281
1282 editor.move_to_end(&MoveToEnd, window, cx);
1283 assert_eq!(
1284 editor.selections.display_ranges(cx),
1285 &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
1286 );
1287
1288 editor.move_to_beginning(&MoveToBeginning, window, cx);
1289 assert_eq!(
1290 editor.selections.display_ranges(cx),
1291 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
1292 );
1293
1294 editor.change_selections(None, window, cx, |s| {
1295 s.select_display_ranges([
1296 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
1297 ]);
1298 });
1299 editor.select_to_beginning(&SelectToBeginning, window, cx);
1300 assert_eq!(
1301 editor.selections.display_ranges(cx),
1302 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
1303 );
1304
1305 editor.select_to_end(&SelectToEnd, window, cx);
1306 assert_eq!(
1307 editor.selections.display_ranges(cx),
1308 &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
1309 );
1310 });
1311}
1312
1313// TODO: Re-enable this test
1314#[cfg(target_os = "macos")]
1315#[gpui::test]
1316fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
1317 init_test(cx, |_| {});
1318
1319 let editor = cx.add_window(|window, cx| {
1320 let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
1321 build_editor(buffer.clone(), window, cx)
1322 });
1323
1324 assert_eq!('🟥'.len_utf8(), 4);
1325 assert_eq!('α'.len_utf8(), 2);
1326
1327 _ = editor.update(cx, |editor, window, cx| {
1328 editor.fold_creases(
1329 vec![
1330 Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
1331 Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
1332 Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
1333 ],
1334 true,
1335 window,
1336 cx,
1337 );
1338 assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
1339
1340 editor.move_right(&MoveRight, window, cx);
1341 assert_eq!(
1342 editor.selections.display_ranges(cx),
1343 &[empty_range(0, "🟥".len())]
1344 );
1345 editor.move_right(&MoveRight, window, cx);
1346 assert_eq!(
1347 editor.selections.display_ranges(cx),
1348 &[empty_range(0, "🟥🟧".len())]
1349 );
1350 editor.move_right(&MoveRight, window, cx);
1351 assert_eq!(
1352 editor.selections.display_ranges(cx),
1353 &[empty_range(0, "🟥🟧⋯".len())]
1354 );
1355
1356 editor.move_down(&MoveDown, window, cx);
1357 assert_eq!(
1358 editor.selections.display_ranges(cx),
1359 &[empty_range(1, "ab⋯e".len())]
1360 );
1361 editor.move_left(&MoveLeft, window, cx);
1362 assert_eq!(
1363 editor.selections.display_ranges(cx),
1364 &[empty_range(1, "ab⋯".len())]
1365 );
1366 editor.move_left(&MoveLeft, window, cx);
1367 assert_eq!(
1368 editor.selections.display_ranges(cx),
1369 &[empty_range(1, "ab".len())]
1370 );
1371 editor.move_left(&MoveLeft, window, cx);
1372 assert_eq!(
1373 editor.selections.display_ranges(cx),
1374 &[empty_range(1, "a".len())]
1375 );
1376
1377 editor.move_down(&MoveDown, window, cx);
1378 assert_eq!(
1379 editor.selections.display_ranges(cx),
1380 &[empty_range(2, "α".len())]
1381 );
1382 editor.move_right(&MoveRight, window, cx);
1383 assert_eq!(
1384 editor.selections.display_ranges(cx),
1385 &[empty_range(2, "αβ".len())]
1386 );
1387 editor.move_right(&MoveRight, window, cx);
1388 assert_eq!(
1389 editor.selections.display_ranges(cx),
1390 &[empty_range(2, "αβ⋯".len())]
1391 );
1392 editor.move_right(&MoveRight, window, cx);
1393 assert_eq!(
1394 editor.selections.display_ranges(cx),
1395 &[empty_range(2, "αβ⋯ε".len())]
1396 );
1397
1398 editor.move_up(&MoveUp, window, cx);
1399 assert_eq!(
1400 editor.selections.display_ranges(cx),
1401 &[empty_range(1, "ab⋯e".len())]
1402 );
1403 editor.move_down(&MoveDown, window, cx);
1404 assert_eq!(
1405 editor.selections.display_ranges(cx),
1406 &[empty_range(2, "αβ⋯ε".len())]
1407 );
1408 editor.move_up(&MoveUp, window, cx);
1409 assert_eq!(
1410 editor.selections.display_ranges(cx),
1411 &[empty_range(1, "ab⋯e".len())]
1412 );
1413
1414 editor.move_up(&MoveUp, window, cx);
1415 assert_eq!(
1416 editor.selections.display_ranges(cx),
1417 &[empty_range(0, "🟥🟧".len())]
1418 );
1419 editor.move_left(&MoveLeft, window, cx);
1420 assert_eq!(
1421 editor.selections.display_ranges(cx),
1422 &[empty_range(0, "🟥".len())]
1423 );
1424 editor.move_left(&MoveLeft, window, cx);
1425 assert_eq!(
1426 editor.selections.display_ranges(cx),
1427 &[empty_range(0, "".len())]
1428 );
1429 });
1430}
1431
1432#[gpui::test]
1433fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
1434 init_test(cx, |_| {});
1435
1436 let editor = cx.add_window(|window, cx| {
1437 let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
1438 build_editor(buffer.clone(), window, cx)
1439 });
1440 _ = editor.update(cx, |editor, window, cx| {
1441 editor.change_selections(None, window, cx, |s| {
1442 s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
1443 });
1444
1445 // moving above start of document should move selection to start of document,
1446 // but the next move down should still be at the original goal_x
1447 editor.move_up(&MoveUp, window, cx);
1448 assert_eq!(
1449 editor.selections.display_ranges(cx),
1450 &[empty_range(0, "".len())]
1451 );
1452
1453 editor.move_down(&MoveDown, window, cx);
1454 assert_eq!(
1455 editor.selections.display_ranges(cx),
1456 &[empty_range(1, "abcd".len())]
1457 );
1458
1459 editor.move_down(&MoveDown, window, cx);
1460 assert_eq!(
1461 editor.selections.display_ranges(cx),
1462 &[empty_range(2, "αβγ".len())]
1463 );
1464
1465 editor.move_down(&MoveDown, window, cx);
1466 assert_eq!(
1467 editor.selections.display_ranges(cx),
1468 &[empty_range(3, "abcd".len())]
1469 );
1470
1471 editor.move_down(&MoveDown, window, cx);
1472 assert_eq!(
1473 editor.selections.display_ranges(cx),
1474 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1475 );
1476
1477 // moving past end of document should not change goal_x
1478 editor.move_down(&MoveDown, window, cx);
1479 assert_eq!(
1480 editor.selections.display_ranges(cx),
1481 &[empty_range(5, "".len())]
1482 );
1483
1484 editor.move_down(&MoveDown, window, cx);
1485 assert_eq!(
1486 editor.selections.display_ranges(cx),
1487 &[empty_range(5, "".len())]
1488 );
1489
1490 editor.move_up(&MoveUp, window, cx);
1491 assert_eq!(
1492 editor.selections.display_ranges(cx),
1493 &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
1494 );
1495
1496 editor.move_up(&MoveUp, window, cx);
1497 assert_eq!(
1498 editor.selections.display_ranges(cx),
1499 &[empty_range(3, "abcd".len())]
1500 );
1501
1502 editor.move_up(&MoveUp, window, cx);
1503 assert_eq!(
1504 editor.selections.display_ranges(cx),
1505 &[empty_range(2, "αβγ".len())]
1506 );
1507 });
1508}
1509
1510#[gpui::test]
1511fn test_beginning_end_of_line(cx: &mut TestAppContext) {
1512 init_test(cx, |_| {});
1513 let move_to_beg = MoveToBeginningOfLine {
1514 stop_at_soft_wraps: true,
1515 stop_at_indent: true,
1516 };
1517
1518 let delete_to_beg = DeleteToBeginningOfLine {
1519 stop_at_indent: false,
1520 };
1521
1522 let move_to_end = MoveToEndOfLine {
1523 stop_at_soft_wraps: true,
1524 };
1525
1526 let editor = cx.add_window(|window, cx| {
1527 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1528 build_editor(buffer, window, cx)
1529 });
1530 _ = editor.update(cx, |editor, window, cx| {
1531 editor.change_selections(None, window, cx, |s| {
1532 s.select_display_ranges([
1533 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1534 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1535 ]);
1536 });
1537 });
1538
1539 _ = editor.update(cx, |editor, window, cx| {
1540 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1541 assert_eq!(
1542 editor.selections.display_ranges(cx),
1543 &[
1544 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1545 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1546 ]
1547 );
1548 });
1549
1550 _ = editor.update(cx, |editor, window, cx| {
1551 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1552 assert_eq!(
1553 editor.selections.display_ranges(cx),
1554 &[
1555 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1556 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1557 ]
1558 );
1559 });
1560
1561 _ = editor.update(cx, |editor, window, cx| {
1562 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1563 assert_eq!(
1564 editor.selections.display_ranges(cx),
1565 &[
1566 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1567 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1568 ]
1569 );
1570 });
1571
1572 _ = editor.update(cx, |editor, window, cx| {
1573 editor.move_to_end_of_line(&move_to_end, window, cx);
1574 assert_eq!(
1575 editor.selections.display_ranges(cx),
1576 &[
1577 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1578 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1579 ]
1580 );
1581 });
1582
1583 // Moving to the end of line again is a no-op.
1584 _ = editor.update(cx, |editor, window, cx| {
1585 editor.move_to_end_of_line(&move_to_end, window, cx);
1586 assert_eq!(
1587 editor.selections.display_ranges(cx),
1588 &[
1589 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
1590 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
1591 ]
1592 );
1593 });
1594
1595 _ = editor.update(cx, |editor, window, cx| {
1596 editor.move_left(&MoveLeft, window, cx);
1597 editor.select_to_beginning_of_line(
1598 &SelectToBeginningOfLine {
1599 stop_at_soft_wraps: true,
1600 stop_at_indent: true,
1601 },
1602 window,
1603 cx,
1604 );
1605 assert_eq!(
1606 editor.selections.display_ranges(cx),
1607 &[
1608 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1609 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1610 ]
1611 );
1612 });
1613
1614 _ = editor.update(cx, |editor, window, cx| {
1615 editor.select_to_beginning_of_line(
1616 &SelectToBeginningOfLine {
1617 stop_at_soft_wraps: true,
1618 stop_at_indent: true,
1619 },
1620 window,
1621 cx,
1622 );
1623 assert_eq!(
1624 editor.selections.display_ranges(cx),
1625 &[
1626 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1627 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1628 ]
1629 );
1630 });
1631
1632 _ = editor.update(cx, |editor, window, cx| {
1633 editor.select_to_beginning_of_line(
1634 &SelectToBeginningOfLine {
1635 stop_at_soft_wraps: true,
1636 stop_at_indent: true,
1637 },
1638 window,
1639 cx,
1640 );
1641 assert_eq!(
1642 editor.selections.display_ranges(cx),
1643 &[
1644 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1645 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1646 ]
1647 );
1648 });
1649
1650 _ = editor.update(cx, |editor, window, cx| {
1651 editor.select_to_end_of_line(
1652 &SelectToEndOfLine {
1653 stop_at_soft_wraps: true,
1654 },
1655 window,
1656 cx,
1657 );
1658 assert_eq!(
1659 editor.selections.display_ranges(cx),
1660 &[
1661 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
1662 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
1663 ]
1664 );
1665 });
1666
1667 _ = editor.update(cx, |editor, window, cx| {
1668 editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
1669 assert_eq!(editor.display_text(cx), "ab\n de");
1670 assert_eq!(
1671 editor.selections.display_ranges(cx),
1672 &[
1673 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
1674 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1675 ]
1676 );
1677 });
1678
1679 _ = editor.update(cx, |editor, window, cx| {
1680 editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
1681 assert_eq!(editor.display_text(cx), "\n");
1682 assert_eq!(
1683 editor.selections.display_ranges(cx),
1684 &[
1685 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1686 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1687 ]
1688 );
1689 });
1690}
1691
1692#[gpui::test]
1693fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
1694 init_test(cx, |_| {});
1695 let move_to_beg = MoveToBeginningOfLine {
1696 stop_at_soft_wraps: false,
1697 stop_at_indent: false,
1698 };
1699
1700 let move_to_end = MoveToEndOfLine {
1701 stop_at_soft_wraps: false,
1702 };
1703
1704 let editor = cx.add_window(|window, cx| {
1705 let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
1706 build_editor(buffer, window, cx)
1707 });
1708
1709 _ = editor.update(cx, |editor, window, cx| {
1710 editor.set_wrap_width(Some(140.0.into()), cx);
1711
1712 // We expect the following lines after wrapping
1713 // ```
1714 // thequickbrownfox
1715 // jumpedoverthelazydo
1716 // gs
1717 // ```
1718 // The final `gs` was soft-wrapped onto a new line.
1719 assert_eq!(
1720 "thequickbrownfox\njumpedoverthelaz\nydogs",
1721 editor.display_text(cx),
1722 );
1723
1724 // First, let's assert behavior on the first line, that was not soft-wrapped.
1725 // Start the cursor at the `k` on the first line
1726 editor.change_selections(None, window, cx, |s| {
1727 s.select_display_ranges([
1728 DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
1729 ]);
1730 });
1731
1732 // Moving to the beginning of the line should put us at the beginning of the line.
1733 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1734 assert_eq!(
1735 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
1736 editor.selections.display_ranges(cx)
1737 );
1738
1739 // Moving to the end of the line should put us at the end of the line.
1740 editor.move_to_end_of_line(&move_to_end, window, cx);
1741 assert_eq!(
1742 vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
1743 editor.selections.display_ranges(cx)
1744 );
1745
1746 // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
1747 // Start the cursor at the last line (`y` that was wrapped to a new line)
1748 editor.change_selections(None, window, cx, |s| {
1749 s.select_display_ranges([
1750 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
1751 ]);
1752 });
1753
1754 // Moving to the beginning of the line should put us at the start of the second line of
1755 // display text, i.e., the `j`.
1756 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1757 assert_eq!(
1758 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1759 editor.selections.display_ranges(cx)
1760 );
1761
1762 // Moving to the beginning of the line again should be a no-op.
1763 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1764 assert_eq!(
1765 vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
1766 editor.selections.display_ranges(cx)
1767 );
1768
1769 // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
1770 // next display line.
1771 editor.move_to_end_of_line(&move_to_end, window, cx);
1772 assert_eq!(
1773 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1774 editor.selections.display_ranges(cx)
1775 );
1776
1777 // Moving to the end of the line again should be a no-op.
1778 editor.move_to_end_of_line(&move_to_end, window, cx);
1779 assert_eq!(
1780 vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
1781 editor.selections.display_ranges(cx)
1782 );
1783 });
1784}
1785
1786#[gpui::test]
1787fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
1788 init_test(cx, |_| {});
1789
1790 let move_to_beg = MoveToBeginningOfLine {
1791 stop_at_soft_wraps: true,
1792 stop_at_indent: true,
1793 };
1794
1795 let select_to_beg = SelectToBeginningOfLine {
1796 stop_at_soft_wraps: true,
1797 stop_at_indent: true,
1798 };
1799
1800 let delete_to_beg = DeleteToBeginningOfLine {
1801 stop_at_indent: true,
1802 };
1803
1804 let move_to_end = MoveToEndOfLine {
1805 stop_at_soft_wraps: false,
1806 };
1807
1808 let editor = cx.add_window(|window, cx| {
1809 let buffer = MultiBuffer::build_simple("abc\n def", cx);
1810 build_editor(buffer, window, cx)
1811 });
1812
1813 _ = editor.update(cx, |editor, window, cx| {
1814 editor.change_selections(None, window, cx, |s| {
1815 s.select_display_ranges([
1816 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
1817 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
1818 ]);
1819 });
1820
1821 // Moving to the beginning of the line should put the first cursor at the beginning of the line,
1822 // and the second cursor at the first non-whitespace character in the line.
1823 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1824 assert_eq!(
1825 editor.selections.display_ranges(cx),
1826 &[
1827 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1828 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1829 ]
1830 );
1831
1832 // Moving to the beginning of the line again should be a no-op for the first cursor,
1833 // and should move the second cursor to the beginning of the line.
1834 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1835 assert_eq!(
1836 editor.selections.display_ranges(cx),
1837 &[
1838 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1839 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
1840 ]
1841 );
1842
1843 // Moving to the beginning of the line again should still be a no-op for the first cursor,
1844 // and should move the second cursor back to the first non-whitespace character in the line.
1845 editor.move_to_beginning_of_line(&move_to_beg, window, cx);
1846 assert_eq!(
1847 editor.selections.display_ranges(cx),
1848 &[
1849 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
1850 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
1851 ]
1852 );
1853
1854 // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
1855 // and to the first non-whitespace character in the line for the second cursor.
1856 editor.move_to_end_of_line(&move_to_end, window, cx);
1857 editor.move_left(&MoveLeft, window, cx);
1858 editor.select_to_beginning_of_line(&select_to_beg, window, cx);
1859 assert_eq!(
1860 editor.selections.display_ranges(cx),
1861 &[
1862 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1863 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
1864 ]
1865 );
1866
1867 // Selecting to the beginning of the line again should be a no-op for the first cursor,
1868 // and should select to the beginning of the line for the second cursor.
1869 editor.select_to_beginning_of_line(&select_to_beg, window, cx);
1870 assert_eq!(
1871 editor.selections.display_ranges(cx),
1872 &[
1873 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
1874 DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
1875 ]
1876 );
1877
1878 // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
1879 // and should delete to the first non-whitespace character in the line for the second cursor.
1880 editor.move_to_end_of_line(&move_to_end, window, cx);
1881 editor.move_left(&MoveLeft, window, cx);
1882 editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
1883 assert_eq!(editor.text(cx), "c\n f");
1884 });
1885}
1886
1887#[gpui::test]
1888fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
1889 init_test(cx, |_| {});
1890
1891 let editor = cx.add_window(|window, cx| {
1892 let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n {baz.qux()}", cx);
1893 build_editor(buffer, window, cx)
1894 });
1895 _ = editor.update(cx, |editor, window, cx| {
1896 editor.change_selections(None, window, cx, |s| {
1897 s.select_display_ranges([
1898 DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
1899 DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
1900 ])
1901 });
1902
1903 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1904 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1905
1906 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1907 assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n ˇ{baz.qux()}", editor, cx);
1908
1909 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1910 assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ {baz.qux()}", editor, cx);
1911
1912 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1913 assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1914
1915 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1916 assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n {baz.qux()}", editor, cx);
1917
1918 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1919 assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n {baz.qux()}", editor, cx);
1920
1921 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1922 assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n {baz.qux()}", editor, cx);
1923
1924 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1925 assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n {ˇbaz.qux()}", editor, cx);
1926
1927 editor.move_right(&MoveRight, window, cx);
1928 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1929 assert_selection_ranges(
1930 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1931 editor,
1932 cx,
1933 );
1934
1935 editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
1936 assert_selection_ranges(
1937 "use std«ˇ::s»tr::{foo, bar}\n\n «ˇ{b»az.qux()}",
1938 editor,
1939 cx,
1940 );
1941
1942 editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
1943 assert_selection_ranges(
1944 "use std::«ˇs»tr::{foo, bar}\n\n {«ˇb»az.qux()}",
1945 editor,
1946 cx,
1947 );
1948 });
1949}
1950
1951#[gpui::test]
1952fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
1953 init_test(cx, |_| {});
1954
1955 let editor = cx.add_window(|window, cx| {
1956 let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
1957 build_editor(buffer, window, cx)
1958 });
1959
1960 _ = editor.update(cx, |editor, window, cx| {
1961 editor.set_wrap_width(Some(140.0.into()), cx);
1962 assert_eq!(
1963 editor.display_text(cx),
1964 "use one::{\n two::three::\n four::five\n};"
1965 );
1966
1967 editor.change_selections(None, window, cx, |s| {
1968 s.select_display_ranges([
1969 DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
1970 ]);
1971 });
1972
1973 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1974 assert_eq!(
1975 editor.selections.display_ranges(cx),
1976 &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
1977 );
1978
1979 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1980 assert_eq!(
1981 editor.selections.display_ranges(cx),
1982 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
1983 );
1984
1985 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1986 assert_eq!(
1987 editor.selections.display_ranges(cx),
1988 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
1989 );
1990
1991 editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
1992 assert_eq!(
1993 editor.selections.display_ranges(cx),
1994 &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
1995 );
1996
1997 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
1998 assert_eq!(
1999 editor.selections.display_ranges(cx),
2000 &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
2001 );
2002
2003 editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
2004 assert_eq!(
2005 editor.selections.display_ranges(cx),
2006 &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
2007 );
2008 });
2009}
2010
2011#[gpui::test]
2012async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
2013 init_test(cx, |_| {});
2014 let mut cx = EditorTestContext::new(cx).await;
2015
2016 let line_height = cx.editor(|editor, window, _| {
2017 editor
2018 .style()
2019 .unwrap()
2020 .text
2021 .line_height_in_pixels(window.rem_size())
2022 });
2023 cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
2024
2025 cx.set_state(
2026 &r#"ˇone
2027 two
2028
2029 three
2030 fourˇ
2031 five
2032
2033 six"#
2034 .unindent(),
2035 );
2036
2037 cx.update_editor(|editor, window, cx| {
2038 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2039 });
2040 cx.assert_editor_state(
2041 &r#"one
2042 two
2043 ˇ
2044 three
2045 four
2046 five
2047 ˇ
2048 six"#
2049 .unindent(),
2050 );
2051
2052 cx.update_editor(|editor, window, cx| {
2053 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2054 });
2055 cx.assert_editor_state(
2056 &r#"one
2057 two
2058
2059 three
2060 four
2061 five
2062 ˇ
2063 sixˇ"#
2064 .unindent(),
2065 );
2066
2067 cx.update_editor(|editor, window, cx| {
2068 editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
2069 });
2070 cx.assert_editor_state(
2071 &r#"one
2072 two
2073
2074 three
2075 four
2076 five
2077
2078 sixˇ"#
2079 .unindent(),
2080 );
2081
2082 cx.update_editor(|editor, window, cx| {
2083 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2084 });
2085 cx.assert_editor_state(
2086 &r#"one
2087 two
2088
2089 three
2090 four
2091 five
2092 ˇ
2093 six"#
2094 .unindent(),
2095 );
2096
2097 cx.update_editor(|editor, window, cx| {
2098 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2099 });
2100 cx.assert_editor_state(
2101 &r#"one
2102 two
2103 ˇ
2104 three
2105 four
2106 five
2107
2108 six"#
2109 .unindent(),
2110 );
2111
2112 cx.update_editor(|editor, window, cx| {
2113 editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
2114 });
2115 cx.assert_editor_state(
2116 &r#"ˇone
2117 two
2118
2119 three
2120 four
2121 five
2122
2123 six"#
2124 .unindent(),
2125 );
2126}
2127
2128#[gpui::test]
2129async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
2130 init_test(cx, |_| {});
2131 let mut cx = EditorTestContext::new(cx).await;
2132 let line_height = cx.editor(|editor, window, _| {
2133 editor
2134 .style()
2135 .unwrap()
2136 .text
2137 .line_height_in_pixels(window.rem_size())
2138 });
2139 let window = cx.window;
2140 cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
2141
2142 cx.set_state(
2143 r#"ˇone
2144 two
2145 three
2146 four
2147 five
2148 six
2149 seven
2150 eight
2151 nine
2152 ten
2153 "#,
2154 );
2155
2156 cx.update_editor(|editor, window, cx| {
2157 assert_eq!(
2158 editor.snapshot(window, cx).scroll_position(),
2159 gpui::Point::new(0., 0.)
2160 );
2161 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2162 assert_eq!(
2163 editor.snapshot(window, cx).scroll_position(),
2164 gpui::Point::new(0., 3.)
2165 );
2166 editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
2167 assert_eq!(
2168 editor.snapshot(window, cx).scroll_position(),
2169 gpui::Point::new(0., 6.)
2170 );
2171 editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
2172 assert_eq!(
2173 editor.snapshot(window, cx).scroll_position(),
2174 gpui::Point::new(0., 3.)
2175 );
2176
2177 editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
2178 assert_eq!(
2179 editor.snapshot(window, cx).scroll_position(),
2180 gpui::Point::new(0., 1.)
2181 );
2182 editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
2183 assert_eq!(
2184 editor.snapshot(window, cx).scroll_position(),
2185 gpui::Point::new(0., 3.)
2186 );
2187 });
2188}
2189
2190#[gpui::test]
2191async fn test_autoscroll(cx: &mut TestAppContext) {
2192 init_test(cx, |_| {});
2193 let mut cx = EditorTestContext::new(cx).await;
2194
2195 let line_height = cx.update_editor(|editor, window, cx| {
2196 editor.set_vertical_scroll_margin(2, cx);
2197 editor
2198 .style()
2199 .unwrap()
2200 .text
2201 .line_height_in_pixels(window.rem_size())
2202 });
2203 let window = cx.window;
2204 cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
2205
2206 cx.set_state(
2207 r#"ˇone
2208 two
2209 three
2210 four
2211 five
2212 six
2213 seven
2214 eight
2215 nine
2216 ten
2217 "#,
2218 );
2219 cx.update_editor(|editor, window, cx| {
2220 assert_eq!(
2221 editor.snapshot(window, cx).scroll_position(),
2222 gpui::Point::new(0., 0.0)
2223 );
2224 });
2225
2226 // Add a cursor below the visible area. Since both cursors cannot fit
2227 // on screen, the editor autoscrolls to reveal the newest cursor, and
2228 // allows the vertical scroll margin below that cursor.
2229 cx.update_editor(|editor, window, cx| {
2230 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2231 selections.select_ranges([
2232 Point::new(0, 0)..Point::new(0, 0),
2233 Point::new(6, 0)..Point::new(6, 0),
2234 ]);
2235 })
2236 });
2237 cx.update_editor(|editor, window, cx| {
2238 assert_eq!(
2239 editor.snapshot(window, cx).scroll_position(),
2240 gpui::Point::new(0., 3.0)
2241 );
2242 });
2243
2244 // Move down. The editor cursor scrolls down to track the newest cursor.
2245 cx.update_editor(|editor, window, cx| {
2246 editor.move_down(&Default::default(), window, cx);
2247 });
2248 cx.update_editor(|editor, window, cx| {
2249 assert_eq!(
2250 editor.snapshot(window, cx).scroll_position(),
2251 gpui::Point::new(0., 4.0)
2252 );
2253 });
2254
2255 // Add a cursor above the visible area. Since both cursors fit on screen,
2256 // the editor scrolls to show both.
2257 cx.update_editor(|editor, window, cx| {
2258 editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
2259 selections.select_ranges([
2260 Point::new(1, 0)..Point::new(1, 0),
2261 Point::new(6, 0)..Point::new(6, 0),
2262 ]);
2263 })
2264 });
2265 cx.update_editor(|editor, window, cx| {
2266 assert_eq!(
2267 editor.snapshot(window, cx).scroll_position(),
2268 gpui::Point::new(0., 1.0)
2269 );
2270 });
2271}
2272
2273#[gpui::test]
2274async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
2275 init_test(cx, |_| {});
2276 let mut cx = EditorTestContext::new(cx).await;
2277
2278 let line_height = cx.editor(|editor, window, _cx| {
2279 editor
2280 .style()
2281 .unwrap()
2282 .text
2283 .line_height_in_pixels(window.rem_size())
2284 });
2285 let window = cx.window;
2286 cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
2287 cx.set_state(
2288 &r#"
2289 ˇone
2290 two
2291 threeˇ
2292 four
2293 five
2294 six
2295 seven
2296 eight
2297 nine
2298 ten
2299 "#
2300 .unindent(),
2301 );
2302
2303 cx.update_editor(|editor, window, cx| {
2304 editor.move_page_down(&MovePageDown::default(), window, cx)
2305 });
2306 cx.assert_editor_state(
2307 &r#"
2308 one
2309 two
2310 three
2311 ˇfour
2312 five
2313 sixˇ
2314 seven
2315 eight
2316 nine
2317 ten
2318 "#
2319 .unindent(),
2320 );
2321
2322 cx.update_editor(|editor, window, cx| {
2323 editor.move_page_down(&MovePageDown::default(), window, cx)
2324 });
2325 cx.assert_editor_state(
2326 &r#"
2327 one
2328 two
2329 three
2330 four
2331 five
2332 six
2333 ˇseven
2334 eight
2335 nineˇ
2336 ten
2337 "#
2338 .unindent(),
2339 );
2340
2341 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2342 cx.assert_editor_state(
2343 &r#"
2344 one
2345 two
2346 three
2347 ˇfour
2348 five
2349 sixˇ
2350 seven
2351 eight
2352 nine
2353 ten
2354 "#
2355 .unindent(),
2356 );
2357
2358 cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
2359 cx.assert_editor_state(
2360 &r#"
2361 ˇone
2362 two
2363 threeˇ
2364 four
2365 five
2366 six
2367 seven
2368 eight
2369 nine
2370 ten
2371 "#
2372 .unindent(),
2373 );
2374
2375 // Test select collapsing
2376 cx.update_editor(|editor, window, cx| {
2377 editor.move_page_down(&MovePageDown::default(), window, cx);
2378 editor.move_page_down(&MovePageDown::default(), window, cx);
2379 editor.move_page_down(&MovePageDown::default(), window, cx);
2380 });
2381 cx.assert_editor_state(
2382 &r#"
2383 one
2384 two
2385 three
2386 four
2387 five
2388 six
2389 seven
2390 eight
2391 nine
2392 ˇten
2393 ˇ"#
2394 .unindent(),
2395 );
2396}
2397
2398#[gpui::test]
2399async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
2400 init_test(cx, |_| {});
2401 let mut cx = EditorTestContext::new(cx).await;
2402 cx.set_state("one «two threeˇ» four");
2403 cx.update_editor(|editor, window, cx| {
2404 editor.delete_to_beginning_of_line(
2405 &DeleteToBeginningOfLine {
2406 stop_at_indent: false,
2407 },
2408 window,
2409 cx,
2410 );
2411 assert_eq!(editor.text(cx), " four");
2412 });
2413}
2414
2415#[gpui::test]
2416fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
2417 init_test(cx, |_| {});
2418
2419 let editor = cx.add_window(|window, cx| {
2420 let buffer = MultiBuffer::build_simple("one two three four", cx);
2421 build_editor(buffer.clone(), window, cx)
2422 });
2423
2424 _ = editor.update(cx, |editor, window, cx| {
2425 editor.change_selections(None, window, cx, |s| {
2426 s.select_display_ranges([
2427 // an empty selection - the preceding word fragment is deleted
2428 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2429 // characters selected - they are deleted
2430 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
2431 ])
2432 });
2433 editor.delete_to_previous_word_start(
2434 &DeleteToPreviousWordStart {
2435 ignore_newlines: false,
2436 },
2437 window,
2438 cx,
2439 );
2440 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
2441 });
2442
2443 _ = editor.update(cx, |editor, window, cx| {
2444 editor.change_selections(None, window, cx, |s| {
2445 s.select_display_ranges([
2446 // an empty selection - the following word fragment is deleted
2447 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
2448 // characters selected - they are deleted
2449 DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
2450 ])
2451 });
2452 editor.delete_to_next_word_end(
2453 &DeleteToNextWordEnd {
2454 ignore_newlines: false,
2455 },
2456 window,
2457 cx,
2458 );
2459 assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
2460 });
2461}
2462
2463#[gpui::test]
2464fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
2465 init_test(cx, |_| {});
2466
2467 let editor = cx.add_window(|window, cx| {
2468 let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
2469 build_editor(buffer.clone(), window, cx)
2470 });
2471 let del_to_prev_word_start = DeleteToPreviousWordStart {
2472 ignore_newlines: false,
2473 };
2474 let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
2475 ignore_newlines: true,
2476 };
2477
2478 _ = editor.update(cx, |editor, window, cx| {
2479 editor.change_selections(None, window, cx, |s| {
2480 s.select_display_ranges([
2481 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
2482 ])
2483 });
2484 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2485 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
2486 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2487 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
2488 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2489 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
2490 editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
2491 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
2492 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2493 assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
2494 editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
2495 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2496 });
2497}
2498
2499#[gpui::test]
2500fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
2501 init_test(cx, |_| {});
2502
2503 let editor = cx.add_window(|window, cx| {
2504 let buffer = MultiBuffer::build_simple("\none\n two\nthree\n four", cx);
2505 build_editor(buffer.clone(), window, cx)
2506 });
2507 let del_to_next_word_end = DeleteToNextWordEnd {
2508 ignore_newlines: false,
2509 };
2510 let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
2511 ignore_newlines: true,
2512 };
2513
2514 _ = editor.update(cx, |editor, window, cx| {
2515 editor.change_selections(None, window, cx, |s| {
2516 s.select_display_ranges([
2517 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
2518 ])
2519 });
2520 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2521 assert_eq!(
2522 editor.buffer.read(cx).read(cx).text(),
2523 "one\n two\nthree\n four"
2524 );
2525 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2526 assert_eq!(
2527 editor.buffer.read(cx).read(cx).text(),
2528 "\n two\nthree\n four"
2529 );
2530 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2531 assert_eq!(
2532 editor.buffer.read(cx).read(cx).text(),
2533 "two\nthree\n four"
2534 );
2535 editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
2536 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n four");
2537 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2538 assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n four");
2539 editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
2540 assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
2541 });
2542}
2543
2544#[gpui::test]
2545fn test_newline(cx: &mut TestAppContext) {
2546 init_test(cx, |_| {});
2547
2548 let editor = cx.add_window(|window, cx| {
2549 let buffer = MultiBuffer::build_simple("aaaa\n bbbb\n", cx);
2550 build_editor(buffer.clone(), window, cx)
2551 });
2552
2553 _ = editor.update(cx, |editor, window, cx| {
2554 editor.change_selections(None, window, cx, |s| {
2555 s.select_display_ranges([
2556 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
2557 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
2558 DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
2559 ])
2560 });
2561
2562 editor.newline(&Newline, window, cx);
2563 assert_eq!(editor.text(cx), "aa\naa\n \n bb\n bb\n");
2564 });
2565}
2566
2567#[gpui::test]
2568fn test_newline_with_old_selections(cx: &mut TestAppContext) {
2569 init_test(cx, |_| {});
2570
2571 let editor = cx.add_window(|window, cx| {
2572 let buffer = MultiBuffer::build_simple(
2573 "
2574 a
2575 b(
2576 X
2577 )
2578 c(
2579 X
2580 )
2581 "
2582 .unindent()
2583 .as_str(),
2584 cx,
2585 );
2586 let mut editor = build_editor(buffer.clone(), window, cx);
2587 editor.change_selections(None, window, cx, |s| {
2588 s.select_ranges([
2589 Point::new(2, 4)..Point::new(2, 5),
2590 Point::new(5, 4)..Point::new(5, 5),
2591 ])
2592 });
2593 editor
2594 });
2595
2596 _ = editor.update(cx, |editor, window, cx| {
2597 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2598 editor.buffer.update(cx, |buffer, cx| {
2599 buffer.edit(
2600 [
2601 (Point::new(1, 2)..Point::new(3, 0), ""),
2602 (Point::new(4, 2)..Point::new(6, 0), ""),
2603 ],
2604 None,
2605 cx,
2606 );
2607 assert_eq!(
2608 buffer.read(cx).text(),
2609 "
2610 a
2611 b()
2612 c()
2613 "
2614 .unindent()
2615 );
2616 });
2617 assert_eq!(
2618 editor.selections.ranges(cx),
2619 &[
2620 Point::new(1, 2)..Point::new(1, 2),
2621 Point::new(2, 2)..Point::new(2, 2),
2622 ],
2623 );
2624
2625 editor.newline(&Newline, window, cx);
2626 assert_eq!(
2627 editor.text(cx),
2628 "
2629 a
2630 b(
2631 )
2632 c(
2633 )
2634 "
2635 .unindent()
2636 );
2637
2638 // The selections are moved after the inserted newlines
2639 assert_eq!(
2640 editor.selections.ranges(cx),
2641 &[
2642 Point::new(2, 0)..Point::new(2, 0),
2643 Point::new(4, 0)..Point::new(4, 0),
2644 ],
2645 );
2646 });
2647}
2648
2649#[gpui::test]
2650async fn test_newline_above(cx: &mut TestAppContext) {
2651 init_test(cx, |settings| {
2652 settings.defaults.tab_size = NonZeroU32::new(4)
2653 });
2654
2655 let language = Arc::new(
2656 Language::new(
2657 LanguageConfig::default(),
2658 Some(tree_sitter_rust::LANGUAGE.into()),
2659 )
2660 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2661 .unwrap(),
2662 );
2663
2664 let mut cx = EditorTestContext::new(cx).await;
2665 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2666 cx.set_state(indoc! {"
2667 const a: ˇA = (
2668 (ˇ
2669 «const_functionˇ»(ˇ),
2670 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2671 )ˇ
2672 ˇ);ˇ
2673 "});
2674
2675 cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
2676 cx.assert_editor_state(indoc! {"
2677 ˇ
2678 const a: A = (
2679 ˇ
2680 (
2681 ˇ
2682 ˇ
2683 const_function(),
2684 ˇ
2685 ˇ
2686 ˇ
2687 ˇ
2688 something_else,
2689 ˇ
2690 )
2691 ˇ
2692 ˇ
2693 );
2694 "});
2695}
2696
2697#[gpui::test]
2698async fn test_newline_below(cx: &mut TestAppContext) {
2699 init_test(cx, |settings| {
2700 settings.defaults.tab_size = NonZeroU32::new(4)
2701 });
2702
2703 let language = Arc::new(
2704 Language::new(
2705 LanguageConfig::default(),
2706 Some(tree_sitter_rust::LANGUAGE.into()),
2707 )
2708 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2709 .unwrap(),
2710 );
2711
2712 let mut cx = EditorTestContext::new(cx).await;
2713 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2714 cx.set_state(indoc! {"
2715 const a: ˇA = (
2716 (ˇ
2717 «const_functionˇ»(ˇ),
2718 so«mˇ»et«hˇ»ing_ˇelse,ˇ
2719 )ˇ
2720 ˇ);ˇ
2721 "});
2722
2723 cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
2724 cx.assert_editor_state(indoc! {"
2725 const a: A = (
2726 ˇ
2727 (
2728 ˇ
2729 const_function(),
2730 ˇ
2731 ˇ
2732 something_else,
2733 ˇ
2734 ˇ
2735 ˇ
2736 ˇ
2737 )
2738 ˇ
2739 );
2740 ˇ
2741 ˇ
2742 "});
2743}
2744
2745#[gpui::test]
2746async fn test_newline_comments(cx: &mut TestAppContext) {
2747 init_test(cx, |settings| {
2748 settings.defaults.tab_size = NonZeroU32::new(4)
2749 });
2750
2751 let language = Arc::new(Language::new(
2752 LanguageConfig {
2753 line_comments: vec!["//".into()],
2754 ..LanguageConfig::default()
2755 },
2756 None,
2757 ));
2758 {
2759 let mut cx = EditorTestContext::new(cx).await;
2760 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2761 cx.set_state(indoc! {"
2762 // Fooˇ
2763 "});
2764
2765 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2766 cx.assert_editor_state(indoc! {"
2767 // Foo
2768 //ˇ
2769 "});
2770 // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
2771 cx.set_state(indoc! {"
2772 ˇ// Foo
2773 "});
2774 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2775 cx.assert_editor_state(indoc! {"
2776
2777 ˇ// Foo
2778 "});
2779 }
2780 // Ensure that comment continuations can be disabled.
2781 update_test_language_settings(cx, |settings| {
2782 settings.defaults.extend_comment_on_newline = Some(false);
2783 });
2784 let mut cx = EditorTestContext::new(cx).await;
2785 cx.set_state(indoc! {"
2786 // Fooˇ
2787 "});
2788 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
2789 cx.assert_editor_state(indoc! {"
2790 // Foo
2791 ˇ
2792 "});
2793}
2794
2795#[gpui::test]
2796fn test_insert_with_old_selections(cx: &mut TestAppContext) {
2797 init_test(cx, |_| {});
2798
2799 let editor = cx.add_window(|window, cx| {
2800 let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
2801 let mut editor = build_editor(buffer.clone(), window, cx);
2802 editor.change_selections(None, window, cx, |s| {
2803 s.select_ranges([3..4, 11..12, 19..20])
2804 });
2805 editor
2806 });
2807
2808 _ = editor.update(cx, |editor, window, cx| {
2809 // Edit the buffer directly, deleting ranges surrounding the editor's selections
2810 editor.buffer.update(cx, |buffer, cx| {
2811 buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
2812 assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
2813 });
2814 assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
2815
2816 editor.insert("Z", window, cx);
2817 assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
2818
2819 // The selections are moved after the inserted characters
2820 assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
2821 });
2822}
2823
2824#[gpui::test]
2825async fn test_tab(cx: &mut TestAppContext) {
2826 init_test(cx, |settings| {
2827 settings.defaults.tab_size = NonZeroU32::new(3)
2828 });
2829
2830 let mut cx = EditorTestContext::new(cx).await;
2831 cx.set_state(indoc! {"
2832 ˇabˇc
2833 ˇ🏀ˇ🏀ˇefg
2834 dˇ
2835 "});
2836 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2837 cx.assert_editor_state(indoc! {"
2838 ˇab ˇc
2839 ˇ🏀 ˇ🏀 ˇefg
2840 d ˇ
2841 "});
2842
2843 cx.set_state(indoc! {"
2844 a
2845 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2846 "});
2847 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2848 cx.assert_editor_state(indoc! {"
2849 a
2850 «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
2851 "});
2852}
2853
2854#[gpui::test]
2855async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
2856 init_test(cx, |_| {});
2857
2858 let mut cx = EditorTestContext::new(cx).await;
2859 let language = Arc::new(
2860 Language::new(
2861 LanguageConfig::default(),
2862 Some(tree_sitter_rust::LANGUAGE.into()),
2863 )
2864 .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
2865 .unwrap(),
2866 );
2867 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2868
2869 // cursors that are already at the suggested indent level insert
2870 // a soft tab. cursors that are to the left of the suggested indent
2871 // auto-indent their line.
2872 cx.set_state(indoc! {"
2873 ˇ
2874 const a: B = (
2875 c(
2876 d(
2877 ˇ
2878 )
2879 ˇ
2880 ˇ )
2881 );
2882 "});
2883 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2884 cx.assert_editor_state(indoc! {"
2885 ˇ
2886 const a: B = (
2887 c(
2888 d(
2889 ˇ
2890 )
2891 ˇ
2892 ˇ)
2893 );
2894 "});
2895
2896 // handle auto-indent when there are multiple cursors on the same line
2897 cx.set_state(indoc! {"
2898 const a: B = (
2899 c(
2900 ˇ ˇ
2901 ˇ )
2902 );
2903 "});
2904 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2905 cx.assert_editor_state(indoc! {"
2906 const a: B = (
2907 c(
2908 ˇ
2909 ˇ)
2910 );
2911 "});
2912}
2913
2914#[gpui::test]
2915async fn test_tab_with_mixed_whitespace(cx: &mut TestAppContext) {
2916 init_test(cx, |settings| {
2917 settings.defaults.tab_size = NonZeroU32::new(4)
2918 });
2919
2920 let language = Arc::new(
2921 Language::new(
2922 LanguageConfig::default(),
2923 Some(tree_sitter_rust::LANGUAGE.into()),
2924 )
2925 .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
2926 .unwrap(),
2927 );
2928
2929 let mut cx = EditorTestContext::new(cx).await;
2930 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
2931 cx.set_state(indoc! {"
2932 fn a() {
2933 if b {
2934 \t ˇc
2935 }
2936 }
2937 "});
2938
2939 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2940 cx.assert_editor_state(indoc! {"
2941 fn a() {
2942 if b {
2943 ˇc
2944 }
2945 }
2946 "});
2947}
2948
2949#[gpui::test]
2950async fn test_indent_outdent(cx: &mut TestAppContext) {
2951 init_test(cx, |settings| {
2952 settings.defaults.tab_size = NonZeroU32::new(4);
2953 });
2954
2955 let mut cx = EditorTestContext::new(cx).await;
2956
2957 cx.set_state(indoc! {"
2958 «oneˇ» «twoˇ»
2959 three
2960 four
2961 "});
2962 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2963 cx.assert_editor_state(indoc! {"
2964 «oneˇ» «twoˇ»
2965 three
2966 four
2967 "});
2968
2969 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
2970 cx.assert_editor_state(indoc! {"
2971 «oneˇ» «twoˇ»
2972 three
2973 four
2974 "});
2975
2976 // select across line ending
2977 cx.set_state(indoc! {"
2978 one two
2979 t«hree
2980 ˇ» four
2981 "});
2982 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
2983 cx.assert_editor_state(indoc! {"
2984 one two
2985 t«hree
2986 ˇ» four
2987 "});
2988
2989 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
2990 cx.assert_editor_state(indoc! {"
2991 one two
2992 t«hree
2993 ˇ» four
2994 "});
2995
2996 // Ensure that indenting/outdenting works when the cursor is at column 0.
2997 cx.set_state(indoc! {"
2998 one two
2999 ˇthree
3000 four
3001 "});
3002 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3003 cx.assert_editor_state(indoc! {"
3004 one two
3005 ˇthree
3006 four
3007 "});
3008
3009 cx.set_state(indoc! {"
3010 one two
3011 ˇ three
3012 four
3013 "});
3014 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3015 cx.assert_editor_state(indoc! {"
3016 one two
3017 ˇthree
3018 four
3019 "});
3020}
3021
3022#[gpui::test]
3023async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
3024 init_test(cx, |settings| {
3025 settings.defaults.hard_tabs = Some(true);
3026 });
3027
3028 let mut cx = EditorTestContext::new(cx).await;
3029
3030 // select two ranges on one line
3031 cx.set_state(indoc! {"
3032 «oneˇ» «twoˇ»
3033 three
3034 four
3035 "});
3036 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3037 cx.assert_editor_state(indoc! {"
3038 \t«oneˇ» «twoˇ»
3039 three
3040 four
3041 "});
3042 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3043 cx.assert_editor_state(indoc! {"
3044 \t\t«oneˇ» «twoˇ»
3045 three
3046 four
3047 "});
3048 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3049 cx.assert_editor_state(indoc! {"
3050 \t«oneˇ» «twoˇ»
3051 three
3052 four
3053 "});
3054 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3055 cx.assert_editor_state(indoc! {"
3056 «oneˇ» «twoˇ»
3057 three
3058 four
3059 "});
3060
3061 // select across a line ending
3062 cx.set_state(indoc! {"
3063 one two
3064 t«hree
3065 ˇ»four
3066 "});
3067 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3068 cx.assert_editor_state(indoc! {"
3069 one two
3070 \tt«hree
3071 ˇ»four
3072 "});
3073 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3074 cx.assert_editor_state(indoc! {"
3075 one two
3076 \t\tt«hree
3077 ˇ»four
3078 "});
3079 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3080 cx.assert_editor_state(indoc! {"
3081 one two
3082 \tt«hree
3083 ˇ»four
3084 "});
3085 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3086 cx.assert_editor_state(indoc! {"
3087 one two
3088 t«hree
3089 ˇ»four
3090 "});
3091
3092 // Ensure that indenting/outdenting works when the cursor is at column 0.
3093 cx.set_state(indoc! {"
3094 one two
3095 ˇthree
3096 four
3097 "});
3098 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3099 cx.assert_editor_state(indoc! {"
3100 one two
3101 ˇthree
3102 four
3103 "});
3104 cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
3105 cx.assert_editor_state(indoc! {"
3106 one two
3107 \tˇthree
3108 four
3109 "});
3110 cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
3111 cx.assert_editor_state(indoc! {"
3112 one two
3113 ˇthree
3114 four
3115 "});
3116}
3117
3118#[gpui::test]
3119fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
3120 init_test(cx, |settings| {
3121 settings.languages.extend([
3122 (
3123 "TOML".into(),
3124 LanguageSettingsContent {
3125 tab_size: NonZeroU32::new(2),
3126 ..Default::default()
3127 },
3128 ),
3129 (
3130 "Rust".into(),
3131 LanguageSettingsContent {
3132 tab_size: NonZeroU32::new(4),
3133 ..Default::default()
3134 },
3135 ),
3136 ]);
3137 });
3138
3139 let toml_language = Arc::new(Language::new(
3140 LanguageConfig {
3141 name: "TOML".into(),
3142 ..Default::default()
3143 },
3144 None,
3145 ));
3146 let rust_language = Arc::new(Language::new(
3147 LanguageConfig {
3148 name: "Rust".into(),
3149 ..Default::default()
3150 },
3151 None,
3152 ));
3153
3154 let toml_buffer =
3155 cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
3156 let rust_buffer =
3157 cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
3158 let multibuffer = cx.new(|cx| {
3159 let mut multibuffer = MultiBuffer::new(ReadWrite);
3160 multibuffer.push_excerpts(
3161 toml_buffer.clone(),
3162 [ExcerptRange {
3163 context: Point::new(0, 0)..Point::new(2, 0),
3164 primary: None,
3165 }],
3166 cx,
3167 );
3168 multibuffer.push_excerpts(
3169 rust_buffer.clone(),
3170 [ExcerptRange {
3171 context: Point::new(0, 0)..Point::new(1, 0),
3172 primary: None,
3173 }],
3174 cx,
3175 );
3176 multibuffer
3177 });
3178
3179 cx.add_window(|window, cx| {
3180 let mut editor = build_editor(multibuffer, window, cx);
3181
3182 assert_eq!(
3183 editor.text(cx),
3184 indoc! {"
3185 a = 1
3186 b = 2
3187
3188 const c: usize = 3;
3189 "}
3190 );
3191
3192 select_ranges(
3193 &mut editor,
3194 indoc! {"
3195 «aˇ» = 1
3196 b = 2
3197
3198 «const c:ˇ» usize = 3;
3199 "},
3200 window,
3201 cx,
3202 );
3203
3204 editor.tab(&Tab, window, cx);
3205 assert_text_with_selections(
3206 &mut editor,
3207 indoc! {"
3208 «aˇ» = 1
3209 b = 2
3210
3211 «const c:ˇ» usize = 3;
3212 "},
3213 cx,
3214 );
3215 editor.backtab(&Backtab, window, cx);
3216 assert_text_with_selections(
3217 &mut editor,
3218 indoc! {"
3219 «aˇ» = 1
3220 b = 2
3221
3222 «const c:ˇ» usize = 3;
3223 "},
3224 cx,
3225 );
3226
3227 editor
3228 });
3229}
3230
3231#[gpui::test]
3232async fn test_backspace(cx: &mut TestAppContext) {
3233 init_test(cx, |_| {});
3234
3235 let mut cx = EditorTestContext::new(cx).await;
3236
3237 // Basic backspace
3238 cx.set_state(indoc! {"
3239 onˇe two three
3240 fou«rˇ» five six
3241 seven «ˇeight nine
3242 »ten
3243 "});
3244 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3245 cx.assert_editor_state(indoc! {"
3246 oˇe two three
3247 fouˇ five six
3248 seven ˇten
3249 "});
3250
3251 // Test backspace inside and around indents
3252 cx.set_state(indoc! {"
3253 zero
3254 ˇone
3255 ˇtwo
3256 ˇ ˇ ˇ three
3257 ˇ ˇ four
3258 "});
3259 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3260 cx.assert_editor_state(indoc! {"
3261 zero
3262 ˇone
3263 ˇtwo
3264 ˇ threeˇ four
3265 "});
3266
3267 // Test backspace with line_mode set to true
3268 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3269 cx.set_state(indoc! {"
3270 The ˇquick ˇbrown
3271 fox jumps over
3272 the lazy dog
3273 ˇThe qu«ick bˇ»rown"});
3274 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3275 cx.assert_editor_state(indoc! {"
3276 ˇfox jumps over
3277 the lazy dogˇ"});
3278}
3279
3280#[gpui::test]
3281async fn test_delete(cx: &mut TestAppContext) {
3282 init_test(cx, |_| {});
3283
3284 let mut cx = EditorTestContext::new(cx).await;
3285 cx.set_state(indoc! {"
3286 onˇe two three
3287 fou«rˇ» five six
3288 seven «ˇeight nine
3289 »ten
3290 "});
3291 cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
3292 cx.assert_editor_state(indoc! {"
3293 onˇ two three
3294 fouˇ five six
3295 seven ˇten
3296 "});
3297
3298 // Test backspace with line_mode set to true
3299 cx.update_editor(|e, _, _| e.selections.line_mode = true);
3300 cx.set_state(indoc! {"
3301 The ˇquick ˇbrown
3302 fox «ˇjum»ps over
3303 the lazy dog
3304 ˇThe qu«ick bˇ»rown"});
3305 cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
3306 cx.assert_editor_state("ˇthe lazy dogˇ");
3307}
3308
3309#[gpui::test]
3310fn test_delete_line(cx: &mut TestAppContext) {
3311 init_test(cx, |_| {});
3312
3313 let editor = cx.add_window(|window, cx| {
3314 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3315 build_editor(buffer, window, cx)
3316 });
3317 _ = editor.update(cx, |editor, window, cx| {
3318 editor.change_selections(None, window, cx, |s| {
3319 s.select_display_ranges([
3320 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
3321 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
3322 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
3323 ])
3324 });
3325 editor.delete_line(&DeleteLine, window, cx);
3326 assert_eq!(editor.display_text(cx), "ghi");
3327 assert_eq!(
3328 editor.selections.display_ranges(cx),
3329 vec![
3330 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
3331 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
3332 ]
3333 );
3334 });
3335
3336 let editor = cx.add_window(|window, cx| {
3337 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
3338 build_editor(buffer, window, cx)
3339 });
3340 _ = editor.update(cx, |editor, window, cx| {
3341 editor.change_selections(None, window, cx, |s| {
3342 s.select_display_ranges([
3343 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
3344 ])
3345 });
3346 editor.delete_line(&DeleteLine, window, cx);
3347 assert_eq!(editor.display_text(cx), "ghi\n");
3348 assert_eq!(
3349 editor.selections.display_ranges(cx),
3350 vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
3351 );
3352 });
3353}
3354
3355#[gpui::test]
3356fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
3357 init_test(cx, |_| {});
3358
3359 cx.add_window(|window, cx| {
3360 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3361 let mut editor = build_editor(buffer.clone(), window, cx);
3362 let buffer = buffer.read(cx).as_singleton().unwrap();
3363
3364 assert_eq!(
3365 editor.selections.ranges::<Point>(cx),
3366 &[Point::new(0, 0)..Point::new(0, 0)]
3367 );
3368
3369 // When on single line, replace newline at end by space
3370 editor.join_lines(&JoinLines, window, cx);
3371 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3372 assert_eq!(
3373 editor.selections.ranges::<Point>(cx),
3374 &[Point::new(0, 3)..Point::new(0, 3)]
3375 );
3376
3377 // When multiple lines are selected, remove newlines that are spanned by the selection
3378 editor.change_selections(None, window, cx, |s| {
3379 s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
3380 });
3381 editor.join_lines(&JoinLines, window, cx);
3382 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
3383 assert_eq!(
3384 editor.selections.ranges::<Point>(cx),
3385 &[Point::new(0, 11)..Point::new(0, 11)]
3386 );
3387
3388 // Undo should be transactional
3389 editor.undo(&Undo, window, cx);
3390 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
3391 assert_eq!(
3392 editor.selections.ranges::<Point>(cx),
3393 &[Point::new(0, 5)..Point::new(2, 2)]
3394 );
3395
3396 // When joining an empty line don't insert a space
3397 editor.change_selections(None, window, cx, |s| {
3398 s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
3399 });
3400 editor.join_lines(&JoinLines, window, cx);
3401 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
3402 assert_eq!(
3403 editor.selections.ranges::<Point>(cx),
3404 [Point::new(2, 3)..Point::new(2, 3)]
3405 );
3406
3407 // We can remove trailing newlines
3408 editor.join_lines(&JoinLines, window, cx);
3409 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3410 assert_eq!(
3411 editor.selections.ranges::<Point>(cx),
3412 [Point::new(2, 3)..Point::new(2, 3)]
3413 );
3414
3415 // We don't blow up on the last line
3416 editor.join_lines(&JoinLines, window, cx);
3417 assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
3418 assert_eq!(
3419 editor.selections.ranges::<Point>(cx),
3420 [Point::new(2, 3)..Point::new(2, 3)]
3421 );
3422
3423 // reset to test indentation
3424 editor.buffer.update(cx, |buffer, cx| {
3425 buffer.edit(
3426 [
3427 (Point::new(1, 0)..Point::new(1, 2), " "),
3428 (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
3429 ],
3430 None,
3431 cx,
3432 )
3433 });
3434
3435 // We remove any leading spaces
3436 assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
3437 editor.change_selections(None, window, cx, |s| {
3438 s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
3439 });
3440 editor.join_lines(&JoinLines, window, cx);
3441 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
3442
3443 // We don't insert a space for a line containing only spaces
3444 editor.join_lines(&JoinLines, window, cx);
3445 assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
3446
3447 // We ignore any leading tabs
3448 editor.join_lines(&JoinLines, window, cx);
3449 assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
3450
3451 editor
3452 });
3453}
3454
3455#[gpui::test]
3456fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
3457 init_test(cx, |_| {});
3458
3459 cx.add_window(|window, cx| {
3460 let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
3461 let mut editor = build_editor(buffer.clone(), window, cx);
3462 let buffer = buffer.read(cx).as_singleton().unwrap();
3463
3464 editor.change_selections(None, window, cx, |s| {
3465 s.select_ranges([
3466 Point::new(0, 2)..Point::new(1, 1),
3467 Point::new(1, 2)..Point::new(1, 2),
3468 Point::new(3, 1)..Point::new(3, 2),
3469 ])
3470 });
3471
3472 editor.join_lines(&JoinLines, window, cx);
3473 assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
3474
3475 assert_eq!(
3476 editor.selections.ranges::<Point>(cx),
3477 [
3478 Point::new(0, 7)..Point::new(0, 7),
3479 Point::new(1, 3)..Point::new(1, 3)
3480 ]
3481 );
3482 editor
3483 });
3484}
3485
3486#[gpui::test]
3487async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
3488 init_test(cx, |_| {});
3489
3490 let mut cx = EditorTestContext::new(cx).await;
3491
3492 let diff_base = r#"
3493 Line 0
3494 Line 1
3495 Line 2
3496 Line 3
3497 "#
3498 .unindent();
3499
3500 cx.set_state(
3501 &r#"
3502 ˇLine 0
3503 Line 1
3504 Line 2
3505 Line 3
3506 "#
3507 .unindent(),
3508 );
3509
3510 cx.set_head_text(&diff_base);
3511 executor.run_until_parked();
3512
3513 // Join lines
3514 cx.update_editor(|editor, window, cx| {
3515 editor.join_lines(&JoinLines, window, cx);
3516 });
3517 executor.run_until_parked();
3518
3519 cx.assert_editor_state(
3520 &r#"
3521 Line 0ˇ Line 1
3522 Line 2
3523 Line 3
3524 "#
3525 .unindent(),
3526 );
3527 // Join again
3528 cx.update_editor(|editor, window, cx| {
3529 editor.join_lines(&JoinLines, window, cx);
3530 });
3531 executor.run_until_parked();
3532
3533 cx.assert_editor_state(
3534 &r#"
3535 Line 0 Line 1ˇ Line 2
3536 Line 3
3537 "#
3538 .unindent(),
3539 );
3540}
3541
3542#[gpui::test]
3543async fn test_custom_newlines_cause_no_false_positive_diffs(
3544 executor: BackgroundExecutor,
3545 cx: &mut TestAppContext,
3546) {
3547 init_test(cx, |_| {});
3548 let mut cx = EditorTestContext::new(cx).await;
3549 cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
3550 cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
3551 executor.run_until_parked();
3552
3553 cx.update_editor(|editor, window, cx| {
3554 let snapshot = editor.snapshot(window, cx);
3555 assert_eq!(
3556 snapshot
3557 .buffer_snapshot
3558 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
3559 .collect::<Vec<_>>(),
3560 Vec::new(),
3561 "Should not have any diffs for files with custom newlines"
3562 );
3563 });
3564}
3565
3566#[gpui::test]
3567async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
3568 init_test(cx, |_| {});
3569
3570 let mut cx = EditorTestContext::new(cx).await;
3571
3572 // Test sort_lines_case_insensitive()
3573 cx.set_state(indoc! {"
3574 «z
3575 y
3576 x
3577 Z
3578 Y
3579 Xˇ»
3580 "});
3581 cx.update_editor(|e, window, cx| {
3582 e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
3583 });
3584 cx.assert_editor_state(indoc! {"
3585 «x
3586 X
3587 y
3588 Y
3589 z
3590 Zˇ»
3591 "});
3592
3593 // Test reverse_lines()
3594 cx.set_state(indoc! {"
3595 «5
3596 4
3597 3
3598 2
3599 1ˇ»
3600 "});
3601 cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
3602 cx.assert_editor_state(indoc! {"
3603 «1
3604 2
3605 3
3606 4
3607 5ˇ»
3608 "});
3609
3610 // Skip testing shuffle_line()
3611
3612 // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
3613 // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
3614
3615 // Don't manipulate when cursor is on single line, but expand the selection
3616 cx.set_state(indoc! {"
3617 ddˇdd
3618 ccc
3619 bb
3620 a
3621 "});
3622 cx.update_editor(|e, window, cx| {
3623 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3624 });
3625 cx.assert_editor_state(indoc! {"
3626 «ddddˇ»
3627 ccc
3628 bb
3629 a
3630 "});
3631
3632 // Basic manipulate case
3633 // Start selection moves to column 0
3634 // End of selection shrinks to fit shorter line
3635 cx.set_state(indoc! {"
3636 dd«d
3637 ccc
3638 bb
3639 aaaaaˇ»
3640 "});
3641 cx.update_editor(|e, window, cx| {
3642 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3643 });
3644 cx.assert_editor_state(indoc! {"
3645 «aaaaa
3646 bb
3647 ccc
3648 dddˇ»
3649 "});
3650
3651 // Manipulate case with newlines
3652 cx.set_state(indoc! {"
3653 dd«d
3654 ccc
3655
3656 bb
3657 aaaaa
3658
3659 ˇ»
3660 "});
3661 cx.update_editor(|e, window, cx| {
3662 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3663 });
3664 cx.assert_editor_state(indoc! {"
3665 «
3666
3667 aaaaa
3668 bb
3669 ccc
3670 dddˇ»
3671
3672 "});
3673
3674 // Adding new line
3675 cx.set_state(indoc! {"
3676 aa«a
3677 bbˇ»b
3678 "});
3679 cx.update_editor(|e, window, cx| {
3680 e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
3681 });
3682 cx.assert_editor_state(indoc! {"
3683 «aaa
3684 bbb
3685 added_lineˇ»
3686 "});
3687
3688 // Removing line
3689 cx.set_state(indoc! {"
3690 aa«a
3691 bbbˇ»
3692 "});
3693 cx.update_editor(|e, window, cx| {
3694 e.manipulate_lines(window, cx, |lines| {
3695 lines.pop();
3696 })
3697 });
3698 cx.assert_editor_state(indoc! {"
3699 «aaaˇ»
3700 "});
3701
3702 // Removing all lines
3703 cx.set_state(indoc! {"
3704 aa«a
3705 bbbˇ»
3706 "});
3707 cx.update_editor(|e, window, cx| {
3708 e.manipulate_lines(window, cx, |lines| {
3709 lines.drain(..);
3710 })
3711 });
3712 cx.assert_editor_state(indoc! {"
3713 ˇ
3714 "});
3715}
3716
3717#[gpui::test]
3718async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
3719 init_test(cx, |_| {});
3720
3721 let mut cx = EditorTestContext::new(cx).await;
3722
3723 // Consider continuous selection as single selection
3724 cx.set_state(indoc! {"
3725 Aaa«aa
3726 cˇ»c«c
3727 bb
3728 aaaˇ»aa
3729 "});
3730 cx.update_editor(|e, window, cx| {
3731 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3732 });
3733 cx.assert_editor_state(indoc! {"
3734 «Aaaaa
3735 ccc
3736 bb
3737 aaaaaˇ»
3738 "});
3739
3740 cx.set_state(indoc! {"
3741 Aaa«aa
3742 cˇ»c«c
3743 bb
3744 aaaˇ»aa
3745 "});
3746 cx.update_editor(|e, window, cx| {
3747 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3748 });
3749 cx.assert_editor_state(indoc! {"
3750 «Aaaaa
3751 ccc
3752 bbˇ»
3753 "});
3754
3755 // Consider non continuous selection as distinct dedup operations
3756 cx.set_state(indoc! {"
3757 «aaaaa
3758 bb
3759 aaaaa
3760 aaaaaˇ»
3761
3762 aaa«aaˇ»
3763 "});
3764 cx.update_editor(|e, window, cx| {
3765 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3766 });
3767 cx.assert_editor_state(indoc! {"
3768 «aaaaa
3769 bbˇ»
3770
3771 «aaaaaˇ»
3772 "});
3773}
3774
3775#[gpui::test]
3776async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
3777 init_test(cx, |_| {});
3778
3779 let mut cx = EditorTestContext::new(cx).await;
3780
3781 cx.set_state(indoc! {"
3782 «Aaa
3783 aAa
3784 Aaaˇ»
3785 "});
3786 cx.update_editor(|e, window, cx| {
3787 e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
3788 });
3789 cx.assert_editor_state(indoc! {"
3790 «Aaa
3791 aAaˇ»
3792 "});
3793
3794 cx.set_state(indoc! {"
3795 «Aaa
3796 aAa
3797 aaAˇ»
3798 "});
3799 cx.update_editor(|e, window, cx| {
3800 e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
3801 });
3802 cx.assert_editor_state(indoc! {"
3803 «Aaaˇ»
3804 "});
3805}
3806
3807#[gpui::test]
3808async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
3809 init_test(cx, |_| {});
3810
3811 let mut cx = EditorTestContext::new(cx).await;
3812
3813 // Manipulate with multiple selections on a single line
3814 cx.set_state(indoc! {"
3815 dd«dd
3816 cˇ»c«c
3817 bb
3818 aaaˇ»aa
3819 "});
3820 cx.update_editor(|e, window, cx| {
3821 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3822 });
3823 cx.assert_editor_state(indoc! {"
3824 «aaaaa
3825 bb
3826 ccc
3827 ddddˇ»
3828 "});
3829
3830 // Manipulate with multiple disjoin selections
3831 cx.set_state(indoc! {"
3832 5«
3833 4
3834 3
3835 2
3836 1ˇ»
3837
3838 dd«dd
3839 ccc
3840 bb
3841 aaaˇ»aa
3842 "});
3843 cx.update_editor(|e, window, cx| {
3844 e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
3845 });
3846 cx.assert_editor_state(indoc! {"
3847 «1
3848 2
3849 3
3850 4
3851 5ˇ»
3852
3853 «aaaaa
3854 bb
3855 ccc
3856 ddddˇ»
3857 "});
3858
3859 // Adding lines on each selection
3860 cx.set_state(indoc! {"
3861 2«
3862 1ˇ»
3863
3864 bb«bb
3865 aaaˇ»aa
3866 "});
3867 cx.update_editor(|e, window, cx| {
3868 e.manipulate_lines(window, cx, |lines| lines.push("added line"))
3869 });
3870 cx.assert_editor_state(indoc! {"
3871 «2
3872 1
3873 added lineˇ»
3874
3875 «bbbb
3876 aaaaa
3877 added lineˇ»
3878 "});
3879
3880 // Removing lines on each selection
3881 cx.set_state(indoc! {"
3882 2«
3883 1ˇ»
3884
3885 bb«bb
3886 aaaˇ»aa
3887 "});
3888 cx.update_editor(|e, window, cx| {
3889 e.manipulate_lines(window, cx, |lines| {
3890 lines.pop();
3891 })
3892 });
3893 cx.assert_editor_state(indoc! {"
3894 «2ˇ»
3895
3896 «bbbbˇ»
3897 "});
3898}
3899
3900#[gpui::test]
3901async fn test_manipulate_text(cx: &mut TestAppContext) {
3902 init_test(cx, |_| {});
3903
3904 let mut cx = EditorTestContext::new(cx).await;
3905
3906 // Test convert_to_upper_case()
3907 cx.set_state(indoc! {"
3908 «hello worldˇ»
3909 "});
3910 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3911 cx.assert_editor_state(indoc! {"
3912 «HELLO WORLDˇ»
3913 "});
3914
3915 // Test convert_to_lower_case()
3916 cx.set_state(indoc! {"
3917 «HELLO WORLDˇ»
3918 "});
3919 cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
3920 cx.assert_editor_state(indoc! {"
3921 «hello worldˇ»
3922 "});
3923
3924 // Test multiple line, single selection case
3925 cx.set_state(indoc! {"
3926 «The quick brown
3927 fox jumps over
3928 the lazy dogˇ»
3929 "});
3930 cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
3931 cx.assert_editor_state(indoc! {"
3932 «The Quick Brown
3933 Fox Jumps Over
3934 The Lazy Dogˇ»
3935 "});
3936
3937 // Test multiple line, single selection case
3938 cx.set_state(indoc! {"
3939 «The quick brown
3940 fox jumps over
3941 the lazy dogˇ»
3942 "});
3943 cx.update_editor(|e, window, cx| {
3944 e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
3945 });
3946 cx.assert_editor_state(indoc! {"
3947 «TheQuickBrown
3948 FoxJumpsOver
3949 TheLazyDogˇ»
3950 "});
3951
3952 // From here on out, test more complex cases of manipulate_text()
3953
3954 // Test no selection case - should affect words cursors are in
3955 // Cursor at beginning, middle, and end of word
3956 cx.set_state(indoc! {"
3957 ˇhello big beauˇtiful worldˇ
3958 "});
3959 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3960 cx.assert_editor_state(indoc! {"
3961 «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
3962 "});
3963
3964 // Test multiple selections on a single line and across multiple lines
3965 cx.set_state(indoc! {"
3966 «Theˇ» quick «brown
3967 foxˇ» jumps «overˇ»
3968 the «lazyˇ» dog
3969 "});
3970 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3971 cx.assert_editor_state(indoc! {"
3972 «THEˇ» quick «BROWN
3973 FOXˇ» jumps «OVERˇ»
3974 the «LAZYˇ» dog
3975 "});
3976
3977 // Test case where text length grows
3978 cx.set_state(indoc! {"
3979 «tschüߡ»
3980 "});
3981 cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
3982 cx.assert_editor_state(indoc! {"
3983 «TSCHÜSSˇ»
3984 "});
3985
3986 // Test to make sure we don't crash when text shrinks
3987 cx.set_state(indoc! {"
3988 aaa_bbbˇ
3989 "});
3990 cx.update_editor(|e, window, cx| {
3991 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
3992 });
3993 cx.assert_editor_state(indoc! {"
3994 «aaaBbbˇ»
3995 "});
3996
3997 // Test to make sure we all aware of the fact that each word can grow and shrink
3998 // Final selections should be aware of this fact
3999 cx.set_state(indoc! {"
4000 aaa_bˇbb bbˇb_ccc ˇccc_ddd
4001 "});
4002 cx.update_editor(|e, window, cx| {
4003 e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
4004 });
4005 cx.assert_editor_state(indoc! {"
4006 «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
4007 "});
4008
4009 cx.set_state(indoc! {"
4010 «hElLo, WoRld!ˇ»
4011 "});
4012 cx.update_editor(|e, window, cx| {
4013 e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
4014 });
4015 cx.assert_editor_state(indoc! {"
4016 «HeLlO, wOrLD!ˇ»
4017 "});
4018}
4019
4020#[gpui::test]
4021fn test_duplicate_line(cx: &mut TestAppContext) {
4022 init_test(cx, |_| {});
4023
4024 let editor = cx.add_window(|window, cx| {
4025 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4026 build_editor(buffer, window, cx)
4027 });
4028 _ = editor.update(cx, |editor, window, cx| {
4029 editor.change_selections(None, window, cx, |s| {
4030 s.select_display_ranges([
4031 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4032 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4033 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4034 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4035 ])
4036 });
4037 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
4038 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
4039 assert_eq!(
4040 editor.selections.display_ranges(cx),
4041 vec![
4042 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
4043 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
4044 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4045 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
4046 ]
4047 );
4048 });
4049
4050 let editor = cx.add_window(|window, cx| {
4051 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4052 build_editor(buffer, window, cx)
4053 });
4054 _ = editor.update(cx, |editor, window, cx| {
4055 editor.change_selections(None, window, cx, |s| {
4056 s.select_display_ranges([
4057 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4058 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4059 ])
4060 });
4061 editor.duplicate_line_down(&DuplicateLineDown, window, cx);
4062 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4063 assert_eq!(
4064 editor.selections.display_ranges(cx),
4065 vec![
4066 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
4067 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
4068 ]
4069 );
4070 });
4071
4072 // With `move_upwards` the selections stay in place, except for
4073 // the lines inserted above them
4074 let editor = cx.add_window(|window, cx| {
4075 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4076 build_editor(buffer, window, cx)
4077 });
4078 _ = editor.update(cx, |editor, window, cx| {
4079 editor.change_selections(None, window, cx, |s| {
4080 s.select_display_ranges([
4081 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4082 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4083 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
4084 DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
4085 ])
4086 });
4087 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4088 assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
4089 assert_eq!(
4090 editor.selections.display_ranges(cx),
4091 vec![
4092 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
4093 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
4094 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
4095 DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
4096 ]
4097 );
4098 });
4099
4100 let editor = cx.add_window(|window, cx| {
4101 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4102 build_editor(buffer, window, cx)
4103 });
4104 _ = editor.update(cx, |editor, window, cx| {
4105 editor.change_selections(None, window, cx, |s| {
4106 s.select_display_ranges([
4107 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4108 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4109 ])
4110 });
4111 editor.duplicate_line_up(&DuplicateLineUp, window, cx);
4112 assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
4113 assert_eq!(
4114 editor.selections.display_ranges(cx),
4115 vec![
4116 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4117 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4118 ]
4119 );
4120 });
4121
4122 let editor = cx.add_window(|window, cx| {
4123 let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
4124 build_editor(buffer, window, cx)
4125 });
4126 _ = editor.update(cx, |editor, window, cx| {
4127 editor.change_selections(None, window, cx, |s| {
4128 s.select_display_ranges([
4129 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4130 DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
4131 ])
4132 });
4133 editor.duplicate_selection(&DuplicateSelection, window, cx);
4134 assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
4135 assert_eq!(
4136 editor.selections.display_ranges(cx),
4137 vec![
4138 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
4139 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
4140 ]
4141 );
4142 });
4143}
4144
4145#[gpui::test]
4146fn test_move_line_up_down(cx: &mut TestAppContext) {
4147 init_test(cx, |_| {});
4148
4149 let editor = cx.add_window(|window, cx| {
4150 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4151 build_editor(buffer, window, cx)
4152 });
4153 _ = editor.update(cx, |editor, window, cx| {
4154 editor.fold_creases(
4155 vec![
4156 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
4157 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
4158 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
4159 ],
4160 true,
4161 window,
4162 cx,
4163 );
4164 editor.change_selections(None, window, cx, |s| {
4165 s.select_display_ranges([
4166 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4167 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4168 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4169 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
4170 ])
4171 });
4172 assert_eq!(
4173 editor.display_text(cx),
4174 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
4175 );
4176
4177 editor.move_line_up(&MoveLineUp, window, cx);
4178 assert_eq!(
4179 editor.display_text(cx),
4180 "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
4181 );
4182 assert_eq!(
4183 editor.selections.display_ranges(cx),
4184 vec![
4185 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
4186 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4187 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4188 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4189 ]
4190 );
4191 });
4192
4193 _ = editor.update(cx, |editor, window, cx| {
4194 editor.move_line_down(&MoveLineDown, window, cx);
4195 assert_eq!(
4196 editor.display_text(cx),
4197 "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
4198 );
4199 assert_eq!(
4200 editor.selections.display_ranges(cx),
4201 vec![
4202 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4203 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4204 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4205 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4206 ]
4207 );
4208 });
4209
4210 _ = editor.update(cx, |editor, window, cx| {
4211 editor.move_line_down(&MoveLineDown, window, cx);
4212 assert_eq!(
4213 editor.display_text(cx),
4214 "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
4215 );
4216 assert_eq!(
4217 editor.selections.display_ranges(cx),
4218 vec![
4219 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4220 DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
4221 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
4222 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
4223 ]
4224 );
4225 });
4226
4227 _ = editor.update(cx, |editor, window, cx| {
4228 editor.move_line_up(&MoveLineUp, window, cx);
4229 assert_eq!(
4230 editor.display_text(cx),
4231 "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
4232 );
4233 assert_eq!(
4234 editor.selections.display_ranges(cx),
4235 vec![
4236 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
4237 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
4238 DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
4239 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
4240 ]
4241 );
4242 });
4243}
4244
4245#[gpui::test]
4246fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
4247 init_test(cx, |_| {});
4248
4249 let editor = cx.add_window(|window, cx| {
4250 let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
4251 build_editor(buffer, window, cx)
4252 });
4253 _ = editor.update(cx, |editor, window, cx| {
4254 let snapshot = editor.buffer.read(cx).snapshot(cx);
4255 editor.insert_blocks(
4256 [BlockProperties {
4257 style: BlockStyle::Fixed,
4258 placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
4259 height: 1,
4260 render: Arc::new(|_| div().into_any()),
4261 priority: 0,
4262 }],
4263 Some(Autoscroll::fit()),
4264 cx,
4265 );
4266 editor.change_selections(None, window, cx, |s| {
4267 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
4268 });
4269 editor.move_line_down(&MoveLineDown, window, cx);
4270 });
4271}
4272
4273#[gpui::test]
4274async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
4275 init_test(cx, |_| {});
4276
4277 let mut cx = EditorTestContext::new(cx).await;
4278 cx.set_state(
4279 &"
4280 ˇzero
4281 one
4282 two
4283 three
4284 four
4285 five
4286 "
4287 .unindent(),
4288 );
4289
4290 // Create a four-line block that replaces three lines of text.
4291 cx.update_editor(|editor, window, cx| {
4292 let snapshot = editor.snapshot(window, cx);
4293 let snapshot = &snapshot.buffer_snapshot;
4294 let placement = BlockPlacement::Replace(
4295 snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
4296 );
4297 editor.insert_blocks(
4298 [BlockProperties {
4299 placement,
4300 height: 4,
4301 style: BlockStyle::Sticky,
4302 render: Arc::new(|_| gpui::div().into_any_element()),
4303 priority: 0,
4304 }],
4305 None,
4306 cx,
4307 );
4308 });
4309
4310 // Move down so that the cursor touches the block.
4311 cx.update_editor(|editor, window, cx| {
4312 editor.move_down(&Default::default(), window, cx);
4313 });
4314 cx.assert_editor_state(
4315 &"
4316 zero
4317 «one
4318 two
4319 threeˇ»
4320 four
4321 five
4322 "
4323 .unindent(),
4324 );
4325
4326 // Move down past the block.
4327 cx.update_editor(|editor, window, cx| {
4328 editor.move_down(&Default::default(), window, cx);
4329 });
4330 cx.assert_editor_state(
4331 &"
4332 zero
4333 one
4334 two
4335 three
4336 ˇfour
4337 five
4338 "
4339 .unindent(),
4340 );
4341}
4342
4343#[gpui::test]
4344fn test_transpose(cx: &mut TestAppContext) {
4345 init_test(cx, |_| {});
4346
4347 _ = cx.add_window(|window, cx| {
4348 let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
4349 editor.set_style(EditorStyle::default(), window, cx);
4350 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
4351 editor.transpose(&Default::default(), window, cx);
4352 assert_eq!(editor.text(cx), "bac");
4353 assert_eq!(editor.selections.ranges(cx), [2..2]);
4354
4355 editor.transpose(&Default::default(), window, cx);
4356 assert_eq!(editor.text(cx), "bca");
4357 assert_eq!(editor.selections.ranges(cx), [3..3]);
4358
4359 editor.transpose(&Default::default(), window, cx);
4360 assert_eq!(editor.text(cx), "bac");
4361 assert_eq!(editor.selections.ranges(cx), [3..3]);
4362
4363 editor
4364 });
4365
4366 _ = cx.add_window(|window, cx| {
4367 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4368 editor.set_style(EditorStyle::default(), window, cx);
4369 editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
4370 editor.transpose(&Default::default(), window, cx);
4371 assert_eq!(editor.text(cx), "acb\nde");
4372 assert_eq!(editor.selections.ranges(cx), [3..3]);
4373
4374 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4375 editor.transpose(&Default::default(), window, cx);
4376 assert_eq!(editor.text(cx), "acbd\ne");
4377 assert_eq!(editor.selections.ranges(cx), [5..5]);
4378
4379 editor.transpose(&Default::default(), window, cx);
4380 assert_eq!(editor.text(cx), "acbde\n");
4381 assert_eq!(editor.selections.ranges(cx), [6..6]);
4382
4383 editor.transpose(&Default::default(), window, cx);
4384 assert_eq!(editor.text(cx), "acbd\ne");
4385 assert_eq!(editor.selections.ranges(cx), [6..6]);
4386
4387 editor
4388 });
4389
4390 _ = cx.add_window(|window, cx| {
4391 let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
4392 editor.set_style(EditorStyle::default(), window, cx);
4393 editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
4394 editor.transpose(&Default::default(), window, cx);
4395 assert_eq!(editor.text(cx), "bacd\ne");
4396 assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
4397
4398 editor.transpose(&Default::default(), window, cx);
4399 assert_eq!(editor.text(cx), "bcade\n");
4400 assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
4401
4402 editor.transpose(&Default::default(), window, cx);
4403 assert_eq!(editor.text(cx), "bcda\ne");
4404 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4405
4406 editor.transpose(&Default::default(), window, cx);
4407 assert_eq!(editor.text(cx), "bcade\n");
4408 assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
4409
4410 editor.transpose(&Default::default(), window, cx);
4411 assert_eq!(editor.text(cx), "bcaed\n");
4412 assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
4413
4414 editor
4415 });
4416
4417 _ = cx.add_window(|window, cx| {
4418 let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
4419 editor.set_style(EditorStyle::default(), window, cx);
4420 editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
4421 editor.transpose(&Default::default(), window, cx);
4422 assert_eq!(editor.text(cx), "🏀🍐✋");
4423 assert_eq!(editor.selections.ranges(cx), [8..8]);
4424
4425 editor.transpose(&Default::default(), window, cx);
4426 assert_eq!(editor.text(cx), "🏀✋🍐");
4427 assert_eq!(editor.selections.ranges(cx), [11..11]);
4428
4429 editor.transpose(&Default::default(), window, cx);
4430 assert_eq!(editor.text(cx), "🏀🍐✋");
4431 assert_eq!(editor.selections.ranges(cx), [11..11]);
4432
4433 editor
4434 });
4435}
4436
4437#[gpui::test]
4438async fn test_rewrap(cx: &mut TestAppContext) {
4439 init_test(cx, |settings| {
4440 settings.languages.extend([
4441 (
4442 "Markdown".into(),
4443 LanguageSettingsContent {
4444 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4445 ..Default::default()
4446 },
4447 ),
4448 (
4449 "Plain Text".into(),
4450 LanguageSettingsContent {
4451 allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
4452 ..Default::default()
4453 },
4454 ),
4455 ])
4456 });
4457
4458 let mut cx = EditorTestContext::new(cx).await;
4459
4460 let language_with_c_comments = Arc::new(Language::new(
4461 LanguageConfig {
4462 line_comments: vec!["// ".into()],
4463 ..LanguageConfig::default()
4464 },
4465 None,
4466 ));
4467 let language_with_pound_comments = Arc::new(Language::new(
4468 LanguageConfig {
4469 line_comments: vec!["# ".into()],
4470 ..LanguageConfig::default()
4471 },
4472 None,
4473 ));
4474 let markdown_language = Arc::new(Language::new(
4475 LanguageConfig {
4476 name: "Markdown".into(),
4477 ..LanguageConfig::default()
4478 },
4479 None,
4480 ));
4481 let language_with_doc_comments = Arc::new(Language::new(
4482 LanguageConfig {
4483 line_comments: vec!["// ".into(), "/// ".into()],
4484 ..LanguageConfig::default()
4485 },
4486 Some(tree_sitter_rust::LANGUAGE.into()),
4487 ));
4488
4489 let plaintext_language = Arc::new(Language::new(
4490 LanguageConfig {
4491 name: "Plain Text".into(),
4492 ..LanguageConfig::default()
4493 },
4494 None,
4495 ));
4496
4497 assert_rewrap(
4498 indoc! {"
4499 // ˇ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.
4500 "},
4501 indoc! {"
4502 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4503 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4504 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4505 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4506 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4507 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4508 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4509 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4510 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4511 // porttitor id. Aliquam id accumsan eros.
4512 "},
4513 language_with_c_comments.clone(),
4514 &mut cx,
4515 );
4516
4517 // Test that rewrapping works inside of a selection
4518 assert_rewrap(
4519 indoc! {"
4520 «// 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.ˇ»
4521 "},
4522 indoc! {"
4523 «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4524 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4525 // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
4526 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4527 // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
4528 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4529 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4530 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4531 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4532 // porttitor id. Aliquam id accumsan eros.ˇ»
4533 "},
4534 language_with_c_comments.clone(),
4535 &mut cx,
4536 );
4537
4538 // Test that cursors that expand to the same region are collapsed.
4539 assert_rewrap(
4540 indoc! {"
4541 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4542 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4543 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4544 // ˇ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.
4545 "},
4546 indoc! {"
4547 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4548 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4549 // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
4550 // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
4551 // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
4552 // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
4553 // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
4554 // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
4555 // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
4556 // porttitor id. Aliquam id accumsan eros.
4557 "},
4558 language_with_c_comments.clone(),
4559 &mut cx,
4560 );
4561
4562 // Test that non-contiguous selections are treated separately.
4563 assert_rewrap(
4564 indoc! {"
4565 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
4566 // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
4567 //
4568 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4569 // ˇ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.
4570 "},
4571 indoc! {"
4572 // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
4573 // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
4574 // auctor, eu lacinia sapien scelerisque.
4575 //
4576 // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
4577 // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
4578 // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
4579 // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
4580 // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
4581 // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
4582 // vulputate turpis porttitor id. Aliquam id accumsan eros.
4583 "},
4584 language_with_c_comments.clone(),
4585 &mut cx,
4586 );
4587
4588 // Test that different comment prefixes are supported.
4589 assert_rewrap(
4590 indoc! {"
4591 # ˇ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.
4592 "},
4593 indoc! {"
4594 # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
4595 # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4596 # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4597 # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4598 # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
4599 # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
4600 # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
4601 # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
4602 # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
4603 # accumsan eros.
4604 "},
4605 language_with_pound_comments.clone(),
4606 &mut cx,
4607 );
4608
4609 // Test that rewrapping is ignored outside of comments in most languages.
4610 assert_rewrap(
4611 indoc! {"
4612 /// Adds two numbers.
4613 /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4614 fn add(a: u32, b: u32) -> u32 {
4615 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ˇ
4616 }
4617 "},
4618 indoc! {"
4619 /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
4620 /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
4621 fn add(a: u32, b: u32) -> u32 {
4622 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ˇ
4623 }
4624 "},
4625 language_with_doc_comments.clone(),
4626 &mut cx,
4627 );
4628
4629 // Test that rewrapping works in Markdown and Plain Text languages.
4630 assert_rewrap(
4631 indoc! {"
4632 # Hello
4633
4634 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.
4635 "},
4636 indoc! {"
4637 # Hello
4638
4639 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4640 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4641 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4642 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4643 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4644 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4645 Integer sit amet scelerisque nisi.
4646 "},
4647 markdown_language,
4648 &mut cx,
4649 );
4650
4651 assert_rewrap(
4652 indoc! {"
4653 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.
4654 "},
4655 indoc! {"
4656 Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
4657 purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
4658 eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
4659 hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
4660 lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
4661 nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
4662 Integer sit amet scelerisque nisi.
4663 "},
4664 plaintext_language,
4665 &mut cx,
4666 );
4667
4668 // Test rewrapping unaligned comments in a selection.
4669 assert_rewrap(
4670 indoc! {"
4671 fn foo() {
4672 if true {
4673 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4674 // Praesent semper egestas tellus id dignissim.ˇ»
4675 do_something();
4676 } else {
4677 //
4678 }
4679 }
4680 "},
4681 indoc! {"
4682 fn foo() {
4683 if true {
4684 « // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4685 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4686 // egestas tellus id dignissim.ˇ»
4687 do_something();
4688 } else {
4689 //
4690 }
4691 }
4692 "},
4693 language_with_doc_comments.clone(),
4694 &mut cx,
4695 );
4696
4697 assert_rewrap(
4698 indoc! {"
4699 fn foo() {
4700 if true {
4701 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
4702 // Praesent semper egestas tellus id dignissim.»
4703 do_something();
4704 } else {
4705 //
4706 }
4707
4708 }
4709 "},
4710 indoc! {"
4711 fn foo() {
4712 if true {
4713 «ˇ // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
4714 // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
4715 // egestas tellus id dignissim.»
4716 do_something();
4717 } else {
4718 //
4719 }
4720
4721 }
4722 "},
4723 language_with_doc_comments.clone(),
4724 &mut cx,
4725 );
4726
4727 #[track_caller]
4728 fn assert_rewrap(
4729 unwrapped_text: &str,
4730 wrapped_text: &str,
4731 language: Arc<Language>,
4732 cx: &mut EditorTestContext,
4733 ) {
4734 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
4735 cx.set_state(unwrapped_text);
4736 cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
4737 cx.assert_editor_state(wrapped_text);
4738 }
4739}
4740
4741#[gpui::test]
4742async fn test_hard_wrap(cx: &mut TestAppContext) {
4743 init_test(cx, |_| {});
4744 let mut cx = EditorTestContext::new(cx).await;
4745
4746 cx.update_editor(|editor, _, cx| {
4747 editor.set_hard_wrap(Some(14), cx);
4748 });
4749
4750 cx.set_state(indoc!(
4751 "
4752 one two three ˇ
4753 "
4754 ));
4755 cx.simulate_input("four");
4756 cx.run_until_parked();
4757
4758 cx.assert_editor_state(indoc!(
4759 "
4760 one two three
4761 fourˇ
4762 "
4763 ));
4764}
4765
4766#[gpui::test]
4767async fn test_clipboard(cx: &mut TestAppContext) {
4768 init_test(cx, |_| {});
4769
4770 let mut cx = EditorTestContext::new(cx).await;
4771
4772 cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
4773 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4774 cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
4775
4776 // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
4777 cx.set_state("two ˇfour ˇsix ˇ");
4778 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4779 cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
4780
4781 // Paste again but with only two cursors. Since the number of cursors doesn't
4782 // match the number of slices in the clipboard, the entire clipboard text
4783 // is pasted at each cursor.
4784 cx.set_state("ˇtwo one✅ four three six five ˇ");
4785 cx.update_editor(|e, window, cx| {
4786 e.handle_input("( ", window, cx);
4787 e.paste(&Paste, window, cx);
4788 e.handle_input(") ", window, cx);
4789 });
4790 cx.assert_editor_state(
4791 &([
4792 "( one✅ ",
4793 "three ",
4794 "five ) ˇtwo one✅ four three six five ( one✅ ",
4795 "three ",
4796 "five ) ˇ",
4797 ]
4798 .join("\n")),
4799 );
4800
4801 // Cut with three selections, one of which is full-line.
4802 cx.set_state(indoc! {"
4803 1«2ˇ»3
4804 4ˇ567
4805 «8ˇ»9"});
4806 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4807 cx.assert_editor_state(indoc! {"
4808 1ˇ3
4809 ˇ9"});
4810
4811 // Paste with three selections, noticing how the copied selection that was full-line
4812 // gets inserted before the second cursor.
4813 cx.set_state(indoc! {"
4814 1ˇ3
4815 9ˇ
4816 «oˇ»ne"});
4817 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4818 cx.assert_editor_state(indoc! {"
4819 12ˇ3
4820 4567
4821 9ˇ
4822 8ˇne"});
4823
4824 // Copy with a single cursor only, which writes the whole line into the clipboard.
4825 cx.set_state(indoc! {"
4826 The quick brown
4827 fox juˇmps over
4828 the lazy dog"});
4829 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4830 assert_eq!(
4831 cx.read_from_clipboard()
4832 .and_then(|item| item.text().as_deref().map(str::to_string)),
4833 Some("fox jumps over\n".to_string())
4834 );
4835
4836 // Paste with three selections, noticing how the copied full-line selection is inserted
4837 // before the empty selections but replaces the selection that is non-empty.
4838 cx.set_state(indoc! {"
4839 Tˇhe quick brown
4840 «foˇ»x jumps over
4841 tˇhe lazy dog"});
4842 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4843 cx.assert_editor_state(indoc! {"
4844 fox jumps over
4845 Tˇhe quick brown
4846 fox jumps over
4847 ˇx jumps over
4848 fox jumps over
4849 tˇhe lazy dog"});
4850}
4851
4852#[gpui::test]
4853async fn test_paste_multiline(cx: &mut TestAppContext) {
4854 init_test(cx, |_| {});
4855
4856 let mut cx = EditorTestContext::new(cx).await;
4857 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
4858
4859 // Cut an indented block, without the leading whitespace.
4860 cx.set_state(indoc! {"
4861 const a: B = (
4862 c(),
4863 «d(
4864 e,
4865 f
4866 )ˇ»
4867 );
4868 "});
4869 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4870 cx.assert_editor_state(indoc! {"
4871 const a: B = (
4872 c(),
4873 ˇ
4874 );
4875 "});
4876
4877 // Paste it at the same position.
4878 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4879 cx.assert_editor_state(indoc! {"
4880 const a: B = (
4881 c(),
4882 d(
4883 e,
4884 f
4885 )ˇ
4886 );
4887 "});
4888
4889 // Paste it at a line with a lower indent level.
4890 cx.set_state(indoc! {"
4891 ˇ
4892 const a: B = (
4893 c(),
4894 );
4895 "});
4896 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4897 cx.assert_editor_state(indoc! {"
4898 d(
4899 e,
4900 f
4901 )ˇ
4902 const a: B = (
4903 c(),
4904 );
4905 "});
4906
4907 // Cut an indented block, with the leading whitespace.
4908 cx.set_state(indoc! {"
4909 const a: B = (
4910 c(),
4911 « d(
4912 e,
4913 f
4914 )
4915 ˇ»);
4916 "});
4917 cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
4918 cx.assert_editor_state(indoc! {"
4919 const a: B = (
4920 c(),
4921 ˇ);
4922 "});
4923
4924 // Paste it at the same position.
4925 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4926 cx.assert_editor_state(indoc! {"
4927 const a: B = (
4928 c(),
4929 d(
4930 e,
4931 f
4932 )
4933 ˇ);
4934 "});
4935
4936 // Paste it at a line with a higher indent level.
4937 cx.set_state(indoc! {"
4938 const a: B = (
4939 c(),
4940 d(
4941 e,
4942 fˇ
4943 )
4944 );
4945 "});
4946 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4947 cx.assert_editor_state(indoc! {"
4948 const a: B = (
4949 c(),
4950 d(
4951 e,
4952 f d(
4953 e,
4954 f
4955 )
4956 ˇ
4957 )
4958 );
4959 "});
4960
4961 // Copy an indented block, starting mid-line
4962 cx.set_state(indoc! {"
4963 const a: B = (
4964 c(),
4965 somethin«g(
4966 e,
4967 f
4968 )ˇ»
4969 );
4970 "});
4971 cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
4972
4973 // Paste it on a line with a lower indent level
4974 cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
4975 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
4976 cx.assert_editor_state(indoc! {"
4977 const a: B = (
4978 c(),
4979 something(
4980 e,
4981 f
4982 )
4983 );
4984 g(
4985 e,
4986 f
4987 )ˇ"});
4988}
4989
4990#[gpui::test]
4991async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
4992 init_test(cx, |_| {});
4993
4994 cx.write_to_clipboard(ClipboardItem::new_string(
4995 " d(\n e\n );\n".into(),
4996 ));
4997
4998 let mut cx = EditorTestContext::new(cx).await;
4999 cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
5000
5001 cx.set_state(indoc! {"
5002 fn a() {
5003 b();
5004 if c() {
5005 ˇ
5006 }
5007 }
5008 "});
5009
5010 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5011 cx.assert_editor_state(indoc! {"
5012 fn a() {
5013 b();
5014 if c() {
5015 d(
5016 e
5017 );
5018 ˇ
5019 }
5020 }
5021 "});
5022
5023 cx.set_state(indoc! {"
5024 fn a() {
5025 b();
5026 ˇ
5027 }
5028 "});
5029
5030 cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
5031 cx.assert_editor_state(indoc! {"
5032 fn a() {
5033 b();
5034 d(
5035 e
5036 );
5037 ˇ
5038 }
5039 "});
5040}
5041
5042#[gpui::test]
5043fn test_select_all(cx: &mut TestAppContext) {
5044 init_test(cx, |_| {});
5045
5046 let editor = cx.add_window(|window, cx| {
5047 let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
5048 build_editor(buffer, window, cx)
5049 });
5050 _ = editor.update(cx, |editor, window, cx| {
5051 editor.select_all(&SelectAll, window, cx);
5052 assert_eq!(
5053 editor.selections.display_ranges(cx),
5054 &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
5055 );
5056 });
5057}
5058
5059#[gpui::test]
5060fn test_select_line(cx: &mut TestAppContext) {
5061 init_test(cx, |_| {});
5062
5063 let editor = cx.add_window(|window, cx| {
5064 let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
5065 build_editor(buffer, window, cx)
5066 });
5067 _ = editor.update(cx, |editor, window, cx| {
5068 editor.change_selections(None, window, cx, |s| {
5069 s.select_display_ranges([
5070 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5071 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5072 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5073 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
5074 ])
5075 });
5076 editor.select_line(&SelectLine, window, cx);
5077 assert_eq!(
5078 editor.selections.display_ranges(cx),
5079 vec![
5080 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
5081 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
5082 ]
5083 );
5084 });
5085
5086 _ = editor.update(cx, |editor, window, cx| {
5087 editor.select_line(&SelectLine, window, cx);
5088 assert_eq!(
5089 editor.selections.display_ranges(cx),
5090 vec![
5091 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
5092 DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
5093 ]
5094 );
5095 });
5096
5097 _ = editor.update(cx, |editor, window, cx| {
5098 editor.select_line(&SelectLine, window, cx);
5099 assert_eq!(
5100 editor.selections.display_ranges(cx),
5101 vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
5102 );
5103 });
5104}
5105
5106#[gpui::test]
5107async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
5108 init_test(cx, |_| {});
5109 let mut cx = EditorTestContext::new(cx).await;
5110
5111 #[track_caller]
5112 fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
5113 cx.set_state(initial_state);
5114 cx.update_editor(|e, window, cx| {
5115 e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
5116 });
5117 cx.assert_editor_state(expected_state);
5118 }
5119
5120 // Selection starts and ends at the middle of lines, left-to-right
5121 test(
5122 &mut cx,
5123 "aa\nb«ˇb\ncc\ndd\ne»e\nff",
5124 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5125 );
5126 // Same thing, right-to-left
5127 test(
5128 &mut cx,
5129 "aa\nb«b\ncc\ndd\neˇ»e\nff",
5130 "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
5131 );
5132
5133 // Whole buffer, left-to-right, last line *doesn't* end with newline
5134 test(
5135 &mut cx,
5136 "«ˇaa\nbb\ncc\ndd\nee\nff»",
5137 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5138 );
5139 // Same thing, right-to-left
5140 test(
5141 &mut cx,
5142 "«aa\nbb\ncc\ndd\nee\nffˇ»",
5143 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
5144 );
5145
5146 // Whole buffer, left-to-right, last line ends with newline
5147 test(
5148 &mut cx,
5149 "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
5150 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5151 );
5152 // Same thing, right-to-left
5153 test(
5154 &mut cx,
5155 "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
5156 "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
5157 );
5158
5159 // Starts at the end of a line, ends at the start of another
5160 test(
5161 &mut cx,
5162 "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
5163 "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
5164 );
5165}
5166
5167#[gpui::test]
5168async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
5169 init_test(cx, |_| {});
5170
5171 let editor = cx.add_window(|window, cx| {
5172 let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
5173 build_editor(buffer, window, cx)
5174 });
5175
5176 // setup
5177 _ = editor.update(cx, |editor, window, cx| {
5178 editor.fold_creases(
5179 vec![
5180 Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
5181 Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
5182 Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
5183 ],
5184 true,
5185 window,
5186 cx,
5187 );
5188 assert_eq!(
5189 editor.display_text(cx),
5190 "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5191 );
5192 });
5193
5194 _ = editor.update(cx, |editor, window, cx| {
5195 editor.change_selections(None, window, cx, |s| {
5196 s.select_display_ranges([
5197 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
5198 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
5199 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
5200 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
5201 ])
5202 });
5203 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5204 assert_eq!(
5205 editor.display_text(cx),
5206 "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
5207 );
5208 });
5209 EditorTestContext::for_editor(editor, cx)
5210 .await
5211 .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
5212
5213 _ = editor.update(cx, |editor, window, cx| {
5214 editor.change_selections(None, window, cx, |s| {
5215 s.select_display_ranges([
5216 DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
5217 ])
5218 });
5219 editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
5220 assert_eq!(
5221 editor.display_text(cx),
5222 "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
5223 );
5224 assert_eq!(
5225 editor.selections.display_ranges(cx),
5226 [
5227 DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
5228 DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
5229 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
5230 DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
5231 DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
5232 DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
5233 DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
5234 ]
5235 );
5236 });
5237 EditorTestContext::for_editor(editor, cx)
5238 .await
5239 .assert_editor_state(
5240 "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
5241 );
5242}
5243
5244#[gpui::test]
5245async fn test_add_selection_above_below(cx: &mut TestAppContext) {
5246 init_test(cx, |_| {});
5247
5248 let mut cx = EditorTestContext::new(cx).await;
5249
5250 cx.set_state(indoc!(
5251 r#"abc
5252 defˇghi
5253
5254 jk
5255 nlmo
5256 "#
5257 ));
5258
5259 cx.update_editor(|editor, window, cx| {
5260 editor.add_selection_above(&Default::default(), window, cx);
5261 });
5262
5263 cx.assert_editor_state(indoc!(
5264 r#"abcˇ
5265 defˇghi
5266
5267 jk
5268 nlmo
5269 "#
5270 ));
5271
5272 cx.update_editor(|editor, window, cx| {
5273 editor.add_selection_above(&Default::default(), window, cx);
5274 });
5275
5276 cx.assert_editor_state(indoc!(
5277 r#"abcˇ
5278 defˇghi
5279
5280 jk
5281 nlmo
5282 "#
5283 ));
5284
5285 cx.update_editor(|editor, window, cx| {
5286 editor.add_selection_below(&Default::default(), window, cx);
5287 });
5288
5289 cx.assert_editor_state(indoc!(
5290 r#"abc
5291 defˇghi
5292
5293 jk
5294 nlmo
5295 "#
5296 ));
5297
5298 cx.update_editor(|editor, window, cx| {
5299 editor.undo_selection(&Default::default(), window, cx);
5300 });
5301
5302 cx.assert_editor_state(indoc!(
5303 r#"abcˇ
5304 defˇghi
5305
5306 jk
5307 nlmo
5308 "#
5309 ));
5310
5311 cx.update_editor(|editor, window, cx| {
5312 editor.redo_selection(&Default::default(), window, cx);
5313 });
5314
5315 cx.assert_editor_state(indoc!(
5316 r#"abc
5317 defˇghi
5318
5319 jk
5320 nlmo
5321 "#
5322 ));
5323
5324 cx.update_editor(|editor, window, cx| {
5325 editor.add_selection_below(&Default::default(), window, cx);
5326 });
5327
5328 cx.assert_editor_state(indoc!(
5329 r#"abc
5330 defˇghi
5331
5332 jk
5333 nlmˇo
5334 "#
5335 ));
5336
5337 cx.update_editor(|editor, window, cx| {
5338 editor.add_selection_below(&Default::default(), window, cx);
5339 });
5340
5341 cx.assert_editor_state(indoc!(
5342 r#"abc
5343 defˇghi
5344
5345 jk
5346 nlmˇo
5347 "#
5348 ));
5349
5350 // change selections
5351 cx.set_state(indoc!(
5352 r#"abc
5353 def«ˇg»hi
5354
5355 jk
5356 nlmo
5357 "#
5358 ));
5359
5360 cx.update_editor(|editor, window, cx| {
5361 editor.add_selection_below(&Default::default(), window, cx);
5362 });
5363
5364 cx.assert_editor_state(indoc!(
5365 r#"abc
5366 def«ˇg»hi
5367
5368 jk
5369 nlm«ˇo»
5370 "#
5371 ));
5372
5373 cx.update_editor(|editor, window, cx| {
5374 editor.add_selection_below(&Default::default(), window, cx);
5375 });
5376
5377 cx.assert_editor_state(indoc!(
5378 r#"abc
5379 def«ˇg»hi
5380
5381 jk
5382 nlm«ˇo»
5383 "#
5384 ));
5385
5386 cx.update_editor(|editor, window, cx| {
5387 editor.add_selection_above(&Default::default(), window, cx);
5388 });
5389
5390 cx.assert_editor_state(indoc!(
5391 r#"abc
5392 def«ˇg»hi
5393
5394 jk
5395 nlmo
5396 "#
5397 ));
5398
5399 cx.update_editor(|editor, window, cx| {
5400 editor.add_selection_above(&Default::default(), window, cx);
5401 });
5402
5403 cx.assert_editor_state(indoc!(
5404 r#"abc
5405 def«ˇg»hi
5406
5407 jk
5408 nlmo
5409 "#
5410 ));
5411
5412 // Change selections again
5413 cx.set_state(indoc!(
5414 r#"a«bc
5415 defgˇ»hi
5416
5417 jk
5418 nlmo
5419 "#
5420 ));
5421
5422 cx.update_editor(|editor, window, cx| {
5423 editor.add_selection_below(&Default::default(), window, cx);
5424 });
5425
5426 cx.assert_editor_state(indoc!(
5427 r#"a«bcˇ»
5428 d«efgˇ»hi
5429
5430 j«kˇ»
5431 nlmo
5432 "#
5433 ));
5434
5435 cx.update_editor(|editor, window, cx| {
5436 editor.add_selection_below(&Default::default(), window, cx);
5437 });
5438 cx.assert_editor_state(indoc!(
5439 r#"a«bcˇ»
5440 d«efgˇ»hi
5441
5442 j«kˇ»
5443 n«lmoˇ»
5444 "#
5445 ));
5446 cx.update_editor(|editor, window, cx| {
5447 editor.add_selection_above(&Default::default(), window, cx);
5448 });
5449
5450 cx.assert_editor_state(indoc!(
5451 r#"a«bcˇ»
5452 d«efgˇ»hi
5453
5454 j«kˇ»
5455 nlmo
5456 "#
5457 ));
5458
5459 // Change selections again
5460 cx.set_state(indoc!(
5461 r#"abc
5462 d«ˇefghi
5463
5464 jk
5465 nlm»o
5466 "#
5467 ));
5468
5469 cx.update_editor(|editor, window, cx| {
5470 editor.add_selection_above(&Default::default(), window, cx);
5471 });
5472
5473 cx.assert_editor_state(indoc!(
5474 r#"a«ˇbc»
5475 d«ˇef»ghi
5476
5477 j«ˇk»
5478 n«ˇlm»o
5479 "#
5480 ));
5481
5482 cx.update_editor(|editor, window, cx| {
5483 editor.add_selection_below(&Default::default(), window, cx);
5484 });
5485
5486 cx.assert_editor_state(indoc!(
5487 r#"abc
5488 d«ˇef»ghi
5489
5490 j«ˇk»
5491 n«ˇlm»o
5492 "#
5493 ));
5494}
5495
5496#[gpui::test]
5497async fn test_select_next(cx: &mut TestAppContext) {
5498 init_test(cx, |_| {});
5499
5500 let mut cx = EditorTestContext::new(cx).await;
5501 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5502
5503 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5504 .unwrap();
5505 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5506
5507 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5508 .unwrap();
5509 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5510
5511 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5512 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5513
5514 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5515 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
5516
5517 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5518 .unwrap();
5519 cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5520
5521 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5522 .unwrap();
5523 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5524}
5525
5526#[gpui::test]
5527async fn test_select_all_matches(cx: &mut TestAppContext) {
5528 init_test(cx, |_| {});
5529
5530 let mut cx = EditorTestContext::new(cx).await;
5531
5532 // Test caret-only selections
5533 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5534 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5535 .unwrap();
5536 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
5537
5538 // Test left-to-right selections
5539 cx.set_state("abc\n«abcˇ»\nabc");
5540 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5541 .unwrap();
5542 cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
5543
5544 // Test right-to-left selections
5545 cx.set_state("abc\n«ˇabc»\nabc");
5546 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5547 .unwrap();
5548 cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
5549
5550 // Test selecting whitespace with caret selection
5551 cx.set_state("abc\nˇ abc\nabc");
5552 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5553 .unwrap();
5554 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5555
5556 // Test selecting whitespace with left-to-right selection
5557 cx.set_state("abc\n«ˇ »abc\nabc");
5558 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5559 .unwrap();
5560 cx.assert_editor_state("abc\n«ˇ »abc\nabc");
5561
5562 // Test no matches with right-to-left selection
5563 cx.set_state("abc\n« ˇ»abc\nabc");
5564 cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
5565 .unwrap();
5566 cx.assert_editor_state("abc\n« ˇ»abc\nabc");
5567}
5568
5569#[gpui::test]
5570async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
5571 init_test(cx, |_| {});
5572
5573 let mut cx = EditorTestContext::new(cx).await;
5574 cx.set_state(
5575 r#"let foo = 2;
5576lˇet foo = 2;
5577let fooˇ = 2;
5578let foo = 2;
5579let foo = ˇ2;"#,
5580 );
5581
5582 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5583 .unwrap();
5584 cx.assert_editor_state(
5585 r#"let foo = 2;
5586«letˇ» foo = 2;
5587let «fooˇ» = 2;
5588let foo = 2;
5589let foo = «2ˇ»;"#,
5590 );
5591
5592 // noop for multiple selections with different contents
5593 cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
5594 .unwrap();
5595 cx.assert_editor_state(
5596 r#"let foo = 2;
5597«letˇ» foo = 2;
5598let «fooˇ» = 2;
5599let foo = 2;
5600let foo = «2ˇ»;"#,
5601 );
5602}
5603
5604#[gpui::test]
5605async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
5606 init_test(cx, |_| {});
5607
5608 let mut cx =
5609 EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
5610
5611 cx.assert_editor_state(indoc! {"
5612 ˇbbb
5613 ccc
5614
5615 bbb
5616 ccc
5617 "});
5618 cx.dispatch_action(SelectPrevious::default());
5619 cx.assert_editor_state(indoc! {"
5620 «bbbˇ»
5621 ccc
5622
5623 bbb
5624 ccc
5625 "});
5626 cx.dispatch_action(SelectPrevious::default());
5627 cx.assert_editor_state(indoc! {"
5628 «bbbˇ»
5629 ccc
5630
5631 «bbbˇ»
5632 ccc
5633 "});
5634}
5635
5636#[gpui::test]
5637async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
5638 init_test(cx, |_| {});
5639
5640 let mut cx = EditorTestContext::new(cx).await;
5641 cx.set_state("abc\nˇabc abc\ndefabc\nabc");
5642
5643 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5644 .unwrap();
5645 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5646
5647 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5648 .unwrap();
5649 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5650
5651 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5652 cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
5653
5654 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5655 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
5656
5657 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5658 .unwrap();
5659 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
5660
5661 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5662 .unwrap();
5663 cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
5664
5665 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5666 .unwrap();
5667 cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5668}
5669
5670#[gpui::test]
5671async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
5672 init_test(cx, |_| {});
5673
5674 let mut cx = EditorTestContext::new(cx).await;
5675 cx.set_state("aˇ");
5676
5677 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5678 .unwrap();
5679 cx.assert_editor_state("«aˇ»");
5680 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5681 .unwrap();
5682 cx.assert_editor_state("«aˇ»");
5683}
5684
5685#[gpui::test]
5686async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
5687 init_test(cx, |_| {});
5688
5689 let mut cx = EditorTestContext::new(cx).await;
5690 cx.set_state(
5691 r#"let foo = 2;
5692lˇet foo = 2;
5693let fooˇ = 2;
5694let foo = 2;
5695let foo = ˇ2;"#,
5696 );
5697
5698 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5699 .unwrap();
5700 cx.assert_editor_state(
5701 r#"let foo = 2;
5702«letˇ» foo = 2;
5703let «fooˇ» = 2;
5704let foo = 2;
5705let foo = «2ˇ»;"#,
5706 );
5707
5708 // noop for multiple selections with different contents
5709 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5710 .unwrap();
5711 cx.assert_editor_state(
5712 r#"let foo = 2;
5713«letˇ» foo = 2;
5714let «fooˇ» = 2;
5715let foo = 2;
5716let foo = «2ˇ»;"#,
5717 );
5718}
5719
5720#[gpui::test]
5721async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
5722 init_test(cx, |_| {});
5723
5724 let mut cx = EditorTestContext::new(cx).await;
5725 cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
5726
5727 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5728 .unwrap();
5729 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5730
5731 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5732 .unwrap();
5733 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5734
5735 cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
5736 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
5737
5738 cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
5739 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
5740
5741 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5742 .unwrap();
5743 cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
5744
5745 cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
5746 .unwrap();
5747 cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
5748}
5749
5750#[gpui::test]
5751async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
5752 init_test(cx, |_| {});
5753
5754 let language = Arc::new(Language::new(
5755 LanguageConfig::default(),
5756 Some(tree_sitter_rust::LANGUAGE.into()),
5757 ));
5758
5759 let text = r#"
5760 use mod1::mod2::{mod3, mod4};
5761
5762 fn fn_1(param1: bool, param2: &str) {
5763 let var1 = "text";
5764 }
5765 "#
5766 .unindent();
5767
5768 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
5769 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
5770 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
5771
5772 editor
5773 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
5774 .await;
5775
5776 editor.update_in(cx, |editor, window, cx| {
5777 editor.change_selections(None, window, cx, |s| {
5778 s.select_display_ranges([
5779 DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
5780 DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
5781 DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
5782 ]);
5783 });
5784 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5785 });
5786 editor.update(cx, |editor, cx| {
5787 assert_text_with_selections(
5788 editor,
5789 indoc! {r#"
5790 use mod1::mod2::{mod3, «mod4ˇ»};
5791
5792 fn fn_1«ˇ(param1: bool, param2: &str)» {
5793 let var1 = "«textˇ»";
5794 }
5795 "#},
5796 cx,
5797 );
5798 });
5799
5800 editor.update_in(cx, |editor, window, cx| {
5801 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5802 });
5803 editor.update(cx, |editor, cx| {
5804 assert_text_with_selections(
5805 editor,
5806 indoc! {r#"
5807 use mod1::mod2::«{mod3, mod4}ˇ»;
5808
5809 «ˇfn fn_1(param1: bool, param2: &str) {
5810 let var1 = "text";
5811 }»
5812 "#},
5813 cx,
5814 );
5815 });
5816
5817 editor.update_in(cx, |editor, window, cx| {
5818 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5819 });
5820 assert_eq!(
5821 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5822 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5823 );
5824
5825 // Trying to expand the selected syntax node one more time has no effect.
5826 editor.update_in(cx, |editor, window, cx| {
5827 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5828 });
5829 assert_eq!(
5830 editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
5831 &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
5832 );
5833
5834 editor.update_in(cx, |editor, window, cx| {
5835 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5836 });
5837 editor.update(cx, |editor, cx| {
5838 assert_text_with_selections(
5839 editor,
5840 indoc! {r#"
5841 use mod1::mod2::«{mod3, mod4}ˇ»;
5842
5843 «ˇfn fn_1(param1: bool, param2: &str) {
5844 let var1 = "text";
5845 }»
5846 "#},
5847 cx,
5848 );
5849 });
5850
5851 editor.update_in(cx, |editor, window, cx| {
5852 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5853 });
5854 editor.update(cx, |editor, cx| {
5855 assert_text_with_selections(
5856 editor,
5857 indoc! {r#"
5858 use mod1::mod2::{mod3, «mod4ˇ»};
5859
5860 fn fn_1«ˇ(param1: bool, param2: &str)» {
5861 let var1 = "«textˇ»";
5862 }
5863 "#},
5864 cx,
5865 );
5866 });
5867
5868 editor.update_in(cx, |editor, window, cx| {
5869 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5870 });
5871 editor.update(cx, |editor, cx| {
5872 assert_text_with_selections(
5873 editor,
5874 indoc! {r#"
5875 use mod1::mod2::{mod3, mo«ˇ»d4};
5876
5877 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5878 let var1 = "te«ˇ»xt";
5879 }
5880 "#},
5881 cx,
5882 );
5883 });
5884
5885 // Trying to shrink the selected syntax node one more time has no effect.
5886 editor.update_in(cx, |editor, window, cx| {
5887 editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
5888 });
5889 editor.update_in(cx, |editor, _, cx| {
5890 assert_text_with_selections(
5891 editor,
5892 indoc! {r#"
5893 use mod1::mod2::{mod3, mo«ˇ»d4};
5894
5895 fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
5896 let var1 = "te«ˇ»xt";
5897 }
5898 "#},
5899 cx,
5900 );
5901 });
5902
5903 // Ensure that we keep expanding the selection if the larger selection starts or ends within
5904 // a fold.
5905 editor.update_in(cx, |editor, window, cx| {
5906 editor.fold_creases(
5907 vec![
5908 Crease::simple(
5909 Point::new(0, 21)..Point::new(0, 24),
5910 FoldPlaceholder::test(),
5911 ),
5912 Crease::simple(
5913 Point::new(3, 20)..Point::new(3, 22),
5914 FoldPlaceholder::test(),
5915 ),
5916 ],
5917 true,
5918 window,
5919 cx,
5920 );
5921 editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
5922 });
5923 editor.update(cx, |editor, cx| {
5924 assert_text_with_selections(
5925 editor,
5926 indoc! {r#"
5927 use mod1::mod2::«{mod3, mod4}ˇ»;
5928
5929 fn fn_1«ˇ(param1: bool, param2: &str)» {
5930 «let var1 = "text";ˇ»
5931 }
5932 "#},
5933 cx,
5934 );
5935 });
5936}
5937
5938#[gpui::test]
5939async fn test_fold_function_bodies(cx: &mut TestAppContext) {
5940 init_test(cx, |_| {});
5941
5942 let base_text = r#"
5943 impl A {
5944 // this is an uncommitted comment
5945
5946 fn b() {
5947 c();
5948 }
5949
5950 // this is another uncommitted comment
5951
5952 fn d() {
5953 // e
5954 // f
5955 }
5956 }
5957
5958 fn g() {
5959 // h
5960 }
5961 "#
5962 .unindent();
5963
5964 let text = r#"
5965 ˇimpl A {
5966
5967 fn b() {
5968 c();
5969 }
5970
5971 fn d() {
5972 // e
5973 // f
5974 }
5975 }
5976
5977 fn g() {
5978 // h
5979 }
5980 "#
5981 .unindent();
5982
5983 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
5984 cx.set_state(&text);
5985 cx.set_head_text(&base_text);
5986 cx.update_editor(|editor, window, cx| {
5987 editor.expand_all_diff_hunks(&Default::default(), window, cx);
5988 });
5989
5990 cx.assert_state_with_diff(
5991 "
5992 ˇimpl A {
5993 - // this is an uncommitted comment
5994
5995 fn b() {
5996 c();
5997 }
5998
5999 - // this is another uncommitted comment
6000 -
6001 fn d() {
6002 // e
6003 // f
6004 }
6005 }
6006
6007 fn g() {
6008 // h
6009 }
6010 "
6011 .unindent(),
6012 );
6013
6014 let expected_display_text = "
6015 impl A {
6016 // this is an uncommitted comment
6017
6018 fn b() {
6019 ⋯
6020 }
6021
6022 // this is another uncommitted comment
6023
6024 fn d() {
6025 ⋯
6026 }
6027 }
6028
6029 fn g() {
6030 ⋯
6031 }
6032 "
6033 .unindent();
6034
6035 cx.update_editor(|editor, window, cx| {
6036 editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
6037 assert_eq!(editor.display_text(cx), expected_display_text);
6038 });
6039}
6040
6041#[gpui::test]
6042async fn test_autoindent(cx: &mut TestAppContext) {
6043 init_test(cx, |_| {});
6044
6045 let language = Arc::new(
6046 Language::new(
6047 LanguageConfig {
6048 brackets: BracketPairConfig {
6049 pairs: vec![
6050 BracketPair {
6051 start: "{".to_string(),
6052 end: "}".to_string(),
6053 close: false,
6054 surround: false,
6055 newline: true,
6056 },
6057 BracketPair {
6058 start: "(".to_string(),
6059 end: ")".to_string(),
6060 close: false,
6061 surround: false,
6062 newline: true,
6063 },
6064 ],
6065 ..Default::default()
6066 },
6067 ..Default::default()
6068 },
6069 Some(tree_sitter_rust::LANGUAGE.into()),
6070 )
6071 .with_indents_query(
6072 r#"
6073 (_ "(" ")" @end) @indent
6074 (_ "{" "}" @end) @indent
6075 "#,
6076 )
6077 .unwrap(),
6078 );
6079
6080 let text = "fn a() {}";
6081
6082 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6083 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6084 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6085 editor
6086 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6087 .await;
6088
6089 editor.update_in(cx, |editor, window, cx| {
6090 editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
6091 editor.newline(&Newline, window, cx);
6092 assert_eq!(editor.text(cx), "fn a(\n \n) {\n \n}\n");
6093 assert_eq!(
6094 editor.selections.ranges(cx),
6095 &[
6096 Point::new(1, 4)..Point::new(1, 4),
6097 Point::new(3, 4)..Point::new(3, 4),
6098 Point::new(5, 0)..Point::new(5, 0)
6099 ]
6100 );
6101 });
6102}
6103
6104#[gpui::test]
6105async fn test_autoindent_selections(cx: &mut TestAppContext) {
6106 init_test(cx, |_| {});
6107
6108 {
6109 let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
6110 cx.set_state(indoc! {"
6111 impl A {
6112
6113 fn b() {}
6114
6115 «fn c() {
6116
6117 }ˇ»
6118 }
6119 "});
6120
6121 cx.update_editor(|editor, window, cx| {
6122 editor.autoindent(&Default::default(), window, cx);
6123 });
6124
6125 cx.assert_editor_state(indoc! {"
6126 impl A {
6127
6128 fn b() {}
6129
6130 «fn c() {
6131
6132 }ˇ»
6133 }
6134 "});
6135 }
6136
6137 {
6138 let mut cx = EditorTestContext::new_multibuffer(
6139 cx,
6140 [indoc! { "
6141 impl A {
6142 «
6143 // a
6144 fn b(){}
6145 »
6146 «
6147 }
6148 fn c(){}
6149 »
6150 "}],
6151 );
6152
6153 let buffer = cx.update_editor(|editor, _, cx| {
6154 let buffer = editor.buffer().update(cx, |buffer, _| {
6155 buffer.all_buffers().iter().next().unwrap().clone()
6156 });
6157 buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
6158 buffer
6159 });
6160
6161 cx.run_until_parked();
6162 cx.update_editor(|editor, window, cx| {
6163 editor.select_all(&Default::default(), window, cx);
6164 editor.autoindent(&Default::default(), window, cx)
6165 });
6166 cx.run_until_parked();
6167
6168 cx.update(|_, cx| {
6169 pretty_assertions::assert_eq!(
6170 buffer.read(cx).text(),
6171 indoc! { "
6172 impl A {
6173
6174 // a
6175 fn b(){}
6176
6177
6178 }
6179 fn c(){}
6180
6181 " }
6182 )
6183 });
6184 }
6185}
6186
6187#[gpui::test]
6188async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
6189 init_test(cx, |_| {});
6190
6191 let mut cx = EditorTestContext::new(cx).await;
6192
6193 let language = Arc::new(Language::new(
6194 LanguageConfig {
6195 brackets: BracketPairConfig {
6196 pairs: vec![
6197 BracketPair {
6198 start: "{".to_string(),
6199 end: "}".to_string(),
6200 close: true,
6201 surround: true,
6202 newline: true,
6203 },
6204 BracketPair {
6205 start: "(".to_string(),
6206 end: ")".to_string(),
6207 close: true,
6208 surround: true,
6209 newline: true,
6210 },
6211 BracketPair {
6212 start: "/*".to_string(),
6213 end: " */".to_string(),
6214 close: true,
6215 surround: true,
6216 newline: true,
6217 },
6218 BracketPair {
6219 start: "[".to_string(),
6220 end: "]".to_string(),
6221 close: false,
6222 surround: false,
6223 newline: true,
6224 },
6225 BracketPair {
6226 start: "\"".to_string(),
6227 end: "\"".to_string(),
6228 close: true,
6229 surround: true,
6230 newline: false,
6231 },
6232 BracketPair {
6233 start: "<".to_string(),
6234 end: ">".to_string(),
6235 close: false,
6236 surround: true,
6237 newline: true,
6238 },
6239 ],
6240 ..Default::default()
6241 },
6242 autoclose_before: "})]".to_string(),
6243 ..Default::default()
6244 },
6245 Some(tree_sitter_rust::LANGUAGE.into()),
6246 ));
6247
6248 cx.language_registry().add(language.clone());
6249 cx.update_buffer(|buffer, cx| {
6250 buffer.set_language(Some(language), cx);
6251 });
6252
6253 cx.set_state(
6254 &r#"
6255 🏀ˇ
6256 εˇ
6257 ❤️ˇ
6258 "#
6259 .unindent(),
6260 );
6261
6262 // autoclose multiple nested brackets at multiple cursors
6263 cx.update_editor(|editor, window, cx| {
6264 editor.handle_input("{", window, cx);
6265 editor.handle_input("{", window, cx);
6266 editor.handle_input("{", window, cx);
6267 });
6268 cx.assert_editor_state(
6269 &"
6270 🏀{{{ˇ}}}
6271 ε{{{ˇ}}}
6272 ❤️{{{ˇ}}}
6273 "
6274 .unindent(),
6275 );
6276
6277 // insert a different closing bracket
6278 cx.update_editor(|editor, window, cx| {
6279 editor.handle_input(")", window, cx);
6280 });
6281 cx.assert_editor_state(
6282 &"
6283 🏀{{{)ˇ}}}
6284 ε{{{)ˇ}}}
6285 ❤️{{{)ˇ}}}
6286 "
6287 .unindent(),
6288 );
6289
6290 // skip over the auto-closed brackets when typing a closing bracket
6291 cx.update_editor(|editor, window, cx| {
6292 editor.move_right(&MoveRight, window, cx);
6293 editor.handle_input("}", window, cx);
6294 editor.handle_input("}", window, cx);
6295 editor.handle_input("}", window, cx);
6296 });
6297 cx.assert_editor_state(
6298 &"
6299 🏀{{{)}}}}ˇ
6300 ε{{{)}}}}ˇ
6301 ❤️{{{)}}}}ˇ
6302 "
6303 .unindent(),
6304 );
6305
6306 // autoclose multi-character pairs
6307 cx.set_state(
6308 &"
6309 ˇ
6310 ˇ
6311 "
6312 .unindent(),
6313 );
6314 cx.update_editor(|editor, window, cx| {
6315 editor.handle_input("/", window, cx);
6316 editor.handle_input("*", window, cx);
6317 });
6318 cx.assert_editor_state(
6319 &"
6320 /*ˇ */
6321 /*ˇ */
6322 "
6323 .unindent(),
6324 );
6325
6326 // one cursor autocloses a multi-character pair, one cursor
6327 // does not autoclose.
6328 cx.set_state(
6329 &"
6330 /ˇ
6331 ˇ
6332 "
6333 .unindent(),
6334 );
6335 cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
6336 cx.assert_editor_state(
6337 &"
6338 /*ˇ */
6339 *ˇ
6340 "
6341 .unindent(),
6342 );
6343
6344 // Don't autoclose if the next character isn't whitespace and isn't
6345 // listed in the language's "autoclose_before" section.
6346 cx.set_state("ˇa b");
6347 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6348 cx.assert_editor_state("{ˇa b");
6349
6350 // Don't autoclose if `close` is false for the bracket pair
6351 cx.set_state("ˇ");
6352 cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
6353 cx.assert_editor_state("[ˇ");
6354
6355 // Surround with brackets if text is selected
6356 cx.set_state("«aˇ» b");
6357 cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
6358 cx.assert_editor_state("{«aˇ»} b");
6359
6360 // Autclose pair where the start and end characters are the same
6361 cx.set_state("aˇ");
6362 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6363 cx.assert_editor_state("a\"ˇ\"");
6364 cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
6365 cx.assert_editor_state("a\"\"ˇ");
6366
6367 // Don't autoclose pair if autoclose is disabled
6368 cx.set_state("ˇ");
6369 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6370 cx.assert_editor_state("<ˇ");
6371
6372 // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
6373 cx.set_state("«aˇ» b");
6374 cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
6375 cx.assert_editor_state("<«aˇ»> b");
6376}
6377
6378#[gpui::test]
6379async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
6380 init_test(cx, |settings| {
6381 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
6382 });
6383
6384 let mut cx = EditorTestContext::new(cx).await;
6385
6386 let language = Arc::new(Language::new(
6387 LanguageConfig {
6388 brackets: BracketPairConfig {
6389 pairs: vec![
6390 BracketPair {
6391 start: "{".to_string(),
6392 end: "}".to_string(),
6393 close: true,
6394 surround: true,
6395 newline: true,
6396 },
6397 BracketPair {
6398 start: "(".to_string(),
6399 end: ")".to_string(),
6400 close: true,
6401 surround: true,
6402 newline: true,
6403 },
6404 BracketPair {
6405 start: "[".to_string(),
6406 end: "]".to_string(),
6407 close: false,
6408 surround: false,
6409 newline: true,
6410 },
6411 ],
6412 ..Default::default()
6413 },
6414 autoclose_before: "})]".to_string(),
6415 ..Default::default()
6416 },
6417 Some(tree_sitter_rust::LANGUAGE.into()),
6418 ));
6419
6420 cx.language_registry().add(language.clone());
6421 cx.update_buffer(|buffer, cx| {
6422 buffer.set_language(Some(language), cx);
6423 });
6424
6425 cx.set_state(
6426 &"
6427 ˇ
6428 ˇ
6429 ˇ
6430 "
6431 .unindent(),
6432 );
6433
6434 // ensure only matching closing brackets are skipped over
6435 cx.update_editor(|editor, window, cx| {
6436 editor.handle_input("}", window, cx);
6437 editor.move_left(&MoveLeft, window, cx);
6438 editor.handle_input(")", window, cx);
6439 editor.move_left(&MoveLeft, window, cx);
6440 });
6441 cx.assert_editor_state(
6442 &"
6443 ˇ)}
6444 ˇ)}
6445 ˇ)}
6446 "
6447 .unindent(),
6448 );
6449
6450 // skip-over closing brackets at multiple cursors
6451 cx.update_editor(|editor, window, cx| {
6452 editor.handle_input(")", window, cx);
6453 editor.handle_input("}", window, cx);
6454 });
6455 cx.assert_editor_state(
6456 &"
6457 )}ˇ
6458 )}ˇ
6459 )}ˇ
6460 "
6461 .unindent(),
6462 );
6463
6464 // ignore non-close brackets
6465 cx.update_editor(|editor, window, cx| {
6466 editor.handle_input("]", window, cx);
6467 editor.move_left(&MoveLeft, window, cx);
6468 editor.handle_input("]", window, cx);
6469 });
6470 cx.assert_editor_state(
6471 &"
6472 )}]ˇ]
6473 )}]ˇ]
6474 )}]ˇ]
6475 "
6476 .unindent(),
6477 );
6478}
6479
6480#[gpui::test]
6481async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
6482 init_test(cx, |_| {});
6483
6484 let mut cx = EditorTestContext::new(cx).await;
6485
6486 let html_language = Arc::new(
6487 Language::new(
6488 LanguageConfig {
6489 name: "HTML".into(),
6490 brackets: BracketPairConfig {
6491 pairs: vec![
6492 BracketPair {
6493 start: "<".into(),
6494 end: ">".into(),
6495 close: true,
6496 ..Default::default()
6497 },
6498 BracketPair {
6499 start: "{".into(),
6500 end: "}".into(),
6501 close: true,
6502 ..Default::default()
6503 },
6504 BracketPair {
6505 start: "(".into(),
6506 end: ")".into(),
6507 close: true,
6508 ..Default::default()
6509 },
6510 ],
6511 ..Default::default()
6512 },
6513 autoclose_before: "})]>".into(),
6514 ..Default::default()
6515 },
6516 Some(tree_sitter_html::LANGUAGE.into()),
6517 )
6518 .with_injection_query(
6519 r#"
6520 (script_element
6521 (raw_text) @injection.content
6522 (#set! injection.language "javascript"))
6523 "#,
6524 )
6525 .unwrap(),
6526 );
6527
6528 let javascript_language = Arc::new(Language::new(
6529 LanguageConfig {
6530 name: "JavaScript".into(),
6531 brackets: BracketPairConfig {
6532 pairs: vec![
6533 BracketPair {
6534 start: "/*".into(),
6535 end: " */".into(),
6536 close: true,
6537 ..Default::default()
6538 },
6539 BracketPair {
6540 start: "{".into(),
6541 end: "}".into(),
6542 close: true,
6543 ..Default::default()
6544 },
6545 BracketPair {
6546 start: "(".into(),
6547 end: ")".into(),
6548 close: true,
6549 ..Default::default()
6550 },
6551 ],
6552 ..Default::default()
6553 },
6554 autoclose_before: "})]>".into(),
6555 ..Default::default()
6556 },
6557 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
6558 ));
6559
6560 cx.language_registry().add(html_language.clone());
6561 cx.language_registry().add(javascript_language.clone());
6562
6563 cx.update_buffer(|buffer, cx| {
6564 buffer.set_language(Some(html_language), cx);
6565 });
6566
6567 cx.set_state(
6568 &r#"
6569 <body>ˇ
6570 <script>
6571 var x = 1;ˇ
6572 </script>
6573 </body>ˇ
6574 "#
6575 .unindent(),
6576 );
6577
6578 // Precondition: different languages are active at different locations.
6579 cx.update_editor(|editor, window, cx| {
6580 let snapshot = editor.snapshot(window, cx);
6581 let cursors = editor.selections.ranges::<usize>(cx);
6582 let languages = cursors
6583 .iter()
6584 .map(|c| snapshot.language_at(c.start).unwrap().name())
6585 .collect::<Vec<_>>();
6586 assert_eq!(
6587 languages,
6588 &["HTML".into(), "JavaScript".into(), "HTML".into()]
6589 );
6590 });
6591
6592 // Angle brackets autoclose in HTML, but not JavaScript.
6593 cx.update_editor(|editor, window, cx| {
6594 editor.handle_input("<", window, cx);
6595 editor.handle_input("a", window, cx);
6596 });
6597 cx.assert_editor_state(
6598 &r#"
6599 <body><aˇ>
6600 <script>
6601 var x = 1;<aˇ
6602 </script>
6603 </body><aˇ>
6604 "#
6605 .unindent(),
6606 );
6607
6608 // Curly braces and parens autoclose in both HTML and JavaScript.
6609 cx.update_editor(|editor, window, cx| {
6610 editor.handle_input(" b=", window, cx);
6611 editor.handle_input("{", window, cx);
6612 editor.handle_input("c", window, cx);
6613 editor.handle_input("(", window, cx);
6614 });
6615 cx.assert_editor_state(
6616 &r#"
6617 <body><a b={c(ˇ)}>
6618 <script>
6619 var x = 1;<a b={c(ˇ)}
6620 </script>
6621 </body><a b={c(ˇ)}>
6622 "#
6623 .unindent(),
6624 );
6625
6626 // Brackets that were already autoclosed are skipped.
6627 cx.update_editor(|editor, window, cx| {
6628 editor.handle_input(")", window, cx);
6629 editor.handle_input("d", window, cx);
6630 editor.handle_input("}", window, cx);
6631 });
6632 cx.assert_editor_state(
6633 &r#"
6634 <body><a b={c()d}ˇ>
6635 <script>
6636 var x = 1;<a b={c()d}ˇ
6637 </script>
6638 </body><a b={c()d}ˇ>
6639 "#
6640 .unindent(),
6641 );
6642 cx.update_editor(|editor, window, cx| {
6643 editor.handle_input(">", window, cx);
6644 });
6645 cx.assert_editor_state(
6646 &r#"
6647 <body><a b={c()d}>ˇ
6648 <script>
6649 var x = 1;<a b={c()d}>ˇ
6650 </script>
6651 </body><a b={c()d}>ˇ
6652 "#
6653 .unindent(),
6654 );
6655
6656 // Reset
6657 cx.set_state(
6658 &r#"
6659 <body>ˇ
6660 <script>
6661 var x = 1;ˇ
6662 </script>
6663 </body>ˇ
6664 "#
6665 .unindent(),
6666 );
6667
6668 cx.update_editor(|editor, window, cx| {
6669 editor.handle_input("<", window, cx);
6670 });
6671 cx.assert_editor_state(
6672 &r#"
6673 <body><ˇ>
6674 <script>
6675 var x = 1;<ˇ
6676 </script>
6677 </body><ˇ>
6678 "#
6679 .unindent(),
6680 );
6681
6682 // When backspacing, the closing angle brackets are removed.
6683 cx.update_editor(|editor, window, cx| {
6684 editor.backspace(&Backspace, window, cx);
6685 });
6686 cx.assert_editor_state(
6687 &r#"
6688 <body>ˇ
6689 <script>
6690 var x = 1;ˇ
6691 </script>
6692 </body>ˇ
6693 "#
6694 .unindent(),
6695 );
6696
6697 // Block comments autoclose in JavaScript, but not HTML.
6698 cx.update_editor(|editor, window, cx| {
6699 editor.handle_input("/", window, cx);
6700 editor.handle_input("*", window, cx);
6701 });
6702 cx.assert_editor_state(
6703 &r#"
6704 <body>/*ˇ
6705 <script>
6706 var x = 1;/*ˇ */
6707 </script>
6708 </body>/*ˇ
6709 "#
6710 .unindent(),
6711 );
6712}
6713
6714#[gpui::test]
6715async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
6716 init_test(cx, |_| {});
6717
6718 let mut cx = EditorTestContext::new(cx).await;
6719
6720 let rust_language = Arc::new(
6721 Language::new(
6722 LanguageConfig {
6723 name: "Rust".into(),
6724 brackets: serde_json::from_value(json!([
6725 { "start": "{", "end": "}", "close": true, "newline": true },
6726 { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
6727 ]))
6728 .unwrap(),
6729 autoclose_before: "})]>".into(),
6730 ..Default::default()
6731 },
6732 Some(tree_sitter_rust::LANGUAGE.into()),
6733 )
6734 .with_override_query("(string_literal) @string")
6735 .unwrap(),
6736 );
6737
6738 cx.language_registry().add(rust_language.clone());
6739 cx.update_buffer(|buffer, cx| {
6740 buffer.set_language(Some(rust_language), cx);
6741 });
6742
6743 cx.set_state(
6744 &r#"
6745 let x = ˇ
6746 "#
6747 .unindent(),
6748 );
6749
6750 // Inserting a quotation mark. A closing quotation mark is automatically inserted.
6751 cx.update_editor(|editor, window, cx| {
6752 editor.handle_input("\"", window, cx);
6753 });
6754 cx.assert_editor_state(
6755 &r#"
6756 let x = "ˇ"
6757 "#
6758 .unindent(),
6759 );
6760
6761 // Inserting another quotation mark. The cursor moves across the existing
6762 // automatically-inserted quotation mark.
6763 cx.update_editor(|editor, window, cx| {
6764 editor.handle_input("\"", window, cx);
6765 });
6766 cx.assert_editor_state(
6767 &r#"
6768 let x = ""ˇ
6769 "#
6770 .unindent(),
6771 );
6772
6773 // Reset
6774 cx.set_state(
6775 &r#"
6776 let x = ˇ
6777 "#
6778 .unindent(),
6779 );
6780
6781 // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
6782 cx.update_editor(|editor, window, cx| {
6783 editor.handle_input("\"", window, cx);
6784 editor.handle_input(" ", window, cx);
6785 editor.move_left(&Default::default(), window, cx);
6786 editor.handle_input("\\", window, cx);
6787 editor.handle_input("\"", window, cx);
6788 });
6789 cx.assert_editor_state(
6790 &r#"
6791 let x = "\"ˇ "
6792 "#
6793 .unindent(),
6794 );
6795
6796 // Inserting a closing quotation mark at the position of an automatically-inserted quotation
6797 // mark. Nothing is inserted.
6798 cx.update_editor(|editor, window, cx| {
6799 editor.move_right(&Default::default(), window, cx);
6800 editor.handle_input("\"", window, cx);
6801 });
6802 cx.assert_editor_state(
6803 &r#"
6804 let x = "\" "ˇ
6805 "#
6806 .unindent(),
6807 );
6808}
6809
6810#[gpui::test]
6811async fn test_surround_with_pair(cx: &mut TestAppContext) {
6812 init_test(cx, |_| {});
6813
6814 let language = Arc::new(Language::new(
6815 LanguageConfig {
6816 brackets: BracketPairConfig {
6817 pairs: vec![
6818 BracketPair {
6819 start: "{".to_string(),
6820 end: "}".to_string(),
6821 close: true,
6822 surround: true,
6823 newline: true,
6824 },
6825 BracketPair {
6826 start: "/* ".to_string(),
6827 end: "*/".to_string(),
6828 close: true,
6829 surround: true,
6830 ..Default::default()
6831 },
6832 ],
6833 ..Default::default()
6834 },
6835 ..Default::default()
6836 },
6837 Some(tree_sitter_rust::LANGUAGE.into()),
6838 ));
6839
6840 let text = r#"
6841 a
6842 b
6843 c
6844 "#
6845 .unindent();
6846
6847 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6848 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6849 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
6850 editor
6851 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
6852 .await;
6853
6854 editor.update_in(cx, |editor, window, cx| {
6855 editor.change_selections(None, window, cx, |s| {
6856 s.select_display_ranges([
6857 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6858 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6859 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
6860 ])
6861 });
6862
6863 editor.handle_input("{", window, cx);
6864 editor.handle_input("{", window, cx);
6865 editor.handle_input("{", window, cx);
6866 assert_eq!(
6867 editor.text(cx),
6868 "
6869 {{{a}}}
6870 {{{b}}}
6871 {{{c}}}
6872 "
6873 .unindent()
6874 );
6875 assert_eq!(
6876 editor.selections.display_ranges(cx),
6877 [
6878 DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
6879 DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
6880 DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
6881 ]
6882 );
6883
6884 editor.undo(&Undo, window, cx);
6885 editor.undo(&Undo, window, cx);
6886 editor.undo(&Undo, window, cx);
6887 assert_eq!(
6888 editor.text(cx),
6889 "
6890 a
6891 b
6892 c
6893 "
6894 .unindent()
6895 );
6896 assert_eq!(
6897 editor.selections.display_ranges(cx),
6898 [
6899 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6900 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6901 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6902 ]
6903 );
6904
6905 // Ensure inserting the first character of a multi-byte bracket pair
6906 // doesn't surround the selections with the bracket.
6907 editor.handle_input("/", window, cx);
6908 assert_eq!(
6909 editor.text(cx),
6910 "
6911 /
6912 /
6913 /
6914 "
6915 .unindent()
6916 );
6917 assert_eq!(
6918 editor.selections.display_ranges(cx),
6919 [
6920 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6921 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6922 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6923 ]
6924 );
6925
6926 editor.undo(&Undo, window, cx);
6927 assert_eq!(
6928 editor.text(cx),
6929 "
6930 a
6931 b
6932 c
6933 "
6934 .unindent()
6935 );
6936 assert_eq!(
6937 editor.selections.display_ranges(cx),
6938 [
6939 DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
6940 DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
6941 DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
6942 ]
6943 );
6944
6945 // Ensure inserting the last character of a multi-byte bracket pair
6946 // doesn't surround the selections with the bracket.
6947 editor.handle_input("*", window, cx);
6948 assert_eq!(
6949 editor.text(cx),
6950 "
6951 *
6952 *
6953 *
6954 "
6955 .unindent()
6956 );
6957 assert_eq!(
6958 editor.selections.display_ranges(cx),
6959 [
6960 DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
6961 DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
6962 DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
6963 ]
6964 );
6965 });
6966}
6967
6968#[gpui::test]
6969async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
6970 init_test(cx, |_| {});
6971
6972 let language = Arc::new(Language::new(
6973 LanguageConfig {
6974 brackets: BracketPairConfig {
6975 pairs: vec![BracketPair {
6976 start: "{".to_string(),
6977 end: "}".to_string(),
6978 close: true,
6979 surround: true,
6980 newline: true,
6981 }],
6982 ..Default::default()
6983 },
6984 autoclose_before: "}".to_string(),
6985 ..Default::default()
6986 },
6987 Some(tree_sitter_rust::LANGUAGE.into()),
6988 ));
6989
6990 let text = r#"
6991 a
6992 b
6993 c
6994 "#
6995 .unindent();
6996
6997 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
6998 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
6999 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7000 editor
7001 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7002 .await;
7003
7004 editor.update_in(cx, |editor, window, cx| {
7005 editor.change_selections(None, window, cx, |s| {
7006 s.select_ranges([
7007 Point::new(0, 1)..Point::new(0, 1),
7008 Point::new(1, 1)..Point::new(1, 1),
7009 Point::new(2, 1)..Point::new(2, 1),
7010 ])
7011 });
7012
7013 editor.handle_input("{", window, cx);
7014 editor.handle_input("{", window, cx);
7015 editor.handle_input("_", window, cx);
7016 assert_eq!(
7017 editor.text(cx),
7018 "
7019 a{{_}}
7020 b{{_}}
7021 c{{_}}
7022 "
7023 .unindent()
7024 );
7025 assert_eq!(
7026 editor.selections.ranges::<Point>(cx),
7027 [
7028 Point::new(0, 4)..Point::new(0, 4),
7029 Point::new(1, 4)..Point::new(1, 4),
7030 Point::new(2, 4)..Point::new(2, 4)
7031 ]
7032 );
7033
7034 editor.backspace(&Default::default(), window, cx);
7035 editor.backspace(&Default::default(), window, cx);
7036 assert_eq!(
7037 editor.text(cx),
7038 "
7039 a{}
7040 b{}
7041 c{}
7042 "
7043 .unindent()
7044 );
7045 assert_eq!(
7046 editor.selections.ranges::<Point>(cx),
7047 [
7048 Point::new(0, 2)..Point::new(0, 2),
7049 Point::new(1, 2)..Point::new(1, 2),
7050 Point::new(2, 2)..Point::new(2, 2)
7051 ]
7052 );
7053
7054 editor.delete_to_previous_word_start(&Default::default(), window, cx);
7055 assert_eq!(
7056 editor.text(cx),
7057 "
7058 a
7059 b
7060 c
7061 "
7062 .unindent()
7063 );
7064 assert_eq!(
7065 editor.selections.ranges::<Point>(cx),
7066 [
7067 Point::new(0, 1)..Point::new(0, 1),
7068 Point::new(1, 1)..Point::new(1, 1),
7069 Point::new(2, 1)..Point::new(2, 1)
7070 ]
7071 );
7072 });
7073}
7074
7075#[gpui::test]
7076async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
7077 init_test(cx, |settings| {
7078 settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
7079 });
7080
7081 let mut cx = EditorTestContext::new(cx).await;
7082
7083 let language = Arc::new(Language::new(
7084 LanguageConfig {
7085 brackets: BracketPairConfig {
7086 pairs: vec![
7087 BracketPair {
7088 start: "{".to_string(),
7089 end: "}".to_string(),
7090 close: true,
7091 surround: true,
7092 newline: true,
7093 },
7094 BracketPair {
7095 start: "(".to_string(),
7096 end: ")".to_string(),
7097 close: true,
7098 surround: true,
7099 newline: true,
7100 },
7101 BracketPair {
7102 start: "[".to_string(),
7103 end: "]".to_string(),
7104 close: false,
7105 surround: true,
7106 newline: true,
7107 },
7108 ],
7109 ..Default::default()
7110 },
7111 autoclose_before: "})]".to_string(),
7112 ..Default::default()
7113 },
7114 Some(tree_sitter_rust::LANGUAGE.into()),
7115 ));
7116
7117 cx.language_registry().add(language.clone());
7118 cx.update_buffer(|buffer, cx| {
7119 buffer.set_language(Some(language), cx);
7120 });
7121
7122 cx.set_state(
7123 &"
7124 {(ˇ)}
7125 [[ˇ]]
7126 {(ˇ)}
7127 "
7128 .unindent(),
7129 );
7130
7131 cx.update_editor(|editor, window, cx| {
7132 editor.backspace(&Default::default(), window, cx);
7133 editor.backspace(&Default::default(), window, cx);
7134 });
7135
7136 cx.assert_editor_state(
7137 &"
7138 ˇ
7139 ˇ]]
7140 ˇ
7141 "
7142 .unindent(),
7143 );
7144
7145 cx.update_editor(|editor, window, cx| {
7146 editor.handle_input("{", window, cx);
7147 editor.handle_input("{", window, cx);
7148 editor.move_right(&MoveRight, window, cx);
7149 editor.move_right(&MoveRight, window, cx);
7150 editor.move_left(&MoveLeft, window, cx);
7151 editor.move_left(&MoveLeft, window, cx);
7152 editor.backspace(&Default::default(), window, cx);
7153 });
7154
7155 cx.assert_editor_state(
7156 &"
7157 {ˇ}
7158 {ˇ}]]
7159 {ˇ}
7160 "
7161 .unindent(),
7162 );
7163
7164 cx.update_editor(|editor, window, cx| {
7165 editor.backspace(&Default::default(), window, cx);
7166 });
7167
7168 cx.assert_editor_state(
7169 &"
7170 ˇ
7171 ˇ]]
7172 ˇ
7173 "
7174 .unindent(),
7175 );
7176}
7177
7178#[gpui::test]
7179async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
7180 init_test(cx, |_| {});
7181
7182 let language = Arc::new(Language::new(
7183 LanguageConfig::default(),
7184 Some(tree_sitter_rust::LANGUAGE.into()),
7185 ));
7186
7187 let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
7188 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7189 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7190 editor
7191 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
7192 .await;
7193
7194 editor.update_in(cx, |editor, window, cx| {
7195 editor.set_auto_replace_emoji_shortcode(true);
7196
7197 editor.handle_input("Hello ", window, cx);
7198 editor.handle_input(":wave", window, cx);
7199 assert_eq!(editor.text(cx), "Hello :wave".unindent());
7200
7201 editor.handle_input(":", window, cx);
7202 assert_eq!(editor.text(cx), "Hello 👋".unindent());
7203
7204 editor.handle_input(" :smile", window, cx);
7205 assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
7206
7207 editor.handle_input(":", window, cx);
7208 assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
7209
7210 // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
7211 editor.handle_input(":wave", window, cx);
7212 assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
7213
7214 editor.handle_input(":", window, cx);
7215 assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
7216
7217 editor.handle_input(":1", window, cx);
7218 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
7219
7220 editor.handle_input(":", window, cx);
7221 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
7222
7223 // Ensure shortcode does not get replaced when it is part of a word
7224 editor.handle_input(" Test:wave", window, cx);
7225 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
7226
7227 editor.handle_input(":", window, cx);
7228 assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
7229
7230 editor.set_auto_replace_emoji_shortcode(false);
7231
7232 // Ensure shortcode does not get replaced when auto replace is off
7233 editor.handle_input(" :wave", window, cx);
7234 assert_eq!(
7235 editor.text(cx),
7236 "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
7237 );
7238
7239 editor.handle_input(":", window, cx);
7240 assert_eq!(
7241 editor.text(cx),
7242 "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
7243 );
7244 });
7245}
7246
7247#[gpui::test]
7248async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
7249 init_test(cx, |_| {});
7250
7251 let (text, insertion_ranges) = marked_text_ranges(
7252 indoc! {"
7253 ˇ
7254 "},
7255 false,
7256 );
7257
7258 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7259 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7260
7261 _ = editor.update_in(cx, |editor, window, cx| {
7262 let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
7263
7264 editor
7265 .insert_snippet(&insertion_ranges, snippet, window, cx)
7266 .unwrap();
7267
7268 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7269 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7270 assert_eq!(editor.text(cx), expected_text);
7271 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7272 }
7273
7274 assert(
7275 editor,
7276 cx,
7277 indoc! {"
7278 type «» =•
7279 "},
7280 );
7281
7282 assert!(editor.context_menu_visible(), "There should be a matches");
7283 });
7284}
7285
7286#[gpui::test]
7287async fn test_snippets(cx: &mut TestAppContext) {
7288 init_test(cx, |_| {});
7289
7290 let (text, insertion_ranges) = marked_text_ranges(
7291 indoc! {"
7292 a.ˇ b
7293 a.ˇ b
7294 a.ˇ b
7295 "},
7296 false,
7297 );
7298
7299 let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
7300 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
7301
7302 editor.update_in(cx, |editor, window, cx| {
7303 let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
7304
7305 editor
7306 .insert_snippet(&insertion_ranges, snippet, window, cx)
7307 .unwrap();
7308
7309 fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
7310 let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
7311 assert_eq!(editor.text(cx), expected_text);
7312 assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
7313 }
7314
7315 assert(
7316 editor,
7317 cx,
7318 indoc! {"
7319 a.f(«one», two, «three») b
7320 a.f(«one», two, «three») b
7321 a.f(«one», two, «three») b
7322 "},
7323 );
7324
7325 // Can't move earlier than the first tab stop
7326 assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
7327 assert(
7328 editor,
7329 cx,
7330 indoc! {"
7331 a.f(«one», two, «three») b
7332 a.f(«one», two, «three») b
7333 a.f(«one», two, «three») b
7334 "},
7335 );
7336
7337 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7338 assert(
7339 editor,
7340 cx,
7341 indoc! {"
7342 a.f(one, «two», three) b
7343 a.f(one, «two», three) b
7344 a.f(one, «two», three) b
7345 "},
7346 );
7347
7348 editor.move_to_prev_snippet_tabstop(window, cx);
7349 assert(
7350 editor,
7351 cx,
7352 indoc! {"
7353 a.f(«one», two, «three») b
7354 a.f(«one», two, «three») b
7355 a.f(«one», two, «three») b
7356 "},
7357 );
7358
7359 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7360 assert(
7361 editor,
7362 cx,
7363 indoc! {"
7364 a.f(one, «two», three) b
7365 a.f(one, «two», three) b
7366 a.f(one, «two», three) b
7367 "},
7368 );
7369 assert!(editor.move_to_next_snippet_tabstop(window, cx));
7370 assert(
7371 editor,
7372 cx,
7373 indoc! {"
7374 a.f(one, two, three)ˇ b
7375 a.f(one, two, three)ˇ b
7376 a.f(one, two, three)ˇ b
7377 "},
7378 );
7379
7380 // As soon as the last tab stop is reached, snippet state is gone
7381 editor.move_to_prev_snippet_tabstop(window, cx);
7382 assert(
7383 editor,
7384 cx,
7385 indoc! {"
7386 a.f(one, two, three)ˇ b
7387 a.f(one, two, three)ˇ b
7388 a.f(one, two, three)ˇ b
7389 "},
7390 );
7391 });
7392}
7393
7394#[gpui::test]
7395async fn test_document_format_during_save(cx: &mut TestAppContext) {
7396 init_test(cx, |_| {});
7397
7398 let fs = FakeFs::new(cx.executor());
7399 fs.insert_file(path!("/file.rs"), Default::default()).await;
7400
7401 let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
7402
7403 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7404 language_registry.add(rust_lang());
7405 let mut fake_servers = language_registry.register_fake_lsp(
7406 "Rust",
7407 FakeLspAdapter {
7408 capabilities: lsp::ServerCapabilities {
7409 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7410 ..Default::default()
7411 },
7412 ..Default::default()
7413 },
7414 );
7415
7416 let buffer = project
7417 .update(cx, |project, cx| {
7418 project.open_local_buffer(path!("/file.rs"), cx)
7419 })
7420 .await
7421 .unwrap();
7422
7423 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7424 let (editor, cx) = cx.add_window_view(|window, cx| {
7425 build_editor_with_project(project.clone(), buffer, window, cx)
7426 });
7427 editor.update_in(cx, |editor, window, cx| {
7428 editor.set_text("one\ntwo\nthree\n", window, cx)
7429 });
7430 assert!(cx.read(|cx| editor.is_dirty(cx)));
7431
7432 cx.executor().start_waiting();
7433 let fake_server = fake_servers.next().await.unwrap();
7434
7435 let save = editor
7436 .update_in(cx, |editor, window, cx| {
7437 editor.save(true, project.clone(), window, cx)
7438 })
7439 .unwrap();
7440 fake_server
7441 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7442 assert_eq!(
7443 params.text_document.uri,
7444 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7445 );
7446 assert_eq!(params.options.tab_size, 4);
7447 Ok(Some(vec![lsp::TextEdit::new(
7448 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7449 ", ".to_string(),
7450 )]))
7451 })
7452 .next()
7453 .await;
7454 cx.executor().start_waiting();
7455 save.await;
7456
7457 assert_eq!(
7458 editor.update(cx, |editor, cx| editor.text(cx)),
7459 "one, two\nthree\n"
7460 );
7461 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7462
7463 editor.update_in(cx, |editor, window, cx| {
7464 editor.set_text("one\ntwo\nthree\n", window, cx)
7465 });
7466 assert!(cx.read(|cx| editor.is_dirty(cx)));
7467
7468 // Ensure we can still save even if formatting hangs.
7469 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7470 assert_eq!(
7471 params.text_document.uri,
7472 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7473 );
7474 futures::future::pending::<()>().await;
7475 unreachable!()
7476 });
7477 let save = editor
7478 .update_in(cx, |editor, window, cx| {
7479 editor.save(true, project.clone(), window, cx)
7480 })
7481 .unwrap();
7482 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7483 cx.executor().start_waiting();
7484 save.await;
7485 assert_eq!(
7486 editor.update(cx, |editor, cx| editor.text(cx)),
7487 "one\ntwo\nthree\n"
7488 );
7489 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7490
7491 // For non-dirty buffer, no formatting request should be sent
7492 let save = editor
7493 .update_in(cx, |editor, window, cx| {
7494 editor.save(true, project.clone(), window, cx)
7495 })
7496 .unwrap();
7497 let _pending_format_request = fake_server
7498 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7499 panic!("Should not be invoked on non-dirty buffer");
7500 })
7501 .next();
7502 cx.executor().start_waiting();
7503 save.await;
7504
7505 // Set rust language override and assert overridden tabsize is sent to language server
7506 update_test_language_settings(cx, |settings| {
7507 settings.languages.insert(
7508 "Rust".into(),
7509 LanguageSettingsContent {
7510 tab_size: NonZeroU32::new(8),
7511 ..Default::default()
7512 },
7513 );
7514 });
7515
7516 editor.update_in(cx, |editor, window, cx| {
7517 editor.set_text("somehting_new\n", window, cx)
7518 });
7519 assert!(cx.read(|cx| editor.is_dirty(cx)));
7520 let save = editor
7521 .update_in(cx, |editor, window, cx| {
7522 editor.save(true, project.clone(), window, cx)
7523 })
7524 .unwrap();
7525 fake_server
7526 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7527 assert_eq!(
7528 params.text_document.uri,
7529 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7530 );
7531 assert_eq!(params.options.tab_size, 8);
7532 Ok(Some(vec![]))
7533 })
7534 .next()
7535 .await;
7536 cx.executor().start_waiting();
7537 save.await;
7538}
7539
7540#[gpui::test]
7541async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
7542 init_test(cx, |_| {});
7543
7544 let cols = 4;
7545 let rows = 10;
7546 let sample_text_1 = sample_text(rows, cols, 'a');
7547 assert_eq!(
7548 sample_text_1,
7549 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
7550 );
7551 let sample_text_2 = sample_text(rows, cols, 'l');
7552 assert_eq!(
7553 sample_text_2,
7554 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
7555 );
7556 let sample_text_3 = sample_text(rows, cols, 'v');
7557 assert_eq!(
7558 sample_text_3,
7559 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
7560 );
7561
7562 let fs = FakeFs::new(cx.executor());
7563 fs.insert_tree(
7564 path!("/a"),
7565 json!({
7566 "main.rs": sample_text_1,
7567 "other.rs": sample_text_2,
7568 "lib.rs": sample_text_3,
7569 }),
7570 )
7571 .await;
7572
7573 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
7574 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
7575 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
7576
7577 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7578 language_registry.add(rust_lang());
7579 let mut fake_servers = language_registry.register_fake_lsp(
7580 "Rust",
7581 FakeLspAdapter {
7582 capabilities: lsp::ServerCapabilities {
7583 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7584 ..Default::default()
7585 },
7586 ..Default::default()
7587 },
7588 );
7589
7590 let worktree = project.update(cx, |project, cx| {
7591 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
7592 assert_eq!(worktrees.len(), 1);
7593 worktrees.pop().unwrap()
7594 });
7595 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
7596
7597 let buffer_1 = project
7598 .update(cx, |project, cx| {
7599 project.open_buffer((worktree_id, "main.rs"), cx)
7600 })
7601 .await
7602 .unwrap();
7603 let buffer_2 = project
7604 .update(cx, |project, cx| {
7605 project.open_buffer((worktree_id, "other.rs"), cx)
7606 })
7607 .await
7608 .unwrap();
7609 let buffer_3 = project
7610 .update(cx, |project, cx| {
7611 project.open_buffer((worktree_id, "lib.rs"), cx)
7612 })
7613 .await
7614 .unwrap();
7615
7616 let multi_buffer = cx.new(|cx| {
7617 let mut multi_buffer = MultiBuffer::new(ReadWrite);
7618 multi_buffer.push_excerpts(
7619 buffer_1.clone(),
7620 [
7621 ExcerptRange {
7622 context: Point::new(0, 0)..Point::new(3, 0),
7623 primary: None,
7624 },
7625 ExcerptRange {
7626 context: Point::new(5, 0)..Point::new(7, 0),
7627 primary: None,
7628 },
7629 ExcerptRange {
7630 context: Point::new(9, 0)..Point::new(10, 4),
7631 primary: None,
7632 },
7633 ],
7634 cx,
7635 );
7636 multi_buffer.push_excerpts(
7637 buffer_2.clone(),
7638 [
7639 ExcerptRange {
7640 context: Point::new(0, 0)..Point::new(3, 0),
7641 primary: None,
7642 },
7643 ExcerptRange {
7644 context: Point::new(5, 0)..Point::new(7, 0),
7645 primary: None,
7646 },
7647 ExcerptRange {
7648 context: Point::new(9, 0)..Point::new(10, 4),
7649 primary: None,
7650 },
7651 ],
7652 cx,
7653 );
7654 multi_buffer.push_excerpts(
7655 buffer_3.clone(),
7656 [
7657 ExcerptRange {
7658 context: Point::new(0, 0)..Point::new(3, 0),
7659 primary: None,
7660 },
7661 ExcerptRange {
7662 context: Point::new(5, 0)..Point::new(7, 0),
7663 primary: None,
7664 },
7665 ExcerptRange {
7666 context: Point::new(9, 0)..Point::new(10, 4),
7667 primary: None,
7668 },
7669 ],
7670 cx,
7671 );
7672 multi_buffer
7673 });
7674 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
7675 Editor::new(
7676 EditorMode::Full,
7677 multi_buffer,
7678 Some(project.clone()),
7679 window,
7680 cx,
7681 )
7682 });
7683
7684 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7685 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7686 s.select_ranges(Some(1..2))
7687 });
7688 editor.insert("|one|two|three|", window, cx);
7689 });
7690 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7691 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7692 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7693 s.select_ranges(Some(60..70))
7694 });
7695 editor.insert("|four|five|six|", window, cx);
7696 });
7697 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7698
7699 // First two buffers should be edited, but not the third one.
7700 assert_eq!(
7701 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7702 "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}",
7703 );
7704 buffer_1.update(cx, |buffer, _| {
7705 assert!(buffer.is_dirty());
7706 assert_eq!(
7707 buffer.text(),
7708 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7709 )
7710 });
7711 buffer_2.update(cx, |buffer, _| {
7712 assert!(buffer.is_dirty());
7713 assert_eq!(
7714 buffer.text(),
7715 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7716 )
7717 });
7718 buffer_3.update(cx, |buffer, _| {
7719 assert!(!buffer.is_dirty());
7720 assert_eq!(buffer.text(), sample_text_3,)
7721 });
7722 cx.executor().run_until_parked();
7723
7724 cx.executor().start_waiting();
7725 let save = multi_buffer_editor
7726 .update_in(cx, |editor, window, cx| {
7727 editor.save(true, project.clone(), window, cx)
7728 })
7729 .unwrap();
7730
7731 let fake_server = fake_servers.next().await.unwrap();
7732 fake_server
7733 .server
7734 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7735 Ok(Some(vec![lsp::TextEdit::new(
7736 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7737 format!("[{} formatted]", params.text_document.uri),
7738 )]))
7739 })
7740 .detach();
7741 save.await;
7742
7743 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7744 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7745 assert_eq!(
7746 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7747 uri!("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}"),
7748 );
7749 buffer_1.update(cx, |buffer, _| {
7750 assert!(!buffer.is_dirty());
7751 assert_eq!(
7752 buffer.text(),
7753 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
7754 )
7755 });
7756 buffer_2.update(cx, |buffer, _| {
7757 assert!(!buffer.is_dirty());
7758 assert_eq!(
7759 buffer.text(),
7760 uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
7761 )
7762 });
7763 buffer_3.update(cx, |buffer, _| {
7764 assert!(!buffer.is_dirty());
7765 assert_eq!(buffer.text(), sample_text_3,)
7766 });
7767}
7768
7769#[gpui::test]
7770async fn test_range_format_during_save(cx: &mut TestAppContext) {
7771 init_test(cx, |_| {});
7772
7773 let fs = FakeFs::new(cx.executor());
7774 fs.insert_file(path!("/file.rs"), Default::default()).await;
7775
7776 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7777
7778 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7779 language_registry.add(rust_lang());
7780 let mut fake_servers = language_registry.register_fake_lsp(
7781 "Rust",
7782 FakeLspAdapter {
7783 capabilities: lsp::ServerCapabilities {
7784 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7785 ..Default::default()
7786 },
7787 ..Default::default()
7788 },
7789 );
7790
7791 let buffer = project
7792 .update(cx, |project, cx| {
7793 project.open_local_buffer(path!("/file.rs"), cx)
7794 })
7795 .await
7796 .unwrap();
7797
7798 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7799 let (editor, cx) = cx.add_window_view(|window, cx| {
7800 build_editor_with_project(project.clone(), buffer, window, cx)
7801 });
7802 editor.update_in(cx, |editor, window, cx| {
7803 editor.set_text("one\ntwo\nthree\n", window, cx)
7804 });
7805 assert!(cx.read(|cx| editor.is_dirty(cx)));
7806
7807 cx.executor().start_waiting();
7808 let fake_server = fake_servers.next().await.unwrap();
7809
7810 let save = editor
7811 .update_in(cx, |editor, window, cx| {
7812 editor.save(true, project.clone(), window, cx)
7813 })
7814 .unwrap();
7815 fake_server
7816 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7817 assert_eq!(
7818 params.text_document.uri,
7819 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7820 );
7821 assert_eq!(params.options.tab_size, 4);
7822 Ok(Some(vec![lsp::TextEdit::new(
7823 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7824 ", ".to_string(),
7825 )]))
7826 })
7827 .next()
7828 .await;
7829 cx.executor().start_waiting();
7830 save.await;
7831 assert_eq!(
7832 editor.update(cx, |editor, cx| editor.text(cx)),
7833 "one, two\nthree\n"
7834 );
7835 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7836
7837 editor.update_in(cx, |editor, window, cx| {
7838 editor.set_text("one\ntwo\nthree\n", window, cx)
7839 });
7840 assert!(cx.read(|cx| editor.is_dirty(cx)));
7841
7842 // Ensure we can still save even if formatting hangs.
7843 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7844 move |params, _| async move {
7845 assert_eq!(
7846 params.text_document.uri,
7847 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7848 );
7849 futures::future::pending::<()>().await;
7850 unreachable!()
7851 },
7852 );
7853 let save = editor
7854 .update_in(cx, |editor, window, cx| {
7855 editor.save(true, project.clone(), window, cx)
7856 })
7857 .unwrap();
7858 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7859 cx.executor().start_waiting();
7860 save.await;
7861 assert_eq!(
7862 editor.update(cx, |editor, cx| editor.text(cx)),
7863 "one\ntwo\nthree\n"
7864 );
7865 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7866
7867 // For non-dirty buffer, no formatting request should be sent
7868 let save = editor
7869 .update_in(cx, |editor, window, cx| {
7870 editor.save(true, project.clone(), window, cx)
7871 })
7872 .unwrap();
7873 let _pending_format_request = fake_server
7874 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7875 panic!("Should not be invoked on non-dirty buffer");
7876 })
7877 .next();
7878 cx.executor().start_waiting();
7879 save.await;
7880
7881 // Set Rust language override and assert overridden tabsize is sent to language server
7882 update_test_language_settings(cx, |settings| {
7883 settings.languages.insert(
7884 "Rust".into(),
7885 LanguageSettingsContent {
7886 tab_size: NonZeroU32::new(8),
7887 ..Default::default()
7888 },
7889 );
7890 });
7891
7892 editor.update_in(cx, |editor, window, cx| {
7893 editor.set_text("somehting_new\n", window, cx)
7894 });
7895 assert!(cx.read(|cx| editor.is_dirty(cx)));
7896 let save = editor
7897 .update_in(cx, |editor, window, cx| {
7898 editor.save(true, project.clone(), window, cx)
7899 })
7900 .unwrap();
7901 fake_server
7902 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7903 assert_eq!(
7904 params.text_document.uri,
7905 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7906 );
7907 assert_eq!(params.options.tab_size, 8);
7908 Ok(Some(vec![]))
7909 })
7910 .next()
7911 .await;
7912 cx.executor().start_waiting();
7913 save.await;
7914}
7915
7916#[gpui::test]
7917async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
7918 init_test(cx, |settings| {
7919 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7920 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7921 ))
7922 });
7923
7924 let fs = FakeFs::new(cx.executor());
7925 fs.insert_file(path!("/file.rs"), Default::default()).await;
7926
7927 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7928
7929 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7930 language_registry.add(Arc::new(Language::new(
7931 LanguageConfig {
7932 name: "Rust".into(),
7933 matcher: LanguageMatcher {
7934 path_suffixes: vec!["rs".to_string()],
7935 ..Default::default()
7936 },
7937 ..LanguageConfig::default()
7938 },
7939 Some(tree_sitter_rust::LANGUAGE.into()),
7940 )));
7941 update_test_language_settings(cx, |settings| {
7942 // Enable Prettier formatting for the same buffer, and ensure
7943 // LSP is called instead of Prettier.
7944 settings.defaults.prettier = Some(PrettierSettings {
7945 allowed: true,
7946 ..PrettierSettings::default()
7947 });
7948 });
7949 let mut fake_servers = language_registry.register_fake_lsp(
7950 "Rust",
7951 FakeLspAdapter {
7952 capabilities: lsp::ServerCapabilities {
7953 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7954 ..Default::default()
7955 },
7956 ..Default::default()
7957 },
7958 );
7959
7960 let buffer = project
7961 .update(cx, |project, cx| {
7962 project.open_local_buffer(path!("/file.rs"), cx)
7963 })
7964 .await
7965 .unwrap();
7966
7967 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7968 let (editor, cx) = cx.add_window_view(|window, cx| {
7969 build_editor_with_project(project.clone(), buffer, window, cx)
7970 });
7971 editor.update_in(cx, |editor, window, cx| {
7972 editor.set_text("one\ntwo\nthree\n", window, cx)
7973 });
7974
7975 cx.executor().start_waiting();
7976 let fake_server = fake_servers.next().await.unwrap();
7977
7978 let format = editor
7979 .update_in(cx, |editor, window, cx| {
7980 editor.perform_format(
7981 project.clone(),
7982 FormatTrigger::Manual,
7983 FormatTarget::Buffers,
7984 window,
7985 cx,
7986 )
7987 })
7988 .unwrap();
7989 fake_server
7990 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7991 assert_eq!(
7992 params.text_document.uri,
7993 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7994 );
7995 assert_eq!(params.options.tab_size, 4);
7996 Ok(Some(vec![lsp::TextEdit::new(
7997 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7998 ", ".to_string(),
7999 )]))
8000 })
8001 .next()
8002 .await;
8003 cx.executor().start_waiting();
8004 format.await;
8005 assert_eq!(
8006 editor.update(cx, |editor, cx| editor.text(cx)),
8007 "one, two\nthree\n"
8008 );
8009
8010 editor.update_in(cx, |editor, window, cx| {
8011 editor.set_text("one\ntwo\nthree\n", window, cx)
8012 });
8013 // Ensure we don't lock if formatting hangs.
8014 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
8015 assert_eq!(
8016 params.text_document.uri,
8017 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8018 );
8019 futures::future::pending::<()>().await;
8020 unreachable!()
8021 });
8022 let format = editor
8023 .update_in(cx, |editor, window, cx| {
8024 editor.perform_format(
8025 project,
8026 FormatTrigger::Manual,
8027 FormatTarget::Buffers,
8028 window,
8029 cx,
8030 )
8031 })
8032 .unwrap();
8033 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
8034 cx.executor().start_waiting();
8035 format.await;
8036 assert_eq!(
8037 editor.update(cx, |editor, cx| editor.text(cx)),
8038 "one\ntwo\nthree\n"
8039 );
8040}
8041
8042#[gpui::test]
8043async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
8044 init_test(cx, |settings| {
8045 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
8046 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
8047 ))
8048 });
8049
8050 let fs = FakeFs::new(cx.executor());
8051 fs.insert_file(path!("/file.ts"), Default::default()).await;
8052
8053 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8054
8055 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8056 language_registry.add(Arc::new(Language::new(
8057 LanguageConfig {
8058 name: "TypeScript".into(),
8059 matcher: LanguageMatcher {
8060 path_suffixes: vec!["ts".to_string()],
8061 ..Default::default()
8062 },
8063 ..LanguageConfig::default()
8064 },
8065 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
8066 )));
8067 update_test_language_settings(cx, |settings| {
8068 settings.defaults.prettier = Some(PrettierSettings {
8069 allowed: true,
8070 ..PrettierSettings::default()
8071 });
8072 });
8073 let mut fake_servers = language_registry.register_fake_lsp(
8074 "TypeScript",
8075 FakeLspAdapter {
8076 capabilities: lsp::ServerCapabilities {
8077 code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
8078 ..Default::default()
8079 },
8080 ..Default::default()
8081 },
8082 );
8083
8084 let buffer = project
8085 .update(cx, |project, cx| {
8086 project.open_local_buffer(path!("/file.ts"), cx)
8087 })
8088 .await
8089 .unwrap();
8090
8091 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8092 let (editor, cx) = cx.add_window_view(|window, cx| {
8093 build_editor_with_project(project.clone(), buffer, window, cx)
8094 });
8095 editor.update_in(cx, |editor, window, cx| {
8096 editor.set_text(
8097 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8098 window,
8099 cx,
8100 )
8101 });
8102
8103 cx.executor().start_waiting();
8104 let fake_server = fake_servers.next().await.unwrap();
8105
8106 let format = editor
8107 .update_in(cx, |editor, window, cx| {
8108 editor.perform_code_action_kind(
8109 project.clone(),
8110 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8111 window,
8112 cx,
8113 )
8114 })
8115 .unwrap();
8116 fake_server
8117 .handle_request::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
8118 assert_eq!(
8119 params.text_document.uri,
8120 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8121 );
8122 Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
8123 lsp::CodeAction {
8124 title: "Organize Imports".to_string(),
8125 kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
8126 edit: Some(lsp::WorkspaceEdit {
8127 changes: Some(
8128 [(
8129 params.text_document.uri.clone(),
8130 vec![lsp::TextEdit::new(
8131 lsp::Range::new(
8132 lsp::Position::new(1, 0),
8133 lsp::Position::new(2, 0),
8134 ),
8135 "".to_string(),
8136 )],
8137 )]
8138 .into_iter()
8139 .collect(),
8140 ),
8141 ..Default::default()
8142 }),
8143 ..Default::default()
8144 },
8145 )]))
8146 })
8147 .next()
8148 .await;
8149 cx.executor().start_waiting();
8150 format.await;
8151 assert_eq!(
8152 editor.update(cx, |editor, cx| editor.text(cx)),
8153 "import { a } from 'module';\n\nconst x = a;\n"
8154 );
8155
8156 editor.update_in(cx, |editor, window, cx| {
8157 editor.set_text(
8158 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8159 window,
8160 cx,
8161 )
8162 });
8163 // Ensure we don't lock if code action hangs.
8164 fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
8165 move |params, _| async move {
8166 assert_eq!(
8167 params.text_document.uri,
8168 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8169 );
8170 futures::future::pending::<()>().await;
8171 unreachable!()
8172 },
8173 );
8174 let format = editor
8175 .update_in(cx, |editor, window, cx| {
8176 editor.perform_code_action_kind(
8177 project,
8178 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8179 window,
8180 cx,
8181 )
8182 })
8183 .unwrap();
8184 cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
8185 cx.executor().start_waiting();
8186 format.await;
8187 assert_eq!(
8188 editor.update(cx, |editor, cx| editor.text(cx)),
8189 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
8190 );
8191}
8192
8193#[gpui::test]
8194async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
8195 init_test(cx, |_| {});
8196
8197 let mut cx = EditorLspTestContext::new_rust(
8198 lsp::ServerCapabilities {
8199 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8200 ..Default::default()
8201 },
8202 cx,
8203 )
8204 .await;
8205
8206 cx.set_state(indoc! {"
8207 one.twoˇ
8208 "});
8209
8210 // The format request takes a long time. When it completes, it inserts
8211 // a newline and an indent before the `.`
8212 cx.lsp
8213 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
8214 let executor = cx.background_executor().clone();
8215 async move {
8216 executor.timer(Duration::from_millis(100)).await;
8217 Ok(Some(vec![lsp::TextEdit {
8218 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
8219 new_text: "\n ".into(),
8220 }]))
8221 }
8222 });
8223
8224 // Submit a format request.
8225 let format_1 = cx
8226 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8227 .unwrap();
8228 cx.executor().run_until_parked();
8229
8230 // Submit a second format request.
8231 let format_2 = cx
8232 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8233 .unwrap();
8234 cx.executor().run_until_parked();
8235
8236 // Wait for both format requests to complete
8237 cx.executor().advance_clock(Duration::from_millis(200));
8238 cx.executor().start_waiting();
8239 format_1.await.unwrap();
8240 cx.executor().start_waiting();
8241 format_2.await.unwrap();
8242
8243 // The formatting edits only happens once.
8244 cx.assert_editor_state(indoc! {"
8245 one
8246 .twoˇ
8247 "});
8248}
8249
8250#[gpui::test]
8251async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
8252 init_test(cx, |settings| {
8253 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
8254 });
8255
8256 let mut cx = EditorLspTestContext::new_rust(
8257 lsp::ServerCapabilities {
8258 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8259 ..Default::default()
8260 },
8261 cx,
8262 )
8263 .await;
8264
8265 // Set up a buffer white some trailing whitespace and no trailing newline.
8266 cx.set_state(
8267 &[
8268 "one ", //
8269 "twoˇ", //
8270 "three ", //
8271 "four", //
8272 ]
8273 .join("\n"),
8274 );
8275
8276 // Submit a format request.
8277 let format = cx
8278 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8279 .unwrap();
8280
8281 // Record which buffer changes have been sent to the language server
8282 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
8283 cx.lsp
8284 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
8285 let buffer_changes = buffer_changes.clone();
8286 move |params, _| {
8287 buffer_changes.lock().extend(
8288 params
8289 .content_changes
8290 .into_iter()
8291 .map(|e| (e.range.unwrap(), e.text)),
8292 );
8293 }
8294 });
8295
8296 // Handle formatting requests to the language server.
8297 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
8298 let buffer_changes = buffer_changes.clone();
8299 move |_, _| {
8300 // When formatting is requested, trailing whitespace has already been stripped,
8301 // and the trailing newline has already been added.
8302 assert_eq!(
8303 &buffer_changes.lock()[1..],
8304 &[
8305 (
8306 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
8307 "".into()
8308 ),
8309 (
8310 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
8311 "".into()
8312 ),
8313 (
8314 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
8315 "\n".into()
8316 ),
8317 ]
8318 );
8319
8320 // Insert blank lines between each line of the buffer.
8321 async move {
8322 Ok(Some(vec![
8323 lsp::TextEdit {
8324 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
8325 new_text: "\n".into(),
8326 },
8327 lsp::TextEdit {
8328 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
8329 new_text: "\n".into(),
8330 },
8331 ]))
8332 }
8333 }
8334 });
8335
8336 // After formatting the buffer, the trailing whitespace is stripped,
8337 // a newline is appended, and the edits provided by the language server
8338 // have been applied.
8339 format.await.unwrap();
8340 cx.assert_editor_state(
8341 &[
8342 "one", //
8343 "", //
8344 "twoˇ", //
8345 "", //
8346 "three", //
8347 "four", //
8348 "", //
8349 ]
8350 .join("\n"),
8351 );
8352
8353 // Undoing the formatting undoes the trailing whitespace removal, the
8354 // trailing newline, and the LSP edits.
8355 cx.update_buffer(|buffer, cx| buffer.undo(cx));
8356 cx.assert_editor_state(
8357 &[
8358 "one ", //
8359 "twoˇ", //
8360 "three ", //
8361 "four", //
8362 ]
8363 .join("\n"),
8364 );
8365}
8366
8367#[gpui::test]
8368async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
8369 cx: &mut TestAppContext,
8370) {
8371 init_test(cx, |_| {});
8372
8373 cx.update(|cx| {
8374 cx.update_global::<SettingsStore, _>(|settings, cx| {
8375 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8376 settings.auto_signature_help = Some(true);
8377 });
8378 });
8379 });
8380
8381 let mut cx = EditorLspTestContext::new_rust(
8382 lsp::ServerCapabilities {
8383 signature_help_provider: Some(lsp::SignatureHelpOptions {
8384 ..Default::default()
8385 }),
8386 ..Default::default()
8387 },
8388 cx,
8389 )
8390 .await;
8391
8392 let language = Language::new(
8393 LanguageConfig {
8394 name: "Rust".into(),
8395 brackets: BracketPairConfig {
8396 pairs: vec![
8397 BracketPair {
8398 start: "{".to_string(),
8399 end: "}".to_string(),
8400 close: true,
8401 surround: true,
8402 newline: true,
8403 },
8404 BracketPair {
8405 start: "(".to_string(),
8406 end: ")".to_string(),
8407 close: true,
8408 surround: true,
8409 newline: true,
8410 },
8411 BracketPair {
8412 start: "/*".to_string(),
8413 end: " */".to_string(),
8414 close: true,
8415 surround: true,
8416 newline: true,
8417 },
8418 BracketPair {
8419 start: "[".to_string(),
8420 end: "]".to_string(),
8421 close: false,
8422 surround: false,
8423 newline: true,
8424 },
8425 BracketPair {
8426 start: "\"".to_string(),
8427 end: "\"".to_string(),
8428 close: true,
8429 surround: true,
8430 newline: false,
8431 },
8432 BracketPair {
8433 start: "<".to_string(),
8434 end: ">".to_string(),
8435 close: false,
8436 surround: true,
8437 newline: true,
8438 },
8439 ],
8440 ..Default::default()
8441 },
8442 autoclose_before: "})]".to_string(),
8443 ..Default::default()
8444 },
8445 Some(tree_sitter_rust::LANGUAGE.into()),
8446 );
8447 let language = Arc::new(language);
8448
8449 cx.language_registry().add(language.clone());
8450 cx.update_buffer(|buffer, cx| {
8451 buffer.set_language(Some(language), cx);
8452 });
8453
8454 cx.set_state(
8455 &r#"
8456 fn main() {
8457 sampleˇ
8458 }
8459 "#
8460 .unindent(),
8461 );
8462
8463 cx.update_editor(|editor, window, cx| {
8464 editor.handle_input("(", window, cx);
8465 });
8466 cx.assert_editor_state(
8467 &"
8468 fn main() {
8469 sample(ˇ)
8470 }
8471 "
8472 .unindent(),
8473 );
8474
8475 let mocked_response = lsp::SignatureHelp {
8476 signatures: vec![lsp::SignatureInformation {
8477 label: "fn sample(param1: u8, param2: u8)".to_string(),
8478 documentation: None,
8479 parameters: Some(vec![
8480 lsp::ParameterInformation {
8481 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8482 documentation: None,
8483 },
8484 lsp::ParameterInformation {
8485 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8486 documentation: None,
8487 },
8488 ]),
8489 active_parameter: None,
8490 }],
8491 active_signature: Some(0),
8492 active_parameter: Some(0),
8493 };
8494 handle_signature_help_request(&mut cx, mocked_response).await;
8495
8496 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8497 .await;
8498
8499 cx.editor(|editor, _, _| {
8500 let signature_help_state = editor.signature_help_state.popover().cloned();
8501 assert_eq!(
8502 signature_help_state.unwrap().label,
8503 "param1: u8, param2: u8"
8504 );
8505 });
8506}
8507
8508#[gpui::test]
8509async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
8510 init_test(cx, |_| {});
8511
8512 cx.update(|cx| {
8513 cx.update_global::<SettingsStore, _>(|settings, cx| {
8514 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8515 settings.auto_signature_help = Some(false);
8516 settings.show_signature_help_after_edits = Some(false);
8517 });
8518 });
8519 });
8520
8521 let mut cx = EditorLspTestContext::new_rust(
8522 lsp::ServerCapabilities {
8523 signature_help_provider: Some(lsp::SignatureHelpOptions {
8524 ..Default::default()
8525 }),
8526 ..Default::default()
8527 },
8528 cx,
8529 )
8530 .await;
8531
8532 let language = Language::new(
8533 LanguageConfig {
8534 name: "Rust".into(),
8535 brackets: BracketPairConfig {
8536 pairs: vec![
8537 BracketPair {
8538 start: "{".to_string(),
8539 end: "}".to_string(),
8540 close: true,
8541 surround: true,
8542 newline: true,
8543 },
8544 BracketPair {
8545 start: "(".to_string(),
8546 end: ")".to_string(),
8547 close: true,
8548 surround: true,
8549 newline: true,
8550 },
8551 BracketPair {
8552 start: "/*".to_string(),
8553 end: " */".to_string(),
8554 close: true,
8555 surround: true,
8556 newline: true,
8557 },
8558 BracketPair {
8559 start: "[".to_string(),
8560 end: "]".to_string(),
8561 close: false,
8562 surround: false,
8563 newline: true,
8564 },
8565 BracketPair {
8566 start: "\"".to_string(),
8567 end: "\"".to_string(),
8568 close: true,
8569 surround: true,
8570 newline: false,
8571 },
8572 BracketPair {
8573 start: "<".to_string(),
8574 end: ">".to_string(),
8575 close: false,
8576 surround: true,
8577 newline: true,
8578 },
8579 ],
8580 ..Default::default()
8581 },
8582 autoclose_before: "})]".to_string(),
8583 ..Default::default()
8584 },
8585 Some(tree_sitter_rust::LANGUAGE.into()),
8586 );
8587 let language = Arc::new(language);
8588
8589 cx.language_registry().add(language.clone());
8590 cx.update_buffer(|buffer, cx| {
8591 buffer.set_language(Some(language), cx);
8592 });
8593
8594 // Ensure that signature_help is not called when no signature help is enabled.
8595 cx.set_state(
8596 &r#"
8597 fn main() {
8598 sampleˇ
8599 }
8600 "#
8601 .unindent(),
8602 );
8603 cx.update_editor(|editor, window, cx| {
8604 editor.handle_input("(", window, cx);
8605 });
8606 cx.assert_editor_state(
8607 &"
8608 fn main() {
8609 sample(ˇ)
8610 }
8611 "
8612 .unindent(),
8613 );
8614 cx.editor(|editor, _, _| {
8615 assert!(editor.signature_help_state.task().is_none());
8616 });
8617
8618 let mocked_response = lsp::SignatureHelp {
8619 signatures: vec![lsp::SignatureInformation {
8620 label: "fn sample(param1: u8, param2: u8)".to_string(),
8621 documentation: None,
8622 parameters: Some(vec![
8623 lsp::ParameterInformation {
8624 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8625 documentation: None,
8626 },
8627 lsp::ParameterInformation {
8628 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8629 documentation: None,
8630 },
8631 ]),
8632 active_parameter: None,
8633 }],
8634 active_signature: Some(0),
8635 active_parameter: Some(0),
8636 };
8637
8638 // Ensure that signature_help is called when enabled afte edits
8639 cx.update(|_, cx| {
8640 cx.update_global::<SettingsStore, _>(|settings, cx| {
8641 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8642 settings.auto_signature_help = Some(false);
8643 settings.show_signature_help_after_edits = Some(true);
8644 });
8645 });
8646 });
8647 cx.set_state(
8648 &r#"
8649 fn main() {
8650 sampleˇ
8651 }
8652 "#
8653 .unindent(),
8654 );
8655 cx.update_editor(|editor, window, cx| {
8656 editor.handle_input("(", window, cx);
8657 });
8658 cx.assert_editor_state(
8659 &"
8660 fn main() {
8661 sample(ˇ)
8662 }
8663 "
8664 .unindent(),
8665 );
8666 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8667 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8668 .await;
8669 cx.update_editor(|editor, _, _| {
8670 let signature_help_state = editor.signature_help_state.popover().cloned();
8671 assert!(signature_help_state.is_some());
8672 assert_eq!(
8673 signature_help_state.unwrap().label,
8674 "param1: u8, param2: u8"
8675 );
8676 editor.signature_help_state = SignatureHelpState::default();
8677 });
8678
8679 // Ensure that signature_help is called when auto signature help override is enabled
8680 cx.update(|_, cx| {
8681 cx.update_global::<SettingsStore, _>(|settings, cx| {
8682 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8683 settings.auto_signature_help = Some(true);
8684 settings.show_signature_help_after_edits = Some(false);
8685 });
8686 });
8687 });
8688 cx.set_state(
8689 &r#"
8690 fn main() {
8691 sampleˇ
8692 }
8693 "#
8694 .unindent(),
8695 );
8696 cx.update_editor(|editor, window, cx| {
8697 editor.handle_input("(", window, cx);
8698 });
8699 cx.assert_editor_state(
8700 &"
8701 fn main() {
8702 sample(ˇ)
8703 }
8704 "
8705 .unindent(),
8706 );
8707 handle_signature_help_request(&mut cx, mocked_response).await;
8708 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8709 .await;
8710 cx.editor(|editor, _, _| {
8711 let signature_help_state = editor.signature_help_state.popover().cloned();
8712 assert!(signature_help_state.is_some());
8713 assert_eq!(
8714 signature_help_state.unwrap().label,
8715 "param1: u8, param2: u8"
8716 );
8717 });
8718}
8719
8720#[gpui::test]
8721async fn test_signature_help(cx: &mut TestAppContext) {
8722 init_test(cx, |_| {});
8723 cx.update(|cx| {
8724 cx.update_global::<SettingsStore, _>(|settings, cx| {
8725 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8726 settings.auto_signature_help = Some(true);
8727 });
8728 });
8729 });
8730
8731 let mut cx = EditorLspTestContext::new_rust(
8732 lsp::ServerCapabilities {
8733 signature_help_provider: Some(lsp::SignatureHelpOptions {
8734 ..Default::default()
8735 }),
8736 ..Default::default()
8737 },
8738 cx,
8739 )
8740 .await;
8741
8742 // A test that directly calls `show_signature_help`
8743 cx.update_editor(|editor, window, cx| {
8744 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8745 });
8746
8747 let mocked_response = lsp::SignatureHelp {
8748 signatures: vec![lsp::SignatureInformation {
8749 label: "fn sample(param1: u8, param2: u8)".to_string(),
8750 documentation: None,
8751 parameters: Some(vec![
8752 lsp::ParameterInformation {
8753 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8754 documentation: None,
8755 },
8756 lsp::ParameterInformation {
8757 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8758 documentation: None,
8759 },
8760 ]),
8761 active_parameter: None,
8762 }],
8763 active_signature: Some(0),
8764 active_parameter: Some(0),
8765 };
8766 handle_signature_help_request(&mut cx, mocked_response).await;
8767
8768 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8769 .await;
8770
8771 cx.editor(|editor, _, _| {
8772 let signature_help_state = editor.signature_help_state.popover().cloned();
8773 assert!(signature_help_state.is_some());
8774 assert_eq!(
8775 signature_help_state.unwrap().label,
8776 "param1: u8, param2: u8"
8777 );
8778 });
8779
8780 // When exiting outside from inside the brackets, `signature_help` is closed.
8781 cx.set_state(indoc! {"
8782 fn main() {
8783 sample(ˇ);
8784 }
8785
8786 fn sample(param1: u8, param2: u8) {}
8787 "});
8788
8789 cx.update_editor(|editor, window, cx| {
8790 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
8791 });
8792
8793 let mocked_response = lsp::SignatureHelp {
8794 signatures: Vec::new(),
8795 active_signature: None,
8796 active_parameter: None,
8797 };
8798 handle_signature_help_request(&mut cx, mocked_response).await;
8799
8800 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8801 .await;
8802
8803 cx.editor(|editor, _, _| {
8804 assert!(!editor.signature_help_state.is_shown());
8805 });
8806
8807 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8808 cx.set_state(indoc! {"
8809 fn main() {
8810 sample(ˇ);
8811 }
8812
8813 fn sample(param1: u8, param2: u8) {}
8814 "});
8815
8816 let mocked_response = lsp::SignatureHelp {
8817 signatures: vec![lsp::SignatureInformation {
8818 label: "fn sample(param1: u8, param2: u8)".to_string(),
8819 documentation: None,
8820 parameters: Some(vec![
8821 lsp::ParameterInformation {
8822 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8823 documentation: None,
8824 },
8825 lsp::ParameterInformation {
8826 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8827 documentation: None,
8828 },
8829 ]),
8830 active_parameter: None,
8831 }],
8832 active_signature: Some(0),
8833 active_parameter: Some(0),
8834 };
8835 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8836 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8837 .await;
8838 cx.editor(|editor, _, _| {
8839 assert!(editor.signature_help_state.is_shown());
8840 });
8841
8842 // Restore the popover with more parameter input
8843 cx.set_state(indoc! {"
8844 fn main() {
8845 sample(param1, param2ˇ);
8846 }
8847
8848 fn sample(param1: u8, param2: u8) {}
8849 "});
8850
8851 let mocked_response = lsp::SignatureHelp {
8852 signatures: vec![lsp::SignatureInformation {
8853 label: "fn sample(param1: u8, param2: u8)".to_string(),
8854 documentation: None,
8855 parameters: Some(vec![
8856 lsp::ParameterInformation {
8857 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8858 documentation: None,
8859 },
8860 lsp::ParameterInformation {
8861 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8862 documentation: None,
8863 },
8864 ]),
8865 active_parameter: None,
8866 }],
8867 active_signature: Some(0),
8868 active_parameter: Some(1),
8869 };
8870 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8871 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8872 .await;
8873
8874 // When selecting a range, the popover is gone.
8875 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8876 cx.update_editor(|editor, window, cx| {
8877 editor.change_selections(None, window, cx, |s| {
8878 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8879 })
8880 });
8881 cx.assert_editor_state(indoc! {"
8882 fn main() {
8883 sample(param1, «ˇparam2»);
8884 }
8885
8886 fn sample(param1: u8, param2: u8) {}
8887 "});
8888 cx.editor(|editor, _, _| {
8889 assert!(!editor.signature_help_state.is_shown());
8890 });
8891
8892 // When unselecting again, the popover is back if within the brackets.
8893 cx.update_editor(|editor, window, cx| {
8894 editor.change_selections(None, window, cx, |s| {
8895 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8896 })
8897 });
8898 cx.assert_editor_state(indoc! {"
8899 fn main() {
8900 sample(param1, ˇparam2);
8901 }
8902
8903 fn sample(param1: u8, param2: u8) {}
8904 "});
8905 handle_signature_help_request(&mut cx, mocked_response).await;
8906 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8907 .await;
8908 cx.editor(|editor, _, _| {
8909 assert!(editor.signature_help_state.is_shown());
8910 });
8911
8912 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8913 cx.update_editor(|editor, window, cx| {
8914 editor.change_selections(None, window, cx, |s| {
8915 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8916 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8917 })
8918 });
8919 cx.assert_editor_state(indoc! {"
8920 fn main() {
8921 sample(param1, ˇparam2);
8922 }
8923
8924 fn sample(param1: u8, param2: u8) {}
8925 "});
8926
8927 let mocked_response = lsp::SignatureHelp {
8928 signatures: vec![lsp::SignatureInformation {
8929 label: "fn sample(param1: u8, param2: u8)".to_string(),
8930 documentation: None,
8931 parameters: Some(vec![
8932 lsp::ParameterInformation {
8933 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8934 documentation: None,
8935 },
8936 lsp::ParameterInformation {
8937 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8938 documentation: None,
8939 },
8940 ]),
8941 active_parameter: None,
8942 }],
8943 active_signature: Some(0),
8944 active_parameter: Some(1),
8945 };
8946 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8947 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8948 .await;
8949 cx.update_editor(|editor, _, cx| {
8950 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8951 });
8952 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8953 .await;
8954 cx.update_editor(|editor, window, cx| {
8955 editor.change_selections(None, window, cx, |s| {
8956 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8957 })
8958 });
8959 cx.assert_editor_state(indoc! {"
8960 fn main() {
8961 sample(param1, «ˇparam2»);
8962 }
8963
8964 fn sample(param1: u8, param2: u8) {}
8965 "});
8966 cx.update_editor(|editor, window, cx| {
8967 editor.change_selections(None, window, cx, |s| {
8968 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8969 })
8970 });
8971 cx.assert_editor_state(indoc! {"
8972 fn main() {
8973 sample(param1, ˇparam2);
8974 }
8975
8976 fn sample(param1: u8, param2: u8) {}
8977 "});
8978 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8979 .await;
8980}
8981
8982#[gpui::test]
8983async fn test_completion(cx: &mut TestAppContext) {
8984 init_test(cx, |_| {});
8985
8986 let mut cx = EditorLspTestContext::new_rust(
8987 lsp::ServerCapabilities {
8988 completion_provider: Some(lsp::CompletionOptions {
8989 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8990 resolve_provider: Some(true),
8991 ..Default::default()
8992 }),
8993 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8994 ..Default::default()
8995 },
8996 cx,
8997 )
8998 .await;
8999 let counter = Arc::new(AtomicUsize::new(0));
9000
9001 cx.set_state(indoc! {"
9002 oneˇ
9003 two
9004 three
9005 "});
9006 cx.simulate_keystroke(".");
9007 handle_completion_request(
9008 &mut cx,
9009 indoc! {"
9010 one.|<>
9011 two
9012 three
9013 "},
9014 vec!["first_completion", "second_completion"],
9015 counter.clone(),
9016 )
9017 .await;
9018 cx.condition(|editor, _| editor.context_menu_visible())
9019 .await;
9020 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9021
9022 let _handler = handle_signature_help_request(
9023 &mut cx,
9024 lsp::SignatureHelp {
9025 signatures: vec![lsp::SignatureInformation {
9026 label: "test signature".to_string(),
9027 documentation: None,
9028 parameters: Some(vec![lsp::ParameterInformation {
9029 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
9030 documentation: None,
9031 }]),
9032 active_parameter: None,
9033 }],
9034 active_signature: None,
9035 active_parameter: None,
9036 },
9037 );
9038 cx.update_editor(|editor, window, cx| {
9039 assert!(
9040 !editor.signature_help_state.is_shown(),
9041 "No signature help was called for"
9042 );
9043 editor.show_signature_help(&ShowSignatureHelp, window, cx);
9044 });
9045 cx.run_until_parked();
9046 cx.update_editor(|editor, _, _| {
9047 assert!(
9048 !editor.signature_help_state.is_shown(),
9049 "No signature help should be shown when completions menu is open"
9050 );
9051 });
9052
9053 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9054 editor.context_menu_next(&Default::default(), window, cx);
9055 editor
9056 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9057 .unwrap()
9058 });
9059 cx.assert_editor_state(indoc! {"
9060 one.second_completionˇ
9061 two
9062 three
9063 "});
9064
9065 handle_resolve_completion_request(
9066 &mut cx,
9067 Some(vec![
9068 (
9069 //This overlaps with the primary completion edit which is
9070 //misbehavior from the LSP spec, test that we filter it out
9071 indoc! {"
9072 one.second_ˇcompletion
9073 two
9074 threeˇ
9075 "},
9076 "overlapping additional edit",
9077 ),
9078 (
9079 indoc! {"
9080 one.second_completion
9081 two
9082 threeˇ
9083 "},
9084 "\nadditional edit",
9085 ),
9086 ]),
9087 )
9088 .await;
9089 apply_additional_edits.await.unwrap();
9090 cx.assert_editor_state(indoc! {"
9091 one.second_completionˇ
9092 two
9093 three
9094 additional edit
9095 "});
9096
9097 cx.set_state(indoc! {"
9098 one.second_completion
9099 twoˇ
9100 threeˇ
9101 additional edit
9102 "});
9103 cx.simulate_keystroke(" ");
9104 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9105 cx.simulate_keystroke("s");
9106 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9107
9108 cx.assert_editor_state(indoc! {"
9109 one.second_completion
9110 two sˇ
9111 three sˇ
9112 additional edit
9113 "});
9114 handle_completion_request(
9115 &mut cx,
9116 indoc! {"
9117 one.second_completion
9118 two s
9119 three <s|>
9120 additional edit
9121 "},
9122 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
9123 counter.clone(),
9124 )
9125 .await;
9126 cx.condition(|editor, _| editor.context_menu_visible())
9127 .await;
9128 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
9129
9130 cx.simulate_keystroke("i");
9131
9132 handle_completion_request(
9133 &mut cx,
9134 indoc! {"
9135 one.second_completion
9136 two si
9137 three <si|>
9138 additional edit
9139 "},
9140 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
9141 counter.clone(),
9142 )
9143 .await;
9144 cx.condition(|editor, _| editor.context_menu_visible())
9145 .await;
9146 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
9147
9148 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9149 editor
9150 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9151 .unwrap()
9152 });
9153 cx.assert_editor_state(indoc! {"
9154 one.second_completion
9155 two sixth_completionˇ
9156 three sixth_completionˇ
9157 additional edit
9158 "});
9159
9160 apply_additional_edits.await.unwrap();
9161
9162 update_test_language_settings(&mut cx, |settings| {
9163 settings.defaults.show_completions_on_input = Some(false);
9164 });
9165 cx.set_state("editorˇ");
9166 cx.simulate_keystroke(".");
9167 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9168 cx.simulate_keystroke("c");
9169 cx.simulate_keystroke("l");
9170 cx.simulate_keystroke("o");
9171 cx.assert_editor_state("editor.cloˇ");
9172 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9173 cx.update_editor(|editor, window, cx| {
9174 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
9175 });
9176 handle_completion_request(
9177 &mut cx,
9178 "editor.<clo|>",
9179 vec!["close", "clobber"],
9180 counter.clone(),
9181 )
9182 .await;
9183 cx.condition(|editor, _| editor.context_menu_visible())
9184 .await;
9185 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
9186
9187 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9188 editor
9189 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9190 .unwrap()
9191 });
9192 cx.assert_editor_state("editor.closeˇ");
9193 handle_resolve_completion_request(&mut cx, None).await;
9194 apply_additional_edits.await.unwrap();
9195}
9196
9197#[gpui::test]
9198async fn test_words_completion(cx: &mut TestAppContext) {
9199 let lsp_fetch_timeout_ms = 10;
9200 init_test(cx, |language_settings| {
9201 language_settings.defaults.completions = Some(CompletionSettings {
9202 words: WordsCompletionMode::Fallback,
9203 lsp: true,
9204 lsp_fetch_timeout_ms: 10,
9205 });
9206 });
9207
9208 let mut cx = EditorLspTestContext::new_rust(
9209 lsp::ServerCapabilities {
9210 completion_provider: Some(lsp::CompletionOptions {
9211 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9212 ..lsp::CompletionOptions::default()
9213 }),
9214 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9215 ..lsp::ServerCapabilities::default()
9216 },
9217 cx,
9218 )
9219 .await;
9220
9221 let throttle_completions = Arc::new(AtomicBool::new(false));
9222
9223 let lsp_throttle_completions = throttle_completions.clone();
9224 let _completion_requests_handler =
9225 cx.lsp
9226 .server
9227 .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
9228 let lsp_throttle_completions = lsp_throttle_completions.clone();
9229 async move {
9230 if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
9231 cx.background_executor()
9232 .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
9233 .await;
9234 }
9235 Ok(Some(lsp::CompletionResponse::Array(vec![
9236 lsp::CompletionItem {
9237 label: "first".into(),
9238 ..lsp::CompletionItem::default()
9239 },
9240 lsp::CompletionItem {
9241 label: "last".into(),
9242 ..lsp::CompletionItem::default()
9243 },
9244 ])))
9245 }
9246 });
9247
9248 cx.set_state(indoc! {"
9249 oneˇ
9250 two
9251 three
9252 "});
9253 cx.simulate_keystroke(".");
9254 cx.executor().run_until_parked();
9255 cx.condition(|editor, _| editor.context_menu_visible())
9256 .await;
9257 cx.update_editor(|editor, window, cx| {
9258 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9259 {
9260 assert_eq!(
9261 completion_menu_entries(&menu),
9262 &["first", "last"],
9263 "When LSP server is fast to reply, no fallback word completions are used"
9264 );
9265 } else {
9266 panic!("expected completion menu to be open");
9267 }
9268 editor.cancel(&Cancel, window, cx);
9269 });
9270 cx.executor().run_until_parked();
9271 cx.condition(|editor, _| !editor.context_menu_visible())
9272 .await;
9273
9274 throttle_completions.store(true, atomic::Ordering::Release);
9275 cx.simulate_keystroke(".");
9276 cx.executor()
9277 .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
9278 cx.executor().run_until_parked();
9279 cx.condition(|editor, _| editor.context_menu_visible())
9280 .await;
9281 cx.update_editor(|editor, _, _| {
9282 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9283 {
9284 assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
9285 "When LSP server is slow, document words can be shown instead, if configured accordingly");
9286 } else {
9287 panic!("expected completion menu to be open");
9288 }
9289 });
9290}
9291
9292#[gpui::test]
9293async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
9294 init_test(cx, |language_settings| {
9295 language_settings.defaults.completions = Some(CompletionSettings {
9296 words: WordsCompletionMode::Enabled,
9297 lsp: true,
9298 lsp_fetch_timeout_ms: 0,
9299 });
9300 });
9301
9302 let mut cx = EditorLspTestContext::new_rust(
9303 lsp::ServerCapabilities {
9304 completion_provider: Some(lsp::CompletionOptions {
9305 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9306 ..lsp::CompletionOptions::default()
9307 }),
9308 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9309 ..lsp::ServerCapabilities::default()
9310 },
9311 cx,
9312 )
9313 .await;
9314
9315 let _completion_requests_handler =
9316 cx.lsp
9317 .server
9318 .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9319 Ok(Some(lsp::CompletionResponse::Array(vec![
9320 lsp::CompletionItem {
9321 label: "first".into(),
9322 ..lsp::CompletionItem::default()
9323 },
9324 lsp::CompletionItem {
9325 label: "last".into(),
9326 ..lsp::CompletionItem::default()
9327 },
9328 ])))
9329 });
9330
9331 cx.set_state(indoc! {"ˇ
9332 first
9333 last
9334 second
9335 "});
9336 cx.simulate_keystroke(".");
9337 cx.executor().run_until_parked();
9338 cx.condition(|editor, _| editor.context_menu_visible())
9339 .await;
9340 cx.update_editor(|editor, window, cx| {
9341 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9342 {
9343 assert_eq!(
9344 completion_menu_entries(&menu),
9345 &["first", "last", "second"],
9346 "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
9347 );
9348 } else {
9349 panic!("expected completion menu to be open");
9350 }
9351 editor.cancel(&Cancel, window, cx);
9352 });
9353}
9354
9355#[gpui::test]
9356async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
9357 init_test(cx, |language_settings| {
9358 language_settings.defaults.completions = Some(CompletionSettings {
9359 words: WordsCompletionMode::Fallback,
9360 lsp: false,
9361 lsp_fetch_timeout_ms: 0,
9362 });
9363 });
9364
9365 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
9366
9367 cx.set_state(indoc! {"ˇ
9368 0_usize
9369 let
9370 33
9371 4.5f32
9372 "});
9373 cx.update_editor(|editor, window, cx| {
9374 editor.show_completions(&ShowCompletions::default(), window, cx);
9375 });
9376 cx.executor().run_until_parked();
9377 cx.condition(|editor, _| editor.context_menu_visible())
9378 .await;
9379 cx.update_editor(|editor, window, cx| {
9380 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9381 {
9382 assert_eq!(
9383 completion_menu_entries(&menu),
9384 &["let"],
9385 "With no digits in the completion query, no digits should be in the word completions"
9386 );
9387 } else {
9388 panic!("expected completion menu to be open");
9389 }
9390 editor.cancel(&Cancel, window, cx);
9391 });
9392
9393 cx.set_state(indoc! {"3ˇ
9394 0_usize
9395 let
9396 3
9397 33.35f32
9398 "});
9399 cx.update_editor(|editor, window, cx| {
9400 editor.show_completions(&ShowCompletions::default(), window, cx);
9401 });
9402 cx.executor().run_until_parked();
9403 cx.condition(|editor, _| editor.context_menu_visible())
9404 .await;
9405 cx.update_editor(|editor, _, _| {
9406 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9407 {
9408 assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
9409 return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
9410 } else {
9411 panic!("expected completion menu to be open");
9412 }
9413 });
9414}
9415
9416#[gpui::test]
9417async fn test_multiline_completion(cx: &mut TestAppContext) {
9418 init_test(cx, |_| {});
9419
9420 let fs = FakeFs::new(cx.executor());
9421 fs.insert_tree(
9422 path!("/a"),
9423 json!({
9424 "main.ts": "a",
9425 }),
9426 )
9427 .await;
9428
9429 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
9430 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9431 let typescript_language = Arc::new(Language::new(
9432 LanguageConfig {
9433 name: "TypeScript".into(),
9434 matcher: LanguageMatcher {
9435 path_suffixes: vec!["ts".to_string()],
9436 ..LanguageMatcher::default()
9437 },
9438 line_comments: vec!["// ".into()],
9439 ..LanguageConfig::default()
9440 },
9441 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
9442 ));
9443 language_registry.add(typescript_language.clone());
9444 let mut fake_servers = language_registry.register_fake_lsp(
9445 "TypeScript",
9446 FakeLspAdapter {
9447 capabilities: lsp::ServerCapabilities {
9448 completion_provider: Some(lsp::CompletionOptions {
9449 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9450 ..lsp::CompletionOptions::default()
9451 }),
9452 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9453 ..lsp::ServerCapabilities::default()
9454 },
9455 // Emulate vtsls label generation
9456 label_for_completion: Some(Box::new(|item, _| {
9457 let text = if let Some(description) = item
9458 .label_details
9459 .as_ref()
9460 .and_then(|label_details| label_details.description.as_ref())
9461 {
9462 format!("{} {}", item.label, description)
9463 } else if let Some(detail) = &item.detail {
9464 format!("{} {}", item.label, detail)
9465 } else {
9466 item.label.clone()
9467 };
9468 let len = text.len();
9469 Some(language::CodeLabel {
9470 text,
9471 runs: Vec::new(),
9472 filter_range: 0..len,
9473 })
9474 })),
9475 ..FakeLspAdapter::default()
9476 },
9477 );
9478 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
9479 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9480 let worktree_id = workspace
9481 .update(cx, |workspace, _window, cx| {
9482 workspace.project().update(cx, |project, cx| {
9483 project.worktrees(cx).next().unwrap().read(cx).id()
9484 })
9485 })
9486 .unwrap();
9487 let _buffer = project
9488 .update(cx, |project, cx| {
9489 project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
9490 })
9491 .await
9492 .unwrap();
9493 let editor = workspace
9494 .update(cx, |workspace, window, cx| {
9495 workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
9496 })
9497 .unwrap()
9498 .await
9499 .unwrap()
9500 .downcast::<Editor>()
9501 .unwrap();
9502 let fake_server = fake_servers.next().await.unwrap();
9503
9504 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
9505 let multiline_label_2 = "a\nb\nc\n";
9506 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
9507 let multiline_description = "d\ne\nf\n";
9508 let multiline_detail_2 = "g\nh\ni\n";
9509
9510 let mut completion_handle =
9511 fake_server.handle_request::<lsp::request::Completion, _, _>(move |params, _| async move {
9512 Ok(Some(lsp::CompletionResponse::Array(vec![
9513 lsp::CompletionItem {
9514 label: multiline_label.to_string(),
9515 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9516 range: lsp::Range {
9517 start: lsp::Position {
9518 line: params.text_document_position.position.line,
9519 character: params.text_document_position.position.character,
9520 },
9521 end: lsp::Position {
9522 line: params.text_document_position.position.line,
9523 character: params.text_document_position.position.character,
9524 },
9525 },
9526 new_text: "new_text_1".to_string(),
9527 })),
9528 ..lsp::CompletionItem::default()
9529 },
9530 lsp::CompletionItem {
9531 label: "single line label 1".to_string(),
9532 detail: Some(multiline_detail.to_string()),
9533 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9534 range: lsp::Range {
9535 start: lsp::Position {
9536 line: params.text_document_position.position.line,
9537 character: params.text_document_position.position.character,
9538 },
9539 end: lsp::Position {
9540 line: params.text_document_position.position.line,
9541 character: params.text_document_position.position.character,
9542 },
9543 },
9544 new_text: "new_text_2".to_string(),
9545 })),
9546 ..lsp::CompletionItem::default()
9547 },
9548 lsp::CompletionItem {
9549 label: "single line label 2".to_string(),
9550 label_details: Some(lsp::CompletionItemLabelDetails {
9551 description: Some(multiline_description.to_string()),
9552 detail: None,
9553 }),
9554 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9555 range: lsp::Range {
9556 start: lsp::Position {
9557 line: params.text_document_position.position.line,
9558 character: params.text_document_position.position.character,
9559 },
9560 end: lsp::Position {
9561 line: params.text_document_position.position.line,
9562 character: params.text_document_position.position.character,
9563 },
9564 },
9565 new_text: "new_text_2".to_string(),
9566 })),
9567 ..lsp::CompletionItem::default()
9568 },
9569 lsp::CompletionItem {
9570 label: multiline_label_2.to_string(),
9571 detail: Some(multiline_detail_2.to_string()),
9572 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9573 range: lsp::Range {
9574 start: lsp::Position {
9575 line: params.text_document_position.position.line,
9576 character: params.text_document_position.position.character,
9577 },
9578 end: lsp::Position {
9579 line: params.text_document_position.position.line,
9580 character: params.text_document_position.position.character,
9581 },
9582 },
9583 new_text: "new_text_3".to_string(),
9584 })),
9585 ..lsp::CompletionItem::default()
9586 },
9587 lsp::CompletionItem {
9588 label: "Label with many spaces and \t but without newlines".to_string(),
9589 detail: Some(
9590 "Details with many spaces and \t but without newlines".to_string(),
9591 ),
9592 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9593 range: lsp::Range {
9594 start: lsp::Position {
9595 line: params.text_document_position.position.line,
9596 character: params.text_document_position.position.character,
9597 },
9598 end: lsp::Position {
9599 line: params.text_document_position.position.line,
9600 character: params.text_document_position.position.character,
9601 },
9602 },
9603 new_text: "new_text_4".to_string(),
9604 })),
9605 ..lsp::CompletionItem::default()
9606 },
9607 ])))
9608 });
9609
9610 editor.update_in(cx, |editor, window, cx| {
9611 cx.focus_self(window);
9612 editor.move_to_end(&MoveToEnd, window, cx);
9613 editor.handle_input(".", window, cx);
9614 });
9615 cx.run_until_parked();
9616 completion_handle.next().await.unwrap();
9617
9618 editor.update(cx, |editor, _| {
9619 assert!(editor.context_menu_visible());
9620 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9621 {
9622 let completion_labels = menu
9623 .completions
9624 .borrow()
9625 .iter()
9626 .map(|c| c.label.text.clone())
9627 .collect::<Vec<_>>();
9628 assert_eq!(
9629 completion_labels,
9630 &[
9631 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
9632 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
9633 "single line label 2 d e f ",
9634 "a b c g h i ",
9635 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
9636 ],
9637 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
9638 );
9639
9640 for completion in menu
9641 .completions
9642 .borrow()
9643 .iter() {
9644 assert_eq!(
9645 completion.label.filter_range,
9646 0..completion.label.text.len(),
9647 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
9648 );
9649 }
9650
9651 } else {
9652 panic!("expected completion menu to be open");
9653 }
9654 });
9655}
9656
9657#[gpui::test]
9658async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
9659 init_test(cx, |_| {});
9660 let mut cx = EditorLspTestContext::new_rust(
9661 lsp::ServerCapabilities {
9662 completion_provider: Some(lsp::CompletionOptions {
9663 trigger_characters: Some(vec![".".to_string()]),
9664 ..Default::default()
9665 }),
9666 ..Default::default()
9667 },
9668 cx,
9669 )
9670 .await;
9671 cx.lsp
9672 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9673 Ok(Some(lsp::CompletionResponse::Array(vec![
9674 lsp::CompletionItem {
9675 label: "first".into(),
9676 ..Default::default()
9677 },
9678 lsp::CompletionItem {
9679 label: "last".into(),
9680 ..Default::default()
9681 },
9682 ])))
9683 });
9684 cx.set_state("variableˇ");
9685 cx.simulate_keystroke(".");
9686 cx.executor().run_until_parked();
9687
9688 cx.update_editor(|editor, _, _| {
9689 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9690 {
9691 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
9692 } else {
9693 panic!("expected completion menu to be open");
9694 }
9695 });
9696
9697 cx.update_editor(|editor, window, cx| {
9698 editor.move_page_down(&MovePageDown::default(), window, cx);
9699 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9700 {
9701 assert!(
9702 menu.selected_item == 1,
9703 "expected PageDown to select the last item from the context menu"
9704 );
9705 } else {
9706 panic!("expected completion menu to stay open after PageDown");
9707 }
9708 });
9709
9710 cx.update_editor(|editor, window, cx| {
9711 editor.move_page_up(&MovePageUp::default(), window, cx);
9712 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9713 {
9714 assert!(
9715 menu.selected_item == 0,
9716 "expected PageUp to select the first item from the context menu"
9717 );
9718 } else {
9719 panic!("expected completion menu to stay open after PageUp");
9720 }
9721 });
9722}
9723
9724#[gpui::test]
9725async fn test_completion_sort(cx: &mut TestAppContext) {
9726 init_test(cx, |_| {});
9727 let mut cx = EditorLspTestContext::new_rust(
9728 lsp::ServerCapabilities {
9729 completion_provider: Some(lsp::CompletionOptions {
9730 trigger_characters: Some(vec![".".to_string()]),
9731 ..Default::default()
9732 }),
9733 ..Default::default()
9734 },
9735 cx,
9736 )
9737 .await;
9738 cx.lsp
9739 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9740 Ok(Some(lsp::CompletionResponse::Array(vec![
9741 lsp::CompletionItem {
9742 label: "Range".into(),
9743 sort_text: Some("a".into()),
9744 ..Default::default()
9745 },
9746 lsp::CompletionItem {
9747 label: "r".into(),
9748 sort_text: Some("b".into()),
9749 ..Default::default()
9750 },
9751 lsp::CompletionItem {
9752 label: "ret".into(),
9753 sort_text: Some("c".into()),
9754 ..Default::default()
9755 },
9756 lsp::CompletionItem {
9757 label: "return".into(),
9758 sort_text: Some("d".into()),
9759 ..Default::default()
9760 },
9761 lsp::CompletionItem {
9762 label: "slice".into(),
9763 sort_text: Some("d".into()),
9764 ..Default::default()
9765 },
9766 ])))
9767 });
9768 cx.set_state("rˇ");
9769 cx.executor().run_until_parked();
9770 cx.update_editor(|editor, window, cx| {
9771 editor.show_completions(
9772 &ShowCompletions {
9773 trigger: Some("r".into()),
9774 },
9775 window,
9776 cx,
9777 );
9778 });
9779 cx.executor().run_until_parked();
9780
9781 cx.update_editor(|editor, _, _| {
9782 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9783 {
9784 assert_eq!(
9785 completion_menu_entries(&menu),
9786 &["r", "ret", "Range", "return"]
9787 );
9788 } else {
9789 panic!("expected completion menu to be open");
9790 }
9791 });
9792}
9793
9794#[gpui::test]
9795async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
9796 init_test(cx, |_| {});
9797
9798 let mut cx = EditorLspTestContext::new_rust(
9799 lsp::ServerCapabilities {
9800 completion_provider: Some(lsp::CompletionOptions {
9801 trigger_characters: Some(vec![".".to_string()]),
9802 resolve_provider: Some(true),
9803 ..Default::default()
9804 }),
9805 ..Default::default()
9806 },
9807 cx,
9808 )
9809 .await;
9810
9811 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9812 cx.simulate_keystroke(".");
9813 let completion_item = lsp::CompletionItem {
9814 label: "Some".into(),
9815 kind: Some(lsp::CompletionItemKind::SNIPPET),
9816 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9817 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9818 kind: lsp::MarkupKind::Markdown,
9819 value: "```rust\nSome(2)\n```".to_string(),
9820 })),
9821 deprecated: Some(false),
9822 sort_text: Some("Some".to_string()),
9823 filter_text: Some("Some".to_string()),
9824 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9825 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9826 range: lsp::Range {
9827 start: lsp::Position {
9828 line: 0,
9829 character: 22,
9830 },
9831 end: lsp::Position {
9832 line: 0,
9833 character: 22,
9834 },
9835 },
9836 new_text: "Some(2)".to_string(),
9837 })),
9838 additional_text_edits: Some(vec![lsp::TextEdit {
9839 range: lsp::Range {
9840 start: lsp::Position {
9841 line: 0,
9842 character: 20,
9843 },
9844 end: lsp::Position {
9845 line: 0,
9846 character: 22,
9847 },
9848 },
9849 new_text: "".to_string(),
9850 }]),
9851 ..Default::default()
9852 };
9853
9854 let closure_completion_item = completion_item.clone();
9855 let counter = Arc::new(AtomicUsize::new(0));
9856 let counter_clone = counter.clone();
9857 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9858 let task_completion_item = closure_completion_item.clone();
9859 counter_clone.fetch_add(1, atomic::Ordering::Release);
9860 async move {
9861 Ok(Some(lsp::CompletionResponse::Array(vec![
9862 task_completion_item,
9863 ])))
9864 }
9865 });
9866
9867 cx.condition(|editor, _| editor.context_menu_visible())
9868 .await;
9869 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
9870 assert!(request.next().await.is_some());
9871 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9872
9873 cx.simulate_keystroke("S");
9874 cx.simulate_keystroke("o");
9875 cx.simulate_keystroke("m");
9876 cx.condition(|editor, _| editor.context_menu_visible())
9877 .await;
9878 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
9879 assert!(request.next().await.is_some());
9880 assert!(request.next().await.is_some());
9881 assert!(request.next().await.is_some());
9882 request.close();
9883 assert!(request.next().await.is_none());
9884 assert_eq!(
9885 counter.load(atomic::Ordering::Acquire),
9886 4,
9887 "With the completions menu open, only one LSP request should happen per input"
9888 );
9889}
9890
9891#[gpui::test]
9892async fn test_toggle_comment(cx: &mut TestAppContext) {
9893 init_test(cx, |_| {});
9894 let mut cx = EditorTestContext::new(cx).await;
9895 let language = Arc::new(Language::new(
9896 LanguageConfig {
9897 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9898 ..Default::default()
9899 },
9900 Some(tree_sitter_rust::LANGUAGE.into()),
9901 ));
9902 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9903
9904 // If multiple selections intersect a line, the line is only toggled once.
9905 cx.set_state(indoc! {"
9906 fn a() {
9907 «//b();
9908 ˇ»// «c();
9909 //ˇ» d();
9910 }
9911 "});
9912
9913 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9914
9915 cx.assert_editor_state(indoc! {"
9916 fn a() {
9917 «b();
9918 c();
9919 ˇ» d();
9920 }
9921 "});
9922
9923 // The comment prefix is inserted at the same column for every line in a
9924 // selection.
9925 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9926
9927 cx.assert_editor_state(indoc! {"
9928 fn a() {
9929 // «b();
9930 // c();
9931 ˇ»// d();
9932 }
9933 "});
9934
9935 // If a selection ends at the beginning of a line, that line is not toggled.
9936 cx.set_selections_state(indoc! {"
9937 fn a() {
9938 // b();
9939 «// c();
9940 ˇ» // d();
9941 }
9942 "});
9943
9944 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9945
9946 cx.assert_editor_state(indoc! {"
9947 fn a() {
9948 // b();
9949 «c();
9950 ˇ» // d();
9951 }
9952 "});
9953
9954 // If a selection span a single line and is empty, the line is toggled.
9955 cx.set_state(indoc! {"
9956 fn a() {
9957 a();
9958 b();
9959 ˇ
9960 }
9961 "});
9962
9963 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9964
9965 cx.assert_editor_state(indoc! {"
9966 fn a() {
9967 a();
9968 b();
9969 //•ˇ
9970 }
9971 "});
9972
9973 // If a selection span multiple lines, empty lines are not toggled.
9974 cx.set_state(indoc! {"
9975 fn a() {
9976 «a();
9977
9978 c();ˇ»
9979 }
9980 "});
9981
9982 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9983
9984 cx.assert_editor_state(indoc! {"
9985 fn a() {
9986 // «a();
9987
9988 // c();ˇ»
9989 }
9990 "});
9991
9992 // If a selection includes multiple comment prefixes, all lines are uncommented.
9993 cx.set_state(indoc! {"
9994 fn a() {
9995 «// a();
9996 /// b();
9997 //! c();ˇ»
9998 }
9999 "});
10000
10001 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10002
10003 cx.assert_editor_state(indoc! {"
10004 fn a() {
10005 «a();
10006 b();
10007 c();ˇ»
10008 }
10009 "});
10010}
10011
10012#[gpui::test]
10013async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
10014 init_test(cx, |_| {});
10015 let mut cx = EditorTestContext::new(cx).await;
10016 let language = Arc::new(Language::new(
10017 LanguageConfig {
10018 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
10019 ..Default::default()
10020 },
10021 Some(tree_sitter_rust::LANGUAGE.into()),
10022 ));
10023 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
10024
10025 let toggle_comments = &ToggleComments {
10026 advance_downwards: false,
10027 ignore_indent: true,
10028 };
10029
10030 // If multiple selections intersect a line, the line is only toggled once.
10031 cx.set_state(indoc! {"
10032 fn a() {
10033 // «b();
10034 // c();
10035 // ˇ» d();
10036 }
10037 "});
10038
10039 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10040
10041 cx.assert_editor_state(indoc! {"
10042 fn a() {
10043 «b();
10044 c();
10045 ˇ» d();
10046 }
10047 "});
10048
10049 // The comment prefix is inserted at the beginning of each line
10050 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10051
10052 cx.assert_editor_state(indoc! {"
10053 fn a() {
10054 // «b();
10055 // c();
10056 // ˇ» d();
10057 }
10058 "});
10059
10060 // If a selection ends at the beginning of a line, that line is not toggled.
10061 cx.set_selections_state(indoc! {"
10062 fn a() {
10063 // b();
10064 // «c();
10065 ˇ»// d();
10066 }
10067 "});
10068
10069 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10070
10071 cx.assert_editor_state(indoc! {"
10072 fn a() {
10073 // b();
10074 «c();
10075 ˇ»// d();
10076 }
10077 "});
10078
10079 // If a selection span a single line and is empty, the line is toggled.
10080 cx.set_state(indoc! {"
10081 fn a() {
10082 a();
10083 b();
10084 ˇ
10085 }
10086 "});
10087
10088 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10089
10090 cx.assert_editor_state(indoc! {"
10091 fn a() {
10092 a();
10093 b();
10094 //ˇ
10095 }
10096 "});
10097
10098 // If a selection span multiple lines, empty lines are not toggled.
10099 cx.set_state(indoc! {"
10100 fn a() {
10101 «a();
10102
10103 c();ˇ»
10104 }
10105 "});
10106
10107 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10108
10109 cx.assert_editor_state(indoc! {"
10110 fn a() {
10111 // «a();
10112
10113 // c();ˇ»
10114 }
10115 "});
10116
10117 // If a selection includes multiple comment prefixes, all lines are uncommented.
10118 cx.set_state(indoc! {"
10119 fn a() {
10120 // «a();
10121 /// b();
10122 //! c();ˇ»
10123 }
10124 "});
10125
10126 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10127
10128 cx.assert_editor_state(indoc! {"
10129 fn a() {
10130 «a();
10131 b();
10132 c();ˇ»
10133 }
10134 "});
10135}
10136
10137#[gpui::test]
10138async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
10139 init_test(cx, |_| {});
10140
10141 let language = Arc::new(Language::new(
10142 LanguageConfig {
10143 line_comments: vec!["// ".into()],
10144 ..Default::default()
10145 },
10146 Some(tree_sitter_rust::LANGUAGE.into()),
10147 ));
10148
10149 let mut cx = EditorTestContext::new(cx).await;
10150
10151 cx.language_registry().add(language.clone());
10152 cx.update_buffer(|buffer, cx| {
10153 buffer.set_language(Some(language), cx);
10154 });
10155
10156 let toggle_comments = &ToggleComments {
10157 advance_downwards: true,
10158 ignore_indent: false,
10159 };
10160
10161 // Single cursor on one line -> advance
10162 // Cursor moves horizontally 3 characters as well on non-blank line
10163 cx.set_state(indoc!(
10164 "fn a() {
10165 ˇdog();
10166 cat();
10167 }"
10168 ));
10169 cx.update_editor(|editor, window, cx| {
10170 editor.toggle_comments(toggle_comments, window, cx);
10171 });
10172 cx.assert_editor_state(indoc!(
10173 "fn a() {
10174 // dog();
10175 catˇ();
10176 }"
10177 ));
10178
10179 // Single selection on one line -> don't advance
10180 cx.set_state(indoc!(
10181 "fn a() {
10182 «dog()ˇ»;
10183 cat();
10184 }"
10185 ));
10186 cx.update_editor(|editor, window, cx| {
10187 editor.toggle_comments(toggle_comments, window, cx);
10188 });
10189 cx.assert_editor_state(indoc!(
10190 "fn a() {
10191 // «dog()ˇ»;
10192 cat();
10193 }"
10194 ));
10195
10196 // Multiple cursors on one line -> advance
10197 cx.set_state(indoc!(
10198 "fn a() {
10199 ˇdˇog();
10200 cat();
10201 }"
10202 ));
10203 cx.update_editor(|editor, window, cx| {
10204 editor.toggle_comments(toggle_comments, window, cx);
10205 });
10206 cx.assert_editor_state(indoc!(
10207 "fn a() {
10208 // dog();
10209 catˇ(ˇ);
10210 }"
10211 ));
10212
10213 // Multiple cursors on one line, with selection -> don't advance
10214 cx.set_state(indoc!(
10215 "fn a() {
10216 ˇdˇog«()ˇ»;
10217 cat();
10218 }"
10219 ));
10220 cx.update_editor(|editor, window, cx| {
10221 editor.toggle_comments(toggle_comments, window, cx);
10222 });
10223 cx.assert_editor_state(indoc!(
10224 "fn a() {
10225 // ˇdˇog«()ˇ»;
10226 cat();
10227 }"
10228 ));
10229
10230 // Single cursor on one line -> advance
10231 // Cursor moves to column 0 on blank line
10232 cx.set_state(indoc!(
10233 "fn a() {
10234 ˇdog();
10235
10236 cat();
10237 }"
10238 ));
10239 cx.update_editor(|editor, window, cx| {
10240 editor.toggle_comments(toggle_comments, window, cx);
10241 });
10242 cx.assert_editor_state(indoc!(
10243 "fn a() {
10244 // dog();
10245 ˇ
10246 cat();
10247 }"
10248 ));
10249
10250 // Single cursor on one line -> advance
10251 // Cursor starts and ends at column 0
10252 cx.set_state(indoc!(
10253 "fn a() {
10254 ˇ dog();
10255 cat();
10256 }"
10257 ));
10258 cx.update_editor(|editor, window, cx| {
10259 editor.toggle_comments(toggle_comments, window, cx);
10260 });
10261 cx.assert_editor_state(indoc!(
10262 "fn a() {
10263 // dog();
10264 ˇ cat();
10265 }"
10266 ));
10267}
10268
10269#[gpui::test]
10270async fn test_toggle_block_comment(cx: &mut TestAppContext) {
10271 init_test(cx, |_| {});
10272
10273 let mut cx = EditorTestContext::new(cx).await;
10274
10275 let html_language = Arc::new(
10276 Language::new(
10277 LanguageConfig {
10278 name: "HTML".into(),
10279 block_comment: Some(("<!-- ".into(), " -->".into())),
10280 ..Default::default()
10281 },
10282 Some(tree_sitter_html::LANGUAGE.into()),
10283 )
10284 .with_injection_query(
10285 r#"
10286 (script_element
10287 (raw_text) @injection.content
10288 (#set! injection.language "javascript"))
10289 "#,
10290 )
10291 .unwrap(),
10292 );
10293
10294 let javascript_language = Arc::new(Language::new(
10295 LanguageConfig {
10296 name: "JavaScript".into(),
10297 line_comments: vec!["// ".into()],
10298 ..Default::default()
10299 },
10300 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10301 ));
10302
10303 cx.language_registry().add(html_language.clone());
10304 cx.language_registry().add(javascript_language.clone());
10305 cx.update_buffer(|buffer, cx| {
10306 buffer.set_language(Some(html_language), cx);
10307 });
10308
10309 // Toggle comments for empty selections
10310 cx.set_state(
10311 &r#"
10312 <p>A</p>ˇ
10313 <p>B</p>ˇ
10314 <p>C</p>ˇ
10315 "#
10316 .unindent(),
10317 );
10318 cx.update_editor(|editor, window, cx| {
10319 editor.toggle_comments(&ToggleComments::default(), window, cx)
10320 });
10321 cx.assert_editor_state(
10322 &r#"
10323 <!-- <p>A</p>ˇ -->
10324 <!-- <p>B</p>ˇ -->
10325 <!-- <p>C</p>ˇ -->
10326 "#
10327 .unindent(),
10328 );
10329 cx.update_editor(|editor, window, cx| {
10330 editor.toggle_comments(&ToggleComments::default(), window, cx)
10331 });
10332 cx.assert_editor_state(
10333 &r#"
10334 <p>A</p>ˇ
10335 <p>B</p>ˇ
10336 <p>C</p>ˇ
10337 "#
10338 .unindent(),
10339 );
10340
10341 // Toggle comments for mixture of empty and non-empty selections, where
10342 // multiple selections occupy a given line.
10343 cx.set_state(
10344 &r#"
10345 <p>A«</p>
10346 <p>ˇ»B</p>ˇ
10347 <p>C«</p>
10348 <p>ˇ»D</p>ˇ
10349 "#
10350 .unindent(),
10351 );
10352
10353 cx.update_editor(|editor, window, cx| {
10354 editor.toggle_comments(&ToggleComments::default(), window, cx)
10355 });
10356 cx.assert_editor_state(
10357 &r#"
10358 <!-- <p>A«</p>
10359 <p>ˇ»B</p>ˇ -->
10360 <!-- <p>C«</p>
10361 <p>ˇ»D</p>ˇ -->
10362 "#
10363 .unindent(),
10364 );
10365 cx.update_editor(|editor, window, cx| {
10366 editor.toggle_comments(&ToggleComments::default(), window, cx)
10367 });
10368 cx.assert_editor_state(
10369 &r#"
10370 <p>A«</p>
10371 <p>ˇ»B</p>ˇ
10372 <p>C«</p>
10373 <p>ˇ»D</p>ˇ
10374 "#
10375 .unindent(),
10376 );
10377
10378 // Toggle comments when different languages are active for different
10379 // selections.
10380 cx.set_state(
10381 &r#"
10382 ˇ<script>
10383 ˇvar x = new Y();
10384 ˇ</script>
10385 "#
10386 .unindent(),
10387 );
10388 cx.executor().run_until_parked();
10389 cx.update_editor(|editor, window, cx| {
10390 editor.toggle_comments(&ToggleComments::default(), window, cx)
10391 });
10392 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
10393 // Uncommenting and commenting from this position brings in even more wrong artifacts.
10394 cx.assert_editor_state(
10395 &r#"
10396 <!-- ˇ<script> -->
10397 // ˇvar x = new Y();
10398 <!-- ˇ</script> -->
10399 "#
10400 .unindent(),
10401 );
10402}
10403
10404#[gpui::test]
10405fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
10406 init_test(cx, |_| {});
10407
10408 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10409 let multibuffer = cx.new(|cx| {
10410 let mut multibuffer = MultiBuffer::new(ReadWrite);
10411 multibuffer.push_excerpts(
10412 buffer.clone(),
10413 [
10414 ExcerptRange {
10415 context: Point::new(0, 0)..Point::new(0, 4),
10416 primary: None,
10417 },
10418 ExcerptRange {
10419 context: Point::new(1, 0)..Point::new(1, 4),
10420 primary: None,
10421 },
10422 ],
10423 cx,
10424 );
10425 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
10426 multibuffer
10427 });
10428
10429 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10430 editor.update_in(cx, |editor, window, cx| {
10431 assert_eq!(editor.text(cx), "aaaa\nbbbb");
10432 editor.change_selections(None, window, cx, |s| {
10433 s.select_ranges([
10434 Point::new(0, 0)..Point::new(0, 0),
10435 Point::new(1, 0)..Point::new(1, 0),
10436 ])
10437 });
10438
10439 editor.handle_input("X", window, cx);
10440 assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
10441 assert_eq!(
10442 editor.selections.ranges(cx),
10443 [
10444 Point::new(0, 1)..Point::new(0, 1),
10445 Point::new(1, 1)..Point::new(1, 1),
10446 ]
10447 );
10448
10449 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
10450 editor.change_selections(None, window, cx, |s| {
10451 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
10452 });
10453 editor.backspace(&Default::default(), window, cx);
10454 assert_eq!(editor.text(cx), "Xa\nbbb");
10455 assert_eq!(
10456 editor.selections.ranges(cx),
10457 [Point::new(1, 0)..Point::new(1, 0)]
10458 );
10459
10460 editor.change_selections(None, window, cx, |s| {
10461 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
10462 });
10463 editor.backspace(&Default::default(), window, cx);
10464 assert_eq!(editor.text(cx), "X\nbb");
10465 assert_eq!(
10466 editor.selections.ranges(cx),
10467 [Point::new(0, 1)..Point::new(0, 1)]
10468 );
10469 });
10470}
10471
10472#[gpui::test]
10473fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
10474 init_test(cx, |_| {});
10475
10476 let markers = vec![('[', ']').into(), ('(', ')').into()];
10477 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
10478 indoc! {"
10479 [aaaa
10480 (bbbb]
10481 cccc)",
10482 },
10483 markers.clone(),
10484 );
10485 let excerpt_ranges = markers.into_iter().map(|marker| {
10486 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
10487 ExcerptRange {
10488 context,
10489 primary: None,
10490 }
10491 });
10492 let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
10493 let multibuffer = cx.new(|cx| {
10494 let mut multibuffer = MultiBuffer::new(ReadWrite);
10495 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
10496 multibuffer
10497 });
10498
10499 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10500 editor.update_in(cx, |editor, window, cx| {
10501 let (expected_text, selection_ranges) = marked_text_ranges(
10502 indoc! {"
10503 aaaa
10504 bˇbbb
10505 bˇbbˇb
10506 cccc"
10507 },
10508 true,
10509 );
10510 assert_eq!(editor.text(cx), expected_text);
10511 editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
10512
10513 editor.handle_input("X", window, cx);
10514
10515 let (expected_text, expected_selections) = marked_text_ranges(
10516 indoc! {"
10517 aaaa
10518 bXˇbbXb
10519 bXˇbbXˇb
10520 cccc"
10521 },
10522 false,
10523 );
10524 assert_eq!(editor.text(cx), expected_text);
10525 assert_eq!(editor.selections.ranges(cx), expected_selections);
10526
10527 editor.newline(&Newline, window, cx);
10528 let (expected_text, expected_selections) = marked_text_ranges(
10529 indoc! {"
10530 aaaa
10531 bX
10532 ˇbbX
10533 b
10534 bX
10535 ˇbbX
10536 ˇb
10537 cccc"
10538 },
10539 false,
10540 );
10541 assert_eq!(editor.text(cx), expected_text);
10542 assert_eq!(editor.selections.ranges(cx), expected_selections);
10543 });
10544}
10545
10546#[gpui::test]
10547fn test_refresh_selections(cx: &mut TestAppContext) {
10548 init_test(cx, |_| {});
10549
10550 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10551 let mut excerpt1_id = None;
10552 let multibuffer = cx.new(|cx| {
10553 let mut multibuffer = MultiBuffer::new(ReadWrite);
10554 excerpt1_id = multibuffer
10555 .push_excerpts(
10556 buffer.clone(),
10557 [
10558 ExcerptRange {
10559 context: Point::new(0, 0)..Point::new(1, 4),
10560 primary: None,
10561 },
10562 ExcerptRange {
10563 context: Point::new(1, 0)..Point::new(2, 4),
10564 primary: None,
10565 },
10566 ],
10567 cx,
10568 )
10569 .into_iter()
10570 .next();
10571 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10572 multibuffer
10573 });
10574
10575 let editor = cx.add_window(|window, cx| {
10576 let mut editor = build_editor(multibuffer.clone(), window, cx);
10577 let snapshot = editor.snapshot(window, cx);
10578 editor.change_selections(None, window, cx, |s| {
10579 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
10580 });
10581 editor.begin_selection(
10582 Point::new(2, 1).to_display_point(&snapshot),
10583 true,
10584 1,
10585 window,
10586 cx,
10587 );
10588 assert_eq!(
10589 editor.selections.ranges(cx),
10590 [
10591 Point::new(1, 3)..Point::new(1, 3),
10592 Point::new(2, 1)..Point::new(2, 1),
10593 ]
10594 );
10595 editor
10596 });
10597
10598 // Refreshing selections is a no-op when excerpts haven't changed.
10599 _ = editor.update(cx, |editor, window, cx| {
10600 editor.change_selections(None, window, cx, |s| s.refresh());
10601 assert_eq!(
10602 editor.selections.ranges(cx),
10603 [
10604 Point::new(1, 3)..Point::new(1, 3),
10605 Point::new(2, 1)..Point::new(2, 1),
10606 ]
10607 );
10608 });
10609
10610 multibuffer.update(cx, |multibuffer, cx| {
10611 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10612 });
10613 _ = editor.update(cx, |editor, window, cx| {
10614 // Removing an excerpt causes the first selection to become degenerate.
10615 assert_eq!(
10616 editor.selections.ranges(cx),
10617 [
10618 Point::new(0, 0)..Point::new(0, 0),
10619 Point::new(0, 1)..Point::new(0, 1)
10620 ]
10621 );
10622
10623 // Refreshing selections will relocate the first selection to the original buffer
10624 // location.
10625 editor.change_selections(None, window, cx, |s| s.refresh());
10626 assert_eq!(
10627 editor.selections.ranges(cx),
10628 [
10629 Point::new(0, 1)..Point::new(0, 1),
10630 Point::new(0, 3)..Point::new(0, 3)
10631 ]
10632 );
10633 assert!(editor.selections.pending_anchor().is_some());
10634 });
10635}
10636
10637#[gpui::test]
10638fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
10639 init_test(cx, |_| {});
10640
10641 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10642 let mut excerpt1_id = None;
10643 let multibuffer = cx.new(|cx| {
10644 let mut multibuffer = MultiBuffer::new(ReadWrite);
10645 excerpt1_id = multibuffer
10646 .push_excerpts(
10647 buffer.clone(),
10648 [
10649 ExcerptRange {
10650 context: Point::new(0, 0)..Point::new(1, 4),
10651 primary: None,
10652 },
10653 ExcerptRange {
10654 context: Point::new(1, 0)..Point::new(2, 4),
10655 primary: None,
10656 },
10657 ],
10658 cx,
10659 )
10660 .into_iter()
10661 .next();
10662 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10663 multibuffer
10664 });
10665
10666 let editor = cx.add_window(|window, cx| {
10667 let mut editor = build_editor(multibuffer.clone(), window, cx);
10668 let snapshot = editor.snapshot(window, cx);
10669 editor.begin_selection(
10670 Point::new(1, 3).to_display_point(&snapshot),
10671 false,
10672 1,
10673 window,
10674 cx,
10675 );
10676 assert_eq!(
10677 editor.selections.ranges(cx),
10678 [Point::new(1, 3)..Point::new(1, 3)]
10679 );
10680 editor
10681 });
10682
10683 multibuffer.update(cx, |multibuffer, cx| {
10684 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10685 });
10686 _ = editor.update(cx, |editor, window, cx| {
10687 assert_eq!(
10688 editor.selections.ranges(cx),
10689 [Point::new(0, 0)..Point::new(0, 0)]
10690 );
10691
10692 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
10693 editor.change_selections(None, window, cx, |s| s.refresh());
10694 assert_eq!(
10695 editor.selections.ranges(cx),
10696 [Point::new(0, 3)..Point::new(0, 3)]
10697 );
10698 assert!(editor.selections.pending_anchor().is_some());
10699 });
10700}
10701
10702#[gpui::test]
10703async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
10704 init_test(cx, |_| {});
10705
10706 let language = Arc::new(
10707 Language::new(
10708 LanguageConfig {
10709 brackets: BracketPairConfig {
10710 pairs: vec![
10711 BracketPair {
10712 start: "{".to_string(),
10713 end: "}".to_string(),
10714 close: true,
10715 surround: true,
10716 newline: true,
10717 },
10718 BracketPair {
10719 start: "/* ".to_string(),
10720 end: " */".to_string(),
10721 close: true,
10722 surround: true,
10723 newline: true,
10724 },
10725 ],
10726 ..Default::default()
10727 },
10728 ..Default::default()
10729 },
10730 Some(tree_sitter_rust::LANGUAGE.into()),
10731 )
10732 .with_indents_query("")
10733 .unwrap(),
10734 );
10735
10736 let text = concat!(
10737 "{ }\n", //
10738 " x\n", //
10739 " /* */\n", //
10740 "x\n", //
10741 "{{} }\n", //
10742 );
10743
10744 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10745 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10746 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10747 editor
10748 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10749 .await;
10750
10751 editor.update_in(cx, |editor, window, cx| {
10752 editor.change_selections(None, window, cx, |s| {
10753 s.select_display_ranges([
10754 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
10755 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
10756 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
10757 ])
10758 });
10759 editor.newline(&Newline, window, cx);
10760
10761 assert_eq!(
10762 editor.buffer().read(cx).read(cx).text(),
10763 concat!(
10764 "{ \n", // Suppress rustfmt
10765 "\n", //
10766 "}\n", //
10767 " x\n", //
10768 " /* \n", //
10769 " \n", //
10770 " */\n", //
10771 "x\n", //
10772 "{{} \n", //
10773 "}\n", //
10774 )
10775 );
10776 });
10777}
10778
10779#[gpui::test]
10780fn test_highlighted_ranges(cx: &mut TestAppContext) {
10781 init_test(cx, |_| {});
10782
10783 let editor = cx.add_window(|window, cx| {
10784 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
10785 build_editor(buffer.clone(), window, cx)
10786 });
10787
10788 _ = editor.update(cx, |editor, window, cx| {
10789 struct Type1;
10790 struct Type2;
10791
10792 let buffer = editor.buffer.read(cx).snapshot(cx);
10793
10794 let anchor_range =
10795 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
10796
10797 editor.highlight_background::<Type1>(
10798 &[
10799 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
10800 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
10801 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
10802 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
10803 ],
10804 |_| Hsla::red(),
10805 cx,
10806 );
10807 editor.highlight_background::<Type2>(
10808 &[
10809 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
10810 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
10811 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
10812 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
10813 ],
10814 |_| Hsla::green(),
10815 cx,
10816 );
10817
10818 let snapshot = editor.snapshot(window, cx);
10819 let mut highlighted_ranges = editor.background_highlights_in_range(
10820 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
10821 &snapshot,
10822 cx.theme().colors(),
10823 );
10824 // Enforce a consistent ordering based on color without relying on the ordering of the
10825 // highlight's `TypeId` which is non-executor.
10826 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
10827 assert_eq!(
10828 highlighted_ranges,
10829 &[
10830 (
10831 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
10832 Hsla::red(),
10833 ),
10834 (
10835 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10836 Hsla::red(),
10837 ),
10838 (
10839 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
10840 Hsla::green(),
10841 ),
10842 (
10843 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
10844 Hsla::green(),
10845 ),
10846 ]
10847 );
10848 assert_eq!(
10849 editor.background_highlights_in_range(
10850 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
10851 &snapshot,
10852 cx.theme().colors(),
10853 ),
10854 &[(
10855 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10856 Hsla::red(),
10857 )]
10858 );
10859 });
10860}
10861
10862#[gpui::test]
10863async fn test_following(cx: &mut TestAppContext) {
10864 init_test(cx, |_| {});
10865
10866 let fs = FakeFs::new(cx.executor());
10867 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10868
10869 let buffer = project.update(cx, |project, cx| {
10870 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
10871 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
10872 });
10873 let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
10874 let follower = cx.update(|cx| {
10875 cx.open_window(
10876 WindowOptions {
10877 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
10878 gpui::Point::new(px(0.), px(0.)),
10879 gpui::Point::new(px(10.), px(80.)),
10880 ))),
10881 ..Default::default()
10882 },
10883 |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
10884 )
10885 .unwrap()
10886 });
10887
10888 let is_still_following = Rc::new(RefCell::new(true));
10889 let follower_edit_event_count = Rc::new(RefCell::new(0));
10890 let pending_update = Rc::new(RefCell::new(None));
10891 let leader_entity = leader.root(cx).unwrap();
10892 let follower_entity = follower.root(cx).unwrap();
10893 _ = follower.update(cx, {
10894 let update = pending_update.clone();
10895 let is_still_following = is_still_following.clone();
10896 let follower_edit_event_count = follower_edit_event_count.clone();
10897 |_, window, cx| {
10898 cx.subscribe_in(
10899 &leader_entity,
10900 window,
10901 move |_, leader, event, window, cx| {
10902 leader.read(cx).add_event_to_update_proto(
10903 event,
10904 &mut update.borrow_mut(),
10905 window,
10906 cx,
10907 );
10908 },
10909 )
10910 .detach();
10911
10912 cx.subscribe_in(
10913 &follower_entity,
10914 window,
10915 move |_, _, event: &EditorEvent, _window, _cx| {
10916 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
10917 *is_still_following.borrow_mut() = false;
10918 }
10919
10920 if let EditorEvent::BufferEdited = event {
10921 *follower_edit_event_count.borrow_mut() += 1;
10922 }
10923 },
10924 )
10925 .detach();
10926 }
10927 });
10928
10929 // Update the selections only
10930 _ = leader.update(cx, |leader, window, cx| {
10931 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10932 });
10933 follower
10934 .update(cx, |follower, window, cx| {
10935 follower.apply_update_proto(
10936 &project,
10937 pending_update.borrow_mut().take().unwrap(),
10938 window,
10939 cx,
10940 )
10941 })
10942 .unwrap()
10943 .await
10944 .unwrap();
10945 _ = follower.update(cx, |follower, _, cx| {
10946 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
10947 });
10948 assert!(*is_still_following.borrow());
10949 assert_eq!(*follower_edit_event_count.borrow(), 0);
10950
10951 // Update the scroll position only
10952 _ = leader.update(cx, |leader, window, cx| {
10953 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10954 });
10955 follower
10956 .update(cx, |follower, window, cx| {
10957 follower.apply_update_proto(
10958 &project,
10959 pending_update.borrow_mut().take().unwrap(),
10960 window,
10961 cx,
10962 )
10963 })
10964 .unwrap()
10965 .await
10966 .unwrap();
10967 assert_eq!(
10968 follower
10969 .update(cx, |follower, _, cx| follower.scroll_position(cx))
10970 .unwrap(),
10971 gpui::Point::new(1.5, 3.5)
10972 );
10973 assert!(*is_still_following.borrow());
10974 assert_eq!(*follower_edit_event_count.borrow(), 0);
10975
10976 // Update the selections and scroll position. The follower's scroll position is updated
10977 // via autoscroll, not via the leader's exact scroll position.
10978 _ = leader.update(cx, |leader, window, cx| {
10979 leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10980 leader.request_autoscroll(Autoscroll::newest(), cx);
10981 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10982 });
10983 follower
10984 .update(cx, |follower, window, cx| {
10985 follower.apply_update_proto(
10986 &project,
10987 pending_update.borrow_mut().take().unwrap(),
10988 window,
10989 cx,
10990 )
10991 })
10992 .unwrap()
10993 .await
10994 .unwrap();
10995 _ = follower.update(cx, |follower, _, cx| {
10996 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
10997 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
10998 });
10999 assert!(*is_still_following.borrow());
11000
11001 // Creating a pending selection that precedes another selection
11002 _ = leader.update(cx, |leader, window, cx| {
11003 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
11004 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
11005 });
11006 follower
11007 .update(cx, |follower, window, cx| {
11008 follower.apply_update_proto(
11009 &project,
11010 pending_update.borrow_mut().take().unwrap(),
11011 window,
11012 cx,
11013 )
11014 })
11015 .unwrap()
11016 .await
11017 .unwrap();
11018 _ = follower.update(cx, |follower, _, cx| {
11019 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
11020 });
11021 assert!(*is_still_following.borrow());
11022
11023 // Extend the pending selection so that it surrounds another selection
11024 _ = leader.update(cx, |leader, window, cx| {
11025 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
11026 });
11027 follower
11028 .update(cx, |follower, window, cx| {
11029 follower.apply_update_proto(
11030 &project,
11031 pending_update.borrow_mut().take().unwrap(),
11032 window,
11033 cx,
11034 )
11035 })
11036 .unwrap()
11037 .await
11038 .unwrap();
11039 _ = follower.update(cx, |follower, _, cx| {
11040 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
11041 });
11042
11043 // Scrolling locally breaks the follow
11044 _ = follower.update(cx, |follower, window, cx| {
11045 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
11046 follower.set_scroll_anchor(
11047 ScrollAnchor {
11048 anchor: top_anchor,
11049 offset: gpui::Point::new(0.0, 0.5),
11050 },
11051 window,
11052 cx,
11053 );
11054 });
11055 assert!(!(*is_still_following.borrow()));
11056}
11057
11058#[gpui::test]
11059async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
11060 init_test(cx, |_| {});
11061
11062 let fs = FakeFs::new(cx.executor());
11063 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
11064 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11065 let pane = workspace
11066 .update(cx, |workspace, _, _| workspace.active_pane().clone())
11067 .unwrap();
11068
11069 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11070
11071 let leader = pane.update_in(cx, |_, window, cx| {
11072 let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
11073 cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
11074 });
11075
11076 // Start following the editor when it has no excerpts.
11077 let mut state_message =
11078 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
11079 let workspace_entity = workspace.root(cx).unwrap();
11080 let follower_1 = cx
11081 .update_window(*workspace.deref(), |_, window, cx| {
11082 Editor::from_state_proto(
11083 workspace_entity,
11084 ViewId {
11085 creator: Default::default(),
11086 id: 0,
11087 },
11088 &mut state_message,
11089 window,
11090 cx,
11091 )
11092 })
11093 .unwrap()
11094 .unwrap()
11095 .await
11096 .unwrap();
11097
11098 let update_message = Rc::new(RefCell::new(None));
11099 follower_1.update_in(cx, {
11100 let update = update_message.clone();
11101 |_, window, cx| {
11102 cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
11103 leader.read(cx).add_event_to_update_proto(
11104 event,
11105 &mut update.borrow_mut(),
11106 window,
11107 cx,
11108 );
11109 })
11110 .detach();
11111 }
11112 });
11113
11114 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
11115 (
11116 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
11117 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
11118 )
11119 });
11120
11121 // Insert some excerpts.
11122 leader.update(cx, |leader, cx| {
11123 leader.buffer.update(cx, |multibuffer, cx| {
11124 let excerpt_ids = multibuffer.push_excerpts(
11125 buffer_1.clone(),
11126 [
11127 ExcerptRange {
11128 context: 1..6,
11129 primary: None,
11130 },
11131 ExcerptRange {
11132 context: 12..15,
11133 primary: None,
11134 },
11135 ExcerptRange {
11136 context: 0..3,
11137 primary: None,
11138 },
11139 ],
11140 cx,
11141 );
11142 multibuffer.insert_excerpts_after(
11143 excerpt_ids[0],
11144 buffer_2.clone(),
11145 [
11146 ExcerptRange {
11147 context: 8..12,
11148 primary: None,
11149 },
11150 ExcerptRange {
11151 context: 0..6,
11152 primary: None,
11153 },
11154 ],
11155 cx,
11156 );
11157 });
11158 });
11159
11160 // Apply the update of adding the excerpts.
11161 follower_1
11162 .update_in(cx, |follower, window, cx| {
11163 follower.apply_update_proto(
11164 &project,
11165 update_message.borrow().clone().unwrap(),
11166 window,
11167 cx,
11168 )
11169 })
11170 .await
11171 .unwrap();
11172 assert_eq!(
11173 follower_1.update(cx, |editor, cx| editor.text(cx)),
11174 leader.update(cx, |editor, cx| editor.text(cx))
11175 );
11176 update_message.borrow_mut().take();
11177
11178 // Start following separately after it already has excerpts.
11179 let mut state_message =
11180 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
11181 let workspace_entity = workspace.root(cx).unwrap();
11182 let follower_2 = cx
11183 .update_window(*workspace.deref(), |_, window, cx| {
11184 Editor::from_state_proto(
11185 workspace_entity,
11186 ViewId {
11187 creator: Default::default(),
11188 id: 0,
11189 },
11190 &mut state_message,
11191 window,
11192 cx,
11193 )
11194 })
11195 .unwrap()
11196 .unwrap()
11197 .await
11198 .unwrap();
11199 assert_eq!(
11200 follower_2.update(cx, |editor, cx| editor.text(cx)),
11201 leader.update(cx, |editor, cx| editor.text(cx))
11202 );
11203
11204 // Remove some excerpts.
11205 leader.update(cx, |leader, cx| {
11206 leader.buffer.update(cx, |multibuffer, cx| {
11207 let excerpt_ids = multibuffer.excerpt_ids();
11208 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
11209 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
11210 });
11211 });
11212
11213 // Apply the update of removing the excerpts.
11214 follower_1
11215 .update_in(cx, |follower, window, cx| {
11216 follower.apply_update_proto(
11217 &project,
11218 update_message.borrow().clone().unwrap(),
11219 window,
11220 cx,
11221 )
11222 })
11223 .await
11224 .unwrap();
11225 follower_2
11226 .update_in(cx, |follower, window, cx| {
11227 follower.apply_update_proto(
11228 &project,
11229 update_message.borrow().clone().unwrap(),
11230 window,
11231 cx,
11232 )
11233 })
11234 .await
11235 .unwrap();
11236 update_message.borrow_mut().take();
11237 assert_eq!(
11238 follower_1.update(cx, |editor, cx| editor.text(cx)),
11239 leader.update(cx, |editor, cx| editor.text(cx))
11240 );
11241}
11242
11243#[gpui::test]
11244async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11245 init_test(cx, |_| {});
11246
11247 let mut cx = EditorTestContext::new(cx).await;
11248 let lsp_store =
11249 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11250
11251 cx.set_state(indoc! {"
11252 ˇfn func(abc def: i32) -> u32 {
11253 }
11254 "});
11255
11256 cx.update(|_, cx| {
11257 lsp_store.update(cx, |lsp_store, cx| {
11258 lsp_store
11259 .update_diagnostics(
11260 LanguageServerId(0),
11261 lsp::PublishDiagnosticsParams {
11262 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11263 version: None,
11264 diagnostics: vec![
11265 lsp::Diagnostic {
11266 range: lsp::Range::new(
11267 lsp::Position::new(0, 11),
11268 lsp::Position::new(0, 12),
11269 ),
11270 severity: Some(lsp::DiagnosticSeverity::ERROR),
11271 ..Default::default()
11272 },
11273 lsp::Diagnostic {
11274 range: lsp::Range::new(
11275 lsp::Position::new(0, 12),
11276 lsp::Position::new(0, 15),
11277 ),
11278 severity: Some(lsp::DiagnosticSeverity::ERROR),
11279 ..Default::default()
11280 },
11281 lsp::Diagnostic {
11282 range: lsp::Range::new(
11283 lsp::Position::new(0, 25),
11284 lsp::Position::new(0, 28),
11285 ),
11286 severity: Some(lsp::DiagnosticSeverity::ERROR),
11287 ..Default::default()
11288 },
11289 ],
11290 },
11291 &[],
11292 cx,
11293 )
11294 .unwrap()
11295 });
11296 });
11297
11298 executor.run_until_parked();
11299
11300 cx.update_editor(|editor, window, cx| {
11301 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11302 });
11303
11304 cx.assert_editor_state(indoc! {"
11305 fn func(abc def: i32) -> ˇu32 {
11306 }
11307 "});
11308
11309 cx.update_editor(|editor, window, cx| {
11310 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11311 });
11312
11313 cx.assert_editor_state(indoc! {"
11314 fn func(abc ˇdef: i32) -> u32 {
11315 }
11316 "});
11317
11318 cx.update_editor(|editor, window, cx| {
11319 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11320 });
11321
11322 cx.assert_editor_state(indoc! {"
11323 fn func(abcˇ def: i32) -> u32 {
11324 }
11325 "});
11326
11327 cx.update_editor(|editor, window, cx| {
11328 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11329 });
11330
11331 cx.assert_editor_state(indoc! {"
11332 fn func(abc def: i32) -> ˇu32 {
11333 }
11334 "});
11335}
11336
11337#[gpui::test]
11338async fn cycle_through_same_place_diagnostics(
11339 executor: BackgroundExecutor,
11340 cx: &mut TestAppContext,
11341) {
11342 init_test(cx, |_| {});
11343
11344 let mut cx = EditorTestContext::new(cx).await;
11345 let lsp_store =
11346 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11347
11348 cx.set_state(indoc! {"
11349 ˇfn func(abc def: i32) -> u32 {
11350 }
11351 "});
11352
11353 cx.update(|_, cx| {
11354 lsp_store.update(cx, |lsp_store, cx| {
11355 lsp_store
11356 .update_diagnostics(
11357 LanguageServerId(0),
11358 lsp::PublishDiagnosticsParams {
11359 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11360 version: None,
11361 diagnostics: vec![
11362 lsp::Diagnostic {
11363 range: lsp::Range::new(
11364 lsp::Position::new(0, 11),
11365 lsp::Position::new(0, 12),
11366 ),
11367 severity: Some(lsp::DiagnosticSeverity::ERROR),
11368 ..Default::default()
11369 },
11370 lsp::Diagnostic {
11371 range: lsp::Range::new(
11372 lsp::Position::new(0, 12),
11373 lsp::Position::new(0, 15),
11374 ),
11375 severity: Some(lsp::DiagnosticSeverity::ERROR),
11376 ..Default::default()
11377 },
11378 lsp::Diagnostic {
11379 range: lsp::Range::new(
11380 lsp::Position::new(0, 12),
11381 lsp::Position::new(0, 15),
11382 ),
11383 severity: Some(lsp::DiagnosticSeverity::ERROR),
11384 ..Default::default()
11385 },
11386 lsp::Diagnostic {
11387 range: lsp::Range::new(
11388 lsp::Position::new(0, 25),
11389 lsp::Position::new(0, 28),
11390 ),
11391 severity: Some(lsp::DiagnosticSeverity::ERROR),
11392 ..Default::default()
11393 },
11394 ],
11395 },
11396 &[],
11397 cx,
11398 )
11399 .unwrap()
11400 });
11401 });
11402 executor.run_until_parked();
11403
11404 //// Backward
11405
11406 // Fourth diagnostic
11407 cx.update_editor(|editor, window, cx| {
11408 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11409 });
11410 cx.assert_editor_state(indoc! {"
11411 fn func(abc def: i32) -> ˇu32 {
11412 }
11413 "});
11414
11415 // Third diagnostic
11416 cx.update_editor(|editor, window, cx| {
11417 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11418 });
11419 cx.assert_editor_state(indoc! {"
11420 fn func(abc ˇdef: i32) -> u32 {
11421 }
11422 "});
11423
11424 // Second diagnostic, same place
11425 cx.update_editor(|editor, window, cx| {
11426 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11427 });
11428 cx.assert_editor_state(indoc! {"
11429 fn func(abc ˇdef: i32) -> u32 {
11430 }
11431 "});
11432
11433 // First diagnostic
11434 cx.update_editor(|editor, window, cx| {
11435 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11436 });
11437 cx.assert_editor_state(indoc! {"
11438 fn func(abcˇ def: i32) -> u32 {
11439 }
11440 "});
11441
11442 // Wrapped over, fourth diagnostic
11443 cx.update_editor(|editor, window, cx| {
11444 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11445 });
11446 cx.assert_editor_state(indoc! {"
11447 fn func(abc def: i32) -> ˇu32 {
11448 }
11449 "});
11450
11451 cx.update_editor(|editor, window, cx| {
11452 editor.move_to_beginning(&MoveToBeginning, window, cx);
11453 });
11454 cx.assert_editor_state(indoc! {"
11455 ˇfn func(abc def: i32) -> u32 {
11456 }
11457 "});
11458
11459 //// Forward
11460
11461 // First diagnostic
11462 cx.update_editor(|editor, window, cx| {
11463 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11464 });
11465 cx.assert_editor_state(indoc! {"
11466 fn func(abcˇ def: i32) -> u32 {
11467 }
11468 "});
11469
11470 // Second diagnostic
11471 cx.update_editor(|editor, window, cx| {
11472 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11473 });
11474 cx.assert_editor_state(indoc! {"
11475 fn func(abc ˇdef: i32) -> u32 {
11476 }
11477 "});
11478
11479 // Third diagnostic, same place
11480 cx.update_editor(|editor, window, cx| {
11481 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11482 });
11483 cx.assert_editor_state(indoc! {"
11484 fn func(abc ˇdef: i32) -> u32 {
11485 }
11486 "});
11487
11488 // Fourth diagnostic
11489 cx.update_editor(|editor, window, cx| {
11490 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11491 });
11492 cx.assert_editor_state(indoc! {"
11493 fn func(abc def: i32) -> ˇu32 {
11494 }
11495 "});
11496
11497 // Wrapped around, first diagnostic
11498 cx.update_editor(|editor, window, cx| {
11499 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11500 });
11501 cx.assert_editor_state(indoc! {"
11502 fn func(abcˇ def: i32) -> u32 {
11503 }
11504 "});
11505}
11506
11507#[gpui::test]
11508async fn active_diagnostics_dismiss_after_invalidation(
11509 executor: BackgroundExecutor,
11510 cx: &mut TestAppContext,
11511) {
11512 init_test(cx, |_| {});
11513
11514 let mut cx = EditorTestContext::new(cx).await;
11515 let lsp_store =
11516 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11517
11518 cx.set_state(indoc! {"
11519 ˇfn func(abc def: i32) -> u32 {
11520 }
11521 "});
11522
11523 let message = "Something's wrong!";
11524 cx.update(|_, cx| {
11525 lsp_store.update(cx, |lsp_store, cx| {
11526 lsp_store
11527 .update_diagnostics(
11528 LanguageServerId(0),
11529 lsp::PublishDiagnosticsParams {
11530 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11531 version: None,
11532 diagnostics: vec![lsp::Diagnostic {
11533 range: lsp::Range::new(
11534 lsp::Position::new(0, 11),
11535 lsp::Position::new(0, 12),
11536 ),
11537 severity: Some(lsp::DiagnosticSeverity::ERROR),
11538 message: message.to_string(),
11539 ..Default::default()
11540 }],
11541 },
11542 &[],
11543 cx,
11544 )
11545 .unwrap()
11546 });
11547 });
11548 executor.run_until_parked();
11549
11550 cx.update_editor(|editor, window, cx| {
11551 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11552 assert_eq!(
11553 editor
11554 .active_diagnostics
11555 .as_ref()
11556 .map(|diagnostics_group| diagnostics_group.primary_message.as_str()),
11557 Some(message),
11558 "Should have a diagnostics group activated"
11559 );
11560 });
11561 cx.assert_editor_state(indoc! {"
11562 fn func(abcˇ def: i32) -> u32 {
11563 }
11564 "});
11565
11566 cx.update(|_, cx| {
11567 lsp_store.update(cx, |lsp_store, cx| {
11568 lsp_store
11569 .update_diagnostics(
11570 LanguageServerId(0),
11571 lsp::PublishDiagnosticsParams {
11572 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11573 version: None,
11574 diagnostics: Vec::new(),
11575 },
11576 &[],
11577 cx,
11578 )
11579 .unwrap()
11580 });
11581 });
11582 executor.run_until_parked();
11583 cx.update_editor(|editor, _, _| {
11584 assert_eq!(
11585 editor.active_diagnostics, None,
11586 "After no diagnostics set to the editor, no diagnostics should be active"
11587 );
11588 });
11589 cx.assert_editor_state(indoc! {"
11590 fn func(abcˇ def: i32) -> u32 {
11591 }
11592 "});
11593
11594 cx.update_editor(|editor, window, cx| {
11595 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11596 assert_eq!(
11597 editor.active_diagnostics, None,
11598 "Should be no diagnostics to go to and activate"
11599 );
11600 });
11601 cx.assert_editor_state(indoc! {"
11602 fn func(abcˇ def: i32) -> u32 {
11603 }
11604 "});
11605}
11606
11607#[gpui::test]
11608async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
11609 init_test(cx, |_| {});
11610
11611 let mut cx = EditorTestContext::new(cx).await;
11612
11613 cx.set_state(indoc! {"
11614 fn func(abˇc def: i32) -> u32 {
11615 }
11616 "});
11617 let lsp_store =
11618 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11619
11620 cx.update(|_, cx| {
11621 lsp_store.update(cx, |lsp_store, cx| {
11622 lsp_store.update_diagnostics(
11623 LanguageServerId(0),
11624 lsp::PublishDiagnosticsParams {
11625 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11626 version: None,
11627 diagnostics: vec![lsp::Diagnostic {
11628 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
11629 severity: Some(lsp::DiagnosticSeverity::ERROR),
11630 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
11631 ..Default::default()
11632 }],
11633 },
11634 &[],
11635 cx,
11636 )
11637 })
11638 }).unwrap();
11639 cx.run_until_parked();
11640 cx.update_editor(|editor, window, cx| {
11641 hover_popover::hover(editor, &Default::default(), window, cx)
11642 });
11643 cx.run_until_parked();
11644 cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
11645}
11646
11647#[gpui::test]
11648async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11649 init_test(cx, |_| {});
11650
11651 let mut cx = EditorTestContext::new(cx).await;
11652
11653 let diff_base = r#"
11654 use some::mod;
11655
11656 const A: u32 = 42;
11657
11658 fn main() {
11659 println!("hello");
11660
11661 println!("world");
11662 }
11663 "#
11664 .unindent();
11665
11666 // Edits are modified, removed, modified, added
11667 cx.set_state(
11668 &r#"
11669 use some::modified;
11670
11671 ˇ
11672 fn main() {
11673 println!("hello there");
11674
11675 println!("around the");
11676 println!("world");
11677 }
11678 "#
11679 .unindent(),
11680 );
11681
11682 cx.set_head_text(&diff_base);
11683 executor.run_until_parked();
11684
11685 cx.update_editor(|editor, window, cx| {
11686 //Wrap around the bottom of the buffer
11687 for _ in 0..3 {
11688 editor.go_to_next_hunk(&GoToHunk, window, cx);
11689 }
11690 });
11691
11692 cx.assert_editor_state(
11693 &r#"
11694 ˇuse some::modified;
11695
11696
11697 fn main() {
11698 println!("hello there");
11699
11700 println!("around the");
11701 println!("world");
11702 }
11703 "#
11704 .unindent(),
11705 );
11706
11707 cx.update_editor(|editor, window, cx| {
11708 //Wrap around the top of the buffer
11709 for _ in 0..2 {
11710 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11711 }
11712 });
11713
11714 cx.assert_editor_state(
11715 &r#"
11716 use some::modified;
11717
11718
11719 fn main() {
11720 ˇ println!("hello there");
11721
11722 println!("around the");
11723 println!("world");
11724 }
11725 "#
11726 .unindent(),
11727 );
11728
11729 cx.update_editor(|editor, window, cx| {
11730 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11731 });
11732
11733 cx.assert_editor_state(
11734 &r#"
11735 use some::modified;
11736
11737 ˇ
11738 fn main() {
11739 println!("hello there");
11740
11741 println!("around the");
11742 println!("world");
11743 }
11744 "#
11745 .unindent(),
11746 );
11747
11748 cx.update_editor(|editor, window, cx| {
11749 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11750 });
11751
11752 cx.assert_editor_state(
11753 &r#"
11754 ˇuse some::modified;
11755
11756
11757 fn main() {
11758 println!("hello there");
11759
11760 println!("around the");
11761 println!("world");
11762 }
11763 "#
11764 .unindent(),
11765 );
11766
11767 cx.update_editor(|editor, window, cx| {
11768 for _ in 0..2 {
11769 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11770 }
11771 });
11772
11773 cx.assert_editor_state(
11774 &r#"
11775 use some::modified;
11776
11777
11778 fn main() {
11779 ˇ println!("hello there");
11780
11781 println!("around the");
11782 println!("world");
11783 }
11784 "#
11785 .unindent(),
11786 );
11787
11788 cx.update_editor(|editor, window, cx| {
11789 editor.fold(&Fold, window, cx);
11790 });
11791
11792 cx.update_editor(|editor, window, cx| {
11793 editor.go_to_next_hunk(&GoToHunk, window, cx);
11794 });
11795
11796 cx.assert_editor_state(
11797 &r#"
11798 ˇuse some::modified;
11799
11800
11801 fn main() {
11802 println!("hello there");
11803
11804 println!("around the");
11805 println!("world");
11806 }
11807 "#
11808 .unindent(),
11809 );
11810}
11811
11812#[test]
11813fn test_split_words() {
11814 fn split(text: &str) -> Vec<&str> {
11815 split_words(text).collect()
11816 }
11817
11818 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
11819 assert_eq!(split("hello_world"), &["hello_", "world"]);
11820 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
11821 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
11822 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
11823 assert_eq!(split("helloworld"), &["helloworld"]);
11824
11825 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
11826}
11827
11828#[gpui::test]
11829async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
11830 init_test(cx, |_| {});
11831
11832 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
11833 let mut assert = |before, after| {
11834 let _state_context = cx.set_state(before);
11835 cx.run_until_parked();
11836 cx.update_editor(|editor, window, cx| {
11837 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
11838 });
11839 cx.assert_editor_state(after);
11840 };
11841
11842 // Outside bracket jumps to outside of matching bracket
11843 assert("console.logˇ(var);", "console.log(var)ˇ;");
11844 assert("console.log(var)ˇ;", "console.logˇ(var);");
11845
11846 // Inside bracket jumps to inside of matching bracket
11847 assert("console.log(ˇvar);", "console.log(varˇ);");
11848 assert("console.log(varˇ);", "console.log(ˇvar);");
11849
11850 // When outside a bracket and inside, favor jumping to the inside bracket
11851 assert(
11852 "console.log('foo', [1, 2, 3]ˇ);",
11853 "console.log(ˇ'foo', [1, 2, 3]);",
11854 );
11855 assert(
11856 "console.log(ˇ'foo', [1, 2, 3]);",
11857 "console.log('foo', [1, 2, 3]ˇ);",
11858 );
11859
11860 // Bias forward if two options are equally likely
11861 assert(
11862 "let result = curried_fun()ˇ();",
11863 "let result = curried_fun()()ˇ;",
11864 );
11865
11866 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
11867 assert(
11868 indoc! {"
11869 function test() {
11870 console.log('test')ˇ
11871 }"},
11872 indoc! {"
11873 function test() {
11874 console.logˇ('test')
11875 }"},
11876 );
11877}
11878
11879#[gpui::test]
11880async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
11881 init_test(cx, |_| {});
11882
11883 let fs = FakeFs::new(cx.executor());
11884 fs.insert_tree(
11885 path!("/a"),
11886 json!({
11887 "main.rs": "fn main() { let a = 5; }",
11888 "other.rs": "// Test file",
11889 }),
11890 )
11891 .await;
11892 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11893
11894 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11895 language_registry.add(Arc::new(Language::new(
11896 LanguageConfig {
11897 name: "Rust".into(),
11898 matcher: LanguageMatcher {
11899 path_suffixes: vec!["rs".to_string()],
11900 ..Default::default()
11901 },
11902 brackets: BracketPairConfig {
11903 pairs: vec![BracketPair {
11904 start: "{".to_string(),
11905 end: "}".to_string(),
11906 close: true,
11907 surround: true,
11908 newline: true,
11909 }],
11910 disabled_scopes_by_bracket_ix: Vec::new(),
11911 },
11912 ..Default::default()
11913 },
11914 Some(tree_sitter_rust::LANGUAGE.into()),
11915 )));
11916 let mut fake_servers = language_registry.register_fake_lsp(
11917 "Rust",
11918 FakeLspAdapter {
11919 capabilities: lsp::ServerCapabilities {
11920 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
11921 first_trigger_character: "{".to_string(),
11922 more_trigger_character: None,
11923 }),
11924 ..Default::default()
11925 },
11926 ..Default::default()
11927 },
11928 );
11929
11930 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11931
11932 let cx = &mut VisualTestContext::from_window(*workspace, cx);
11933
11934 let worktree_id = workspace
11935 .update(cx, |workspace, _, cx| {
11936 workspace.project().update(cx, |project, cx| {
11937 project.worktrees(cx).next().unwrap().read(cx).id()
11938 })
11939 })
11940 .unwrap();
11941
11942 let buffer = project
11943 .update(cx, |project, cx| {
11944 project.open_local_buffer(path!("/a/main.rs"), cx)
11945 })
11946 .await
11947 .unwrap();
11948 let editor_handle = workspace
11949 .update(cx, |workspace, window, cx| {
11950 workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
11951 })
11952 .unwrap()
11953 .await
11954 .unwrap()
11955 .downcast::<Editor>()
11956 .unwrap();
11957
11958 cx.executor().start_waiting();
11959 let fake_server = fake_servers.next().await.unwrap();
11960
11961 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
11962 assert_eq!(
11963 params.text_document_position.text_document.uri,
11964 lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
11965 );
11966 assert_eq!(
11967 params.text_document_position.position,
11968 lsp::Position::new(0, 21),
11969 );
11970
11971 Ok(Some(vec![lsp::TextEdit {
11972 new_text: "]".to_string(),
11973 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11974 }]))
11975 });
11976
11977 editor_handle.update_in(cx, |editor, window, cx| {
11978 window.focus(&editor.focus_handle(cx));
11979 editor.change_selections(None, window, cx, |s| {
11980 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
11981 });
11982 editor.handle_input("{", window, cx);
11983 });
11984
11985 cx.executor().run_until_parked();
11986
11987 buffer.update(cx, |buffer, _| {
11988 assert_eq!(
11989 buffer.text(),
11990 "fn main() { let a = {5}; }",
11991 "No extra braces from on type formatting should appear in the buffer"
11992 )
11993 });
11994}
11995
11996#[gpui::test]
11997async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
11998 init_test(cx, |_| {});
11999
12000 let fs = FakeFs::new(cx.executor());
12001 fs.insert_tree(
12002 path!("/a"),
12003 json!({
12004 "main.rs": "fn main() { let a = 5; }",
12005 "other.rs": "// Test file",
12006 }),
12007 )
12008 .await;
12009
12010 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12011
12012 let server_restarts = Arc::new(AtomicUsize::new(0));
12013 let closure_restarts = Arc::clone(&server_restarts);
12014 let language_server_name = "test language server";
12015 let language_name: LanguageName = "Rust".into();
12016
12017 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12018 language_registry.add(Arc::new(Language::new(
12019 LanguageConfig {
12020 name: language_name.clone(),
12021 matcher: LanguageMatcher {
12022 path_suffixes: vec!["rs".to_string()],
12023 ..Default::default()
12024 },
12025 ..Default::default()
12026 },
12027 Some(tree_sitter_rust::LANGUAGE.into()),
12028 )));
12029 let mut fake_servers = language_registry.register_fake_lsp(
12030 "Rust",
12031 FakeLspAdapter {
12032 name: language_server_name,
12033 initialization_options: Some(json!({
12034 "testOptionValue": true
12035 })),
12036 initializer: Some(Box::new(move |fake_server| {
12037 let task_restarts = Arc::clone(&closure_restarts);
12038 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
12039 task_restarts.fetch_add(1, atomic::Ordering::Release);
12040 futures::future::ready(Ok(()))
12041 });
12042 })),
12043 ..Default::default()
12044 },
12045 );
12046
12047 let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12048 let _buffer = project
12049 .update(cx, |project, cx| {
12050 project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
12051 })
12052 .await
12053 .unwrap();
12054 let _fake_server = fake_servers.next().await.unwrap();
12055 update_test_language_settings(cx, |language_settings| {
12056 language_settings.languages.insert(
12057 language_name.clone(),
12058 LanguageSettingsContent {
12059 tab_size: NonZeroU32::new(8),
12060 ..Default::default()
12061 },
12062 );
12063 });
12064 cx.executor().run_until_parked();
12065 assert_eq!(
12066 server_restarts.load(atomic::Ordering::Acquire),
12067 0,
12068 "Should not restart LSP server on an unrelated change"
12069 );
12070
12071 update_test_project_settings(cx, |project_settings| {
12072 project_settings.lsp.insert(
12073 "Some other server name".into(),
12074 LspSettings {
12075 binary: None,
12076 settings: None,
12077 initialization_options: Some(json!({
12078 "some other init value": false
12079 })),
12080 },
12081 );
12082 });
12083 cx.executor().run_until_parked();
12084 assert_eq!(
12085 server_restarts.load(atomic::Ordering::Acquire),
12086 0,
12087 "Should not restart LSP server on an unrelated LSP settings change"
12088 );
12089
12090 update_test_project_settings(cx, |project_settings| {
12091 project_settings.lsp.insert(
12092 language_server_name.into(),
12093 LspSettings {
12094 binary: None,
12095 settings: None,
12096 initialization_options: Some(json!({
12097 "anotherInitValue": false
12098 })),
12099 },
12100 );
12101 });
12102 cx.executor().run_until_parked();
12103 assert_eq!(
12104 server_restarts.load(atomic::Ordering::Acquire),
12105 1,
12106 "Should restart LSP server on a related LSP settings change"
12107 );
12108
12109 update_test_project_settings(cx, |project_settings| {
12110 project_settings.lsp.insert(
12111 language_server_name.into(),
12112 LspSettings {
12113 binary: None,
12114 settings: None,
12115 initialization_options: Some(json!({
12116 "anotherInitValue": false
12117 })),
12118 },
12119 );
12120 });
12121 cx.executor().run_until_parked();
12122 assert_eq!(
12123 server_restarts.load(atomic::Ordering::Acquire),
12124 1,
12125 "Should not restart LSP server on a related LSP settings change that is the same"
12126 );
12127
12128 update_test_project_settings(cx, |project_settings| {
12129 project_settings.lsp.insert(
12130 language_server_name.into(),
12131 LspSettings {
12132 binary: None,
12133 settings: None,
12134 initialization_options: None,
12135 },
12136 );
12137 });
12138 cx.executor().run_until_parked();
12139 assert_eq!(
12140 server_restarts.load(atomic::Ordering::Acquire),
12141 2,
12142 "Should restart LSP server on another related LSP settings change"
12143 );
12144}
12145
12146#[gpui::test]
12147async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
12148 init_test(cx, |_| {});
12149
12150 let mut cx = EditorLspTestContext::new_rust(
12151 lsp::ServerCapabilities {
12152 completion_provider: Some(lsp::CompletionOptions {
12153 trigger_characters: Some(vec![".".to_string()]),
12154 resolve_provider: Some(true),
12155 ..Default::default()
12156 }),
12157 ..Default::default()
12158 },
12159 cx,
12160 )
12161 .await;
12162
12163 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12164 cx.simulate_keystroke(".");
12165 let completion_item = lsp::CompletionItem {
12166 label: "some".into(),
12167 kind: Some(lsp::CompletionItemKind::SNIPPET),
12168 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
12169 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
12170 kind: lsp::MarkupKind::Markdown,
12171 value: "```rust\nSome(2)\n```".to_string(),
12172 })),
12173 deprecated: Some(false),
12174 sort_text: Some("fffffff2".to_string()),
12175 filter_text: Some("some".to_string()),
12176 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
12177 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12178 range: lsp::Range {
12179 start: lsp::Position {
12180 line: 0,
12181 character: 22,
12182 },
12183 end: lsp::Position {
12184 line: 0,
12185 character: 22,
12186 },
12187 },
12188 new_text: "Some(2)".to_string(),
12189 })),
12190 additional_text_edits: Some(vec![lsp::TextEdit {
12191 range: lsp::Range {
12192 start: lsp::Position {
12193 line: 0,
12194 character: 20,
12195 },
12196 end: lsp::Position {
12197 line: 0,
12198 character: 22,
12199 },
12200 },
12201 new_text: "".to_string(),
12202 }]),
12203 ..Default::default()
12204 };
12205
12206 let closure_completion_item = completion_item.clone();
12207 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12208 let task_completion_item = closure_completion_item.clone();
12209 async move {
12210 Ok(Some(lsp::CompletionResponse::Array(vec![
12211 task_completion_item,
12212 ])))
12213 }
12214 });
12215
12216 request.next().await;
12217
12218 cx.condition(|editor, _| editor.context_menu_visible())
12219 .await;
12220 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12221 editor
12222 .confirm_completion(&ConfirmCompletion::default(), window, cx)
12223 .unwrap()
12224 });
12225 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
12226
12227 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
12228 let task_completion_item = completion_item.clone();
12229 async move { Ok(task_completion_item) }
12230 })
12231 .next()
12232 .await
12233 .unwrap();
12234 apply_additional_edits.await.unwrap();
12235 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
12236}
12237
12238#[gpui::test]
12239async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
12240 init_test(cx, |_| {});
12241
12242 let mut cx = EditorLspTestContext::new_rust(
12243 lsp::ServerCapabilities {
12244 completion_provider: Some(lsp::CompletionOptions {
12245 trigger_characters: Some(vec![".".to_string()]),
12246 resolve_provider: Some(true),
12247 ..Default::default()
12248 }),
12249 ..Default::default()
12250 },
12251 cx,
12252 )
12253 .await;
12254
12255 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12256 cx.simulate_keystroke(".");
12257
12258 let item1 = lsp::CompletionItem {
12259 label: "method id()".to_string(),
12260 filter_text: Some("id".to_string()),
12261 detail: None,
12262 documentation: None,
12263 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12264 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12265 new_text: ".id".to_string(),
12266 })),
12267 ..lsp::CompletionItem::default()
12268 };
12269
12270 let item2 = lsp::CompletionItem {
12271 label: "other".to_string(),
12272 filter_text: Some("other".to_string()),
12273 detail: None,
12274 documentation: None,
12275 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12276 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12277 new_text: ".other".to_string(),
12278 })),
12279 ..lsp::CompletionItem::default()
12280 };
12281
12282 let item1 = item1.clone();
12283 cx.handle_request::<lsp::request::Completion, _, _>({
12284 let item1 = item1.clone();
12285 move |_, _, _| {
12286 let item1 = item1.clone();
12287 let item2 = item2.clone();
12288 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
12289 }
12290 })
12291 .next()
12292 .await;
12293
12294 cx.condition(|editor, _| editor.context_menu_visible())
12295 .await;
12296 cx.update_editor(|editor, _, _| {
12297 let context_menu = editor.context_menu.borrow_mut();
12298 let context_menu = context_menu
12299 .as_ref()
12300 .expect("Should have the context menu deployed");
12301 match context_menu {
12302 CodeContextMenu::Completions(completions_menu) => {
12303 let completions = completions_menu.completions.borrow_mut();
12304 assert_eq!(
12305 completions
12306 .iter()
12307 .map(|completion| &completion.label.text)
12308 .collect::<Vec<_>>(),
12309 vec!["method id()", "other"]
12310 )
12311 }
12312 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12313 }
12314 });
12315
12316 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
12317 let item1 = item1.clone();
12318 move |_, item_to_resolve, _| {
12319 let item1 = item1.clone();
12320 async move {
12321 if item1 == item_to_resolve {
12322 Ok(lsp::CompletionItem {
12323 label: "method id()".to_string(),
12324 filter_text: Some("id".to_string()),
12325 detail: Some("Now resolved!".to_string()),
12326 documentation: Some(lsp::Documentation::String("Docs".to_string())),
12327 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12328 range: lsp::Range::new(
12329 lsp::Position::new(0, 22),
12330 lsp::Position::new(0, 22),
12331 ),
12332 new_text: ".id".to_string(),
12333 })),
12334 ..lsp::CompletionItem::default()
12335 })
12336 } else {
12337 Ok(item_to_resolve)
12338 }
12339 }
12340 }
12341 })
12342 .next()
12343 .await
12344 .unwrap();
12345 cx.run_until_parked();
12346
12347 cx.update_editor(|editor, window, cx| {
12348 editor.context_menu_next(&Default::default(), window, cx);
12349 });
12350
12351 cx.update_editor(|editor, _, _| {
12352 let context_menu = editor.context_menu.borrow_mut();
12353 let context_menu = context_menu
12354 .as_ref()
12355 .expect("Should have the context menu deployed");
12356 match context_menu {
12357 CodeContextMenu::Completions(completions_menu) => {
12358 let completions = completions_menu.completions.borrow_mut();
12359 assert_eq!(
12360 completions
12361 .iter()
12362 .map(|completion| &completion.label.text)
12363 .collect::<Vec<_>>(),
12364 vec!["method id() Now resolved!", "other"],
12365 "Should update first completion label, but not second as the filter text did not match."
12366 );
12367 }
12368 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12369 }
12370 });
12371}
12372
12373#[gpui::test]
12374async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
12375 init_test(cx, |_| {});
12376
12377 let mut cx = EditorLspTestContext::new_rust(
12378 lsp::ServerCapabilities {
12379 completion_provider: Some(lsp::CompletionOptions {
12380 trigger_characters: Some(vec![".".to_string()]),
12381 resolve_provider: Some(true),
12382 ..Default::default()
12383 }),
12384 ..Default::default()
12385 },
12386 cx,
12387 )
12388 .await;
12389
12390 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12391 cx.simulate_keystroke(".");
12392
12393 let unresolved_item_1 = lsp::CompletionItem {
12394 label: "id".to_string(),
12395 filter_text: Some("id".to_string()),
12396 detail: None,
12397 documentation: None,
12398 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12399 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12400 new_text: ".id".to_string(),
12401 })),
12402 ..lsp::CompletionItem::default()
12403 };
12404 let resolved_item_1 = lsp::CompletionItem {
12405 additional_text_edits: Some(vec![lsp::TextEdit {
12406 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12407 new_text: "!!".to_string(),
12408 }]),
12409 ..unresolved_item_1.clone()
12410 };
12411 let unresolved_item_2 = lsp::CompletionItem {
12412 label: "other".to_string(),
12413 filter_text: Some("other".to_string()),
12414 detail: None,
12415 documentation: None,
12416 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12417 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12418 new_text: ".other".to_string(),
12419 })),
12420 ..lsp::CompletionItem::default()
12421 };
12422 let resolved_item_2 = lsp::CompletionItem {
12423 additional_text_edits: Some(vec![lsp::TextEdit {
12424 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12425 new_text: "??".to_string(),
12426 }]),
12427 ..unresolved_item_2.clone()
12428 };
12429
12430 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
12431 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
12432 cx.lsp
12433 .server
12434 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12435 let unresolved_item_1 = unresolved_item_1.clone();
12436 let resolved_item_1 = resolved_item_1.clone();
12437 let unresolved_item_2 = unresolved_item_2.clone();
12438 let resolved_item_2 = resolved_item_2.clone();
12439 let resolve_requests_1 = resolve_requests_1.clone();
12440 let resolve_requests_2 = resolve_requests_2.clone();
12441 move |unresolved_request, _| {
12442 let unresolved_item_1 = unresolved_item_1.clone();
12443 let resolved_item_1 = resolved_item_1.clone();
12444 let unresolved_item_2 = unresolved_item_2.clone();
12445 let resolved_item_2 = resolved_item_2.clone();
12446 let resolve_requests_1 = resolve_requests_1.clone();
12447 let resolve_requests_2 = resolve_requests_2.clone();
12448 async move {
12449 if unresolved_request == unresolved_item_1 {
12450 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
12451 Ok(resolved_item_1.clone())
12452 } else if unresolved_request == unresolved_item_2 {
12453 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
12454 Ok(resolved_item_2.clone())
12455 } else {
12456 panic!("Unexpected completion item {unresolved_request:?}")
12457 }
12458 }
12459 }
12460 })
12461 .detach();
12462
12463 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12464 let unresolved_item_1 = unresolved_item_1.clone();
12465 let unresolved_item_2 = unresolved_item_2.clone();
12466 async move {
12467 Ok(Some(lsp::CompletionResponse::Array(vec![
12468 unresolved_item_1,
12469 unresolved_item_2,
12470 ])))
12471 }
12472 })
12473 .next()
12474 .await;
12475
12476 cx.condition(|editor, _| editor.context_menu_visible())
12477 .await;
12478 cx.update_editor(|editor, _, _| {
12479 let context_menu = editor.context_menu.borrow_mut();
12480 let context_menu = context_menu
12481 .as_ref()
12482 .expect("Should have the context menu deployed");
12483 match context_menu {
12484 CodeContextMenu::Completions(completions_menu) => {
12485 let completions = completions_menu.completions.borrow_mut();
12486 assert_eq!(
12487 completions
12488 .iter()
12489 .map(|completion| &completion.label.text)
12490 .collect::<Vec<_>>(),
12491 vec!["id", "other"]
12492 )
12493 }
12494 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12495 }
12496 });
12497 cx.run_until_parked();
12498
12499 cx.update_editor(|editor, window, cx| {
12500 editor.context_menu_next(&ContextMenuNext, window, cx);
12501 });
12502 cx.run_until_parked();
12503 cx.update_editor(|editor, window, cx| {
12504 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12505 });
12506 cx.run_until_parked();
12507 cx.update_editor(|editor, window, cx| {
12508 editor.context_menu_next(&ContextMenuNext, window, cx);
12509 });
12510 cx.run_until_parked();
12511 cx.update_editor(|editor, window, cx| {
12512 editor
12513 .compose_completion(&ComposeCompletion::default(), window, cx)
12514 .expect("No task returned")
12515 })
12516 .await
12517 .expect("Completion failed");
12518 cx.run_until_parked();
12519
12520 cx.update_editor(|editor, _, cx| {
12521 assert_eq!(
12522 resolve_requests_1.load(atomic::Ordering::Acquire),
12523 1,
12524 "Should always resolve once despite multiple selections"
12525 );
12526 assert_eq!(
12527 resolve_requests_2.load(atomic::Ordering::Acquire),
12528 1,
12529 "Should always resolve once after multiple selections and applying the completion"
12530 );
12531 assert_eq!(
12532 editor.text(cx),
12533 "fn main() { let a = ??.other; }",
12534 "Should use resolved data when applying the completion"
12535 );
12536 });
12537}
12538
12539#[gpui::test]
12540async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
12541 init_test(cx, |_| {});
12542
12543 let item_0 = lsp::CompletionItem {
12544 label: "abs".into(),
12545 insert_text: Some("abs".into()),
12546 data: Some(json!({ "very": "special"})),
12547 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
12548 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12549 lsp::InsertReplaceEdit {
12550 new_text: "abs".to_string(),
12551 insert: lsp::Range::default(),
12552 replace: lsp::Range::default(),
12553 },
12554 )),
12555 ..lsp::CompletionItem::default()
12556 };
12557 let items = iter::once(item_0.clone())
12558 .chain((11..51).map(|i| lsp::CompletionItem {
12559 label: format!("item_{}", i),
12560 insert_text: Some(format!("item_{}", i)),
12561 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
12562 ..lsp::CompletionItem::default()
12563 }))
12564 .collect::<Vec<_>>();
12565
12566 let default_commit_characters = vec!["?".to_string()];
12567 let default_data = json!({ "default": "data"});
12568 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
12569 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
12570 let default_edit_range = lsp::Range {
12571 start: lsp::Position {
12572 line: 0,
12573 character: 5,
12574 },
12575 end: lsp::Position {
12576 line: 0,
12577 character: 5,
12578 },
12579 };
12580
12581 let mut cx = EditorLspTestContext::new_rust(
12582 lsp::ServerCapabilities {
12583 completion_provider: Some(lsp::CompletionOptions {
12584 trigger_characters: Some(vec![".".to_string()]),
12585 resolve_provider: Some(true),
12586 ..Default::default()
12587 }),
12588 ..Default::default()
12589 },
12590 cx,
12591 )
12592 .await;
12593
12594 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12595 cx.simulate_keystroke(".");
12596
12597 let completion_data = default_data.clone();
12598 let completion_characters = default_commit_characters.clone();
12599 let completion_items = items.clone();
12600 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12601 let default_data = completion_data.clone();
12602 let default_commit_characters = completion_characters.clone();
12603 let items = completion_items.clone();
12604 async move {
12605 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12606 items,
12607 item_defaults: Some(lsp::CompletionListItemDefaults {
12608 data: Some(default_data.clone()),
12609 commit_characters: Some(default_commit_characters.clone()),
12610 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
12611 default_edit_range,
12612 )),
12613 insert_text_format: Some(default_insert_text_format),
12614 insert_text_mode: Some(default_insert_text_mode),
12615 }),
12616 ..lsp::CompletionList::default()
12617 })))
12618 }
12619 })
12620 .next()
12621 .await;
12622
12623 let resolved_items = Arc::new(Mutex::new(Vec::new()));
12624 cx.lsp
12625 .server
12626 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12627 let closure_resolved_items = resolved_items.clone();
12628 move |item_to_resolve, _| {
12629 let closure_resolved_items = closure_resolved_items.clone();
12630 async move {
12631 closure_resolved_items.lock().push(item_to_resolve.clone());
12632 Ok(item_to_resolve)
12633 }
12634 }
12635 })
12636 .detach();
12637
12638 cx.condition(|editor, _| editor.context_menu_visible())
12639 .await;
12640 cx.run_until_parked();
12641 cx.update_editor(|editor, _, _| {
12642 let menu = editor.context_menu.borrow_mut();
12643 match menu.as_ref().expect("should have the completions menu") {
12644 CodeContextMenu::Completions(completions_menu) => {
12645 assert_eq!(
12646 completions_menu
12647 .entries
12648 .borrow()
12649 .iter()
12650 .map(|mat| mat.string.clone())
12651 .collect::<Vec<String>>(),
12652 items
12653 .iter()
12654 .map(|completion| completion.label.clone())
12655 .collect::<Vec<String>>()
12656 );
12657 }
12658 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
12659 }
12660 });
12661 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
12662 // with 4 from the end.
12663 assert_eq!(
12664 *resolved_items.lock(),
12665 [&items[0..16], &items[items.len() - 4..items.len()]]
12666 .concat()
12667 .iter()
12668 .cloned()
12669 .map(|mut item| {
12670 if item.data.is_none() {
12671 item.data = Some(default_data.clone());
12672 }
12673 item
12674 })
12675 .collect::<Vec<lsp::CompletionItem>>(),
12676 "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
12677 );
12678 resolved_items.lock().clear();
12679
12680 cx.update_editor(|editor, window, cx| {
12681 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12682 });
12683 cx.run_until_parked();
12684 // Completions that have already been resolved are skipped.
12685 assert_eq!(
12686 *resolved_items.lock(),
12687 items[items.len() - 16..items.len() - 4]
12688 .iter()
12689 .cloned()
12690 .map(|mut item| {
12691 if item.data.is_none() {
12692 item.data = Some(default_data.clone());
12693 }
12694 item
12695 })
12696 .collect::<Vec<lsp::CompletionItem>>()
12697 );
12698 resolved_items.lock().clear();
12699}
12700
12701#[gpui::test]
12702async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
12703 init_test(cx, |_| {});
12704
12705 let mut cx = EditorLspTestContext::new(
12706 Language::new(
12707 LanguageConfig {
12708 matcher: LanguageMatcher {
12709 path_suffixes: vec!["jsx".into()],
12710 ..Default::default()
12711 },
12712 overrides: [(
12713 "element".into(),
12714 LanguageConfigOverride {
12715 word_characters: Override::Set(['-'].into_iter().collect()),
12716 ..Default::default()
12717 },
12718 )]
12719 .into_iter()
12720 .collect(),
12721 ..Default::default()
12722 },
12723 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12724 )
12725 .with_override_query("(jsx_self_closing_element) @element")
12726 .unwrap(),
12727 lsp::ServerCapabilities {
12728 completion_provider: Some(lsp::CompletionOptions {
12729 trigger_characters: Some(vec![":".to_string()]),
12730 ..Default::default()
12731 }),
12732 ..Default::default()
12733 },
12734 cx,
12735 )
12736 .await;
12737
12738 cx.lsp
12739 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12740 Ok(Some(lsp::CompletionResponse::Array(vec![
12741 lsp::CompletionItem {
12742 label: "bg-blue".into(),
12743 ..Default::default()
12744 },
12745 lsp::CompletionItem {
12746 label: "bg-red".into(),
12747 ..Default::default()
12748 },
12749 lsp::CompletionItem {
12750 label: "bg-yellow".into(),
12751 ..Default::default()
12752 },
12753 ])))
12754 });
12755
12756 cx.set_state(r#"<p class="bgˇ" />"#);
12757
12758 // Trigger completion when typing a dash, because the dash is an extra
12759 // word character in the 'element' scope, which contains the cursor.
12760 cx.simulate_keystroke("-");
12761 cx.executor().run_until_parked();
12762 cx.update_editor(|editor, _, _| {
12763 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12764 {
12765 assert_eq!(
12766 completion_menu_entries(&menu),
12767 &["bg-red", "bg-blue", "bg-yellow"]
12768 );
12769 } else {
12770 panic!("expected completion menu to be open");
12771 }
12772 });
12773
12774 cx.simulate_keystroke("l");
12775 cx.executor().run_until_parked();
12776 cx.update_editor(|editor, _, _| {
12777 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12778 {
12779 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
12780 } else {
12781 panic!("expected completion menu to be open");
12782 }
12783 });
12784
12785 // When filtering completions, consider the character after the '-' to
12786 // be the start of a subword.
12787 cx.set_state(r#"<p class="yelˇ" />"#);
12788 cx.simulate_keystroke("l");
12789 cx.executor().run_until_parked();
12790 cx.update_editor(|editor, _, _| {
12791 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12792 {
12793 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
12794 } else {
12795 panic!("expected completion menu to be open");
12796 }
12797 });
12798}
12799
12800fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
12801 let entries = menu.entries.borrow();
12802 entries.iter().map(|mat| mat.string.clone()).collect()
12803}
12804
12805#[gpui::test]
12806async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
12807 init_test(cx, |settings| {
12808 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
12809 FormatterList(vec![Formatter::Prettier].into()),
12810 ))
12811 });
12812
12813 let fs = FakeFs::new(cx.executor());
12814 fs.insert_file(path!("/file.ts"), Default::default()).await;
12815
12816 let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
12817 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12818
12819 language_registry.add(Arc::new(Language::new(
12820 LanguageConfig {
12821 name: "TypeScript".into(),
12822 matcher: LanguageMatcher {
12823 path_suffixes: vec!["ts".to_string()],
12824 ..Default::default()
12825 },
12826 ..Default::default()
12827 },
12828 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12829 )));
12830 update_test_language_settings(cx, |settings| {
12831 settings.defaults.prettier = Some(PrettierSettings {
12832 allowed: true,
12833 ..PrettierSettings::default()
12834 });
12835 });
12836
12837 let test_plugin = "test_plugin";
12838 let _ = language_registry.register_fake_lsp(
12839 "TypeScript",
12840 FakeLspAdapter {
12841 prettier_plugins: vec![test_plugin],
12842 ..Default::default()
12843 },
12844 );
12845
12846 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
12847 let buffer = project
12848 .update(cx, |project, cx| {
12849 project.open_local_buffer(path!("/file.ts"), cx)
12850 })
12851 .await
12852 .unwrap();
12853
12854 let buffer_text = "one\ntwo\nthree\n";
12855 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12856 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12857 editor.update_in(cx, |editor, window, cx| {
12858 editor.set_text(buffer_text, window, cx)
12859 });
12860
12861 editor
12862 .update_in(cx, |editor, window, cx| {
12863 editor.perform_format(
12864 project.clone(),
12865 FormatTrigger::Manual,
12866 FormatTarget::Buffers,
12867 window,
12868 cx,
12869 )
12870 })
12871 .unwrap()
12872 .await;
12873 assert_eq!(
12874 editor.update(cx, |editor, cx| editor.text(cx)),
12875 buffer_text.to_string() + prettier_format_suffix,
12876 "Test prettier formatting was not applied to the original buffer text",
12877 );
12878
12879 update_test_language_settings(cx, |settings| {
12880 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
12881 });
12882 let format = editor.update_in(cx, |editor, window, cx| {
12883 editor.perform_format(
12884 project.clone(),
12885 FormatTrigger::Manual,
12886 FormatTarget::Buffers,
12887 window,
12888 cx,
12889 )
12890 });
12891 format.await.unwrap();
12892 assert_eq!(
12893 editor.update(cx, |editor, cx| editor.text(cx)),
12894 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
12895 "Autoformatting (via test prettier) was not applied to the original buffer text",
12896 );
12897}
12898
12899#[gpui::test]
12900async fn test_addition_reverts(cx: &mut TestAppContext) {
12901 init_test(cx, |_| {});
12902 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12903 let base_text = indoc! {r#"
12904 struct Row;
12905 struct Row1;
12906 struct Row2;
12907
12908 struct Row4;
12909 struct Row5;
12910 struct Row6;
12911
12912 struct Row8;
12913 struct Row9;
12914 struct Row10;"#};
12915
12916 // When addition hunks are not adjacent to carets, no hunk revert is performed
12917 assert_hunk_revert(
12918 indoc! {r#"struct Row;
12919 struct Row1;
12920 struct Row1.1;
12921 struct Row1.2;
12922 struct Row2;ˇ
12923
12924 struct Row4;
12925 struct Row5;
12926 struct Row6;
12927
12928 struct Row8;
12929 ˇstruct Row9;
12930 struct Row9.1;
12931 struct Row9.2;
12932 struct Row9.3;
12933 struct Row10;"#},
12934 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
12935 indoc! {r#"struct Row;
12936 struct Row1;
12937 struct Row1.1;
12938 struct Row1.2;
12939 struct Row2;ˇ
12940
12941 struct Row4;
12942 struct Row5;
12943 struct Row6;
12944
12945 struct Row8;
12946 ˇstruct Row9;
12947 struct Row9.1;
12948 struct Row9.2;
12949 struct Row9.3;
12950 struct Row10;"#},
12951 base_text,
12952 &mut cx,
12953 );
12954 // Same for selections
12955 assert_hunk_revert(
12956 indoc! {r#"struct Row;
12957 struct Row1;
12958 struct Row2;
12959 struct Row2.1;
12960 struct Row2.2;
12961 «ˇ
12962 struct Row4;
12963 struct» Row5;
12964 «struct Row6;
12965 ˇ»
12966 struct Row9.1;
12967 struct Row9.2;
12968 struct Row9.3;
12969 struct Row8;
12970 struct Row9;
12971 struct Row10;"#},
12972 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
12973 indoc! {r#"struct Row;
12974 struct Row1;
12975 struct Row2;
12976 struct Row2.1;
12977 struct Row2.2;
12978 «ˇ
12979 struct Row4;
12980 struct» Row5;
12981 «struct Row6;
12982 ˇ»
12983 struct Row9.1;
12984 struct Row9.2;
12985 struct Row9.3;
12986 struct Row8;
12987 struct Row9;
12988 struct Row10;"#},
12989 base_text,
12990 &mut cx,
12991 );
12992
12993 // When carets and selections intersect the addition hunks, those are reverted.
12994 // Adjacent carets got merged.
12995 assert_hunk_revert(
12996 indoc! {r#"struct Row;
12997 ˇ// something on the top
12998 struct Row1;
12999 struct Row2;
13000 struct Roˇw3.1;
13001 struct Row2.2;
13002 struct Row2.3;ˇ
13003
13004 struct Row4;
13005 struct ˇRow5.1;
13006 struct Row5.2;
13007 struct «Rowˇ»5.3;
13008 struct Row5;
13009 struct Row6;
13010 ˇ
13011 struct Row9.1;
13012 struct «Rowˇ»9.2;
13013 struct «ˇRow»9.3;
13014 struct Row8;
13015 struct Row9;
13016 «ˇ// something on bottom»
13017 struct Row10;"#},
13018 vec![
13019 DiffHunkStatusKind::Added,
13020 DiffHunkStatusKind::Added,
13021 DiffHunkStatusKind::Added,
13022 DiffHunkStatusKind::Added,
13023 DiffHunkStatusKind::Added,
13024 ],
13025 indoc! {r#"struct Row;
13026 ˇstruct Row1;
13027 struct Row2;
13028 ˇ
13029 struct Row4;
13030 ˇstruct Row5;
13031 struct Row6;
13032 ˇ
13033 ˇstruct Row8;
13034 struct Row9;
13035 ˇstruct Row10;"#},
13036 base_text,
13037 &mut cx,
13038 );
13039}
13040
13041#[gpui::test]
13042async fn test_modification_reverts(cx: &mut TestAppContext) {
13043 init_test(cx, |_| {});
13044 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13045 let base_text = indoc! {r#"
13046 struct Row;
13047 struct Row1;
13048 struct Row2;
13049
13050 struct Row4;
13051 struct Row5;
13052 struct Row6;
13053
13054 struct Row8;
13055 struct Row9;
13056 struct Row10;"#};
13057
13058 // Modification hunks behave the same as the addition ones.
13059 assert_hunk_revert(
13060 indoc! {r#"struct Row;
13061 struct Row1;
13062 struct Row33;
13063 ˇ
13064 struct Row4;
13065 struct Row5;
13066 struct Row6;
13067 ˇ
13068 struct Row99;
13069 struct Row9;
13070 struct Row10;"#},
13071 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
13072 indoc! {r#"struct Row;
13073 struct Row1;
13074 struct Row33;
13075 ˇ
13076 struct Row4;
13077 struct Row5;
13078 struct Row6;
13079 ˇ
13080 struct Row99;
13081 struct Row9;
13082 struct Row10;"#},
13083 base_text,
13084 &mut cx,
13085 );
13086 assert_hunk_revert(
13087 indoc! {r#"struct Row;
13088 struct Row1;
13089 struct Row33;
13090 «ˇ
13091 struct Row4;
13092 struct» Row5;
13093 «struct Row6;
13094 ˇ»
13095 struct Row99;
13096 struct Row9;
13097 struct Row10;"#},
13098 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
13099 indoc! {r#"struct Row;
13100 struct Row1;
13101 struct Row33;
13102 «ˇ
13103 struct Row4;
13104 struct» Row5;
13105 «struct Row6;
13106 ˇ»
13107 struct Row99;
13108 struct Row9;
13109 struct Row10;"#},
13110 base_text,
13111 &mut cx,
13112 );
13113
13114 assert_hunk_revert(
13115 indoc! {r#"ˇstruct Row1.1;
13116 struct Row1;
13117 «ˇstr»uct Row22;
13118
13119 struct ˇRow44;
13120 struct Row5;
13121 struct «Rˇ»ow66;ˇ
13122
13123 «struˇ»ct Row88;
13124 struct Row9;
13125 struct Row1011;ˇ"#},
13126 vec![
13127 DiffHunkStatusKind::Modified,
13128 DiffHunkStatusKind::Modified,
13129 DiffHunkStatusKind::Modified,
13130 DiffHunkStatusKind::Modified,
13131 DiffHunkStatusKind::Modified,
13132 DiffHunkStatusKind::Modified,
13133 ],
13134 indoc! {r#"struct Row;
13135 ˇstruct Row1;
13136 struct Row2;
13137 ˇ
13138 struct Row4;
13139 ˇstruct Row5;
13140 struct Row6;
13141 ˇ
13142 struct Row8;
13143 ˇstruct Row9;
13144 struct Row10;ˇ"#},
13145 base_text,
13146 &mut cx,
13147 );
13148}
13149
13150#[gpui::test]
13151async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
13152 init_test(cx, |_| {});
13153 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13154 let base_text = indoc! {r#"
13155 one
13156
13157 two
13158 three
13159 "#};
13160
13161 cx.set_head_text(base_text);
13162 cx.set_state("\nˇ\n");
13163 cx.executor().run_until_parked();
13164 cx.update_editor(|editor, _window, cx| {
13165 editor.expand_selected_diff_hunks(cx);
13166 });
13167 cx.executor().run_until_parked();
13168 cx.update_editor(|editor, window, cx| {
13169 editor.backspace(&Default::default(), window, cx);
13170 });
13171 cx.run_until_parked();
13172 cx.assert_state_with_diff(
13173 indoc! {r#"
13174
13175 - two
13176 - threeˇ
13177 +
13178 "#}
13179 .to_string(),
13180 );
13181}
13182
13183#[gpui::test]
13184async fn test_deletion_reverts(cx: &mut TestAppContext) {
13185 init_test(cx, |_| {});
13186 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13187 let base_text = indoc! {r#"struct Row;
13188struct Row1;
13189struct Row2;
13190
13191struct Row4;
13192struct Row5;
13193struct Row6;
13194
13195struct Row8;
13196struct Row9;
13197struct Row10;"#};
13198
13199 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
13200 assert_hunk_revert(
13201 indoc! {r#"struct Row;
13202 struct Row2;
13203
13204 ˇstruct Row4;
13205 struct Row5;
13206 struct Row6;
13207 ˇ
13208 struct Row8;
13209 struct Row10;"#},
13210 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13211 indoc! {r#"struct Row;
13212 struct Row2;
13213
13214 ˇstruct Row4;
13215 struct Row5;
13216 struct Row6;
13217 ˇ
13218 struct Row8;
13219 struct Row10;"#},
13220 base_text,
13221 &mut cx,
13222 );
13223 assert_hunk_revert(
13224 indoc! {r#"struct Row;
13225 struct Row2;
13226
13227 «ˇstruct Row4;
13228 struct» Row5;
13229 «struct Row6;
13230 ˇ»
13231 struct Row8;
13232 struct Row10;"#},
13233 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13234 indoc! {r#"struct Row;
13235 struct Row2;
13236
13237 «ˇstruct Row4;
13238 struct» Row5;
13239 «struct Row6;
13240 ˇ»
13241 struct Row8;
13242 struct Row10;"#},
13243 base_text,
13244 &mut cx,
13245 );
13246
13247 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
13248 assert_hunk_revert(
13249 indoc! {r#"struct Row;
13250 ˇstruct Row2;
13251
13252 struct Row4;
13253 struct Row5;
13254 struct Row6;
13255
13256 struct Row8;ˇ
13257 struct Row10;"#},
13258 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13259 indoc! {r#"struct Row;
13260 struct Row1;
13261 ˇstruct Row2;
13262
13263 struct Row4;
13264 struct Row5;
13265 struct Row6;
13266
13267 struct Row8;ˇ
13268 struct Row9;
13269 struct Row10;"#},
13270 base_text,
13271 &mut cx,
13272 );
13273 assert_hunk_revert(
13274 indoc! {r#"struct Row;
13275 struct Row2«ˇ;
13276 struct Row4;
13277 struct» Row5;
13278 «struct Row6;
13279
13280 struct Row8;ˇ»
13281 struct Row10;"#},
13282 vec![
13283 DiffHunkStatusKind::Deleted,
13284 DiffHunkStatusKind::Deleted,
13285 DiffHunkStatusKind::Deleted,
13286 ],
13287 indoc! {r#"struct Row;
13288 struct Row1;
13289 struct Row2«ˇ;
13290
13291 struct Row4;
13292 struct» Row5;
13293 «struct Row6;
13294
13295 struct Row8;ˇ»
13296 struct Row9;
13297 struct Row10;"#},
13298 base_text,
13299 &mut cx,
13300 );
13301}
13302
13303#[gpui::test]
13304async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
13305 init_test(cx, |_| {});
13306
13307 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
13308 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
13309 let base_text_3 =
13310 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
13311
13312 let text_1 = edit_first_char_of_every_line(base_text_1);
13313 let text_2 = edit_first_char_of_every_line(base_text_2);
13314 let text_3 = edit_first_char_of_every_line(base_text_3);
13315
13316 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
13317 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
13318 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
13319
13320 let multibuffer = cx.new(|cx| {
13321 let mut multibuffer = MultiBuffer::new(ReadWrite);
13322 multibuffer.push_excerpts(
13323 buffer_1.clone(),
13324 [
13325 ExcerptRange {
13326 context: Point::new(0, 0)..Point::new(3, 0),
13327 primary: None,
13328 },
13329 ExcerptRange {
13330 context: Point::new(5, 0)..Point::new(7, 0),
13331 primary: None,
13332 },
13333 ExcerptRange {
13334 context: Point::new(9, 0)..Point::new(10, 4),
13335 primary: None,
13336 },
13337 ],
13338 cx,
13339 );
13340 multibuffer.push_excerpts(
13341 buffer_2.clone(),
13342 [
13343 ExcerptRange {
13344 context: Point::new(0, 0)..Point::new(3, 0),
13345 primary: None,
13346 },
13347 ExcerptRange {
13348 context: Point::new(5, 0)..Point::new(7, 0),
13349 primary: None,
13350 },
13351 ExcerptRange {
13352 context: Point::new(9, 0)..Point::new(10, 4),
13353 primary: None,
13354 },
13355 ],
13356 cx,
13357 );
13358 multibuffer.push_excerpts(
13359 buffer_3.clone(),
13360 [
13361 ExcerptRange {
13362 context: Point::new(0, 0)..Point::new(3, 0),
13363 primary: None,
13364 },
13365 ExcerptRange {
13366 context: Point::new(5, 0)..Point::new(7, 0),
13367 primary: None,
13368 },
13369 ExcerptRange {
13370 context: Point::new(9, 0)..Point::new(10, 4),
13371 primary: None,
13372 },
13373 ],
13374 cx,
13375 );
13376 multibuffer
13377 });
13378
13379 let fs = FakeFs::new(cx.executor());
13380 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13381 let (editor, cx) = cx
13382 .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
13383 editor.update_in(cx, |editor, _window, cx| {
13384 for (buffer, diff_base) in [
13385 (buffer_1.clone(), base_text_1),
13386 (buffer_2.clone(), base_text_2),
13387 (buffer_3.clone(), base_text_3),
13388 ] {
13389 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13390 editor
13391 .buffer
13392 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13393 }
13394 });
13395 cx.executor().run_until_parked();
13396
13397 editor.update_in(cx, |editor, window, cx| {
13398 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}");
13399 editor.select_all(&SelectAll, window, cx);
13400 editor.git_restore(&Default::default(), window, cx);
13401 });
13402 cx.executor().run_until_parked();
13403
13404 // When all ranges are selected, all buffer hunks are reverted.
13405 editor.update(cx, |editor, cx| {
13406 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");
13407 });
13408 buffer_1.update(cx, |buffer, _| {
13409 assert_eq!(buffer.text(), base_text_1);
13410 });
13411 buffer_2.update(cx, |buffer, _| {
13412 assert_eq!(buffer.text(), base_text_2);
13413 });
13414 buffer_3.update(cx, |buffer, _| {
13415 assert_eq!(buffer.text(), base_text_3);
13416 });
13417
13418 editor.update_in(cx, |editor, window, cx| {
13419 editor.undo(&Default::default(), window, cx);
13420 });
13421
13422 editor.update_in(cx, |editor, window, cx| {
13423 editor.change_selections(None, window, cx, |s| {
13424 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
13425 });
13426 editor.git_restore(&Default::default(), window, cx);
13427 });
13428
13429 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
13430 // but not affect buffer_2 and its related excerpts.
13431 editor.update(cx, |editor, cx| {
13432 assert_eq!(
13433 editor.text(cx),
13434 "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}"
13435 );
13436 });
13437 buffer_1.update(cx, |buffer, _| {
13438 assert_eq!(buffer.text(), base_text_1);
13439 });
13440 buffer_2.update(cx, |buffer, _| {
13441 assert_eq!(
13442 buffer.text(),
13443 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
13444 );
13445 });
13446 buffer_3.update(cx, |buffer, _| {
13447 assert_eq!(
13448 buffer.text(),
13449 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
13450 );
13451 });
13452
13453 fn edit_first_char_of_every_line(text: &str) -> String {
13454 text.split('\n')
13455 .map(|line| format!("X{}", &line[1..]))
13456 .collect::<Vec<_>>()
13457 .join("\n")
13458 }
13459}
13460
13461#[gpui::test]
13462async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
13463 init_test(cx, |_| {});
13464
13465 let cols = 4;
13466 let rows = 10;
13467 let sample_text_1 = sample_text(rows, cols, 'a');
13468 assert_eq!(
13469 sample_text_1,
13470 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
13471 );
13472 let sample_text_2 = sample_text(rows, cols, 'l');
13473 assert_eq!(
13474 sample_text_2,
13475 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
13476 );
13477 let sample_text_3 = sample_text(rows, cols, 'v');
13478 assert_eq!(
13479 sample_text_3,
13480 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
13481 );
13482
13483 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
13484 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
13485 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
13486
13487 let multi_buffer = cx.new(|cx| {
13488 let mut multibuffer = MultiBuffer::new(ReadWrite);
13489 multibuffer.push_excerpts(
13490 buffer_1.clone(),
13491 [
13492 ExcerptRange {
13493 context: Point::new(0, 0)..Point::new(3, 0),
13494 primary: None,
13495 },
13496 ExcerptRange {
13497 context: Point::new(5, 0)..Point::new(7, 0),
13498 primary: None,
13499 },
13500 ExcerptRange {
13501 context: Point::new(9, 0)..Point::new(10, 4),
13502 primary: None,
13503 },
13504 ],
13505 cx,
13506 );
13507 multibuffer.push_excerpts(
13508 buffer_2.clone(),
13509 [
13510 ExcerptRange {
13511 context: Point::new(0, 0)..Point::new(3, 0),
13512 primary: None,
13513 },
13514 ExcerptRange {
13515 context: Point::new(5, 0)..Point::new(7, 0),
13516 primary: None,
13517 },
13518 ExcerptRange {
13519 context: Point::new(9, 0)..Point::new(10, 4),
13520 primary: None,
13521 },
13522 ],
13523 cx,
13524 );
13525 multibuffer.push_excerpts(
13526 buffer_3.clone(),
13527 [
13528 ExcerptRange {
13529 context: Point::new(0, 0)..Point::new(3, 0),
13530 primary: None,
13531 },
13532 ExcerptRange {
13533 context: Point::new(5, 0)..Point::new(7, 0),
13534 primary: None,
13535 },
13536 ExcerptRange {
13537 context: Point::new(9, 0)..Point::new(10, 4),
13538 primary: None,
13539 },
13540 ],
13541 cx,
13542 );
13543 multibuffer
13544 });
13545
13546 let fs = FakeFs::new(cx.executor());
13547 fs.insert_tree(
13548 "/a",
13549 json!({
13550 "main.rs": sample_text_1,
13551 "other.rs": sample_text_2,
13552 "lib.rs": sample_text_3,
13553 }),
13554 )
13555 .await;
13556 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13557 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13558 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13559 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
13560 Editor::new(
13561 EditorMode::Full,
13562 multi_buffer,
13563 Some(project.clone()),
13564 window,
13565 cx,
13566 )
13567 });
13568 let multibuffer_item_id = workspace
13569 .update(cx, |workspace, window, cx| {
13570 assert!(
13571 workspace.active_item(cx).is_none(),
13572 "active item should be None before the first item is added"
13573 );
13574 workspace.add_item_to_active_pane(
13575 Box::new(multi_buffer_editor.clone()),
13576 None,
13577 true,
13578 window,
13579 cx,
13580 );
13581 let active_item = workspace
13582 .active_item(cx)
13583 .expect("should have an active item after adding the multi buffer");
13584 assert!(
13585 !active_item.is_singleton(cx),
13586 "A multi buffer was expected to active after adding"
13587 );
13588 active_item.item_id()
13589 })
13590 .unwrap();
13591 cx.executor().run_until_parked();
13592
13593 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13594 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13595 s.select_ranges(Some(1..2))
13596 });
13597 editor.open_excerpts(&OpenExcerpts, window, cx);
13598 });
13599 cx.executor().run_until_parked();
13600 let first_item_id = workspace
13601 .update(cx, |workspace, window, cx| {
13602 let active_item = workspace
13603 .active_item(cx)
13604 .expect("should have an active item after navigating into the 1st buffer");
13605 let first_item_id = active_item.item_id();
13606 assert_ne!(
13607 first_item_id, multibuffer_item_id,
13608 "Should navigate into the 1st buffer and activate it"
13609 );
13610 assert!(
13611 active_item.is_singleton(cx),
13612 "New active item should be a singleton buffer"
13613 );
13614 assert_eq!(
13615 active_item
13616 .act_as::<Editor>(cx)
13617 .expect("should have navigated into an editor for the 1st buffer")
13618 .read(cx)
13619 .text(cx),
13620 sample_text_1
13621 );
13622
13623 workspace
13624 .go_back(workspace.active_pane().downgrade(), window, cx)
13625 .detach_and_log_err(cx);
13626
13627 first_item_id
13628 })
13629 .unwrap();
13630 cx.executor().run_until_parked();
13631 workspace
13632 .update(cx, |workspace, _, cx| {
13633 let active_item = workspace
13634 .active_item(cx)
13635 .expect("should have an active item after navigating back");
13636 assert_eq!(
13637 active_item.item_id(),
13638 multibuffer_item_id,
13639 "Should navigate back to the multi buffer"
13640 );
13641 assert!(!active_item.is_singleton(cx));
13642 })
13643 .unwrap();
13644
13645 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13646 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13647 s.select_ranges(Some(39..40))
13648 });
13649 editor.open_excerpts(&OpenExcerpts, window, cx);
13650 });
13651 cx.executor().run_until_parked();
13652 let second_item_id = workspace
13653 .update(cx, |workspace, window, cx| {
13654 let active_item = workspace
13655 .active_item(cx)
13656 .expect("should have an active item after navigating into the 2nd buffer");
13657 let second_item_id = active_item.item_id();
13658 assert_ne!(
13659 second_item_id, multibuffer_item_id,
13660 "Should navigate away from the multibuffer"
13661 );
13662 assert_ne!(
13663 second_item_id, first_item_id,
13664 "Should navigate into the 2nd buffer and activate it"
13665 );
13666 assert!(
13667 active_item.is_singleton(cx),
13668 "New active item should be a singleton buffer"
13669 );
13670 assert_eq!(
13671 active_item
13672 .act_as::<Editor>(cx)
13673 .expect("should have navigated into an editor")
13674 .read(cx)
13675 .text(cx),
13676 sample_text_2
13677 );
13678
13679 workspace
13680 .go_back(workspace.active_pane().downgrade(), window, cx)
13681 .detach_and_log_err(cx);
13682
13683 second_item_id
13684 })
13685 .unwrap();
13686 cx.executor().run_until_parked();
13687 workspace
13688 .update(cx, |workspace, _, cx| {
13689 let active_item = workspace
13690 .active_item(cx)
13691 .expect("should have an active item after navigating back from the 2nd buffer");
13692 assert_eq!(
13693 active_item.item_id(),
13694 multibuffer_item_id,
13695 "Should navigate back from the 2nd buffer to the multi buffer"
13696 );
13697 assert!(!active_item.is_singleton(cx));
13698 })
13699 .unwrap();
13700
13701 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13702 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13703 s.select_ranges(Some(70..70))
13704 });
13705 editor.open_excerpts(&OpenExcerpts, window, cx);
13706 });
13707 cx.executor().run_until_parked();
13708 workspace
13709 .update(cx, |workspace, window, cx| {
13710 let active_item = workspace
13711 .active_item(cx)
13712 .expect("should have an active item after navigating into the 3rd buffer");
13713 let third_item_id = active_item.item_id();
13714 assert_ne!(
13715 third_item_id, multibuffer_item_id,
13716 "Should navigate into the 3rd buffer and activate it"
13717 );
13718 assert_ne!(third_item_id, first_item_id);
13719 assert_ne!(third_item_id, second_item_id);
13720 assert!(
13721 active_item.is_singleton(cx),
13722 "New active item should be a singleton buffer"
13723 );
13724 assert_eq!(
13725 active_item
13726 .act_as::<Editor>(cx)
13727 .expect("should have navigated into an editor")
13728 .read(cx)
13729 .text(cx),
13730 sample_text_3
13731 );
13732
13733 workspace
13734 .go_back(workspace.active_pane().downgrade(), window, cx)
13735 .detach_and_log_err(cx);
13736 })
13737 .unwrap();
13738 cx.executor().run_until_parked();
13739 workspace
13740 .update(cx, |workspace, _, cx| {
13741 let active_item = workspace
13742 .active_item(cx)
13743 .expect("should have an active item after navigating back from the 3rd buffer");
13744 assert_eq!(
13745 active_item.item_id(),
13746 multibuffer_item_id,
13747 "Should navigate back from the 3rd buffer to the multi buffer"
13748 );
13749 assert!(!active_item.is_singleton(cx));
13750 })
13751 .unwrap();
13752}
13753
13754#[gpui::test]
13755async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13756 init_test(cx, |_| {});
13757
13758 let mut cx = EditorTestContext::new(cx).await;
13759
13760 let diff_base = r#"
13761 use some::mod;
13762
13763 const A: u32 = 42;
13764
13765 fn main() {
13766 println!("hello");
13767
13768 println!("world");
13769 }
13770 "#
13771 .unindent();
13772
13773 cx.set_state(
13774 &r#"
13775 use some::modified;
13776
13777 ˇ
13778 fn main() {
13779 println!("hello there");
13780
13781 println!("around the");
13782 println!("world");
13783 }
13784 "#
13785 .unindent(),
13786 );
13787
13788 cx.set_head_text(&diff_base);
13789 executor.run_until_parked();
13790
13791 cx.update_editor(|editor, window, cx| {
13792 editor.go_to_next_hunk(&GoToHunk, window, cx);
13793 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13794 });
13795 executor.run_until_parked();
13796 cx.assert_state_with_diff(
13797 r#"
13798 use some::modified;
13799
13800
13801 fn main() {
13802 - println!("hello");
13803 + ˇ println!("hello there");
13804
13805 println!("around the");
13806 println!("world");
13807 }
13808 "#
13809 .unindent(),
13810 );
13811
13812 cx.update_editor(|editor, window, cx| {
13813 for _ in 0..2 {
13814 editor.go_to_next_hunk(&GoToHunk, window, cx);
13815 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13816 }
13817 });
13818 executor.run_until_parked();
13819 cx.assert_state_with_diff(
13820 r#"
13821 - use some::mod;
13822 + ˇuse some::modified;
13823
13824
13825 fn main() {
13826 - println!("hello");
13827 + println!("hello there");
13828
13829 + println!("around the");
13830 println!("world");
13831 }
13832 "#
13833 .unindent(),
13834 );
13835
13836 cx.update_editor(|editor, window, cx| {
13837 editor.go_to_next_hunk(&GoToHunk, window, cx);
13838 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13839 });
13840 executor.run_until_parked();
13841 cx.assert_state_with_diff(
13842 r#"
13843 - use some::mod;
13844 + use some::modified;
13845
13846 - const A: u32 = 42;
13847 ˇ
13848 fn main() {
13849 - println!("hello");
13850 + println!("hello there");
13851
13852 + println!("around the");
13853 println!("world");
13854 }
13855 "#
13856 .unindent(),
13857 );
13858
13859 cx.update_editor(|editor, window, cx| {
13860 editor.cancel(&Cancel, window, cx);
13861 });
13862
13863 cx.assert_state_with_diff(
13864 r#"
13865 use some::modified;
13866
13867 ˇ
13868 fn main() {
13869 println!("hello there");
13870
13871 println!("around the");
13872 println!("world");
13873 }
13874 "#
13875 .unindent(),
13876 );
13877}
13878
13879#[gpui::test]
13880async fn test_diff_base_change_with_expanded_diff_hunks(
13881 executor: BackgroundExecutor,
13882 cx: &mut TestAppContext,
13883) {
13884 init_test(cx, |_| {});
13885
13886 let mut cx = EditorTestContext::new(cx).await;
13887
13888 let diff_base = r#"
13889 use some::mod1;
13890 use some::mod2;
13891
13892 const A: u32 = 42;
13893 const B: u32 = 42;
13894 const C: u32 = 42;
13895
13896 fn main() {
13897 println!("hello");
13898
13899 println!("world");
13900 }
13901 "#
13902 .unindent();
13903
13904 cx.set_state(
13905 &r#"
13906 use some::mod2;
13907
13908 const A: u32 = 42;
13909 const C: u32 = 42;
13910
13911 fn main(ˇ) {
13912 //println!("hello");
13913
13914 println!("world");
13915 //
13916 //
13917 }
13918 "#
13919 .unindent(),
13920 );
13921
13922 cx.set_head_text(&diff_base);
13923 executor.run_until_parked();
13924
13925 cx.update_editor(|editor, window, cx| {
13926 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
13927 });
13928 executor.run_until_parked();
13929 cx.assert_state_with_diff(
13930 r#"
13931 - use some::mod1;
13932 use some::mod2;
13933
13934 const A: u32 = 42;
13935 - const B: u32 = 42;
13936 const C: u32 = 42;
13937
13938 fn main(ˇ) {
13939 - println!("hello");
13940 + //println!("hello");
13941
13942 println!("world");
13943 + //
13944 + //
13945 }
13946 "#
13947 .unindent(),
13948 );
13949
13950 cx.set_head_text("new diff base!");
13951 executor.run_until_parked();
13952 cx.assert_state_with_diff(
13953 r#"
13954 - new diff base!
13955 + use some::mod2;
13956 +
13957 + const A: u32 = 42;
13958 + const C: u32 = 42;
13959 +
13960 + fn main(ˇ) {
13961 + //println!("hello");
13962 +
13963 + println!("world");
13964 + //
13965 + //
13966 + }
13967 "#
13968 .unindent(),
13969 );
13970}
13971
13972#[gpui::test]
13973async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
13974 init_test(cx, |_| {});
13975
13976 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13977 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13978 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13979 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13980 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
13981 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
13982
13983 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
13984 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
13985 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
13986
13987 let multi_buffer = cx.new(|cx| {
13988 let mut multibuffer = MultiBuffer::new(ReadWrite);
13989 multibuffer.push_excerpts(
13990 buffer_1.clone(),
13991 [
13992 ExcerptRange {
13993 context: Point::new(0, 0)..Point::new(3, 0),
13994 primary: None,
13995 },
13996 ExcerptRange {
13997 context: Point::new(5, 0)..Point::new(7, 0),
13998 primary: None,
13999 },
14000 ExcerptRange {
14001 context: Point::new(9, 0)..Point::new(10, 3),
14002 primary: None,
14003 },
14004 ],
14005 cx,
14006 );
14007 multibuffer.push_excerpts(
14008 buffer_2.clone(),
14009 [
14010 ExcerptRange {
14011 context: Point::new(0, 0)..Point::new(3, 0),
14012 primary: None,
14013 },
14014 ExcerptRange {
14015 context: Point::new(5, 0)..Point::new(7, 0),
14016 primary: None,
14017 },
14018 ExcerptRange {
14019 context: Point::new(9, 0)..Point::new(10, 3),
14020 primary: None,
14021 },
14022 ],
14023 cx,
14024 );
14025 multibuffer.push_excerpts(
14026 buffer_3.clone(),
14027 [
14028 ExcerptRange {
14029 context: Point::new(0, 0)..Point::new(3, 0),
14030 primary: None,
14031 },
14032 ExcerptRange {
14033 context: Point::new(5, 0)..Point::new(7, 0),
14034 primary: None,
14035 },
14036 ExcerptRange {
14037 context: Point::new(9, 0)..Point::new(10, 3),
14038 primary: None,
14039 },
14040 ],
14041 cx,
14042 );
14043 multibuffer
14044 });
14045
14046 let editor =
14047 cx.add_window(|window, cx| Editor::new(EditorMode::Full, multi_buffer, None, window, cx));
14048 editor
14049 .update(cx, |editor, _window, cx| {
14050 for (buffer, diff_base) in [
14051 (buffer_1.clone(), file_1_old),
14052 (buffer_2.clone(), file_2_old),
14053 (buffer_3.clone(), file_3_old),
14054 ] {
14055 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
14056 editor
14057 .buffer
14058 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
14059 }
14060 })
14061 .unwrap();
14062
14063 let mut cx = EditorTestContext::for_editor(editor, cx).await;
14064 cx.run_until_parked();
14065
14066 cx.assert_editor_state(
14067 &"
14068 ˇaaa
14069 ccc
14070 ddd
14071
14072 ggg
14073 hhh
14074
14075
14076 lll
14077 mmm
14078 NNN
14079
14080 qqq
14081 rrr
14082
14083 uuu
14084 111
14085 222
14086 333
14087
14088 666
14089 777
14090
14091 000
14092 !!!"
14093 .unindent(),
14094 );
14095
14096 cx.update_editor(|editor, window, cx| {
14097 editor.select_all(&SelectAll, window, cx);
14098 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
14099 });
14100 cx.executor().run_until_parked();
14101
14102 cx.assert_state_with_diff(
14103 "
14104 «aaa
14105 - bbb
14106 ccc
14107 ddd
14108
14109 ggg
14110 hhh
14111
14112
14113 lll
14114 mmm
14115 - nnn
14116 + NNN
14117
14118 qqq
14119 rrr
14120
14121 uuu
14122 111
14123 222
14124 333
14125
14126 + 666
14127 777
14128
14129 000
14130 !!!ˇ»"
14131 .unindent(),
14132 );
14133}
14134
14135#[gpui::test]
14136async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
14137 init_test(cx, |_| {});
14138
14139 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
14140 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
14141
14142 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
14143 let multi_buffer = cx.new(|cx| {
14144 let mut multibuffer = MultiBuffer::new(ReadWrite);
14145 multibuffer.push_excerpts(
14146 buffer.clone(),
14147 [
14148 ExcerptRange {
14149 context: Point::new(0, 0)..Point::new(2, 0),
14150 primary: None,
14151 },
14152 ExcerptRange {
14153 context: Point::new(4, 0)..Point::new(7, 0),
14154 primary: None,
14155 },
14156 ExcerptRange {
14157 context: Point::new(9, 0)..Point::new(10, 0),
14158 primary: None,
14159 },
14160 ],
14161 cx,
14162 );
14163 multibuffer
14164 });
14165
14166 let editor =
14167 cx.add_window(|window, cx| Editor::new(EditorMode::Full, multi_buffer, None, window, cx));
14168 editor
14169 .update(cx, |editor, _window, cx| {
14170 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
14171 editor
14172 .buffer
14173 .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
14174 })
14175 .unwrap();
14176
14177 let mut cx = EditorTestContext::for_editor(editor, cx).await;
14178 cx.run_until_parked();
14179
14180 cx.update_editor(|editor, window, cx| {
14181 editor.expand_all_diff_hunks(&Default::default(), window, cx)
14182 });
14183 cx.executor().run_until_parked();
14184
14185 // When the start of a hunk coincides with the start of its excerpt,
14186 // the hunk is expanded. When the start of a a hunk is earlier than
14187 // the start of its excerpt, the hunk is not expanded.
14188 cx.assert_state_with_diff(
14189 "
14190 ˇaaa
14191 - bbb
14192 + BBB
14193
14194 - ddd
14195 - eee
14196 + DDD
14197 + EEE
14198 fff
14199
14200 iii
14201 "
14202 .unindent(),
14203 );
14204}
14205
14206#[gpui::test]
14207async fn test_edits_around_expanded_insertion_hunks(
14208 executor: BackgroundExecutor,
14209 cx: &mut TestAppContext,
14210) {
14211 init_test(cx, |_| {});
14212
14213 let mut cx = EditorTestContext::new(cx).await;
14214
14215 let diff_base = r#"
14216 use some::mod1;
14217 use some::mod2;
14218
14219 const A: u32 = 42;
14220
14221 fn main() {
14222 println!("hello");
14223
14224 println!("world");
14225 }
14226 "#
14227 .unindent();
14228 executor.run_until_parked();
14229 cx.set_state(
14230 &r#"
14231 use some::mod1;
14232 use some::mod2;
14233
14234 const A: u32 = 42;
14235 const B: u32 = 42;
14236 const C: u32 = 42;
14237 ˇ
14238
14239 fn main() {
14240 println!("hello");
14241
14242 println!("world");
14243 }
14244 "#
14245 .unindent(),
14246 );
14247
14248 cx.set_head_text(&diff_base);
14249 executor.run_until_parked();
14250
14251 cx.update_editor(|editor, window, cx| {
14252 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14253 });
14254 executor.run_until_parked();
14255
14256 cx.assert_state_with_diff(
14257 r#"
14258 use some::mod1;
14259 use some::mod2;
14260
14261 const A: u32 = 42;
14262 + const B: u32 = 42;
14263 + const C: u32 = 42;
14264 + ˇ
14265
14266 fn main() {
14267 println!("hello");
14268
14269 println!("world");
14270 }
14271 "#
14272 .unindent(),
14273 );
14274
14275 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
14276 executor.run_until_parked();
14277
14278 cx.assert_state_with_diff(
14279 r#"
14280 use some::mod1;
14281 use some::mod2;
14282
14283 const A: u32 = 42;
14284 + const B: u32 = 42;
14285 + const C: u32 = 42;
14286 + const D: u32 = 42;
14287 + ˇ
14288
14289 fn main() {
14290 println!("hello");
14291
14292 println!("world");
14293 }
14294 "#
14295 .unindent(),
14296 );
14297
14298 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
14299 executor.run_until_parked();
14300
14301 cx.assert_state_with_diff(
14302 r#"
14303 use some::mod1;
14304 use some::mod2;
14305
14306 const A: u32 = 42;
14307 + const B: u32 = 42;
14308 + const C: u32 = 42;
14309 + const D: u32 = 42;
14310 + const E: u32 = 42;
14311 + ˇ
14312
14313 fn main() {
14314 println!("hello");
14315
14316 println!("world");
14317 }
14318 "#
14319 .unindent(),
14320 );
14321
14322 cx.update_editor(|editor, window, cx| {
14323 editor.delete_line(&DeleteLine, window, cx);
14324 });
14325 executor.run_until_parked();
14326
14327 cx.assert_state_with_diff(
14328 r#"
14329 use some::mod1;
14330 use some::mod2;
14331
14332 const A: u32 = 42;
14333 + const B: u32 = 42;
14334 + const C: u32 = 42;
14335 + const D: u32 = 42;
14336 + const E: u32 = 42;
14337 ˇ
14338 fn main() {
14339 println!("hello");
14340
14341 println!("world");
14342 }
14343 "#
14344 .unindent(),
14345 );
14346
14347 cx.update_editor(|editor, window, cx| {
14348 editor.move_up(&MoveUp, window, cx);
14349 editor.delete_line(&DeleteLine, window, cx);
14350 editor.move_up(&MoveUp, window, cx);
14351 editor.delete_line(&DeleteLine, window, cx);
14352 editor.move_up(&MoveUp, window, cx);
14353 editor.delete_line(&DeleteLine, window, cx);
14354 });
14355 executor.run_until_parked();
14356 cx.assert_state_with_diff(
14357 r#"
14358 use some::mod1;
14359 use some::mod2;
14360
14361 const A: u32 = 42;
14362 + const B: u32 = 42;
14363 ˇ
14364 fn main() {
14365 println!("hello");
14366
14367 println!("world");
14368 }
14369 "#
14370 .unindent(),
14371 );
14372
14373 cx.update_editor(|editor, window, cx| {
14374 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
14375 editor.delete_line(&DeleteLine, window, cx);
14376 });
14377 executor.run_until_parked();
14378 cx.assert_state_with_diff(
14379 r#"
14380 ˇ
14381 fn main() {
14382 println!("hello");
14383
14384 println!("world");
14385 }
14386 "#
14387 .unindent(),
14388 );
14389}
14390
14391#[gpui::test]
14392async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
14393 init_test(cx, |_| {});
14394
14395 let mut cx = EditorTestContext::new(cx).await;
14396 cx.set_head_text(indoc! { "
14397 one
14398 two
14399 three
14400 four
14401 five
14402 "
14403 });
14404 cx.set_state(indoc! { "
14405 one
14406 ˇthree
14407 five
14408 "});
14409 cx.run_until_parked();
14410 cx.update_editor(|editor, window, cx| {
14411 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14412 });
14413 cx.assert_state_with_diff(
14414 indoc! { "
14415 one
14416 - two
14417 ˇthree
14418 - four
14419 five
14420 "}
14421 .to_string(),
14422 );
14423 cx.update_editor(|editor, window, cx| {
14424 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14425 });
14426
14427 cx.assert_state_with_diff(
14428 indoc! { "
14429 one
14430 ˇthree
14431 five
14432 "}
14433 .to_string(),
14434 );
14435
14436 cx.set_state(indoc! { "
14437 one
14438 ˇTWO
14439 three
14440 four
14441 five
14442 "});
14443 cx.run_until_parked();
14444 cx.update_editor(|editor, window, cx| {
14445 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14446 });
14447
14448 cx.assert_state_with_diff(
14449 indoc! { "
14450 one
14451 - two
14452 + ˇTWO
14453 three
14454 four
14455 five
14456 "}
14457 .to_string(),
14458 );
14459 cx.update_editor(|editor, window, cx| {
14460 editor.move_up(&Default::default(), window, cx);
14461 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14462 });
14463 cx.assert_state_with_diff(
14464 indoc! { "
14465 one
14466 ˇTWO
14467 three
14468 four
14469 five
14470 "}
14471 .to_string(),
14472 );
14473}
14474
14475#[gpui::test]
14476async fn test_edits_around_expanded_deletion_hunks(
14477 executor: BackgroundExecutor,
14478 cx: &mut TestAppContext,
14479) {
14480 init_test(cx, |_| {});
14481
14482 let mut cx = EditorTestContext::new(cx).await;
14483
14484 let diff_base = r#"
14485 use some::mod1;
14486 use some::mod2;
14487
14488 const A: u32 = 42;
14489 const B: u32 = 42;
14490 const C: u32 = 42;
14491
14492
14493 fn main() {
14494 println!("hello");
14495
14496 println!("world");
14497 }
14498 "#
14499 .unindent();
14500 executor.run_until_parked();
14501 cx.set_state(
14502 &r#"
14503 use some::mod1;
14504 use some::mod2;
14505
14506 ˇconst B: u32 = 42;
14507 const C: u32 = 42;
14508
14509
14510 fn main() {
14511 println!("hello");
14512
14513 println!("world");
14514 }
14515 "#
14516 .unindent(),
14517 );
14518
14519 cx.set_head_text(&diff_base);
14520 executor.run_until_parked();
14521
14522 cx.update_editor(|editor, window, cx| {
14523 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14524 });
14525 executor.run_until_parked();
14526
14527 cx.assert_state_with_diff(
14528 r#"
14529 use some::mod1;
14530 use some::mod2;
14531
14532 - const A: u32 = 42;
14533 ˇconst B: u32 = 42;
14534 const C: u32 = 42;
14535
14536
14537 fn main() {
14538 println!("hello");
14539
14540 println!("world");
14541 }
14542 "#
14543 .unindent(),
14544 );
14545
14546 cx.update_editor(|editor, window, cx| {
14547 editor.delete_line(&DeleteLine, window, cx);
14548 });
14549 executor.run_until_parked();
14550 cx.assert_state_with_diff(
14551 r#"
14552 use some::mod1;
14553 use some::mod2;
14554
14555 - const A: u32 = 42;
14556 - const B: u32 = 42;
14557 ˇconst C: u32 = 42;
14558
14559
14560 fn main() {
14561 println!("hello");
14562
14563 println!("world");
14564 }
14565 "#
14566 .unindent(),
14567 );
14568
14569 cx.update_editor(|editor, window, cx| {
14570 editor.delete_line(&DeleteLine, window, cx);
14571 });
14572 executor.run_until_parked();
14573 cx.assert_state_with_diff(
14574 r#"
14575 use some::mod1;
14576 use some::mod2;
14577
14578 - const A: u32 = 42;
14579 - const B: u32 = 42;
14580 - const C: u32 = 42;
14581 ˇ
14582
14583 fn main() {
14584 println!("hello");
14585
14586 println!("world");
14587 }
14588 "#
14589 .unindent(),
14590 );
14591
14592 cx.update_editor(|editor, window, cx| {
14593 editor.handle_input("replacement", window, cx);
14594 });
14595 executor.run_until_parked();
14596 cx.assert_state_with_diff(
14597 r#"
14598 use some::mod1;
14599 use some::mod2;
14600
14601 - const A: u32 = 42;
14602 - const B: u32 = 42;
14603 - const C: u32 = 42;
14604 -
14605 + replacementˇ
14606
14607 fn main() {
14608 println!("hello");
14609
14610 println!("world");
14611 }
14612 "#
14613 .unindent(),
14614 );
14615}
14616
14617#[gpui::test]
14618async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14619 init_test(cx, |_| {});
14620
14621 let mut cx = EditorTestContext::new(cx).await;
14622
14623 let base_text = r#"
14624 one
14625 two
14626 three
14627 four
14628 five
14629 "#
14630 .unindent();
14631 executor.run_until_parked();
14632 cx.set_state(
14633 &r#"
14634 one
14635 two
14636 fˇour
14637 five
14638 "#
14639 .unindent(),
14640 );
14641
14642 cx.set_head_text(&base_text);
14643 executor.run_until_parked();
14644
14645 cx.update_editor(|editor, window, cx| {
14646 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14647 });
14648 executor.run_until_parked();
14649
14650 cx.assert_state_with_diff(
14651 r#"
14652 one
14653 two
14654 - three
14655 fˇour
14656 five
14657 "#
14658 .unindent(),
14659 );
14660
14661 cx.update_editor(|editor, window, cx| {
14662 editor.backspace(&Backspace, window, cx);
14663 editor.backspace(&Backspace, window, cx);
14664 });
14665 executor.run_until_parked();
14666 cx.assert_state_with_diff(
14667 r#"
14668 one
14669 two
14670 - threeˇ
14671 - four
14672 + our
14673 five
14674 "#
14675 .unindent(),
14676 );
14677}
14678
14679#[gpui::test]
14680async fn test_edit_after_expanded_modification_hunk(
14681 executor: BackgroundExecutor,
14682 cx: &mut TestAppContext,
14683) {
14684 init_test(cx, |_| {});
14685
14686 let mut cx = EditorTestContext::new(cx).await;
14687
14688 let diff_base = r#"
14689 use some::mod1;
14690 use some::mod2;
14691
14692 const A: u32 = 42;
14693 const B: u32 = 42;
14694 const C: u32 = 42;
14695 const D: u32 = 42;
14696
14697
14698 fn main() {
14699 println!("hello");
14700
14701 println!("world");
14702 }"#
14703 .unindent();
14704
14705 cx.set_state(
14706 &r#"
14707 use some::mod1;
14708 use some::mod2;
14709
14710 const A: u32 = 42;
14711 const B: u32 = 42;
14712 const C: u32 = 43ˇ
14713 const D: u32 = 42;
14714
14715
14716 fn main() {
14717 println!("hello");
14718
14719 println!("world");
14720 }"#
14721 .unindent(),
14722 );
14723
14724 cx.set_head_text(&diff_base);
14725 executor.run_until_parked();
14726 cx.update_editor(|editor, window, cx| {
14727 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14728 });
14729 executor.run_until_parked();
14730
14731 cx.assert_state_with_diff(
14732 r#"
14733 use some::mod1;
14734 use some::mod2;
14735
14736 const A: u32 = 42;
14737 const B: u32 = 42;
14738 - const C: u32 = 42;
14739 + const C: u32 = 43ˇ
14740 const D: u32 = 42;
14741
14742
14743 fn main() {
14744 println!("hello");
14745
14746 println!("world");
14747 }"#
14748 .unindent(),
14749 );
14750
14751 cx.update_editor(|editor, window, cx| {
14752 editor.handle_input("\nnew_line\n", window, cx);
14753 });
14754 executor.run_until_parked();
14755
14756 cx.assert_state_with_diff(
14757 r#"
14758 use some::mod1;
14759 use some::mod2;
14760
14761 const A: u32 = 42;
14762 const B: u32 = 42;
14763 - const C: u32 = 42;
14764 + const C: u32 = 43
14765 + new_line
14766 + ˇ
14767 const D: u32 = 42;
14768
14769
14770 fn main() {
14771 println!("hello");
14772
14773 println!("world");
14774 }"#
14775 .unindent(),
14776 );
14777}
14778
14779#[gpui::test]
14780async fn test_stage_and_unstage_added_file_hunk(
14781 executor: BackgroundExecutor,
14782 cx: &mut TestAppContext,
14783) {
14784 init_test(cx, |_| {});
14785
14786 let mut cx = EditorTestContext::new(cx).await;
14787 cx.update_editor(|editor, _, cx| {
14788 editor.set_expand_all_diff_hunks(cx);
14789 });
14790
14791 let working_copy = r#"
14792 ˇfn main() {
14793 println!("hello, world!");
14794 }
14795 "#
14796 .unindent();
14797
14798 cx.set_state(&working_copy);
14799 executor.run_until_parked();
14800
14801 cx.assert_state_with_diff(
14802 r#"
14803 + ˇfn main() {
14804 + println!("hello, world!");
14805 + }
14806 "#
14807 .unindent(),
14808 );
14809 cx.assert_index_text(None);
14810
14811 cx.update_editor(|editor, window, cx| {
14812 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14813 });
14814 executor.run_until_parked();
14815 cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
14816 cx.assert_state_with_diff(
14817 r#"
14818 + ˇfn main() {
14819 + println!("hello, world!");
14820 + }
14821 "#
14822 .unindent(),
14823 );
14824
14825 cx.update_editor(|editor, window, cx| {
14826 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14827 });
14828 executor.run_until_parked();
14829 cx.assert_index_text(None);
14830}
14831
14832async fn setup_indent_guides_editor(
14833 text: &str,
14834 cx: &mut TestAppContext,
14835) -> (BufferId, EditorTestContext) {
14836 init_test(cx, |_| {});
14837
14838 let mut cx = EditorTestContext::new(cx).await;
14839
14840 let buffer_id = cx.update_editor(|editor, window, cx| {
14841 editor.set_text(text, window, cx);
14842 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
14843
14844 buffer_ids[0]
14845 });
14846
14847 (buffer_id, cx)
14848}
14849
14850fn assert_indent_guides(
14851 range: Range<u32>,
14852 expected: Vec<IndentGuide>,
14853 active_indices: Option<Vec<usize>>,
14854 cx: &mut EditorTestContext,
14855) {
14856 let indent_guides = cx.update_editor(|editor, window, cx| {
14857 let snapshot = editor.snapshot(window, cx).display_snapshot;
14858 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
14859 editor,
14860 MultiBufferRow(range.start)..MultiBufferRow(range.end),
14861 true,
14862 &snapshot,
14863 cx,
14864 );
14865
14866 indent_guides.sort_by(|a, b| {
14867 a.depth.cmp(&b.depth).then(
14868 a.start_row
14869 .cmp(&b.start_row)
14870 .then(a.end_row.cmp(&b.end_row)),
14871 )
14872 });
14873 indent_guides
14874 });
14875
14876 if let Some(expected) = active_indices {
14877 let active_indices = cx.update_editor(|editor, window, cx| {
14878 let snapshot = editor.snapshot(window, cx).display_snapshot;
14879 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
14880 });
14881
14882 assert_eq!(
14883 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
14884 expected,
14885 "Active indent guide indices do not match"
14886 );
14887 }
14888
14889 assert_eq!(indent_guides, expected, "Indent guides do not match");
14890}
14891
14892fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
14893 IndentGuide {
14894 buffer_id,
14895 start_row: MultiBufferRow(start_row),
14896 end_row: MultiBufferRow(end_row),
14897 depth,
14898 tab_size: 4,
14899 settings: IndentGuideSettings {
14900 enabled: true,
14901 line_width: 1,
14902 active_line_width: 1,
14903 ..Default::default()
14904 },
14905 }
14906}
14907
14908#[gpui::test]
14909async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
14910 let (buffer_id, mut cx) = setup_indent_guides_editor(
14911 &"
14912 fn main() {
14913 let a = 1;
14914 }"
14915 .unindent(),
14916 cx,
14917 )
14918 .await;
14919
14920 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14921}
14922
14923#[gpui::test]
14924async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
14925 let (buffer_id, mut cx) = setup_indent_guides_editor(
14926 &"
14927 fn main() {
14928 let a = 1;
14929 let b = 2;
14930 }"
14931 .unindent(),
14932 cx,
14933 )
14934 .await;
14935
14936 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
14937}
14938
14939#[gpui::test]
14940async fn test_indent_guide_nested(cx: &mut TestAppContext) {
14941 let (buffer_id, mut cx) = setup_indent_guides_editor(
14942 &"
14943 fn main() {
14944 let a = 1;
14945 if a == 3 {
14946 let b = 2;
14947 } else {
14948 let c = 3;
14949 }
14950 }"
14951 .unindent(),
14952 cx,
14953 )
14954 .await;
14955
14956 assert_indent_guides(
14957 0..8,
14958 vec![
14959 indent_guide(buffer_id, 1, 6, 0),
14960 indent_guide(buffer_id, 3, 3, 1),
14961 indent_guide(buffer_id, 5, 5, 1),
14962 ],
14963 None,
14964 &mut cx,
14965 );
14966}
14967
14968#[gpui::test]
14969async fn test_indent_guide_tab(cx: &mut TestAppContext) {
14970 let (buffer_id, mut cx) = setup_indent_guides_editor(
14971 &"
14972 fn main() {
14973 let a = 1;
14974 let b = 2;
14975 let c = 3;
14976 }"
14977 .unindent(),
14978 cx,
14979 )
14980 .await;
14981
14982 assert_indent_guides(
14983 0..5,
14984 vec![
14985 indent_guide(buffer_id, 1, 3, 0),
14986 indent_guide(buffer_id, 2, 2, 1),
14987 ],
14988 None,
14989 &mut cx,
14990 );
14991}
14992
14993#[gpui::test]
14994async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
14995 let (buffer_id, mut cx) = setup_indent_guides_editor(
14996 &"
14997 fn main() {
14998 let a = 1;
14999
15000 let c = 3;
15001 }"
15002 .unindent(),
15003 cx,
15004 )
15005 .await;
15006
15007 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
15008}
15009
15010#[gpui::test]
15011async fn test_indent_guide_complex(cx: &mut TestAppContext) {
15012 let (buffer_id, mut cx) = setup_indent_guides_editor(
15013 &"
15014 fn main() {
15015 let a = 1;
15016
15017 let c = 3;
15018
15019 if a == 3 {
15020 let b = 2;
15021 } else {
15022 let c = 3;
15023 }
15024 }"
15025 .unindent(),
15026 cx,
15027 )
15028 .await;
15029
15030 assert_indent_guides(
15031 0..11,
15032 vec![
15033 indent_guide(buffer_id, 1, 9, 0),
15034 indent_guide(buffer_id, 6, 6, 1),
15035 indent_guide(buffer_id, 8, 8, 1),
15036 ],
15037 None,
15038 &mut cx,
15039 );
15040}
15041
15042#[gpui::test]
15043async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
15044 let (buffer_id, mut cx) = setup_indent_guides_editor(
15045 &"
15046 fn main() {
15047 let a = 1;
15048
15049 let c = 3;
15050
15051 if a == 3 {
15052 let b = 2;
15053 } else {
15054 let c = 3;
15055 }
15056 }"
15057 .unindent(),
15058 cx,
15059 )
15060 .await;
15061
15062 assert_indent_guides(
15063 1..11,
15064 vec![
15065 indent_guide(buffer_id, 1, 9, 0),
15066 indent_guide(buffer_id, 6, 6, 1),
15067 indent_guide(buffer_id, 8, 8, 1),
15068 ],
15069 None,
15070 &mut cx,
15071 );
15072}
15073
15074#[gpui::test]
15075async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
15076 let (buffer_id, mut cx) = setup_indent_guides_editor(
15077 &"
15078 fn main() {
15079 let a = 1;
15080
15081 let c = 3;
15082
15083 if a == 3 {
15084 let b = 2;
15085 } else {
15086 let c = 3;
15087 }
15088 }"
15089 .unindent(),
15090 cx,
15091 )
15092 .await;
15093
15094 assert_indent_guides(
15095 1..10,
15096 vec![
15097 indent_guide(buffer_id, 1, 9, 0),
15098 indent_guide(buffer_id, 6, 6, 1),
15099 indent_guide(buffer_id, 8, 8, 1),
15100 ],
15101 None,
15102 &mut cx,
15103 );
15104}
15105
15106#[gpui::test]
15107async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
15108 let (buffer_id, mut cx) = setup_indent_guides_editor(
15109 &"
15110 block1
15111 block2
15112 block3
15113 block4
15114 block2
15115 block1
15116 block1"
15117 .unindent(),
15118 cx,
15119 )
15120 .await;
15121
15122 assert_indent_guides(
15123 1..10,
15124 vec![
15125 indent_guide(buffer_id, 1, 4, 0),
15126 indent_guide(buffer_id, 2, 3, 1),
15127 indent_guide(buffer_id, 3, 3, 2),
15128 ],
15129 None,
15130 &mut cx,
15131 );
15132}
15133
15134#[gpui::test]
15135async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
15136 let (buffer_id, mut cx) = setup_indent_guides_editor(
15137 &"
15138 block1
15139 block2
15140 block3
15141
15142 block1
15143 block1"
15144 .unindent(),
15145 cx,
15146 )
15147 .await;
15148
15149 assert_indent_guides(
15150 0..6,
15151 vec![
15152 indent_guide(buffer_id, 1, 2, 0),
15153 indent_guide(buffer_id, 2, 2, 1),
15154 ],
15155 None,
15156 &mut cx,
15157 );
15158}
15159
15160#[gpui::test]
15161async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
15162 let (buffer_id, mut cx) = setup_indent_guides_editor(
15163 &"
15164 block1
15165
15166
15167
15168 block2
15169 "
15170 .unindent(),
15171 cx,
15172 )
15173 .await;
15174
15175 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
15176}
15177
15178#[gpui::test]
15179async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
15180 let (buffer_id, mut cx) = setup_indent_guides_editor(
15181 &"
15182 def a:
15183 \tb = 3
15184 \tif True:
15185 \t\tc = 4
15186 \t\td = 5
15187 \tprint(b)
15188 "
15189 .unindent(),
15190 cx,
15191 )
15192 .await;
15193
15194 assert_indent_guides(
15195 0..6,
15196 vec![
15197 indent_guide(buffer_id, 1, 6, 0),
15198 indent_guide(buffer_id, 3, 4, 1),
15199 ],
15200 None,
15201 &mut cx,
15202 );
15203}
15204
15205#[gpui::test]
15206async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
15207 let (buffer_id, mut cx) = setup_indent_guides_editor(
15208 &"
15209 fn main() {
15210 let a = 1;
15211 }"
15212 .unindent(),
15213 cx,
15214 )
15215 .await;
15216
15217 cx.update_editor(|editor, window, cx| {
15218 editor.change_selections(None, window, cx, |s| {
15219 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15220 });
15221 });
15222
15223 assert_indent_guides(
15224 0..3,
15225 vec![indent_guide(buffer_id, 1, 1, 0)],
15226 Some(vec![0]),
15227 &mut cx,
15228 );
15229}
15230
15231#[gpui::test]
15232async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
15233 let (buffer_id, mut cx) = setup_indent_guides_editor(
15234 &"
15235 fn main() {
15236 if 1 == 2 {
15237 let a = 1;
15238 }
15239 }"
15240 .unindent(),
15241 cx,
15242 )
15243 .await;
15244
15245 cx.update_editor(|editor, window, cx| {
15246 editor.change_selections(None, window, cx, |s| {
15247 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15248 });
15249 });
15250
15251 assert_indent_guides(
15252 0..4,
15253 vec![
15254 indent_guide(buffer_id, 1, 3, 0),
15255 indent_guide(buffer_id, 2, 2, 1),
15256 ],
15257 Some(vec![1]),
15258 &mut cx,
15259 );
15260
15261 cx.update_editor(|editor, window, cx| {
15262 editor.change_selections(None, window, cx, |s| {
15263 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15264 });
15265 });
15266
15267 assert_indent_guides(
15268 0..4,
15269 vec![
15270 indent_guide(buffer_id, 1, 3, 0),
15271 indent_guide(buffer_id, 2, 2, 1),
15272 ],
15273 Some(vec![1]),
15274 &mut cx,
15275 );
15276
15277 cx.update_editor(|editor, window, cx| {
15278 editor.change_selections(None, window, cx, |s| {
15279 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
15280 });
15281 });
15282
15283 assert_indent_guides(
15284 0..4,
15285 vec![
15286 indent_guide(buffer_id, 1, 3, 0),
15287 indent_guide(buffer_id, 2, 2, 1),
15288 ],
15289 Some(vec![0]),
15290 &mut cx,
15291 );
15292}
15293
15294#[gpui::test]
15295async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
15296 let (buffer_id, mut cx) = setup_indent_guides_editor(
15297 &"
15298 fn main() {
15299 let a = 1;
15300
15301 let b = 2;
15302 }"
15303 .unindent(),
15304 cx,
15305 )
15306 .await;
15307
15308 cx.update_editor(|editor, window, cx| {
15309 editor.change_selections(None, window, cx, |s| {
15310 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15311 });
15312 });
15313
15314 assert_indent_guides(
15315 0..5,
15316 vec![indent_guide(buffer_id, 1, 3, 0)],
15317 Some(vec![0]),
15318 &mut cx,
15319 );
15320}
15321
15322#[gpui::test]
15323async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
15324 let (buffer_id, mut cx) = setup_indent_guides_editor(
15325 &"
15326 def m:
15327 a = 1
15328 pass"
15329 .unindent(),
15330 cx,
15331 )
15332 .await;
15333
15334 cx.update_editor(|editor, window, cx| {
15335 editor.change_selections(None, window, cx, |s| {
15336 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15337 });
15338 });
15339
15340 assert_indent_guides(
15341 0..3,
15342 vec![indent_guide(buffer_id, 1, 2, 0)],
15343 Some(vec![0]),
15344 &mut cx,
15345 );
15346}
15347
15348#[gpui::test]
15349async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
15350 init_test(cx, |_| {});
15351 let mut cx = EditorTestContext::new(cx).await;
15352 let text = indoc! {
15353 "
15354 impl A {
15355 fn b() {
15356 0;
15357 3;
15358 5;
15359 6;
15360 7;
15361 }
15362 }
15363 "
15364 };
15365 let base_text = indoc! {
15366 "
15367 impl A {
15368 fn b() {
15369 0;
15370 1;
15371 2;
15372 3;
15373 4;
15374 }
15375 fn c() {
15376 5;
15377 6;
15378 7;
15379 }
15380 }
15381 "
15382 };
15383
15384 cx.update_editor(|editor, window, cx| {
15385 editor.set_text(text, window, cx);
15386
15387 editor.buffer().update(cx, |multibuffer, cx| {
15388 let buffer = multibuffer.as_singleton().unwrap();
15389 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
15390
15391 multibuffer.set_all_diff_hunks_expanded(cx);
15392 multibuffer.add_diff(diff, cx);
15393
15394 buffer.read(cx).remote_id()
15395 })
15396 });
15397 cx.run_until_parked();
15398
15399 cx.assert_state_with_diff(
15400 indoc! { "
15401 impl A {
15402 fn b() {
15403 0;
15404 - 1;
15405 - 2;
15406 3;
15407 - 4;
15408 - }
15409 - fn c() {
15410 5;
15411 6;
15412 7;
15413 }
15414 }
15415 ˇ"
15416 }
15417 .to_string(),
15418 );
15419
15420 let mut actual_guides = cx.update_editor(|editor, window, cx| {
15421 editor
15422 .snapshot(window, cx)
15423 .buffer_snapshot
15424 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
15425 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
15426 .collect::<Vec<_>>()
15427 });
15428 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
15429 assert_eq!(
15430 actual_guides,
15431 vec![
15432 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
15433 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
15434 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
15435 ]
15436 );
15437}
15438
15439#[gpui::test]
15440async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15441 init_test(cx, |_| {});
15442 let mut cx = EditorTestContext::new(cx).await;
15443
15444 let diff_base = r#"
15445 a
15446 b
15447 c
15448 "#
15449 .unindent();
15450
15451 cx.set_state(
15452 &r#"
15453 ˇA
15454 b
15455 C
15456 "#
15457 .unindent(),
15458 );
15459 cx.set_head_text(&diff_base);
15460 cx.update_editor(|editor, window, cx| {
15461 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15462 });
15463 executor.run_until_parked();
15464
15465 let both_hunks_expanded = r#"
15466 - a
15467 + ˇA
15468 b
15469 - c
15470 + C
15471 "#
15472 .unindent();
15473
15474 cx.assert_state_with_diff(both_hunks_expanded.clone());
15475
15476 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15477 let snapshot = editor.snapshot(window, cx);
15478 let hunks = editor
15479 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15480 .collect::<Vec<_>>();
15481 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15482 let buffer_id = hunks[0].buffer_id;
15483 hunks
15484 .into_iter()
15485 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15486 .collect::<Vec<_>>()
15487 });
15488 assert_eq!(hunk_ranges.len(), 2);
15489
15490 cx.update_editor(|editor, _, cx| {
15491 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15492 });
15493 executor.run_until_parked();
15494
15495 let second_hunk_expanded = r#"
15496 ˇA
15497 b
15498 - c
15499 + C
15500 "#
15501 .unindent();
15502
15503 cx.assert_state_with_diff(second_hunk_expanded);
15504
15505 cx.update_editor(|editor, _, cx| {
15506 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15507 });
15508 executor.run_until_parked();
15509
15510 cx.assert_state_with_diff(both_hunks_expanded.clone());
15511
15512 cx.update_editor(|editor, _, cx| {
15513 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15514 });
15515 executor.run_until_parked();
15516
15517 let first_hunk_expanded = r#"
15518 - a
15519 + ˇA
15520 b
15521 C
15522 "#
15523 .unindent();
15524
15525 cx.assert_state_with_diff(first_hunk_expanded);
15526
15527 cx.update_editor(|editor, _, cx| {
15528 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15529 });
15530 executor.run_until_parked();
15531
15532 cx.assert_state_with_diff(both_hunks_expanded);
15533
15534 cx.set_state(
15535 &r#"
15536 ˇA
15537 b
15538 "#
15539 .unindent(),
15540 );
15541 cx.run_until_parked();
15542
15543 // TODO this cursor position seems bad
15544 cx.assert_state_with_diff(
15545 r#"
15546 - ˇa
15547 + A
15548 b
15549 "#
15550 .unindent(),
15551 );
15552
15553 cx.update_editor(|editor, window, cx| {
15554 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15555 });
15556
15557 cx.assert_state_with_diff(
15558 r#"
15559 - ˇa
15560 + A
15561 b
15562 - c
15563 "#
15564 .unindent(),
15565 );
15566
15567 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15568 let snapshot = editor.snapshot(window, cx);
15569 let hunks = editor
15570 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15571 .collect::<Vec<_>>();
15572 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15573 let buffer_id = hunks[0].buffer_id;
15574 hunks
15575 .into_iter()
15576 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15577 .collect::<Vec<_>>()
15578 });
15579 assert_eq!(hunk_ranges.len(), 2);
15580
15581 cx.update_editor(|editor, _, cx| {
15582 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15583 });
15584 executor.run_until_parked();
15585
15586 cx.assert_state_with_diff(
15587 r#"
15588 - ˇa
15589 + A
15590 b
15591 "#
15592 .unindent(),
15593 );
15594}
15595
15596#[gpui::test]
15597async fn test_toggle_deletion_hunk_at_start_of_file(
15598 executor: BackgroundExecutor,
15599 cx: &mut TestAppContext,
15600) {
15601 init_test(cx, |_| {});
15602 let mut cx = EditorTestContext::new(cx).await;
15603
15604 let diff_base = r#"
15605 a
15606 b
15607 c
15608 "#
15609 .unindent();
15610
15611 cx.set_state(
15612 &r#"
15613 ˇb
15614 c
15615 "#
15616 .unindent(),
15617 );
15618 cx.set_head_text(&diff_base);
15619 cx.update_editor(|editor, window, cx| {
15620 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15621 });
15622 executor.run_until_parked();
15623
15624 let hunk_expanded = r#"
15625 - a
15626 ˇb
15627 c
15628 "#
15629 .unindent();
15630
15631 cx.assert_state_with_diff(hunk_expanded.clone());
15632
15633 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15634 let snapshot = editor.snapshot(window, cx);
15635 let hunks = editor
15636 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15637 .collect::<Vec<_>>();
15638 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15639 let buffer_id = hunks[0].buffer_id;
15640 hunks
15641 .into_iter()
15642 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15643 .collect::<Vec<_>>()
15644 });
15645 assert_eq!(hunk_ranges.len(), 1);
15646
15647 cx.update_editor(|editor, _, cx| {
15648 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15649 });
15650 executor.run_until_parked();
15651
15652 let hunk_collapsed = r#"
15653 ˇb
15654 c
15655 "#
15656 .unindent();
15657
15658 cx.assert_state_with_diff(hunk_collapsed);
15659
15660 cx.update_editor(|editor, _, cx| {
15661 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15662 });
15663 executor.run_until_parked();
15664
15665 cx.assert_state_with_diff(hunk_expanded.clone());
15666}
15667
15668#[gpui::test]
15669async fn test_display_diff_hunks(cx: &mut TestAppContext) {
15670 init_test(cx, |_| {});
15671
15672 let fs = FakeFs::new(cx.executor());
15673 fs.insert_tree(
15674 path!("/test"),
15675 json!({
15676 ".git": {},
15677 "file-1": "ONE\n",
15678 "file-2": "TWO\n",
15679 "file-3": "THREE\n",
15680 }),
15681 )
15682 .await;
15683
15684 fs.set_head_for_repo(
15685 path!("/test/.git").as_ref(),
15686 &[
15687 ("file-1".into(), "one\n".into()),
15688 ("file-2".into(), "two\n".into()),
15689 ("file-3".into(), "three\n".into()),
15690 ],
15691 );
15692
15693 let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
15694 let mut buffers = vec![];
15695 for i in 1..=3 {
15696 let buffer = project
15697 .update(cx, |project, cx| {
15698 let path = format!(path!("/test/file-{}"), i);
15699 project.open_local_buffer(path, cx)
15700 })
15701 .await
15702 .unwrap();
15703 buffers.push(buffer);
15704 }
15705
15706 let multibuffer = cx.new(|cx| {
15707 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
15708 multibuffer.set_all_diff_hunks_expanded(cx);
15709 for buffer in &buffers {
15710 let snapshot = buffer.read(cx).snapshot();
15711 multibuffer.set_excerpts_for_path(
15712 PathKey::namespaced("", buffer.read(cx).file().unwrap().path().clone()),
15713 buffer.clone(),
15714 vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
15715 DEFAULT_MULTIBUFFER_CONTEXT,
15716 cx,
15717 );
15718 }
15719 multibuffer
15720 });
15721
15722 let editor = cx.add_window(|window, cx| {
15723 Editor::new(EditorMode::Full, multibuffer, Some(project), window, cx)
15724 });
15725 cx.run_until_parked();
15726
15727 let snapshot = editor
15728 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15729 .unwrap();
15730 let hunks = snapshot
15731 .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
15732 .map(|hunk| match hunk {
15733 DisplayDiffHunk::Unfolded {
15734 display_row_range, ..
15735 } => display_row_range,
15736 DisplayDiffHunk::Folded { .. } => unreachable!(),
15737 })
15738 .collect::<Vec<_>>();
15739 assert_eq!(
15740 hunks,
15741 [
15742 DisplayRow(2)..DisplayRow(4),
15743 DisplayRow(7)..DisplayRow(9),
15744 DisplayRow(12)..DisplayRow(14),
15745 ]
15746 );
15747}
15748
15749#[gpui::test]
15750async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
15751 init_test(cx, |_| {});
15752
15753 let mut cx = EditorTestContext::new(cx).await;
15754 cx.set_head_text(indoc! { "
15755 one
15756 two
15757 three
15758 four
15759 five
15760 "
15761 });
15762 cx.set_index_text(indoc! { "
15763 one
15764 two
15765 three
15766 four
15767 five
15768 "
15769 });
15770 cx.set_state(indoc! {"
15771 one
15772 TWO
15773 ˇTHREE
15774 FOUR
15775 five
15776 "});
15777 cx.run_until_parked();
15778 cx.update_editor(|editor, window, cx| {
15779 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15780 });
15781 cx.run_until_parked();
15782 cx.assert_index_text(Some(indoc! {"
15783 one
15784 TWO
15785 THREE
15786 FOUR
15787 five
15788 "}));
15789 cx.set_state(indoc! { "
15790 one
15791 TWO
15792 ˇTHREE-HUNDRED
15793 FOUR
15794 five
15795 "});
15796 cx.run_until_parked();
15797 cx.update_editor(|editor, window, cx| {
15798 let snapshot = editor.snapshot(window, cx);
15799 let hunks = editor
15800 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15801 .collect::<Vec<_>>();
15802 assert_eq!(hunks.len(), 1);
15803 assert_eq!(
15804 hunks[0].status(),
15805 DiffHunkStatus {
15806 kind: DiffHunkStatusKind::Modified,
15807 secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
15808 }
15809 );
15810
15811 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15812 });
15813 cx.run_until_parked();
15814 cx.assert_index_text(Some(indoc! {"
15815 one
15816 TWO
15817 THREE-HUNDRED
15818 FOUR
15819 five
15820 "}));
15821}
15822
15823#[gpui::test]
15824fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
15825 init_test(cx, |_| {});
15826
15827 let editor = cx.add_window(|window, cx| {
15828 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
15829 build_editor(buffer, window, cx)
15830 });
15831
15832 let render_args = Arc::new(Mutex::new(None));
15833 let snapshot = editor
15834 .update(cx, |editor, window, cx| {
15835 let snapshot = editor.buffer().read(cx).snapshot(cx);
15836 let range =
15837 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
15838
15839 struct RenderArgs {
15840 row: MultiBufferRow,
15841 folded: bool,
15842 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
15843 }
15844
15845 let crease = Crease::inline(
15846 range,
15847 FoldPlaceholder::test(),
15848 {
15849 let toggle_callback = render_args.clone();
15850 move |row, folded, callback, _window, _cx| {
15851 *toggle_callback.lock() = Some(RenderArgs {
15852 row,
15853 folded,
15854 callback,
15855 });
15856 div()
15857 }
15858 },
15859 |_row, _folded, _window, _cx| div(),
15860 );
15861
15862 editor.insert_creases(Some(crease), cx);
15863 let snapshot = editor.snapshot(window, cx);
15864 let _div = snapshot.render_crease_toggle(
15865 MultiBufferRow(1),
15866 false,
15867 cx.entity().clone(),
15868 window,
15869 cx,
15870 );
15871 snapshot
15872 })
15873 .unwrap();
15874
15875 let render_args = render_args.lock().take().unwrap();
15876 assert_eq!(render_args.row, MultiBufferRow(1));
15877 assert!(!render_args.folded);
15878 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15879
15880 cx.update_window(*editor, |_, window, cx| {
15881 (render_args.callback)(true, window, cx)
15882 })
15883 .unwrap();
15884 let snapshot = editor
15885 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15886 .unwrap();
15887 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
15888
15889 cx.update_window(*editor, |_, window, cx| {
15890 (render_args.callback)(false, window, cx)
15891 })
15892 .unwrap();
15893 let snapshot = editor
15894 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15895 .unwrap();
15896 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15897}
15898
15899#[gpui::test]
15900async fn test_input_text(cx: &mut TestAppContext) {
15901 init_test(cx, |_| {});
15902 let mut cx = EditorTestContext::new(cx).await;
15903
15904 cx.set_state(
15905 &r#"ˇone
15906 two
15907
15908 three
15909 fourˇ
15910 five
15911
15912 siˇx"#
15913 .unindent(),
15914 );
15915
15916 cx.dispatch_action(HandleInput(String::new()));
15917 cx.assert_editor_state(
15918 &r#"ˇone
15919 two
15920
15921 three
15922 fourˇ
15923 five
15924
15925 siˇx"#
15926 .unindent(),
15927 );
15928
15929 cx.dispatch_action(HandleInput("AAAA".to_string()));
15930 cx.assert_editor_state(
15931 &r#"AAAAˇone
15932 two
15933
15934 three
15935 fourAAAAˇ
15936 five
15937
15938 siAAAAˇx"#
15939 .unindent(),
15940 );
15941}
15942
15943#[gpui::test]
15944async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
15945 init_test(cx, |_| {});
15946
15947 let mut cx = EditorTestContext::new(cx).await;
15948 cx.set_state(
15949 r#"let foo = 1;
15950let foo = 2;
15951let foo = 3;
15952let fooˇ = 4;
15953let foo = 5;
15954let foo = 6;
15955let foo = 7;
15956let foo = 8;
15957let foo = 9;
15958let foo = 10;
15959let foo = 11;
15960let foo = 12;
15961let foo = 13;
15962let foo = 14;
15963let foo = 15;"#,
15964 );
15965
15966 cx.update_editor(|e, window, cx| {
15967 assert_eq!(
15968 e.next_scroll_position,
15969 NextScrollCursorCenterTopBottom::Center,
15970 "Default next scroll direction is center",
15971 );
15972
15973 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15974 assert_eq!(
15975 e.next_scroll_position,
15976 NextScrollCursorCenterTopBottom::Top,
15977 "After center, next scroll direction should be top",
15978 );
15979
15980 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15981 assert_eq!(
15982 e.next_scroll_position,
15983 NextScrollCursorCenterTopBottom::Bottom,
15984 "After top, next scroll direction should be bottom",
15985 );
15986
15987 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15988 assert_eq!(
15989 e.next_scroll_position,
15990 NextScrollCursorCenterTopBottom::Center,
15991 "After bottom, scrolling should start over",
15992 );
15993
15994 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15995 assert_eq!(
15996 e.next_scroll_position,
15997 NextScrollCursorCenterTopBottom::Top,
15998 "Scrolling continues if retriggered fast enough"
15999 );
16000 });
16001
16002 cx.executor()
16003 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
16004 cx.executor().run_until_parked();
16005 cx.update_editor(|e, _, _| {
16006 assert_eq!(
16007 e.next_scroll_position,
16008 NextScrollCursorCenterTopBottom::Center,
16009 "If scrolling is not triggered fast enough, it should reset"
16010 );
16011 });
16012}
16013
16014#[gpui::test]
16015async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
16016 init_test(cx, |_| {});
16017 let mut cx = EditorLspTestContext::new_rust(
16018 lsp::ServerCapabilities {
16019 definition_provider: Some(lsp::OneOf::Left(true)),
16020 references_provider: Some(lsp::OneOf::Left(true)),
16021 ..lsp::ServerCapabilities::default()
16022 },
16023 cx,
16024 )
16025 .await;
16026
16027 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
16028 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
16029 move |params, _| async move {
16030 if empty_go_to_definition {
16031 Ok(None)
16032 } else {
16033 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
16034 uri: params.text_document_position_params.text_document.uri,
16035 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
16036 })))
16037 }
16038 },
16039 );
16040 let references =
16041 cx.lsp
16042 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
16043 Ok(Some(vec![lsp::Location {
16044 uri: params.text_document_position.text_document.uri,
16045 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
16046 }]))
16047 });
16048 (go_to_definition, references)
16049 };
16050
16051 cx.set_state(
16052 &r#"fn one() {
16053 let mut a = ˇtwo();
16054 }
16055
16056 fn two() {}"#
16057 .unindent(),
16058 );
16059 set_up_lsp_handlers(false, &mut cx);
16060 let navigated = cx
16061 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
16062 .await
16063 .expect("Failed to navigate to definition");
16064 assert_eq!(
16065 navigated,
16066 Navigated::Yes,
16067 "Should have navigated to definition from the GetDefinition response"
16068 );
16069 cx.assert_editor_state(
16070 &r#"fn one() {
16071 let mut a = two();
16072 }
16073
16074 fn «twoˇ»() {}"#
16075 .unindent(),
16076 );
16077
16078 let editors = cx.update_workspace(|workspace, _, cx| {
16079 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
16080 });
16081 cx.update_editor(|_, _, test_editor_cx| {
16082 assert_eq!(
16083 editors.len(),
16084 1,
16085 "Initially, only one, test, editor should be open in the workspace"
16086 );
16087 assert_eq!(
16088 test_editor_cx.entity(),
16089 editors.last().expect("Asserted len is 1").clone()
16090 );
16091 });
16092
16093 set_up_lsp_handlers(true, &mut cx);
16094 let navigated = cx
16095 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
16096 .await
16097 .expect("Failed to navigate to lookup references");
16098 assert_eq!(
16099 navigated,
16100 Navigated::Yes,
16101 "Should have navigated to references as a fallback after empty GoToDefinition response"
16102 );
16103 // We should not change the selections in the existing file,
16104 // if opening another milti buffer with the references
16105 cx.assert_editor_state(
16106 &r#"fn one() {
16107 let mut a = two();
16108 }
16109
16110 fn «twoˇ»() {}"#
16111 .unindent(),
16112 );
16113 let editors = cx.update_workspace(|workspace, _, cx| {
16114 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
16115 });
16116 cx.update_editor(|_, _, test_editor_cx| {
16117 assert_eq!(
16118 editors.len(),
16119 2,
16120 "After falling back to references search, we open a new editor with the results"
16121 );
16122 let references_fallback_text = editors
16123 .into_iter()
16124 .find(|new_editor| *new_editor != test_editor_cx.entity())
16125 .expect("Should have one non-test editor now")
16126 .read(test_editor_cx)
16127 .text(test_editor_cx);
16128 assert_eq!(
16129 references_fallback_text, "fn one() {\n let mut a = two();\n}",
16130 "Should use the range from the references response and not the GoToDefinition one"
16131 );
16132 });
16133}
16134
16135#[gpui::test]
16136async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
16137 init_test(cx, |_| {});
16138
16139 let language = Arc::new(Language::new(
16140 LanguageConfig::default(),
16141 Some(tree_sitter_rust::LANGUAGE.into()),
16142 ));
16143
16144 let text = r#"
16145 #[cfg(test)]
16146 mod tests() {
16147 #[test]
16148 fn runnable_1() {
16149 let a = 1;
16150 }
16151
16152 #[test]
16153 fn runnable_2() {
16154 let a = 1;
16155 let b = 2;
16156 }
16157 }
16158 "#
16159 .unindent();
16160
16161 let fs = FakeFs::new(cx.executor());
16162 fs.insert_file("/file.rs", Default::default()).await;
16163
16164 let project = Project::test(fs, ["/a".as_ref()], cx).await;
16165 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16166 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16167 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16168 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
16169
16170 let editor = cx.new_window_entity(|window, cx| {
16171 Editor::new(
16172 EditorMode::Full,
16173 multi_buffer,
16174 Some(project.clone()),
16175 window,
16176 cx,
16177 )
16178 });
16179
16180 editor.update_in(cx, |editor, window, cx| {
16181 let snapshot = editor.buffer().read(cx).snapshot(cx);
16182 editor.tasks.insert(
16183 (buffer.read(cx).remote_id(), 3),
16184 RunnableTasks {
16185 templates: vec![],
16186 offset: snapshot.anchor_before(43),
16187 column: 0,
16188 extra_variables: HashMap::default(),
16189 context_range: BufferOffset(43)..BufferOffset(85),
16190 },
16191 );
16192 editor.tasks.insert(
16193 (buffer.read(cx).remote_id(), 8),
16194 RunnableTasks {
16195 templates: vec![],
16196 offset: snapshot.anchor_before(86),
16197 column: 0,
16198 extra_variables: HashMap::default(),
16199 context_range: BufferOffset(86)..BufferOffset(191),
16200 },
16201 );
16202
16203 // Test finding task when cursor is inside function body
16204 editor.change_selections(None, window, cx, |s| {
16205 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
16206 });
16207 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
16208 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
16209
16210 // Test finding task when cursor is on function name
16211 editor.change_selections(None, window, cx, |s| {
16212 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
16213 });
16214 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
16215 assert_eq!(row, 8, "Should find task when cursor is on function name");
16216 });
16217}
16218
16219#[gpui::test]
16220async fn test_folding_buffers(cx: &mut TestAppContext) {
16221 init_test(cx, |_| {});
16222
16223 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16224 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
16225 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
16226
16227 let fs = FakeFs::new(cx.executor());
16228 fs.insert_tree(
16229 path!("/a"),
16230 json!({
16231 "first.rs": sample_text_1,
16232 "second.rs": sample_text_2,
16233 "third.rs": sample_text_3,
16234 }),
16235 )
16236 .await;
16237 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16238 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16239 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16240 let worktree = project.update(cx, |project, cx| {
16241 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16242 assert_eq!(worktrees.len(), 1);
16243 worktrees.pop().unwrap()
16244 });
16245 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16246
16247 let buffer_1 = project
16248 .update(cx, |project, cx| {
16249 project.open_buffer((worktree_id, "first.rs"), cx)
16250 })
16251 .await
16252 .unwrap();
16253 let buffer_2 = project
16254 .update(cx, |project, cx| {
16255 project.open_buffer((worktree_id, "second.rs"), cx)
16256 })
16257 .await
16258 .unwrap();
16259 let buffer_3 = project
16260 .update(cx, |project, cx| {
16261 project.open_buffer((worktree_id, "third.rs"), cx)
16262 })
16263 .await
16264 .unwrap();
16265
16266 let multi_buffer = cx.new(|cx| {
16267 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16268 multi_buffer.push_excerpts(
16269 buffer_1.clone(),
16270 [
16271 ExcerptRange {
16272 context: Point::new(0, 0)..Point::new(3, 0),
16273 primary: None,
16274 },
16275 ExcerptRange {
16276 context: Point::new(5, 0)..Point::new(7, 0),
16277 primary: None,
16278 },
16279 ExcerptRange {
16280 context: Point::new(9, 0)..Point::new(10, 4),
16281 primary: None,
16282 },
16283 ],
16284 cx,
16285 );
16286 multi_buffer.push_excerpts(
16287 buffer_2.clone(),
16288 [
16289 ExcerptRange {
16290 context: Point::new(0, 0)..Point::new(3, 0),
16291 primary: None,
16292 },
16293 ExcerptRange {
16294 context: Point::new(5, 0)..Point::new(7, 0),
16295 primary: None,
16296 },
16297 ExcerptRange {
16298 context: Point::new(9, 0)..Point::new(10, 4),
16299 primary: None,
16300 },
16301 ],
16302 cx,
16303 );
16304 multi_buffer.push_excerpts(
16305 buffer_3.clone(),
16306 [
16307 ExcerptRange {
16308 context: Point::new(0, 0)..Point::new(3, 0),
16309 primary: None,
16310 },
16311 ExcerptRange {
16312 context: Point::new(5, 0)..Point::new(7, 0),
16313 primary: None,
16314 },
16315 ExcerptRange {
16316 context: Point::new(9, 0)..Point::new(10, 4),
16317 primary: None,
16318 },
16319 ],
16320 cx,
16321 );
16322 multi_buffer
16323 });
16324 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16325 Editor::new(
16326 EditorMode::Full,
16327 multi_buffer.clone(),
16328 Some(project.clone()),
16329 window,
16330 cx,
16331 )
16332 });
16333
16334 assert_eq!(
16335 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16336 "\n\naaaa\nbbbb\ncccc\n\n\nffff\ngggg\n\n\njjjj\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
16337 );
16338
16339 multi_buffer_editor.update(cx, |editor, cx| {
16340 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16341 });
16342 assert_eq!(
16343 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16344 "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
16345 "After folding the first buffer, its text should not be displayed"
16346 );
16347
16348 multi_buffer_editor.update(cx, |editor, cx| {
16349 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16350 });
16351 assert_eq!(
16352 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16353 "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
16354 "After folding the second buffer, its text should not be displayed"
16355 );
16356
16357 multi_buffer_editor.update(cx, |editor, cx| {
16358 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16359 });
16360 assert_eq!(
16361 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16362 "\n\n\n\n\n",
16363 "After folding the third buffer, its text should not be displayed"
16364 );
16365
16366 // Emulate selection inside the fold logic, that should work
16367 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16368 editor
16369 .snapshot(window, cx)
16370 .next_line_boundary(Point::new(0, 4));
16371 });
16372
16373 multi_buffer_editor.update(cx, |editor, cx| {
16374 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16375 });
16376 assert_eq!(
16377 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16378 "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
16379 "After unfolding the second buffer, its text should be displayed"
16380 );
16381
16382 // Typing inside of buffer 1 causes that buffer to be unfolded.
16383 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16384 assert_eq!(
16385 multi_buffer
16386 .read(cx)
16387 .snapshot(cx)
16388 .text_for_range(Point::new(1, 0)..Point::new(1, 4))
16389 .collect::<String>(),
16390 "bbbb"
16391 );
16392 editor.change_selections(None, window, cx, |selections| {
16393 selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
16394 });
16395 editor.handle_input("B", window, cx);
16396 });
16397
16398 assert_eq!(
16399 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16400 "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
16401 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
16402 );
16403
16404 multi_buffer_editor.update(cx, |editor, cx| {
16405 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16406 });
16407 assert_eq!(
16408 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16409 "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
16410 "After unfolding the all buffers, all original text should be displayed"
16411 );
16412}
16413
16414#[gpui::test]
16415async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
16416 init_test(cx, |_| {});
16417
16418 let sample_text_1 = "1111\n2222\n3333".to_string();
16419 let sample_text_2 = "4444\n5555\n6666".to_string();
16420 let sample_text_3 = "7777\n8888\n9999".to_string();
16421
16422 let fs = FakeFs::new(cx.executor());
16423 fs.insert_tree(
16424 path!("/a"),
16425 json!({
16426 "first.rs": sample_text_1,
16427 "second.rs": sample_text_2,
16428 "third.rs": sample_text_3,
16429 }),
16430 )
16431 .await;
16432 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16433 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16434 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16435 let worktree = project.update(cx, |project, cx| {
16436 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16437 assert_eq!(worktrees.len(), 1);
16438 worktrees.pop().unwrap()
16439 });
16440 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16441
16442 let buffer_1 = project
16443 .update(cx, |project, cx| {
16444 project.open_buffer((worktree_id, "first.rs"), cx)
16445 })
16446 .await
16447 .unwrap();
16448 let buffer_2 = project
16449 .update(cx, |project, cx| {
16450 project.open_buffer((worktree_id, "second.rs"), cx)
16451 })
16452 .await
16453 .unwrap();
16454 let buffer_3 = project
16455 .update(cx, |project, cx| {
16456 project.open_buffer((worktree_id, "third.rs"), cx)
16457 })
16458 .await
16459 .unwrap();
16460
16461 let multi_buffer = cx.new(|cx| {
16462 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16463 multi_buffer.push_excerpts(
16464 buffer_1.clone(),
16465 [ExcerptRange {
16466 context: Point::new(0, 0)..Point::new(3, 0),
16467 primary: None,
16468 }],
16469 cx,
16470 );
16471 multi_buffer.push_excerpts(
16472 buffer_2.clone(),
16473 [ExcerptRange {
16474 context: Point::new(0, 0)..Point::new(3, 0),
16475 primary: None,
16476 }],
16477 cx,
16478 );
16479 multi_buffer.push_excerpts(
16480 buffer_3.clone(),
16481 [ExcerptRange {
16482 context: Point::new(0, 0)..Point::new(3, 0),
16483 primary: None,
16484 }],
16485 cx,
16486 );
16487 multi_buffer
16488 });
16489
16490 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16491 Editor::new(
16492 EditorMode::Full,
16493 multi_buffer,
16494 Some(project.clone()),
16495 window,
16496 cx,
16497 )
16498 });
16499
16500 let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
16501 assert_eq!(
16502 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16503 full_text,
16504 );
16505
16506 multi_buffer_editor.update(cx, |editor, cx| {
16507 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16508 });
16509 assert_eq!(
16510 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16511 "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
16512 "After folding the first buffer, its text should not be displayed"
16513 );
16514
16515 multi_buffer_editor.update(cx, |editor, cx| {
16516 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16517 });
16518
16519 assert_eq!(
16520 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16521 "\n\n\n\n\n\n7777\n8888\n9999",
16522 "After folding the second buffer, its text should not be displayed"
16523 );
16524
16525 multi_buffer_editor.update(cx, |editor, cx| {
16526 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16527 });
16528 assert_eq!(
16529 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16530 "\n\n\n\n\n",
16531 "After folding the third buffer, its text should not be displayed"
16532 );
16533
16534 multi_buffer_editor.update(cx, |editor, cx| {
16535 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16536 });
16537 assert_eq!(
16538 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16539 "\n\n\n\n4444\n5555\n6666\n\n",
16540 "After unfolding the second buffer, its text should be displayed"
16541 );
16542
16543 multi_buffer_editor.update(cx, |editor, cx| {
16544 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
16545 });
16546 assert_eq!(
16547 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16548 "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
16549 "After unfolding the first buffer, its text should be displayed"
16550 );
16551
16552 multi_buffer_editor.update(cx, |editor, cx| {
16553 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16554 });
16555 assert_eq!(
16556 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16557 full_text,
16558 "After unfolding all buffers, all original text should be displayed"
16559 );
16560}
16561
16562#[gpui::test]
16563async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
16564 init_test(cx, |_| {});
16565
16566 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16567
16568 let fs = FakeFs::new(cx.executor());
16569 fs.insert_tree(
16570 path!("/a"),
16571 json!({
16572 "main.rs": sample_text,
16573 }),
16574 )
16575 .await;
16576 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16577 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16578 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16579 let worktree = project.update(cx, |project, cx| {
16580 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16581 assert_eq!(worktrees.len(), 1);
16582 worktrees.pop().unwrap()
16583 });
16584 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16585
16586 let buffer_1 = project
16587 .update(cx, |project, cx| {
16588 project.open_buffer((worktree_id, "main.rs"), cx)
16589 })
16590 .await
16591 .unwrap();
16592
16593 let multi_buffer = cx.new(|cx| {
16594 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16595 multi_buffer.push_excerpts(
16596 buffer_1.clone(),
16597 [ExcerptRange {
16598 context: Point::new(0, 0)
16599 ..Point::new(
16600 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
16601 0,
16602 ),
16603 primary: None,
16604 }],
16605 cx,
16606 );
16607 multi_buffer
16608 });
16609 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16610 Editor::new(
16611 EditorMode::Full,
16612 multi_buffer,
16613 Some(project.clone()),
16614 window,
16615 cx,
16616 )
16617 });
16618
16619 let selection_range = Point::new(1, 0)..Point::new(2, 0);
16620 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16621 enum TestHighlight {}
16622 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
16623 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
16624 editor.highlight_text::<TestHighlight>(
16625 vec![highlight_range.clone()],
16626 HighlightStyle::color(Hsla::green()),
16627 cx,
16628 );
16629 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
16630 });
16631
16632 let full_text = format!("\n\n{sample_text}");
16633 assert_eq!(
16634 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16635 full_text,
16636 );
16637}
16638
16639#[gpui::test]
16640async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
16641 init_test(cx, |_| {});
16642 cx.update(|cx| {
16643 let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
16644 "keymaps/default-linux.json",
16645 cx,
16646 )
16647 .unwrap();
16648 cx.bind_keys(default_key_bindings);
16649 });
16650
16651 let (editor, cx) = cx.add_window_view(|window, cx| {
16652 let multi_buffer = MultiBuffer::build_multi(
16653 [
16654 ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
16655 ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
16656 ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
16657 ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
16658 ],
16659 cx,
16660 );
16661 let mut editor = Editor::new(EditorMode::Full, multi_buffer.clone(), None, window, cx);
16662
16663 let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
16664 // fold all but the second buffer, so that we test navigating between two
16665 // adjacent folded buffers, as well as folded buffers at the start and
16666 // end the multibuffer
16667 editor.fold_buffer(buffer_ids[0], cx);
16668 editor.fold_buffer(buffer_ids[2], cx);
16669 editor.fold_buffer(buffer_ids[3], cx);
16670
16671 editor
16672 });
16673 cx.simulate_resize(size(px(1000.), px(1000.)));
16674
16675 let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
16676 cx.assert_excerpts_with_selections(indoc! {"
16677 [EXCERPT]
16678 ˇ[FOLDED]
16679 [EXCERPT]
16680 a1
16681 b1
16682 [EXCERPT]
16683 [FOLDED]
16684 [EXCERPT]
16685 [FOLDED]
16686 "
16687 });
16688 cx.simulate_keystroke("down");
16689 cx.assert_excerpts_with_selections(indoc! {"
16690 [EXCERPT]
16691 [FOLDED]
16692 [EXCERPT]
16693 ˇa1
16694 b1
16695 [EXCERPT]
16696 [FOLDED]
16697 [EXCERPT]
16698 [FOLDED]
16699 "
16700 });
16701 cx.simulate_keystroke("down");
16702 cx.assert_excerpts_with_selections(indoc! {"
16703 [EXCERPT]
16704 [FOLDED]
16705 [EXCERPT]
16706 a1
16707 ˇb1
16708 [EXCERPT]
16709 [FOLDED]
16710 [EXCERPT]
16711 [FOLDED]
16712 "
16713 });
16714 cx.simulate_keystroke("down");
16715 cx.assert_excerpts_with_selections(indoc! {"
16716 [EXCERPT]
16717 [FOLDED]
16718 [EXCERPT]
16719 a1
16720 b1
16721 ˇ[EXCERPT]
16722 [FOLDED]
16723 [EXCERPT]
16724 [FOLDED]
16725 "
16726 });
16727 cx.simulate_keystroke("down");
16728 cx.assert_excerpts_with_selections(indoc! {"
16729 [EXCERPT]
16730 [FOLDED]
16731 [EXCERPT]
16732 a1
16733 b1
16734 [EXCERPT]
16735 ˇ[FOLDED]
16736 [EXCERPT]
16737 [FOLDED]
16738 "
16739 });
16740 for _ in 0..5 {
16741 cx.simulate_keystroke("down");
16742 cx.assert_excerpts_with_selections(indoc! {"
16743 [EXCERPT]
16744 [FOLDED]
16745 [EXCERPT]
16746 a1
16747 b1
16748 [EXCERPT]
16749 [FOLDED]
16750 [EXCERPT]
16751 ˇ[FOLDED]
16752 "
16753 });
16754 }
16755
16756 cx.simulate_keystroke("up");
16757 cx.assert_excerpts_with_selections(indoc! {"
16758 [EXCERPT]
16759 [FOLDED]
16760 [EXCERPT]
16761 a1
16762 b1
16763 [EXCERPT]
16764 ˇ[FOLDED]
16765 [EXCERPT]
16766 [FOLDED]
16767 "
16768 });
16769 cx.simulate_keystroke("up");
16770 cx.assert_excerpts_with_selections(indoc! {"
16771 [EXCERPT]
16772 [FOLDED]
16773 [EXCERPT]
16774 a1
16775 b1
16776 ˇ[EXCERPT]
16777 [FOLDED]
16778 [EXCERPT]
16779 [FOLDED]
16780 "
16781 });
16782 cx.simulate_keystroke("up");
16783 cx.assert_excerpts_with_selections(indoc! {"
16784 [EXCERPT]
16785 [FOLDED]
16786 [EXCERPT]
16787 a1
16788 ˇb1
16789 [EXCERPT]
16790 [FOLDED]
16791 [EXCERPT]
16792 [FOLDED]
16793 "
16794 });
16795 cx.simulate_keystroke("up");
16796 cx.assert_excerpts_with_selections(indoc! {"
16797 [EXCERPT]
16798 [FOLDED]
16799 [EXCERPT]
16800 ˇa1
16801 b1
16802 [EXCERPT]
16803 [FOLDED]
16804 [EXCERPT]
16805 [FOLDED]
16806 "
16807 });
16808 for _ in 0..5 {
16809 cx.simulate_keystroke("up");
16810 cx.assert_excerpts_with_selections(indoc! {"
16811 [EXCERPT]
16812 ˇ[FOLDED]
16813 [EXCERPT]
16814 a1
16815 b1
16816 [EXCERPT]
16817 [FOLDED]
16818 [EXCERPT]
16819 [FOLDED]
16820 "
16821 });
16822 }
16823}
16824
16825#[gpui::test]
16826async fn test_inline_completion_text(cx: &mut TestAppContext) {
16827 init_test(cx, |_| {});
16828
16829 // Simple insertion
16830 assert_highlighted_edits(
16831 "Hello, world!",
16832 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
16833 true,
16834 cx,
16835 |highlighted_edits, cx| {
16836 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
16837 assert_eq!(highlighted_edits.highlights.len(), 1);
16838 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
16839 assert_eq!(
16840 highlighted_edits.highlights[0].1.background_color,
16841 Some(cx.theme().status().created_background)
16842 );
16843 },
16844 )
16845 .await;
16846
16847 // Replacement
16848 assert_highlighted_edits(
16849 "This is a test.",
16850 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
16851 false,
16852 cx,
16853 |highlighted_edits, cx| {
16854 assert_eq!(highlighted_edits.text, "That is a test.");
16855 assert_eq!(highlighted_edits.highlights.len(), 1);
16856 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
16857 assert_eq!(
16858 highlighted_edits.highlights[0].1.background_color,
16859 Some(cx.theme().status().created_background)
16860 );
16861 },
16862 )
16863 .await;
16864
16865 // Multiple edits
16866 assert_highlighted_edits(
16867 "Hello, world!",
16868 vec![
16869 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
16870 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
16871 ],
16872 false,
16873 cx,
16874 |highlighted_edits, cx| {
16875 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
16876 assert_eq!(highlighted_edits.highlights.len(), 2);
16877 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
16878 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
16879 assert_eq!(
16880 highlighted_edits.highlights[0].1.background_color,
16881 Some(cx.theme().status().created_background)
16882 );
16883 assert_eq!(
16884 highlighted_edits.highlights[1].1.background_color,
16885 Some(cx.theme().status().created_background)
16886 );
16887 },
16888 )
16889 .await;
16890
16891 // Multiple lines with edits
16892 assert_highlighted_edits(
16893 "First line\nSecond line\nThird line\nFourth line",
16894 vec![
16895 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
16896 (
16897 Point::new(2, 0)..Point::new(2, 10),
16898 "New third line".to_string(),
16899 ),
16900 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
16901 ],
16902 false,
16903 cx,
16904 |highlighted_edits, cx| {
16905 assert_eq!(
16906 highlighted_edits.text,
16907 "Second modified\nNew third line\nFourth updated line"
16908 );
16909 assert_eq!(highlighted_edits.highlights.len(), 3);
16910 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
16911 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
16912 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
16913 for highlight in &highlighted_edits.highlights {
16914 assert_eq!(
16915 highlight.1.background_color,
16916 Some(cx.theme().status().created_background)
16917 );
16918 }
16919 },
16920 )
16921 .await;
16922}
16923
16924#[gpui::test]
16925async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
16926 init_test(cx, |_| {});
16927
16928 // Deletion
16929 assert_highlighted_edits(
16930 "Hello, world!",
16931 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
16932 true,
16933 cx,
16934 |highlighted_edits, cx| {
16935 assert_eq!(highlighted_edits.text, "Hello, world!");
16936 assert_eq!(highlighted_edits.highlights.len(), 1);
16937 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
16938 assert_eq!(
16939 highlighted_edits.highlights[0].1.background_color,
16940 Some(cx.theme().status().deleted_background)
16941 );
16942 },
16943 )
16944 .await;
16945
16946 // Insertion
16947 assert_highlighted_edits(
16948 "Hello, world!",
16949 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
16950 true,
16951 cx,
16952 |highlighted_edits, cx| {
16953 assert_eq!(highlighted_edits.highlights.len(), 1);
16954 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
16955 assert_eq!(
16956 highlighted_edits.highlights[0].1.background_color,
16957 Some(cx.theme().status().created_background)
16958 );
16959 },
16960 )
16961 .await;
16962}
16963
16964async fn assert_highlighted_edits(
16965 text: &str,
16966 edits: Vec<(Range<Point>, String)>,
16967 include_deletions: bool,
16968 cx: &mut TestAppContext,
16969 assertion_fn: impl Fn(HighlightedText, &App),
16970) {
16971 let window = cx.add_window(|window, cx| {
16972 let buffer = MultiBuffer::build_simple(text, cx);
16973 Editor::new(EditorMode::Full, buffer, None, window, cx)
16974 });
16975 let cx = &mut VisualTestContext::from_window(*window, cx);
16976
16977 let (buffer, snapshot) = window
16978 .update(cx, |editor, _window, cx| {
16979 (
16980 editor.buffer().clone(),
16981 editor.buffer().read(cx).snapshot(cx),
16982 )
16983 })
16984 .unwrap();
16985
16986 let edits = edits
16987 .into_iter()
16988 .map(|(range, edit)| {
16989 (
16990 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
16991 edit,
16992 )
16993 })
16994 .collect::<Vec<_>>();
16995
16996 let text_anchor_edits = edits
16997 .clone()
16998 .into_iter()
16999 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
17000 .collect::<Vec<_>>();
17001
17002 let edit_preview = window
17003 .update(cx, |_, _window, cx| {
17004 buffer
17005 .read(cx)
17006 .as_singleton()
17007 .unwrap()
17008 .read(cx)
17009 .preview_edits(text_anchor_edits.into(), cx)
17010 })
17011 .unwrap()
17012 .await;
17013
17014 cx.update(|_window, cx| {
17015 let highlighted_edits = inline_completion_edit_text(
17016 &snapshot.as_singleton().unwrap().2,
17017 &edits,
17018 &edit_preview,
17019 include_deletions,
17020 cx,
17021 );
17022 assertion_fn(highlighted_edits, cx)
17023 });
17024}
17025
17026#[gpui::test]
17027async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
17028 init_test(cx, |_| {});
17029 let capabilities = lsp::ServerCapabilities {
17030 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
17031 prepare_provider: Some(true),
17032 work_done_progress_options: Default::default(),
17033 })),
17034 ..Default::default()
17035 };
17036 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
17037
17038 cx.set_state(indoc! {"
17039 struct Fˇoo {}
17040 "});
17041
17042 cx.update_editor(|editor, _, cx| {
17043 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
17044 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
17045 editor.highlight_background::<DocumentHighlightRead>(
17046 &[highlight_range],
17047 |c| c.editor_document_highlight_read_background,
17048 cx,
17049 );
17050 });
17051
17052 let mut prepare_rename_handler =
17053 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
17054 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
17055 start: lsp::Position {
17056 line: 0,
17057 character: 7,
17058 },
17059 end: lsp::Position {
17060 line: 0,
17061 character: 10,
17062 },
17063 })))
17064 });
17065 let prepare_rename_task = cx
17066 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
17067 .expect("Prepare rename was not started");
17068 prepare_rename_handler.next().await.unwrap();
17069 prepare_rename_task.await.expect("Prepare rename failed");
17070
17071 let mut rename_handler =
17072 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
17073 let edit = lsp::TextEdit {
17074 range: lsp::Range {
17075 start: lsp::Position {
17076 line: 0,
17077 character: 7,
17078 },
17079 end: lsp::Position {
17080 line: 0,
17081 character: 10,
17082 },
17083 },
17084 new_text: "FooRenamed".to_string(),
17085 };
17086 Ok(Some(lsp::WorkspaceEdit::new(
17087 // Specify the same edit twice
17088 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
17089 )))
17090 });
17091 let rename_task = cx
17092 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
17093 .expect("Confirm rename was not started");
17094 rename_handler.next().await.unwrap();
17095 rename_task.await.expect("Confirm rename failed");
17096 cx.run_until_parked();
17097
17098 // Despite two edits, only one is actually applied as those are identical
17099 cx.assert_editor_state(indoc! {"
17100 struct FooRenamedˇ {}
17101 "});
17102}
17103
17104#[gpui::test]
17105async fn test_rename_without_prepare(cx: &mut TestAppContext) {
17106 init_test(cx, |_| {});
17107 // These capabilities indicate that the server does not support prepare rename.
17108 let capabilities = lsp::ServerCapabilities {
17109 rename_provider: Some(lsp::OneOf::Left(true)),
17110 ..Default::default()
17111 };
17112 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
17113
17114 cx.set_state(indoc! {"
17115 struct Fˇoo {}
17116 "});
17117
17118 cx.update_editor(|editor, _window, cx| {
17119 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
17120 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
17121 editor.highlight_background::<DocumentHighlightRead>(
17122 &[highlight_range],
17123 |c| c.editor_document_highlight_read_background,
17124 cx,
17125 );
17126 });
17127
17128 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
17129 .expect("Prepare rename was not started")
17130 .await
17131 .expect("Prepare rename failed");
17132
17133 let mut rename_handler =
17134 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
17135 let edit = lsp::TextEdit {
17136 range: lsp::Range {
17137 start: lsp::Position {
17138 line: 0,
17139 character: 7,
17140 },
17141 end: lsp::Position {
17142 line: 0,
17143 character: 10,
17144 },
17145 },
17146 new_text: "FooRenamed".to_string(),
17147 };
17148 Ok(Some(lsp::WorkspaceEdit::new(
17149 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
17150 )))
17151 });
17152 let rename_task = cx
17153 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
17154 .expect("Confirm rename was not started");
17155 rename_handler.next().await.unwrap();
17156 rename_task.await.expect("Confirm rename failed");
17157 cx.run_until_parked();
17158
17159 // Correct range is renamed, as `surrounding_word` is used to find it.
17160 cx.assert_editor_state(indoc! {"
17161 struct FooRenamedˇ {}
17162 "});
17163}
17164
17165#[gpui::test]
17166async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
17167 init_test(cx, |_| {});
17168 let mut cx = EditorTestContext::new(cx).await;
17169
17170 let language = Arc::new(
17171 Language::new(
17172 LanguageConfig::default(),
17173 Some(tree_sitter_html::LANGUAGE.into()),
17174 )
17175 .with_brackets_query(
17176 r#"
17177 ("<" @open "/>" @close)
17178 ("</" @open ">" @close)
17179 ("<" @open ">" @close)
17180 ("\"" @open "\"" @close)
17181 ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
17182 "#,
17183 )
17184 .unwrap(),
17185 );
17186 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
17187
17188 cx.set_state(indoc! {"
17189 <span>ˇ</span>
17190 "});
17191 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17192 cx.assert_editor_state(indoc! {"
17193 <span>
17194 ˇ
17195 </span>
17196 "});
17197
17198 cx.set_state(indoc! {"
17199 <span><span></span>ˇ</span>
17200 "});
17201 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17202 cx.assert_editor_state(indoc! {"
17203 <span><span></span>
17204 ˇ</span>
17205 "});
17206
17207 cx.set_state(indoc! {"
17208 <span>ˇ
17209 </span>
17210 "});
17211 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17212 cx.assert_editor_state(indoc! {"
17213 <span>
17214 ˇ
17215 </span>
17216 "});
17217}
17218
17219mod autoclose_tags {
17220 use super::*;
17221 use language::language_settings::JsxTagAutoCloseSettings;
17222 use languages::language;
17223
17224 async fn test_setup(cx: &mut TestAppContext) -> EditorTestContext {
17225 init_test(cx, |settings| {
17226 settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true });
17227 });
17228
17229 let mut cx = EditorTestContext::new(cx).await;
17230 cx.update_buffer(|buffer, cx| {
17231 let language = language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into());
17232
17233 buffer.set_language(Some(language), cx)
17234 });
17235
17236 cx
17237 }
17238
17239 macro_rules! check {
17240 ($name:ident, $initial:literal + $input:literal => $expected:expr) => {
17241 #[gpui::test]
17242 async fn $name(cx: &mut TestAppContext) {
17243 let mut cx = test_setup(cx).await;
17244 cx.set_state($initial);
17245 cx.run_until_parked();
17246
17247 cx.update_editor(|editor, window, cx| {
17248 editor.handle_input($input, window, cx);
17249 });
17250 cx.run_until_parked();
17251 cx.assert_editor_state($expected);
17252 }
17253 };
17254 }
17255
17256 check!(
17257 test_basic,
17258 "<divˇ" + ">" => "<div>ˇ</div>"
17259 );
17260
17261 check!(
17262 test_basic_nested,
17263 "<div><divˇ</div>" + ">" => "<div><div>ˇ</div></div>"
17264 );
17265
17266 check!(
17267 test_basic_ignore_already_closed,
17268 "<div><divˇ</div></div>" + ">" => "<div><div>ˇ</div></div>"
17269 );
17270
17271 check!(
17272 test_doesnt_autoclose_closing_tag,
17273 "</divˇ" + ">" => "</div>ˇ"
17274 );
17275
17276 check!(
17277 test_jsx_attr,
17278 "<div attr={</div>}ˇ" + ">" => "<div attr={</div>}>ˇ</div>"
17279 );
17280
17281 check!(
17282 test_ignores_closing_tags_in_expr_block,
17283 "<div><divˇ{</div>}</div>" + ">" => "<div><div>ˇ</div>{</div>}</div>"
17284 );
17285
17286 check!(
17287 test_doesnt_autoclose_on_gt_in_expr,
17288 "<div attr={1 ˇ" + ">" => "<div attr={1 >ˇ"
17289 );
17290
17291 check!(
17292 test_ignores_closing_tags_with_different_tag_names,
17293 "<div><divˇ</div></span>" + ">" => "<div><div>ˇ</div></div></span>"
17294 );
17295
17296 check!(
17297 test_autocloses_in_jsx_expression,
17298 "<div>{<divˇ}</div>" + ">" => "<div>{<div>ˇ</div>}</div>"
17299 );
17300
17301 check!(
17302 test_doesnt_autoclose_already_closed_in_jsx_expression,
17303 "<div>{<divˇ</div>}</div>" + ">" => "<div>{<div>ˇ</div>}</div>"
17304 );
17305
17306 check!(
17307 test_autocloses_fragment,
17308 "<ˇ" + ">" => "<>ˇ</>"
17309 );
17310
17311 check!(
17312 test_does_not_include_type_argument_in_autoclose_tag_name,
17313 "<Component<T> attr={boolean_value}ˇ" + ">" => "<Component<T> attr={boolean_value}>ˇ</Component>"
17314 );
17315
17316 check!(
17317 test_does_not_autoclose_doctype,
17318 "<!DOCTYPE htmlˇ" + ">" => "<!DOCTYPE html>ˇ"
17319 );
17320
17321 check!(
17322 test_does_not_autoclose_comment,
17323 "<!-- comment --ˇ" + ">" => "<!-- comment -->ˇ"
17324 );
17325
17326 check!(
17327 test_multi_cursor_autoclose_same_tag,
17328 r#"
17329 <divˇ
17330 <divˇ
17331 "#
17332 + ">" =>
17333 r#"
17334 <div>ˇ</div>
17335 <div>ˇ</div>
17336 "#
17337 );
17338
17339 check!(
17340 test_multi_cursor_autoclose_different_tags,
17341 r#"
17342 <divˇ
17343 <spanˇ
17344 "#
17345 + ">" =>
17346 r#"
17347 <div>ˇ</div>
17348 <span>ˇ</span>
17349 "#
17350 );
17351
17352 check!(
17353 test_multi_cursor_autoclose_some_dont_autoclose_others,
17354 r#"
17355 <divˇ
17356 <div /ˇ
17357 <spanˇ</span>
17358 <!DOCTYPE htmlˇ
17359 </headˇ
17360 <Component<T>ˇ
17361 ˇ
17362 "#
17363 + ">" =>
17364 r#"
17365 <div>ˇ</div>
17366 <div />ˇ
17367 <span>ˇ</span>
17368 <!DOCTYPE html>ˇ
17369 </head>ˇ
17370 <Component<T>>ˇ</Component>
17371 >ˇ
17372 "#
17373 );
17374
17375 check!(
17376 test_doesnt_mess_up_trailing_text,
17377 "<divˇfoobar" + ">" => "<div>ˇ</div>foobar"
17378 );
17379
17380 #[gpui::test]
17381 async fn test_multibuffer(cx: &mut TestAppContext) {
17382 init_test(cx, |settings| {
17383 settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true });
17384 });
17385
17386 let buffer_a = cx.new(|cx| {
17387 let mut buf = language::Buffer::local("<div", cx);
17388 buf.set_language(
17389 Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
17390 cx,
17391 );
17392 buf
17393 });
17394 let buffer_b = cx.new(|cx| {
17395 let mut buf = language::Buffer::local("<pre", cx);
17396 buf.set_language(
17397 Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
17398 cx,
17399 );
17400 buf
17401 });
17402 let buffer_c = cx.new(|cx| {
17403 let buf = language::Buffer::local("<span", cx);
17404 buf
17405 });
17406 let buffer = cx.new(|cx| {
17407 let mut buf = MultiBuffer::new(language::Capability::ReadWrite);
17408 buf.push_excerpts(
17409 buffer_a,
17410 [ExcerptRange {
17411 context: text::Anchor::MIN..text::Anchor::MAX,
17412 primary: None,
17413 }],
17414 cx,
17415 );
17416 buf.push_excerpts(
17417 buffer_b,
17418 [ExcerptRange {
17419 context: text::Anchor::MIN..text::Anchor::MAX,
17420 primary: None,
17421 }],
17422 cx,
17423 );
17424 buf.push_excerpts(
17425 buffer_c,
17426 [ExcerptRange {
17427 context: text::Anchor::MIN..text::Anchor::MAX,
17428 primary: None,
17429 }],
17430 cx,
17431 );
17432 buf
17433 });
17434 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17435
17436 let mut cx = EditorTestContext::for_editor(editor, cx).await;
17437
17438 cx.update_editor(|editor, window, cx| {
17439 editor.change_selections(None, window, cx, |selections| {
17440 selections.select(vec![
17441 Selection::from_offset(4),
17442 Selection::from_offset(9),
17443 Selection::from_offset(15),
17444 ])
17445 })
17446 });
17447 cx.run_until_parked();
17448
17449 cx.update_editor(|editor, window, cx| {
17450 editor.handle_input(">", window, cx);
17451 });
17452 cx.run_until_parked();
17453
17454 cx.assert_editor_state("<div>ˇ</div>\n<pre>ˇ</pre>\n<span>ˇ");
17455 }
17456}
17457
17458fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
17459 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
17460 point..point
17461}
17462
17463fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
17464 let (text, ranges) = marked_text_ranges(marked_text, true);
17465 assert_eq!(editor.text(cx), text);
17466 assert_eq!(
17467 editor.selections.ranges(cx),
17468 ranges,
17469 "Assert selections are {}",
17470 marked_text
17471 );
17472}
17473
17474pub fn handle_signature_help_request(
17475 cx: &mut EditorLspTestContext,
17476 mocked_response: lsp::SignatureHelp,
17477) -> impl Future<Output = ()> {
17478 let mut request =
17479 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
17480 let mocked_response = mocked_response.clone();
17481 async move { Ok(Some(mocked_response)) }
17482 });
17483
17484 async move {
17485 request.next().await;
17486 }
17487}
17488
17489/// Handle completion request passing a marked string specifying where the completion
17490/// should be triggered from using '|' character, what range should be replaced, and what completions
17491/// should be returned using '<' and '>' to delimit the range
17492pub fn handle_completion_request(
17493 cx: &mut EditorLspTestContext,
17494 marked_string: &str,
17495 completions: Vec<&'static str>,
17496 counter: Arc<AtomicUsize>,
17497) -> impl Future<Output = ()> {
17498 let complete_from_marker: TextRangeMarker = '|'.into();
17499 let replace_range_marker: TextRangeMarker = ('<', '>').into();
17500 let (_, mut marked_ranges) = marked_text_ranges_by(
17501 marked_string,
17502 vec![complete_from_marker.clone(), replace_range_marker.clone()],
17503 );
17504
17505 let complete_from_position =
17506 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
17507 let replace_range =
17508 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
17509
17510 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
17511 let completions = completions.clone();
17512 counter.fetch_add(1, atomic::Ordering::Release);
17513 async move {
17514 assert_eq!(params.text_document_position.text_document.uri, url.clone());
17515 assert_eq!(
17516 params.text_document_position.position,
17517 complete_from_position
17518 );
17519 Ok(Some(lsp::CompletionResponse::Array(
17520 completions
17521 .iter()
17522 .map(|completion_text| lsp::CompletionItem {
17523 label: completion_text.to_string(),
17524 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17525 range: replace_range,
17526 new_text: completion_text.to_string(),
17527 })),
17528 ..Default::default()
17529 })
17530 .collect(),
17531 )))
17532 }
17533 });
17534
17535 async move {
17536 request.next().await;
17537 }
17538}
17539
17540fn handle_resolve_completion_request(
17541 cx: &mut EditorLspTestContext,
17542 edits: Option<Vec<(&'static str, &'static str)>>,
17543) -> impl Future<Output = ()> {
17544 let edits = edits.map(|edits| {
17545 edits
17546 .iter()
17547 .map(|(marked_string, new_text)| {
17548 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
17549 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
17550 lsp::TextEdit::new(replace_range, new_text.to_string())
17551 })
17552 .collect::<Vec<_>>()
17553 });
17554
17555 let mut request =
17556 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17557 let edits = edits.clone();
17558 async move {
17559 Ok(lsp::CompletionItem {
17560 additional_text_edits: edits,
17561 ..Default::default()
17562 })
17563 }
17564 });
17565
17566 async move {
17567 request.next().await;
17568 }
17569}
17570
17571pub(crate) fn update_test_language_settings(
17572 cx: &mut TestAppContext,
17573 f: impl Fn(&mut AllLanguageSettingsContent),
17574) {
17575 cx.update(|cx| {
17576 SettingsStore::update_global(cx, |store, cx| {
17577 store.update_user_settings::<AllLanguageSettings>(cx, f);
17578 });
17579 });
17580}
17581
17582pub(crate) fn update_test_project_settings(
17583 cx: &mut TestAppContext,
17584 f: impl Fn(&mut ProjectSettings),
17585) {
17586 cx.update(|cx| {
17587 SettingsStore::update_global(cx, |store, cx| {
17588 store.update_user_settings::<ProjectSettings>(cx, f);
17589 });
17590 });
17591}
17592
17593pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
17594 cx.update(|cx| {
17595 assets::Assets.load_test_fonts(cx);
17596 let store = SettingsStore::test(cx);
17597 cx.set_global(store);
17598 theme::init(theme::LoadThemes::JustBase, cx);
17599 release_channel::init(SemanticVersion::default(), cx);
17600 client::init_settings(cx);
17601 language::init(cx);
17602 Project::init_settings(cx);
17603 workspace::init_settings(cx);
17604 crate::init(cx);
17605 });
17606
17607 update_test_language_settings(cx, f);
17608}
17609
17610#[track_caller]
17611fn assert_hunk_revert(
17612 not_reverted_text_with_selections: &str,
17613 expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
17614 expected_reverted_text_with_selections: &str,
17615 base_text: &str,
17616 cx: &mut EditorLspTestContext,
17617) {
17618 cx.set_state(not_reverted_text_with_selections);
17619 cx.set_head_text(base_text);
17620 cx.executor().run_until_parked();
17621
17622 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
17623 let snapshot = editor.snapshot(window, cx);
17624 let reverted_hunk_statuses = snapshot
17625 .buffer_snapshot
17626 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
17627 .map(|hunk| hunk.status().kind)
17628 .collect::<Vec<_>>();
17629
17630 editor.git_restore(&Default::default(), window, cx);
17631 reverted_hunk_statuses
17632 });
17633 cx.executor().run_until_parked();
17634 cx.assert_editor_state(expected_reverted_text_with_selections);
17635 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
17636}