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 true,
7680 window,
7681 cx,
7682 )
7683 });
7684
7685 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7686 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7687 s.select_ranges(Some(1..2))
7688 });
7689 editor.insert("|one|two|three|", window, cx);
7690 });
7691 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7692 multi_buffer_editor.update_in(cx, |editor, window, cx| {
7693 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
7694 s.select_ranges(Some(60..70))
7695 });
7696 editor.insert("|four|five|six|", window, cx);
7697 });
7698 assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
7699
7700 // First two buffers should be edited, but not the third one.
7701 assert_eq!(
7702 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7703 "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}",
7704 );
7705 buffer_1.update(cx, |buffer, _| {
7706 assert!(buffer.is_dirty());
7707 assert_eq!(
7708 buffer.text(),
7709 "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
7710 )
7711 });
7712 buffer_2.update(cx, |buffer, _| {
7713 assert!(buffer.is_dirty());
7714 assert_eq!(
7715 buffer.text(),
7716 "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
7717 )
7718 });
7719 buffer_3.update(cx, |buffer, _| {
7720 assert!(!buffer.is_dirty());
7721 assert_eq!(buffer.text(), sample_text_3,)
7722 });
7723 cx.executor().run_until_parked();
7724
7725 cx.executor().start_waiting();
7726 let save = multi_buffer_editor
7727 .update_in(cx, |editor, window, cx| {
7728 editor.save(true, project.clone(), window, cx)
7729 })
7730 .unwrap();
7731
7732 let fake_server = fake_servers.next().await.unwrap();
7733 fake_server
7734 .server
7735 .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7736 Ok(Some(vec![lsp::TextEdit::new(
7737 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7738 format!("[{} formatted]", params.text_document.uri),
7739 )]))
7740 })
7741 .detach();
7742 save.await;
7743
7744 // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
7745 assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
7746 assert_eq!(
7747 multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
7748 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}"),
7749 );
7750 buffer_1.update(cx, |buffer, _| {
7751 assert!(!buffer.is_dirty());
7752 assert_eq!(
7753 buffer.text(),
7754 uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
7755 )
7756 });
7757 buffer_2.update(cx, |buffer, _| {
7758 assert!(!buffer.is_dirty());
7759 assert_eq!(
7760 buffer.text(),
7761 uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
7762 )
7763 });
7764 buffer_3.update(cx, |buffer, _| {
7765 assert!(!buffer.is_dirty());
7766 assert_eq!(buffer.text(), sample_text_3,)
7767 });
7768}
7769
7770#[gpui::test]
7771async fn test_range_format_during_save(cx: &mut TestAppContext) {
7772 init_test(cx, |_| {});
7773
7774 let fs = FakeFs::new(cx.executor());
7775 fs.insert_file(path!("/file.rs"), Default::default()).await;
7776
7777 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7778
7779 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7780 language_registry.add(rust_lang());
7781 let mut fake_servers = language_registry.register_fake_lsp(
7782 "Rust",
7783 FakeLspAdapter {
7784 capabilities: lsp::ServerCapabilities {
7785 document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
7786 ..Default::default()
7787 },
7788 ..Default::default()
7789 },
7790 );
7791
7792 let buffer = project
7793 .update(cx, |project, cx| {
7794 project.open_local_buffer(path!("/file.rs"), cx)
7795 })
7796 .await
7797 .unwrap();
7798
7799 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7800 let (editor, cx) = cx.add_window_view(|window, cx| {
7801 build_editor_with_project(project.clone(), buffer, window, cx)
7802 });
7803 editor.update_in(cx, |editor, window, cx| {
7804 editor.set_text("one\ntwo\nthree\n", window, cx)
7805 });
7806 assert!(cx.read(|cx| editor.is_dirty(cx)));
7807
7808 cx.executor().start_waiting();
7809 let fake_server = fake_servers.next().await.unwrap();
7810
7811 let save = editor
7812 .update_in(cx, |editor, window, cx| {
7813 editor.save(true, project.clone(), window, cx)
7814 })
7815 .unwrap();
7816 fake_server
7817 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7818 assert_eq!(
7819 params.text_document.uri,
7820 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7821 );
7822 assert_eq!(params.options.tab_size, 4);
7823 Ok(Some(vec![lsp::TextEdit::new(
7824 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7825 ", ".to_string(),
7826 )]))
7827 })
7828 .next()
7829 .await;
7830 cx.executor().start_waiting();
7831 save.await;
7832 assert_eq!(
7833 editor.update(cx, |editor, cx| editor.text(cx)),
7834 "one, two\nthree\n"
7835 );
7836 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7837
7838 editor.update_in(cx, |editor, window, cx| {
7839 editor.set_text("one\ntwo\nthree\n", window, cx)
7840 });
7841 assert!(cx.read(|cx| editor.is_dirty(cx)));
7842
7843 // Ensure we can still save even if formatting hangs.
7844 fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
7845 move |params, _| async move {
7846 assert_eq!(
7847 params.text_document.uri,
7848 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7849 );
7850 futures::future::pending::<()>().await;
7851 unreachable!()
7852 },
7853 );
7854 let save = editor
7855 .update_in(cx, |editor, window, cx| {
7856 editor.save(true, project.clone(), window, cx)
7857 })
7858 .unwrap();
7859 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
7860 cx.executor().start_waiting();
7861 save.await;
7862 assert_eq!(
7863 editor.update(cx, |editor, cx| editor.text(cx)),
7864 "one\ntwo\nthree\n"
7865 );
7866 assert!(!cx.read(|cx| editor.is_dirty(cx)));
7867
7868 // For non-dirty buffer, no formatting request should be sent
7869 let save = editor
7870 .update_in(cx, |editor, window, cx| {
7871 editor.save(true, project.clone(), window, cx)
7872 })
7873 .unwrap();
7874 let _pending_format_request = fake_server
7875 .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
7876 panic!("Should not be invoked on non-dirty buffer");
7877 })
7878 .next();
7879 cx.executor().start_waiting();
7880 save.await;
7881
7882 // Set Rust language override and assert overridden tabsize is sent to language server
7883 update_test_language_settings(cx, |settings| {
7884 settings.languages.insert(
7885 "Rust".into(),
7886 LanguageSettingsContent {
7887 tab_size: NonZeroU32::new(8),
7888 ..Default::default()
7889 },
7890 );
7891 });
7892
7893 editor.update_in(cx, |editor, window, cx| {
7894 editor.set_text("somehting_new\n", window, cx)
7895 });
7896 assert!(cx.read(|cx| editor.is_dirty(cx)));
7897 let save = editor
7898 .update_in(cx, |editor, window, cx| {
7899 editor.save(true, project.clone(), window, cx)
7900 })
7901 .unwrap();
7902 fake_server
7903 .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
7904 assert_eq!(
7905 params.text_document.uri,
7906 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7907 );
7908 assert_eq!(params.options.tab_size, 8);
7909 Ok(Some(vec![]))
7910 })
7911 .next()
7912 .await;
7913 cx.executor().start_waiting();
7914 save.await;
7915}
7916
7917#[gpui::test]
7918async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
7919 init_test(cx, |settings| {
7920 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
7921 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
7922 ))
7923 });
7924
7925 let fs = FakeFs::new(cx.executor());
7926 fs.insert_file(path!("/file.rs"), Default::default()).await;
7927
7928 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
7929
7930 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
7931 language_registry.add(Arc::new(Language::new(
7932 LanguageConfig {
7933 name: "Rust".into(),
7934 matcher: LanguageMatcher {
7935 path_suffixes: vec!["rs".to_string()],
7936 ..Default::default()
7937 },
7938 ..LanguageConfig::default()
7939 },
7940 Some(tree_sitter_rust::LANGUAGE.into()),
7941 )));
7942 update_test_language_settings(cx, |settings| {
7943 // Enable Prettier formatting for the same buffer, and ensure
7944 // LSP is called instead of Prettier.
7945 settings.defaults.prettier = Some(PrettierSettings {
7946 allowed: true,
7947 ..PrettierSettings::default()
7948 });
7949 });
7950 let mut fake_servers = language_registry.register_fake_lsp(
7951 "Rust",
7952 FakeLspAdapter {
7953 capabilities: lsp::ServerCapabilities {
7954 document_formatting_provider: Some(lsp::OneOf::Left(true)),
7955 ..Default::default()
7956 },
7957 ..Default::default()
7958 },
7959 );
7960
7961 let buffer = project
7962 .update(cx, |project, cx| {
7963 project.open_local_buffer(path!("/file.rs"), cx)
7964 })
7965 .await
7966 .unwrap();
7967
7968 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
7969 let (editor, cx) = cx.add_window_view(|window, cx| {
7970 build_editor_with_project(project.clone(), buffer, window, cx)
7971 });
7972 editor.update_in(cx, |editor, window, cx| {
7973 editor.set_text("one\ntwo\nthree\n", window, cx)
7974 });
7975
7976 cx.executor().start_waiting();
7977 let fake_server = fake_servers.next().await.unwrap();
7978
7979 let format = editor
7980 .update_in(cx, |editor, window, cx| {
7981 editor.perform_format(
7982 project.clone(),
7983 FormatTrigger::Manual,
7984 FormatTarget::Buffers,
7985 window,
7986 cx,
7987 )
7988 })
7989 .unwrap();
7990 fake_server
7991 .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
7992 assert_eq!(
7993 params.text_document.uri,
7994 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
7995 );
7996 assert_eq!(params.options.tab_size, 4);
7997 Ok(Some(vec![lsp::TextEdit::new(
7998 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
7999 ", ".to_string(),
8000 )]))
8001 })
8002 .next()
8003 .await;
8004 cx.executor().start_waiting();
8005 format.await;
8006 assert_eq!(
8007 editor.update(cx, |editor, cx| editor.text(cx)),
8008 "one, two\nthree\n"
8009 );
8010
8011 editor.update_in(cx, |editor, window, cx| {
8012 editor.set_text("one\ntwo\nthree\n", window, cx)
8013 });
8014 // Ensure we don't lock if formatting hangs.
8015 fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
8016 assert_eq!(
8017 params.text_document.uri,
8018 lsp::Url::from_file_path(path!("/file.rs")).unwrap()
8019 );
8020 futures::future::pending::<()>().await;
8021 unreachable!()
8022 });
8023 let format = editor
8024 .update_in(cx, |editor, window, cx| {
8025 editor.perform_format(
8026 project,
8027 FormatTrigger::Manual,
8028 FormatTarget::Buffers,
8029 window,
8030 cx,
8031 )
8032 })
8033 .unwrap();
8034 cx.executor().advance_clock(super::FORMAT_TIMEOUT);
8035 cx.executor().start_waiting();
8036 format.await;
8037 assert_eq!(
8038 editor.update(cx, |editor, cx| editor.text(cx)),
8039 "one\ntwo\nthree\n"
8040 );
8041}
8042
8043#[gpui::test]
8044async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
8045 init_test(cx, |settings| {
8046 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
8047 FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
8048 ))
8049 });
8050
8051 let fs = FakeFs::new(cx.executor());
8052 fs.insert_file(path!("/file.ts"), Default::default()).await;
8053
8054 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
8055
8056 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
8057 language_registry.add(Arc::new(Language::new(
8058 LanguageConfig {
8059 name: "TypeScript".into(),
8060 matcher: LanguageMatcher {
8061 path_suffixes: vec!["ts".to_string()],
8062 ..Default::default()
8063 },
8064 ..LanguageConfig::default()
8065 },
8066 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
8067 )));
8068 update_test_language_settings(cx, |settings| {
8069 settings.defaults.prettier = Some(PrettierSettings {
8070 allowed: true,
8071 ..PrettierSettings::default()
8072 });
8073 });
8074 let mut fake_servers = language_registry.register_fake_lsp(
8075 "TypeScript",
8076 FakeLspAdapter {
8077 capabilities: lsp::ServerCapabilities {
8078 code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
8079 ..Default::default()
8080 },
8081 ..Default::default()
8082 },
8083 );
8084
8085 let buffer = project
8086 .update(cx, |project, cx| {
8087 project.open_local_buffer(path!("/file.ts"), cx)
8088 })
8089 .await
8090 .unwrap();
8091
8092 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
8093 let (editor, cx) = cx.add_window_view(|window, cx| {
8094 build_editor_with_project(project.clone(), buffer, window, cx)
8095 });
8096 editor.update_in(cx, |editor, window, cx| {
8097 editor.set_text(
8098 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8099 window,
8100 cx,
8101 )
8102 });
8103
8104 cx.executor().start_waiting();
8105 let fake_server = fake_servers.next().await.unwrap();
8106
8107 let format = editor
8108 .update_in(cx, |editor, window, cx| {
8109 editor.perform_code_action_kind(
8110 project.clone(),
8111 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8112 window,
8113 cx,
8114 )
8115 })
8116 .unwrap();
8117 fake_server
8118 .handle_request::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
8119 assert_eq!(
8120 params.text_document.uri,
8121 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8122 );
8123 Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
8124 lsp::CodeAction {
8125 title: "Organize Imports".to_string(),
8126 kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
8127 edit: Some(lsp::WorkspaceEdit {
8128 changes: Some(
8129 [(
8130 params.text_document.uri.clone(),
8131 vec![lsp::TextEdit::new(
8132 lsp::Range::new(
8133 lsp::Position::new(1, 0),
8134 lsp::Position::new(2, 0),
8135 ),
8136 "".to_string(),
8137 )],
8138 )]
8139 .into_iter()
8140 .collect(),
8141 ),
8142 ..Default::default()
8143 }),
8144 ..Default::default()
8145 },
8146 )]))
8147 })
8148 .next()
8149 .await;
8150 cx.executor().start_waiting();
8151 format.await;
8152 assert_eq!(
8153 editor.update(cx, |editor, cx| editor.text(cx)),
8154 "import { a } from 'module';\n\nconst x = a;\n"
8155 );
8156
8157 editor.update_in(cx, |editor, window, cx| {
8158 editor.set_text(
8159 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
8160 window,
8161 cx,
8162 )
8163 });
8164 // Ensure we don't lock if code action hangs.
8165 fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
8166 move |params, _| async move {
8167 assert_eq!(
8168 params.text_document.uri,
8169 lsp::Url::from_file_path(path!("/file.ts")).unwrap()
8170 );
8171 futures::future::pending::<()>().await;
8172 unreachable!()
8173 },
8174 );
8175 let format = editor
8176 .update_in(cx, |editor, window, cx| {
8177 editor.perform_code_action_kind(
8178 project,
8179 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
8180 window,
8181 cx,
8182 )
8183 })
8184 .unwrap();
8185 cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
8186 cx.executor().start_waiting();
8187 format.await;
8188 assert_eq!(
8189 editor.update(cx, |editor, cx| editor.text(cx)),
8190 "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
8191 );
8192}
8193
8194#[gpui::test]
8195async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
8196 init_test(cx, |_| {});
8197
8198 let mut cx = EditorLspTestContext::new_rust(
8199 lsp::ServerCapabilities {
8200 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8201 ..Default::default()
8202 },
8203 cx,
8204 )
8205 .await;
8206
8207 cx.set_state(indoc! {"
8208 one.twoˇ
8209 "});
8210
8211 // The format request takes a long time. When it completes, it inserts
8212 // a newline and an indent before the `.`
8213 cx.lsp
8214 .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
8215 let executor = cx.background_executor().clone();
8216 async move {
8217 executor.timer(Duration::from_millis(100)).await;
8218 Ok(Some(vec![lsp::TextEdit {
8219 range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
8220 new_text: "\n ".into(),
8221 }]))
8222 }
8223 });
8224
8225 // Submit a format request.
8226 let format_1 = cx
8227 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8228 .unwrap();
8229 cx.executor().run_until_parked();
8230
8231 // Submit a second format request.
8232 let format_2 = cx
8233 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8234 .unwrap();
8235 cx.executor().run_until_parked();
8236
8237 // Wait for both format requests to complete
8238 cx.executor().advance_clock(Duration::from_millis(200));
8239 cx.executor().start_waiting();
8240 format_1.await.unwrap();
8241 cx.executor().start_waiting();
8242 format_2.await.unwrap();
8243
8244 // The formatting edits only happens once.
8245 cx.assert_editor_state(indoc! {"
8246 one
8247 .twoˇ
8248 "});
8249}
8250
8251#[gpui::test]
8252async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
8253 init_test(cx, |settings| {
8254 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
8255 });
8256
8257 let mut cx = EditorLspTestContext::new_rust(
8258 lsp::ServerCapabilities {
8259 document_formatting_provider: Some(lsp::OneOf::Left(true)),
8260 ..Default::default()
8261 },
8262 cx,
8263 )
8264 .await;
8265
8266 // Set up a buffer white some trailing whitespace and no trailing newline.
8267 cx.set_state(
8268 &[
8269 "one ", //
8270 "twoˇ", //
8271 "three ", //
8272 "four", //
8273 ]
8274 .join("\n"),
8275 );
8276
8277 // Submit a format request.
8278 let format = cx
8279 .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
8280 .unwrap();
8281
8282 // Record which buffer changes have been sent to the language server
8283 let buffer_changes = Arc::new(Mutex::new(Vec::new()));
8284 cx.lsp
8285 .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
8286 let buffer_changes = buffer_changes.clone();
8287 move |params, _| {
8288 buffer_changes.lock().extend(
8289 params
8290 .content_changes
8291 .into_iter()
8292 .map(|e| (e.range.unwrap(), e.text)),
8293 );
8294 }
8295 });
8296
8297 // Handle formatting requests to the language server.
8298 cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
8299 let buffer_changes = buffer_changes.clone();
8300 move |_, _| {
8301 // When formatting is requested, trailing whitespace has already been stripped,
8302 // and the trailing newline has already been added.
8303 assert_eq!(
8304 &buffer_changes.lock()[1..],
8305 &[
8306 (
8307 lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
8308 "".into()
8309 ),
8310 (
8311 lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
8312 "".into()
8313 ),
8314 (
8315 lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
8316 "\n".into()
8317 ),
8318 ]
8319 );
8320
8321 // Insert blank lines between each line of the buffer.
8322 async move {
8323 Ok(Some(vec![
8324 lsp::TextEdit {
8325 range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
8326 new_text: "\n".into(),
8327 },
8328 lsp::TextEdit {
8329 range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
8330 new_text: "\n".into(),
8331 },
8332 ]))
8333 }
8334 }
8335 });
8336
8337 // After formatting the buffer, the trailing whitespace is stripped,
8338 // a newline is appended, and the edits provided by the language server
8339 // have been applied.
8340 format.await.unwrap();
8341 cx.assert_editor_state(
8342 &[
8343 "one", //
8344 "", //
8345 "twoˇ", //
8346 "", //
8347 "three", //
8348 "four", //
8349 "", //
8350 ]
8351 .join("\n"),
8352 );
8353
8354 // Undoing the formatting undoes the trailing whitespace removal, the
8355 // trailing newline, and the LSP edits.
8356 cx.update_buffer(|buffer, cx| buffer.undo(cx));
8357 cx.assert_editor_state(
8358 &[
8359 "one ", //
8360 "twoˇ", //
8361 "three ", //
8362 "four", //
8363 ]
8364 .join("\n"),
8365 );
8366}
8367
8368#[gpui::test]
8369async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
8370 cx: &mut TestAppContext,
8371) {
8372 init_test(cx, |_| {});
8373
8374 cx.update(|cx| {
8375 cx.update_global::<SettingsStore, _>(|settings, cx| {
8376 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8377 settings.auto_signature_help = Some(true);
8378 });
8379 });
8380 });
8381
8382 let mut cx = EditorLspTestContext::new_rust(
8383 lsp::ServerCapabilities {
8384 signature_help_provider: Some(lsp::SignatureHelpOptions {
8385 ..Default::default()
8386 }),
8387 ..Default::default()
8388 },
8389 cx,
8390 )
8391 .await;
8392
8393 let language = Language::new(
8394 LanguageConfig {
8395 name: "Rust".into(),
8396 brackets: BracketPairConfig {
8397 pairs: vec![
8398 BracketPair {
8399 start: "{".to_string(),
8400 end: "}".to_string(),
8401 close: true,
8402 surround: true,
8403 newline: true,
8404 },
8405 BracketPair {
8406 start: "(".to_string(),
8407 end: ")".to_string(),
8408 close: true,
8409 surround: true,
8410 newline: true,
8411 },
8412 BracketPair {
8413 start: "/*".to_string(),
8414 end: " */".to_string(),
8415 close: true,
8416 surround: true,
8417 newline: true,
8418 },
8419 BracketPair {
8420 start: "[".to_string(),
8421 end: "]".to_string(),
8422 close: false,
8423 surround: false,
8424 newline: true,
8425 },
8426 BracketPair {
8427 start: "\"".to_string(),
8428 end: "\"".to_string(),
8429 close: true,
8430 surround: true,
8431 newline: false,
8432 },
8433 BracketPair {
8434 start: "<".to_string(),
8435 end: ">".to_string(),
8436 close: false,
8437 surround: true,
8438 newline: true,
8439 },
8440 ],
8441 ..Default::default()
8442 },
8443 autoclose_before: "})]".to_string(),
8444 ..Default::default()
8445 },
8446 Some(tree_sitter_rust::LANGUAGE.into()),
8447 );
8448 let language = Arc::new(language);
8449
8450 cx.language_registry().add(language.clone());
8451 cx.update_buffer(|buffer, cx| {
8452 buffer.set_language(Some(language), cx);
8453 });
8454
8455 cx.set_state(
8456 &r#"
8457 fn main() {
8458 sampleˇ
8459 }
8460 "#
8461 .unindent(),
8462 );
8463
8464 cx.update_editor(|editor, window, cx| {
8465 editor.handle_input("(", window, cx);
8466 });
8467 cx.assert_editor_state(
8468 &"
8469 fn main() {
8470 sample(ˇ)
8471 }
8472 "
8473 .unindent(),
8474 );
8475
8476 let mocked_response = lsp::SignatureHelp {
8477 signatures: vec![lsp::SignatureInformation {
8478 label: "fn sample(param1: u8, param2: u8)".to_string(),
8479 documentation: None,
8480 parameters: Some(vec![
8481 lsp::ParameterInformation {
8482 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8483 documentation: None,
8484 },
8485 lsp::ParameterInformation {
8486 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8487 documentation: None,
8488 },
8489 ]),
8490 active_parameter: None,
8491 }],
8492 active_signature: Some(0),
8493 active_parameter: Some(0),
8494 };
8495 handle_signature_help_request(&mut cx, mocked_response).await;
8496
8497 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8498 .await;
8499
8500 cx.editor(|editor, _, _| {
8501 let signature_help_state = editor.signature_help_state.popover().cloned();
8502 assert_eq!(
8503 signature_help_state.unwrap().label,
8504 "param1: u8, param2: u8"
8505 );
8506 });
8507}
8508
8509#[gpui::test]
8510async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
8511 init_test(cx, |_| {});
8512
8513 cx.update(|cx| {
8514 cx.update_global::<SettingsStore, _>(|settings, cx| {
8515 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8516 settings.auto_signature_help = Some(false);
8517 settings.show_signature_help_after_edits = Some(false);
8518 });
8519 });
8520 });
8521
8522 let mut cx = EditorLspTestContext::new_rust(
8523 lsp::ServerCapabilities {
8524 signature_help_provider: Some(lsp::SignatureHelpOptions {
8525 ..Default::default()
8526 }),
8527 ..Default::default()
8528 },
8529 cx,
8530 )
8531 .await;
8532
8533 let language = Language::new(
8534 LanguageConfig {
8535 name: "Rust".into(),
8536 brackets: BracketPairConfig {
8537 pairs: vec![
8538 BracketPair {
8539 start: "{".to_string(),
8540 end: "}".to_string(),
8541 close: true,
8542 surround: true,
8543 newline: true,
8544 },
8545 BracketPair {
8546 start: "(".to_string(),
8547 end: ")".to_string(),
8548 close: true,
8549 surround: true,
8550 newline: true,
8551 },
8552 BracketPair {
8553 start: "/*".to_string(),
8554 end: " */".to_string(),
8555 close: true,
8556 surround: true,
8557 newline: true,
8558 },
8559 BracketPair {
8560 start: "[".to_string(),
8561 end: "]".to_string(),
8562 close: false,
8563 surround: false,
8564 newline: true,
8565 },
8566 BracketPair {
8567 start: "\"".to_string(),
8568 end: "\"".to_string(),
8569 close: true,
8570 surround: true,
8571 newline: false,
8572 },
8573 BracketPair {
8574 start: "<".to_string(),
8575 end: ">".to_string(),
8576 close: false,
8577 surround: true,
8578 newline: true,
8579 },
8580 ],
8581 ..Default::default()
8582 },
8583 autoclose_before: "})]".to_string(),
8584 ..Default::default()
8585 },
8586 Some(tree_sitter_rust::LANGUAGE.into()),
8587 );
8588 let language = Arc::new(language);
8589
8590 cx.language_registry().add(language.clone());
8591 cx.update_buffer(|buffer, cx| {
8592 buffer.set_language(Some(language), cx);
8593 });
8594
8595 // Ensure that signature_help is not called when no signature help is enabled.
8596 cx.set_state(
8597 &r#"
8598 fn main() {
8599 sampleˇ
8600 }
8601 "#
8602 .unindent(),
8603 );
8604 cx.update_editor(|editor, window, cx| {
8605 editor.handle_input("(", window, cx);
8606 });
8607 cx.assert_editor_state(
8608 &"
8609 fn main() {
8610 sample(ˇ)
8611 }
8612 "
8613 .unindent(),
8614 );
8615 cx.editor(|editor, _, _| {
8616 assert!(editor.signature_help_state.task().is_none());
8617 });
8618
8619 let mocked_response = lsp::SignatureHelp {
8620 signatures: vec![lsp::SignatureInformation {
8621 label: "fn sample(param1: u8, param2: u8)".to_string(),
8622 documentation: None,
8623 parameters: Some(vec![
8624 lsp::ParameterInformation {
8625 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8626 documentation: None,
8627 },
8628 lsp::ParameterInformation {
8629 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8630 documentation: None,
8631 },
8632 ]),
8633 active_parameter: None,
8634 }],
8635 active_signature: Some(0),
8636 active_parameter: Some(0),
8637 };
8638
8639 // Ensure that signature_help is called when enabled afte edits
8640 cx.update(|_, cx| {
8641 cx.update_global::<SettingsStore, _>(|settings, cx| {
8642 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8643 settings.auto_signature_help = Some(false);
8644 settings.show_signature_help_after_edits = Some(true);
8645 });
8646 });
8647 });
8648 cx.set_state(
8649 &r#"
8650 fn main() {
8651 sampleˇ
8652 }
8653 "#
8654 .unindent(),
8655 );
8656 cx.update_editor(|editor, window, cx| {
8657 editor.handle_input("(", window, cx);
8658 });
8659 cx.assert_editor_state(
8660 &"
8661 fn main() {
8662 sample(ˇ)
8663 }
8664 "
8665 .unindent(),
8666 );
8667 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8668 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8669 .await;
8670 cx.update_editor(|editor, _, _| {
8671 let signature_help_state = editor.signature_help_state.popover().cloned();
8672 assert!(signature_help_state.is_some());
8673 assert_eq!(
8674 signature_help_state.unwrap().label,
8675 "param1: u8, param2: u8"
8676 );
8677 editor.signature_help_state = SignatureHelpState::default();
8678 });
8679
8680 // Ensure that signature_help is called when auto signature help override is enabled
8681 cx.update(|_, cx| {
8682 cx.update_global::<SettingsStore, _>(|settings, cx| {
8683 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8684 settings.auto_signature_help = Some(true);
8685 settings.show_signature_help_after_edits = Some(false);
8686 });
8687 });
8688 });
8689 cx.set_state(
8690 &r#"
8691 fn main() {
8692 sampleˇ
8693 }
8694 "#
8695 .unindent(),
8696 );
8697 cx.update_editor(|editor, window, cx| {
8698 editor.handle_input("(", window, cx);
8699 });
8700 cx.assert_editor_state(
8701 &"
8702 fn main() {
8703 sample(ˇ)
8704 }
8705 "
8706 .unindent(),
8707 );
8708 handle_signature_help_request(&mut cx, mocked_response).await;
8709 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8710 .await;
8711 cx.editor(|editor, _, _| {
8712 let signature_help_state = editor.signature_help_state.popover().cloned();
8713 assert!(signature_help_state.is_some());
8714 assert_eq!(
8715 signature_help_state.unwrap().label,
8716 "param1: u8, param2: u8"
8717 );
8718 });
8719}
8720
8721#[gpui::test]
8722async fn test_signature_help(cx: &mut TestAppContext) {
8723 init_test(cx, |_| {});
8724 cx.update(|cx| {
8725 cx.update_global::<SettingsStore, _>(|settings, cx| {
8726 settings.update_user_settings::<EditorSettings>(cx, |settings| {
8727 settings.auto_signature_help = Some(true);
8728 });
8729 });
8730 });
8731
8732 let mut cx = EditorLspTestContext::new_rust(
8733 lsp::ServerCapabilities {
8734 signature_help_provider: Some(lsp::SignatureHelpOptions {
8735 ..Default::default()
8736 }),
8737 ..Default::default()
8738 },
8739 cx,
8740 )
8741 .await;
8742
8743 // A test that directly calls `show_signature_help`
8744 cx.update_editor(|editor, window, cx| {
8745 editor.show_signature_help(&ShowSignatureHelp, window, cx);
8746 });
8747
8748 let mocked_response = lsp::SignatureHelp {
8749 signatures: vec![lsp::SignatureInformation {
8750 label: "fn sample(param1: u8, param2: u8)".to_string(),
8751 documentation: None,
8752 parameters: Some(vec![
8753 lsp::ParameterInformation {
8754 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8755 documentation: None,
8756 },
8757 lsp::ParameterInformation {
8758 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8759 documentation: None,
8760 },
8761 ]),
8762 active_parameter: None,
8763 }],
8764 active_signature: Some(0),
8765 active_parameter: Some(0),
8766 };
8767 handle_signature_help_request(&mut cx, mocked_response).await;
8768
8769 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8770 .await;
8771
8772 cx.editor(|editor, _, _| {
8773 let signature_help_state = editor.signature_help_state.popover().cloned();
8774 assert!(signature_help_state.is_some());
8775 assert_eq!(
8776 signature_help_state.unwrap().label,
8777 "param1: u8, param2: u8"
8778 );
8779 });
8780
8781 // When exiting outside from inside the brackets, `signature_help` is closed.
8782 cx.set_state(indoc! {"
8783 fn main() {
8784 sample(ˇ);
8785 }
8786
8787 fn sample(param1: u8, param2: u8) {}
8788 "});
8789
8790 cx.update_editor(|editor, window, cx| {
8791 editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
8792 });
8793
8794 let mocked_response = lsp::SignatureHelp {
8795 signatures: Vec::new(),
8796 active_signature: None,
8797 active_parameter: None,
8798 };
8799 handle_signature_help_request(&mut cx, mocked_response).await;
8800
8801 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8802 .await;
8803
8804 cx.editor(|editor, _, _| {
8805 assert!(!editor.signature_help_state.is_shown());
8806 });
8807
8808 // When entering inside the brackets from outside, `show_signature_help` is automatically called.
8809 cx.set_state(indoc! {"
8810 fn main() {
8811 sample(ˇ);
8812 }
8813
8814 fn sample(param1: u8, param2: u8) {}
8815 "});
8816
8817 let mocked_response = lsp::SignatureHelp {
8818 signatures: vec![lsp::SignatureInformation {
8819 label: "fn sample(param1: u8, param2: u8)".to_string(),
8820 documentation: None,
8821 parameters: Some(vec![
8822 lsp::ParameterInformation {
8823 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8824 documentation: None,
8825 },
8826 lsp::ParameterInformation {
8827 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8828 documentation: None,
8829 },
8830 ]),
8831 active_parameter: None,
8832 }],
8833 active_signature: Some(0),
8834 active_parameter: Some(0),
8835 };
8836 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8837 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8838 .await;
8839 cx.editor(|editor, _, _| {
8840 assert!(editor.signature_help_state.is_shown());
8841 });
8842
8843 // Restore the popover with more parameter input
8844 cx.set_state(indoc! {"
8845 fn main() {
8846 sample(param1, param2ˇ);
8847 }
8848
8849 fn sample(param1: u8, param2: u8) {}
8850 "});
8851
8852 let mocked_response = lsp::SignatureHelp {
8853 signatures: vec![lsp::SignatureInformation {
8854 label: "fn sample(param1: u8, param2: u8)".to_string(),
8855 documentation: None,
8856 parameters: Some(vec![
8857 lsp::ParameterInformation {
8858 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8859 documentation: None,
8860 },
8861 lsp::ParameterInformation {
8862 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8863 documentation: None,
8864 },
8865 ]),
8866 active_parameter: None,
8867 }],
8868 active_signature: Some(0),
8869 active_parameter: Some(1),
8870 };
8871 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8872 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8873 .await;
8874
8875 // When selecting a range, the popover is gone.
8876 // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
8877 cx.update_editor(|editor, window, cx| {
8878 editor.change_selections(None, window, cx, |s| {
8879 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8880 })
8881 });
8882 cx.assert_editor_state(indoc! {"
8883 fn main() {
8884 sample(param1, «ˇparam2»);
8885 }
8886
8887 fn sample(param1: u8, param2: u8) {}
8888 "});
8889 cx.editor(|editor, _, _| {
8890 assert!(!editor.signature_help_state.is_shown());
8891 });
8892
8893 // When unselecting again, the popover is back if within the brackets.
8894 cx.update_editor(|editor, window, cx| {
8895 editor.change_selections(None, window, cx, |s| {
8896 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8897 })
8898 });
8899 cx.assert_editor_state(indoc! {"
8900 fn main() {
8901 sample(param1, ˇparam2);
8902 }
8903
8904 fn sample(param1: u8, param2: u8) {}
8905 "});
8906 handle_signature_help_request(&mut cx, mocked_response).await;
8907 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8908 .await;
8909 cx.editor(|editor, _, _| {
8910 assert!(editor.signature_help_state.is_shown());
8911 });
8912
8913 // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
8914 cx.update_editor(|editor, window, cx| {
8915 editor.change_selections(None, window, cx, |s| {
8916 s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
8917 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8918 })
8919 });
8920 cx.assert_editor_state(indoc! {"
8921 fn main() {
8922 sample(param1, ˇparam2);
8923 }
8924
8925 fn sample(param1: u8, param2: u8) {}
8926 "});
8927
8928 let mocked_response = lsp::SignatureHelp {
8929 signatures: vec![lsp::SignatureInformation {
8930 label: "fn sample(param1: u8, param2: u8)".to_string(),
8931 documentation: None,
8932 parameters: Some(vec![
8933 lsp::ParameterInformation {
8934 label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
8935 documentation: None,
8936 },
8937 lsp::ParameterInformation {
8938 label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
8939 documentation: None,
8940 },
8941 ]),
8942 active_parameter: None,
8943 }],
8944 active_signature: Some(0),
8945 active_parameter: Some(1),
8946 };
8947 handle_signature_help_request(&mut cx, mocked_response.clone()).await;
8948 cx.condition(|editor, _| editor.signature_help_state.is_shown())
8949 .await;
8950 cx.update_editor(|editor, _, cx| {
8951 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
8952 });
8953 cx.condition(|editor, _| !editor.signature_help_state.is_shown())
8954 .await;
8955 cx.update_editor(|editor, window, cx| {
8956 editor.change_selections(None, window, cx, |s| {
8957 s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
8958 })
8959 });
8960 cx.assert_editor_state(indoc! {"
8961 fn main() {
8962 sample(param1, «ˇparam2»);
8963 }
8964
8965 fn sample(param1: u8, param2: u8) {}
8966 "});
8967 cx.update_editor(|editor, window, cx| {
8968 editor.change_selections(None, window, cx, |s| {
8969 s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
8970 })
8971 });
8972 cx.assert_editor_state(indoc! {"
8973 fn main() {
8974 sample(param1, ˇparam2);
8975 }
8976
8977 fn sample(param1: u8, param2: u8) {}
8978 "});
8979 cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
8980 .await;
8981}
8982
8983#[gpui::test]
8984async fn test_completion(cx: &mut TestAppContext) {
8985 init_test(cx, |_| {});
8986
8987 let mut cx = EditorLspTestContext::new_rust(
8988 lsp::ServerCapabilities {
8989 completion_provider: Some(lsp::CompletionOptions {
8990 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
8991 resolve_provider: Some(true),
8992 ..Default::default()
8993 }),
8994 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
8995 ..Default::default()
8996 },
8997 cx,
8998 )
8999 .await;
9000 let counter = Arc::new(AtomicUsize::new(0));
9001
9002 cx.set_state(indoc! {"
9003 oneˇ
9004 two
9005 three
9006 "});
9007 cx.simulate_keystroke(".");
9008 handle_completion_request(
9009 &mut cx,
9010 indoc! {"
9011 one.|<>
9012 two
9013 three
9014 "},
9015 vec!["first_completion", "second_completion"],
9016 counter.clone(),
9017 )
9018 .await;
9019 cx.condition(|editor, _| editor.context_menu_visible())
9020 .await;
9021 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9022
9023 let _handler = handle_signature_help_request(
9024 &mut cx,
9025 lsp::SignatureHelp {
9026 signatures: vec![lsp::SignatureInformation {
9027 label: "test signature".to_string(),
9028 documentation: None,
9029 parameters: Some(vec![lsp::ParameterInformation {
9030 label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
9031 documentation: None,
9032 }]),
9033 active_parameter: None,
9034 }],
9035 active_signature: None,
9036 active_parameter: None,
9037 },
9038 );
9039 cx.update_editor(|editor, window, cx| {
9040 assert!(
9041 !editor.signature_help_state.is_shown(),
9042 "No signature help was called for"
9043 );
9044 editor.show_signature_help(&ShowSignatureHelp, window, cx);
9045 });
9046 cx.run_until_parked();
9047 cx.update_editor(|editor, _, _| {
9048 assert!(
9049 !editor.signature_help_state.is_shown(),
9050 "No signature help should be shown when completions menu is open"
9051 );
9052 });
9053
9054 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9055 editor.context_menu_next(&Default::default(), window, cx);
9056 editor
9057 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9058 .unwrap()
9059 });
9060 cx.assert_editor_state(indoc! {"
9061 one.second_completionˇ
9062 two
9063 three
9064 "});
9065
9066 handle_resolve_completion_request(
9067 &mut cx,
9068 Some(vec![
9069 (
9070 //This overlaps with the primary completion edit which is
9071 //misbehavior from the LSP spec, test that we filter it out
9072 indoc! {"
9073 one.second_ˇcompletion
9074 two
9075 threeˇ
9076 "},
9077 "overlapping additional edit",
9078 ),
9079 (
9080 indoc! {"
9081 one.second_completion
9082 two
9083 threeˇ
9084 "},
9085 "\nadditional edit",
9086 ),
9087 ]),
9088 )
9089 .await;
9090 apply_additional_edits.await.unwrap();
9091 cx.assert_editor_state(indoc! {"
9092 one.second_completionˇ
9093 two
9094 three
9095 additional edit
9096 "});
9097
9098 cx.set_state(indoc! {"
9099 one.second_completion
9100 twoˇ
9101 threeˇ
9102 additional edit
9103 "});
9104 cx.simulate_keystroke(" ");
9105 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9106 cx.simulate_keystroke("s");
9107 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9108
9109 cx.assert_editor_state(indoc! {"
9110 one.second_completion
9111 two sˇ
9112 three sˇ
9113 additional edit
9114 "});
9115 handle_completion_request(
9116 &mut cx,
9117 indoc! {"
9118 one.second_completion
9119 two s
9120 three <s|>
9121 additional edit
9122 "},
9123 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
9124 counter.clone(),
9125 )
9126 .await;
9127 cx.condition(|editor, _| editor.context_menu_visible())
9128 .await;
9129 assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
9130
9131 cx.simulate_keystroke("i");
9132
9133 handle_completion_request(
9134 &mut cx,
9135 indoc! {"
9136 one.second_completion
9137 two si
9138 three <si|>
9139 additional edit
9140 "},
9141 vec!["fourth_completion", "fifth_completion", "sixth_completion"],
9142 counter.clone(),
9143 )
9144 .await;
9145 cx.condition(|editor, _| editor.context_menu_visible())
9146 .await;
9147 assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
9148
9149 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9150 editor
9151 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9152 .unwrap()
9153 });
9154 cx.assert_editor_state(indoc! {"
9155 one.second_completion
9156 two sixth_completionˇ
9157 three sixth_completionˇ
9158 additional edit
9159 "});
9160
9161 apply_additional_edits.await.unwrap();
9162
9163 update_test_language_settings(&mut cx, |settings| {
9164 settings.defaults.show_completions_on_input = Some(false);
9165 });
9166 cx.set_state("editorˇ");
9167 cx.simulate_keystroke(".");
9168 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9169 cx.simulate_keystroke("c");
9170 cx.simulate_keystroke("l");
9171 cx.simulate_keystroke("o");
9172 cx.assert_editor_state("editor.cloˇ");
9173 assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
9174 cx.update_editor(|editor, window, cx| {
9175 editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
9176 });
9177 handle_completion_request(
9178 &mut cx,
9179 "editor.<clo|>",
9180 vec!["close", "clobber"],
9181 counter.clone(),
9182 )
9183 .await;
9184 cx.condition(|editor, _| editor.context_menu_visible())
9185 .await;
9186 assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
9187
9188 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
9189 editor
9190 .confirm_completion(&ConfirmCompletion::default(), window, cx)
9191 .unwrap()
9192 });
9193 cx.assert_editor_state("editor.closeˇ");
9194 handle_resolve_completion_request(&mut cx, None).await;
9195 apply_additional_edits.await.unwrap();
9196}
9197
9198#[gpui::test]
9199async fn test_words_completion(cx: &mut TestAppContext) {
9200 let lsp_fetch_timeout_ms = 10;
9201 init_test(cx, |language_settings| {
9202 language_settings.defaults.completions = Some(CompletionSettings {
9203 words: WordsCompletionMode::Fallback,
9204 lsp: true,
9205 lsp_fetch_timeout_ms: 10,
9206 });
9207 });
9208
9209 let mut cx = EditorLspTestContext::new_rust(
9210 lsp::ServerCapabilities {
9211 completion_provider: Some(lsp::CompletionOptions {
9212 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9213 ..lsp::CompletionOptions::default()
9214 }),
9215 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9216 ..lsp::ServerCapabilities::default()
9217 },
9218 cx,
9219 )
9220 .await;
9221
9222 let throttle_completions = Arc::new(AtomicBool::new(false));
9223
9224 let lsp_throttle_completions = throttle_completions.clone();
9225 let _completion_requests_handler =
9226 cx.lsp
9227 .server
9228 .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
9229 let lsp_throttle_completions = lsp_throttle_completions.clone();
9230 async move {
9231 if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
9232 cx.background_executor()
9233 .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
9234 .await;
9235 }
9236 Ok(Some(lsp::CompletionResponse::Array(vec![
9237 lsp::CompletionItem {
9238 label: "first".into(),
9239 ..Default::default()
9240 },
9241 lsp::CompletionItem {
9242 label: "last".into(),
9243 ..Default::default()
9244 },
9245 ])))
9246 }
9247 });
9248
9249 cx.set_state(indoc! {"
9250 oneˇ
9251 two
9252 three
9253 "});
9254 cx.simulate_keystroke(".");
9255 cx.executor().run_until_parked();
9256 cx.condition(|editor, _| editor.context_menu_visible())
9257 .await;
9258 cx.update_editor(|editor, window, cx| {
9259 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9260 {
9261 assert_eq!(
9262 completion_menu_entries(&menu),
9263 &["first", "last"],
9264 "When LSP server is fast to reply, no fallback word completions are used"
9265 );
9266 } else {
9267 panic!("expected completion menu to be open");
9268 }
9269 editor.cancel(&Cancel, window, cx);
9270 });
9271 cx.executor().run_until_parked();
9272 cx.condition(|editor, _| !editor.context_menu_visible())
9273 .await;
9274
9275 throttle_completions.store(true, atomic::Ordering::Release);
9276 cx.simulate_keystroke(".");
9277 cx.executor()
9278 .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
9279 cx.executor().run_until_parked();
9280 cx.condition(|editor, _| editor.context_menu_visible())
9281 .await;
9282 cx.update_editor(|editor, _, _| {
9283 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9284 {
9285 assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
9286 "When LSP server is slow, document words can be shown instead, if configured accordingly");
9287 } else {
9288 panic!("expected completion menu to be open");
9289 }
9290 });
9291}
9292
9293#[gpui::test]
9294async fn test_multiline_completion(cx: &mut TestAppContext) {
9295 init_test(cx, |_| {});
9296
9297 let fs = FakeFs::new(cx.executor());
9298 fs.insert_tree(
9299 path!("/a"),
9300 json!({
9301 "main.ts": "a",
9302 }),
9303 )
9304 .await;
9305
9306 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
9307 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
9308 let typescript_language = Arc::new(Language::new(
9309 LanguageConfig {
9310 name: "TypeScript".into(),
9311 matcher: LanguageMatcher {
9312 path_suffixes: vec!["ts".to_string()],
9313 ..LanguageMatcher::default()
9314 },
9315 line_comments: vec!["// ".into()],
9316 ..LanguageConfig::default()
9317 },
9318 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
9319 ));
9320 language_registry.add(typescript_language.clone());
9321 let mut fake_servers = language_registry.register_fake_lsp(
9322 "TypeScript",
9323 FakeLspAdapter {
9324 capabilities: lsp::ServerCapabilities {
9325 completion_provider: Some(lsp::CompletionOptions {
9326 trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
9327 ..lsp::CompletionOptions::default()
9328 }),
9329 signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
9330 ..lsp::ServerCapabilities::default()
9331 },
9332 // Emulate vtsls label generation
9333 label_for_completion: Some(Box::new(|item, _| {
9334 let text = if let Some(description) = item
9335 .label_details
9336 .as_ref()
9337 .and_then(|label_details| label_details.description.as_ref())
9338 {
9339 format!("{} {}", item.label, description)
9340 } else if let Some(detail) = &item.detail {
9341 format!("{} {}", item.label, detail)
9342 } else {
9343 item.label.clone()
9344 };
9345 let len = text.len();
9346 Some(language::CodeLabel {
9347 text,
9348 runs: Vec::new(),
9349 filter_range: 0..len,
9350 })
9351 })),
9352 ..FakeLspAdapter::default()
9353 },
9354 );
9355 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
9356 let cx = &mut VisualTestContext::from_window(*workspace, cx);
9357 let worktree_id = workspace
9358 .update(cx, |workspace, _window, cx| {
9359 workspace.project().update(cx, |project, cx| {
9360 project.worktrees(cx).next().unwrap().read(cx).id()
9361 })
9362 })
9363 .unwrap();
9364 let _buffer = project
9365 .update(cx, |project, cx| {
9366 project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
9367 })
9368 .await
9369 .unwrap();
9370 let editor = workspace
9371 .update(cx, |workspace, window, cx| {
9372 workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
9373 })
9374 .unwrap()
9375 .await
9376 .unwrap()
9377 .downcast::<Editor>()
9378 .unwrap();
9379 let fake_server = fake_servers.next().await.unwrap();
9380
9381 let multiline_label = "StickyHeaderExcerpt {\n excerpt,\n next_excerpt_controls_present,\n next_buffer_row,\n }: StickyHeaderExcerpt<'_>,";
9382 let multiline_label_2 = "a\nb\nc\n";
9383 let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
9384 let multiline_description = "d\ne\nf\n";
9385 let multiline_detail_2 = "g\nh\ni\n";
9386
9387 let mut completion_handle =
9388 fake_server.handle_request::<lsp::request::Completion, _, _>(move |params, _| async move {
9389 Ok(Some(lsp::CompletionResponse::Array(vec![
9390 lsp::CompletionItem {
9391 label: multiline_label.to_string(),
9392 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9393 range: lsp::Range {
9394 start: lsp::Position {
9395 line: params.text_document_position.position.line,
9396 character: params.text_document_position.position.character,
9397 },
9398 end: lsp::Position {
9399 line: params.text_document_position.position.line,
9400 character: params.text_document_position.position.character,
9401 },
9402 },
9403 new_text: "new_text_1".to_string(),
9404 })),
9405 ..lsp::CompletionItem::default()
9406 },
9407 lsp::CompletionItem {
9408 label: "single line label 1".to_string(),
9409 detail: Some(multiline_detail.to_string()),
9410 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9411 range: lsp::Range {
9412 start: lsp::Position {
9413 line: params.text_document_position.position.line,
9414 character: params.text_document_position.position.character,
9415 },
9416 end: lsp::Position {
9417 line: params.text_document_position.position.line,
9418 character: params.text_document_position.position.character,
9419 },
9420 },
9421 new_text: "new_text_2".to_string(),
9422 })),
9423 ..lsp::CompletionItem::default()
9424 },
9425 lsp::CompletionItem {
9426 label: "single line label 2".to_string(),
9427 label_details: Some(lsp::CompletionItemLabelDetails {
9428 description: Some(multiline_description.to_string()),
9429 detail: None,
9430 }),
9431 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9432 range: lsp::Range {
9433 start: lsp::Position {
9434 line: params.text_document_position.position.line,
9435 character: params.text_document_position.position.character,
9436 },
9437 end: lsp::Position {
9438 line: params.text_document_position.position.line,
9439 character: params.text_document_position.position.character,
9440 },
9441 },
9442 new_text: "new_text_2".to_string(),
9443 })),
9444 ..lsp::CompletionItem::default()
9445 },
9446 lsp::CompletionItem {
9447 label: multiline_label_2.to_string(),
9448 detail: Some(multiline_detail_2.to_string()),
9449 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9450 range: lsp::Range {
9451 start: lsp::Position {
9452 line: params.text_document_position.position.line,
9453 character: params.text_document_position.position.character,
9454 },
9455 end: lsp::Position {
9456 line: params.text_document_position.position.line,
9457 character: params.text_document_position.position.character,
9458 },
9459 },
9460 new_text: "new_text_3".to_string(),
9461 })),
9462 ..lsp::CompletionItem::default()
9463 },
9464 lsp::CompletionItem {
9465 label: "Label with many spaces and \t but without newlines".to_string(),
9466 detail: Some(
9467 "Details with many spaces and \t but without newlines".to_string(),
9468 ),
9469 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9470 range: lsp::Range {
9471 start: lsp::Position {
9472 line: params.text_document_position.position.line,
9473 character: params.text_document_position.position.character,
9474 },
9475 end: lsp::Position {
9476 line: params.text_document_position.position.line,
9477 character: params.text_document_position.position.character,
9478 },
9479 },
9480 new_text: "new_text_4".to_string(),
9481 })),
9482 ..lsp::CompletionItem::default()
9483 },
9484 ])))
9485 });
9486
9487 editor.update_in(cx, |editor, window, cx| {
9488 cx.focus_self(window);
9489 editor.move_to_end(&MoveToEnd, window, cx);
9490 editor.handle_input(".", window, cx);
9491 });
9492 cx.run_until_parked();
9493 completion_handle.next().await.unwrap();
9494
9495 editor.update(cx, |editor, _| {
9496 assert!(editor.context_menu_visible());
9497 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9498 {
9499 let completion_labels = menu
9500 .completions
9501 .borrow()
9502 .iter()
9503 .map(|c| c.label.text.clone())
9504 .collect::<Vec<_>>();
9505 assert_eq!(
9506 completion_labels,
9507 &[
9508 "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
9509 "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
9510 "single line label 2 d e f ",
9511 "a b c g h i ",
9512 "Label with many spaces and \t but without newlines Details with many spaces and \t but without newlines",
9513 ],
9514 "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
9515 );
9516
9517 for completion in menu
9518 .completions
9519 .borrow()
9520 .iter() {
9521 assert_eq!(
9522 completion.label.filter_range,
9523 0..completion.label.text.len(),
9524 "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
9525 );
9526 }
9527
9528 } else {
9529 panic!("expected completion menu to be open");
9530 }
9531 });
9532}
9533
9534#[gpui::test]
9535async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
9536 init_test(cx, |_| {});
9537 let mut cx = EditorLspTestContext::new_rust(
9538 lsp::ServerCapabilities {
9539 completion_provider: Some(lsp::CompletionOptions {
9540 trigger_characters: Some(vec![".".to_string()]),
9541 ..Default::default()
9542 }),
9543 ..Default::default()
9544 },
9545 cx,
9546 )
9547 .await;
9548 cx.lsp
9549 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9550 Ok(Some(lsp::CompletionResponse::Array(vec![
9551 lsp::CompletionItem {
9552 label: "first".into(),
9553 ..Default::default()
9554 },
9555 lsp::CompletionItem {
9556 label: "last".into(),
9557 ..Default::default()
9558 },
9559 ])))
9560 });
9561 cx.set_state("variableˇ");
9562 cx.simulate_keystroke(".");
9563 cx.executor().run_until_parked();
9564
9565 cx.update_editor(|editor, _, _| {
9566 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9567 {
9568 assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
9569 } else {
9570 panic!("expected completion menu to be open");
9571 }
9572 });
9573
9574 cx.update_editor(|editor, window, cx| {
9575 editor.move_page_down(&MovePageDown::default(), window, cx);
9576 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9577 {
9578 assert!(
9579 menu.selected_item == 1,
9580 "expected PageDown to select the last item from the context menu"
9581 );
9582 } else {
9583 panic!("expected completion menu to stay open after PageDown");
9584 }
9585 });
9586
9587 cx.update_editor(|editor, window, cx| {
9588 editor.move_page_up(&MovePageUp::default(), window, cx);
9589 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9590 {
9591 assert!(
9592 menu.selected_item == 0,
9593 "expected PageUp to select the first item from the context menu"
9594 );
9595 } else {
9596 panic!("expected completion menu to stay open after PageUp");
9597 }
9598 });
9599}
9600
9601#[gpui::test]
9602async fn test_completion_sort(cx: &mut TestAppContext) {
9603 init_test(cx, |_| {});
9604 let mut cx = EditorLspTestContext::new_rust(
9605 lsp::ServerCapabilities {
9606 completion_provider: Some(lsp::CompletionOptions {
9607 trigger_characters: Some(vec![".".to_string()]),
9608 ..Default::default()
9609 }),
9610 ..Default::default()
9611 },
9612 cx,
9613 )
9614 .await;
9615 cx.lsp
9616 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
9617 Ok(Some(lsp::CompletionResponse::Array(vec![
9618 lsp::CompletionItem {
9619 label: "Range".into(),
9620 sort_text: Some("a".into()),
9621 ..Default::default()
9622 },
9623 lsp::CompletionItem {
9624 label: "r".into(),
9625 sort_text: Some("b".into()),
9626 ..Default::default()
9627 },
9628 lsp::CompletionItem {
9629 label: "ret".into(),
9630 sort_text: Some("c".into()),
9631 ..Default::default()
9632 },
9633 lsp::CompletionItem {
9634 label: "return".into(),
9635 sort_text: Some("d".into()),
9636 ..Default::default()
9637 },
9638 lsp::CompletionItem {
9639 label: "slice".into(),
9640 sort_text: Some("d".into()),
9641 ..Default::default()
9642 },
9643 ])))
9644 });
9645 cx.set_state("rˇ");
9646 cx.executor().run_until_parked();
9647 cx.update_editor(|editor, window, cx| {
9648 editor.show_completions(
9649 &ShowCompletions {
9650 trigger: Some("r".into()),
9651 },
9652 window,
9653 cx,
9654 );
9655 });
9656 cx.executor().run_until_parked();
9657
9658 cx.update_editor(|editor, _, _| {
9659 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
9660 {
9661 assert_eq!(
9662 completion_menu_entries(&menu),
9663 &["r", "ret", "Range", "return"]
9664 );
9665 } else {
9666 panic!("expected completion menu to be open");
9667 }
9668 });
9669}
9670
9671#[gpui::test]
9672async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
9673 init_test(cx, |_| {});
9674
9675 let mut cx = EditorLspTestContext::new_rust(
9676 lsp::ServerCapabilities {
9677 completion_provider: Some(lsp::CompletionOptions {
9678 trigger_characters: Some(vec![".".to_string()]),
9679 resolve_provider: Some(true),
9680 ..Default::default()
9681 }),
9682 ..Default::default()
9683 },
9684 cx,
9685 )
9686 .await;
9687
9688 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
9689 cx.simulate_keystroke(".");
9690 let completion_item = lsp::CompletionItem {
9691 label: "Some".into(),
9692 kind: Some(lsp::CompletionItemKind::SNIPPET),
9693 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
9694 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
9695 kind: lsp::MarkupKind::Markdown,
9696 value: "```rust\nSome(2)\n```".to_string(),
9697 })),
9698 deprecated: Some(false),
9699 sort_text: Some("Some".to_string()),
9700 filter_text: Some("Some".to_string()),
9701 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
9702 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
9703 range: lsp::Range {
9704 start: lsp::Position {
9705 line: 0,
9706 character: 22,
9707 },
9708 end: lsp::Position {
9709 line: 0,
9710 character: 22,
9711 },
9712 },
9713 new_text: "Some(2)".to_string(),
9714 })),
9715 additional_text_edits: Some(vec![lsp::TextEdit {
9716 range: lsp::Range {
9717 start: lsp::Position {
9718 line: 0,
9719 character: 20,
9720 },
9721 end: lsp::Position {
9722 line: 0,
9723 character: 22,
9724 },
9725 },
9726 new_text: "".to_string(),
9727 }]),
9728 ..Default::default()
9729 };
9730
9731 let closure_completion_item = completion_item.clone();
9732 let counter = Arc::new(AtomicUsize::new(0));
9733 let counter_clone = counter.clone();
9734 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
9735 let task_completion_item = closure_completion_item.clone();
9736 counter_clone.fetch_add(1, atomic::Ordering::Release);
9737 async move {
9738 Ok(Some(lsp::CompletionResponse::Array(vec![
9739 task_completion_item,
9740 ])))
9741 }
9742 });
9743
9744 cx.condition(|editor, _| editor.context_menu_visible())
9745 .await;
9746 cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
9747 assert!(request.next().await.is_some());
9748 assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
9749
9750 cx.simulate_keystroke("S");
9751 cx.simulate_keystroke("o");
9752 cx.simulate_keystroke("m");
9753 cx.condition(|editor, _| editor.context_menu_visible())
9754 .await;
9755 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
9756 assert!(request.next().await.is_some());
9757 assert!(request.next().await.is_some());
9758 assert!(request.next().await.is_some());
9759 request.close();
9760 assert!(request.next().await.is_none());
9761 assert_eq!(
9762 counter.load(atomic::Ordering::Acquire),
9763 4,
9764 "With the completions menu open, only one LSP request should happen per input"
9765 );
9766}
9767
9768#[gpui::test]
9769async fn test_toggle_comment(cx: &mut TestAppContext) {
9770 init_test(cx, |_| {});
9771 let mut cx = EditorTestContext::new(cx).await;
9772 let language = Arc::new(Language::new(
9773 LanguageConfig {
9774 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9775 ..Default::default()
9776 },
9777 Some(tree_sitter_rust::LANGUAGE.into()),
9778 ));
9779 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9780
9781 // If multiple selections intersect a line, the line is only toggled once.
9782 cx.set_state(indoc! {"
9783 fn a() {
9784 «//b();
9785 ˇ»// «c();
9786 //ˇ» d();
9787 }
9788 "});
9789
9790 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9791
9792 cx.assert_editor_state(indoc! {"
9793 fn a() {
9794 «b();
9795 c();
9796 ˇ» d();
9797 }
9798 "});
9799
9800 // The comment prefix is inserted at the same column for every line in a
9801 // selection.
9802 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9803
9804 cx.assert_editor_state(indoc! {"
9805 fn a() {
9806 // «b();
9807 // c();
9808 ˇ»// d();
9809 }
9810 "});
9811
9812 // If a selection ends at the beginning of a line, that line is not toggled.
9813 cx.set_selections_state(indoc! {"
9814 fn a() {
9815 // b();
9816 «// c();
9817 ˇ» // d();
9818 }
9819 "});
9820
9821 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9822
9823 cx.assert_editor_state(indoc! {"
9824 fn a() {
9825 // b();
9826 «c();
9827 ˇ» // d();
9828 }
9829 "});
9830
9831 // If a selection span a single line and is empty, the line is toggled.
9832 cx.set_state(indoc! {"
9833 fn a() {
9834 a();
9835 b();
9836 ˇ
9837 }
9838 "});
9839
9840 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9841
9842 cx.assert_editor_state(indoc! {"
9843 fn a() {
9844 a();
9845 b();
9846 //•ˇ
9847 }
9848 "});
9849
9850 // If a selection span multiple lines, empty lines are not toggled.
9851 cx.set_state(indoc! {"
9852 fn a() {
9853 «a();
9854
9855 c();ˇ»
9856 }
9857 "});
9858
9859 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9860
9861 cx.assert_editor_state(indoc! {"
9862 fn a() {
9863 // «a();
9864
9865 // c();ˇ»
9866 }
9867 "});
9868
9869 // If a selection includes multiple comment prefixes, all lines are uncommented.
9870 cx.set_state(indoc! {"
9871 fn a() {
9872 «// a();
9873 /// b();
9874 //! c();ˇ»
9875 }
9876 "});
9877
9878 cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
9879
9880 cx.assert_editor_state(indoc! {"
9881 fn a() {
9882 «a();
9883 b();
9884 c();ˇ»
9885 }
9886 "});
9887}
9888
9889#[gpui::test]
9890async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
9891 init_test(cx, |_| {});
9892 let mut cx = EditorTestContext::new(cx).await;
9893 let language = Arc::new(Language::new(
9894 LanguageConfig {
9895 line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
9896 ..Default::default()
9897 },
9898 Some(tree_sitter_rust::LANGUAGE.into()),
9899 ));
9900 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
9901
9902 let toggle_comments = &ToggleComments {
9903 advance_downwards: false,
9904 ignore_indent: true,
9905 };
9906
9907 // If multiple selections intersect a line, the line is only toggled once.
9908 cx.set_state(indoc! {"
9909 fn a() {
9910 // «b();
9911 // c();
9912 // ˇ» d();
9913 }
9914 "});
9915
9916 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9917
9918 cx.assert_editor_state(indoc! {"
9919 fn a() {
9920 «b();
9921 c();
9922 ˇ» d();
9923 }
9924 "});
9925
9926 // The comment prefix is inserted at the beginning of each line
9927 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9928
9929 cx.assert_editor_state(indoc! {"
9930 fn a() {
9931 // «b();
9932 // c();
9933 // ˇ» d();
9934 }
9935 "});
9936
9937 // If a selection ends at the beginning of a line, that line is not toggled.
9938 cx.set_selections_state(indoc! {"
9939 fn a() {
9940 // b();
9941 // «c();
9942 ˇ»// d();
9943 }
9944 "});
9945
9946 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9947
9948 cx.assert_editor_state(indoc! {"
9949 fn a() {
9950 // b();
9951 «c();
9952 ˇ»// d();
9953 }
9954 "});
9955
9956 // If a selection span a single line and is empty, the line is toggled.
9957 cx.set_state(indoc! {"
9958 fn a() {
9959 a();
9960 b();
9961 ˇ
9962 }
9963 "});
9964
9965 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9966
9967 cx.assert_editor_state(indoc! {"
9968 fn a() {
9969 a();
9970 b();
9971 //ˇ
9972 }
9973 "});
9974
9975 // If a selection span multiple lines, empty lines are not toggled.
9976 cx.set_state(indoc! {"
9977 fn a() {
9978 «a();
9979
9980 c();ˇ»
9981 }
9982 "});
9983
9984 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
9985
9986 cx.assert_editor_state(indoc! {"
9987 fn a() {
9988 // «a();
9989
9990 // c();ˇ»
9991 }
9992 "});
9993
9994 // If a selection includes multiple comment prefixes, all lines are uncommented.
9995 cx.set_state(indoc! {"
9996 fn a() {
9997 // «a();
9998 /// b();
9999 //! c();ˇ»
10000 }
10001 "});
10002
10003 cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10004
10005 cx.assert_editor_state(indoc! {"
10006 fn a() {
10007 «a();
10008 b();
10009 c();ˇ»
10010 }
10011 "});
10012}
10013
10014#[gpui::test]
10015async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
10016 init_test(cx, |_| {});
10017
10018 let language = Arc::new(Language::new(
10019 LanguageConfig {
10020 line_comments: vec!["// ".into()],
10021 ..Default::default()
10022 },
10023 Some(tree_sitter_rust::LANGUAGE.into()),
10024 ));
10025
10026 let mut cx = EditorTestContext::new(cx).await;
10027
10028 cx.language_registry().add(language.clone());
10029 cx.update_buffer(|buffer, cx| {
10030 buffer.set_language(Some(language), cx);
10031 });
10032
10033 let toggle_comments = &ToggleComments {
10034 advance_downwards: true,
10035 ignore_indent: false,
10036 };
10037
10038 // Single cursor on one line -> advance
10039 // Cursor moves horizontally 3 characters as well on non-blank line
10040 cx.set_state(indoc!(
10041 "fn a() {
10042 ˇdog();
10043 cat();
10044 }"
10045 ));
10046 cx.update_editor(|editor, window, cx| {
10047 editor.toggle_comments(toggle_comments, window, cx);
10048 });
10049 cx.assert_editor_state(indoc!(
10050 "fn a() {
10051 // dog();
10052 catˇ();
10053 }"
10054 ));
10055
10056 // Single selection on one line -> don't advance
10057 cx.set_state(indoc!(
10058 "fn a() {
10059 «dog()ˇ»;
10060 cat();
10061 }"
10062 ));
10063 cx.update_editor(|editor, window, cx| {
10064 editor.toggle_comments(toggle_comments, window, cx);
10065 });
10066 cx.assert_editor_state(indoc!(
10067 "fn a() {
10068 // «dog()ˇ»;
10069 cat();
10070 }"
10071 ));
10072
10073 // Multiple cursors on one line -> advance
10074 cx.set_state(indoc!(
10075 "fn a() {
10076 ˇdˇog();
10077 cat();
10078 }"
10079 ));
10080 cx.update_editor(|editor, window, cx| {
10081 editor.toggle_comments(toggle_comments, window, cx);
10082 });
10083 cx.assert_editor_state(indoc!(
10084 "fn a() {
10085 // dog();
10086 catˇ(ˇ);
10087 }"
10088 ));
10089
10090 // Multiple cursors on one line, with selection -> don't advance
10091 cx.set_state(indoc!(
10092 "fn a() {
10093 ˇdˇog«()ˇ»;
10094 cat();
10095 }"
10096 ));
10097 cx.update_editor(|editor, window, cx| {
10098 editor.toggle_comments(toggle_comments, window, cx);
10099 });
10100 cx.assert_editor_state(indoc!(
10101 "fn a() {
10102 // ˇdˇog«()ˇ»;
10103 cat();
10104 }"
10105 ));
10106
10107 // Single cursor on one line -> advance
10108 // Cursor moves to column 0 on blank line
10109 cx.set_state(indoc!(
10110 "fn a() {
10111 ˇdog();
10112
10113 cat();
10114 }"
10115 ));
10116 cx.update_editor(|editor, window, cx| {
10117 editor.toggle_comments(toggle_comments, window, cx);
10118 });
10119 cx.assert_editor_state(indoc!(
10120 "fn a() {
10121 // dog();
10122 ˇ
10123 cat();
10124 }"
10125 ));
10126
10127 // Single cursor on one line -> advance
10128 // Cursor starts and ends at column 0
10129 cx.set_state(indoc!(
10130 "fn a() {
10131 ˇ dog();
10132 cat();
10133 }"
10134 ));
10135 cx.update_editor(|editor, window, cx| {
10136 editor.toggle_comments(toggle_comments, window, cx);
10137 });
10138 cx.assert_editor_state(indoc!(
10139 "fn a() {
10140 // dog();
10141 ˇ cat();
10142 }"
10143 ));
10144}
10145
10146#[gpui::test]
10147async fn test_toggle_block_comment(cx: &mut TestAppContext) {
10148 init_test(cx, |_| {});
10149
10150 let mut cx = EditorTestContext::new(cx).await;
10151
10152 let html_language = Arc::new(
10153 Language::new(
10154 LanguageConfig {
10155 name: "HTML".into(),
10156 block_comment: Some(("<!-- ".into(), " -->".into())),
10157 ..Default::default()
10158 },
10159 Some(tree_sitter_html::LANGUAGE.into()),
10160 )
10161 .with_injection_query(
10162 r#"
10163 (script_element
10164 (raw_text) @injection.content
10165 (#set! injection.language "javascript"))
10166 "#,
10167 )
10168 .unwrap(),
10169 );
10170
10171 let javascript_language = Arc::new(Language::new(
10172 LanguageConfig {
10173 name: "JavaScript".into(),
10174 line_comments: vec!["// ".into()],
10175 ..Default::default()
10176 },
10177 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10178 ));
10179
10180 cx.language_registry().add(html_language.clone());
10181 cx.language_registry().add(javascript_language.clone());
10182 cx.update_buffer(|buffer, cx| {
10183 buffer.set_language(Some(html_language), cx);
10184 });
10185
10186 // Toggle comments for empty selections
10187 cx.set_state(
10188 &r#"
10189 <p>A</p>ˇ
10190 <p>B</p>ˇ
10191 <p>C</p>ˇ
10192 "#
10193 .unindent(),
10194 );
10195 cx.update_editor(|editor, window, cx| {
10196 editor.toggle_comments(&ToggleComments::default(), window, cx)
10197 });
10198 cx.assert_editor_state(
10199 &r#"
10200 <!-- <p>A</p>ˇ -->
10201 <!-- <p>B</p>ˇ -->
10202 <!-- <p>C</p>ˇ -->
10203 "#
10204 .unindent(),
10205 );
10206 cx.update_editor(|editor, window, cx| {
10207 editor.toggle_comments(&ToggleComments::default(), window, cx)
10208 });
10209 cx.assert_editor_state(
10210 &r#"
10211 <p>A</p>ˇ
10212 <p>B</p>ˇ
10213 <p>C</p>ˇ
10214 "#
10215 .unindent(),
10216 );
10217
10218 // Toggle comments for mixture of empty and non-empty selections, where
10219 // multiple selections occupy a given line.
10220 cx.set_state(
10221 &r#"
10222 <p>A«</p>
10223 <p>ˇ»B</p>ˇ
10224 <p>C«</p>
10225 <p>ˇ»D</p>ˇ
10226 "#
10227 .unindent(),
10228 );
10229
10230 cx.update_editor(|editor, window, cx| {
10231 editor.toggle_comments(&ToggleComments::default(), window, cx)
10232 });
10233 cx.assert_editor_state(
10234 &r#"
10235 <!-- <p>A«</p>
10236 <p>ˇ»B</p>ˇ -->
10237 <!-- <p>C«</p>
10238 <p>ˇ»D</p>ˇ -->
10239 "#
10240 .unindent(),
10241 );
10242 cx.update_editor(|editor, window, cx| {
10243 editor.toggle_comments(&ToggleComments::default(), window, cx)
10244 });
10245 cx.assert_editor_state(
10246 &r#"
10247 <p>A«</p>
10248 <p>ˇ»B</p>ˇ
10249 <p>C«</p>
10250 <p>ˇ»D</p>ˇ
10251 "#
10252 .unindent(),
10253 );
10254
10255 // Toggle comments when different languages are active for different
10256 // selections.
10257 cx.set_state(
10258 &r#"
10259 ˇ<script>
10260 ˇvar x = new Y();
10261 ˇ</script>
10262 "#
10263 .unindent(),
10264 );
10265 cx.executor().run_until_parked();
10266 cx.update_editor(|editor, window, cx| {
10267 editor.toggle_comments(&ToggleComments::default(), window, cx)
10268 });
10269 // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
10270 // Uncommenting and commenting from this position brings in even more wrong artifacts.
10271 cx.assert_editor_state(
10272 &r#"
10273 <!-- ˇ<script> -->
10274 // ˇvar x = new Y();
10275 <!-- ˇ</script> -->
10276 "#
10277 .unindent(),
10278 );
10279}
10280
10281#[gpui::test]
10282fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
10283 init_test(cx, |_| {});
10284
10285 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10286 let multibuffer = cx.new(|cx| {
10287 let mut multibuffer = MultiBuffer::new(ReadWrite);
10288 multibuffer.push_excerpts(
10289 buffer.clone(),
10290 [
10291 ExcerptRange {
10292 context: Point::new(0, 0)..Point::new(0, 4),
10293 primary: None,
10294 },
10295 ExcerptRange {
10296 context: Point::new(1, 0)..Point::new(1, 4),
10297 primary: None,
10298 },
10299 ],
10300 cx,
10301 );
10302 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
10303 multibuffer
10304 });
10305
10306 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10307 editor.update_in(cx, |editor, window, cx| {
10308 assert_eq!(editor.text(cx), "aaaa\nbbbb");
10309 editor.change_selections(None, window, cx, |s| {
10310 s.select_ranges([
10311 Point::new(0, 0)..Point::new(0, 0),
10312 Point::new(1, 0)..Point::new(1, 0),
10313 ])
10314 });
10315
10316 editor.handle_input("X", window, cx);
10317 assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
10318 assert_eq!(
10319 editor.selections.ranges(cx),
10320 [
10321 Point::new(0, 1)..Point::new(0, 1),
10322 Point::new(1, 1)..Point::new(1, 1),
10323 ]
10324 );
10325
10326 // Ensure the cursor's head is respected when deleting across an excerpt boundary.
10327 editor.change_selections(None, window, cx, |s| {
10328 s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
10329 });
10330 editor.backspace(&Default::default(), window, cx);
10331 assert_eq!(editor.text(cx), "Xa\nbbb");
10332 assert_eq!(
10333 editor.selections.ranges(cx),
10334 [Point::new(1, 0)..Point::new(1, 0)]
10335 );
10336
10337 editor.change_selections(None, window, cx, |s| {
10338 s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
10339 });
10340 editor.backspace(&Default::default(), window, cx);
10341 assert_eq!(editor.text(cx), "X\nbb");
10342 assert_eq!(
10343 editor.selections.ranges(cx),
10344 [Point::new(0, 1)..Point::new(0, 1)]
10345 );
10346 });
10347}
10348
10349#[gpui::test]
10350fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
10351 init_test(cx, |_| {});
10352
10353 let markers = vec![('[', ']').into(), ('(', ')').into()];
10354 let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
10355 indoc! {"
10356 [aaaa
10357 (bbbb]
10358 cccc)",
10359 },
10360 markers.clone(),
10361 );
10362 let excerpt_ranges = markers.into_iter().map(|marker| {
10363 let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
10364 ExcerptRange {
10365 context,
10366 primary: None,
10367 }
10368 });
10369 let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
10370 let multibuffer = cx.new(|cx| {
10371 let mut multibuffer = MultiBuffer::new(ReadWrite);
10372 multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
10373 multibuffer
10374 });
10375
10376 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10377 editor.update_in(cx, |editor, window, cx| {
10378 let (expected_text, selection_ranges) = marked_text_ranges(
10379 indoc! {"
10380 aaaa
10381 bˇbbb
10382 bˇbbˇb
10383 cccc"
10384 },
10385 true,
10386 );
10387 assert_eq!(editor.text(cx), expected_text);
10388 editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
10389
10390 editor.handle_input("X", window, cx);
10391
10392 let (expected_text, expected_selections) = marked_text_ranges(
10393 indoc! {"
10394 aaaa
10395 bXˇbbXb
10396 bXˇbbXˇb
10397 cccc"
10398 },
10399 false,
10400 );
10401 assert_eq!(editor.text(cx), expected_text);
10402 assert_eq!(editor.selections.ranges(cx), expected_selections);
10403
10404 editor.newline(&Newline, window, cx);
10405 let (expected_text, expected_selections) = marked_text_ranges(
10406 indoc! {"
10407 aaaa
10408 bX
10409 ˇbbX
10410 b
10411 bX
10412 ˇbbX
10413 ˇb
10414 cccc"
10415 },
10416 false,
10417 );
10418 assert_eq!(editor.text(cx), expected_text);
10419 assert_eq!(editor.selections.ranges(cx), expected_selections);
10420 });
10421}
10422
10423#[gpui::test]
10424fn test_refresh_selections(cx: &mut TestAppContext) {
10425 init_test(cx, |_| {});
10426
10427 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10428 let mut excerpt1_id = None;
10429 let multibuffer = cx.new(|cx| {
10430 let mut multibuffer = MultiBuffer::new(ReadWrite);
10431 excerpt1_id = multibuffer
10432 .push_excerpts(
10433 buffer.clone(),
10434 [
10435 ExcerptRange {
10436 context: Point::new(0, 0)..Point::new(1, 4),
10437 primary: None,
10438 },
10439 ExcerptRange {
10440 context: Point::new(1, 0)..Point::new(2, 4),
10441 primary: None,
10442 },
10443 ],
10444 cx,
10445 )
10446 .into_iter()
10447 .next();
10448 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10449 multibuffer
10450 });
10451
10452 let editor = cx.add_window(|window, cx| {
10453 let mut editor = build_editor(multibuffer.clone(), window, cx);
10454 let snapshot = editor.snapshot(window, cx);
10455 editor.change_selections(None, window, cx, |s| {
10456 s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
10457 });
10458 editor.begin_selection(
10459 Point::new(2, 1).to_display_point(&snapshot),
10460 true,
10461 1,
10462 window,
10463 cx,
10464 );
10465 assert_eq!(
10466 editor.selections.ranges(cx),
10467 [
10468 Point::new(1, 3)..Point::new(1, 3),
10469 Point::new(2, 1)..Point::new(2, 1),
10470 ]
10471 );
10472 editor
10473 });
10474
10475 // Refreshing selections is a no-op when excerpts haven't changed.
10476 _ = editor.update(cx, |editor, window, cx| {
10477 editor.change_selections(None, window, cx, |s| s.refresh());
10478 assert_eq!(
10479 editor.selections.ranges(cx),
10480 [
10481 Point::new(1, 3)..Point::new(1, 3),
10482 Point::new(2, 1)..Point::new(2, 1),
10483 ]
10484 );
10485 });
10486
10487 multibuffer.update(cx, |multibuffer, cx| {
10488 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10489 });
10490 _ = editor.update(cx, |editor, window, cx| {
10491 // Removing an excerpt causes the first selection to become degenerate.
10492 assert_eq!(
10493 editor.selections.ranges(cx),
10494 [
10495 Point::new(0, 0)..Point::new(0, 0),
10496 Point::new(0, 1)..Point::new(0, 1)
10497 ]
10498 );
10499
10500 // Refreshing selections will relocate the first selection to the original buffer
10501 // location.
10502 editor.change_selections(None, window, cx, |s| s.refresh());
10503 assert_eq!(
10504 editor.selections.ranges(cx),
10505 [
10506 Point::new(0, 1)..Point::new(0, 1),
10507 Point::new(0, 3)..Point::new(0, 3)
10508 ]
10509 );
10510 assert!(editor.selections.pending_anchor().is_some());
10511 });
10512}
10513
10514#[gpui::test]
10515fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
10516 init_test(cx, |_| {});
10517
10518 let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10519 let mut excerpt1_id = None;
10520 let multibuffer = cx.new(|cx| {
10521 let mut multibuffer = MultiBuffer::new(ReadWrite);
10522 excerpt1_id = multibuffer
10523 .push_excerpts(
10524 buffer.clone(),
10525 [
10526 ExcerptRange {
10527 context: Point::new(0, 0)..Point::new(1, 4),
10528 primary: None,
10529 },
10530 ExcerptRange {
10531 context: Point::new(1, 0)..Point::new(2, 4),
10532 primary: None,
10533 },
10534 ],
10535 cx,
10536 )
10537 .into_iter()
10538 .next();
10539 assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10540 multibuffer
10541 });
10542
10543 let editor = cx.add_window(|window, cx| {
10544 let mut editor = build_editor(multibuffer.clone(), window, cx);
10545 let snapshot = editor.snapshot(window, cx);
10546 editor.begin_selection(
10547 Point::new(1, 3).to_display_point(&snapshot),
10548 false,
10549 1,
10550 window,
10551 cx,
10552 );
10553 assert_eq!(
10554 editor.selections.ranges(cx),
10555 [Point::new(1, 3)..Point::new(1, 3)]
10556 );
10557 editor
10558 });
10559
10560 multibuffer.update(cx, |multibuffer, cx| {
10561 multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10562 });
10563 _ = editor.update(cx, |editor, window, cx| {
10564 assert_eq!(
10565 editor.selections.ranges(cx),
10566 [Point::new(0, 0)..Point::new(0, 0)]
10567 );
10568
10569 // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
10570 editor.change_selections(None, window, cx, |s| s.refresh());
10571 assert_eq!(
10572 editor.selections.ranges(cx),
10573 [Point::new(0, 3)..Point::new(0, 3)]
10574 );
10575 assert!(editor.selections.pending_anchor().is_some());
10576 });
10577}
10578
10579#[gpui::test]
10580async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
10581 init_test(cx, |_| {});
10582
10583 let language = Arc::new(
10584 Language::new(
10585 LanguageConfig {
10586 brackets: BracketPairConfig {
10587 pairs: vec![
10588 BracketPair {
10589 start: "{".to_string(),
10590 end: "}".to_string(),
10591 close: true,
10592 surround: true,
10593 newline: true,
10594 },
10595 BracketPair {
10596 start: "/* ".to_string(),
10597 end: " */".to_string(),
10598 close: true,
10599 surround: true,
10600 newline: true,
10601 },
10602 ],
10603 ..Default::default()
10604 },
10605 ..Default::default()
10606 },
10607 Some(tree_sitter_rust::LANGUAGE.into()),
10608 )
10609 .with_indents_query("")
10610 .unwrap(),
10611 );
10612
10613 let text = concat!(
10614 "{ }\n", //
10615 " x\n", //
10616 " /* */\n", //
10617 "x\n", //
10618 "{{} }\n", //
10619 );
10620
10621 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10622 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10623 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10624 editor
10625 .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10626 .await;
10627
10628 editor.update_in(cx, |editor, window, cx| {
10629 editor.change_selections(None, window, cx, |s| {
10630 s.select_display_ranges([
10631 DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
10632 DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
10633 DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
10634 ])
10635 });
10636 editor.newline(&Newline, window, cx);
10637
10638 assert_eq!(
10639 editor.buffer().read(cx).read(cx).text(),
10640 concat!(
10641 "{ \n", // Suppress rustfmt
10642 "\n", //
10643 "}\n", //
10644 " x\n", //
10645 " /* \n", //
10646 " \n", //
10647 " */\n", //
10648 "x\n", //
10649 "{{} \n", //
10650 "}\n", //
10651 )
10652 );
10653 });
10654}
10655
10656#[gpui::test]
10657fn test_highlighted_ranges(cx: &mut TestAppContext) {
10658 init_test(cx, |_| {});
10659
10660 let editor = cx.add_window(|window, cx| {
10661 let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
10662 build_editor(buffer.clone(), window, cx)
10663 });
10664
10665 _ = editor.update(cx, |editor, window, cx| {
10666 struct Type1;
10667 struct Type2;
10668
10669 let buffer = editor.buffer.read(cx).snapshot(cx);
10670
10671 let anchor_range =
10672 |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
10673
10674 editor.highlight_background::<Type1>(
10675 &[
10676 anchor_range(Point::new(2, 1)..Point::new(2, 3)),
10677 anchor_range(Point::new(4, 2)..Point::new(4, 4)),
10678 anchor_range(Point::new(6, 3)..Point::new(6, 5)),
10679 anchor_range(Point::new(8, 4)..Point::new(8, 6)),
10680 ],
10681 |_| Hsla::red(),
10682 cx,
10683 );
10684 editor.highlight_background::<Type2>(
10685 &[
10686 anchor_range(Point::new(3, 2)..Point::new(3, 5)),
10687 anchor_range(Point::new(5, 3)..Point::new(5, 6)),
10688 anchor_range(Point::new(7, 4)..Point::new(7, 7)),
10689 anchor_range(Point::new(9, 5)..Point::new(9, 8)),
10690 ],
10691 |_| Hsla::green(),
10692 cx,
10693 );
10694
10695 let snapshot = editor.snapshot(window, cx);
10696 let mut highlighted_ranges = editor.background_highlights_in_range(
10697 anchor_range(Point::new(3, 4)..Point::new(7, 4)),
10698 &snapshot,
10699 cx.theme().colors(),
10700 );
10701 // Enforce a consistent ordering based on color without relying on the ordering of the
10702 // highlight's `TypeId` which is non-executor.
10703 highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
10704 assert_eq!(
10705 highlighted_ranges,
10706 &[
10707 (
10708 DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
10709 Hsla::red(),
10710 ),
10711 (
10712 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10713 Hsla::red(),
10714 ),
10715 (
10716 DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
10717 Hsla::green(),
10718 ),
10719 (
10720 DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
10721 Hsla::green(),
10722 ),
10723 ]
10724 );
10725 assert_eq!(
10726 editor.background_highlights_in_range(
10727 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
10728 &snapshot,
10729 cx.theme().colors(),
10730 ),
10731 &[(
10732 DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
10733 Hsla::red(),
10734 )]
10735 );
10736 });
10737}
10738
10739#[gpui::test]
10740async fn test_following(cx: &mut TestAppContext) {
10741 init_test(cx, |_| {});
10742
10743 let fs = FakeFs::new(cx.executor());
10744 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10745
10746 let buffer = project.update(cx, |project, cx| {
10747 let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
10748 cx.new(|cx| MultiBuffer::singleton(buffer, cx))
10749 });
10750 let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
10751 let follower = cx.update(|cx| {
10752 cx.open_window(
10753 WindowOptions {
10754 window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
10755 gpui::Point::new(px(0.), px(0.)),
10756 gpui::Point::new(px(10.), px(80.)),
10757 ))),
10758 ..Default::default()
10759 },
10760 |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
10761 )
10762 .unwrap()
10763 });
10764
10765 let is_still_following = Rc::new(RefCell::new(true));
10766 let follower_edit_event_count = Rc::new(RefCell::new(0));
10767 let pending_update = Rc::new(RefCell::new(None));
10768 let leader_entity = leader.root(cx).unwrap();
10769 let follower_entity = follower.root(cx).unwrap();
10770 _ = follower.update(cx, {
10771 let update = pending_update.clone();
10772 let is_still_following = is_still_following.clone();
10773 let follower_edit_event_count = follower_edit_event_count.clone();
10774 |_, window, cx| {
10775 cx.subscribe_in(
10776 &leader_entity,
10777 window,
10778 move |_, leader, event, window, cx| {
10779 leader.read(cx).add_event_to_update_proto(
10780 event,
10781 &mut update.borrow_mut(),
10782 window,
10783 cx,
10784 );
10785 },
10786 )
10787 .detach();
10788
10789 cx.subscribe_in(
10790 &follower_entity,
10791 window,
10792 move |_, _, event: &EditorEvent, _window, _cx| {
10793 if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
10794 *is_still_following.borrow_mut() = false;
10795 }
10796
10797 if let EditorEvent::BufferEdited = event {
10798 *follower_edit_event_count.borrow_mut() += 1;
10799 }
10800 },
10801 )
10802 .detach();
10803 }
10804 });
10805
10806 // Update the selections only
10807 _ = leader.update(cx, |leader, window, cx| {
10808 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10809 });
10810 follower
10811 .update(cx, |follower, window, cx| {
10812 follower.apply_update_proto(
10813 &project,
10814 pending_update.borrow_mut().take().unwrap(),
10815 window,
10816 cx,
10817 )
10818 })
10819 .unwrap()
10820 .await
10821 .unwrap();
10822 _ = follower.update(cx, |follower, _, cx| {
10823 assert_eq!(follower.selections.ranges(cx), vec![1..1]);
10824 });
10825 assert!(*is_still_following.borrow());
10826 assert_eq!(*follower_edit_event_count.borrow(), 0);
10827
10828 // Update the scroll position only
10829 _ = leader.update(cx, |leader, window, cx| {
10830 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10831 });
10832 follower
10833 .update(cx, |follower, window, cx| {
10834 follower.apply_update_proto(
10835 &project,
10836 pending_update.borrow_mut().take().unwrap(),
10837 window,
10838 cx,
10839 )
10840 })
10841 .unwrap()
10842 .await
10843 .unwrap();
10844 assert_eq!(
10845 follower
10846 .update(cx, |follower, _, cx| follower.scroll_position(cx))
10847 .unwrap(),
10848 gpui::Point::new(1.5, 3.5)
10849 );
10850 assert!(*is_still_following.borrow());
10851 assert_eq!(*follower_edit_event_count.borrow(), 0);
10852
10853 // Update the selections and scroll position. The follower's scroll position is updated
10854 // via autoscroll, not via the leader's exact scroll position.
10855 _ = leader.update(cx, |leader, window, cx| {
10856 leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
10857 leader.request_autoscroll(Autoscroll::newest(), cx);
10858 leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
10859 });
10860 follower
10861 .update(cx, |follower, window, cx| {
10862 follower.apply_update_proto(
10863 &project,
10864 pending_update.borrow_mut().take().unwrap(),
10865 window,
10866 cx,
10867 )
10868 })
10869 .unwrap()
10870 .await
10871 .unwrap();
10872 _ = follower.update(cx, |follower, _, cx| {
10873 assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
10874 assert_eq!(follower.selections.ranges(cx), vec![0..0]);
10875 });
10876 assert!(*is_still_following.borrow());
10877
10878 // Creating a pending selection that precedes another selection
10879 _ = leader.update(cx, |leader, window, cx| {
10880 leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
10881 leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
10882 });
10883 follower
10884 .update(cx, |follower, window, cx| {
10885 follower.apply_update_proto(
10886 &project,
10887 pending_update.borrow_mut().take().unwrap(),
10888 window,
10889 cx,
10890 )
10891 })
10892 .unwrap()
10893 .await
10894 .unwrap();
10895 _ = follower.update(cx, |follower, _, cx| {
10896 assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
10897 });
10898 assert!(*is_still_following.borrow());
10899
10900 // Extend the pending selection so that it surrounds another selection
10901 _ = leader.update(cx, |leader, window, cx| {
10902 leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
10903 });
10904 follower
10905 .update(cx, |follower, window, cx| {
10906 follower.apply_update_proto(
10907 &project,
10908 pending_update.borrow_mut().take().unwrap(),
10909 window,
10910 cx,
10911 )
10912 })
10913 .unwrap()
10914 .await
10915 .unwrap();
10916 _ = follower.update(cx, |follower, _, cx| {
10917 assert_eq!(follower.selections.ranges(cx), vec![0..2]);
10918 });
10919
10920 // Scrolling locally breaks the follow
10921 _ = follower.update(cx, |follower, window, cx| {
10922 let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
10923 follower.set_scroll_anchor(
10924 ScrollAnchor {
10925 anchor: top_anchor,
10926 offset: gpui::Point::new(0.0, 0.5),
10927 },
10928 window,
10929 cx,
10930 );
10931 });
10932 assert!(!(*is_still_following.borrow()));
10933}
10934
10935#[gpui::test]
10936async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
10937 init_test(cx, |_| {});
10938
10939 let fs = FakeFs::new(cx.executor());
10940 let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
10941 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10942 let pane = workspace
10943 .update(cx, |workspace, _, _| workspace.active_pane().clone())
10944 .unwrap();
10945
10946 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10947
10948 let leader = pane.update_in(cx, |_, window, cx| {
10949 let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
10950 cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
10951 });
10952
10953 // Start following the editor when it has no excerpts.
10954 let mut state_message =
10955 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
10956 let workspace_entity = workspace.root(cx).unwrap();
10957 let follower_1 = cx
10958 .update_window(*workspace.deref(), |_, window, cx| {
10959 Editor::from_state_proto(
10960 workspace_entity,
10961 ViewId {
10962 creator: Default::default(),
10963 id: 0,
10964 },
10965 &mut state_message,
10966 window,
10967 cx,
10968 )
10969 })
10970 .unwrap()
10971 .unwrap()
10972 .await
10973 .unwrap();
10974
10975 let update_message = Rc::new(RefCell::new(None));
10976 follower_1.update_in(cx, {
10977 let update = update_message.clone();
10978 |_, window, cx| {
10979 cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
10980 leader.read(cx).add_event_to_update_proto(
10981 event,
10982 &mut update.borrow_mut(),
10983 window,
10984 cx,
10985 );
10986 })
10987 .detach();
10988 }
10989 });
10990
10991 let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
10992 (
10993 project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
10994 project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
10995 )
10996 });
10997
10998 // Insert some excerpts.
10999 leader.update(cx, |leader, cx| {
11000 leader.buffer.update(cx, |multibuffer, cx| {
11001 let excerpt_ids = multibuffer.push_excerpts(
11002 buffer_1.clone(),
11003 [
11004 ExcerptRange {
11005 context: 1..6,
11006 primary: None,
11007 },
11008 ExcerptRange {
11009 context: 12..15,
11010 primary: None,
11011 },
11012 ExcerptRange {
11013 context: 0..3,
11014 primary: None,
11015 },
11016 ],
11017 cx,
11018 );
11019 multibuffer.insert_excerpts_after(
11020 excerpt_ids[0],
11021 buffer_2.clone(),
11022 [
11023 ExcerptRange {
11024 context: 8..12,
11025 primary: None,
11026 },
11027 ExcerptRange {
11028 context: 0..6,
11029 primary: None,
11030 },
11031 ],
11032 cx,
11033 );
11034 });
11035 });
11036
11037 // Apply the update of adding the excerpts.
11038 follower_1
11039 .update_in(cx, |follower, window, cx| {
11040 follower.apply_update_proto(
11041 &project,
11042 update_message.borrow().clone().unwrap(),
11043 window,
11044 cx,
11045 )
11046 })
11047 .await
11048 .unwrap();
11049 assert_eq!(
11050 follower_1.update(cx, |editor, cx| editor.text(cx)),
11051 leader.update(cx, |editor, cx| editor.text(cx))
11052 );
11053 update_message.borrow_mut().take();
11054
11055 // Start following separately after it already has excerpts.
11056 let mut state_message =
11057 leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
11058 let workspace_entity = workspace.root(cx).unwrap();
11059 let follower_2 = cx
11060 .update_window(*workspace.deref(), |_, window, cx| {
11061 Editor::from_state_proto(
11062 workspace_entity,
11063 ViewId {
11064 creator: Default::default(),
11065 id: 0,
11066 },
11067 &mut state_message,
11068 window,
11069 cx,
11070 )
11071 })
11072 .unwrap()
11073 .unwrap()
11074 .await
11075 .unwrap();
11076 assert_eq!(
11077 follower_2.update(cx, |editor, cx| editor.text(cx)),
11078 leader.update(cx, |editor, cx| editor.text(cx))
11079 );
11080
11081 // Remove some excerpts.
11082 leader.update(cx, |leader, cx| {
11083 leader.buffer.update(cx, |multibuffer, cx| {
11084 let excerpt_ids = multibuffer.excerpt_ids();
11085 multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
11086 multibuffer.remove_excerpts([excerpt_ids[0]], cx);
11087 });
11088 });
11089
11090 // Apply the update of removing the excerpts.
11091 follower_1
11092 .update_in(cx, |follower, window, cx| {
11093 follower.apply_update_proto(
11094 &project,
11095 update_message.borrow().clone().unwrap(),
11096 window,
11097 cx,
11098 )
11099 })
11100 .await
11101 .unwrap();
11102 follower_2
11103 .update_in(cx, |follower, window, cx| {
11104 follower.apply_update_proto(
11105 &project,
11106 update_message.borrow().clone().unwrap(),
11107 window,
11108 cx,
11109 )
11110 })
11111 .await
11112 .unwrap();
11113 update_message.borrow_mut().take();
11114 assert_eq!(
11115 follower_1.update(cx, |editor, cx| editor.text(cx)),
11116 leader.update(cx, |editor, cx| editor.text(cx))
11117 );
11118}
11119
11120#[gpui::test]
11121async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11122 init_test(cx, |_| {});
11123
11124 let mut cx = EditorTestContext::new(cx).await;
11125 let lsp_store =
11126 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11127
11128 cx.set_state(indoc! {"
11129 ˇfn func(abc def: i32) -> u32 {
11130 }
11131 "});
11132
11133 cx.update(|_, cx| {
11134 lsp_store.update(cx, |lsp_store, cx| {
11135 lsp_store
11136 .update_diagnostics(
11137 LanguageServerId(0),
11138 lsp::PublishDiagnosticsParams {
11139 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11140 version: None,
11141 diagnostics: vec![
11142 lsp::Diagnostic {
11143 range: lsp::Range::new(
11144 lsp::Position::new(0, 11),
11145 lsp::Position::new(0, 12),
11146 ),
11147 severity: Some(lsp::DiagnosticSeverity::ERROR),
11148 ..Default::default()
11149 },
11150 lsp::Diagnostic {
11151 range: lsp::Range::new(
11152 lsp::Position::new(0, 12),
11153 lsp::Position::new(0, 15),
11154 ),
11155 severity: Some(lsp::DiagnosticSeverity::ERROR),
11156 ..Default::default()
11157 },
11158 lsp::Diagnostic {
11159 range: lsp::Range::new(
11160 lsp::Position::new(0, 25),
11161 lsp::Position::new(0, 28),
11162 ),
11163 severity: Some(lsp::DiagnosticSeverity::ERROR),
11164 ..Default::default()
11165 },
11166 ],
11167 },
11168 &[],
11169 cx,
11170 )
11171 .unwrap()
11172 });
11173 });
11174
11175 executor.run_until_parked();
11176
11177 cx.update_editor(|editor, window, cx| {
11178 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11179 });
11180
11181 cx.assert_editor_state(indoc! {"
11182 fn func(abc def: i32) -> ˇu32 {
11183 }
11184 "});
11185
11186 cx.update_editor(|editor, window, cx| {
11187 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11188 });
11189
11190 cx.assert_editor_state(indoc! {"
11191 fn func(abc ˇdef: i32) -> u32 {
11192 }
11193 "});
11194
11195 cx.update_editor(|editor, window, cx| {
11196 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11197 });
11198
11199 cx.assert_editor_state(indoc! {"
11200 fn func(abcˇ def: i32) -> u32 {
11201 }
11202 "});
11203
11204 cx.update_editor(|editor, window, cx| {
11205 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11206 });
11207
11208 cx.assert_editor_state(indoc! {"
11209 fn func(abc def: i32) -> ˇu32 {
11210 }
11211 "});
11212}
11213
11214#[gpui::test]
11215async fn cycle_through_same_place_diagnostics(
11216 executor: BackgroundExecutor,
11217 cx: &mut TestAppContext,
11218) {
11219 init_test(cx, |_| {});
11220
11221 let mut cx = EditorTestContext::new(cx).await;
11222 let lsp_store =
11223 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11224
11225 cx.set_state(indoc! {"
11226 ˇfn func(abc def: i32) -> u32 {
11227 }
11228 "});
11229
11230 cx.update(|_, cx| {
11231 lsp_store.update(cx, |lsp_store, cx| {
11232 lsp_store
11233 .update_diagnostics(
11234 LanguageServerId(0),
11235 lsp::PublishDiagnosticsParams {
11236 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11237 version: None,
11238 diagnostics: vec![
11239 lsp::Diagnostic {
11240 range: lsp::Range::new(
11241 lsp::Position::new(0, 11),
11242 lsp::Position::new(0, 12),
11243 ),
11244 severity: Some(lsp::DiagnosticSeverity::ERROR),
11245 ..Default::default()
11246 },
11247 lsp::Diagnostic {
11248 range: lsp::Range::new(
11249 lsp::Position::new(0, 12),
11250 lsp::Position::new(0, 15),
11251 ),
11252 severity: Some(lsp::DiagnosticSeverity::ERROR),
11253 ..Default::default()
11254 },
11255 lsp::Diagnostic {
11256 range: lsp::Range::new(
11257 lsp::Position::new(0, 12),
11258 lsp::Position::new(0, 15),
11259 ),
11260 severity: Some(lsp::DiagnosticSeverity::ERROR),
11261 ..Default::default()
11262 },
11263 lsp::Diagnostic {
11264 range: lsp::Range::new(
11265 lsp::Position::new(0, 25),
11266 lsp::Position::new(0, 28),
11267 ),
11268 severity: Some(lsp::DiagnosticSeverity::ERROR),
11269 ..Default::default()
11270 },
11271 ],
11272 },
11273 &[],
11274 cx,
11275 )
11276 .unwrap()
11277 });
11278 });
11279 executor.run_until_parked();
11280
11281 //// Backward
11282
11283 // Fourth diagnostic
11284 cx.update_editor(|editor, window, cx| {
11285 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11286 });
11287 cx.assert_editor_state(indoc! {"
11288 fn func(abc def: i32) -> ˇu32 {
11289 }
11290 "});
11291
11292 // Third diagnostic
11293 cx.update_editor(|editor, window, cx| {
11294 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11295 });
11296 cx.assert_editor_state(indoc! {"
11297 fn func(abc ˇdef: i32) -> u32 {
11298 }
11299 "});
11300
11301 // Second diagnostic, same place
11302 cx.update_editor(|editor, window, cx| {
11303 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11304 });
11305 cx.assert_editor_state(indoc! {"
11306 fn func(abc ˇdef: i32) -> u32 {
11307 }
11308 "});
11309
11310 // First diagnostic
11311 cx.update_editor(|editor, window, cx| {
11312 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11313 });
11314 cx.assert_editor_state(indoc! {"
11315 fn func(abcˇ def: i32) -> u32 {
11316 }
11317 "});
11318
11319 // Wrapped over, fourth diagnostic
11320 cx.update_editor(|editor, window, cx| {
11321 editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11322 });
11323 cx.assert_editor_state(indoc! {"
11324 fn func(abc def: i32) -> ˇu32 {
11325 }
11326 "});
11327
11328 cx.update_editor(|editor, window, cx| {
11329 editor.move_to_beginning(&MoveToBeginning, window, cx);
11330 });
11331 cx.assert_editor_state(indoc! {"
11332 ˇfn func(abc def: i32) -> u32 {
11333 }
11334 "});
11335
11336 //// Forward
11337
11338 // First diagnostic
11339 cx.update_editor(|editor, window, cx| {
11340 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11341 });
11342 cx.assert_editor_state(indoc! {"
11343 fn func(abcˇ def: i32) -> u32 {
11344 }
11345 "});
11346
11347 // Second diagnostic
11348 cx.update_editor(|editor, window, cx| {
11349 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11350 });
11351 cx.assert_editor_state(indoc! {"
11352 fn func(abc ˇdef: i32) -> u32 {
11353 }
11354 "});
11355
11356 // Third diagnostic, same place
11357 cx.update_editor(|editor, window, cx| {
11358 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11359 });
11360 cx.assert_editor_state(indoc! {"
11361 fn func(abc ˇdef: i32) -> u32 {
11362 }
11363 "});
11364
11365 // Fourth diagnostic
11366 cx.update_editor(|editor, window, cx| {
11367 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11368 });
11369 cx.assert_editor_state(indoc! {"
11370 fn func(abc def: i32) -> ˇu32 {
11371 }
11372 "});
11373
11374 // Wrapped around, first diagnostic
11375 cx.update_editor(|editor, window, cx| {
11376 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11377 });
11378 cx.assert_editor_state(indoc! {"
11379 fn func(abcˇ def: i32) -> u32 {
11380 }
11381 "});
11382}
11383
11384#[gpui::test]
11385async fn active_diagnostics_dismiss_after_invalidation(
11386 executor: BackgroundExecutor,
11387 cx: &mut TestAppContext,
11388) {
11389 init_test(cx, |_| {});
11390
11391 let mut cx = EditorTestContext::new(cx).await;
11392 let lsp_store =
11393 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11394
11395 cx.set_state(indoc! {"
11396 ˇfn func(abc def: i32) -> u32 {
11397 }
11398 "});
11399
11400 let message = "Something's wrong!";
11401 cx.update(|_, cx| {
11402 lsp_store.update(cx, |lsp_store, cx| {
11403 lsp_store
11404 .update_diagnostics(
11405 LanguageServerId(0),
11406 lsp::PublishDiagnosticsParams {
11407 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11408 version: None,
11409 diagnostics: vec![lsp::Diagnostic {
11410 range: lsp::Range::new(
11411 lsp::Position::new(0, 11),
11412 lsp::Position::new(0, 12),
11413 ),
11414 severity: Some(lsp::DiagnosticSeverity::ERROR),
11415 message: message.to_string(),
11416 ..Default::default()
11417 }],
11418 },
11419 &[],
11420 cx,
11421 )
11422 .unwrap()
11423 });
11424 });
11425 executor.run_until_parked();
11426
11427 cx.update_editor(|editor, window, cx| {
11428 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11429 assert_eq!(
11430 editor
11431 .active_diagnostics
11432 .as_ref()
11433 .map(|diagnostics_group| diagnostics_group.primary_message.as_str()),
11434 Some(message),
11435 "Should have a diagnostics group activated"
11436 );
11437 });
11438 cx.assert_editor_state(indoc! {"
11439 fn func(abcˇ def: i32) -> u32 {
11440 }
11441 "});
11442
11443 cx.update(|_, cx| {
11444 lsp_store.update(cx, |lsp_store, cx| {
11445 lsp_store
11446 .update_diagnostics(
11447 LanguageServerId(0),
11448 lsp::PublishDiagnosticsParams {
11449 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11450 version: None,
11451 diagnostics: Vec::new(),
11452 },
11453 &[],
11454 cx,
11455 )
11456 .unwrap()
11457 });
11458 });
11459 executor.run_until_parked();
11460 cx.update_editor(|editor, _, _| {
11461 assert_eq!(
11462 editor.active_diagnostics, None,
11463 "After no diagnostics set to the editor, no diagnostics should be active"
11464 );
11465 });
11466 cx.assert_editor_state(indoc! {"
11467 fn func(abcˇ def: i32) -> u32 {
11468 }
11469 "});
11470
11471 cx.update_editor(|editor, window, cx| {
11472 editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11473 assert_eq!(
11474 editor.active_diagnostics, None,
11475 "Should be no diagnostics to go to and activate"
11476 );
11477 });
11478 cx.assert_editor_state(indoc! {"
11479 fn func(abcˇ def: i32) -> u32 {
11480 }
11481 "});
11482}
11483
11484#[gpui::test]
11485async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
11486 init_test(cx, |_| {});
11487
11488 let mut cx = EditorTestContext::new(cx).await;
11489
11490 cx.set_state(indoc! {"
11491 fn func(abˇc def: i32) -> u32 {
11492 }
11493 "});
11494 let lsp_store =
11495 cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11496
11497 cx.update(|_, cx| {
11498 lsp_store.update(cx, |lsp_store, cx| {
11499 lsp_store.update_diagnostics(
11500 LanguageServerId(0),
11501 lsp::PublishDiagnosticsParams {
11502 uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11503 version: None,
11504 diagnostics: vec![lsp::Diagnostic {
11505 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
11506 severity: Some(lsp::DiagnosticSeverity::ERROR),
11507 message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
11508 ..Default::default()
11509 }],
11510 },
11511 &[],
11512 cx,
11513 )
11514 })
11515 }).unwrap();
11516 cx.run_until_parked();
11517 cx.update_editor(|editor, window, cx| {
11518 hover_popover::hover(editor, &Default::default(), window, cx)
11519 });
11520 cx.run_until_parked();
11521 cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
11522}
11523
11524#[gpui::test]
11525async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11526 init_test(cx, |_| {});
11527
11528 let mut cx = EditorTestContext::new(cx).await;
11529
11530 let diff_base = r#"
11531 use some::mod;
11532
11533 const A: u32 = 42;
11534
11535 fn main() {
11536 println!("hello");
11537
11538 println!("world");
11539 }
11540 "#
11541 .unindent();
11542
11543 // Edits are modified, removed, modified, added
11544 cx.set_state(
11545 &r#"
11546 use some::modified;
11547
11548 ˇ
11549 fn main() {
11550 println!("hello there");
11551
11552 println!("around the");
11553 println!("world");
11554 }
11555 "#
11556 .unindent(),
11557 );
11558
11559 cx.set_head_text(&diff_base);
11560 executor.run_until_parked();
11561
11562 cx.update_editor(|editor, window, cx| {
11563 //Wrap around the bottom of the buffer
11564 for _ in 0..3 {
11565 editor.go_to_next_hunk(&GoToHunk, window, cx);
11566 }
11567 });
11568
11569 cx.assert_editor_state(
11570 &r#"
11571 ˇuse some::modified;
11572
11573
11574 fn main() {
11575 println!("hello there");
11576
11577 println!("around the");
11578 println!("world");
11579 }
11580 "#
11581 .unindent(),
11582 );
11583
11584 cx.update_editor(|editor, window, cx| {
11585 //Wrap around the top of the buffer
11586 for _ in 0..2 {
11587 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11588 }
11589 });
11590
11591 cx.assert_editor_state(
11592 &r#"
11593 use some::modified;
11594
11595
11596 fn main() {
11597 ˇ println!("hello there");
11598
11599 println!("around the");
11600 println!("world");
11601 }
11602 "#
11603 .unindent(),
11604 );
11605
11606 cx.update_editor(|editor, window, cx| {
11607 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11608 });
11609
11610 cx.assert_editor_state(
11611 &r#"
11612 use some::modified;
11613
11614 ˇ
11615 fn main() {
11616 println!("hello there");
11617
11618 println!("around the");
11619 println!("world");
11620 }
11621 "#
11622 .unindent(),
11623 );
11624
11625 cx.update_editor(|editor, window, cx| {
11626 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11627 });
11628
11629 cx.assert_editor_state(
11630 &r#"
11631 ˇuse some::modified;
11632
11633
11634 fn main() {
11635 println!("hello there");
11636
11637 println!("around the");
11638 println!("world");
11639 }
11640 "#
11641 .unindent(),
11642 );
11643
11644 cx.update_editor(|editor, window, cx| {
11645 for _ in 0..2 {
11646 editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
11647 }
11648 });
11649
11650 cx.assert_editor_state(
11651 &r#"
11652 use some::modified;
11653
11654
11655 fn main() {
11656 ˇ println!("hello there");
11657
11658 println!("around the");
11659 println!("world");
11660 }
11661 "#
11662 .unindent(),
11663 );
11664
11665 cx.update_editor(|editor, window, cx| {
11666 editor.fold(&Fold, window, cx);
11667 });
11668
11669 cx.update_editor(|editor, window, cx| {
11670 editor.go_to_next_hunk(&GoToHunk, window, cx);
11671 });
11672
11673 cx.assert_editor_state(
11674 &r#"
11675 ˇuse some::modified;
11676
11677
11678 fn main() {
11679 println!("hello there");
11680
11681 println!("around the");
11682 println!("world");
11683 }
11684 "#
11685 .unindent(),
11686 );
11687}
11688
11689#[test]
11690fn test_split_words() {
11691 fn split(text: &str) -> Vec<&str> {
11692 split_words(text).collect()
11693 }
11694
11695 assert_eq!(split("HelloWorld"), &["Hello", "World"]);
11696 assert_eq!(split("hello_world"), &["hello_", "world"]);
11697 assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
11698 assert_eq!(split("Hello_World"), &["Hello_", "World"]);
11699 assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
11700 assert_eq!(split("helloworld"), &["helloworld"]);
11701
11702 assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
11703}
11704
11705#[gpui::test]
11706async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
11707 init_test(cx, |_| {});
11708
11709 let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
11710 let mut assert = |before, after| {
11711 let _state_context = cx.set_state(before);
11712 cx.run_until_parked();
11713 cx.update_editor(|editor, window, cx| {
11714 editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
11715 });
11716 cx.assert_editor_state(after);
11717 };
11718
11719 // Outside bracket jumps to outside of matching bracket
11720 assert("console.logˇ(var);", "console.log(var)ˇ;");
11721 assert("console.log(var)ˇ;", "console.logˇ(var);");
11722
11723 // Inside bracket jumps to inside of matching bracket
11724 assert("console.log(ˇvar);", "console.log(varˇ);");
11725 assert("console.log(varˇ);", "console.log(ˇvar);");
11726
11727 // When outside a bracket and inside, favor jumping to the inside bracket
11728 assert(
11729 "console.log('foo', [1, 2, 3]ˇ);",
11730 "console.log(ˇ'foo', [1, 2, 3]);",
11731 );
11732 assert(
11733 "console.log(ˇ'foo', [1, 2, 3]);",
11734 "console.log('foo', [1, 2, 3]ˇ);",
11735 );
11736
11737 // Bias forward if two options are equally likely
11738 assert(
11739 "let result = curried_fun()ˇ();",
11740 "let result = curried_fun()()ˇ;",
11741 );
11742
11743 // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
11744 assert(
11745 indoc! {"
11746 function test() {
11747 console.log('test')ˇ
11748 }"},
11749 indoc! {"
11750 function test() {
11751 console.logˇ('test')
11752 }"},
11753 );
11754}
11755
11756#[gpui::test]
11757async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
11758 init_test(cx, |_| {});
11759
11760 let fs = FakeFs::new(cx.executor());
11761 fs.insert_tree(
11762 path!("/a"),
11763 json!({
11764 "main.rs": "fn main() { let a = 5; }",
11765 "other.rs": "// Test file",
11766 }),
11767 )
11768 .await;
11769 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11770
11771 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11772 language_registry.add(Arc::new(Language::new(
11773 LanguageConfig {
11774 name: "Rust".into(),
11775 matcher: LanguageMatcher {
11776 path_suffixes: vec!["rs".to_string()],
11777 ..Default::default()
11778 },
11779 brackets: BracketPairConfig {
11780 pairs: vec![BracketPair {
11781 start: "{".to_string(),
11782 end: "}".to_string(),
11783 close: true,
11784 surround: true,
11785 newline: true,
11786 }],
11787 disabled_scopes_by_bracket_ix: Vec::new(),
11788 },
11789 ..Default::default()
11790 },
11791 Some(tree_sitter_rust::LANGUAGE.into()),
11792 )));
11793 let mut fake_servers = language_registry.register_fake_lsp(
11794 "Rust",
11795 FakeLspAdapter {
11796 capabilities: lsp::ServerCapabilities {
11797 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
11798 first_trigger_character: "{".to_string(),
11799 more_trigger_character: None,
11800 }),
11801 ..Default::default()
11802 },
11803 ..Default::default()
11804 },
11805 );
11806
11807 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11808
11809 let cx = &mut VisualTestContext::from_window(*workspace, cx);
11810
11811 let worktree_id = workspace
11812 .update(cx, |workspace, _, cx| {
11813 workspace.project().update(cx, |project, cx| {
11814 project.worktrees(cx).next().unwrap().read(cx).id()
11815 })
11816 })
11817 .unwrap();
11818
11819 let buffer = project
11820 .update(cx, |project, cx| {
11821 project.open_local_buffer(path!("/a/main.rs"), cx)
11822 })
11823 .await
11824 .unwrap();
11825 let editor_handle = workspace
11826 .update(cx, |workspace, window, cx| {
11827 workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
11828 })
11829 .unwrap()
11830 .await
11831 .unwrap()
11832 .downcast::<Editor>()
11833 .unwrap();
11834
11835 cx.executor().start_waiting();
11836 let fake_server = fake_servers.next().await.unwrap();
11837
11838 fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
11839 assert_eq!(
11840 params.text_document_position.text_document.uri,
11841 lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
11842 );
11843 assert_eq!(
11844 params.text_document_position.position,
11845 lsp::Position::new(0, 21),
11846 );
11847
11848 Ok(Some(vec![lsp::TextEdit {
11849 new_text: "]".to_string(),
11850 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
11851 }]))
11852 });
11853
11854 editor_handle.update_in(cx, |editor, window, cx| {
11855 window.focus(&editor.focus_handle(cx));
11856 editor.change_selections(None, window, cx, |s| {
11857 s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
11858 });
11859 editor.handle_input("{", window, cx);
11860 });
11861
11862 cx.executor().run_until_parked();
11863
11864 buffer.update(cx, |buffer, _| {
11865 assert_eq!(
11866 buffer.text(),
11867 "fn main() { let a = {5}; }",
11868 "No extra braces from on type formatting should appear in the buffer"
11869 )
11870 });
11871}
11872
11873#[gpui::test]
11874async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
11875 init_test(cx, |_| {});
11876
11877 let fs = FakeFs::new(cx.executor());
11878 fs.insert_tree(
11879 path!("/a"),
11880 json!({
11881 "main.rs": "fn main() { let a = 5; }",
11882 "other.rs": "// Test file",
11883 }),
11884 )
11885 .await;
11886
11887 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11888
11889 let server_restarts = Arc::new(AtomicUsize::new(0));
11890 let closure_restarts = Arc::clone(&server_restarts);
11891 let language_server_name = "test language server";
11892 let language_name: LanguageName = "Rust".into();
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: language_name.clone(),
11898 matcher: LanguageMatcher {
11899 path_suffixes: vec!["rs".to_string()],
11900 ..Default::default()
11901 },
11902 ..Default::default()
11903 },
11904 Some(tree_sitter_rust::LANGUAGE.into()),
11905 )));
11906 let mut fake_servers = language_registry.register_fake_lsp(
11907 "Rust",
11908 FakeLspAdapter {
11909 name: language_server_name,
11910 initialization_options: Some(json!({
11911 "testOptionValue": true
11912 })),
11913 initializer: Some(Box::new(move |fake_server| {
11914 let task_restarts = Arc::clone(&closure_restarts);
11915 fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
11916 task_restarts.fetch_add(1, atomic::Ordering::Release);
11917 futures::future::ready(Ok(()))
11918 });
11919 })),
11920 ..Default::default()
11921 },
11922 );
11923
11924 let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11925 let _buffer = project
11926 .update(cx, |project, cx| {
11927 project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
11928 })
11929 .await
11930 .unwrap();
11931 let _fake_server = fake_servers.next().await.unwrap();
11932 update_test_language_settings(cx, |language_settings| {
11933 language_settings.languages.insert(
11934 language_name.clone(),
11935 LanguageSettingsContent {
11936 tab_size: NonZeroU32::new(8),
11937 ..Default::default()
11938 },
11939 );
11940 });
11941 cx.executor().run_until_parked();
11942 assert_eq!(
11943 server_restarts.load(atomic::Ordering::Acquire),
11944 0,
11945 "Should not restart LSP server on an unrelated change"
11946 );
11947
11948 update_test_project_settings(cx, |project_settings| {
11949 project_settings.lsp.insert(
11950 "Some other server name".into(),
11951 LspSettings {
11952 binary: None,
11953 settings: None,
11954 initialization_options: Some(json!({
11955 "some other init value": false
11956 })),
11957 },
11958 );
11959 });
11960 cx.executor().run_until_parked();
11961 assert_eq!(
11962 server_restarts.load(atomic::Ordering::Acquire),
11963 0,
11964 "Should not restart LSP server on an unrelated LSP settings change"
11965 );
11966
11967 update_test_project_settings(cx, |project_settings| {
11968 project_settings.lsp.insert(
11969 language_server_name.into(),
11970 LspSettings {
11971 binary: None,
11972 settings: None,
11973 initialization_options: Some(json!({
11974 "anotherInitValue": false
11975 })),
11976 },
11977 );
11978 });
11979 cx.executor().run_until_parked();
11980 assert_eq!(
11981 server_restarts.load(atomic::Ordering::Acquire),
11982 1,
11983 "Should restart LSP server on a related LSP settings change"
11984 );
11985
11986 update_test_project_settings(cx, |project_settings| {
11987 project_settings.lsp.insert(
11988 language_server_name.into(),
11989 LspSettings {
11990 binary: None,
11991 settings: None,
11992 initialization_options: Some(json!({
11993 "anotherInitValue": false
11994 })),
11995 },
11996 );
11997 });
11998 cx.executor().run_until_parked();
11999 assert_eq!(
12000 server_restarts.load(atomic::Ordering::Acquire),
12001 1,
12002 "Should not restart LSP server on a related LSP settings change that is the same"
12003 );
12004
12005 update_test_project_settings(cx, |project_settings| {
12006 project_settings.lsp.insert(
12007 language_server_name.into(),
12008 LspSettings {
12009 binary: None,
12010 settings: None,
12011 initialization_options: None,
12012 },
12013 );
12014 });
12015 cx.executor().run_until_parked();
12016 assert_eq!(
12017 server_restarts.load(atomic::Ordering::Acquire),
12018 2,
12019 "Should restart LSP server on another related LSP settings change"
12020 );
12021}
12022
12023#[gpui::test]
12024async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
12025 init_test(cx, |_| {});
12026
12027 let mut cx = EditorLspTestContext::new_rust(
12028 lsp::ServerCapabilities {
12029 completion_provider: Some(lsp::CompletionOptions {
12030 trigger_characters: Some(vec![".".to_string()]),
12031 resolve_provider: Some(true),
12032 ..Default::default()
12033 }),
12034 ..Default::default()
12035 },
12036 cx,
12037 )
12038 .await;
12039
12040 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12041 cx.simulate_keystroke(".");
12042 let completion_item = lsp::CompletionItem {
12043 label: "some".into(),
12044 kind: Some(lsp::CompletionItemKind::SNIPPET),
12045 detail: Some("Wrap the expression in an `Option::Some`".to_string()),
12046 documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
12047 kind: lsp::MarkupKind::Markdown,
12048 value: "```rust\nSome(2)\n```".to_string(),
12049 })),
12050 deprecated: Some(false),
12051 sort_text: Some("fffffff2".to_string()),
12052 filter_text: Some("some".to_string()),
12053 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
12054 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12055 range: lsp::Range {
12056 start: lsp::Position {
12057 line: 0,
12058 character: 22,
12059 },
12060 end: lsp::Position {
12061 line: 0,
12062 character: 22,
12063 },
12064 },
12065 new_text: "Some(2)".to_string(),
12066 })),
12067 additional_text_edits: Some(vec![lsp::TextEdit {
12068 range: lsp::Range {
12069 start: lsp::Position {
12070 line: 0,
12071 character: 20,
12072 },
12073 end: lsp::Position {
12074 line: 0,
12075 character: 22,
12076 },
12077 },
12078 new_text: "".to_string(),
12079 }]),
12080 ..Default::default()
12081 };
12082
12083 let closure_completion_item = completion_item.clone();
12084 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12085 let task_completion_item = closure_completion_item.clone();
12086 async move {
12087 Ok(Some(lsp::CompletionResponse::Array(vec![
12088 task_completion_item,
12089 ])))
12090 }
12091 });
12092
12093 request.next().await;
12094
12095 cx.condition(|editor, _| editor.context_menu_visible())
12096 .await;
12097 let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12098 editor
12099 .confirm_completion(&ConfirmCompletion::default(), window, cx)
12100 .unwrap()
12101 });
12102 cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
12103
12104 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
12105 let task_completion_item = completion_item.clone();
12106 async move { Ok(task_completion_item) }
12107 })
12108 .next()
12109 .await
12110 .unwrap();
12111 apply_additional_edits.await.unwrap();
12112 cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
12113}
12114
12115#[gpui::test]
12116async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
12117 init_test(cx, |_| {});
12118
12119 let mut cx = EditorLspTestContext::new_rust(
12120 lsp::ServerCapabilities {
12121 completion_provider: Some(lsp::CompletionOptions {
12122 trigger_characters: Some(vec![".".to_string()]),
12123 resolve_provider: Some(true),
12124 ..Default::default()
12125 }),
12126 ..Default::default()
12127 },
12128 cx,
12129 )
12130 .await;
12131
12132 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12133 cx.simulate_keystroke(".");
12134
12135 let item1 = lsp::CompletionItem {
12136 label: "method id()".to_string(),
12137 filter_text: Some("id".to_string()),
12138 detail: None,
12139 documentation: None,
12140 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12141 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12142 new_text: ".id".to_string(),
12143 })),
12144 ..lsp::CompletionItem::default()
12145 };
12146
12147 let item2 = lsp::CompletionItem {
12148 label: "other".to_string(),
12149 filter_text: Some("other".to_string()),
12150 detail: None,
12151 documentation: None,
12152 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12153 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12154 new_text: ".other".to_string(),
12155 })),
12156 ..lsp::CompletionItem::default()
12157 };
12158
12159 let item1 = item1.clone();
12160 cx.handle_request::<lsp::request::Completion, _, _>({
12161 let item1 = item1.clone();
12162 move |_, _, _| {
12163 let item1 = item1.clone();
12164 let item2 = item2.clone();
12165 async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
12166 }
12167 })
12168 .next()
12169 .await;
12170
12171 cx.condition(|editor, _| editor.context_menu_visible())
12172 .await;
12173 cx.update_editor(|editor, _, _| {
12174 let context_menu = editor.context_menu.borrow_mut();
12175 let context_menu = context_menu
12176 .as_ref()
12177 .expect("Should have the context menu deployed");
12178 match context_menu {
12179 CodeContextMenu::Completions(completions_menu) => {
12180 let completions = completions_menu.completions.borrow_mut();
12181 assert_eq!(
12182 completions
12183 .iter()
12184 .map(|completion| &completion.label.text)
12185 .collect::<Vec<_>>(),
12186 vec!["method id()", "other"]
12187 )
12188 }
12189 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12190 }
12191 });
12192
12193 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
12194 let item1 = item1.clone();
12195 move |_, item_to_resolve, _| {
12196 let item1 = item1.clone();
12197 async move {
12198 if item1 == item_to_resolve {
12199 Ok(lsp::CompletionItem {
12200 label: "method id()".to_string(),
12201 filter_text: Some("id".to_string()),
12202 detail: Some("Now resolved!".to_string()),
12203 documentation: Some(lsp::Documentation::String("Docs".to_string())),
12204 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12205 range: lsp::Range::new(
12206 lsp::Position::new(0, 22),
12207 lsp::Position::new(0, 22),
12208 ),
12209 new_text: ".id".to_string(),
12210 })),
12211 ..lsp::CompletionItem::default()
12212 })
12213 } else {
12214 Ok(item_to_resolve)
12215 }
12216 }
12217 }
12218 })
12219 .next()
12220 .await
12221 .unwrap();
12222 cx.run_until_parked();
12223
12224 cx.update_editor(|editor, window, cx| {
12225 editor.context_menu_next(&Default::default(), window, cx);
12226 });
12227
12228 cx.update_editor(|editor, _, _| {
12229 let context_menu = editor.context_menu.borrow_mut();
12230 let context_menu = context_menu
12231 .as_ref()
12232 .expect("Should have the context menu deployed");
12233 match context_menu {
12234 CodeContextMenu::Completions(completions_menu) => {
12235 let completions = completions_menu.completions.borrow_mut();
12236 assert_eq!(
12237 completions
12238 .iter()
12239 .map(|completion| &completion.label.text)
12240 .collect::<Vec<_>>(),
12241 vec!["method id() Now resolved!", "other"],
12242 "Should update first completion label, but not second as the filter text did not match."
12243 );
12244 }
12245 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12246 }
12247 });
12248}
12249
12250#[gpui::test]
12251async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
12252 init_test(cx, |_| {});
12253
12254 let mut cx = EditorLspTestContext::new_rust(
12255 lsp::ServerCapabilities {
12256 completion_provider: Some(lsp::CompletionOptions {
12257 trigger_characters: Some(vec![".".to_string()]),
12258 resolve_provider: Some(true),
12259 ..Default::default()
12260 }),
12261 ..Default::default()
12262 },
12263 cx,
12264 )
12265 .await;
12266
12267 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12268 cx.simulate_keystroke(".");
12269
12270 let unresolved_item_1 = lsp::CompletionItem {
12271 label: "id".to_string(),
12272 filter_text: Some("id".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: ".id".to_string(),
12278 })),
12279 ..lsp::CompletionItem::default()
12280 };
12281 let resolved_item_1 = lsp::CompletionItem {
12282 additional_text_edits: Some(vec![lsp::TextEdit {
12283 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12284 new_text: "!!".to_string(),
12285 }]),
12286 ..unresolved_item_1.clone()
12287 };
12288 let unresolved_item_2 = lsp::CompletionItem {
12289 label: "other".to_string(),
12290 filter_text: Some("other".to_string()),
12291 detail: None,
12292 documentation: None,
12293 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12294 range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12295 new_text: ".other".to_string(),
12296 })),
12297 ..lsp::CompletionItem::default()
12298 };
12299 let resolved_item_2 = lsp::CompletionItem {
12300 additional_text_edits: Some(vec![lsp::TextEdit {
12301 range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12302 new_text: "??".to_string(),
12303 }]),
12304 ..unresolved_item_2.clone()
12305 };
12306
12307 let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
12308 let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
12309 cx.lsp
12310 .server
12311 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12312 let unresolved_item_1 = unresolved_item_1.clone();
12313 let resolved_item_1 = resolved_item_1.clone();
12314 let unresolved_item_2 = unresolved_item_2.clone();
12315 let resolved_item_2 = resolved_item_2.clone();
12316 let resolve_requests_1 = resolve_requests_1.clone();
12317 let resolve_requests_2 = resolve_requests_2.clone();
12318 move |unresolved_request, _| {
12319 let unresolved_item_1 = unresolved_item_1.clone();
12320 let resolved_item_1 = resolved_item_1.clone();
12321 let unresolved_item_2 = unresolved_item_2.clone();
12322 let resolved_item_2 = resolved_item_2.clone();
12323 let resolve_requests_1 = resolve_requests_1.clone();
12324 let resolve_requests_2 = resolve_requests_2.clone();
12325 async move {
12326 if unresolved_request == unresolved_item_1 {
12327 resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
12328 Ok(resolved_item_1.clone())
12329 } else if unresolved_request == unresolved_item_2 {
12330 resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
12331 Ok(resolved_item_2.clone())
12332 } else {
12333 panic!("Unexpected completion item {unresolved_request:?}")
12334 }
12335 }
12336 }
12337 })
12338 .detach();
12339
12340 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12341 let unresolved_item_1 = unresolved_item_1.clone();
12342 let unresolved_item_2 = unresolved_item_2.clone();
12343 async move {
12344 Ok(Some(lsp::CompletionResponse::Array(vec![
12345 unresolved_item_1,
12346 unresolved_item_2,
12347 ])))
12348 }
12349 })
12350 .next()
12351 .await;
12352
12353 cx.condition(|editor, _| editor.context_menu_visible())
12354 .await;
12355 cx.update_editor(|editor, _, _| {
12356 let context_menu = editor.context_menu.borrow_mut();
12357 let context_menu = context_menu
12358 .as_ref()
12359 .expect("Should have the context menu deployed");
12360 match context_menu {
12361 CodeContextMenu::Completions(completions_menu) => {
12362 let completions = completions_menu.completions.borrow_mut();
12363 assert_eq!(
12364 completions
12365 .iter()
12366 .map(|completion| &completion.label.text)
12367 .collect::<Vec<_>>(),
12368 vec!["id", "other"]
12369 )
12370 }
12371 CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12372 }
12373 });
12374 cx.run_until_parked();
12375
12376 cx.update_editor(|editor, window, cx| {
12377 editor.context_menu_next(&ContextMenuNext, window, cx);
12378 });
12379 cx.run_until_parked();
12380 cx.update_editor(|editor, window, cx| {
12381 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12382 });
12383 cx.run_until_parked();
12384 cx.update_editor(|editor, window, cx| {
12385 editor.context_menu_next(&ContextMenuNext, window, cx);
12386 });
12387 cx.run_until_parked();
12388 cx.update_editor(|editor, window, cx| {
12389 editor
12390 .compose_completion(&ComposeCompletion::default(), window, cx)
12391 .expect("No task returned")
12392 })
12393 .await
12394 .expect("Completion failed");
12395 cx.run_until_parked();
12396
12397 cx.update_editor(|editor, _, cx| {
12398 assert_eq!(
12399 resolve_requests_1.load(atomic::Ordering::Acquire),
12400 1,
12401 "Should always resolve once despite multiple selections"
12402 );
12403 assert_eq!(
12404 resolve_requests_2.load(atomic::Ordering::Acquire),
12405 1,
12406 "Should always resolve once after multiple selections and applying the completion"
12407 );
12408 assert_eq!(
12409 editor.text(cx),
12410 "fn main() { let a = ??.other; }",
12411 "Should use resolved data when applying the completion"
12412 );
12413 });
12414}
12415
12416#[gpui::test]
12417async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
12418 init_test(cx, |_| {});
12419
12420 let item_0 = lsp::CompletionItem {
12421 label: "abs".into(),
12422 insert_text: Some("abs".into()),
12423 data: Some(json!({ "very": "special"})),
12424 insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
12425 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12426 lsp::InsertReplaceEdit {
12427 new_text: "abs".to_string(),
12428 insert: lsp::Range::default(),
12429 replace: lsp::Range::default(),
12430 },
12431 )),
12432 ..lsp::CompletionItem::default()
12433 };
12434 let items = iter::once(item_0.clone())
12435 .chain((11..51).map(|i| lsp::CompletionItem {
12436 label: format!("item_{}", i),
12437 insert_text: Some(format!("item_{}", i)),
12438 insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
12439 ..lsp::CompletionItem::default()
12440 }))
12441 .collect::<Vec<_>>();
12442
12443 let default_commit_characters = vec!["?".to_string()];
12444 let default_data = json!({ "default": "data"});
12445 let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
12446 let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
12447 let default_edit_range = lsp::Range {
12448 start: lsp::Position {
12449 line: 0,
12450 character: 5,
12451 },
12452 end: lsp::Position {
12453 line: 0,
12454 character: 5,
12455 },
12456 };
12457
12458 let mut cx = EditorLspTestContext::new_rust(
12459 lsp::ServerCapabilities {
12460 completion_provider: Some(lsp::CompletionOptions {
12461 trigger_characters: Some(vec![".".to_string()]),
12462 resolve_provider: Some(true),
12463 ..Default::default()
12464 }),
12465 ..Default::default()
12466 },
12467 cx,
12468 )
12469 .await;
12470
12471 cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12472 cx.simulate_keystroke(".");
12473
12474 let completion_data = default_data.clone();
12475 let completion_characters = default_commit_characters.clone();
12476 let completion_items = items.clone();
12477 cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12478 let default_data = completion_data.clone();
12479 let default_commit_characters = completion_characters.clone();
12480 let items = completion_items.clone();
12481 async move {
12482 Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12483 items,
12484 item_defaults: Some(lsp::CompletionListItemDefaults {
12485 data: Some(default_data.clone()),
12486 commit_characters: Some(default_commit_characters.clone()),
12487 edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
12488 default_edit_range,
12489 )),
12490 insert_text_format: Some(default_insert_text_format),
12491 insert_text_mode: Some(default_insert_text_mode),
12492 }),
12493 ..lsp::CompletionList::default()
12494 })))
12495 }
12496 })
12497 .next()
12498 .await;
12499
12500 let resolved_items = Arc::new(Mutex::new(Vec::new()));
12501 cx.lsp
12502 .server
12503 .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12504 let closure_resolved_items = resolved_items.clone();
12505 move |item_to_resolve, _| {
12506 let closure_resolved_items = closure_resolved_items.clone();
12507 async move {
12508 closure_resolved_items.lock().push(item_to_resolve.clone());
12509 Ok(item_to_resolve)
12510 }
12511 }
12512 })
12513 .detach();
12514
12515 cx.condition(|editor, _| editor.context_menu_visible())
12516 .await;
12517 cx.run_until_parked();
12518 cx.update_editor(|editor, _, _| {
12519 let menu = editor.context_menu.borrow_mut();
12520 match menu.as_ref().expect("should have the completions menu") {
12521 CodeContextMenu::Completions(completions_menu) => {
12522 assert_eq!(
12523 completions_menu
12524 .entries
12525 .borrow()
12526 .iter()
12527 .map(|mat| mat.string.clone())
12528 .collect::<Vec<String>>(),
12529 items
12530 .iter()
12531 .map(|completion| completion.label.clone())
12532 .collect::<Vec<String>>()
12533 );
12534 }
12535 CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
12536 }
12537 });
12538 // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
12539 // with 4 from the end.
12540 assert_eq!(
12541 *resolved_items.lock(),
12542 [&items[0..16], &items[items.len() - 4..items.len()]]
12543 .concat()
12544 .iter()
12545 .cloned()
12546 .map(|mut item| {
12547 if item.data.is_none() {
12548 item.data = Some(default_data.clone());
12549 }
12550 item
12551 })
12552 .collect::<Vec<lsp::CompletionItem>>(),
12553 "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
12554 );
12555 resolved_items.lock().clear();
12556
12557 cx.update_editor(|editor, window, cx| {
12558 editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12559 });
12560 cx.run_until_parked();
12561 // Completions that have already been resolved are skipped.
12562 assert_eq!(
12563 *resolved_items.lock(),
12564 items[items.len() - 16..items.len() - 4]
12565 .iter()
12566 .cloned()
12567 .map(|mut item| {
12568 if item.data.is_none() {
12569 item.data = Some(default_data.clone());
12570 }
12571 item
12572 })
12573 .collect::<Vec<lsp::CompletionItem>>()
12574 );
12575 resolved_items.lock().clear();
12576}
12577
12578#[gpui::test]
12579async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
12580 init_test(cx, |_| {});
12581
12582 let mut cx = EditorLspTestContext::new(
12583 Language::new(
12584 LanguageConfig {
12585 matcher: LanguageMatcher {
12586 path_suffixes: vec!["jsx".into()],
12587 ..Default::default()
12588 },
12589 overrides: [(
12590 "element".into(),
12591 LanguageConfigOverride {
12592 word_characters: Override::Set(['-'].into_iter().collect()),
12593 ..Default::default()
12594 },
12595 )]
12596 .into_iter()
12597 .collect(),
12598 ..Default::default()
12599 },
12600 Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
12601 )
12602 .with_override_query("(jsx_self_closing_element) @element")
12603 .unwrap(),
12604 lsp::ServerCapabilities {
12605 completion_provider: Some(lsp::CompletionOptions {
12606 trigger_characters: Some(vec![":".to_string()]),
12607 ..Default::default()
12608 }),
12609 ..Default::default()
12610 },
12611 cx,
12612 )
12613 .await;
12614
12615 cx.lsp
12616 .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
12617 Ok(Some(lsp::CompletionResponse::Array(vec![
12618 lsp::CompletionItem {
12619 label: "bg-blue".into(),
12620 ..Default::default()
12621 },
12622 lsp::CompletionItem {
12623 label: "bg-red".into(),
12624 ..Default::default()
12625 },
12626 lsp::CompletionItem {
12627 label: "bg-yellow".into(),
12628 ..Default::default()
12629 },
12630 ])))
12631 });
12632
12633 cx.set_state(r#"<p class="bgˇ" />"#);
12634
12635 // Trigger completion when typing a dash, because the dash is an extra
12636 // word character in the 'element' scope, which contains the cursor.
12637 cx.simulate_keystroke("-");
12638 cx.executor().run_until_parked();
12639 cx.update_editor(|editor, _, _| {
12640 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12641 {
12642 assert_eq!(
12643 completion_menu_entries(&menu),
12644 &["bg-red", "bg-blue", "bg-yellow"]
12645 );
12646 } else {
12647 panic!("expected completion menu to be open");
12648 }
12649 });
12650
12651 cx.simulate_keystroke("l");
12652 cx.executor().run_until_parked();
12653 cx.update_editor(|editor, _, _| {
12654 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12655 {
12656 assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
12657 } else {
12658 panic!("expected completion menu to be open");
12659 }
12660 });
12661
12662 // When filtering completions, consider the character after the '-' to
12663 // be the start of a subword.
12664 cx.set_state(r#"<p class="yelˇ" />"#);
12665 cx.simulate_keystroke("l");
12666 cx.executor().run_until_parked();
12667 cx.update_editor(|editor, _, _| {
12668 if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
12669 {
12670 assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
12671 } else {
12672 panic!("expected completion menu to be open");
12673 }
12674 });
12675}
12676
12677fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
12678 let entries = menu.entries.borrow();
12679 entries.iter().map(|mat| mat.string.clone()).collect()
12680}
12681
12682#[gpui::test]
12683async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
12684 init_test(cx, |settings| {
12685 settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
12686 FormatterList(vec![Formatter::Prettier].into()),
12687 ))
12688 });
12689
12690 let fs = FakeFs::new(cx.executor());
12691 fs.insert_file(path!("/file.ts"), Default::default()).await;
12692
12693 let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
12694 let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12695
12696 language_registry.add(Arc::new(Language::new(
12697 LanguageConfig {
12698 name: "TypeScript".into(),
12699 matcher: LanguageMatcher {
12700 path_suffixes: vec!["ts".to_string()],
12701 ..Default::default()
12702 },
12703 ..Default::default()
12704 },
12705 Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12706 )));
12707 update_test_language_settings(cx, |settings| {
12708 settings.defaults.prettier = Some(PrettierSettings {
12709 allowed: true,
12710 ..PrettierSettings::default()
12711 });
12712 });
12713
12714 let test_plugin = "test_plugin";
12715 let _ = language_registry.register_fake_lsp(
12716 "TypeScript",
12717 FakeLspAdapter {
12718 prettier_plugins: vec![test_plugin],
12719 ..Default::default()
12720 },
12721 );
12722
12723 let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
12724 let buffer = project
12725 .update(cx, |project, cx| {
12726 project.open_local_buffer(path!("/file.ts"), cx)
12727 })
12728 .await
12729 .unwrap();
12730
12731 let buffer_text = "one\ntwo\nthree\n";
12732 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12733 let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12734 editor.update_in(cx, |editor, window, cx| {
12735 editor.set_text(buffer_text, window, cx)
12736 });
12737
12738 editor
12739 .update_in(cx, |editor, window, cx| {
12740 editor.perform_format(
12741 project.clone(),
12742 FormatTrigger::Manual,
12743 FormatTarget::Buffers,
12744 window,
12745 cx,
12746 )
12747 })
12748 .unwrap()
12749 .await;
12750 assert_eq!(
12751 editor.update(cx, |editor, cx| editor.text(cx)),
12752 buffer_text.to_string() + prettier_format_suffix,
12753 "Test prettier formatting was not applied to the original buffer text",
12754 );
12755
12756 update_test_language_settings(cx, |settings| {
12757 settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
12758 });
12759 let format = editor.update_in(cx, |editor, window, cx| {
12760 editor.perform_format(
12761 project.clone(),
12762 FormatTrigger::Manual,
12763 FormatTarget::Buffers,
12764 window,
12765 cx,
12766 )
12767 });
12768 format.await.unwrap();
12769 assert_eq!(
12770 editor.update(cx, |editor, cx| editor.text(cx)),
12771 buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
12772 "Autoformatting (via test prettier) was not applied to the original buffer text",
12773 );
12774}
12775
12776#[gpui::test]
12777async fn test_addition_reverts(cx: &mut TestAppContext) {
12778 init_test(cx, |_| {});
12779 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12780 let base_text = indoc! {r#"
12781 struct Row;
12782 struct Row1;
12783 struct Row2;
12784
12785 struct Row4;
12786 struct Row5;
12787 struct Row6;
12788
12789 struct Row8;
12790 struct Row9;
12791 struct Row10;"#};
12792
12793 // When addition hunks are not adjacent to carets, no hunk revert is performed
12794 assert_hunk_revert(
12795 indoc! {r#"struct Row;
12796 struct Row1;
12797 struct Row1.1;
12798 struct Row1.2;
12799 struct Row2;ˇ
12800
12801 struct Row4;
12802 struct Row5;
12803 struct Row6;
12804
12805 struct Row8;
12806 ˇstruct Row9;
12807 struct Row9.1;
12808 struct Row9.2;
12809 struct Row9.3;
12810 struct Row10;"#},
12811 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
12812 indoc! {r#"struct Row;
12813 struct Row1;
12814 struct Row1.1;
12815 struct Row1.2;
12816 struct Row2;ˇ
12817
12818 struct Row4;
12819 struct Row5;
12820 struct Row6;
12821
12822 struct Row8;
12823 ˇstruct Row9;
12824 struct Row9.1;
12825 struct Row9.2;
12826 struct Row9.3;
12827 struct Row10;"#},
12828 base_text,
12829 &mut cx,
12830 );
12831 // Same for selections
12832 assert_hunk_revert(
12833 indoc! {r#"struct Row;
12834 struct Row1;
12835 struct Row2;
12836 struct Row2.1;
12837 struct Row2.2;
12838 «ˇ
12839 struct Row4;
12840 struct» Row5;
12841 «struct Row6;
12842 ˇ»
12843 struct Row9.1;
12844 struct Row9.2;
12845 struct Row9.3;
12846 struct Row8;
12847 struct Row9;
12848 struct Row10;"#},
12849 vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
12850 indoc! {r#"struct Row;
12851 struct Row1;
12852 struct Row2;
12853 struct Row2.1;
12854 struct Row2.2;
12855 «ˇ
12856 struct Row4;
12857 struct» Row5;
12858 «struct Row6;
12859 ˇ»
12860 struct Row9.1;
12861 struct Row9.2;
12862 struct Row9.3;
12863 struct Row8;
12864 struct Row9;
12865 struct Row10;"#},
12866 base_text,
12867 &mut cx,
12868 );
12869
12870 // When carets and selections intersect the addition hunks, those are reverted.
12871 // Adjacent carets got merged.
12872 assert_hunk_revert(
12873 indoc! {r#"struct Row;
12874 ˇ// something on the top
12875 struct Row1;
12876 struct Row2;
12877 struct Roˇw3.1;
12878 struct Row2.2;
12879 struct Row2.3;ˇ
12880
12881 struct Row4;
12882 struct ˇRow5.1;
12883 struct Row5.2;
12884 struct «Rowˇ»5.3;
12885 struct Row5;
12886 struct Row6;
12887 ˇ
12888 struct Row9.1;
12889 struct «Rowˇ»9.2;
12890 struct «ˇRow»9.3;
12891 struct Row8;
12892 struct Row9;
12893 «ˇ// something on bottom»
12894 struct Row10;"#},
12895 vec![
12896 DiffHunkStatusKind::Added,
12897 DiffHunkStatusKind::Added,
12898 DiffHunkStatusKind::Added,
12899 DiffHunkStatusKind::Added,
12900 DiffHunkStatusKind::Added,
12901 ],
12902 indoc! {r#"struct Row;
12903 ˇstruct Row1;
12904 struct Row2;
12905 ˇ
12906 struct Row4;
12907 ˇstruct Row5;
12908 struct Row6;
12909 ˇ
12910 ˇstruct Row8;
12911 struct Row9;
12912 ˇstruct Row10;"#},
12913 base_text,
12914 &mut cx,
12915 );
12916}
12917
12918#[gpui::test]
12919async fn test_modification_reverts(cx: &mut TestAppContext) {
12920 init_test(cx, |_| {});
12921 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
12922 let base_text = indoc! {r#"
12923 struct Row;
12924 struct Row1;
12925 struct Row2;
12926
12927 struct Row4;
12928 struct Row5;
12929 struct Row6;
12930
12931 struct Row8;
12932 struct Row9;
12933 struct Row10;"#};
12934
12935 // Modification hunks behave the same as the addition ones.
12936 assert_hunk_revert(
12937 indoc! {r#"struct Row;
12938 struct Row1;
12939 struct Row33;
12940 ˇ
12941 struct Row4;
12942 struct Row5;
12943 struct Row6;
12944 ˇ
12945 struct Row99;
12946 struct Row9;
12947 struct Row10;"#},
12948 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
12949 indoc! {r#"struct Row;
12950 struct Row1;
12951 struct Row33;
12952 ˇ
12953 struct Row4;
12954 struct Row5;
12955 struct Row6;
12956 ˇ
12957 struct Row99;
12958 struct Row9;
12959 struct Row10;"#},
12960 base_text,
12961 &mut cx,
12962 );
12963 assert_hunk_revert(
12964 indoc! {r#"struct Row;
12965 struct Row1;
12966 struct Row33;
12967 «ˇ
12968 struct Row4;
12969 struct» Row5;
12970 «struct Row6;
12971 ˇ»
12972 struct Row99;
12973 struct Row9;
12974 struct Row10;"#},
12975 vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
12976 indoc! {r#"struct Row;
12977 struct Row1;
12978 struct Row33;
12979 «ˇ
12980 struct Row4;
12981 struct» Row5;
12982 «struct Row6;
12983 ˇ»
12984 struct Row99;
12985 struct Row9;
12986 struct Row10;"#},
12987 base_text,
12988 &mut cx,
12989 );
12990
12991 assert_hunk_revert(
12992 indoc! {r#"ˇstruct Row1.1;
12993 struct Row1;
12994 «ˇstr»uct Row22;
12995
12996 struct ˇRow44;
12997 struct Row5;
12998 struct «Rˇ»ow66;ˇ
12999
13000 «struˇ»ct Row88;
13001 struct Row9;
13002 struct Row1011;ˇ"#},
13003 vec![
13004 DiffHunkStatusKind::Modified,
13005 DiffHunkStatusKind::Modified,
13006 DiffHunkStatusKind::Modified,
13007 DiffHunkStatusKind::Modified,
13008 DiffHunkStatusKind::Modified,
13009 DiffHunkStatusKind::Modified,
13010 ],
13011 indoc! {r#"struct Row;
13012 ˇstruct Row1;
13013 struct Row2;
13014 ˇ
13015 struct Row4;
13016 ˇstruct Row5;
13017 struct Row6;
13018 ˇ
13019 struct Row8;
13020 ˇstruct Row9;
13021 struct Row10;ˇ"#},
13022 base_text,
13023 &mut cx,
13024 );
13025}
13026
13027#[gpui::test]
13028async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
13029 init_test(cx, |_| {});
13030 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13031 let base_text = indoc! {r#"
13032 one
13033
13034 two
13035 three
13036 "#};
13037
13038 cx.set_head_text(base_text);
13039 cx.set_state("\nˇ\n");
13040 cx.executor().run_until_parked();
13041 cx.update_editor(|editor, _window, cx| {
13042 editor.expand_selected_diff_hunks(cx);
13043 });
13044 cx.executor().run_until_parked();
13045 cx.update_editor(|editor, window, cx| {
13046 editor.backspace(&Default::default(), window, cx);
13047 });
13048 cx.run_until_parked();
13049 cx.assert_state_with_diff(
13050 indoc! {r#"
13051
13052 - two
13053 - threeˇ
13054 +
13055 "#}
13056 .to_string(),
13057 );
13058}
13059
13060#[gpui::test]
13061async fn test_deletion_reverts(cx: &mut TestAppContext) {
13062 init_test(cx, |_| {});
13063 let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13064 let base_text = indoc! {r#"struct Row;
13065struct Row1;
13066struct Row2;
13067
13068struct Row4;
13069struct Row5;
13070struct Row6;
13071
13072struct Row8;
13073struct Row9;
13074struct Row10;"#};
13075
13076 // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
13077 assert_hunk_revert(
13078 indoc! {r#"struct Row;
13079 struct Row2;
13080
13081 ˇstruct Row4;
13082 struct Row5;
13083 struct Row6;
13084 ˇ
13085 struct Row8;
13086 struct Row10;"#},
13087 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13088 indoc! {r#"struct Row;
13089 struct Row2;
13090
13091 ˇstruct Row4;
13092 struct Row5;
13093 struct Row6;
13094 ˇ
13095 struct Row8;
13096 struct Row10;"#},
13097 base_text,
13098 &mut cx,
13099 );
13100 assert_hunk_revert(
13101 indoc! {r#"struct Row;
13102 struct Row2;
13103
13104 «ˇstruct Row4;
13105 struct» Row5;
13106 «struct Row6;
13107 ˇ»
13108 struct Row8;
13109 struct Row10;"#},
13110 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13111 indoc! {r#"struct Row;
13112 struct Row2;
13113
13114 «ˇstruct Row4;
13115 struct» Row5;
13116 «struct Row6;
13117 ˇ»
13118 struct Row8;
13119 struct Row10;"#},
13120 base_text,
13121 &mut cx,
13122 );
13123
13124 // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
13125 assert_hunk_revert(
13126 indoc! {r#"struct Row;
13127 ˇstruct Row2;
13128
13129 struct Row4;
13130 struct Row5;
13131 struct Row6;
13132
13133 struct Row8;ˇ
13134 struct Row10;"#},
13135 vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13136 indoc! {r#"struct Row;
13137 struct Row1;
13138 ˇstruct Row2;
13139
13140 struct Row4;
13141 struct Row5;
13142 struct Row6;
13143
13144 struct Row8;ˇ
13145 struct Row9;
13146 struct Row10;"#},
13147 base_text,
13148 &mut cx,
13149 );
13150 assert_hunk_revert(
13151 indoc! {r#"struct Row;
13152 struct Row2«ˇ;
13153 struct Row4;
13154 struct» Row5;
13155 «struct Row6;
13156
13157 struct Row8;ˇ»
13158 struct Row10;"#},
13159 vec![
13160 DiffHunkStatusKind::Deleted,
13161 DiffHunkStatusKind::Deleted,
13162 DiffHunkStatusKind::Deleted,
13163 ],
13164 indoc! {r#"struct Row;
13165 struct Row1;
13166 struct Row2«ˇ;
13167
13168 struct Row4;
13169 struct» Row5;
13170 «struct Row6;
13171
13172 struct Row8;ˇ»
13173 struct Row9;
13174 struct Row10;"#},
13175 base_text,
13176 &mut cx,
13177 );
13178}
13179
13180#[gpui::test]
13181async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
13182 init_test(cx, |_| {});
13183
13184 let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
13185 let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
13186 let base_text_3 =
13187 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
13188
13189 let text_1 = edit_first_char_of_every_line(base_text_1);
13190 let text_2 = edit_first_char_of_every_line(base_text_2);
13191 let text_3 = edit_first_char_of_every_line(base_text_3);
13192
13193 let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
13194 let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
13195 let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
13196
13197 let multibuffer = cx.new(|cx| {
13198 let mut multibuffer = MultiBuffer::new(ReadWrite);
13199 multibuffer.push_excerpts(
13200 buffer_1.clone(),
13201 [
13202 ExcerptRange {
13203 context: Point::new(0, 0)..Point::new(3, 0),
13204 primary: None,
13205 },
13206 ExcerptRange {
13207 context: Point::new(5, 0)..Point::new(7, 0),
13208 primary: None,
13209 },
13210 ExcerptRange {
13211 context: Point::new(9, 0)..Point::new(10, 4),
13212 primary: None,
13213 },
13214 ],
13215 cx,
13216 );
13217 multibuffer.push_excerpts(
13218 buffer_2.clone(),
13219 [
13220 ExcerptRange {
13221 context: Point::new(0, 0)..Point::new(3, 0),
13222 primary: None,
13223 },
13224 ExcerptRange {
13225 context: Point::new(5, 0)..Point::new(7, 0),
13226 primary: None,
13227 },
13228 ExcerptRange {
13229 context: Point::new(9, 0)..Point::new(10, 4),
13230 primary: None,
13231 },
13232 ],
13233 cx,
13234 );
13235 multibuffer.push_excerpts(
13236 buffer_3.clone(),
13237 [
13238 ExcerptRange {
13239 context: Point::new(0, 0)..Point::new(3, 0),
13240 primary: None,
13241 },
13242 ExcerptRange {
13243 context: Point::new(5, 0)..Point::new(7, 0),
13244 primary: None,
13245 },
13246 ExcerptRange {
13247 context: Point::new(9, 0)..Point::new(10, 4),
13248 primary: None,
13249 },
13250 ],
13251 cx,
13252 );
13253 multibuffer
13254 });
13255
13256 let fs = FakeFs::new(cx.executor());
13257 let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13258 let (editor, cx) = cx
13259 .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
13260 editor.update_in(cx, |editor, _window, cx| {
13261 for (buffer, diff_base) in [
13262 (buffer_1.clone(), base_text_1),
13263 (buffer_2.clone(), base_text_2),
13264 (buffer_3.clone(), base_text_3),
13265 ] {
13266 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13267 editor
13268 .buffer
13269 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13270 }
13271 });
13272 cx.executor().run_until_parked();
13273
13274 editor.update_in(cx, |editor, window, cx| {
13275 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}");
13276 editor.select_all(&SelectAll, window, cx);
13277 editor.git_restore(&Default::default(), window, cx);
13278 });
13279 cx.executor().run_until_parked();
13280
13281 // When all ranges are selected, all buffer hunks are reverted.
13282 editor.update(cx, |editor, cx| {
13283 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");
13284 });
13285 buffer_1.update(cx, |buffer, _| {
13286 assert_eq!(buffer.text(), base_text_1);
13287 });
13288 buffer_2.update(cx, |buffer, _| {
13289 assert_eq!(buffer.text(), base_text_2);
13290 });
13291 buffer_3.update(cx, |buffer, _| {
13292 assert_eq!(buffer.text(), base_text_3);
13293 });
13294
13295 editor.update_in(cx, |editor, window, cx| {
13296 editor.undo(&Default::default(), window, cx);
13297 });
13298
13299 editor.update_in(cx, |editor, window, cx| {
13300 editor.change_selections(None, window, cx, |s| {
13301 s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
13302 });
13303 editor.git_restore(&Default::default(), window, cx);
13304 });
13305
13306 // Now, when all ranges selected belong to buffer_1, the revert should succeed,
13307 // but not affect buffer_2 and its related excerpts.
13308 editor.update(cx, |editor, cx| {
13309 assert_eq!(
13310 editor.text(cx),
13311 "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}"
13312 );
13313 });
13314 buffer_1.update(cx, |buffer, _| {
13315 assert_eq!(buffer.text(), base_text_1);
13316 });
13317 buffer_2.update(cx, |buffer, _| {
13318 assert_eq!(
13319 buffer.text(),
13320 "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
13321 );
13322 });
13323 buffer_3.update(cx, |buffer, _| {
13324 assert_eq!(
13325 buffer.text(),
13326 "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
13327 );
13328 });
13329
13330 fn edit_first_char_of_every_line(text: &str) -> String {
13331 text.split('\n')
13332 .map(|line| format!("X{}", &line[1..]))
13333 .collect::<Vec<_>>()
13334 .join("\n")
13335 }
13336}
13337
13338#[gpui::test]
13339async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
13340 init_test(cx, |_| {});
13341
13342 let cols = 4;
13343 let rows = 10;
13344 let sample_text_1 = sample_text(rows, cols, 'a');
13345 assert_eq!(
13346 sample_text_1,
13347 "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
13348 );
13349 let sample_text_2 = sample_text(rows, cols, 'l');
13350 assert_eq!(
13351 sample_text_2,
13352 "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
13353 );
13354 let sample_text_3 = sample_text(rows, cols, 'v');
13355 assert_eq!(
13356 sample_text_3,
13357 "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
13358 );
13359
13360 let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
13361 let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
13362 let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
13363
13364 let multi_buffer = cx.new(|cx| {
13365 let mut multibuffer = MultiBuffer::new(ReadWrite);
13366 multibuffer.push_excerpts(
13367 buffer_1.clone(),
13368 [
13369 ExcerptRange {
13370 context: Point::new(0, 0)..Point::new(3, 0),
13371 primary: None,
13372 },
13373 ExcerptRange {
13374 context: Point::new(5, 0)..Point::new(7, 0),
13375 primary: None,
13376 },
13377 ExcerptRange {
13378 context: Point::new(9, 0)..Point::new(10, 4),
13379 primary: None,
13380 },
13381 ],
13382 cx,
13383 );
13384 multibuffer.push_excerpts(
13385 buffer_2.clone(),
13386 [
13387 ExcerptRange {
13388 context: Point::new(0, 0)..Point::new(3, 0),
13389 primary: None,
13390 },
13391 ExcerptRange {
13392 context: Point::new(5, 0)..Point::new(7, 0),
13393 primary: None,
13394 },
13395 ExcerptRange {
13396 context: Point::new(9, 0)..Point::new(10, 4),
13397 primary: None,
13398 },
13399 ],
13400 cx,
13401 );
13402 multibuffer.push_excerpts(
13403 buffer_3.clone(),
13404 [
13405 ExcerptRange {
13406 context: Point::new(0, 0)..Point::new(3, 0),
13407 primary: None,
13408 },
13409 ExcerptRange {
13410 context: Point::new(5, 0)..Point::new(7, 0),
13411 primary: None,
13412 },
13413 ExcerptRange {
13414 context: Point::new(9, 0)..Point::new(10, 4),
13415 primary: None,
13416 },
13417 ],
13418 cx,
13419 );
13420 multibuffer
13421 });
13422
13423 let fs = FakeFs::new(cx.executor());
13424 fs.insert_tree(
13425 "/a",
13426 json!({
13427 "main.rs": sample_text_1,
13428 "other.rs": sample_text_2,
13429 "lib.rs": sample_text_3,
13430 }),
13431 )
13432 .await;
13433 let project = Project::test(fs, ["/a".as_ref()], cx).await;
13434 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13435 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13436 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
13437 Editor::new(
13438 EditorMode::Full,
13439 multi_buffer,
13440 Some(project.clone()),
13441 true,
13442 window,
13443 cx,
13444 )
13445 });
13446 let multibuffer_item_id = workspace
13447 .update(cx, |workspace, window, cx| {
13448 assert!(
13449 workspace.active_item(cx).is_none(),
13450 "active item should be None before the first item is added"
13451 );
13452 workspace.add_item_to_active_pane(
13453 Box::new(multi_buffer_editor.clone()),
13454 None,
13455 true,
13456 window,
13457 cx,
13458 );
13459 let active_item = workspace
13460 .active_item(cx)
13461 .expect("should have an active item after adding the multi buffer");
13462 assert!(
13463 !active_item.is_singleton(cx),
13464 "A multi buffer was expected to active after adding"
13465 );
13466 active_item.item_id()
13467 })
13468 .unwrap();
13469 cx.executor().run_until_parked();
13470
13471 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13472 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13473 s.select_ranges(Some(1..2))
13474 });
13475 editor.open_excerpts(&OpenExcerpts, window, cx);
13476 });
13477 cx.executor().run_until_parked();
13478 let first_item_id = workspace
13479 .update(cx, |workspace, window, cx| {
13480 let active_item = workspace
13481 .active_item(cx)
13482 .expect("should have an active item after navigating into the 1st buffer");
13483 let first_item_id = active_item.item_id();
13484 assert_ne!(
13485 first_item_id, multibuffer_item_id,
13486 "Should navigate into the 1st buffer and activate it"
13487 );
13488 assert!(
13489 active_item.is_singleton(cx),
13490 "New active item should be a singleton buffer"
13491 );
13492 assert_eq!(
13493 active_item
13494 .act_as::<Editor>(cx)
13495 .expect("should have navigated into an editor for the 1st buffer")
13496 .read(cx)
13497 .text(cx),
13498 sample_text_1
13499 );
13500
13501 workspace
13502 .go_back(workspace.active_pane().downgrade(), window, cx)
13503 .detach_and_log_err(cx);
13504
13505 first_item_id
13506 })
13507 .unwrap();
13508 cx.executor().run_until_parked();
13509 workspace
13510 .update(cx, |workspace, _, cx| {
13511 let active_item = workspace
13512 .active_item(cx)
13513 .expect("should have an active item after navigating back");
13514 assert_eq!(
13515 active_item.item_id(),
13516 multibuffer_item_id,
13517 "Should navigate back to the multi buffer"
13518 );
13519 assert!(!active_item.is_singleton(cx));
13520 })
13521 .unwrap();
13522
13523 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13524 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13525 s.select_ranges(Some(39..40))
13526 });
13527 editor.open_excerpts(&OpenExcerpts, window, cx);
13528 });
13529 cx.executor().run_until_parked();
13530 let second_item_id = workspace
13531 .update(cx, |workspace, window, cx| {
13532 let active_item = workspace
13533 .active_item(cx)
13534 .expect("should have an active item after navigating into the 2nd buffer");
13535 let second_item_id = active_item.item_id();
13536 assert_ne!(
13537 second_item_id, multibuffer_item_id,
13538 "Should navigate away from the multibuffer"
13539 );
13540 assert_ne!(
13541 second_item_id, first_item_id,
13542 "Should navigate into the 2nd buffer and activate it"
13543 );
13544 assert!(
13545 active_item.is_singleton(cx),
13546 "New active item should be a singleton buffer"
13547 );
13548 assert_eq!(
13549 active_item
13550 .act_as::<Editor>(cx)
13551 .expect("should have navigated into an editor")
13552 .read(cx)
13553 .text(cx),
13554 sample_text_2
13555 );
13556
13557 workspace
13558 .go_back(workspace.active_pane().downgrade(), window, cx)
13559 .detach_and_log_err(cx);
13560
13561 second_item_id
13562 })
13563 .unwrap();
13564 cx.executor().run_until_parked();
13565 workspace
13566 .update(cx, |workspace, _, cx| {
13567 let active_item = workspace
13568 .active_item(cx)
13569 .expect("should have an active item after navigating back from the 2nd buffer");
13570 assert_eq!(
13571 active_item.item_id(),
13572 multibuffer_item_id,
13573 "Should navigate back from the 2nd buffer to the multi buffer"
13574 );
13575 assert!(!active_item.is_singleton(cx));
13576 })
13577 .unwrap();
13578
13579 multi_buffer_editor.update_in(cx, |editor, window, cx| {
13580 editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13581 s.select_ranges(Some(70..70))
13582 });
13583 editor.open_excerpts(&OpenExcerpts, window, cx);
13584 });
13585 cx.executor().run_until_parked();
13586 workspace
13587 .update(cx, |workspace, window, cx| {
13588 let active_item = workspace
13589 .active_item(cx)
13590 .expect("should have an active item after navigating into the 3rd buffer");
13591 let third_item_id = active_item.item_id();
13592 assert_ne!(
13593 third_item_id, multibuffer_item_id,
13594 "Should navigate into the 3rd buffer and activate it"
13595 );
13596 assert_ne!(third_item_id, first_item_id);
13597 assert_ne!(third_item_id, second_item_id);
13598 assert!(
13599 active_item.is_singleton(cx),
13600 "New active item should be a singleton buffer"
13601 );
13602 assert_eq!(
13603 active_item
13604 .act_as::<Editor>(cx)
13605 .expect("should have navigated into an editor")
13606 .read(cx)
13607 .text(cx),
13608 sample_text_3
13609 );
13610
13611 workspace
13612 .go_back(workspace.active_pane().downgrade(), window, cx)
13613 .detach_and_log_err(cx);
13614 })
13615 .unwrap();
13616 cx.executor().run_until_parked();
13617 workspace
13618 .update(cx, |workspace, _, cx| {
13619 let active_item = workspace
13620 .active_item(cx)
13621 .expect("should have an active item after navigating back from the 3rd buffer");
13622 assert_eq!(
13623 active_item.item_id(),
13624 multibuffer_item_id,
13625 "Should navigate back from the 3rd buffer to the multi buffer"
13626 );
13627 assert!(!active_item.is_singleton(cx));
13628 })
13629 .unwrap();
13630}
13631
13632#[gpui::test]
13633async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
13634 init_test(cx, |_| {});
13635
13636 let mut cx = EditorTestContext::new(cx).await;
13637
13638 let diff_base = r#"
13639 use some::mod;
13640
13641 const A: u32 = 42;
13642
13643 fn main() {
13644 println!("hello");
13645
13646 println!("world");
13647 }
13648 "#
13649 .unindent();
13650
13651 cx.set_state(
13652 &r#"
13653 use some::modified;
13654
13655 ˇ
13656 fn main() {
13657 println!("hello there");
13658
13659 println!("around the");
13660 println!("world");
13661 }
13662 "#
13663 .unindent(),
13664 );
13665
13666 cx.set_head_text(&diff_base);
13667 executor.run_until_parked();
13668
13669 cx.update_editor(|editor, window, cx| {
13670 editor.go_to_next_hunk(&GoToHunk, window, cx);
13671 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13672 });
13673 executor.run_until_parked();
13674 cx.assert_state_with_diff(
13675 r#"
13676 use some::modified;
13677
13678
13679 fn main() {
13680 - println!("hello");
13681 + ˇ println!("hello there");
13682
13683 println!("around the");
13684 println!("world");
13685 }
13686 "#
13687 .unindent(),
13688 );
13689
13690 cx.update_editor(|editor, window, cx| {
13691 for _ in 0..2 {
13692 editor.go_to_next_hunk(&GoToHunk, window, cx);
13693 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13694 }
13695 });
13696 executor.run_until_parked();
13697 cx.assert_state_with_diff(
13698 r#"
13699 - use some::mod;
13700 + ˇuse some::modified;
13701
13702
13703 fn main() {
13704 - println!("hello");
13705 + println!("hello there");
13706
13707 + println!("around the");
13708 println!("world");
13709 }
13710 "#
13711 .unindent(),
13712 );
13713
13714 cx.update_editor(|editor, window, cx| {
13715 editor.go_to_next_hunk(&GoToHunk, window, cx);
13716 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13717 });
13718 executor.run_until_parked();
13719 cx.assert_state_with_diff(
13720 r#"
13721 - use some::mod;
13722 + use some::modified;
13723
13724 - const A: u32 = 42;
13725 ˇ
13726 fn main() {
13727 - println!("hello");
13728 + println!("hello there");
13729
13730 + println!("around the");
13731 println!("world");
13732 }
13733 "#
13734 .unindent(),
13735 );
13736
13737 cx.update_editor(|editor, window, cx| {
13738 editor.cancel(&Cancel, window, cx);
13739 });
13740
13741 cx.assert_state_with_diff(
13742 r#"
13743 use some::modified;
13744
13745 ˇ
13746 fn main() {
13747 println!("hello there");
13748
13749 println!("around the");
13750 println!("world");
13751 }
13752 "#
13753 .unindent(),
13754 );
13755}
13756
13757#[gpui::test]
13758async fn test_diff_base_change_with_expanded_diff_hunks(
13759 executor: BackgroundExecutor,
13760 cx: &mut TestAppContext,
13761) {
13762 init_test(cx, |_| {});
13763
13764 let mut cx = EditorTestContext::new(cx).await;
13765
13766 let diff_base = r#"
13767 use some::mod1;
13768 use some::mod2;
13769
13770 const A: u32 = 42;
13771 const B: u32 = 42;
13772 const C: u32 = 42;
13773
13774 fn main() {
13775 println!("hello");
13776
13777 println!("world");
13778 }
13779 "#
13780 .unindent();
13781
13782 cx.set_state(
13783 &r#"
13784 use some::mod2;
13785
13786 const A: u32 = 42;
13787 const C: u32 = 42;
13788
13789 fn main(ˇ) {
13790 //println!("hello");
13791
13792 println!("world");
13793 //
13794 //
13795 }
13796 "#
13797 .unindent(),
13798 );
13799
13800 cx.set_head_text(&diff_base);
13801 executor.run_until_parked();
13802
13803 cx.update_editor(|editor, window, cx| {
13804 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
13805 });
13806 executor.run_until_parked();
13807 cx.assert_state_with_diff(
13808 r#"
13809 - use some::mod1;
13810 use some::mod2;
13811
13812 const A: u32 = 42;
13813 - const B: u32 = 42;
13814 const C: u32 = 42;
13815
13816 fn main(ˇ) {
13817 - println!("hello");
13818 + //println!("hello");
13819
13820 println!("world");
13821 + //
13822 + //
13823 }
13824 "#
13825 .unindent(),
13826 );
13827
13828 cx.set_head_text("new diff base!");
13829 executor.run_until_parked();
13830 cx.assert_state_with_diff(
13831 r#"
13832 - new diff base!
13833 + use some::mod2;
13834 +
13835 + const A: u32 = 42;
13836 + const C: u32 = 42;
13837 +
13838 + fn main(ˇ) {
13839 + //println!("hello");
13840 +
13841 + println!("world");
13842 + //
13843 + //
13844 + }
13845 "#
13846 .unindent(),
13847 );
13848}
13849
13850#[gpui::test]
13851async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
13852 init_test(cx, |_| {});
13853
13854 let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13855 let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
13856 let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13857 let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
13858 let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
13859 let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
13860
13861 let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
13862 let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
13863 let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
13864
13865 let multi_buffer = cx.new(|cx| {
13866 let mut multibuffer = MultiBuffer::new(ReadWrite);
13867 multibuffer.push_excerpts(
13868 buffer_1.clone(),
13869 [
13870 ExcerptRange {
13871 context: Point::new(0, 0)..Point::new(3, 0),
13872 primary: None,
13873 },
13874 ExcerptRange {
13875 context: Point::new(5, 0)..Point::new(7, 0),
13876 primary: None,
13877 },
13878 ExcerptRange {
13879 context: Point::new(9, 0)..Point::new(10, 3),
13880 primary: None,
13881 },
13882 ],
13883 cx,
13884 );
13885 multibuffer.push_excerpts(
13886 buffer_2.clone(),
13887 [
13888 ExcerptRange {
13889 context: Point::new(0, 0)..Point::new(3, 0),
13890 primary: None,
13891 },
13892 ExcerptRange {
13893 context: Point::new(5, 0)..Point::new(7, 0),
13894 primary: None,
13895 },
13896 ExcerptRange {
13897 context: Point::new(9, 0)..Point::new(10, 3),
13898 primary: None,
13899 },
13900 ],
13901 cx,
13902 );
13903 multibuffer.push_excerpts(
13904 buffer_3.clone(),
13905 [
13906 ExcerptRange {
13907 context: Point::new(0, 0)..Point::new(3, 0),
13908 primary: None,
13909 },
13910 ExcerptRange {
13911 context: Point::new(5, 0)..Point::new(7, 0),
13912 primary: None,
13913 },
13914 ExcerptRange {
13915 context: Point::new(9, 0)..Point::new(10, 3),
13916 primary: None,
13917 },
13918 ],
13919 cx,
13920 );
13921 multibuffer
13922 });
13923
13924 let editor = cx.add_window(|window, cx| {
13925 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
13926 });
13927 editor
13928 .update(cx, |editor, _window, cx| {
13929 for (buffer, diff_base) in [
13930 (buffer_1.clone(), file_1_old),
13931 (buffer_2.clone(), file_2_old),
13932 (buffer_3.clone(), file_3_old),
13933 ] {
13934 let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13935 editor
13936 .buffer
13937 .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13938 }
13939 })
13940 .unwrap();
13941
13942 let mut cx = EditorTestContext::for_editor(editor, cx).await;
13943 cx.run_until_parked();
13944
13945 cx.assert_editor_state(
13946 &"
13947 ˇaaa
13948 ccc
13949 ddd
13950
13951 ggg
13952 hhh
13953
13954
13955 lll
13956 mmm
13957 NNN
13958
13959 qqq
13960 rrr
13961
13962 uuu
13963 111
13964 222
13965 333
13966
13967 666
13968 777
13969
13970 000
13971 !!!"
13972 .unindent(),
13973 );
13974
13975 cx.update_editor(|editor, window, cx| {
13976 editor.select_all(&SelectAll, window, cx);
13977 editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
13978 });
13979 cx.executor().run_until_parked();
13980
13981 cx.assert_state_with_diff(
13982 "
13983 «aaa
13984 - bbb
13985 ccc
13986 ddd
13987
13988 ggg
13989 hhh
13990
13991
13992 lll
13993 mmm
13994 - nnn
13995 + NNN
13996
13997 qqq
13998 rrr
13999
14000 uuu
14001 111
14002 222
14003 333
14004
14005 + 666
14006 777
14007
14008 000
14009 !!!ˇ»"
14010 .unindent(),
14011 );
14012}
14013
14014#[gpui::test]
14015async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
14016 init_test(cx, |_| {});
14017
14018 let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
14019 let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
14020
14021 let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
14022 let multi_buffer = cx.new(|cx| {
14023 let mut multibuffer = MultiBuffer::new(ReadWrite);
14024 multibuffer.push_excerpts(
14025 buffer.clone(),
14026 [
14027 ExcerptRange {
14028 context: Point::new(0, 0)..Point::new(2, 0),
14029 primary: None,
14030 },
14031 ExcerptRange {
14032 context: Point::new(4, 0)..Point::new(7, 0),
14033 primary: None,
14034 },
14035 ExcerptRange {
14036 context: Point::new(9, 0)..Point::new(10, 0),
14037 primary: None,
14038 },
14039 ],
14040 cx,
14041 );
14042 multibuffer
14043 });
14044
14045 let editor = cx.add_window(|window, cx| {
14046 Editor::new(EditorMode::Full, multi_buffer, None, true, window, cx)
14047 });
14048 editor
14049 .update(cx, |editor, _window, cx| {
14050 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
14051 editor
14052 .buffer
14053 .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
14054 })
14055 .unwrap();
14056
14057 let mut cx = EditorTestContext::for_editor(editor, cx).await;
14058 cx.run_until_parked();
14059
14060 cx.update_editor(|editor, window, cx| {
14061 editor.expand_all_diff_hunks(&Default::default(), window, cx)
14062 });
14063 cx.executor().run_until_parked();
14064
14065 // When the start of a hunk coincides with the start of its excerpt,
14066 // the hunk is expanded. When the start of a a hunk is earlier than
14067 // the start of its excerpt, the hunk is not expanded.
14068 cx.assert_state_with_diff(
14069 "
14070 ˇaaa
14071 - bbb
14072 + BBB
14073
14074 - ddd
14075 - eee
14076 + DDD
14077 + EEE
14078 fff
14079
14080 iii
14081 "
14082 .unindent(),
14083 );
14084}
14085
14086#[gpui::test]
14087async fn test_edits_around_expanded_insertion_hunks(
14088 executor: BackgroundExecutor,
14089 cx: &mut TestAppContext,
14090) {
14091 init_test(cx, |_| {});
14092
14093 let mut cx = EditorTestContext::new(cx).await;
14094
14095 let diff_base = r#"
14096 use some::mod1;
14097 use some::mod2;
14098
14099 const A: u32 = 42;
14100
14101 fn main() {
14102 println!("hello");
14103
14104 println!("world");
14105 }
14106 "#
14107 .unindent();
14108 executor.run_until_parked();
14109 cx.set_state(
14110 &r#"
14111 use some::mod1;
14112 use some::mod2;
14113
14114 const A: u32 = 42;
14115 const B: u32 = 42;
14116 const C: u32 = 42;
14117 ˇ
14118
14119 fn main() {
14120 println!("hello");
14121
14122 println!("world");
14123 }
14124 "#
14125 .unindent(),
14126 );
14127
14128 cx.set_head_text(&diff_base);
14129 executor.run_until_parked();
14130
14131 cx.update_editor(|editor, window, cx| {
14132 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14133 });
14134 executor.run_until_parked();
14135
14136 cx.assert_state_with_diff(
14137 r#"
14138 use some::mod1;
14139 use some::mod2;
14140
14141 const A: u32 = 42;
14142 + const B: u32 = 42;
14143 + const C: u32 = 42;
14144 + ˇ
14145
14146 fn main() {
14147 println!("hello");
14148
14149 println!("world");
14150 }
14151 "#
14152 .unindent(),
14153 );
14154
14155 cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
14156 executor.run_until_parked();
14157
14158 cx.assert_state_with_diff(
14159 r#"
14160 use some::mod1;
14161 use some::mod2;
14162
14163 const A: u32 = 42;
14164 + const B: u32 = 42;
14165 + const C: u32 = 42;
14166 + const D: u32 = 42;
14167 + ˇ
14168
14169 fn main() {
14170 println!("hello");
14171
14172 println!("world");
14173 }
14174 "#
14175 .unindent(),
14176 );
14177
14178 cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
14179 executor.run_until_parked();
14180
14181 cx.assert_state_with_diff(
14182 r#"
14183 use some::mod1;
14184 use some::mod2;
14185
14186 const A: u32 = 42;
14187 + const B: u32 = 42;
14188 + const C: u32 = 42;
14189 + const D: u32 = 42;
14190 + const E: u32 = 42;
14191 + ˇ
14192
14193 fn main() {
14194 println!("hello");
14195
14196 println!("world");
14197 }
14198 "#
14199 .unindent(),
14200 );
14201
14202 cx.update_editor(|editor, window, cx| {
14203 editor.delete_line(&DeleteLine, window, cx);
14204 });
14205 executor.run_until_parked();
14206
14207 cx.assert_state_with_diff(
14208 r#"
14209 use some::mod1;
14210 use some::mod2;
14211
14212 const A: u32 = 42;
14213 + const B: u32 = 42;
14214 + const C: u32 = 42;
14215 + const D: u32 = 42;
14216 + const E: u32 = 42;
14217 ˇ
14218 fn main() {
14219 println!("hello");
14220
14221 println!("world");
14222 }
14223 "#
14224 .unindent(),
14225 );
14226
14227 cx.update_editor(|editor, window, cx| {
14228 editor.move_up(&MoveUp, window, cx);
14229 editor.delete_line(&DeleteLine, window, cx);
14230 editor.move_up(&MoveUp, window, cx);
14231 editor.delete_line(&DeleteLine, window, cx);
14232 editor.move_up(&MoveUp, window, cx);
14233 editor.delete_line(&DeleteLine, window, cx);
14234 });
14235 executor.run_until_parked();
14236 cx.assert_state_with_diff(
14237 r#"
14238 use some::mod1;
14239 use some::mod2;
14240
14241 const A: u32 = 42;
14242 + const B: u32 = 42;
14243 ˇ
14244 fn main() {
14245 println!("hello");
14246
14247 println!("world");
14248 }
14249 "#
14250 .unindent(),
14251 );
14252
14253 cx.update_editor(|editor, window, cx| {
14254 editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
14255 editor.delete_line(&DeleteLine, window, cx);
14256 });
14257 executor.run_until_parked();
14258 cx.assert_state_with_diff(
14259 r#"
14260 ˇ
14261 fn main() {
14262 println!("hello");
14263
14264 println!("world");
14265 }
14266 "#
14267 .unindent(),
14268 );
14269}
14270
14271#[gpui::test]
14272async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
14273 init_test(cx, |_| {});
14274
14275 let mut cx = EditorTestContext::new(cx).await;
14276 cx.set_head_text(indoc! { "
14277 one
14278 two
14279 three
14280 four
14281 five
14282 "
14283 });
14284 cx.set_state(indoc! { "
14285 one
14286 ˇthree
14287 five
14288 "});
14289 cx.run_until_parked();
14290 cx.update_editor(|editor, window, cx| {
14291 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14292 });
14293 cx.assert_state_with_diff(
14294 indoc! { "
14295 one
14296 - two
14297 ˇthree
14298 - four
14299 five
14300 "}
14301 .to_string(),
14302 );
14303 cx.update_editor(|editor, window, cx| {
14304 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14305 });
14306
14307 cx.assert_state_with_diff(
14308 indoc! { "
14309 one
14310 ˇthree
14311 five
14312 "}
14313 .to_string(),
14314 );
14315
14316 cx.set_state(indoc! { "
14317 one
14318 ˇTWO
14319 three
14320 four
14321 five
14322 "});
14323 cx.run_until_parked();
14324 cx.update_editor(|editor, window, cx| {
14325 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14326 });
14327
14328 cx.assert_state_with_diff(
14329 indoc! { "
14330 one
14331 - two
14332 + ˇTWO
14333 three
14334 four
14335 five
14336 "}
14337 .to_string(),
14338 );
14339 cx.update_editor(|editor, window, cx| {
14340 editor.move_up(&Default::default(), window, cx);
14341 editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14342 });
14343 cx.assert_state_with_diff(
14344 indoc! { "
14345 one
14346 ˇTWO
14347 three
14348 four
14349 five
14350 "}
14351 .to_string(),
14352 );
14353}
14354
14355#[gpui::test]
14356async fn test_edits_around_expanded_deletion_hunks(
14357 executor: BackgroundExecutor,
14358 cx: &mut TestAppContext,
14359) {
14360 init_test(cx, |_| {});
14361
14362 let mut cx = EditorTestContext::new(cx).await;
14363
14364 let diff_base = r#"
14365 use some::mod1;
14366 use some::mod2;
14367
14368 const A: u32 = 42;
14369 const B: u32 = 42;
14370 const C: u32 = 42;
14371
14372
14373 fn main() {
14374 println!("hello");
14375
14376 println!("world");
14377 }
14378 "#
14379 .unindent();
14380 executor.run_until_parked();
14381 cx.set_state(
14382 &r#"
14383 use some::mod1;
14384 use some::mod2;
14385
14386 ˇconst B: u32 = 42;
14387 const C: u32 = 42;
14388
14389
14390 fn main() {
14391 println!("hello");
14392
14393 println!("world");
14394 }
14395 "#
14396 .unindent(),
14397 );
14398
14399 cx.set_head_text(&diff_base);
14400 executor.run_until_parked();
14401
14402 cx.update_editor(|editor, window, cx| {
14403 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14404 });
14405 executor.run_until_parked();
14406
14407 cx.assert_state_with_diff(
14408 r#"
14409 use some::mod1;
14410 use some::mod2;
14411
14412 - const A: u32 = 42;
14413 ˇconst B: u32 = 42;
14414 const C: u32 = 42;
14415
14416
14417 fn main() {
14418 println!("hello");
14419
14420 println!("world");
14421 }
14422 "#
14423 .unindent(),
14424 );
14425
14426 cx.update_editor(|editor, window, cx| {
14427 editor.delete_line(&DeleteLine, window, cx);
14428 });
14429 executor.run_until_parked();
14430 cx.assert_state_with_diff(
14431 r#"
14432 use some::mod1;
14433 use some::mod2;
14434
14435 - const A: u32 = 42;
14436 - const B: u32 = 42;
14437 ˇconst C: u32 = 42;
14438
14439
14440 fn main() {
14441 println!("hello");
14442
14443 println!("world");
14444 }
14445 "#
14446 .unindent(),
14447 );
14448
14449 cx.update_editor(|editor, window, cx| {
14450 editor.delete_line(&DeleteLine, window, cx);
14451 });
14452 executor.run_until_parked();
14453 cx.assert_state_with_diff(
14454 r#"
14455 use some::mod1;
14456 use some::mod2;
14457
14458 - const A: u32 = 42;
14459 - const B: u32 = 42;
14460 - const C: u32 = 42;
14461 ˇ
14462
14463 fn main() {
14464 println!("hello");
14465
14466 println!("world");
14467 }
14468 "#
14469 .unindent(),
14470 );
14471
14472 cx.update_editor(|editor, window, cx| {
14473 editor.handle_input("replacement", window, cx);
14474 });
14475 executor.run_until_parked();
14476 cx.assert_state_with_diff(
14477 r#"
14478 use some::mod1;
14479 use some::mod2;
14480
14481 - const A: u32 = 42;
14482 - const B: u32 = 42;
14483 - const C: u32 = 42;
14484 -
14485 + replacementˇ
14486
14487 fn main() {
14488 println!("hello");
14489
14490 println!("world");
14491 }
14492 "#
14493 .unindent(),
14494 );
14495}
14496
14497#[gpui::test]
14498async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14499 init_test(cx, |_| {});
14500
14501 let mut cx = EditorTestContext::new(cx).await;
14502
14503 let base_text = r#"
14504 one
14505 two
14506 three
14507 four
14508 five
14509 "#
14510 .unindent();
14511 executor.run_until_parked();
14512 cx.set_state(
14513 &r#"
14514 one
14515 two
14516 fˇour
14517 five
14518 "#
14519 .unindent(),
14520 );
14521
14522 cx.set_head_text(&base_text);
14523 executor.run_until_parked();
14524
14525 cx.update_editor(|editor, window, cx| {
14526 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14527 });
14528 executor.run_until_parked();
14529
14530 cx.assert_state_with_diff(
14531 r#"
14532 one
14533 two
14534 - three
14535 fˇour
14536 five
14537 "#
14538 .unindent(),
14539 );
14540
14541 cx.update_editor(|editor, window, cx| {
14542 editor.backspace(&Backspace, window, cx);
14543 editor.backspace(&Backspace, window, cx);
14544 });
14545 executor.run_until_parked();
14546 cx.assert_state_with_diff(
14547 r#"
14548 one
14549 two
14550 - threeˇ
14551 - four
14552 + our
14553 five
14554 "#
14555 .unindent(),
14556 );
14557}
14558
14559#[gpui::test]
14560async fn test_edit_after_expanded_modification_hunk(
14561 executor: BackgroundExecutor,
14562 cx: &mut TestAppContext,
14563) {
14564 init_test(cx, |_| {});
14565
14566 let mut cx = EditorTestContext::new(cx).await;
14567
14568 let diff_base = r#"
14569 use some::mod1;
14570 use some::mod2;
14571
14572 const A: u32 = 42;
14573 const B: u32 = 42;
14574 const C: u32 = 42;
14575 const D: u32 = 42;
14576
14577
14578 fn main() {
14579 println!("hello");
14580
14581 println!("world");
14582 }"#
14583 .unindent();
14584
14585 cx.set_state(
14586 &r#"
14587 use some::mod1;
14588 use some::mod2;
14589
14590 const A: u32 = 42;
14591 const B: u32 = 42;
14592 const C: u32 = 43ˇ
14593 const D: u32 = 42;
14594
14595
14596 fn main() {
14597 println!("hello");
14598
14599 println!("world");
14600 }"#
14601 .unindent(),
14602 );
14603
14604 cx.set_head_text(&diff_base);
14605 executor.run_until_parked();
14606 cx.update_editor(|editor, window, cx| {
14607 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14608 });
14609 executor.run_until_parked();
14610
14611 cx.assert_state_with_diff(
14612 r#"
14613 use some::mod1;
14614 use some::mod2;
14615
14616 const A: u32 = 42;
14617 const B: u32 = 42;
14618 - const C: u32 = 42;
14619 + const C: u32 = 43ˇ
14620 const D: u32 = 42;
14621
14622
14623 fn main() {
14624 println!("hello");
14625
14626 println!("world");
14627 }"#
14628 .unindent(),
14629 );
14630
14631 cx.update_editor(|editor, window, cx| {
14632 editor.handle_input("\nnew_line\n", window, cx);
14633 });
14634 executor.run_until_parked();
14635
14636 cx.assert_state_with_diff(
14637 r#"
14638 use some::mod1;
14639 use some::mod2;
14640
14641 const A: u32 = 42;
14642 const B: u32 = 42;
14643 - const C: u32 = 42;
14644 + const C: u32 = 43
14645 + new_line
14646 + ˇ
14647 const D: u32 = 42;
14648
14649
14650 fn main() {
14651 println!("hello");
14652
14653 println!("world");
14654 }"#
14655 .unindent(),
14656 );
14657}
14658
14659#[gpui::test]
14660async fn test_stage_and_unstage_added_file_hunk(
14661 executor: BackgroundExecutor,
14662 cx: &mut TestAppContext,
14663) {
14664 init_test(cx, |_| {});
14665
14666 let mut cx = EditorTestContext::new(cx).await;
14667 cx.update_editor(|editor, _, cx| {
14668 editor.set_expand_all_diff_hunks(cx);
14669 });
14670
14671 let working_copy = r#"
14672 ˇfn main() {
14673 println!("hello, world!");
14674 }
14675 "#
14676 .unindent();
14677
14678 cx.set_state(&working_copy);
14679 executor.run_until_parked();
14680
14681 cx.assert_state_with_diff(
14682 r#"
14683 + ˇfn main() {
14684 + println!("hello, world!");
14685 + }
14686 "#
14687 .unindent(),
14688 );
14689 cx.assert_index_text(None);
14690
14691 cx.update_editor(|editor, window, cx| {
14692 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14693 });
14694 executor.run_until_parked();
14695 cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
14696 cx.assert_state_with_diff(
14697 r#"
14698 + ˇfn main() {
14699 + println!("hello, world!");
14700 + }
14701 "#
14702 .unindent(),
14703 );
14704
14705 cx.update_editor(|editor, window, cx| {
14706 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
14707 });
14708 executor.run_until_parked();
14709 cx.assert_index_text(None);
14710}
14711
14712async fn setup_indent_guides_editor(
14713 text: &str,
14714 cx: &mut TestAppContext,
14715) -> (BufferId, EditorTestContext) {
14716 init_test(cx, |_| {});
14717
14718 let mut cx = EditorTestContext::new(cx).await;
14719
14720 let buffer_id = cx.update_editor(|editor, window, cx| {
14721 editor.set_text(text, window, cx);
14722 let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
14723
14724 buffer_ids[0]
14725 });
14726
14727 (buffer_id, cx)
14728}
14729
14730fn assert_indent_guides(
14731 range: Range<u32>,
14732 expected: Vec<IndentGuide>,
14733 active_indices: Option<Vec<usize>>,
14734 cx: &mut EditorTestContext,
14735) {
14736 let indent_guides = cx.update_editor(|editor, window, cx| {
14737 let snapshot = editor.snapshot(window, cx).display_snapshot;
14738 let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
14739 editor,
14740 MultiBufferRow(range.start)..MultiBufferRow(range.end),
14741 true,
14742 &snapshot,
14743 cx,
14744 );
14745
14746 indent_guides.sort_by(|a, b| {
14747 a.depth.cmp(&b.depth).then(
14748 a.start_row
14749 .cmp(&b.start_row)
14750 .then(a.end_row.cmp(&b.end_row)),
14751 )
14752 });
14753 indent_guides
14754 });
14755
14756 if let Some(expected) = active_indices {
14757 let active_indices = cx.update_editor(|editor, window, cx| {
14758 let snapshot = editor.snapshot(window, cx).display_snapshot;
14759 editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
14760 });
14761
14762 assert_eq!(
14763 active_indices.unwrap().into_iter().collect::<Vec<_>>(),
14764 expected,
14765 "Active indent guide indices do not match"
14766 );
14767 }
14768
14769 assert_eq!(indent_guides, expected, "Indent guides do not match");
14770}
14771
14772fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
14773 IndentGuide {
14774 buffer_id,
14775 start_row: MultiBufferRow(start_row),
14776 end_row: MultiBufferRow(end_row),
14777 depth,
14778 tab_size: 4,
14779 settings: IndentGuideSettings {
14780 enabled: true,
14781 line_width: 1,
14782 active_line_width: 1,
14783 ..Default::default()
14784 },
14785 }
14786}
14787
14788#[gpui::test]
14789async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
14790 let (buffer_id, mut cx) = setup_indent_guides_editor(
14791 &"
14792 fn main() {
14793 let a = 1;
14794 }"
14795 .unindent(),
14796 cx,
14797 )
14798 .await;
14799
14800 assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
14801}
14802
14803#[gpui::test]
14804async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
14805 let (buffer_id, mut cx) = setup_indent_guides_editor(
14806 &"
14807 fn main() {
14808 let a = 1;
14809 let b = 2;
14810 }"
14811 .unindent(),
14812 cx,
14813 )
14814 .await;
14815
14816 assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
14817}
14818
14819#[gpui::test]
14820async fn test_indent_guide_nested(cx: &mut TestAppContext) {
14821 let (buffer_id, mut cx) = setup_indent_guides_editor(
14822 &"
14823 fn main() {
14824 let a = 1;
14825 if a == 3 {
14826 let b = 2;
14827 } else {
14828 let c = 3;
14829 }
14830 }"
14831 .unindent(),
14832 cx,
14833 )
14834 .await;
14835
14836 assert_indent_guides(
14837 0..8,
14838 vec![
14839 indent_guide(buffer_id, 1, 6, 0),
14840 indent_guide(buffer_id, 3, 3, 1),
14841 indent_guide(buffer_id, 5, 5, 1),
14842 ],
14843 None,
14844 &mut cx,
14845 );
14846}
14847
14848#[gpui::test]
14849async fn test_indent_guide_tab(cx: &mut TestAppContext) {
14850 let (buffer_id, mut cx) = setup_indent_guides_editor(
14851 &"
14852 fn main() {
14853 let a = 1;
14854 let b = 2;
14855 let c = 3;
14856 }"
14857 .unindent(),
14858 cx,
14859 )
14860 .await;
14861
14862 assert_indent_guides(
14863 0..5,
14864 vec![
14865 indent_guide(buffer_id, 1, 3, 0),
14866 indent_guide(buffer_id, 2, 2, 1),
14867 ],
14868 None,
14869 &mut cx,
14870 );
14871}
14872
14873#[gpui::test]
14874async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
14875 let (buffer_id, mut cx) = setup_indent_guides_editor(
14876 &"
14877 fn main() {
14878 let a = 1;
14879
14880 let c = 3;
14881 }"
14882 .unindent(),
14883 cx,
14884 )
14885 .await;
14886
14887 assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
14888}
14889
14890#[gpui::test]
14891async fn test_indent_guide_complex(cx: &mut TestAppContext) {
14892 let (buffer_id, mut cx) = setup_indent_guides_editor(
14893 &"
14894 fn main() {
14895 let a = 1;
14896
14897 let c = 3;
14898
14899 if a == 3 {
14900 let b = 2;
14901 } else {
14902 let c = 3;
14903 }
14904 }"
14905 .unindent(),
14906 cx,
14907 )
14908 .await;
14909
14910 assert_indent_guides(
14911 0..11,
14912 vec![
14913 indent_guide(buffer_id, 1, 9, 0),
14914 indent_guide(buffer_id, 6, 6, 1),
14915 indent_guide(buffer_id, 8, 8, 1),
14916 ],
14917 None,
14918 &mut cx,
14919 );
14920}
14921
14922#[gpui::test]
14923async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
14924 let (buffer_id, mut cx) = setup_indent_guides_editor(
14925 &"
14926 fn main() {
14927 let a = 1;
14928
14929 let c = 3;
14930
14931 if a == 3 {
14932 let b = 2;
14933 } else {
14934 let c = 3;
14935 }
14936 }"
14937 .unindent(),
14938 cx,
14939 )
14940 .await;
14941
14942 assert_indent_guides(
14943 1..11,
14944 vec![
14945 indent_guide(buffer_id, 1, 9, 0),
14946 indent_guide(buffer_id, 6, 6, 1),
14947 indent_guide(buffer_id, 8, 8, 1),
14948 ],
14949 None,
14950 &mut cx,
14951 );
14952}
14953
14954#[gpui::test]
14955async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
14956 let (buffer_id, mut cx) = setup_indent_guides_editor(
14957 &"
14958 fn main() {
14959 let a = 1;
14960
14961 let c = 3;
14962
14963 if a == 3 {
14964 let b = 2;
14965 } else {
14966 let c = 3;
14967 }
14968 }"
14969 .unindent(),
14970 cx,
14971 )
14972 .await;
14973
14974 assert_indent_guides(
14975 1..10,
14976 vec![
14977 indent_guide(buffer_id, 1, 9, 0),
14978 indent_guide(buffer_id, 6, 6, 1),
14979 indent_guide(buffer_id, 8, 8, 1),
14980 ],
14981 None,
14982 &mut cx,
14983 );
14984}
14985
14986#[gpui::test]
14987async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
14988 let (buffer_id, mut cx) = setup_indent_guides_editor(
14989 &"
14990 block1
14991 block2
14992 block3
14993 block4
14994 block2
14995 block1
14996 block1"
14997 .unindent(),
14998 cx,
14999 )
15000 .await;
15001
15002 assert_indent_guides(
15003 1..10,
15004 vec![
15005 indent_guide(buffer_id, 1, 4, 0),
15006 indent_guide(buffer_id, 2, 3, 1),
15007 indent_guide(buffer_id, 3, 3, 2),
15008 ],
15009 None,
15010 &mut cx,
15011 );
15012}
15013
15014#[gpui::test]
15015async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
15016 let (buffer_id, mut cx) = setup_indent_guides_editor(
15017 &"
15018 block1
15019 block2
15020 block3
15021
15022 block1
15023 block1"
15024 .unindent(),
15025 cx,
15026 )
15027 .await;
15028
15029 assert_indent_guides(
15030 0..6,
15031 vec![
15032 indent_guide(buffer_id, 1, 2, 0),
15033 indent_guide(buffer_id, 2, 2, 1),
15034 ],
15035 None,
15036 &mut cx,
15037 );
15038}
15039
15040#[gpui::test]
15041async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
15042 let (buffer_id, mut cx) = setup_indent_guides_editor(
15043 &"
15044 block1
15045
15046
15047
15048 block2
15049 "
15050 .unindent(),
15051 cx,
15052 )
15053 .await;
15054
15055 assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
15056}
15057
15058#[gpui::test]
15059async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
15060 let (buffer_id, mut cx) = setup_indent_guides_editor(
15061 &"
15062 def a:
15063 \tb = 3
15064 \tif True:
15065 \t\tc = 4
15066 \t\td = 5
15067 \tprint(b)
15068 "
15069 .unindent(),
15070 cx,
15071 )
15072 .await;
15073
15074 assert_indent_guides(
15075 0..6,
15076 vec![
15077 indent_guide(buffer_id, 1, 6, 0),
15078 indent_guide(buffer_id, 3, 4, 1),
15079 ],
15080 None,
15081 &mut cx,
15082 );
15083}
15084
15085#[gpui::test]
15086async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
15087 let (buffer_id, mut cx) = setup_indent_guides_editor(
15088 &"
15089 fn main() {
15090 let a = 1;
15091 }"
15092 .unindent(),
15093 cx,
15094 )
15095 .await;
15096
15097 cx.update_editor(|editor, window, cx| {
15098 editor.change_selections(None, window, cx, |s| {
15099 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15100 });
15101 });
15102
15103 assert_indent_guides(
15104 0..3,
15105 vec![indent_guide(buffer_id, 1, 1, 0)],
15106 Some(vec![0]),
15107 &mut cx,
15108 );
15109}
15110
15111#[gpui::test]
15112async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
15113 let (buffer_id, mut cx) = setup_indent_guides_editor(
15114 &"
15115 fn main() {
15116 if 1 == 2 {
15117 let a = 1;
15118 }
15119 }"
15120 .unindent(),
15121 cx,
15122 )
15123 .await;
15124
15125 cx.update_editor(|editor, window, cx| {
15126 editor.change_selections(None, window, cx, |s| {
15127 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15128 });
15129 });
15130
15131 assert_indent_guides(
15132 0..4,
15133 vec![
15134 indent_guide(buffer_id, 1, 3, 0),
15135 indent_guide(buffer_id, 2, 2, 1),
15136 ],
15137 Some(vec![1]),
15138 &mut cx,
15139 );
15140
15141 cx.update_editor(|editor, window, cx| {
15142 editor.change_selections(None, window, cx, |s| {
15143 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15144 });
15145 });
15146
15147 assert_indent_guides(
15148 0..4,
15149 vec![
15150 indent_guide(buffer_id, 1, 3, 0),
15151 indent_guide(buffer_id, 2, 2, 1),
15152 ],
15153 Some(vec![1]),
15154 &mut cx,
15155 );
15156
15157 cx.update_editor(|editor, window, cx| {
15158 editor.change_selections(None, window, cx, |s| {
15159 s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
15160 });
15161 });
15162
15163 assert_indent_guides(
15164 0..4,
15165 vec![
15166 indent_guide(buffer_id, 1, 3, 0),
15167 indent_guide(buffer_id, 2, 2, 1),
15168 ],
15169 Some(vec![0]),
15170 &mut cx,
15171 );
15172}
15173
15174#[gpui::test]
15175async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
15176 let (buffer_id, mut cx) = setup_indent_guides_editor(
15177 &"
15178 fn main() {
15179 let a = 1;
15180
15181 let b = 2;
15182 }"
15183 .unindent(),
15184 cx,
15185 )
15186 .await;
15187
15188 cx.update_editor(|editor, window, cx| {
15189 editor.change_selections(None, window, cx, |s| {
15190 s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15191 });
15192 });
15193
15194 assert_indent_guides(
15195 0..5,
15196 vec![indent_guide(buffer_id, 1, 3, 0)],
15197 Some(vec![0]),
15198 &mut cx,
15199 );
15200}
15201
15202#[gpui::test]
15203async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
15204 let (buffer_id, mut cx) = setup_indent_guides_editor(
15205 &"
15206 def m:
15207 a = 1
15208 pass"
15209 .unindent(),
15210 cx,
15211 )
15212 .await;
15213
15214 cx.update_editor(|editor, window, cx| {
15215 editor.change_selections(None, window, cx, |s| {
15216 s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15217 });
15218 });
15219
15220 assert_indent_guides(
15221 0..3,
15222 vec![indent_guide(buffer_id, 1, 2, 0)],
15223 Some(vec![0]),
15224 &mut cx,
15225 );
15226}
15227
15228#[gpui::test]
15229async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
15230 init_test(cx, |_| {});
15231 let mut cx = EditorTestContext::new(cx).await;
15232 let text = indoc! {
15233 "
15234 impl A {
15235 fn b() {
15236 0;
15237 3;
15238 5;
15239 6;
15240 7;
15241 }
15242 }
15243 "
15244 };
15245 let base_text = indoc! {
15246 "
15247 impl A {
15248 fn b() {
15249 0;
15250 1;
15251 2;
15252 3;
15253 4;
15254 }
15255 fn c() {
15256 5;
15257 6;
15258 7;
15259 }
15260 }
15261 "
15262 };
15263
15264 cx.update_editor(|editor, window, cx| {
15265 editor.set_text(text, window, cx);
15266
15267 editor.buffer().update(cx, |multibuffer, cx| {
15268 let buffer = multibuffer.as_singleton().unwrap();
15269 let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
15270
15271 multibuffer.set_all_diff_hunks_expanded(cx);
15272 multibuffer.add_diff(diff, cx);
15273
15274 buffer.read(cx).remote_id()
15275 })
15276 });
15277 cx.run_until_parked();
15278
15279 cx.assert_state_with_diff(
15280 indoc! { "
15281 impl A {
15282 fn b() {
15283 0;
15284 - 1;
15285 - 2;
15286 3;
15287 - 4;
15288 - }
15289 - fn c() {
15290 5;
15291 6;
15292 7;
15293 }
15294 }
15295 ˇ"
15296 }
15297 .to_string(),
15298 );
15299
15300 let mut actual_guides = cx.update_editor(|editor, window, cx| {
15301 editor
15302 .snapshot(window, cx)
15303 .buffer_snapshot
15304 .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
15305 .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
15306 .collect::<Vec<_>>()
15307 });
15308 actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
15309 assert_eq!(
15310 actual_guides,
15311 vec![
15312 (MultiBufferRow(1)..=MultiBufferRow(12), 0),
15313 (MultiBufferRow(2)..=MultiBufferRow(6), 1),
15314 (MultiBufferRow(9)..=MultiBufferRow(11), 1),
15315 ]
15316 );
15317}
15318
15319#[gpui::test]
15320async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15321 init_test(cx, |_| {});
15322 let mut cx = EditorTestContext::new(cx).await;
15323
15324 let diff_base = r#"
15325 a
15326 b
15327 c
15328 "#
15329 .unindent();
15330
15331 cx.set_state(
15332 &r#"
15333 ˇA
15334 b
15335 C
15336 "#
15337 .unindent(),
15338 );
15339 cx.set_head_text(&diff_base);
15340 cx.update_editor(|editor, window, cx| {
15341 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15342 });
15343 executor.run_until_parked();
15344
15345 let both_hunks_expanded = r#"
15346 - a
15347 + ˇA
15348 b
15349 - c
15350 + C
15351 "#
15352 .unindent();
15353
15354 cx.assert_state_with_diff(both_hunks_expanded.clone());
15355
15356 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15357 let snapshot = editor.snapshot(window, cx);
15358 let hunks = editor
15359 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15360 .collect::<Vec<_>>();
15361 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15362 let buffer_id = hunks[0].buffer_id;
15363 hunks
15364 .into_iter()
15365 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15366 .collect::<Vec<_>>()
15367 });
15368 assert_eq!(hunk_ranges.len(), 2);
15369
15370 cx.update_editor(|editor, _, cx| {
15371 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15372 });
15373 executor.run_until_parked();
15374
15375 let second_hunk_expanded = r#"
15376 ˇA
15377 b
15378 - c
15379 + C
15380 "#
15381 .unindent();
15382
15383 cx.assert_state_with_diff(second_hunk_expanded);
15384
15385 cx.update_editor(|editor, _, cx| {
15386 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15387 });
15388 executor.run_until_parked();
15389
15390 cx.assert_state_with_diff(both_hunks_expanded.clone());
15391
15392 cx.update_editor(|editor, _, cx| {
15393 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15394 });
15395 executor.run_until_parked();
15396
15397 let first_hunk_expanded = r#"
15398 - a
15399 + ˇA
15400 b
15401 C
15402 "#
15403 .unindent();
15404
15405 cx.assert_state_with_diff(first_hunk_expanded);
15406
15407 cx.update_editor(|editor, _, cx| {
15408 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15409 });
15410 executor.run_until_parked();
15411
15412 cx.assert_state_with_diff(both_hunks_expanded);
15413
15414 cx.set_state(
15415 &r#"
15416 ˇA
15417 b
15418 "#
15419 .unindent(),
15420 );
15421 cx.run_until_parked();
15422
15423 // TODO this cursor position seems bad
15424 cx.assert_state_with_diff(
15425 r#"
15426 - ˇa
15427 + A
15428 b
15429 "#
15430 .unindent(),
15431 );
15432
15433 cx.update_editor(|editor, window, cx| {
15434 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15435 });
15436
15437 cx.assert_state_with_diff(
15438 r#"
15439 - ˇa
15440 + A
15441 b
15442 - c
15443 "#
15444 .unindent(),
15445 );
15446
15447 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15448 let snapshot = editor.snapshot(window, cx);
15449 let hunks = editor
15450 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15451 .collect::<Vec<_>>();
15452 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15453 let buffer_id = hunks[0].buffer_id;
15454 hunks
15455 .into_iter()
15456 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15457 .collect::<Vec<_>>()
15458 });
15459 assert_eq!(hunk_ranges.len(), 2);
15460
15461 cx.update_editor(|editor, _, cx| {
15462 editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15463 });
15464 executor.run_until_parked();
15465
15466 cx.assert_state_with_diff(
15467 r#"
15468 - ˇa
15469 + A
15470 b
15471 "#
15472 .unindent(),
15473 );
15474}
15475
15476#[gpui::test]
15477async fn test_toggle_deletion_hunk_at_start_of_file(
15478 executor: BackgroundExecutor,
15479 cx: &mut TestAppContext,
15480) {
15481 init_test(cx, |_| {});
15482 let mut cx = EditorTestContext::new(cx).await;
15483
15484 let diff_base = r#"
15485 a
15486 b
15487 c
15488 "#
15489 .unindent();
15490
15491 cx.set_state(
15492 &r#"
15493 ˇb
15494 c
15495 "#
15496 .unindent(),
15497 );
15498 cx.set_head_text(&diff_base);
15499 cx.update_editor(|editor, window, cx| {
15500 editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15501 });
15502 executor.run_until_parked();
15503
15504 let hunk_expanded = r#"
15505 - a
15506 ˇb
15507 c
15508 "#
15509 .unindent();
15510
15511 cx.assert_state_with_diff(hunk_expanded.clone());
15512
15513 let hunk_ranges = cx.update_editor(|editor, window, cx| {
15514 let snapshot = editor.snapshot(window, cx);
15515 let hunks = editor
15516 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15517 .collect::<Vec<_>>();
15518 let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15519 let buffer_id = hunks[0].buffer_id;
15520 hunks
15521 .into_iter()
15522 .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15523 .collect::<Vec<_>>()
15524 });
15525 assert_eq!(hunk_ranges.len(), 1);
15526
15527 cx.update_editor(|editor, _, cx| {
15528 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15529 });
15530 executor.run_until_parked();
15531
15532 let hunk_collapsed = r#"
15533 ˇb
15534 c
15535 "#
15536 .unindent();
15537
15538 cx.assert_state_with_diff(hunk_collapsed);
15539
15540 cx.update_editor(|editor, _, cx| {
15541 editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15542 });
15543 executor.run_until_parked();
15544
15545 cx.assert_state_with_diff(hunk_expanded.clone());
15546}
15547
15548#[gpui::test]
15549async fn test_display_diff_hunks(cx: &mut TestAppContext) {
15550 init_test(cx, |_| {});
15551
15552 let fs = FakeFs::new(cx.executor());
15553 fs.insert_tree(
15554 path!("/test"),
15555 json!({
15556 ".git": {},
15557 "file-1": "ONE\n",
15558 "file-2": "TWO\n",
15559 "file-3": "THREE\n",
15560 }),
15561 )
15562 .await;
15563
15564 fs.set_head_for_repo(
15565 path!("/test/.git").as_ref(),
15566 &[
15567 ("file-1".into(), "one\n".into()),
15568 ("file-2".into(), "two\n".into()),
15569 ("file-3".into(), "three\n".into()),
15570 ],
15571 );
15572
15573 let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
15574 let mut buffers = vec![];
15575 for i in 1..=3 {
15576 let buffer = project
15577 .update(cx, |project, cx| {
15578 let path = format!(path!("/test/file-{}"), i);
15579 project.open_local_buffer(path, cx)
15580 })
15581 .await
15582 .unwrap();
15583 buffers.push(buffer);
15584 }
15585
15586 let multibuffer = cx.new(|cx| {
15587 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
15588 multibuffer.set_all_diff_hunks_expanded(cx);
15589 for buffer in &buffers {
15590 let snapshot = buffer.read(cx).snapshot();
15591 multibuffer.set_excerpts_for_path(
15592 PathKey::namespaced("", buffer.read(cx).file().unwrap().path().clone()),
15593 buffer.clone(),
15594 vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
15595 DEFAULT_MULTIBUFFER_CONTEXT,
15596 cx,
15597 );
15598 }
15599 multibuffer
15600 });
15601
15602 let editor = cx.add_window(|window, cx| {
15603 Editor::new(
15604 EditorMode::Full,
15605 multibuffer,
15606 Some(project),
15607 true,
15608 window,
15609 cx,
15610 )
15611 });
15612 cx.run_until_parked();
15613
15614 let snapshot = editor
15615 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15616 .unwrap();
15617 let hunks = snapshot
15618 .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
15619 .map(|hunk| match hunk {
15620 DisplayDiffHunk::Unfolded {
15621 display_row_range, ..
15622 } => display_row_range,
15623 DisplayDiffHunk::Folded { .. } => unreachable!(),
15624 })
15625 .collect::<Vec<_>>();
15626 assert_eq!(
15627 hunks,
15628 [
15629 DisplayRow(3)..DisplayRow(5),
15630 DisplayRow(10)..DisplayRow(12),
15631 DisplayRow(17)..DisplayRow(19),
15632 ]
15633 );
15634}
15635
15636#[gpui::test]
15637async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
15638 init_test(cx, |_| {});
15639
15640 let mut cx = EditorTestContext::new(cx).await;
15641 cx.set_head_text(indoc! { "
15642 one
15643 two
15644 three
15645 four
15646 five
15647 "
15648 });
15649 cx.set_index_text(indoc! { "
15650 one
15651 two
15652 three
15653 four
15654 five
15655 "
15656 });
15657 cx.set_state(indoc! {"
15658 one
15659 TWO
15660 ˇTHREE
15661 FOUR
15662 five
15663 "});
15664 cx.run_until_parked();
15665 cx.update_editor(|editor, window, cx| {
15666 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15667 });
15668 cx.run_until_parked();
15669 cx.assert_index_text(Some(indoc! {"
15670 one
15671 TWO
15672 THREE
15673 FOUR
15674 five
15675 "}));
15676 cx.set_state(indoc! { "
15677 one
15678 TWO
15679 ˇTHREE-HUNDRED
15680 FOUR
15681 five
15682 "});
15683 cx.run_until_parked();
15684 cx.update_editor(|editor, window, cx| {
15685 let snapshot = editor.snapshot(window, cx);
15686 let hunks = editor
15687 .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15688 .collect::<Vec<_>>();
15689 assert_eq!(hunks.len(), 1);
15690 assert_eq!(
15691 hunks[0].status(),
15692 DiffHunkStatus {
15693 kind: DiffHunkStatusKind::Modified,
15694 secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
15695 }
15696 );
15697
15698 editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15699 });
15700 cx.run_until_parked();
15701 cx.assert_index_text(Some(indoc! {"
15702 one
15703 TWO
15704 THREE-HUNDRED
15705 FOUR
15706 five
15707 "}));
15708}
15709
15710#[gpui::test]
15711fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
15712 init_test(cx, |_| {});
15713
15714 let editor = cx.add_window(|window, cx| {
15715 let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
15716 build_editor(buffer, window, cx)
15717 });
15718
15719 let render_args = Arc::new(Mutex::new(None));
15720 let snapshot = editor
15721 .update(cx, |editor, window, cx| {
15722 let snapshot = editor.buffer().read(cx).snapshot(cx);
15723 let range =
15724 snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
15725
15726 struct RenderArgs {
15727 row: MultiBufferRow,
15728 folded: bool,
15729 callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
15730 }
15731
15732 let crease = Crease::inline(
15733 range,
15734 FoldPlaceholder::test(),
15735 {
15736 let toggle_callback = render_args.clone();
15737 move |row, folded, callback, _window, _cx| {
15738 *toggle_callback.lock() = Some(RenderArgs {
15739 row,
15740 folded,
15741 callback,
15742 });
15743 div()
15744 }
15745 },
15746 |_row, _folded, _window, _cx| div(),
15747 );
15748
15749 editor.insert_creases(Some(crease), cx);
15750 let snapshot = editor.snapshot(window, cx);
15751 let _div = snapshot.render_crease_toggle(
15752 MultiBufferRow(1),
15753 false,
15754 cx.entity().clone(),
15755 window,
15756 cx,
15757 );
15758 snapshot
15759 })
15760 .unwrap();
15761
15762 let render_args = render_args.lock().take().unwrap();
15763 assert_eq!(render_args.row, MultiBufferRow(1));
15764 assert!(!render_args.folded);
15765 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15766
15767 cx.update_window(*editor, |_, window, cx| {
15768 (render_args.callback)(true, window, cx)
15769 })
15770 .unwrap();
15771 let snapshot = editor
15772 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15773 .unwrap();
15774 assert!(snapshot.is_line_folded(MultiBufferRow(1)));
15775
15776 cx.update_window(*editor, |_, window, cx| {
15777 (render_args.callback)(false, window, cx)
15778 })
15779 .unwrap();
15780 let snapshot = editor
15781 .update(cx, |editor, window, cx| editor.snapshot(window, cx))
15782 .unwrap();
15783 assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
15784}
15785
15786#[gpui::test]
15787async fn test_input_text(cx: &mut TestAppContext) {
15788 init_test(cx, |_| {});
15789 let mut cx = EditorTestContext::new(cx).await;
15790
15791 cx.set_state(
15792 &r#"ˇone
15793 two
15794
15795 three
15796 fourˇ
15797 five
15798
15799 siˇx"#
15800 .unindent(),
15801 );
15802
15803 cx.dispatch_action(HandleInput(String::new()));
15804 cx.assert_editor_state(
15805 &r#"ˇone
15806 two
15807
15808 three
15809 fourˇ
15810 five
15811
15812 siˇx"#
15813 .unindent(),
15814 );
15815
15816 cx.dispatch_action(HandleInput("AAAA".to_string()));
15817 cx.assert_editor_state(
15818 &r#"AAAAˇone
15819 two
15820
15821 three
15822 fourAAAAˇ
15823 five
15824
15825 siAAAAˇx"#
15826 .unindent(),
15827 );
15828}
15829
15830#[gpui::test]
15831async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
15832 init_test(cx, |_| {});
15833
15834 let mut cx = EditorTestContext::new(cx).await;
15835 cx.set_state(
15836 r#"let foo = 1;
15837let foo = 2;
15838let foo = 3;
15839let fooˇ = 4;
15840let foo = 5;
15841let foo = 6;
15842let foo = 7;
15843let foo = 8;
15844let foo = 9;
15845let foo = 10;
15846let foo = 11;
15847let foo = 12;
15848let foo = 13;
15849let foo = 14;
15850let foo = 15;"#,
15851 );
15852
15853 cx.update_editor(|e, window, cx| {
15854 assert_eq!(
15855 e.next_scroll_position,
15856 NextScrollCursorCenterTopBottom::Center,
15857 "Default next scroll direction is center",
15858 );
15859
15860 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15861 assert_eq!(
15862 e.next_scroll_position,
15863 NextScrollCursorCenterTopBottom::Top,
15864 "After center, next scroll direction should be top",
15865 );
15866
15867 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15868 assert_eq!(
15869 e.next_scroll_position,
15870 NextScrollCursorCenterTopBottom::Bottom,
15871 "After top, next scroll direction should be bottom",
15872 );
15873
15874 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15875 assert_eq!(
15876 e.next_scroll_position,
15877 NextScrollCursorCenterTopBottom::Center,
15878 "After bottom, scrolling should start over",
15879 );
15880
15881 e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
15882 assert_eq!(
15883 e.next_scroll_position,
15884 NextScrollCursorCenterTopBottom::Top,
15885 "Scrolling continues if retriggered fast enough"
15886 );
15887 });
15888
15889 cx.executor()
15890 .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
15891 cx.executor().run_until_parked();
15892 cx.update_editor(|e, _, _| {
15893 assert_eq!(
15894 e.next_scroll_position,
15895 NextScrollCursorCenterTopBottom::Center,
15896 "If scrolling is not triggered fast enough, it should reset"
15897 );
15898 });
15899}
15900
15901#[gpui::test]
15902async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
15903 init_test(cx, |_| {});
15904 let mut cx = EditorLspTestContext::new_rust(
15905 lsp::ServerCapabilities {
15906 definition_provider: Some(lsp::OneOf::Left(true)),
15907 references_provider: Some(lsp::OneOf::Left(true)),
15908 ..lsp::ServerCapabilities::default()
15909 },
15910 cx,
15911 )
15912 .await;
15913
15914 let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
15915 let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
15916 move |params, _| async move {
15917 if empty_go_to_definition {
15918 Ok(None)
15919 } else {
15920 Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
15921 uri: params.text_document_position_params.text_document.uri,
15922 range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
15923 })))
15924 }
15925 },
15926 );
15927 let references =
15928 cx.lsp
15929 .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
15930 Ok(Some(vec![lsp::Location {
15931 uri: params.text_document_position.text_document.uri,
15932 range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
15933 }]))
15934 });
15935 (go_to_definition, references)
15936 };
15937
15938 cx.set_state(
15939 &r#"fn one() {
15940 let mut a = ˇtwo();
15941 }
15942
15943 fn two() {}"#
15944 .unindent(),
15945 );
15946 set_up_lsp_handlers(false, &mut cx);
15947 let navigated = cx
15948 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
15949 .await
15950 .expect("Failed to navigate to definition");
15951 assert_eq!(
15952 navigated,
15953 Navigated::Yes,
15954 "Should have navigated to definition from the GetDefinition response"
15955 );
15956 cx.assert_editor_state(
15957 &r#"fn one() {
15958 let mut a = two();
15959 }
15960
15961 fn «twoˇ»() {}"#
15962 .unindent(),
15963 );
15964
15965 let editors = cx.update_workspace(|workspace, _, cx| {
15966 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
15967 });
15968 cx.update_editor(|_, _, test_editor_cx| {
15969 assert_eq!(
15970 editors.len(),
15971 1,
15972 "Initially, only one, test, editor should be open in the workspace"
15973 );
15974 assert_eq!(
15975 test_editor_cx.entity(),
15976 editors.last().expect("Asserted len is 1").clone()
15977 );
15978 });
15979
15980 set_up_lsp_handlers(true, &mut cx);
15981 let navigated = cx
15982 .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
15983 .await
15984 .expect("Failed to navigate to lookup references");
15985 assert_eq!(
15986 navigated,
15987 Navigated::Yes,
15988 "Should have navigated to references as a fallback after empty GoToDefinition response"
15989 );
15990 // We should not change the selections in the existing file,
15991 // if opening another milti buffer with the references
15992 cx.assert_editor_state(
15993 &r#"fn one() {
15994 let mut a = two();
15995 }
15996
15997 fn «twoˇ»() {}"#
15998 .unindent(),
15999 );
16000 let editors = cx.update_workspace(|workspace, _, cx| {
16001 workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
16002 });
16003 cx.update_editor(|_, _, test_editor_cx| {
16004 assert_eq!(
16005 editors.len(),
16006 2,
16007 "After falling back to references search, we open a new editor with the results"
16008 );
16009 let references_fallback_text = editors
16010 .into_iter()
16011 .find(|new_editor| *new_editor != test_editor_cx.entity())
16012 .expect("Should have one non-test editor now")
16013 .read(test_editor_cx)
16014 .text(test_editor_cx);
16015 assert_eq!(
16016 references_fallback_text, "fn one() {\n let mut a = two();\n}",
16017 "Should use the range from the references response and not the GoToDefinition one"
16018 );
16019 });
16020}
16021
16022#[gpui::test]
16023async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
16024 init_test(cx, |_| {});
16025
16026 let language = Arc::new(Language::new(
16027 LanguageConfig::default(),
16028 Some(tree_sitter_rust::LANGUAGE.into()),
16029 ));
16030
16031 let text = r#"
16032 #[cfg(test)]
16033 mod tests() {
16034 #[test]
16035 fn runnable_1() {
16036 let a = 1;
16037 }
16038
16039 #[test]
16040 fn runnable_2() {
16041 let a = 1;
16042 let b = 2;
16043 }
16044 }
16045 "#
16046 .unindent();
16047
16048 let fs = FakeFs::new(cx.executor());
16049 fs.insert_file("/file.rs", Default::default()).await;
16050
16051 let project = Project::test(fs, ["/a".as_ref()], cx).await;
16052 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16053 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16054 let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16055 let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
16056
16057 let editor = cx.new_window_entity(|window, cx| {
16058 Editor::new(
16059 EditorMode::Full,
16060 multi_buffer,
16061 Some(project.clone()),
16062 true,
16063 window,
16064 cx,
16065 )
16066 });
16067
16068 editor.update_in(cx, |editor, window, cx| {
16069 let snapshot = editor.buffer().read(cx).snapshot(cx);
16070 editor.tasks.insert(
16071 (buffer.read(cx).remote_id(), 3),
16072 RunnableTasks {
16073 templates: vec![],
16074 offset: snapshot.anchor_before(43),
16075 column: 0,
16076 extra_variables: HashMap::default(),
16077 context_range: BufferOffset(43)..BufferOffset(85),
16078 },
16079 );
16080 editor.tasks.insert(
16081 (buffer.read(cx).remote_id(), 8),
16082 RunnableTasks {
16083 templates: vec![],
16084 offset: snapshot.anchor_before(86),
16085 column: 0,
16086 extra_variables: HashMap::default(),
16087 context_range: BufferOffset(86)..BufferOffset(191),
16088 },
16089 );
16090
16091 // Test finding task when cursor is inside function body
16092 editor.change_selections(None, window, cx, |s| {
16093 s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
16094 });
16095 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
16096 assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
16097
16098 // Test finding task when cursor is on function name
16099 editor.change_selections(None, window, cx, |s| {
16100 s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
16101 });
16102 let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
16103 assert_eq!(row, 8, "Should find task when cursor is on function name");
16104 });
16105}
16106
16107#[gpui::test]
16108async fn test_folding_buffers(cx: &mut TestAppContext) {
16109 init_test(cx, |_| {});
16110
16111 let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16112 let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
16113 let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
16114
16115 let fs = FakeFs::new(cx.executor());
16116 fs.insert_tree(
16117 path!("/a"),
16118 json!({
16119 "first.rs": sample_text_1,
16120 "second.rs": sample_text_2,
16121 "third.rs": sample_text_3,
16122 }),
16123 )
16124 .await;
16125 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16126 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16127 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16128 let worktree = project.update(cx, |project, cx| {
16129 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16130 assert_eq!(worktrees.len(), 1);
16131 worktrees.pop().unwrap()
16132 });
16133 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16134
16135 let buffer_1 = project
16136 .update(cx, |project, cx| {
16137 project.open_buffer((worktree_id, "first.rs"), cx)
16138 })
16139 .await
16140 .unwrap();
16141 let buffer_2 = project
16142 .update(cx, |project, cx| {
16143 project.open_buffer((worktree_id, "second.rs"), cx)
16144 })
16145 .await
16146 .unwrap();
16147 let buffer_3 = project
16148 .update(cx, |project, cx| {
16149 project.open_buffer((worktree_id, "third.rs"), cx)
16150 })
16151 .await
16152 .unwrap();
16153
16154 let multi_buffer = cx.new(|cx| {
16155 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16156 multi_buffer.push_excerpts(
16157 buffer_1.clone(),
16158 [
16159 ExcerptRange {
16160 context: Point::new(0, 0)..Point::new(3, 0),
16161 primary: None,
16162 },
16163 ExcerptRange {
16164 context: Point::new(5, 0)..Point::new(7, 0),
16165 primary: None,
16166 },
16167 ExcerptRange {
16168 context: Point::new(9, 0)..Point::new(10, 4),
16169 primary: None,
16170 },
16171 ],
16172 cx,
16173 );
16174 multi_buffer.push_excerpts(
16175 buffer_2.clone(),
16176 [
16177 ExcerptRange {
16178 context: Point::new(0, 0)..Point::new(3, 0),
16179 primary: None,
16180 },
16181 ExcerptRange {
16182 context: Point::new(5, 0)..Point::new(7, 0),
16183 primary: None,
16184 },
16185 ExcerptRange {
16186 context: Point::new(9, 0)..Point::new(10, 4),
16187 primary: None,
16188 },
16189 ],
16190 cx,
16191 );
16192 multi_buffer.push_excerpts(
16193 buffer_3.clone(),
16194 [
16195 ExcerptRange {
16196 context: Point::new(0, 0)..Point::new(3, 0),
16197 primary: None,
16198 },
16199 ExcerptRange {
16200 context: Point::new(5, 0)..Point::new(7, 0),
16201 primary: None,
16202 },
16203 ExcerptRange {
16204 context: Point::new(9, 0)..Point::new(10, 4),
16205 primary: None,
16206 },
16207 ],
16208 cx,
16209 );
16210 multi_buffer
16211 });
16212 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16213 Editor::new(
16214 EditorMode::Full,
16215 multi_buffer.clone(),
16216 Some(project.clone()),
16217 true,
16218 window,
16219 cx,
16220 )
16221 });
16222
16223 assert_eq!(
16224 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16225 "\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
16226 );
16227
16228 multi_buffer_editor.update(cx, |editor, cx| {
16229 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16230 });
16231 assert_eq!(
16232 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16233 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
16234 "After folding the first buffer, its text should not be displayed"
16235 );
16236
16237 multi_buffer_editor.update(cx, |editor, cx| {
16238 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16239 });
16240 assert_eq!(
16241 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16242 "\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
16243 "After folding the second buffer, its text should not be displayed"
16244 );
16245
16246 multi_buffer_editor.update(cx, |editor, cx| {
16247 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16248 });
16249 assert_eq!(
16250 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16251 "\n\n\n\n\n",
16252 "After folding the third buffer, its text should not be displayed"
16253 );
16254
16255 // Emulate selection inside the fold logic, that should work
16256 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16257 editor
16258 .snapshot(window, cx)
16259 .next_line_boundary(Point::new(0, 4));
16260 });
16261
16262 multi_buffer_editor.update(cx, |editor, cx| {
16263 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16264 });
16265 assert_eq!(
16266 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16267 "\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
16268 "After unfolding the second buffer, its text should be displayed"
16269 );
16270
16271 // Typing inside of buffer 1 causes that buffer to be unfolded.
16272 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16273 assert_eq!(
16274 multi_buffer
16275 .read(cx)
16276 .snapshot(cx)
16277 .text_for_range(Point::new(1, 0)..Point::new(1, 4))
16278 .collect::<String>(),
16279 "bbbb"
16280 );
16281 editor.change_selections(None, window, cx, |selections| {
16282 selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
16283 });
16284 editor.handle_input("B", window, cx);
16285 });
16286
16287 assert_eq!(
16288 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16289 "\n\n\nB\n\n\n\n\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
16290 "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
16291 );
16292
16293 multi_buffer_editor.update(cx, |editor, cx| {
16294 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16295 });
16296 assert_eq!(
16297 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16298 "\n\n\nB\n\n\n\n\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
16299 "After unfolding the all buffers, all original text should be displayed"
16300 );
16301}
16302
16303#[gpui::test]
16304async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
16305 init_test(cx, |_| {});
16306
16307 let sample_text_1 = "1111\n2222\n3333".to_string();
16308 let sample_text_2 = "4444\n5555\n6666".to_string();
16309 let sample_text_3 = "7777\n8888\n9999".to_string();
16310
16311 let fs = FakeFs::new(cx.executor());
16312 fs.insert_tree(
16313 path!("/a"),
16314 json!({
16315 "first.rs": sample_text_1,
16316 "second.rs": sample_text_2,
16317 "third.rs": sample_text_3,
16318 }),
16319 )
16320 .await;
16321 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16322 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16323 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16324 let worktree = project.update(cx, |project, cx| {
16325 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16326 assert_eq!(worktrees.len(), 1);
16327 worktrees.pop().unwrap()
16328 });
16329 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16330
16331 let buffer_1 = project
16332 .update(cx, |project, cx| {
16333 project.open_buffer((worktree_id, "first.rs"), cx)
16334 })
16335 .await
16336 .unwrap();
16337 let buffer_2 = project
16338 .update(cx, |project, cx| {
16339 project.open_buffer((worktree_id, "second.rs"), cx)
16340 })
16341 .await
16342 .unwrap();
16343 let buffer_3 = project
16344 .update(cx, |project, cx| {
16345 project.open_buffer((worktree_id, "third.rs"), cx)
16346 })
16347 .await
16348 .unwrap();
16349
16350 let multi_buffer = cx.new(|cx| {
16351 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16352 multi_buffer.push_excerpts(
16353 buffer_1.clone(),
16354 [ExcerptRange {
16355 context: Point::new(0, 0)..Point::new(3, 0),
16356 primary: None,
16357 }],
16358 cx,
16359 );
16360 multi_buffer.push_excerpts(
16361 buffer_2.clone(),
16362 [ExcerptRange {
16363 context: Point::new(0, 0)..Point::new(3, 0),
16364 primary: None,
16365 }],
16366 cx,
16367 );
16368 multi_buffer.push_excerpts(
16369 buffer_3.clone(),
16370 [ExcerptRange {
16371 context: Point::new(0, 0)..Point::new(3, 0),
16372 primary: None,
16373 }],
16374 cx,
16375 );
16376 multi_buffer
16377 });
16378
16379 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16380 Editor::new(
16381 EditorMode::Full,
16382 multi_buffer,
16383 Some(project.clone()),
16384 true,
16385 window,
16386 cx,
16387 )
16388 });
16389
16390 let full_text = "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n";
16391 assert_eq!(
16392 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16393 full_text,
16394 );
16395
16396 multi_buffer_editor.update(cx, |editor, cx| {
16397 editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16398 });
16399 assert_eq!(
16400 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16401 "\n\n\n\n\n4444\n5555\n6666\n\n\n\n\n7777\n8888\n9999\n",
16402 "After folding the first buffer, its text should not be displayed"
16403 );
16404
16405 multi_buffer_editor.update(cx, |editor, cx| {
16406 editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16407 });
16408
16409 assert_eq!(
16410 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16411 "\n\n\n\n\n\n\n7777\n8888\n9999\n",
16412 "After folding the second buffer, its text should not be displayed"
16413 );
16414
16415 multi_buffer_editor.update(cx, |editor, cx| {
16416 editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16417 });
16418 assert_eq!(
16419 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16420 "\n\n\n\n\n",
16421 "After folding the third buffer, its text should not be displayed"
16422 );
16423
16424 multi_buffer_editor.update(cx, |editor, cx| {
16425 editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16426 });
16427 assert_eq!(
16428 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16429 "\n\n\n\n\n4444\n5555\n6666\n\n\n",
16430 "After unfolding the second buffer, its text should be displayed"
16431 );
16432
16433 multi_buffer_editor.update(cx, |editor, cx| {
16434 editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
16435 });
16436 assert_eq!(
16437 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16438 "\n\n\n1111\n2222\n3333\n\n\n\n\n4444\n5555\n6666\n\n\n",
16439 "After unfolding the first buffer, its text should be displayed"
16440 );
16441
16442 multi_buffer_editor.update(cx, |editor, cx| {
16443 editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16444 });
16445 assert_eq!(
16446 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16447 full_text,
16448 "After unfolding all buffers, all original text should be displayed"
16449 );
16450}
16451
16452#[gpui::test]
16453async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
16454 init_test(cx, |_| {});
16455
16456 let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16457
16458 let fs = FakeFs::new(cx.executor());
16459 fs.insert_tree(
16460 path!("/a"),
16461 json!({
16462 "main.rs": sample_text,
16463 }),
16464 )
16465 .await;
16466 let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16467 let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16468 let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16469 let worktree = project.update(cx, |project, cx| {
16470 let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16471 assert_eq!(worktrees.len(), 1);
16472 worktrees.pop().unwrap()
16473 });
16474 let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16475
16476 let buffer_1 = project
16477 .update(cx, |project, cx| {
16478 project.open_buffer((worktree_id, "main.rs"), cx)
16479 })
16480 .await
16481 .unwrap();
16482
16483 let multi_buffer = cx.new(|cx| {
16484 let mut multi_buffer = MultiBuffer::new(ReadWrite);
16485 multi_buffer.push_excerpts(
16486 buffer_1.clone(),
16487 [ExcerptRange {
16488 context: Point::new(0, 0)
16489 ..Point::new(
16490 sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
16491 0,
16492 ),
16493 primary: None,
16494 }],
16495 cx,
16496 );
16497 multi_buffer
16498 });
16499 let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16500 Editor::new(
16501 EditorMode::Full,
16502 multi_buffer,
16503 Some(project.clone()),
16504 true,
16505 window,
16506 cx,
16507 )
16508 });
16509
16510 let selection_range = Point::new(1, 0)..Point::new(2, 0);
16511 multi_buffer_editor.update_in(cx, |editor, window, cx| {
16512 enum TestHighlight {}
16513 let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
16514 let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
16515 editor.highlight_text::<TestHighlight>(
16516 vec![highlight_range.clone()],
16517 HighlightStyle::color(Hsla::green()),
16518 cx,
16519 );
16520 editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
16521 });
16522
16523 let full_text = format!("\n\n\n{sample_text}\n");
16524 assert_eq!(
16525 multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16526 full_text,
16527 );
16528}
16529
16530#[gpui::test]
16531async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
16532 init_test(cx, |_| {});
16533 cx.update(|cx| {
16534 let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
16535 "keymaps/default-linux.json",
16536 cx,
16537 )
16538 .unwrap();
16539 cx.bind_keys(default_key_bindings);
16540 });
16541
16542 let (editor, cx) = cx.add_window_view(|window, cx| {
16543 let multi_buffer = MultiBuffer::build_multi(
16544 [
16545 ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
16546 ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
16547 ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
16548 ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
16549 ],
16550 cx,
16551 );
16552 let mut editor = Editor::new(
16553 EditorMode::Full,
16554 multi_buffer.clone(),
16555 None,
16556 true,
16557 window,
16558 cx,
16559 );
16560
16561 let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
16562 // fold all but the second buffer, so that we test navigating between two
16563 // adjacent folded buffers, as well as folded buffers at the start and
16564 // end the multibuffer
16565 editor.fold_buffer(buffer_ids[0], cx);
16566 editor.fold_buffer(buffer_ids[2], cx);
16567 editor.fold_buffer(buffer_ids[3], cx);
16568
16569 editor
16570 });
16571 cx.simulate_resize(size(px(1000.), px(1000.)));
16572
16573 let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
16574 cx.assert_excerpts_with_selections(indoc! {"
16575 [EXCERPT]
16576 ˇ[FOLDED]
16577 [EXCERPT]
16578 a1
16579 b1
16580 [EXCERPT]
16581 [FOLDED]
16582 [EXCERPT]
16583 [FOLDED]
16584 "
16585 });
16586 cx.simulate_keystroke("down");
16587 cx.assert_excerpts_with_selections(indoc! {"
16588 [EXCERPT]
16589 [FOLDED]
16590 [EXCERPT]
16591 ˇa1
16592 b1
16593 [EXCERPT]
16594 [FOLDED]
16595 [EXCERPT]
16596 [FOLDED]
16597 "
16598 });
16599 cx.simulate_keystroke("down");
16600 cx.assert_excerpts_with_selections(indoc! {"
16601 [EXCERPT]
16602 [FOLDED]
16603 [EXCERPT]
16604 a1
16605 ˇb1
16606 [EXCERPT]
16607 [FOLDED]
16608 [EXCERPT]
16609 [FOLDED]
16610 "
16611 });
16612 cx.simulate_keystroke("down");
16613 cx.assert_excerpts_with_selections(indoc! {"
16614 [EXCERPT]
16615 [FOLDED]
16616 [EXCERPT]
16617 a1
16618 b1
16619 ˇ[EXCERPT]
16620 [FOLDED]
16621 [EXCERPT]
16622 [FOLDED]
16623 "
16624 });
16625 cx.simulate_keystroke("down");
16626 cx.assert_excerpts_with_selections(indoc! {"
16627 [EXCERPT]
16628 [FOLDED]
16629 [EXCERPT]
16630 a1
16631 b1
16632 [EXCERPT]
16633 ˇ[FOLDED]
16634 [EXCERPT]
16635 [FOLDED]
16636 "
16637 });
16638 for _ in 0..5 {
16639 cx.simulate_keystroke("down");
16640 cx.assert_excerpts_with_selections(indoc! {"
16641 [EXCERPT]
16642 [FOLDED]
16643 [EXCERPT]
16644 a1
16645 b1
16646 [EXCERPT]
16647 [FOLDED]
16648 [EXCERPT]
16649 ˇ[FOLDED]
16650 "
16651 });
16652 }
16653
16654 cx.simulate_keystroke("up");
16655 cx.assert_excerpts_with_selections(indoc! {"
16656 [EXCERPT]
16657 [FOLDED]
16658 [EXCERPT]
16659 a1
16660 b1
16661 [EXCERPT]
16662 ˇ[FOLDED]
16663 [EXCERPT]
16664 [FOLDED]
16665 "
16666 });
16667 cx.simulate_keystroke("up");
16668 cx.assert_excerpts_with_selections(indoc! {"
16669 [EXCERPT]
16670 [FOLDED]
16671 [EXCERPT]
16672 a1
16673 b1
16674 ˇ[EXCERPT]
16675 [FOLDED]
16676 [EXCERPT]
16677 [FOLDED]
16678 "
16679 });
16680 cx.simulate_keystroke("up");
16681 cx.assert_excerpts_with_selections(indoc! {"
16682 [EXCERPT]
16683 [FOLDED]
16684 [EXCERPT]
16685 a1
16686 ˇb1
16687 [EXCERPT]
16688 [FOLDED]
16689 [EXCERPT]
16690 [FOLDED]
16691 "
16692 });
16693 cx.simulate_keystroke("up");
16694 cx.assert_excerpts_with_selections(indoc! {"
16695 [EXCERPT]
16696 [FOLDED]
16697 [EXCERPT]
16698 ˇa1
16699 b1
16700 [EXCERPT]
16701 [FOLDED]
16702 [EXCERPT]
16703 [FOLDED]
16704 "
16705 });
16706 for _ in 0..5 {
16707 cx.simulate_keystroke("up");
16708 cx.assert_excerpts_with_selections(indoc! {"
16709 [EXCERPT]
16710 ˇ[FOLDED]
16711 [EXCERPT]
16712 a1
16713 b1
16714 [EXCERPT]
16715 [FOLDED]
16716 [EXCERPT]
16717 [FOLDED]
16718 "
16719 });
16720 }
16721}
16722
16723#[gpui::test]
16724async fn test_inline_completion_text(cx: &mut TestAppContext) {
16725 init_test(cx, |_| {});
16726
16727 // Simple insertion
16728 assert_highlighted_edits(
16729 "Hello, world!",
16730 vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
16731 true,
16732 cx,
16733 |highlighted_edits, cx| {
16734 assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
16735 assert_eq!(highlighted_edits.highlights.len(), 1);
16736 assert_eq!(highlighted_edits.highlights[0].0, 6..16);
16737 assert_eq!(
16738 highlighted_edits.highlights[0].1.background_color,
16739 Some(cx.theme().status().created_background)
16740 );
16741 },
16742 )
16743 .await;
16744
16745 // Replacement
16746 assert_highlighted_edits(
16747 "This is a test.",
16748 vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
16749 false,
16750 cx,
16751 |highlighted_edits, cx| {
16752 assert_eq!(highlighted_edits.text, "That is a test.");
16753 assert_eq!(highlighted_edits.highlights.len(), 1);
16754 assert_eq!(highlighted_edits.highlights[0].0, 0..4);
16755 assert_eq!(
16756 highlighted_edits.highlights[0].1.background_color,
16757 Some(cx.theme().status().created_background)
16758 );
16759 },
16760 )
16761 .await;
16762
16763 // Multiple edits
16764 assert_highlighted_edits(
16765 "Hello, world!",
16766 vec![
16767 (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
16768 (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
16769 ],
16770 false,
16771 cx,
16772 |highlighted_edits, cx| {
16773 assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
16774 assert_eq!(highlighted_edits.highlights.len(), 2);
16775 assert_eq!(highlighted_edits.highlights[0].0, 0..9);
16776 assert_eq!(highlighted_edits.highlights[1].0, 16..29);
16777 assert_eq!(
16778 highlighted_edits.highlights[0].1.background_color,
16779 Some(cx.theme().status().created_background)
16780 );
16781 assert_eq!(
16782 highlighted_edits.highlights[1].1.background_color,
16783 Some(cx.theme().status().created_background)
16784 );
16785 },
16786 )
16787 .await;
16788
16789 // Multiple lines with edits
16790 assert_highlighted_edits(
16791 "First line\nSecond line\nThird line\nFourth line",
16792 vec![
16793 (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
16794 (
16795 Point::new(2, 0)..Point::new(2, 10),
16796 "New third line".to_string(),
16797 ),
16798 (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
16799 ],
16800 false,
16801 cx,
16802 |highlighted_edits, cx| {
16803 assert_eq!(
16804 highlighted_edits.text,
16805 "Second modified\nNew third line\nFourth updated line"
16806 );
16807 assert_eq!(highlighted_edits.highlights.len(), 3);
16808 assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
16809 assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
16810 assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
16811 for highlight in &highlighted_edits.highlights {
16812 assert_eq!(
16813 highlight.1.background_color,
16814 Some(cx.theme().status().created_background)
16815 );
16816 }
16817 },
16818 )
16819 .await;
16820}
16821
16822#[gpui::test]
16823async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
16824 init_test(cx, |_| {});
16825
16826 // Deletion
16827 assert_highlighted_edits(
16828 "Hello, world!",
16829 vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
16830 true,
16831 cx,
16832 |highlighted_edits, cx| {
16833 assert_eq!(highlighted_edits.text, "Hello, world!");
16834 assert_eq!(highlighted_edits.highlights.len(), 1);
16835 assert_eq!(highlighted_edits.highlights[0].0, 5..11);
16836 assert_eq!(
16837 highlighted_edits.highlights[0].1.background_color,
16838 Some(cx.theme().status().deleted_background)
16839 );
16840 },
16841 )
16842 .await;
16843
16844 // Insertion
16845 assert_highlighted_edits(
16846 "Hello, world!",
16847 vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
16848 true,
16849 cx,
16850 |highlighted_edits, cx| {
16851 assert_eq!(highlighted_edits.highlights.len(), 1);
16852 assert_eq!(highlighted_edits.highlights[0].0, 6..14);
16853 assert_eq!(
16854 highlighted_edits.highlights[0].1.background_color,
16855 Some(cx.theme().status().created_background)
16856 );
16857 },
16858 )
16859 .await;
16860}
16861
16862async fn assert_highlighted_edits(
16863 text: &str,
16864 edits: Vec<(Range<Point>, String)>,
16865 include_deletions: bool,
16866 cx: &mut TestAppContext,
16867 assertion_fn: impl Fn(HighlightedText, &App),
16868) {
16869 let window = cx.add_window(|window, cx| {
16870 let buffer = MultiBuffer::build_simple(text, cx);
16871 Editor::new(EditorMode::Full, buffer, None, true, window, cx)
16872 });
16873 let cx = &mut VisualTestContext::from_window(*window, cx);
16874
16875 let (buffer, snapshot) = window
16876 .update(cx, |editor, _window, cx| {
16877 (
16878 editor.buffer().clone(),
16879 editor.buffer().read(cx).snapshot(cx),
16880 )
16881 })
16882 .unwrap();
16883
16884 let edits = edits
16885 .into_iter()
16886 .map(|(range, edit)| {
16887 (
16888 snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
16889 edit,
16890 )
16891 })
16892 .collect::<Vec<_>>();
16893
16894 let text_anchor_edits = edits
16895 .clone()
16896 .into_iter()
16897 .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
16898 .collect::<Vec<_>>();
16899
16900 let edit_preview = window
16901 .update(cx, |_, _window, cx| {
16902 buffer
16903 .read(cx)
16904 .as_singleton()
16905 .unwrap()
16906 .read(cx)
16907 .preview_edits(text_anchor_edits.into(), cx)
16908 })
16909 .unwrap()
16910 .await;
16911
16912 cx.update(|_window, cx| {
16913 let highlighted_edits = inline_completion_edit_text(
16914 &snapshot.as_singleton().unwrap().2,
16915 &edits,
16916 &edit_preview,
16917 include_deletions,
16918 cx,
16919 );
16920 assertion_fn(highlighted_edits, cx)
16921 });
16922}
16923
16924#[gpui::test]
16925async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
16926 init_test(cx, |_| {});
16927 let capabilities = lsp::ServerCapabilities {
16928 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
16929 prepare_provider: Some(true),
16930 work_done_progress_options: Default::default(),
16931 })),
16932 ..Default::default()
16933 };
16934 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
16935
16936 cx.set_state(indoc! {"
16937 struct Fˇoo {}
16938 "});
16939
16940 cx.update_editor(|editor, _, cx| {
16941 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
16942 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
16943 editor.highlight_background::<DocumentHighlightRead>(
16944 &[highlight_range],
16945 |c| c.editor_document_highlight_read_background,
16946 cx,
16947 );
16948 });
16949
16950 let mut prepare_rename_handler =
16951 cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
16952 Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
16953 start: lsp::Position {
16954 line: 0,
16955 character: 7,
16956 },
16957 end: lsp::Position {
16958 line: 0,
16959 character: 10,
16960 },
16961 })))
16962 });
16963 let prepare_rename_task = cx
16964 .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
16965 .expect("Prepare rename was not started");
16966 prepare_rename_handler.next().await.unwrap();
16967 prepare_rename_task.await.expect("Prepare rename failed");
16968
16969 let mut rename_handler =
16970 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
16971 let edit = lsp::TextEdit {
16972 range: lsp::Range {
16973 start: lsp::Position {
16974 line: 0,
16975 character: 7,
16976 },
16977 end: lsp::Position {
16978 line: 0,
16979 character: 10,
16980 },
16981 },
16982 new_text: "FooRenamed".to_string(),
16983 };
16984 Ok(Some(lsp::WorkspaceEdit::new(
16985 // Specify the same edit twice
16986 std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
16987 )))
16988 });
16989 let rename_task = cx
16990 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
16991 .expect("Confirm rename was not started");
16992 rename_handler.next().await.unwrap();
16993 rename_task.await.expect("Confirm rename failed");
16994 cx.run_until_parked();
16995
16996 // Despite two edits, only one is actually applied as those are identical
16997 cx.assert_editor_state(indoc! {"
16998 struct FooRenamedˇ {}
16999 "});
17000}
17001
17002#[gpui::test]
17003async fn test_rename_without_prepare(cx: &mut TestAppContext) {
17004 init_test(cx, |_| {});
17005 // These capabilities indicate that the server does not support prepare rename.
17006 let capabilities = lsp::ServerCapabilities {
17007 rename_provider: Some(lsp::OneOf::Left(true)),
17008 ..Default::default()
17009 };
17010 let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
17011
17012 cx.set_state(indoc! {"
17013 struct Fˇoo {}
17014 "});
17015
17016 cx.update_editor(|editor, _window, cx| {
17017 let highlight_range = Point::new(0, 7)..Point::new(0, 10);
17018 let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
17019 editor.highlight_background::<DocumentHighlightRead>(
17020 &[highlight_range],
17021 |c| c.editor_document_highlight_read_background,
17022 cx,
17023 );
17024 });
17025
17026 cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
17027 .expect("Prepare rename was not started")
17028 .await
17029 .expect("Prepare rename failed");
17030
17031 let mut rename_handler =
17032 cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
17033 let edit = lsp::TextEdit {
17034 range: lsp::Range {
17035 start: lsp::Position {
17036 line: 0,
17037 character: 7,
17038 },
17039 end: lsp::Position {
17040 line: 0,
17041 character: 10,
17042 },
17043 },
17044 new_text: "FooRenamed".to_string(),
17045 };
17046 Ok(Some(lsp::WorkspaceEdit::new(
17047 std::collections::HashMap::from_iter(Some((url, vec![edit]))),
17048 )))
17049 });
17050 let rename_task = cx
17051 .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
17052 .expect("Confirm rename was not started");
17053 rename_handler.next().await.unwrap();
17054 rename_task.await.expect("Confirm rename failed");
17055 cx.run_until_parked();
17056
17057 // Correct range is renamed, as `surrounding_word` is used to find it.
17058 cx.assert_editor_state(indoc! {"
17059 struct FooRenamedˇ {}
17060 "});
17061}
17062
17063#[gpui::test]
17064async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
17065 init_test(cx, |_| {});
17066 let mut cx = EditorTestContext::new(cx).await;
17067
17068 let language = Arc::new(
17069 Language::new(
17070 LanguageConfig::default(),
17071 Some(tree_sitter_html::LANGUAGE.into()),
17072 )
17073 .with_brackets_query(
17074 r#"
17075 ("<" @open "/>" @close)
17076 ("</" @open ">" @close)
17077 ("<" @open ">" @close)
17078 ("\"" @open "\"" @close)
17079 ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
17080 "#,
17081 )
17082 .unwrap(),
17083 );
17084 cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
17085
17086 cx.set_state(indoc! {"
17087 <span>ˇ</span>
17088 "});
17089 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17090 cx.assert_editor_state(indoc! {"
17091 <span>
17092 ˇ
17093 </span>
17094 "});
17095
17096 cx.set_state(indoc! {"
17097 <span><span></span>ˇ</span>
17098 "});
17099 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17100 cx.assert_editor_state(indoc! {"
17101 <span><span></span>
17102 ˇ</span>
17103 "});
17104
17105 cx.set_state(indoc! {"
17106 <span>ˇ
17107 </span>
17108 "});
17109 cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17110 cx.assert_editor_state(indoc! {"
17111 <span>
17112 ˇ
17113 </span>
17114 "});
17115}
17116
17117mod autoclose_tags {
17118 use super::*;
17119 use language::language_settings::JsxTagAutoCloseSettings;
17120 use languages::language;
17121
17122 async fn test_setup(cx: &mut TestAppContext) -> EditorTestContext {
17123 init_test(cx, |settings| {
17124 settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true });
17125 });
17126
17127 let mut cx = EditorTestContext::new(cx).await;
17128 cx.update_buffer(|buffer, cx| {
17129 let language = language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into());
17130
17131 buffer.set_language(Some(language), cx)
17132 });
17133
17134 cx
17135 }
17136
17137 macro_rules! check {
17138 ($name:ident, $initial:literal + $input:literal => $expected:expr) => {
17139 #[gpui::test]
17140 async fn $name(cx: &mut TestAppContext) {
17141 let mut cx = test_setup(cx).await;
17142 cx.set_state($initial);
17143 cx.run_until_parked();
17144
17145 cx.update_editor(|editor, window, cx| {
17146 editor.handle_input($input, window, cx);
17147 });
17148 cx.run_until_parked();
17149 cx.assert_editor_state($expected);
17150 }
17151 };
17152 }
17153
17154 check!(
17155 test_basic,
17156 "<divˇ" + ">" => "<div>ˇ</div>"
17157 );
17158
17159 check!(
17160 test_basic_nested,
17161 "<div><divˇ</div>" + ">" => "<div><div>ˇ</div></div>"
17162 );
17163
17164 check!(
17165 test_basic_ignore_already_closed,
17166 "<div><divˇ</div></div>" + ">" => "<div><div>ˇ</div></div>"
17167 );
17168
17169 check!(
17170 test_doesnt_autoclose_closing_tag,
17171 "</divˇ" + ">" => "</div>ˇ"
17172 );
17173
17174 check!(
17175 test_jsx_attr,
17176 "<div attr={</div>}ˇ" + ">" => "<div attr={</div>}>ˇ</div>"
17177 );
17178
17179 check!(
17180 test_ignores_closing_tags_in_expr_block,
17181 "<div><divˇ{</div>}</div>" + ">" => "<div><div>ˇ</div>{</div>}</div>"
17182 );
17183
17184 check!(
17185 test_doesnt_autoclose_on_gt_in_expr,
17186 "<div attr={1 ˇ" + ">" => "<div attr={1 >ˇ"
17187 );
17188
17189 check!(
17190 test_ignores_closing_tags_with_different_tag_names,
17191 "<div><divˇ</div></span>" + ">" => "<div><div>ˇ</div></div></span>"
17192 );
17193
17194 check!(
17195 test_autocloses_in_jsx_expression,
17196 "<div>{<divˇ}</div>" + ">" => "<div>{<div>ˇ</div>}</div>"
17197 );
17198
17199 check!(
17200 test_doesnt_autoclose_already_closed_in_jsx_expression,
17201 "<div>{<divˇ</div>}</div>" + ">" => "<div>{<div>ˇ</div>}</div>"
17202 );
17203
17204 check!(
17205 test_autocloses_fragment,
17206 "<ˇ" + ">" => "<>ˇ</>"
17207 );
17208
17209 check!(
17210 test_does_not_include_type_argument_in_autoclose_tag_name,
17211 "<Component<T> attr={boolean_value}ˇ" + ">" => "<Component<T> attr={boolean_value}>ˇ</Component>"
17212 );
17213
17214 check!(
17215 test_does_not_autoclose_doctype,
17216 "<!DOCTYPE htmlˇ" + ">" => "<!DOCTYPE html>ˇ"
17217 );
17218
17219 check!(
17220 test_does_not_autoclose_comment,
17221 "<!-- comment --ˇ" + ">" => "<!-- comment -->ˇ"
17222 );
17223
17224 check!(
17225 test_multi_cursor_autoclose_same_tag,
17226 r#"
17227 <divˇ
17228 <divˇ
17229 "#
17230 + ">" =>
17231 r#"
17232 <div>ˇ</div>
17233 <div>ˇ</div>
17234 "#
17235 );
17236
17237 check!(
17238 test_multi_cursor_autoclose_different_tags,
17239 r#"
17240 <divˇ
17241 <spanˇ
17242 "#
17243 + ">" =>
17244 r#"
17245 <div>ˇ</div>
17246 <span>ˇ</span>
17247 "#
17248 );
17249
17250 check!(
17251 test_multi_cursor_autoclose_some_dont_autoclose_others,
17252 r#"
17253 <divˇ
17254 <div /ˇ
17255 <spanˇ</span>
17256 <!DOCTYPE htmlˇ
17257 </headˇ
17258 <Component<T>ˇ
17259 ˇ
17260 "#
17261 + ">" =>
17262 r#"
17263 <div>ˇ</div>
17264 <div />ˇ
17265 <span>ˇ</span>
17266 <!DOCTYPE html>ˇ
17267 </head>ˇ
17268 <Component<T>>ˇ</Component>
17269 >ˇ
17270 "#
17271 );
17272
17273 check!(
17274 test_doesnt_mess_up_trailing_text,
17275 "<divˇfoobar" + ">" => "<div>ˇ</div>foobar"
17276 );
17277
17278 #[gpui::test]
17279 async fn test_multibuffer(cx: &mut TestAppContext) {
17280 init_test(cx, |settings| {
17281 settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true });
17282 });
17283
17284 let buffer_a = cx.new(|cx| {
17285 let mut buf = language::Buffer::local("<div", cx);
17286 buf.set_language(
17287 Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
17288 cx,
17289 );
17290 buf
17291 });
17292 let buffer_b = cx.new(|cx| {
17293 let mut buf = language::Buffer::local("<pre", cx);
17294 buf.set_language(
17295 Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
17296 cx,
17297 );
17298 buf
17299 });
17300 let buffer_c = cx.new(|cx| {
17301 let buf = language::Buffer::local("<span", cx);
17302 buf
17303 });
17304 let buffer = cx.new(|cx| {
17305 let mut buf = MultiBuffer::new(language::Capability::ReadWrite);
17306 buf.push_excerpts(
17307 buffer_a,
17308 [ExcerptRange {
17309 context: text::Anchor::MIN..text::Anchor::MAX,
17310 primary: None,
17311 }],
17312 cx,
17313 );
17314 buf.push_excerpts(
17315 buffer_b,
17316 [ExcerptRange {
17317 context: text::Anchor::MIN..text::Anchor::MAX,
17318 primary: None,
17319 }],
17320 cx,
17321 );
17322 buf.push_excerpts(
17323 buffer_c,
17324 [ExcerptRange {
17325 context: text::Anchor::MIN..text::Anchor::MAX,
17326 primary: None,
17327 }],
17328 cx,
17329 );
17330 buf
17331 });
17332 let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17333
17334 let mut cx = EditorTestContext::for_editor(editor, cx).await;
17335
17336 cx.update_editor(|editor, window, cx| {
17337 editor.change_selections(None, window, cx, |selections| {
17338 selections.select(vec![
17339 Selection::from_offset(4),
17340 Selection::from_offset(9),
17341 Selection::from_offset(15),
17342 ])
17343 })
17344 });
17345 cx.run_until_parked();
17346
17347 cx.update_editor(|editor, window, cx| {
17348 editor.handle_input(">", window, cx);
17349 });
17350 cx.run_until_parked();
17351
17352 cx.assert_editor_state("<div>ˇ</div>\n<pre>ˇ</pre>\n<span>ˇ");
17353 }
17354}
17355
17356fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
17357 let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
17358 point..point
17359}
17360
17361fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
17362 let (text, ranges) = marked_text_ranges(marked_text, true);
17363 assert_eq!(editor.text(cx), text);
17364 assert_eq!(
17365 editor.selections.ranges(cx),
17366 ranges,
17367 "Assert selections are {}",
17368 marked_text
17369 );
17370}
17371
17372pub fn handle_signature_help_request(
17373 cx: &mut EditorLspTestContext,
17374 mocked_response: lsp::SignatureHelp,
17375) -> impl Future<Output = ()> {
17376 let mut request =
17377 cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
17378 let mocked_response = mocked_response.clone();
17379 async move { Ok(Some(mocked_response)) }
17380 });
17381
17382 async move {
17383 request.next().await;
17384 }
17385}
17386
17387/// Handle completion request passing a marked string specifying where the completion
17388/// should be triggered from using '|' character, what range should be replaced, and what completions
17389/// should be returned using '<' and '>' to delimit the range
17390pub fn handle_completion_request(
17391 cx: &mut EditorLspTestContext,
17392 marked_string: &str,
17393 completions: Vec<&'static str>,
17394 counter: Arc<AtomicUsize>,
17395) -> impl Future<Output = ()> {
17396 let complete_from_marker: TextRangeMarker = '|'.into();
17397 let replace_range_marker: TextRangeMarker = ('<', '>').into();
17398 let (_, mut marked_ranges) = marked_text_ranges_by(
17399 marked_string,
17400 vec![complete_from_marker.clone(), replace_range_marker.clone()],
17401 );
17402
17403 let complete_from_position =
17404 cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
17405 let replace_range =
17406 cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
17407
17408 let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
17409 let completions = completions.clone();
17410 counter.fetch_add(1, atomic::Ordering::Release);
17411 async move {
17412 assert_eq!(params.text_document_position.text_document.uri, url.clone());
17413 assert_eq!(
17414 params.text_document_position.position,
17415 complete_from_position
17416 );
17417 Ok(Some(lsp::CompletionResponse::Array(
17418 completions
17419 .iter()
17420 .map(|completion_text| lsp::CompletionItem {
17421 label: completion_text.to_string(),
17422 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17423 range: replace_range,
17424 new_text: completion_text.to_string(),
17425 })),
17426 ..Default::default()
17427 })
17428 .collect(),
17429 )))
17430 }
17431 });
17432
17433 async move {
17434 request.next().await;
17435 }
17436}
17437
17438fn handle_resolve_completion_request(
17439 cx: &mut EditorLspTestContext,
17440 edits: Option<Vec<(&'static str, &'static str)>>,
17441) -> impl Future<Output = ()> {
17442 let edits = edits.map(|edits| {
17443 edits
17444 .iter()
17445 .map(|(marked_string, new_text)| {
17446 let (_, marked_ranges) = marked_text_ranges(marked_string, false);
17447 let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
17448 lsp::TextEdit::new(replace_range, new_text.to_string())
17449 })
17450 .collect::<Vec<_>>()
17451 });
17452
17453 let mut request =
17454 cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17455 let edits = edits.clone();
17456 async move {
17457 Ok(lsp::CompletionItem {
17458 additional_text_edits: edits,
17459 ..Default::default()
17460 })
17461 }
17462 });
17463
17464 async move {
17465 request.next().await;
17466 }
17467}
17468
17469pub(crate) fn update_test_language_settings(
17470 cx: &mut TestAppContext,
17471 f: impl Fn(&mut AllLanguageSettingsContent),
17472) {
17473 cx.update(|cx| {
17474 SettingsStore::update_global(cx, |store, cx| {
17475 store.update_user_settings::<AllLanguageSettings>(cx, f);
17476 });
17477 });
17478}
17479
17480pub(crate) fn update_test_project_settings(
17481 cx: &mut TestAppContext,
17482 f: impl Fn(&mut ProjectSettings),
17483) {
17484 cx.update(|cx| {
17485 SettingsStore::update_global(cx, |store, cx| {
17486 store.update_user_settings::<ProjectSettings>(cx, f);
17487 });
17488 });
17489}
17490
17491pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
17492 cx.update(|cx| {
17493 assets::Assets.load_test_fonts(cx);
17494 let store = SettingsStore::test(cx);
17495 cx.set_global(store);
17496 theme::init(theme::LoadThemes::JustBase, cx);
17497 release_channel::init(SemanticVersion::default(), cx);
17498 client::init_settings(cx);
17499 language::init(cx);
17500 Project::init_settings(cx);
17501 workspace::init_settings(cx);
17502 crate::init(cx);
17503 });
17504
17505 update_test_language_settings(cx, f);
17506}
17507
17508#[track_caller]
17509fn assert_hunk_revert(
17510 not_reverted_text_with_selections: &str,
17511 expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
17512 expected_reverted_text_with_selections: &str,
17513 base_text: &str,
17514 cx: &mut EditorLspTestContext,
17515) {
17516 cx.set_state(not_reverted_text_with_selections);
17517 cx.set_head_text(base_text);
17518 cx.executor().run_until_parked();
17519
17520 let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
17521 let snapshot = editor.snapshot(window, cx);
17522 let reverted_hunk_statuses = snapshot
17523 .buffer_snapshot
17524 .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
17525 .map(|hunk| hunk.status().kind)
17526 .collect::<Vec<_>>();
17527
17528 editor.git_restore(&Default::default(), window, cx);
17529 reverted_hunk_statuses
17530 });
17531 cx.executor().run_until_parked();
17532 cx.assert_editor_state(expected_reverted_text_with_selections);
17533 assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
17534}